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