2 * Copyright (C) 2013-2015 Canonical, Ltd.
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.
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.
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/>.
18 import "../Components"
19 import Ubuntu.Components 1.3
20 import Ubuntu.Gestures 0.1
21 import Unity.Launcher 0.1
22 import Utils 0.1 as Utils
27 readonly property int ignoreHideIfMouseOverLauncher: 1
29 property bool autohideEnabled: false
30 property bool lockedVisible: false
31 property bool available: true // can be used to disable all interactions
32 property alias inverted: panel.inverted
33 property Item blurSource: null
34 property int topPanelHeight: 0
35 property bool drawerEnabled: true
36 property alias privateMode: panel.privateMode
38 property int panelWidth: units.gu(10)
39 property int dragAreaWidth: units.gu(1)
40 property real progress: dragArea.dragging && dragArea.touchPosition.x > panelWidth ?
41 (width * (dragArea.touchPosition.x-panelWidth) / (width - panelWidth)) : 0
43 property bool superPressed: false
44 property bool superTabPressed: false
46 readonly property bool dragging: dragArea.dragging
47 readonly property real dragDistance: dragArea.dragging ? dragArea.touchPosition.x : 0
48 readonly property real visibleWidth: panel.width + panel.x
49 readonly property alias shortcutHintsShown: panel.shortcutHintsShown
51 readonly property bool shown: panel.x > -panel.width
52 readonly property bool drawerShown: drawer.x == 0
54 // emitted when an application is selected
55 signal launcherApplicationSelected(string appId)
57 // emitted when the dash icon in the launcher has been tapped
62 panel.dismissTimer.stop()
64 panel.dismissTimer.restart()
68 onSuperPressedChanged: {
69 if (state == "drawer")
73 superPressTimer.start();
74 superLongPressTimer.start();
76 superPressTimer.stop();
77 superLongPressTimer.stop();
78 switchToNextState(root.lockedVisible ? "visible" : "");
79 panel.shortcutHintsShown = false;
83 onSuperTabPressedChanged: {
84 if (superTabPressed) {
85 switchToNextState("visible")
86 panel.highlightIndex = -1;
88 superPressTimer.stop();
89 superLongPressTimer.stop();
91 switchToNextState(root.lockedVisible ? "visible" : "");
93 if (panel.highlightIndex == -1) {
95 } else if (panel.highlightIndex >= 0){
96 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
98 panel.highlightIndex = -2;
102 onLockedVisibleChanged: {
103 if (lockedVisible && state == "") {
104 panel.dismissTimer.stop();
105 fadeOutAnimation.stop();
106 switchToNextState("visible")
107 } else if (!lockedVisible && state == "visible") {
112 onPanelWidthChanged: {
116 function hide(flags) {
117 if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
118 if (state == "drawer") {
119 switchToNextState("visibleTemporary");
123 if (root.lockedVisible) {
124 // Due to binding updates when switching between modes
125 // it could happen that our request to show will be overwritten
126 // with a hide request. Rewrite it when we know hiding is not allowed.
127 switchToNextState("visible")
129 switchToNextState("")
134 if (!root.lockedVisible) {
135 fadeOutAnimation.start();
139 function switchToNextState(state) {
140 animateTimer.nextState = state
141 animateTimer.start();
145 if (available && !dragArea.dragging) {
146 teaseTimer.mode = "teasing"
152 if (available && root.state == "") {
153 teaseTimer.mode = "hinting"
158 function pushEdge(amount) {
159 if (root.state === "" || root.state == "visible" || root.state == "visibleTemporary") {
160 edgeBarrier.push(amount);
164 function openForKeyboardNavigation() {
165 panel.highlightIndex = -1; // The BFB
166 drawer.focus = false;
168 switchToNextState("visible")
171 function openDrawer(focusInputField) {
172 if (!drawerEnabled) {
176 panel.shortcutHintsShown = false;
177 superPressTimer.stop();
178 superLongPressTimer.stop();
181 if (focusInputField) {
184 switchToNextState("drawer")
190 panel.highlightPrevious();
191 event.accepted = true;
195 panel.highlightNext()
197 panel.highlightPrevious();
199 event.accepted = true;
202 panel.highlightNext();
203 event.accepted = true;
207 panel.highlightPrevious();
209 panel.highlightNext();
211 event.accepted = true;
215 panel.openQuicklist(panel.highlightIndex)
216 event.accepted = true;
219 panel.highlightIndex = -2;
220 // Falling through intentionally
224 if (panel.highlightIndex == -1) {
226 } else if (panel.highlightIndex >= 0) {
227 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
230 panel.highlightIndex = -2
231 event.accepted = true;
240 switchToNextState("visible")
245 id: superLongPressTimer
248 switchToNextState("visible")
249 panel.shortcutHintsShown = true;
255 interval: mode == "teasing" ? 200 : 300
256 property string mode: "teasing"
259 // Because the animation on x is disabled while dragging
260 // switching state directly in the drag handlers would not animate
261 // the completion of the hide/reveal gesture. Lets update the state
262 // machine and switch to the final state in the next event loop run
265 objectName: "animateTimer"
267 property string nextState: ""
269 // switching to an intermediate state here to make sure all the
270 // values are restored, even if we were already in the target state
272 root.state = nextState
277 target: LauncherModel
283 onLanguageChanged: LauncherModel.refresh()
286 SequentialAnimation {
290 animateTimer.stop(); // Don't change the state behind our back
291 panel.layer.enabled = true
294 UbuntuNumberAnimation {
297 easing.type: Easing.InQuad
302 panel.layer.enabled = false
303 panel.animate = false;
305 panel.x = -panel.width
307 panel.animate = true;
315 enabled: (root.state == "visible" && !root.lockedVisible) || root.state == "drawer" || hoverEnabled
316 hoverEnabled: panel.quickListOpen
319 mouse.accepted = false;
320 panel.highlightIndex = -2;
327 enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary") && !root.lockedVisible
329 anchors.rightMargin: -units.gu(2)
337 if (panel.x < -panel.width/3) {
338 root.switchToNextState("")
340 root.switchToNextState("visible")
348 anchors.topMargin: root.inverted ? 0 : -root.topPanelHeight
349 visible: root.blurSource && drawer.x > -drawer.width
350 blurAmount: units.gu(6)
351 sourceItem: root.blurSource
352 blurRect: Qt.rect(panel.width,
354 drawer.width + drawer.x - panel.width,
355 height - root.topPanelHeight)
356 cached: drawer.moving
364 topMargin: root.inverted ? root.topPanelHeight : 0
365 bottom: parent.bottom
367 onRightMarginChanged: {
368 // Remove (and put back) the focus for the searchfield in
369 // order to hide the copy/paste popover when we move the drawer
370 var hadFocus = drawer.searchTextField.focus;
371 var oldSelectionStart = drawer.searchTextField.selectionStart;
372 var oldSelectionEnd = drawer.searchTextField.selectionEnd;
373 drawer.searchTextField.focus = false;
374 drawer.searchTextField.focus = hadFocus;
375 drawer.searchTextField.select(oldSelectionStart, oldSelectionEnd);
378 width: Math.min(root.width, units.gu(90)) * .9
379 panelWidth: panel.width
382 Behavior on anchors.rightMargin {
383 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate && !drawer.draggingHorizontally
386 easing.type: Easing.OutCubic
390 onApplicationSelected: {
392 root.launcherApplicationSelected(appId)
396 Keys.onEscapePressed: {
400 onDragDistanceChanged: {
401 anchors.rightMargin = Math.max(-drawer.width, anchors.rightMargin + dragDistance);
403 onDraggingHorizontallyChanged: {
404 if (!draggingHorizontally) {
405 if (drawer.x < -units.gu(10)) {
416 objectName: "launcherPanel"
417 enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary" || root.state == "drawer")
418 width: root.panelWidth
421 bottom: parent.bottom
424 visible: root.x > 0 || x > -width || dragArea.pressed
427 property var dismissTimer: Timer { interval: 500 }
429 target: panel.dismissTimer
431 if (root.autohideEnabled && !root.lockedVisible) {
432 if (!edgeBarrier.containsMouse && !panel.preventHiding) {
435 panel.dismissTimer.restart()
441 property bool animate: true
443 onApplicationSelected: {
444 root.hide(ignoreHideIfMouseOverLauncher);
445 launcherApplicationSelected(appId)
448 root.hide(ignoreHideIfMouseOverLauncher);
452 onPreventHidingChanged: {
453 if (panel.dismissTimer.running) {
454 panel.dismissTimer.restart();
458 onKbdNavigationCancelled: {
459 panel.highlightIndex = -2;
465 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
468 easing.type: Easing.OutCubic
472 Behavior on opacity {
474 duration: UbuntuAnimation.FastDuration; easing.type: Easing.OutCubic
483 enabled: root.available
485 if (progress > .5 && root.state != "visibleTemporary" && root.state != "drawer" && root.state != "visible") {
486 root.switchToNextState("visibleTemporary");
490 if (root.drawerEnabled) {
495 material: Component {
501 anchors.centerIn: parent
503 GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
504 GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
513 objectName: "launcherDragArea"
515 direction: Direction.Rightwards
517 enabled: root.available
518 x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
519 width: root.dragAreaWidth
522 function easeInOutCubic(t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
524 property var lastDragPoints: []
526 function dragDirection() {
527 if (lastDragPoints.length < 5) {
533 for (var i = lastDragPoints.length - 5; i < lastDragPoints.length; i++) {
534 if (toRight && lastDragPoints[i] < lastDragPoints[i-1]) {
537 if (toLeft && lastDragPoints[i] > lastDragPoints[i-1]) {
541 return toRight ? "right" : toLeft ? "left" : "unknown";
545 if (dragging && launcher.state != "visible" && launcher.state != "drawer") {
546 panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
549 if (root.drawerEnabled && dragging && launcher.state != "drawer") {
550 lastDragPoints.push(distance)
551 var drawerHintDistance = panel.width + units.gu(1)
552 if (distance < drawerHintDistance) {
553 drawer.anchors.rightMargin = -Math.min(Math.max(0, distance), drawer.width);
555 var linearDrawerX = Math.min(Math.max(0, distance - drawerHintDistance), drawer.width);
556 var linearDrawerProgress = linearDrawerX / (drawer.width)
557 var easedDrawerProgress = easeInOutCubic(linearDrawerProgress);
558 drawer.anchors.rightMargin = -(drawerHintDistance + easedDrawerProgress * (drawer.width - drawerHintDistance));
565 if (distance > panel.width / 2) {
566 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
567 root.openDrawer(false)
569 root.switchToNextState("visible");
571 } else if (root.state === "") {
572 // didn't drag far enough. rollback
573 root.switchToNextState("");
582 name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
589 anchors.rightMargin: 0
596 x: -root.x // so we never go past panelWidth, even when teased by tutorial
600 anchors.rightMargin: 0
608 anchors.rightMargin: -drawer.width + root.x // so we never go past panelWidth, even when teased by tutorial
612 name: "visibleTemporary"
616 autohideEnabled: true
621 when: teaseTimer.running && teaseTimer.mode == "teasing"
624 x: -root.panelWidth + units.gu(2)
629 when: teaseTimer.running && teaseTimer.mode == "hinting"