Unity 8
Launcher.qml
1 /*
2  * Copyright (C) 2013-2015 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  function pushEdge(amount) {
88  if (root.state === "") {
89  edgeBarrier.push(amount);
90  }
91  }
92 
93  Timer {
94  id: teaseTimer
95  interval: mode == "teasing" ? 200 : 300
96  property string mode: "teasing"
97  }
98 
99  Timer {
100  id: dismissTimer
101  objectName: "dismissTimer"
102  interval: 500
103  onTriggered: {
104  if (root.autohideEnabled) {
105  if (!panel.preventHiding) {
106  root.state = ""
107  } else {
108  dismissTimer.restart()
109  }
110  }
111  }
112  }
113 
114  // Because the animation on x is disabled while dragging
115  // switching state directly in the drag handlers would not animate
116  // the completion of the hide/reveal gesture. Lets update the state
117  // machine and switch to the final state in the next event loop run
118  Timer {
119  id: animateTimer
120  objectName: "animateTimer"
121  interval: 1
122  property string nextState: ""
123  onTriggered: {
124  // switching to an intermediate state here to make sure all the
125  // values are restored, even if we were already in the target state
126  root.state = "tmp"
127  root.state = nextState
128  }
129  }
130 
131  Connections {
132  target: LauncherModel
133  onHint: hint();
134  }
135 
136  Connections {
137  target: i18n
138  onLanguageChanged: LauncherModel.refresh()
139  }
140 
141  SequentialAnimation {
142  id: fadeOutAnimation
143  ScriptAction {
144  script: {
145  animateTimer.stop(); // Don't change the state behind our back
146  panel.layer.enabled = true
147  }
148  }
149  UbuntuNumberAnimation {
150  target: panel
151  property: "opacity"
152  easing.type: Easing.InQuad
153  to: 0
154  }
155  ScriptAction {
156  script: {
157  panel.layer.enabled = false
158  panel.animate = false;
159  root.state = "";
160  panel.x = -panel.width
161  panel.opacity = 1;
162  panel.animate = true;
163  }
164  }
165  }
166 
167  MouseArea {
168  id: launcherDragArea
169  enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary")
170  anchors.fill: panel
171  anchors.rightMargin: -units.gu(2)
172  drag {
173  axis: Drag.XAxis
174  maximumX: 0
175  target: panel
176  }
177 
178  onReleased: {
179  if (panel.x < -panel.width/3) {
180  root.switchToNextState("")
181  } else {
182  root.switchToNextState("visible")
183  }
184  }
185  }
186 
187  InverseMouseArea {
188  id: closeMouseArea
189  anchors.fill: panel
190  enabled: root.shadeBackground && root.state == "visible"
191  visible: enabled
192  onPressed: {
193  root.hide();
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  EdgeBarrier {
207  id: edgeBarrier
208  edge: Qt.LeftEdge
209  target: parent
210  enabled: root.available
211  onPassed: { root.switchToNextState("visibleTemporary"); }
212  material: Component {
213  Item {
214  Rectangle {
215  width: parent.height
216  height: parent.width
217  rotation: -90
218  anchors.centerIn: parent
219  gradient: Gradient {
220  GradientStop { position: 0.0; color: panel.color}
221  GradientStop { position: 1.0; color: Qt.rgba(panel.r,panel.g,panel.b,0)}
222  }
223  }
224  }
225  }
226  }
227 
228  LauncherPanel {
229  id: panel
230  objectName: "launcherPanel"
231  enabled: root.available && root.state == "visible" || root.state == "visibleTemporary"
232  width: root.panelWidth
233  anchors {
234  top: parent.top
235  bottom: parent.bottom
236  }
237  x: -width
238  visible: root.x > 0 || x > -width || dragArea.pressed
239  model: LauncherModel
240 
241  property bool animate: true
242 
243  onApplicationSelected: {
244  root.state = ""
245  launcherApplicationSelected(appId)
246  }
247  onShowDashHome: {
248  root.state = ""
249  root.showDashHome();
250  }
251 
252  onPreventHidingChanged: {
253  if (dismissTimer.running) {
254  dismissTimer.restart();
255  }
256  }
257 
258  Behavior on x {
259  enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
260  NumberAnimation {
261  duration: 300
262  easing.type: Easing.OutCubic
263  }
264  }
265 
266  Behavior on opacity {
267  NumberAnimation {
268  duration: UbuntuAnimation.FastDuration; easing.type: Easing.OutCubic
269  }
270  }
271  }
272 
273  DirectionalDragArea {
274  id: dragArea
275  objectName: "launcherDragArea"
276 
277  direction: Direction.Rightwards
278 
279  enabled: root.available
280  x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
281  width: root.dragAreaWidth
282  height: root.height
283 
284  onDistanceChanged: {
285  if (!dragging || launcher.state == "visible")
286  return;
287 
288  panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
289  }
290 
291  onDraggingChanged: {
292  if (!dragging) {
293  if (distance > panel.width / 2) {
294  root.switchToNextState("visible")
295  if (distance > minimizeDistance) {
296  root.dash();
297  }
298  } else if (root.state === "") {
299  // didn't drag far enough. rollback
300  root.switchToNextState("")
301  }
302  }
303  }
304  }
305 
306  states: [
307  State {
308  name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
309  PropertyChanges {
310  target: panel
311  x: -root.panelWidth
312  }
313  },
314  State {
315  name: "visible"
316  PropertyChanges {
317  target: panel
318  x: -root.x // so we never go past panelWidth, even when teased by tutorial
319  }
320  },
321  State {
322  name: "visibleTemporary"
323  extend: "visible"
324  PropertyChanges {
325  target: root
326  autohideEnabled: true
327  }
328  },
329  State {
330  name: "teasing"
331  when: teaseTimer.running && teaseTimer.mode == "teasing"
332  PropertyChanges {
333  target: panel
334  x: -root.panelWidth + units.gu(2)
335  }
336  },
337  State {
338  name: "hinting"
339  when: teaseTimer.running && teaseTimer.mode == "hinting"
340  PropertyChanges {
341  target: panel
342  x: 0
343  }
344  }
345  ]
346 }