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: 5000
97  onTriggered: {
98  if (root.autohideEnabled) {
99  if (!panel.preventHiding) {
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"
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"
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.status === DirectionalDragArea.Undecided
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  EdgeDragArea {
249  id: dragArea
250  objectName: "launcherDragArea"
251 
252  direction: Direction.Rightwards
253 
254  enabled: root.available
255  x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
256  width: root.dragAreaWidth
257  height: root.height
258 
259  onTouchXChanged: {
260  if (status !== DirectionalDragArea.Recognized || launcher.state == "visible")
261  return;
262 
263  // When the gesture finally gets recognized, the finger will likely be
264  // reasonably far from the edge. If we made the panel immediately
265  // follow the finger position it would be visually unpleasant as it
266  // would appear right next to the user's finger out of nowhere.
267  // Instead, we make the panel go towards the user's finger in several
268  // steps. ie., in an animated way.
269  var targetPanelX = Math.min(0, touchX - panel.width) - root.x
270  var delta = targetPanelX - panel.x
271  // the trick is not to go all the way (1.0) as it would cause a sudden jump
272  panel.x += 0.4 * delta
273  }
274 
275  onDraggingChanged: {
276  if (!dragging) {
277  if (distance > panel.width / 2) {
278  root.switchToNextState("visible")
279  if (distance > minimizeDistance) {
280  root.dash();
281  }
282  } else {
283  root.switchToNextState("")
284  }
285  }
286  }
287  }
288 
289  states: [
290  State {
291  name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
292  PropertyChanges {
293  target: panel
294  x: -root.panelWidth
295  }
296  },
297  State {
298  name: "visible"
299  PropertyChanges {
300  target: panel
301  x: -root.x // so we never go past panelWidth, even when teased by tutorial
302  }
303  },
304  State {
305  name: "teasing"
306  when: teaseTimer.running && teaseTimer.mode == "teasing"
307  PropertyChanges {
308  target: panel
309  x: -root.panelWidth + units.gu(2)
310  }
311  },
312  State {
313  name: "hinting"
314  when: teaseTimer.running && teaseTimer.mode == "hinting"
315  PropertyChanges {
316  target: panel
317  x: 0
318  }
319  }
320  ]
321 }