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