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/>.
20import Lomiri.Components 1.3
21import Lomiri.Gestures 0.1
22import Lomiri.Launcher 0.1
23import 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 int topPanelHeight: 0
36 property bool drawerEnabled: true
37 property alias privateMode: panel.privateMode
38 property url background
39 property bool lightMode : false
41 property int panelWidth: units.gu(10)
42 property int dragAreaWidth: units.gu(1)
43 property real progress: dragArea.dragging && dragArea.touchPosition.x > panelWidth ?
44 (width * (dragArea.touchPosition.x-panelWidth) / (width - panelWidth)) : 0
46 property bool superPressed: false
47 property bool superTabPressed: false
48 property bool takesFocus: false;
50 readonly property bool dragging: dragArea.dragging
51 readonly property real dragDistance: dragArea.dragging ? dragArea.touchPosition.x : 0
52 readonly property real visibleWidth: panel.width + panel.x
53 readonly property alias shortcutHintsShown: panel.shortcutHintsShown
55 readonly property bool shown: panel.x > -panel.width
56 readonly property bool drawerShown: drawer.x == 0
58 // emitted when an application is selected
59 signal launcherApplicationSelected(string appId)
61 // emitted when the dash icon in the launcher has been tapped
66 panel.dismissTimer.stop()
68 panel.dismissTimer.restart()
72 onFocusChanged: {if (!focus) { root.takesFocus = false; }}
74 onSuperPressedChanged: {
75 if (state == "drawer")
79 superPressTimer.start();
80 superLongPressTimer.start();
82 superPressTimer.stop();
83 superLongPressTimer.stop();
84 switchToNextState(root.lockedVisible ? "visible" : "");
85 panel.shortcutHintsShown = false;
89 onSuperTabPressedChanged: {
90 if (superTabPressed) {
91 switchToNextState("visible")
92 panel.highlightIndex = -1;
93 root.takesFocus = true;
95 superPressTimer.stop();
96 superLongPressTimer.stop();
98 switchToNextState(root.lockedVisible ? "visible" : "");
100 if (panel.highlightIndex == -1) {
102 } else if (panel.highlightIndex >= 0){
103 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
105 panel.highlightIndex = -2;
109 onLockedVisibleChanged: {
110 // We are in the progress of moving to the drawer
111 // this is caused by the user pressing the bfb on unlock
112 // in this case we want to show the drawer and not
114 if (animateTimer.nextState == "drawer")
117 if (lockedVisible && state == "") {
118 panel.dismissTimer.stop();
119 fadeOutAnimation.stop();
120 switchToNextState("visible")
121 } else if (!lockedVisible && (state == "visible" || state == "drawer")) {
126 onPanelWidthChanged: {
130 // Switches the Launcher to the visible state, but only if it's not already
132 // Prevents closing the Drawer when trying to show the Launcher.
134 if (state === "" || state === "visibleTemporary") {
135 switchToNextState("visible");
139 function hide(flags) {
140 if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
141 if (state == "drawer") {
142 switchToNextState("visibleTemporary");
146 if (root.lockedVisible) {
147 // Due to binding updates when switching between modes
148 // it could happen that our request to show will be overwritten
149 // with a hide request. Rewrite it when we know hiding is not allowed.
150 switchToNextState("visible")
152 switchToNextState("")
158 if (!root.lockedVisible) {
159 fadeOutAnimation.start();
163 function switchToNextState(state) {
164 animateTimer.nextState = state
165 animateTimer.start();
169 if (available && !dragArea.dragging) {
170 teaseTimer.mode = "teasing"
176 if (available && root.state == "") {
177 teaseTimer.mode = "hinting"
182 function pushEdge(amount) {
183 if (root.state === "" || root.state == "visible" || root.state == "visibleTemporary") {
184 edgeBarrier.push(amount);
188 function openForKeyboardNavigation() {
189 panel.highlightIndex = -1; // The BFB
190 drawer.focus = false;
191 root.takesFocus = true;
193 switchToNextState("visible")
196 function toggleDrawer(focusInputField, onlyOpen, alsoToggleLauncher) {
197 if (!drawerEnabled) {
201 panel.shortcutHintsShown = false;
202 superPressTimer.stop();
203 superLongPressTimer.stop();
204 root.takesFocus = true;
206 if (focusInputField) {
209 if (state === "drawer" && !onlyOpen)
210 if (alsoToggleLauncher && !root.lockedVisible)
211 switchToNextState("");
213 switchToNextState("visible");
215 switchToNextState("drawer");
221 panel.highlightPrevious();
222 event.accepted = true;
226 panel.highlightNext()
228 panel.highlightPrevious();
230 event.accepted = true;
233 panel.highlightNext();
234 event.accepted = true;
238 panel.highlightPrevious();
240 panel.highlightNext();
242 event.accepted = true;
246 panel.openQuicklist(panel.highlightIndex)
247 event.accepted = true;
250 panel.highlightIndex = -2;
251 // Falling through intentionally
255 if (panel.highlightIndex == -1) {
257 } else if (panel.highlightIndex >= 0) {
258 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
261 panel.highlightIndex = -2
262 event.accepted = true;
270 switchToNextState("visible")
275 id: superLongPressTimer
278 switchToNextState("visible")
279 panel.shortcutHintsShown = true;
285 interval: mode == "teasing" ? 200 : 300
286 property string mode: "teasing"
289 // Because the animation on x is disabled while dragging
290 // switching state directly in the drag handlers would not animate
291 // the completion of the hide/reveal gesture. Lets update the state
292 // machine and switch to the final state in the next event loop run
295 objectName: "animateTimer"
297 property string nextState: ""
299 // switching to an intermediate state here to make sure all the
300 // values are restored, even if we were already in the target state
302 root.state = nextState
307 target: LauncherModel
308 function onHint() { hint(); }
313 function onLanguageChanged() { LauncherModel.refresh() }
316 SequentialAnimation {
320 animateTimer.stop(); // Don't change the state behind our back
321 panel.layer.enabled = true
324 LomiriNumberAnimation {
327 easing.type: Easing.InQuad
332 panel.layer.enabled = false
333 panel.animate = false;
335 panel.x = -panel.width
337 panel.animate = true;
345 enabled: (root.state == "visible" && !root.lockedVisible) || root.state == "drawer" || hoverEnabled
346 hoverEnabled: panel.quickListOpen
349 mouse.accepted = false;
350 panel.highlightIndex = -2;
357 enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary") && !root.lockedVisible
359 anchors.rightMargin: -units.gu(2)
367 if (panel.x < -panel.width/3) {
368 root.switchToNextState("")
370 root.switchToNextState("visible")
379 width: drawer.width + drawer.x
380 height: drawer.height
386 height: drawer.height
387 visible: drawer.x > -drawer.width
388 sourceItem: root.blurSource
393 occluding: (drawer.width == root.width) && drawer.fullyOpen
398 anchors.left: drawer.right
399 anchors.top: drawer.top
400 anchors.bottom: drawer.bottom
402 visible: !drawer.fullyClosed
403 source: "../graphics/dropshadow_right@20.png"
411 topMargin: root.inverted ? root.topPanelHeight : 0
412 bottom: parent.bottom
415 background: root.background
416 width: Math.min(root.width, units.gu(81))
417 panelWidth: panel.width
418 allowSlidingAnimation: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate
419 lightMode: root.lightMode
421 onApplicationSelected: {
422 root.launcherApplicationSelected(appId)
432 root.toggleDrawer(false, true);
435 onFullyClosedChanged: {
439 drawer.unFocusInput()
446 objectName: "launcherPanel"
447 enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary" || root.state == "drawer")
448 width: root.panelWidth
451 bottom: parent.bottom
454 visible: root.x > 0 || x > -width || dragArea.pressed
455 lightMode: root.lightMode
458 property var dismissTimer: Timer { interval: 500 }
460 target: panel.dismissTimer
461 function onTriggered() {
462 if (root.state !== "drawer" && root.autohideEnabled && !root.lockedVisible) {
463 if (!edgeBarrier.containsMouse && !panel.preventHiding) {
466 panel.dismissTimer.restart()
472 property bool animate: true
474 onApplicationSelected: {
475 launcherApplicationSelected(appId);
476 root.hide(ignoreHideIfMouseOverLauncher);
479 root.hide(ignoreHideIfMouseOverLauncher);
483 onPreventHidingChanged: {
484 if (panel.dismissTimer.running) {
485 panel.dismissTimer.restart();
489 onKbdNavigationCancelled: {
490 panel.highlightIndex = -2;
496 drawer.unFocusInput()
500 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
503 easing.type: Easing.OutCubic
507 Behavior on opacity {
509 duration: LomiriAnimation.FastDuration; easing.type: Easing.OutCubic
518 enabled: root.available
520 if (progress > .5 && root.state != "visibleTemporary" && root.state != "drawer" && root.state != "visible") {
521 root.switchToNextState("visibleTemporary");
525 if (root.drawerEnabled) {
530 material: Component {
536 anchors.centerIn: parent
538 GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
539 GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
548 objectName: "launcherDragArea"
550 direction: Direction.Rightwards
552 enabled: root.available
553 x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
554 width: root.dragAreaWidth
557 function easeInOutCubic(t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
559 property var lastDragPoints: []
561 function dragDirection() {
562 if (lastDragPoints.length < 5) {
568 for (var i = lastDragPoints.length - 5; i < lastDragPoints.length; i++) {
569 if (toRight && lastDragPoints[i] < lastDragPoints[i-1]) {
572 if (toLeft && lastDragPoints[i] > lastDragPoints[i-1]) {
576 return toRight ? "right" : toLeft ? "left" : "unknown";
580 if (dragging && launcher.state != "visible" && launcher.state != "drawer") {
581 panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
584 if (root.drawerEnabled && dragging && launcher.state != "drawer") {
585 lastDragPoints.push(distance)
586 var drawerHintDistance = panel.width + units.gu(1)
587 if (distance < drawerHintDistance) {
588 drawer.anchors.rightMargin = -Math.min(Math.max(0, distance), drawer.width);
590 var linearDrawerX = Math.min(Math.max(0, distance - drawerHintDistance), drawer.width);
591 var linearDrawerProgress = linearDrawerX / (drawer.width)
592 var easedDrawerProgress = easeInOutCubic(linearDrawerProgress);
593 drawer.anchors.rightMargin = -(drawerHintDistance + easedDrawerProgress * (drawer.width - drawerHintDistance));
600 if (distance > panel.width / 2) {
601 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
602 root.toggleDrawer(false)
604 root.switchToNextState("visible");
606 } else if (root.state === "") {
607 // didn't drag far enough. rollback
608 root.switchToNextState("");
614 GestureAreaSizeHint {
621 name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
624 restoreEntryValues: false
629 restoreEntryValues: false
630 anchors.rightMargin: 0
638 restoreEntryValues: false
639 x: -root.x // so we never go past panelWidth, even when teased by tutorial
644 restoreEntryValues: false
645 anchors.rightMargin: 0
650 restoreEntryValues: false
651 autohideEnabled: false
658 restoreEntryValues: false
659 x: -root.x // so we never go past panelWidth, even when teased by tutorial
664 restoreEntryValues: false
665 anchors.rightMargin: -drawer.width + root.x // so we never go past panelWidth, even when teased by tutorial
670 name: "visibleTemporary"
674 restoreEntryValues: false
675 autohideEnabled: true
680 when: teaseTimer.running && teaseTimer.mode == "teasing"
683 restoreEntryValues: false
684 x: -root.panelWidth + units.gu(2)
689 when: teaseTimer.running && teaseTimer.mode == "hinting"
692 restoreEntryValues: false