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