Unity 8
Launcher.qml
1 /*
2  * Copyright (C) 2013-2014 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.0
18 import "../Components"
19 import Ubuntu.Components 0.1
20 import Ubuntu.Gestures 0.1
21 import Unity.Launcher 0.1
22 
23 Item {
24  id: root
25 
26  property bool autohideEnabled: false
27  property bool available: true // can be used to disable all interactions
28  property alias inverted: panel.inverted
29  property bool shadeBackground: true // can be used to disable background shade when launcher is visible
30 
31  property int panelWidth: units.gu(8)
32  property int dragAreaWidth: units.gu(1)
33  property int minimizeDistance: units.gu(26)
34  property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
35  (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
36 
37  readonly property bool dragging: dragArea.dragging
38  readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0
39  readonly property real visibleWidth: panel.width + panel.x
40 
41  readonly property bool shown: panel.x > -panel.width
42 
43  // emitted when an application is selected
44  signal launcherApplicationSelected(string appId)
45 
46  // emitted when the apps dash should be shown because of a swipe gesture
47  signal dash()
48 
49  // emitted when the dash icon in the launcher has been tapped
50  signal showDashHome()
51 
52  onStateChanged: {
53  if (state == "") {
54  dismissTimer.stop()
55  } else {
56  dismissTimer.restart()
57  }
58  }
59 
60  function hide() {
61  switchToNextState("")
62  }
63 
64  function fadeOut() {
65  fadeOutAnimation.start();
66  }
67 
68  function switchToNextState(state) {
69  animateTimer.nextState = state
70  animateTimer.start();
71  }
72 
73  function tease() {
74  if (available && !dragArea.dragging) {
75  teaseTimer.mode = "teasing"
76  teaseTimer.start();
77  }
78  }
79 
80  function hint() {
81  if (available && root.state == "") {
82  teaseTimer.mode = "hinting"
83  teaseTimer.start();
84  }
85  }
86 
87  Timer {
88  id: teaseTimer
89  interval: mode == "teasing" ? 200 : 300
90  property string mode: "teasing"
91  }
92 
93  Timer {
94  id: dismissTimer
95  objectName: "dismissTimer"
96  interval: 500
97  onTriggered: {
98  if (root.autohideEnabled) {
99  if (!panel.preventHiding && !hoverArea.containsMouse) {
100  root.state = ""
101  } else {
102  dismissTimer.restart()
103  }
104  }
105  }
106  }
107 
108  // Because the animation on x is disabled while dragging
109  // switching state directly in the drag handlers would not animate
110  // the completion of the hide/reveal gesture. Lets update the state
111  // machine and switch to the final state in the next event loop run
112  Timer {
113  id: animateTimer
114  objectName: "animateTimer"
115  interval: 1
116  property string nextState: ""
117  onTriggered: {
118  // switching to an intermediate state here to make sure all the
119  // values are restored, even if we were already in the target state
120  root.state = "tmp"
121  root.state = nextState
122  }
123  }
124 
125  Connections {
126  target: LauncherModel
127  onHint: hint();
128  }
129 
130  Connections {
131  target: i18n
132  onLanguageChanged: LauncherModel.refresh()
133  }
134 
135  SequentialAnimation {
136  id: fadeOutAnimation
137  ScriptAction {
138  script: {
139  animateTimer.stop(); // Don't change the state behind our back
140  panel.layer.enabled = true
141  }
142  }
143  UbuntuNumberAnimation {
144  target: panel
145  property: "opacity"
146  easing.type: Easing.InQuad
147  to: 0
148  }
149  ScriptAction {
150  script: {
151  panel.layer.enabled = false
152  panel.animate = false;
153  root.state = "";
154  panel.x = -panel.width
155  panel.opacity = 1;
156  panel.animate = true;
157  }
158  }
159  }
160 
161  MouseArea {
162  id: launcherDragArea
163  enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary")
164  anchors.fill: panel
165  anchors.rightMargin: -units.gu(2)
166  drag {
167  axis: Drag.XAxis
168  maximumX: 0
169  target: panel
170  }
171 
172  onReleased: {
173  if (panel.x < -panel.width/3) {
174  root.switchToNextState("")
175  } else {
176  root.switchToNextState("visible")
177  }
178  }
179 
180  }
181 
182  MultiPointTouchArea {
183  id: closeMouseArea
184  anchors {
185  left: launcherDragArea.right
186  top: parent.top
187  right: parent.right
188  bottom: parent.bottom
189  }
190  enabled: root.shadeBackground && root.state == "visible"
191  onPressed: {
192  root.state = ""
193  }
194  }
195 
196  Rectangle {
197  id: backgroundShade
198  anchors.fill: parent
199  color: "black"
200  opacity: root.shadeBackground && root.state == "visible" ? 0.6 : 0
201 
202  Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
203  }
204 
205  LauncherPanel {
206  id: panel
207  objectName: "launcherPanel"
208  enabled: root.available && root.state == "visible" || root.state == "visibleTemporary"
209  width: root.panelWidth
210  anchors {
211  top: parent.top
212  bottom: parent.bottom
213  }
214  x: -width
215  visible: root.x > 0 || x > -width || dragArea.pressed
216  model: LauncherModel
217 
218  property bool animate: true
219 
220  onApplicationSelected: {
221  root.state = ""
222  launcherApplicationSelected(appId)
223  }
224  onShowDashHome: {
225  root.state = ""
226  root.showDashHome();
227  }
228 
229  onPreventHidingChanged: {
230  if (dismissTimer.running) {
231  dismissTimer.restart();
232  }
233  }
234 
235  Behavior on x {
236  enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
237  NumberAnimation {
238  duration: 300
239  easing.type: Easing.OutCubic
240  }
241  }
242 
243  Behavior on opacity {
244  NumberAnimation {
245  duration: UbuntuAnimation.FastDuration; easing.type: Easing.OutCubic
246  }
247  }
248  }
249 
250  // TODO: This should be replaced by some mechanism that reveals the launcher
251  // after a certain resistance has been overcome, like unity7 does. However,
252  // as we don't get relative mouse coordinates yet, this will do for now.
253  MouseArea {
254  id: hoverArea
255  anchors { fill: panel; rightMargin: -1 }
256  hoverEnabled: true
257  propagateComposedEvents: true
258  onContainsMouseChanged: {
259  if (containsMouse) {
260  root.switchToNextState("visibleTemporary");
261  } else {
262  dismissTimer.restart();
263  }
264  }
265  onPressed: mouse.accepted = false;
266 
267  // We need to eat touch events here in order to make sure that
268  // we don't trigger both, the dragArea and the hoverArea
269  MultiPointTouchArea {
270  anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
271  width: units.dp(1)
272  mouseEnabled: false
273  enabled: parent.enabled
274  }
275  }
276 
277  DirectionalDragArea {
278  id: dragArea
279  objectName: "launcherDragArea"
280 
281  direction: Direction.Rightwards
282 
283  enabled: root.available
284  x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
285  width: root.dragAreaWidth
286  height: root.height
287 
288  onDistanceChanged: {
289  if (!dragging || launcher.state == "visible")
290  return;
291 
292  panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
293  }
294 
295  onDraggingChanged: {
296  if (!dragging) {
297  if (distance > panel.width / 2) {
298  root.switchToNextState("visible")
299  if (distance > minimizeDistance) {
300  root.dash();
301  }
302  } else if (root.state === "") {
303  // didn't drag far enough. rollback
304  root.switchToNextState("")
305  }
306  }
307  }
308  }
309 
310  states: [
311  State {
312  name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
313  PropertyChanges {
314  target: panel
315  x: -root.panelWidth
316  }
317  },
318  State {
319  name: "visible"
320  PropertyChanges {
321  target: panel
322  x: -root.x // so we never go past panelWidth, even when teased by tutorial
323  }
324  PropertyChanges { target: hoverArea; enabled: false }
325  },
326  State {
327  name: "visibleTemporary"
328  extend: "visible"
329  PropertyChanges {
330  target: root
331  autohideEnabled: true
332  }
333  PropertyChanges { target: hoverArea; enabled: true }
334  },
335  State {
336  name: "teasing"
337  when: teaseTimer.running && teaseTimer.mode == "teasing"
338  PropertyChanges {
339  target: panel
340  x: -root.panelWidth + units.gu(2)
341  }
342  },
343  State {
344  name: "hinting"
345  when: teaseTimer.running && teaseTimer.mode == "hinting"
346  PropertyChanges {
347  target: panel
348  x: 0
349  }
350  }
351  ]
352 }