2 * Copyright (C) 2013-2015 Canonical, Ltd.
3 * Copyright (C) 2021 UBports Foundation
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import "../Components"
20 import Ubuntu.Components 1.3
21 import Ubuntu.Gestures 0.1
22 import Unity.Launcher 0.1
23 import Utils 0.1 as Utils
28 readonly property int ignoreHideIfMouseOverLauncher: 1
30 property bool autohideEnabled: false
31 property bool lockedVisible: false
32 property bool available: true // can be used to disable all interactions
33 property alias inverted: panel.inverted
34 property Item blurSource: null
35 property bool interactiveBlur: false
36 property int topPanelHeight: 0
37 property bool drawerEnabled: true
38 property alias privateMode: panel.privateMode
39 property url background
40 property alias backgroundSourceSize: drawer.backgroundSourceSize
42 property int panelWidth: units.gu(10)
43 property int dragAreaWidth: units.gu(1)
44 property real progress: dragArea.dragging && dragArea.touchPosition.x > panelWidth ?
45 (width * (dragArea.touchPosition.x-panelWidth) / (width - panelWidth)) : 0
47 property bool superPressed: false
48 property bool superTabPressed: false
49 property bool takesFocus: false;
51 readonly property bool dragging: dragArea.dragging
52 readonly property real dragDistance: dragArea.dragging ? dragArea.touchPosition.x : 0
53 readonly property real visibleWidth: panel.width + panel.x
54 readonly property alias shortcutHintsShown: panel.shortcutHintsShown
56 readonly property bool shown: panel.x > -panel.width
57 readonly property bool drawerShown: drawer.x == 0
59 // emitted when an application is selected
60 signal launcherApplicationSelected(string appId)
62 // emitted when the dash icon in the launcher has been tapped
67 panel.dismissTimer.stop()
69 panel.dismissTimer.restart()
73 onFocusChanged: {if (!focus) { root.takesFocus = false; }}
75 onSuperPressedChanged: {
76 if (state == "drawer")
80 superPressTimer.start();
81 superLongPressTimer.start();
83 superPressTimer.stop();
84 superLongPressTimer.stop();
85 switchToNextState(root.lockedVisible ? "visible" : "");
86 panel.shortcutHintsShown = false;
90 onSuperTabPressedChanged: {
91 if (superTabPressed) {
92 switchToNextState("visible")
93 panel.highlightIndex = -1;
94 root.takesFocus = true;
96 superPressTimer.stop();
97 superLongPressTimer.stop();
99 switchToNextState(root.lockedVisible ? "visible" : "");
101 if (panel.highlightIndex == -1) {
103 } else if (panel.highlightIndex >= 0){
104 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
106 panel.highlightIndex = -2;
110 onLockedVisibleChanged: {
111 if (lockedVisible && state == "") {
112 panel.dismissTimer.stop();
113 fadeOutAnimation.stop();
114 switchToNextState("visible")
115 } else if (!lockedVisible && (state == "visible" || state == "drawer")) {
120 onPanelWidthChanged: {
124 // Switches the Launcher to the visible state, but only if it's not already
126 // Prevents closing the Drawer when trying to show the Launcher.
128 if (state === "" || state === "visibleTemporary") {
129 switchToNextState("visible");
133 function hide(flags) {
134 if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
135 if (state == "drawer") {
136 switchToNextState("visibleTemporary");
140 if (root.lockedVisible) {
141 // Due to binding updates when switching between modes
142 // it could happen that our request to show will be overwritten
143 // with a hide request. Rewrite it when we know hiding is not allowed.
144 switchToNextState("visible")
146 switchToNextState("")
152 if (!root.lockedVisible) {
153 fadeOutAnimation.start();
157 function switchToNextState(state) {
158 animateTimer.nextState = state
159 animateTimer.start();
163 if (available && !dragArea.dragging) {
164 teaseTimer.mode = "teasing"
170 if (available && root.state == "") {
171 teaseTimer.mode = "hinting"
176 function pushEdge(amount) {
177 if (root.state === "" || root.state == "visible" || root.state == "visibleTemporary") {
178 edgeBarrier.push(amount);
182 function openForKeyboardNavigation() {
183 panel.highlightIndex = -1; // The BFB
184 drawer.focus = false;
185 root.takesFocus = true;
187 switchToNextState("visible")
190 function toggleDrawer(focusInputField, onlyOpen, alsoToggleLauncher) {
191 if (!drawerEnabled) {
195 panel.shortcutHintsShown = false;
196 superPressTimer.stop();
197 superLongPressTimer.stop();
198 root.takesFocus = true;
200 if (focusInputField) {
203 if (state === "drawer" && !onlyOpen)
204 if (alsoToggleLauncher && !root.lockedVisible)
205 switchToNextState("");
207 switchToNextState("visible");
209 switchToNextState("drawer");
215 panel.highlightPrevious();
216 event.accepted = true;
220 panel.highlightNext()
222 panel.highlightPrevious();
224 event.accepted = true;
227 panel.highlightNext();
228 event.accepted = true;
232 panel.highlightPrevious();
234 panel.highlightNext();
236 event.accepted = true;
240 panel.openQuicklist(panel.highlightIndex)
241 event.accepted = true;
244 panel.highlightIndex = -2;
245 // Falling through intentionally
249 if (panel.highlightIndex == -1) {
251 } else if (panel.highlightIndex >= 0) {
252 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
255 panel.highlightIndex = -2
256 event.accepted = true;
264 switchToNextState("visible")
269 id: superLongPressTimer
272 switchToNextState("visible")
273 panel.shortcutHintsShown = true;
279 interval: mode == "teasing" ? 200 : 300
280 property string mode: "teasing"
283 // Because the animation on x is disabled while dragging
284 // switching state directly in the drag handlers would not animate
285 // the completion of the hide/reveal gesture. Lets update the state
286 // machine and switch to the final state in the next event loop run
289 objectName: "animateTimer"
291 property string nextState: ""
293 // switching to an intermediate state here to make sure all the
294 // values are restored, even if we were already in the target state
296 root.state = nextState
301 target: LauncherModel
307 onLanguageChanged: LauncherModel.refresh()
310 SequentialAnimation {
314 animateTimer.stop(); // Don't change the state behind our back
315 panel.layer.enabled = true
318 UbuntuNumberAnimation {
321 easing.type: Easing.InQuad
326 panel.layer.enabled = false
327 panel.animate = false;
329 panel.x = -panel.width
331 panel.animate = true;
339 enabled: (root.state == "visible" && !root.lockedVisible) || root.state == "drawer" || hoverEnabled
340 hoverEnabled: panel.quickListOpen
343 mouse.accepted = false;
344 panel.highlightIndex = -2;
351 enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary") && !root.lockedVisible
353 anchors.rightMargin: -units.gu(2)
361 if (panel.x < -panel.width/3) {
362 root.switchToNextState("")
364 root.switchToNextState("visible")
373 width: drawer.width + drawer.x
374 height: drawer.height
380 height: drawer.height
381 visible: root.interactiveBlur && root.blurSource && drawer.x > -drawer.width
382 sourceItem: root.blurSource
387 occluding: (drawer.width == root.width) && drawer.fullyOpen
396 topMargin: root.inverted ? root.topPanelHeight : 0
397 bottom: parent.bottom
400 background: root.background
401 width: Math.min(root.width, units.gu(81))
402 panelWidth: panel.width
403 allowSlidingAnimation: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate
404 staticBlurEnabled: !root.interactiveBlur
406 onApplicationSelected: {
407 root.launcherApplicationSelected(appId)
417 root.toggleDrawer(false, true);
420 onFullyClosedChanged: {
424 drawer.unFocusInput()
431 objectName: "launcherPanel"
432 enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary" || root.state == "drawer")
433 width: root.panelWidth
436 bottom: parent.bottom
439 visible: root.x > 0 || x > -width || dragArea.pressed
442 property var dismissTimer: Timer { interval: 500 }
444 target: panel.dismissTimer
446 if (root.state !== "drawer" && root.autohideEnabled && !root.lockedVisible) {
447 if (!edgeBarrier.containsMouse && !panel.preventHiding) {
450 panel.dismissTimer.restart()
456 property bool animate: true
458 onApplicationSelected: {
459 launcherApplicationSelected(appId);
460 root.hide(ignoreHideIfMouseOverLauncher);
463 root.hide(ignoreHideIfMouseOverLauncher);
467 onPreventHidingChanged: {
468 if (panel.dismissTimer.running) {
469 panel.dismissTimer.restart();
473 onKbdNavigationCancelled: {
474 panel.highlightIndex = -2;
480 drawer.unFocusInput()
484 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
487 easing.type: Easing.OutCubic
491 Behavior on opacity {
493 duration: UbuntuAnimation.FastDuration; easing.type: Easing.OutCubic
502 enabled: root.available
504 if (progress > .5 && root.state != "visibleTemporary" && root.state != "drawer" && root.state != "visible") {
505 root.switchToNextState("visibleTemporary");
509 if (root.drawerEnabled) {
514 material: Component {
520 anchors.centerIn: parent
522 GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
523 GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
532 objectName: "launcherDragArea"
534 direction: Direction.Rightwards
536 enabled: root.available
537 x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
538 width: root.dragAreaWidth
541 function easeInOutCubic(t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
543 property var lastDragPoints: []
545 function dragDirection() {
546 if (lastDragPoints.length < 5) {
552 for (var i = lastDragPoints.length - 5; i < lastDragPoints.length; i++) {
553 if (toRight && lastDragPoints[i] < lastDragPoints[i-1]) {
556 if (toLeft && lastDragPoints[i] > lastDragPoints[i-1]) {
560 return toRight ? "right" : toLeft ? "left" : "unknown";
564 if (dragging && launcher.state != "visible" && launcher.state != "drawer") {
565 panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
568 if (root.drawerEnabled && dragging && launcher.state != "drawer") {
569 lastDragPoints.push(distance)
570 var drawerHintDistance = panel.width + units.gu(1)
571 if (distance < drawerHintDistance) {
572 drawer.anchors.rightMargin = -Math.min(Math.max(0, distance), drawer.width);
574 var linearDrawerX = Math.min(Math.max(0, distance - drawerHintDistance), drawer.width);
575 var linearDrawerProgress = linearDrawerX / (drawer.width)
576 var easedDrawerProgress = easeInOutCubic(linearDrawerProgress);
577 drawer.anchors.rightMargin = -(drawerHintDistance + easedDrawerProgress * (drawer.width - drawerHintDistance));
584 if (distance > panel.width / 2) {
585 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
586 root.toggleDrawer(false)
588 root.switchToNextState("visible");
590 } else if (root.state === "") {
591 // didn't drag far enough. rollback
592 root.switchToNextState("");
601 name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
604 restoreEntryValues: false
609 restoreEntryValues: false
610 anchors.rightMargin: 0
618 restoreEntryValues: false
619 x: -root.x // so we never go past panelWidth, even when teased by tutorial
624 restoreEntryValues: false
625 anchors.rightMargin: 0
630 restoreEntryValues: false
631 autohideEnabled: false
638 restoreEntryValues: false
639 x: -root.x // so we never go past panelWidth, even when teased by tutorial
644 restoreEntryValues: false
645 anchors.rightMargin: -drawer.width + root.x // so we never go past panelWidth, even when teased by tutorial
650 name: "visibleTemporary"
654 restoreEntryValues: false
655 autohideEnabled: true
660 when: teaseTimer.running && teaseTimer.mode == "teasing"
663 restoreEntryValues: false
664 x: -root.panelWidth + units.gu(2)
669 when: teaseTimer.running && teaseTimer.mode == "hinting"
672 restoreEntryValues: false