@@ -45,6 +45,9 @@ AbstractStage {
4545 // Used by TutorialRight
4646 property bool spreadShown: spread.state == "altTab"
4747
48+ // used by the snap windows (edge maximize) feature
49+ readonly property alias previewRectangle: fakeRectangle
50+
4851 mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
4952
5053 // application windows never rotate independently
@@ -68,28 +71,27 @@ AbstractStage {
6871 id: maximizeWindowShortcut
6972 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
7073 onTriggered: priv.focusedAppDelegate.maximize()
71− active: priv.focusedAppDelegate !== null
74+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximized
7275 }
7376
7477 GlobalShortcut {
7578 id: maximizeWindowLeftShortcut
7679 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
7780 onTriggered: priv.focusedAppDelegate.maximizeLeft()
78− active: priv.focusedAppDelegate !== null
81+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
7982 }
8083
8184 GlobalShortcut {
8285 id: maximizeWindowRightShortcut
8386 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
8487 onTriggered: priv.focusedAppDelegate.maximizeRight()
85− active: priv.focusedAppDelegate !== null
88+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
8689 }
8790
8891 GlobalShortcut {
8992 id: minimizeRestoreShortcut
9093 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
91− onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight ||
92− priv.focusedAppDelegate.maximizedHorizontally || priv.focusedAppDelegate.maximizedVertically
94+ onTriggered: priv.focusedAppDelegate.anyMaximized
9395 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
9496 active: priv.focusedAppDelegate !== null
9597 }
@@ -206,6 +208,12 @@ AbstractStage {
206208 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
207209 }
208210
211+ Binding {
212+ target: PanelState
213+ property: "maximizedAppDelegate"
214+ value: priv.foregroundMaximizedAppDelegate ? priv.foregroundMaximizedAppDelegate : null
215+ }
216+
209217 Component.onDestruction: {
210218 PanelState.title = "";
211219 PanelState.buttonsVisible = false;
@@ -276,7 +284,7 @@ AbstractStage {
276284 value: appDelegate.requestedY -
277285 Math.min(appDelegate.requestedY - PanelState.panelHeight,
278286 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
279− when: root.oskEnabled && appDelegate.focus && appDelegate.state == "normal"
287+ when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
280288 && SurfaceManager.inputMethodSurface
281289 && SurfaceManager.inputMethodSurface.state != Mir.HiddenState
282290 && SurfaceManager.inputMethodSurface.state != Mir.MinimizedState
@@ -291,7 +299,7 @@ AbstractStage {
291299 onShellOrientationAngleChanged: {
292300 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
293301 if (application && application.rotatesWindowContents) {
294− if (state == "normal") {
302+ if (state == "normal" || state == "restored") {
295303 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
296304 angleDiff = (360 + angleDiff) % 360;
297305 if (angleDiff === 90 || angleDiff === 270) {
@@ -317,14 +325,29 @@ AbstractStage {
317325 property int requestedWidth: -1
318326 property int requestedHeight: -1
319327
320− readonly property bool maximized: windowState & WindowStateStorage.WindowStateMaximized
321− readonly property bool maximizedLeft: windowState & WindowStateStorage.WindowStateMaximizedLeft
322− readonly property bool maximizedRight: windowState & WindowStateStorage.WindowStateMaximizedRight
323− readonly property bool maximizedHorizontally: windowState & WindowStateStorage.WindowStateMaximizedHorizontally
324− readonly property bool maximizedVertically: windowState & WindowStateStorage.WindowStateMaximizedVertically
328+ readonly property bool maximized: windowState === WindowStateStorage.WindowStateMaximized
329+ readonly property bool maximizedLeft: windowState === WindowStateStorage.WindowStateMaximizedLeft
330+ readonly property bool maximizedRight: windowState === WindowStateStorage.WindowStateMaximizedRight
331+ readonly property bool maximizedHorizontally: windowState === WindowStateStorage.WindowStateMaximizedHorizontally
332+ readonly property bool maximizedVertically: windowState === WindowStateStorage.WindowStateMaximizedVertically
333+ readonly property bool maximizedTopLeft: windowState === WindowStateStorage.WindowStateMaximizedTopLeft
334+ readonly property bool maximizedTopRight: windowState === WindowStateStorage.WindowStateMaximizedTopRight
335+ readonly property bool maximizedBottomLeft: windowState === WindowStateStorage.WindowStateMaximizedBottomLeft
336+ readonly property bool maximizedBottomRight: windowState === WindowStateStorage.WindowStateMaximizedBottomRight
337+ readonly property bool anyMaximized: maximized || maximizedLeft || maximizedRight || maximizedHorizontally || maximizedVertically ||
338+ maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
339+
325340 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
326341 readonly property alias fullscreen: decoratedWindow.fullscreen
327342
343+ readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
344+ readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
345+ (maximumHeight == 0 || maximumHeight >= appContainer.height)
346+ readonly property bool canBeCornerMaximized: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
347+ (maximumHeight == 0 || maximumHeight >= appContainer.height/2)
348+ readonly property bool canBeMaximizedHorizontally: maximumWidth == 0 || maximumWidth >= appContainer.width
349+ readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
350+
328351 property int windowState: WindowStateStorage.WindowStateNormal
329352 property bool animationsEnabled: true
330353 property alias title: decoratedWindow.title
@@ -335,6 +358,7 @@ AbstractStage {
335358 readonly property var surface: model.surface
336359 readonly property alias resizeArea: resizeArea
337360 readonly property alias focusedSurface: decoratedWindow.focusedSurface
361+ readonly property var moveHandler: touchControls.overlayShown ? touchControls.moveHandler : decoratedWindow.moveHandler
338362
339363 readonly property bool isDash: model.application.appId == "unity8-dash"
340364
@@ -342,7 +366,7 @@ AbstractStage {
342366 if (spread.state == "altTab") {
343367 spread.cancel();
344368 }
345− appDelegate.restore();
369+ appDelegate.restore(true /* animated */, appDelegate.windowState);
346370 }
347371 Connections {
348372 target: model.surface
@@ -362,9 +386,6 @@ AbstractStage {
362386 }
363387
364388 onFocusChanged: {
365− if (appRepeater.startingUp)
366− return;
367−
368389 if (focus) {
369390 // If we're orphan (!parent) it means this stage is no longer the current one
370391 // and will be deleted shortly. So we should no longer have a say over the model
@@ -450,28 +471,34 @@ AbstractStage {
450471 animationsEnabled = (animated === undefined) || animated;
451472 windowState = WindowStateStorage.WindowStateMaximizedVertically;
452473 }
474+ function maximizeTopLeft(animated) {
475+ animationsEnabled = (animated === undefined) || animated;
476+ windowState = WindowStateStorage.WindowStateMaximizedTopLeft;
477+ }
478+ function maximizeTopRight(animated) {
479+ animationsEnabled = (animated === undefined) || animated;
480+ windowState = WindowStateStorage.WindowStateMaximizedTopRight;
481+ }
482+ function maximizeBottomLeft(animated) {
483+ animationsEnabled = (animated === undefined) || animated;
484+ windowState = WindowStateStorage.WindowStateMaximizedBottomLeft;
485+ }
486+ function maximizeBottomRight(animated) {
487+ animationsEnabled = (animated === undefined) || animated;
488+ windowState = WindowStateStorage.WindowStateMaximizedBottomRight;
489+ }
453490 function minimize(animated) {
454491 animationsEnabled = (animated === undefined) || animated;
455492 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
456493 }
457494 function restoreFromMaximized(animated) {
458495 animationsEnabled = (animated === undefined) || animated;
459− windowState = WindowStateStorage.WindowStateNormal;
496+ windowState = WindowStateStorage.WindowStateRestored;
460497 }
461− function restore(animated) {
498+ function restore(animated,state) {
462499 animationsEnabled = (animated === undefined) || animated;
500+ windowState = state || WindowStateStorage.WindowStateRestored;
463501 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
464− if (maximized)
465− maximize();
466− else if (maximizedLeft)
467− maximizeLeft();
468− else if (maximizedRight)
469− maximizeRight();
470− else if (maximizedHorizontally)
471− maximizeHorizontally();
472− else if (maximizedVertically)
473− maximizeVertically();
474−
475502 focus = true;
476503 }
477504
@@ -488,9 +515,14 @@ AbstractStage {
488515 duration: UbuntuAnimation.SnapDuration
489516 }
490517
518+ // unlike requestedX/Y, this is the last known grab position before being pushed against edges/corners
519+ // when restoring, the window should return to these, not to the place where it was dropped near the edge
520+ property real restoredX
521+ property real restoredY
522+
491523 states: [
492524 State {
493− name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
525+ name: "fullscreen"; when: appDelegate.fullscreen && !appDelegate.minimized
494526 PropertyChanges {
495527 target: appDelegate;
496528 x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
@@ -509,61 +541,94 @@ AbstractStage {
509541 }
510542 },
511543 State {
512− name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
544+ name: "restored";
545+ when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
546+ extend: "normal"
513547 PropertyChanges {
514548 target: appDelegate;
515− x: root.leftMargin;
516− y: 0;
517− visuallyMinimized: false;
518− visuallyMaximized: true
549+ requestedX: restoredX;
550+ requestedY: restoredY;
519551 }
552+ },
553+ State {
554+ name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
520555 PropertyChanges {
521− target: decoratedWindow
556+ target: appDelegate;
557+ requestedX: root.leftMargin;
558+ requestedY: 0;
522559 requestedWidth: appContainer.width - root.leftMargin;
523560 requestedHeight: appContainer.height;
561+ visuallyMinimized: false;
562+ visuallyMaximized: true
524563 }
525564 },
526565 State {
527566 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
528567 PropertyChanges {
529568 target: appDelegate
530− x: root.leftMargin
531− y: PanelState.panelHeight
532− }
533− PropertyChanges {
534− target: decoratedWindow
569+ requestedX: root.leftMargin
570+ requestedY: PanelState.panelHeight
535571 requestedWidth: (appContainer.width - root.leftMargin)/2
536572 requestedHeight: appContainer.height - PanelState.panelHeight
537573 }
538574 },
539575 State {
540576 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
577+ extend: "maximizedLeft"
541578 PropertyChanges {
542579 target: appDelegate;
543− x: (appContainer.width + root.leftMargin)/2
544− y: PanelState.panelHeight
580+ requestedX: (appContainer.width + root.leftMargin)/2
545581 }
582+ },
583+ State {
584+ name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
546585 PropertyChanges {
547− target: decoratedWindow
586+ target: appDelegate
587+ requestedX: root.leftMargin
588+ requestedY: PanelState.panelHeight
548589 requestedWidth: (appContainer.width - root.leftMargin)/2
549− requestedHeight: appContainer.height - PanelState.panelHeight
590+ requestedHeight: (appContainer.height - PanelState.panelHeight)/2
591+ }
592+ },
593+ State {
594+ name: "maximizedTopRight"; when: appDelegate.maximizedTopRight && !appDelegate.minimized
595+ extend: "maximizedTopLeft"
596+ PropertyChanges {
597+ target: appDelegate
598+ requestedX: (appContainer.width + root.leftMargin)/2
599+ }
600+ },
601+ State {
602+ name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
603+ PropertyChanges {
604+ target: appDelegate
605+ requestedX: root.leftMargin
606+ requestedY: (appContainer.height + PanelState.panelHeight)/2
607+ requestedWidth: (appContainer.width - root.leftMargin)/2
608+ requestedHeight: appContainer.height/2
609+ }
610+ },
611+ State {
612+ name: "maximizedBottomRight"; when: appDelegate.maximizedBottomRight && !appDelegate.minimized
613+ extend: "maximizedBottomLeft"
614+ PropertyChanges {
615+ target: appDelegate
616+ requestedX: (appContainer.width + root.leftMargin)/2
550617 }
551618 },
552619 State {
553620 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
554− PropertyChanges { target: appDelegate; x: root.leftMargin }
555− PropertyChanges { target: decoratedWindow; requestedWidth: appContainer.width - root.leftMargin }
621+ PropertyChanges { target: appDelegate; requestedX: root.leftMargin; requestedY: requestedY; requestedWidth: appContainer.width - root.leftMargin }
556622 },
557623 State {
558624 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
559− PropertyChanges { target: appDelegate; y: PanelState.panelHeight }
560− PropertyChanges { target: decoratedWindow; requestedHeight: appContainer.height - PanelState.panelHeight }
625+ PropertyChanges { target: appDelegate; requestedX: requestedX; requestedY: PanelState.panelHeight; requestedHeight: appContainer.height - PanelState.panelHeight }
561626 },
562627 State {
563628 name: "minimized"; when: appDelegate.minimized
564629 PropertyChanges {
565630 target: appDelegate;
566− x: -appDelegate.width / 2;
631+ requestedX: -appDelegate.width / 2;
567632 scale: units.gu(5) / appDelegate.width;
568633 opacity: 0;
569634 visuallyMinimized: true;
@@ -573,21 +638,17 @@ AbstractStage {
573638 ]
574639 transitions: [
575640 Transition {
576− to: "normal"
641+ to: "normal,restored"
577642 enabled: appDelegate.animationsEnabled
578643 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
579− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
580− UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
644+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
581645 },
582646 Transition {
583647 to: "minimized"
584648 enabled: appDelegate.animationsEnabled
585649 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
586650 SequentialAnimation {
587− ParallelAnimation {
588− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
589− UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
590− }
651+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
591652 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
592653 ScriptAction {
593654 script: {
@@ -604,11 +665,9 @@ AbstractStage {
604665 enabled: appDelegate.animationsEnabled
605666 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
606667 SequentialAnimation {
607− ParallelAnimation {
608− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
609− UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
610− }
668+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
611669 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
670+ ScriptAction { script: { fakeRectangle.stop(); } }
612671 }
613672 }
614673 ]
@@ -674,27 +733,38 @@ AbstractStage {
674733 surface: model.surface
675734 active: appDelegate.focus
676735 focus: true
677− maximizeButtonShown: (maximumWidth == 0 || maximumWidth >= appContainer.width) &&
678− (maximumHeight == 0 || maximumHeight >= appContainer.height)
736+ maximizeButtonShown: appDelegate.canBeMaximized
679737 overlayShown: touchControls.overlayShown
738+ stageWidth: appContainer.width
739+ stageHeight: appContainer.height
680740
681741 requestedWidth: appDelegate.requestedWidth
682742 requestedHeight: appDelegate.requestedHeight
683743
684744 onCloseClicked: { appDelegate.close(); }
685− onMaximizeClicked: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
686− || appDelegate.maximizedHorizontally || appDelegate.maximizedVertically
687− ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
745+ onMaximizeClicked: appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
688746 onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
689747 onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
690748 onMinimizeClicked: appDelegate.minimize()
691749 onDecorationPressed: { appDelegate.focus = true; }
750+ onShouldCommitSnapWindow: fakeRectangle.commit();
692751 }
693752
694753 WindowControlsOverlay {
695754 id: touchControls
696− anchors.fill: appDelegate
697755 target: appDelegate
756+ stageWidth: appContainer.width
757+ stageHeight: appContainer.height
758+
759+ onFakeMaximizeAnimationRequested: if (!appDelegate.maximized) fakeRectangle.maximize(amount, true)
760+ onFakeMaximizeLeftAnimationRequested: if (!appDelegate.maximizedLeft) fakeRectangle.maximizeLeft(amount, true)
761+ onFakeMaximizeRightAnimationRequested: if (!appDelegate.maximizedRight) fakeRectangle.maximizeRight(amount, true)
762+ onFakeMaximizeTopLeftAnimationRequested: if (!appDelegate.maximizedTopLeft) fakeRectangle.maximizeTopLeft(amount, true);
763+ onFakeMaximizeTopRightAnimationRequested: if (!appDelegate.maximizedTopRight) fakeRectangle.maximizeTopRight(amount, true);
764+ onFakeMaximizeBottomLeftAnimationRequested: if (!appDelegate.maximizedBottomLeft) fakeRectangle.maximizeBottomLeft(amount, true);
765+ onFakeMaximizeBottomRightAnimationRequested: if (!appDelegate.maximizedBottomRight) fakeRectangle.maximizeBottomRight(amount, true);
766+ onStopFakeAnimation: fakeRectangle.stop();
767+ onShouldCommitSnapWindow: fakeRectangle.commit();
698768 }
699769
700770 WindowedFullscreenPolicy {
@@ -706,6 +776,14 @@ AbstractStage {
706776 }
707777 }
708778
779+ FakeMaximizeDelegate {
780+ id: fakeRectangle
781+ target: priv.focusedAppDelegate
782+ leftMargin: root.leftMargin
783+ appContainerWidth: appContainer.width
784+ appContainerHeight: appContainer.height
785+ }
786+
709787 EdgeBarrier {
710788 id: edgeBarrier
711789