on d45fc13 help sign in
unity8/qml/Launcher/Launcher.qml
471 lines 14 kB history raw
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 */
17import QtQuick 2.4
18import "../Components"
19import Ubuntu.Components 1.3
20import Ubuntu.Gestures 0.1
21import Unity.Launcher 0.1
23FocusScope {
24 id: root
26 property bool autohideEnabled: false
27 property bool lockedVisible: false
28 property bool available: true // can be used to disable all interactions
29 property alias inverted: panel.inverted
31 property int panelWidth: units.gu(10)
32 property int dragAreaWidth: units.gu(1)
33 property int minimizeDistance: units.gu(26)
34 property real progress: dragArea.dragging && dragArea.touchPosition.x > panelWidth ?
35 (width * (dragArea.touchPosition.x-panelWidth) / (width - panelWidth)) : 0
37 property bool superPressed: false
38 property bool superTabPressed: false
40 readonly property bool dragging: dragArea.dragging
41 readonly property real dragDistance: dragArea.dragging ? dragArea.touchPosition.x : 0
42 readonly property real visibleWidth: panel.width + panel.x
43 readonly property alias shortcutHintsShown: panel.shortcutHintsShown
45 readonly property bool shown: panel.x > -panel.width
47 // emitted when an application is selected
48 signal launcherApplicationSelected(string appId)
50 // emitted when the apps dash should be shown because of a swipe gesture
51 signal dash()
53 // emitted when the dash icon in the launcher has been tapped
54 signal showDashHome()
56 onStateChanged: {
57 if (state == "") {
58 panel.dismissTimer.stop()
59 } else {
60 panel.dismissTimer.restart()
61 }
62 }
64 onSuperPressedChanged: {
65 if (superPressed) {
66 superPressTimer.start();
67 superLongPressTimer.start();
68 } else {
69 superPressTimer.stop();
70 superLongPressTimer.stop();
71 launcher.switchToNextState("");
72 panel.shortcutHintsShown = false;
73 }
74 }
76 onSuperTabPressedChanged: {
77 if (superTabPressed) {
78 switchToNextState("visible")
79 panel.highlightIndex = -1;
80 root.focus = true;
81 superPressTimer.stop();
82 superLongPressTimer.stop();
83 } else {
84 if (panel.highlightIndex == -1) {
85 showDashHome();
86 } else if (panel.highlightIndex >= 0){
87 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
88 }
89 panel.highlightIndex = -2;
90 switchToNextState("");
91 root.focus = false;
92 }
93 }
95 onLockedVisibleChanged: {
96 print("lockedVisible changed:", lockedVisible, "state:", state)
97 if (lockedVisible && state == "") {
98 panel.dismissTimer.stop();
99 fadeOutAnimation.stop();
100 switchToNextState("visible")
101 } else if (!lockedVisible && state == "visible") {
102 hide();
103 }
104 }
106 function hide() {
107 switchToNextState("")
108 }
110 function fadeOut() {
111 if (!root.lockedVisible) {
112 fadeOutAnimation.start();
113 }
114 }
116 function switchToNextState(state) {
117 animateTimer.nextState = state
118 animateTimer.start();
119 }
121 function tease() {
122 if (available && !dragArea.dragging) {
123 teaseTimer.mode = "teasing"
124 teaseTimer.start();
125 }
126 }
128 function hint() {
129 if (available && root.state == "") {
130 teaseTimer.mode = "hinting"
131 teaseTimer.start();
132 }
133 }
135 function pushEdge(amount) {
136 if (root.state === "") {
137 edgeBarrier.push(amount);
138 }
139 }
141 function openForKeyboardNavigation() {
142 panel.highlightIndex = -1; // The BFB
143 root.focus = true;
144 switchToNextState("visible")
145 }
147 Keys.onPressed: {
148 switch (event.key) {
149 case Qt.Key_Backtab:
150 panel.highlightPrevious();
151 event.accepted = true;
152 break;
153 case Qt.Key_Up:
154 if (root.inverted) {
155 panel.highlightNext()
156 } else {
157 panel.highlightPrevious();
158 }
159 event.accepted = true;
160 break;
161 case Qt.Key_Tab:
162 panel.highlightNext();
163 event.accepted = true;
164 break;
165 case Qt.Key_Down:
166 if (root.inverted) {
167 panel.highlightPrevious();
168 } else {
169 panel.highlightNext();
170 }
171 event.accepted = true;
172 break;
173 case Qt.Key_Right:
174 case Qt.Key_Menu:
175 panel.openQuicklist(panel.highlightIndex)
176 event.accepted = true;
177 break;
178 case Qt.Key_Escape:
179 panel.highlightIndex = -2;
180 // Falling through intentionally
181 case Qt.Key_Enter:
182 case Qt.Key_Return:
183 case Qt.Key_Space:
184 if (panel.highlightIndex == -1) {
185 showDashHome();
186 } else if (panel.highlightIndex >= 0) {
187 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
188 }
189 root.hide();
190 panel.highlightIndex = -2
191 event.accepted = true;
192 root.focus = false;
193 }
194 }
196 Timer {
197 id: superPressTimer
198 interval: 200
199 onTriggered: {
200 switchToNextState("visible")
201 }
202 }
204 Timer {
205 id: superLongPressTimer
206 interval: 1000
207 onTriggered: {
208 switchToNextState("visible")
209 panel.shortcutHintsShown = true;
210 }
211 }
213 Timer {
214 id: teaseTimer
215 interval: mode == "teasing" ? 200 : 300
216 property string mode: "teasing"
217 }
219 // Because the animation on x is disabled while dragging
220 // switching state directly in the drag handlers would not animate
221 // the completion of the hide/reveal gesture. Lets update the state
222 // machine and switch to the final state in the next event loop run
223 Timer {
224 id: animateTimer
225 objectName: "animateTimer"
226 interval: 1
227 property string nextState: ""
228 onTriggered: {
229 if (root.lockedVisible && nextState == "") {
230 // Due to binding updates when switching between modes
231 // it could happen that our request to show will be overwritten
232 // with a hide request. Rewrite it when we know hiding is not allowed.
233 nextState = "visible"
234 }
236 // switching to an intermediate state here to make sure all the
237 // values are restored, even if we were already in the target state
238 root.state = "tmp"
239 root.state = nextState
240 }
241 }
243 Connections {
244 target: LauncherModel
245 onHint: hint();
246 }
248 Connections {
249 target: i18n
250 onLanguageChanged: LauncherModel.refresh()
251 }
253 SequentialAnimation {
254 id: fadeOutAnimation
255 ScriptAction {
256 script: {
257 animateTimer.stop(); // Don't change the state behind our back
258 panel.layer.enabled = true
259 }
260 }
261 UbuntuNumberAnimation {
262 target: panel
263 property: "opacity"
264 easing.type: Easing.InQuad
265 to: 0
266 }
267 ScriptAction {
268 script: {
269 panel.layer.enabled = false
270 panel.animate = false;
271 root.state = "";
272 panel.x = -panel.width
273 panel.opacity = 1;
274 panel.animate = true;
275 }
276 }
277 }
279 InverseMouseArea {
280 id: closeMouseArea
281 anchors.fill: panel
282 enabled: root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
283 visible: enabled
284 onPressed: {
285 mouse.accepted = false;
286 panel.highlightIndex = -2;
287 root.hide();
288 }
289 }
291 MouseArea {
292 id: launcherDragArea
293 enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary") && !root.lockedVisible
294 anchors.fill: panel
295 anchors.rightMargin: -units.gu(2)
296 drag {
297 axis: Drag.XAxis
298 maximumX: 0
299 target: panel
300 }
302 onReleased: {
303 if (panel.x < -panel.width/3) {
304 root.switchToNextState("")
305 } else {
306 root.switchToNextState("visible")
307 }
308 }
309 }
311 EdgeBarrier {
312 id: edgeBarrier
313 edge: Qt.LeftEdge
314 target: parent
315 enabled: root.available
316 onPassed: { root.switchToNextState("visibleTemporary"); }
317 material: Component {
318 Item {
319 Rectangle {
320 width: parent.height
321 height: parent.width
322 rotation: -90
323 anchors.centerIn: parent
324 gradient: Gradient {
325 GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
326 GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
327 }
328 }
329 }
330 }
331 }
333 LauncherPanel {
334 id: panel
335 objectName: "launcherPanel"
336 enabled: root.available && root.state == "visible" || root.state == "visibleTemporary"
337 width: root.panelWidth
338 anchors {
339 top: parent.top
340 bottom: parent.bottom
341 }
342 x: -width
343 visible: root.x > 0 || x > -width || dragArea.pressed
344 model: LauncherModel
346 property var dismissTimer: Timer { interval: 500 }
347 Connections {
348 target: panel.dismissTimer
349 onTriggered: {
350 if (root.autohideEnabled && !root.lockedVisible) {
351 if (!panel.preventHiding) {
352 root.state = ""
353 } else {
354 panel.dismissTimer.restart()
355 }
356 }
357 }
358 }
360 property bool animate: true
362 onApplicationSelected: {
363 root.hide();
364 launcherApplicationSelected(appId)
365 }
366 onShowDashHome: {
367 root.hide();
368 root.showDashHome();
369 }
371 onPreventHidingChanged: {
372 if (panel.dismissTimer.running) {
373 panel.dismissTimer.restart();
374 }
375 }
377 onKbdNavigationCancelled: {
378 panel.highlightIndex = -2;
379 root.hide();
380 root.focus = false;
381 }
383 Behavior on x {
384 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
385 NumberAnimation {
386 duration: 300
387 easing.type: Easing.OutCubic
388 }
389 }
391 Behavior on opacity {
392 NumberAnimation {
393 duration: UbuntuAnimation.FastDuration; easing.type: Easing.OutCubic
394 }
395 }
396 }
398 SwipeArea {
399 id: dragArea
400 objectName: "launcherDragArea"
402 direction: Direction.Rightwards
404 enabled: root.available
405 x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
406 width: root.dragAreaWidth
407 height: root.height
409 onDistanceChanged: {
410 if (!dragging || launcher.state == "visible")
411 return;
413 panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
414 }
416 onDraggingChanged: {
417 if (!dragging) {
418 if (distance > panel.width / 2) {
419 root.switchToNextState("visible")
420 if (distance > minimizeDistance) {
421 root.dash();
422 }
423 } else if (root.state === "") {
424 // didn't drag far enough. rollback
425 root.switchToNextState("")
426 }
427 }
428 }
429 }
431 states: [
432 State {
433 name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
434 PropertyChanges {
435 target: panel
436 x: -root.panelWidth
437 }
438 },
439 State {
440 name: "visible"
441 PropertyChanges {
442 target: panel
443 x: -root.x // so we never go past panelWidth, even when teased by tutorial
444 }
445 },
446 State {
447 name: "visibleTemporary"
448 extend: "visible"
449 PropertyChanges {
450 target: root
451 autohideEnabled: true
452 }
453 },
454 State {
455 name: "teasing"
456 when: teaseTimer.running && teaseTimer.mode == "teasing"
457 PropertyChanges {
458 target: panel
459 x: -root.panelWidth + units.gu(2)
460 }
461 },
462 State {
463 name: "hinting"
464 when: teaseTimer.running && teaseTimer.mode == "hinting"
465 PropertyChanges {
466 target: panel
467 x: 0
468 }
469 }
470 ]