@@ -27,10 +27,12 @@ FocusScope {
2727 property bool lockedVisible: false
2828 property bool available: true // can be used to disable all interactions
2929 property alias inverted: panel.inverted
30+ property Item blurSource: null
31+ property int topPanelHeight: 0
32+ property bool drawerEnabled: true
3033
3134 property int panelWidth: units.gu(10)
3235 property int dragAreaWidth: units.gu(1)
33− property int minimizeDistance: units.gu(26)
3436 property real progress: dragArea.dragging && dragArea.touchPosition.x > panelWidth ?
3537 (width * (dragArea.touchPosition.x-panelWidth) / (width - panelWidth)) : 0
3638
@@ -43,13 +45,11 @@ FocusScope {
4345 readonly property alias shortcutHintsShown: panel.shortcutHintsShown
4446
4547 readonly property bool shown: panel.x > -panel.width
48+ readonly property bool drawerShown: drawer.x == 0
4649
4750 // emitted when an application is selected
4851 signal launcherApplicationSelected(string appId)
4952
50− // emitted when the apps dash should be shown because of a swipe gesture
51− signal dash()
52−
5353 // emitted when the dash icon in the launcher has been tapped
5454 signal showDashHome()
5555
@@ -62,6 +62,9 @@ FocusScope {
6262 }
6363
6464 onSuperPressedChanged: {
65+ if (state == "drawer")
66+ return;
67+
6568 if (superPressed) {
6669 superPressTimer.start();
6770 superLongPressTimer.start();
@@ -132,7 +135,7 @@ FocusScope {
132135 }
133136
134137 function pushEdge(amount) {
135− if (root.state === "") {
138+ if (root.state === "" || root.state == "visible" || root.state == "visibleTemporary") {
136139 edgeBarrier.push(amount);
137140 }
138141 }
@@ -143,6 +146,22 @@ FocusScope {
143146 switchToNextState("visible")
144147 }
145148
149+ function openDrawer(focusInputField) {
150+ if (!drawerEnabled) {
151+ return;
152+ }
153+
154+ panel.shortcutHintsShown = false;
155+ superPressTimer.stop();
156+ superLongPressTimer.stop();
157+ root.focus = true;
158+ drawer.focus = true;
159+ if (focusInputField) {
160+ drawer.focusInput();
161+ }
162+ switchToNextState("drawer")
163+ }
164+
146165 Keys.onPressed: {
147166 switch (event.key) {
148167 case Qt.Key_Backtab:
@@ -278,7 +297,7 @@ FocusScope {
278297 InverseMouseArea {
279298 id: closeMouseArea
280299 anchors.fill: panel
281− enabled: root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
300+ enabled: root.state == "visible" || root.state == "drawer"
282301 visible: enabled
283302 onPressed: {
284303 mouse.accepted = false;
@@ -307,23 +326,61 @@ FocusScope {
307326 }
308327 }
309328
310− EdgeBarrier {
311− id: edgeBarrier
312− edge: Qt.LeftEdge
313− target: parent
314− enabled: root.available
315− onPassed: { root.switchToNextState("visibleTemporary"); }
316− material: Component {
317− Item {
318− Rectangle {
319− width: parent.height
320− height: parent.width
321− rotation: -90
322− anchors.centerIn: parent
323− gradient: Gradient {
324− GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
325− GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
326− }
329+ BackgroundBlur {
330+ id: backgroundBlur
331+ anchors.fill: parent
332+ anchors.topMargin: root.inverted ? 0 : -root.topPanelHeight
333+ visible: root.blurSource && drawer.x > -drawer.width
334+ blurAmount: units.gu(6)
335+ sourceItem: root.blurSource
336+ blurRect: Qt.rect(panel.width,
337+ root.topPanelHeight,
338+ drawer.width + drawer.x - panel.width,
339+ height - root.topPanelHeight)
340+ cached: drawer.moving
341+ }
342+
343+ Drawer {
344+ id: drawer
345+ objectName: "drawer"
346+ anchors {
347+ top: parent.top
348+ topMargin: root.inverted ? root.topPanelHeight : 0
349+ bottom: parent.bottom
350+ right: parent.left
351+ }
352+ width: Math.min(root.width, units.gu(90)) * .9
353+ panelWidth: panel.width
354+ visible: x > -width
355+
356+ Behavior on anchors.rightMargin {
357+ enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate && !drawer.draggingHorizontally
358+ NumberAnimation {
359+ duration: 300
360+ easing.type: Easing.OutCubic
361+ }
362+ }
363+
364+ onApplicationSelected: {
365+ root.hide();
366+ root.launcherApplicationSelected(appId)
367+ root.focus = false;
368+ }
369+
370+ Keys.onEscapePressed: {
371+ switchToNextState("");
372+ root.focus = false;
373+ }
374+
375+ onDragDistanceChanged: {
376+ anchors.rightMargin = Math.max(-drawer.width, anchors.rightMargin + dragDistance);
377+ }
378+ onDraggingHorizontallyChanged: {
379+ if (!draggingHorizontally) {
380+ if (drawer.x < -units.gu(10)) {
381+ root.hide();
382+ } else {
383+ root.openDrawer();
327384 }
328385 }
329386 }
@@ -332,7 +389,7 @@ FocusScope {
332389 LauncherPanel {
333390 id: panel
334391 objectName: "launcherPanel"
335− enabled: root.available && root.state == "visible" || root.state == "visibleTemporary"
392+ enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary" || root.state == "drawer")
336393 width: root.panelWidth
337394 anchors {
338395 top: parent.top
@@ -394,6 +451,38 @@ FocusScope {
394451 }
395452 }
396453
454+ EdgeBarrier {
455+ id: edgeBarrier
456+ edge: Qt.LeftEdge
457+ target: parent
458+ enabled: root.available
459+ onProgressChanged: {
460+ if (progress > .5 && root.state != "visibleTemporary" && root.state != "drawer" && root.state != "visible") {
461+ root.switchToNextState("visibleTemporary");
462+ }
463+ }
464+ onPassed: {
465+ if (root.drawerEnabled) {
466+ root.switchToNextState("drawer");
467+ }
468+ }
469+
470+ material: Component {
471+ Item {
472+ Rectangle {
473+ width: parent.height
474+ height: parent.width
475+ rotation: -90
476+ anchors.centerIn: parent
477+ gradient: Gradient {
478+ GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
479+ GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
480+ }
481+ }
482+ }
483+ }
484+ }
485+
397486 SwipeArea {
398487 id: dragArea
399488 objectName: "launcherDragArea"
@@ -405,25 +494,62 @@ FocusScope {
405494 width: root.dragAreaWidth
406495 height: root.height
407496
497+ function easeInOutCubic(t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
498+
499+ property var lastDragPoints: []
500+
501+ function dragDirection() {
502+ if (lastDragPoints.length < 5) {
503+ return "unknown";
504+ }
505+
506+ var toRight = true;
507+ var toLeft = true;
508+ for (var i = lastDragPoints.length - 5; i < lastDragPoints.length; i++) {
509+ if (toRight && lastDragPoints[i] < lastDragPoints[i-1]) {
510+ toRight = false;
511+ }
512+ if (toLeft && lastDragPoints[i] > lastDragPoints[i-1]) {
513+ toLeft = false;
514+ }
515+ }
516+ return toRight ? "right" : toLeft ? "left" : "unknown";
517+ }
518+
408519 onDistanceChanged: {
409− if (!dragging || launcher.state == "visible")
410− return;
520+ if (dragging && launcher.state != "visible" && launcher.state != "drawer") {
521+ panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
522+ }
411523
412− panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
524+ if (root.drawerEnabled && dragging && launcher.state != "drawer") {
525+ lastDragPoints.push(distance)
526+ var drawerHintDistance = panel.width + units.gu(1)
527+ if (distance < drawerHintDistance) {
528+ drawer.anchors.rightMargin = -Math.min(Math.max(0, distance), drawer.width);
529+ } else {
530+ var linearDrawerX = Math.min(Math.max(0, distance - drawerHintDistance), drawer.width);
531+ var linearDrawerProgress = linearDrawerX / (drawer.width)
532+ var easedDrawerProgress = easeInOutCubic(linearDrawerProgress);
533+ drawer.anchors.rightMargin = -(drawerHintDistance + easedDrawerProgress * (drawer.width - drawerHintDistance));
534+ }
535+ }
413536 }
414537
415538 onDraggingChanged: {
416539 if (!dragging) {
417540 if (distance > panel.width / 2) {
418− root.switchToNextState("visible")
419− if (distance > minimizeDistance) {
420− root.dash();
541+ if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
542+ root.switchToNextState("drawer");
543+ root.focus = true;
544+ } else {
545+ root.switchToNextState("visible");
421546 }
422547 } else if (root.state === "") {
423548 // didn't drag far enough. rollback
424− root.switchToNextState("")
549+ root.switchToNextState("");
425550 }
426551 }
552+ lastDragPoints = [];
427553 }
428554 }
429555
@@ -434,6 +560,10 @@ FocusScope {
434560 target: panel
435561 x: -root.panelWidth
436562 }
563+ PropertyChanges {
564+ target: drawer
565+ anchors.rightMargin: 0
566+ }
437567 },
438568 State {
439569 name: "visible"
@@ -441,6 +571,18 @@ FocusScope {
441571 target: panel
442572 x: -root.x // so we never go past panelWidth, even when teased by tutorial
443573 }
574+ PropertyChanges {
575+ target: drawer
576+ anchors.rightMargin: 0
577+ }
578+ },
579+ State {
580+ name: "drawer"
581+ extend: "visible"
582+ PropertyChanges {
583+ target: drawer
584+ anchors.rightMargin: -drawer.width + root.x // so we never go past panelWidth, even when teased by tutorial
585+ }
444586 },
445587 State {
446588 name: "visibleTemporary"