@@ -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
@@ -52,6 +55,11 @@ AbstractStage {
5255
5356 orientationChangesEnabled: true
5457
58+
59+ itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.surface &&
60+ priv.focusedAppDelegate.surface.confinesMousePointer ?
61+ priv.focusedAppDelegate.clientAreaItem : null;
62+
5563 GlobalShortcut {
5664 id: showSpreadShortcut
5765 shortcut: Qt.MetaModifier|Qt.Key_W
@@ -68,28 +76,27 @@ AbstractStage {
6876 id: maximizeWindowShortcut
6977 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
7078 onTriggered: priv.focusedAppDelegate.maximize()
71− active: priv.focusedAppDelegate !== null
79+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximized
7280 }
7381
7482 GlobalShortcut {
7583 id: maximizeWindowLeftShortcut
7684 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
7785 onTriggered: priv.focusedAppDelegate.maximizeLeft()
78− active: priv.focusedAppDelegate !== null
86+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
7987 }
8088
8189 GlobalShortcut {
8290 id: maximizeWindowRightShortcut
8391 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
8492 onTriggered: priv.focusedAppDelegate.maximizeRight()
85− active: priv.focusedAppDelegate !== null
93+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
8694 }
8795
8896 GlobalShortcut {
8997 id: minimizeRestoreShortcut
9098 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
99+ onTriggered: priv.focusedAppDelegate.anyMaximized
93100 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
94101 active: priv.focusedAppDelegate !== null
95102 }
@@ -165,11 +172,6 @@ AbstractStage {
165172 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
166173 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
167174 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
168− onFocusMaximizedApp: {
169− if (priv.foregroundMaximizedAppDelegate) {
170− priv.foregroundMaximizedAppDelegate.focus = true;
171− }
172− }
173175 }
174176
175177 Binding {
@@ -240,12 +242,10 @@ AbstractStage {
240242 anchors.fill: parent
241243 focus: spread.state !== "altTab"
242244
243− CrossFadeImage {
245+ Wallpaper {
244246 id: wallpaper
245247 anchors.fill: parent
246248 source: root.background
247− sourceSize { height: root.height; width: root.width }
248− fillMode: Image.PreserveAspectCrop
249249 }
250250
251251 TopLevelSurfaceRepeater {
@@ -276,7 +276,7 @@ AbstractStage {
276276 value: appDelegate.requestedY -
277277 Math.min(appDelegate.requestedY - PanelState.panelHeight,
278278 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
279− when: root.oskEnabled && appDelegate.focus && appDelegate.state == "normal"
279+ when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
280280 && SurfaceManager.inputMethodSurface
281281 && SurfaceManager.inputMethodSurface.state != Mir.HiddenState
282282 && SurfaceManager.inputMethodSurface.state != Mir.MinimizedState
@@ -291,7 +291,7 @@ AbstractStage {
291291 onShellOrientationAngleChanged: {
292292 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
293293 if (application && application.rotatesWindowContents) {
294− if (state == "normal") {
294+ if (state == "normal" || state == "restored") {
295295 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
296296 angleDiff = (360 + angleDiff) % 360;
297297 if (angleDiff === 90 || angleDiff === 270) {
@@ -317,14 +317,29 @@ AbstractStage {
317317 property int requestedWidth: -1
318318 property int requestedHeight: -1
319319
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
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
325+ readonly property bool maximizedTopLeft: windowState === WindowStateStorage.WindowStateMaximizedTopLeft
326+ readonly property bool maximizedTopRight: windowState === WindowStateStorage.WindowStateMaximizedTopRight
327+ readonly property bool maximizedBottomLeft: windowState === WindowStateStorage.WindowStateMaximizedBottomLeft
328+ readonly property bool maximizedBottomRight: windowState === WindowStateStorage.WindowStateMaximizedBottomRight
329+ readonly property bool anyMaximized: maximized || maximizedLeft || maximizedRight || maximizedHorizontally || maximizedVertically ||
330+ maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
331+
325332 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
326333 readonly property alias fullscreen: decoratedWindow.fullscreen
327334
335+ readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
336+ readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
337+ (maximumHeight == 0 || maximumHeight >= appContainer.height)
338+ readonly property bool canBeCornerMaximized: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
339+ (maximumHeight == 0 || maximumHeight >= appContainer.height/2)
340+ readonly property bool canBeMaximizedHorizontally: maximumWidth == 0 || maximumWidth >= appContainer.width
341+ readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
342+
328343 property int windowState: WindowStateStorage.WindowStateNormal
329344 property bool animationsEnabled: true
330345 property alias title: decoratedWindow.title
@@ -335,14 +350,16 @@ AbstractStage {
335350 readonly property var surface: model.surface
336351 readonly property alias resizeArea: resizeArea
337352 readonly property alias focusedSurface: decoratedWindow.focusedSurface
353+ readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
338354
339355 readonly property bool isDash: model.application.appId == "unity8-dash"
356+ readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
340357
341358 function claimFocus() {
342359 if (spread.state == "altTab") {
343360 spread.cancel();
344361 }
345− appDelegate.restore();
362+ appDelegate.restore(true /* animated */, appDelegate.windowState);
346363 }
347364 Connections {
348365 target: model.surface
@@ -450,28 +467,34 @@ AbstractStage {
450467 animationsEnabled = (animated === undefined) || animated;
451468 windowState = WindowStateStorage.WindowStateMaximizedVertically;
452469 }
470+ function maximizeTopLeft(animated) {
471+ animationsEnabled = (animated === undefined) || animated;
472+ windowState = WindowStateStorage.WindowStateMaximizedTopLeft;
473+ }
474+ function maximizeTopRight(animated) {
475+ animationsEnabled = (animated === undefined) || animated;
476+ windowState = WindowStateStorage.WindowStateMaximizedTopRight;
477+ }
478+ function maximizeBottomLeft(animated) {
479+ animationsEnabled = (animated === undefined) || animated;
480+ windowState = WindowStateStorage.WindowStateMaximizedBottomLeft;
481+ }
482+ function maximizeBottomRight(animated) {
483+ animationsEnabled = (animated === undefined) || animated;
484+ windowState = WindowStateStorage.WindowStateMaximizedBottomRight;
485+ }
453486 function minimize(animated) {
454487 animationsEnabled = (animated === undefined) || animated;
455488 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
456489 }
457490 function restoreFromMaximized(animated) {
458491 animationsEnabled = (animated === undefined) || animated;
459− windowState = WindowStateStorage.WindowStateNormal;
492+ windowState = WindowStateStorage.WindowStateRestored;
460493 }
461− function restore(animated) {
494+ function restore(animated,state) {
462495 animationsEnabled = (animated === undefined) || animated;
496+ windowState = state || WindowStateStorage.WindowStateRestored;
463497 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−
475498 focus = true;
476499 }
477500
@@ -488,9 +511,14 @@ AbstractStage {
488511 duration: UbuntuAnimation.SnapDuration
489512 }
490513
514+ // unlike requestedX/Y, this is the last known grab position before being pushed against edges/corners
515+ // when restoring, the window should return to these, not to the place where it was dropped near the edge
516+ property real restoredX
517+ property real restoredY
518+
491519 states: [
492520 State {
493− name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
521+ name: "fullscreen"; when: appDelegate.fullscreen && !appDelegate.minimized
494522 PropertyChanges {
495523 target: appDelegate;
496524 x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
@@ -509,61 +537,94 @@ AbstractStage {
509537 }
510538 },
511539 State {
512− name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
540+ name: "restored";
541+ when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
542+ extend: "normal"
513543 PropertyChanges {
514544 target: appDelegate;
515− x: root.leftMargin;
516− y: 0;
517− visuallyMinimized: false;
518− visuallyMaximized: true
545+ requestedX: restoredX;
546+ requestedY: restoredY;
519547 }
548+ },
549+ State {
550+ name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
520551 PropertyChanges {
521− target: decoratedWindow
552+ target: appDelegate;
553+ requestedX: root.leftMargin;
554+ requestedY: 0;
522555 requestedWidth: appContainer.width - root.leftMargin;
523556 requestedHeight: appContainer.height;
557+ visuallyMinimized: false;
558+ visuallyMaximized: true
524559 }
525560 },
526561 State {
527562 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
528563 PropertyChanges {
529564 target: appDelegate
530− x: root.leftMargin
531− y: PanelState.panelHeight
532− }
533− PropertyChanges {
534− target: decoratedWindow
565+ requestedX: root.leftMargin
566+ requestedY: PanelState.panelHeight
535567 requestedWidth: (appContainer.width - root.leftMargin)/2
536568 requestedHeight: appContainer.height - PanelState.panelHeight
537569 }
538570 },
539571 State {
540572 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
573+ extend: "maximizedLeft"
541574 PropertyChanges {
542575 target: appDelegate;
543− x: (appContainer.width + root.leftMargin)/2
544− y: PanelState.panelHeight
576+ requestedX: (appContainer.width + root.leftMargin)/2
545577 }
578+ },
579+ State {
580+ name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
546581 PropertyChanges {
547− target: decoratedWindow
582+ target: appDelegate
583+ requestedX: root.leftMargin
584+ requestedY: PanelState.panelHeight
548585 requestedWidth: (appContainer.width - root.leftMargin)/2
549− requestedHeight: appContainer.height - PanelState.panelHeight
586+ requestedHeight: (appContainer.height - PanelState.panelHeight)/2
587+ }
588+ },
589+ State {
590+ name: "maximizedTopRight"; when: appDelegate.maximizedTopRight && !appDelegate.minimized
591+ extend: "maximizedTopLeft"
592+ PropertyChanges {
593+ target: appDelegate
594+ requestedX: (appContainer.width + root.leftMargin)/2
595+ }
596+ },
597+ State {
598+ name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
599+ PropertyChanges {
600+ target: appDelegate
601+ requestedX: root.leftMargin
602+ requestedY: (appContainer.height + PanelState.panelHeight)/2
603+ requestedWidth: (appContainer.width - root.leftMargin)/2
604+ requestedHeight: appContainer.height/2
605+ }
606+ },
607+ State {
608+ name: "maximizedBottomRight"; when: appDelegate.maximizedBottomRight && !appDelegate.minimized
609+ extend: "maximizedBottomLeft"
610+ PropertyChanges {
611+ target: appDelegate
612+ requestedX: (appContainer.width + root.leftMargin)/2
550613 }
551614 },
552615 State {
553616 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
554− PropertyChanges { target: appDelegate; x: root.leftMargin }
555− PropertyChanges { target: decoratedWindow; requestedWidth: appContainer.width - root.leftMargin }
617+ PropertyChanges { target: appDelegate; requestedX: root.leftMargin; requestedY: requestedY; requestedWidth: appContainer.width - root.leftMargin }
556618 },
557619 State {
558620 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
559− PropertyChanges { target: appDelegate; y: PanelState.panelHeight }
560− PropertyChanges { target: decoratedWindow; requestedHeight: appContainer.height - PanelState.panelHeight }
621+ PropertyChanges { target: appDelegate; requestedX: requestedX; requestedY: PanelState.panelHeight; requestedHeight: appContainer.height - PanelState.panelHeight }
561622 },
562623 State {
563624 name: "minimized"; when: appDelegate.minimized
564625 PropertyChanges {
565626 target: appDelegate;
566− x: -appDelegate.width / 2;
627+ requestedX: -appDelegate.width / 2;
567628 scale: units.gu(5) / appDelegate.width;
568629 opacity: 0;
569630 visuallyMinimized: true;
@@ -573,21 +634,17 @@ AbstractStage {
573634 ]
574635 transitions: [
575636 Transition {
576− to: "normal"
637+ to: "normal,restored"
577638 enabled: appDelegate.animationsEnabled
578639 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 }
640+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
581641 },
582642 Transition {
583643 to: "minimized"
584644 enabled: appDelegate.animationsEnabled
585645 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
586646 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− }
647+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
591648 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
592649 ScriptAction {
593650 script: {
@@ -604,11 +661,9 @@ AbstractStage {
604661 enabled: appDelegate.animationsEnabled
605662 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
606663 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− }
664+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
611665 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
666+ ScriptAction { script: { fakeRectangle.stop(); } }
612667 }
613668 }
614669 ]
@@ -674,27 +729,38 @@ AbstractStage {
674729 surface: model.surface
675730 active: appDelegate.focus
676731 focus: true
677− maximizeButtonShown: (maximumWidth == 0 || maximumWidth >= appContainer.width) &&
678− (maximumHeight == 0 || maximumHeight >= appContainer.height)
732+ maximizeButtonShown: appDelegate.canBeMaximized
679733 overlayShown: touchControls.overlayShown
734+ stageWidth: appContainer.width
735+ stageHeight: appContainer.height
680736
681737 requestedWidth: appDelegate.requestedWidth
682738 requestedHeight: appDelegate.requestedHeight
683739
684740 onCloseClicked: { appDelegate.close(); }
685− onMaximizeClicked: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
686− || appDelegate.maximizedHorizontally || appDelegate.maximizedVertically
687− ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
741+ onMaximizeClicked: appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
688742 onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
689743 onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
690744 onMinimizeClicked: appDelegate.minimize()
691745 onDecorationPressed: { appDelegate.focus = true; }
746+ onDecorationReleased: fakeRectangle.commit();
692747 }
693748
694749 WindowControlsOverlay {
695750 id: touchControls
696− anchors.fill: appDelegate
697751 target: appDelegate
752+ stageWidth: appContainer.width
753+ stageHeight: appContainer.height
754+
755+ onFakeMaximizeAnimationRequested: if (!appDelegate.maximized) fakeRectangle.maximize(amount, true)
756+ onFakeMaximizeLeftAnimationRequested: if (!appDelegate.maximizedLeft) fakeRectangle.maximizeLeft(amount, true)
757+ onFakeMaximizeRightAnimationRequested: if (!appDelegate.maximizedRight) fakeRectangle.maximizeRight(amount, true)
758+ onFakeMaximizeTopLeftAnimationRequested: if (!appDelegate.maximizedTopLeft) fakeRectangle.maximizeTopLeft(amount, true);
759+ onFakeMaximizeTopRightAnimationRequested: if (!appDelegate.maximizedTopRight) fakeRectangle.maximizeTopRight(amount, true);
760+ onFakeMaximizeBottomLeftAnimationRequested: if (!appDelegate.maximizedBottomLeft) fakeRectangle.maximizeBottomLeft(amount, true);
761+ onFakeMaximizeBottomRightAnimationRequested: if (!appDelegate.maximizedBottomRight) fakeRectangle.maximizeBottomRight(amount, true);
762+ onStopFakeAnimation: fakeRectangle.stop();
763+ onDragReleased: fakeRectangle.commit();
698764 }
699765
700766 WindowedFullscreenPolicy {
@@ -706,6 +772,14 @@ AbstractStage {
706772 }
707773 }
708774
775+ FakeMaximizeDelegate {
776+ id: fakeRectangle
777+ target: priv.focusedAppDelegate
778+ leftMargin: root.leftMargin
779+ appContainerWidth: appContainer.width
780+ appContainerHeight: appContainer.height
781+ }
782+
709783 EdgeBarrier {
710784 id: edgeBarrier
711785