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.4
18 import "../Components"
19 import Ubuntu.Components 1.3
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  visible: enabled // otherwise it will get in the way of cursor selection for some reason
192  onPressed: {
193  root.state = ""
194  }
195  }
196 
197  Rectangle {
198  id: backgroundShade
199  anchors.fill: parent
200  color: "black"
201  opacity: root.shadeBackground && root.state == "visible" ? 0.6 : 0
202 
203  Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
204  }
205 
206  LauncherPanel {
207  id: panel
208  objectName: "launcherPanel"
209  enabled: root.available && root.state == "visible" || root.state == "visibleTemporary"
210  width: root.panelWidth
211  anchors {
212  top: parent.top
213  bottom: parent.bottom
214  }
215  x: -width
216  visible: root.x > 0 || x > -width || dragArea.pressed
217  model: LauncherModel
218 
219  property bool animate: true
220 
221  onApplicationSelected: {
222  root.state = ""
223  launcherApplicationSelected(appId)
224  }
225  onShowDashHome: {
226  root.state = ""
227  root.showDashHome();
228  }
229 
230  onPreventHidingChanged: {
231  if (dismissTimer.running) {
232  dismissTimer.restart();
233  }
234  }
235 
236  Behavior on x {
237  enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
238  NumberAnimation {
239  duration: 300
240  easing.type: Easing.OutCubic
241  }
242  }
243 
244  Behavior on opacity {
245  NumberAnimation {
246  duration: UbuntuAnimation.FastDuration; easing.type: Easing.OutCubic
247  }
248  }
249  }
250 
251  // TODO: This should be replaced by some mechanism that reveals the launcher
252  // after a certain resistance has been overcome, like unity7 does. However,
253  // as we don't get relative mouse coordinates yet, this will do for now.
254  MouseArea {
255  id: hoverArea
256  anchors { fill: panel; rightMargin: -1 }
257  hoverEnabled: true
258  propagateComposedEvents: true
259  onContainsMouseChanged: {
260  if (containsMouse) {
261  root.switchToNextState("visibleTemporary");
262  } else {
263  dismissTimer.restart();
264  }
265  }
266  onPressed: mouse.accepted = false;
267 
268  // We need to eat touch events here in order to make sure that
269  // we don't trigger both, the dragArea and the hoverArea
270  MultiPointTouchArea {
271  anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
272  width: units.dp(1)
273  mouseEnabled: false
274  enabled: parent.enabled
275  }
276  }
277 
278  DirectionalDragArea {
279  id: dragArea
280  objectName: "launcherDragArea"
281 
282  direction: Direction.Rightwards
283 
284  enabled: root.available
285  x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
286  width: root.dragAreaWidth
287  height: root.height
288 
289  onDistanceChanged: {
290  if (!dragging || launcher.state == "visible")
291  return;
292 
293  panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
294  }
295 
296  onDraggingChanged: {
297  if (!dragging) {
298  if (distance > panel.width / 2) {
299  root.switchToNextState("visible")
300  if (distance > minimizeDistance) {
301  root.dash();
302  }
303  } else if (root.state === "") {
304  // didn't drag far enough. rollback
305  root.switchToNextState("")
306  }
307  }
308  }
309  }
310 
311  states: [
312  State {
313  name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
314  PropertyChanges {
315  target: panel
316  x: -root.panelWidth
317  }
318  },
319  State {
320  name: "visible"
321  PropertyChanges {
322  target: panel
323  x: -root.x // so we never go past panelWidth, even when teased by tutorial
324  }
325  PropertyChanges { target: hoverArea; enabled: false }
326  },
327  State {
328  name: "visibleTemporary"
329  extend: "visible"
330  PropertyChanges {
331  target: root
332  autohideEnabled: true
333  }
334  PropertyChanges { target: hoverArea; enabled: true }
335  },
336  State {
337  name: "teasing"
338  when: teaseTimer.running && teaseTimer.mode == "teasing"
339  PropertyChanges {
340  target: panel
341  x: -root.panelWidth + units.gu(2)
342  }
343  },
344  State {
345  name: "hinting"
346  when: teaseTimer.running && teaseTimer.mode == "hinting"
347  PropertyChanges {
348  target: panel
349  x: 0
350  }
351  }
352  ]
353 }