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 int topPanelHeight: 0
34 property bool drawerEnabled: true
35 property alias privateMode: panel.privateMode
36 property url background
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
45 property bool takesFocus: false;
47 readonly property bool dragging: dragArea.dragging
48 readonly property real dragDistance: dragArea.dragging ? dragArea.touchPosition.x : 0
49 readonly property real visibleWidth: panel.width + panel.x
50 readonly property alias shortcutHintsShown: panel.shortcutHintsShown
52 readonly property bool shown: panel.x > -panel.width
53 readonly property bool drawerShown: drawer.x == 0
55 // emitted when an application is selected
56 signal launcherApplicationSelected(string appId)
58 // emitted when the dash icon in the launcher has been tapped
63 panel.dismissTimer.stop()
65 panel.dismissTimer.restart()
69 onFocusChanged: {if (!focus) { root.takesFocus = false; }}
71 onSuperPressedChanged: {
72 if (state == "drawer")
76 superPressTimer.start();
77 superLongPressTimer.start();
79 superPressTimer.stop();
80 superLongPressTimer.stop();
81 switchToNextState(root.lockedVisible ? "visible" : "");
82 panel.shortcutHintsShown = false;
86 onSuperTabPressedChanged: {
87 if (superTabPressed) {
88 switchToNextState("visible")
89 panel.highlightIndex = -1;
90 root.takesFocus = true;
92 superPressTimer.stop();
93 superLongPressTimer.stop();
95 switchToNextState(root.lockedVisible ? "visible" : "");
97 if (panel.highlightIndex == -1) {
99 } else if (panel.highlightIndex >= 0){
100 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
102 panel.highlightIndex = -2;
106 onLockedVisibleChanged: {
107 // We are in the progress of moving to the drawer
108 // this is caused by the user pressing the bfb on unlock
109 // in this case we want to show the drawer and not
111 if (animateTimer.nextState == "drawer")
114 if (lockedVisible && state == "") {
115 panel.dismissTimer.stop();
116 fadeOutAnimation.stop();
117 switchToNextState("visible")
118 } else if (!lockedVisible && (state == "visible" || state == "drawer")) {
123 onPanelWidthChanged: {
127 // Switches the Launcher to the visible state, but only if it's not already
129 // Prevents closing the Drawer when trying to show the Launcher.
131 if (state === "" || state === "visibleTemporary") {
132 switchToNextState("visible");
136 function hide(flags) {
137 if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
138 if (state == "drawer") {
139 switchToNextState("visibleTemporary");
143 if (root.lockedVisible) {
144 // Due to binding updates when switching between modes
145 // it could happen that our request to show will be overwritten
146 // with a hide request. Rewrite it when we know hiding is not allowed.
147 switchToNextState("visible")
149 switchToNextState("")
155 if (!root.lockedVisible) {
156 fadeOutAnimation.start();
160 function switchToNextState(state) {
161 animateTimer.nextState = state
162 animateTimer.start();
166 if (available && !dragArea.dragging) {
167 teaseTimer.mode = "teasing"
173 if (available && root.state == "") {
174 teaseTimer.mode = "hinting"
179 function pushEdge(amount) {
180 if (root.state === "" || root.state == "visible" || root.state == "visibleTemporary") {
181 edgeBarrier.push(amount);
185 function openForKeyboardNavigation() {
186 panel.highlightIndex = -1; // The BFB
187 drawer.focus = false;
188 root.takesFocus = true;
190 switchToNextState("visible")
193 function toggleDrawer(focusInputField, onlyOpen, alsoToggleLauncher) {
194 if (!drawerEnabled) {
198 panel.shortcutHintsShown = false;
199 superPressTimer.stop();
200 superLongPressTimer.stop();
201 root.takesFocus = true;
203 if (focusInputField) {
206 if (state === "drawer" && !onlyOpen)
207 if (alsoToggleLauncher && !root.lockedVisible)
208 switchToNextState("");
210 switchToNextState("visible");
212 switchToNextState("drawer");
218 panel.highlightPrevious();
219 event.accepted = true;
223 panel.highlightNext()
225 panel.highlightPrevious();
227 event.accepted = true;
230 panel.highlightNext();
231 event.accepted = true;
235 panel.highlightPrevious();
237 panel.highlightNext();
239 event.accepted = true;
243 panel.openQuicklist(panel.highlightIndex)
244 event.accepted = true;
247 panel.highlightIndex = -2;
248 // Falling through intentionally
252 if (panel.highlightIndex == -1) {
254 } else if (panel.highlightIndex >= 0) {
255 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
258 panel.highlightIndex = -2
259 event.accepted = true;
267 switchToNextState("visible")
272 id: superLongPressTimer
275 switchToNextState("visible")
276 panel.shortcutHintsShown = true;
282 interval: mode == "teasing" ? 200 : 300
283 property string mode: "teasing"
286 // Because the animation on x is disabled while dragging
287 // switching state directly in the drag handlers would not animate
288 // the completion of the hide/reveal gesture. Lets update the state
289 // machine and switch to the final state in the next event loop run
292 objectName: "animateTimer"
294 property string nextState: ""
296 // switching to an intermediate state here to make sure all the
297 // values are restored, even if we were already in the target state
299 root.state = nextState
304 target: LauncherModel
310 onLanguageChanged: LauncherModel.refresh()
313 SequentialAnimation {
317 animateTimer.stop(); // Don't change the state behind our back
318 panel.layer.enabled = true
321 UbuntuNumberAnimation {
324 easing.type: Easing.InQuad
329 panel.layer.enabled = false
330 panel.animate = false;
332 panel.x = -panel.width
334 panel.animate = true;
342 enabled: (root.state == "visible" && !root.lockedVisible) || root.state == "drawer" || hoverEnabled
343 hoverEnabled: panel.quickListOpen
346 mouse.accepted = false;
347 panel.highlightIndex = -2;
354 enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary") && !root.lockedVisible
356 anchors.rightMargin: -units.gu(2)
364 if (panel.x < -panel.width/3) {
365 root.switchToNextState("")
367 root.switchToNextState("visible")
377 topMargin: root.inverted ? root.topPanelHeight : 0
378 bottom: parent.bottom
381 background: root.background
382 width: Math.min(root.width, units.gu(81))
383 panelWidth: panel.width
384 allowSlidingAnimation: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate
386 onApplicationSelected: {
387 root.launcherApplicationSelected(appId)
397 root.toggleDrawer(false, true);
403 objectName: "launcherPanel"
404 enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary" || root.state == "drawer")
405 width: root.panelWidth
408 bottom: parent.bottom
411 visible: root.x > 0 || x > -width || dragArea.pressed
414 property var dismissTimer: Timer { interval: 500 }
416 target: panel.dismissTimer
418 if (root.autohideEnabled && !root.lockedVisible) {
419 if (!edgeBarrier.containsMouse && !panel.preventHiding) {
422 panel.dismissTimer.restart()
428 property bool animate: true
430 onApplicationSelected: {
431 launcherApplicationSelected(appId);
432 root.hide(ignoreHideIfMouseOverLauncher);
435 root.hide(ignoreHideIfMouseOverLauncher);
439 onPreventHidingChanged: {
440 if (panel.dismissTimer.running) {
441 panel.dismissTimer.restart();
445 onKbdNavigationCancelled: {
446 panel.highlightIndex = -2;
452 drawer.unFocusInput()
456 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
459 easing.type: Easing.OutCubic
463 Behavior on opacity {
465 duration: UbuntuAnimation.FastDuration; easing.type: Easing.OutCubic
474 enabled: root.available
476 if (progress > .5 && root.state != "visibleTemporary" && root.state != "drawer" && root.state != "visible") {
477 root.switchToNextState("visibleTemporary");
481 if (root.drawerEnabled) {
486 material: Component {
492 anchors.centerIn: parent
494 GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
495 GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
504 objectName: "launcherDragArea"
506 direction: Direction.Rightwards
508 enabled: root.available
509 x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
510 width: root.dragAreaWidth
513 function easeInOutCubic(t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
515 property var lastDragPoints: []
517 function dragDirection() {
518 if (lastDragPoints.length < 5) {
524 for (var i = lastDragPoints.length - 5; i < lastDragPoints.length; i++) {
525 if (toRight && lastDragPoints[i] < lastDragPoints[i-1]) {
528 if (toLeft && lastDragPoints[i] > lastDragPoints[i-1]) {
532 return toRight ? "right" : toLeft ? "left" : "unknown";
536 if (dragging && launcher.state != "visible" && launcher.state != "drawer") {
537 panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
540 if (root.drawerEnabled && dragging && launcher.state != "drawer") {
541 lastDragPoints.push(distance)
542 var drawerHintDistance = panel.width + units.gu(1)
543 if (distance < drawerHintDistance) {
544 drawer.anchors.rightMargin = -Math.min(Math.max(0, distance), drawer.width);
546 var linearDrawerX = Math.min(Math.max(0, distance - drawerHintDistance), drawer.width);
547 var linearDrawerProgress = linearDrawerX / (drawer.width)
548 var easedDrawerProgress = easeInOutCubic(linearDrawerProgress);
549 drawer.anchors.rightMargin = -(drawerHintDistance + easedDrawerProgress * (drawer.width - drawerHintDistance));
556 if (distance > panel.width / 2) {
557 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
558 root.toggleDrawer(false)
560 root.switchToNextState("visible");
562 } else if (root.state === "") {
563 // didn't drag far enough. rollback
564 root.switchToNextState("");
573 name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
580 anchors.rightMargin: 0
588 x: -root.x // so we never go past panelWidth, even when teased by tutorial
593 anchors.rightMargin: 0
602 anchors.rightMargin: -drawer.width + root.x // so we never go past panelWidth, even when teased by tutorial
610 name: "visibleTemporary"
614 autohideEnabled: true
619 when: teaseTimer.running && teaseTimer.mode == "teasing"
622 x: -root.panelWidth + units.gu(2)
627 when: teaseTimer.running && teaseTimer.mode == "hinting"