on xenial help sign in
merge lp:~lukas-kde/unity8/edgeMaximize
commit5953e33cdec076365ba8e6cbe5f4a10fc0fbe7a1 authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateWed 31 Aug 2016, 10:16 (10 years ago) parentbf2cf1fcee9f51
24 files changed+854−213
plugins/Cursor/CMakeLists.txt+2−2 plugins/Cursor/MousePointer.cpp+42−14 plugins/Cursor/MousePointer.h+15 plugins/Ubuntu/Gestures/TouchGestureArea.h+2−2 plugins/Utils/windowstatestorage.cpp+5−4 plugins/Utils/windowstatestorage.h+8−3 qml/Components/PanelState/PanelState.qml+1 qml/Components/WindowControlButtons.qml−1 qml/OrientedShell.qml+1−1 qml/Panel/Panel.qml+22−6 qml/Shell.qml+40 qml/Stages/DecoratedWindow.qml+21 qml/Stages/DesktopStage.qml+145−67 qml/Stages/FakeMaximizeDelegate.qml+235 qml/Stages/MoveHandler.qml+183 qml/Stages/WindowControlsOverlay.qml+76−38 qml/Stages/WindowDecoration.qml+12−41 qml/Stages/WindowResizeArea.qml+13−24 tests/mocks/Cursor/CMakeLists.txt+1−1 tests/mocks/Cursor/Cursor.qml+9−1 tests/mocks/Utils/windowstatestorage.cpp+2−2 tests/mocks/Utils/windowstatestorage.h+8−3 tests/qmltests/Stages/tst_DesktopStage.qml+2−2 tests/qmltests/Stages/tst_WindowResizeArea.qml+9−1
plugins/Cursor/CMakeLists.txt+2−2
@@ -28,6 +28,6 @@ target_link_libraries(Cursor-qml
2828 ${QT5PLATFORM_SUPPORT_LDFLAGS}
2929)
3030
31−qt5_use_modules(Cursor-qml Qml Quick DBus Network Gui Sql Concurrent Svg)
31+qt5_use_modules(Cursor-qml Qml Quick Svg)
3232
33−add_unity8_plugin(Cursor 1.0 Cursor TARGETS Cursor-qml)
33+add_unity8_plugin(Cursor 1.1 Cursor TARGETS Cursor-qml)
plugins/Cursor/MousePointer.cpp+42−14
@@ -22,6 +22,8 @@
2222
2323#include <QQuickWindow>
2424#include <QGuiApplication>
25+#include <QQmlProperty>
26+#include <QtMath>
2527
2628#include <qpa/qwindowsysteminterface.h>
2729
@@ -48,23 +50,35 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
4850 QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
4951 m_accumulatedMovement -= appliedMovement;
5052
51− qreal newX = x() + appliedMovement.x();
52− if (newX < 0) {
53+ const qreal newX = x() + appliedMovement.x();
54+ const qreal newY = y() + appliedMovement.y();
55+ const qreal sceneWidth = parentItem()->width();
56+ const qreal sceneHeight = parentItem()->height();
57+
58+ if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
59+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
60+ Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
61+ } else if (newX >= sceneWidth-1 && newY < m_topBoundaryOffset) { // top right corner
62+ const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-m_topBoundaryOffset, 2));
63+ Q_EMIT pushedTopRightCorner(qAbs(distance), buttons);
64+ } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
65+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
66+ Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
67+ } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
68+ const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
69+ Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
70+ } else if (newX < 0) { // left edge
5371 Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
54− newX = 0;
55− } else if (newX >= parentItem()->width()) {
56− Q_EMIT pushedRightBoundary(newX - (parentItem()->width() - 1), buttons);
57− newX = parentItem()->width() - 1;
72+ } else if (newX >= sceneWidth) { // right edge
73+ Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
74+ } else if (newY < m_topBoundaryOffset) { // top edge
75+ Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
76+ } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
77+ Q_EMIT pushStopped();
5878 }
59− setX(newX);
6079
61− qreal newY = y() + appliedMovement.y();
62− if (newY < 0) {
63− newY = 0;
64− } else if (newY >= parentItem()->height()) {
65− newY = parentItem()->height() - 1;
66− }
67− setY(newY);
80+ setX(qBound(0.0, newX, sceneWidth - 1));
81+ setY(qBound(0.0, newY, sceneHeight - 1));
6882
6983 QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
7084 QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
@@ -82,6 +96,20 @@ void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::Keyb
8296 QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
8397}
8498
99+int MousePointer::topBoundaryOffset() const
100+{
101+ return m_topBoundaryOffset;
102+}
103+
104+void MousePointer::setTopBoundaryOffset(int topBoundaryOffset)
105+{
106+ if (m_topBoundaryOffset == topBoundaryOffset)
107+ return;
108+
109+ m_topBoundaryOffset = topBoundaryOffset;
110+ Q_EMIT topBoundaryOffsetChanged(topBoundaryOffset);
111+}
112+
85113void MousePointer::itemChange(ItemChange change, const ItemChangeData &value)
86114{
87115 if (change == ItemSceneChange) {
plugins/Cursor/MousePointer.h+15
@@ -27,6 +27,8 @@
2727
2828class MousePointer : public MirMousePointerInterface {
2929 Q_OBJECT
30+ Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
31+
3032public:
3133 MousePointer(QQuickItem *parent = nullptr);
3234
@@ -38,6 +40,9 @@ public:
3840
3941 void setCustomCursor(const QCursor &) override;
4042
43+ int topBoundaryOffset() const;
44+ void setTopBoundaryOffset(int topBoundaryOffset);
45+
4146public Q_SLOTS:
4247 void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
4348 Qt::KeyboardModifiers modifiers) override;
@@ -46,8 +51,16 @@ public Q_SLOTS:
4651Q_SIGNALS:
4752 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
4853 void pushedRightBoundary(qreal amount, Qt::MouseButtons buttons);
54+ void pushedTopBoundary(qreal amount, Qt::MouseButtons buttons);
55+ void pushedTopLeftCorner(qreal amount, Qt::MouseButtons buttons);
56+ void pushedTopRightCorner(qreal amount, Qt::MouseButtons buttons);
57+ void pushedBottomLeftCorner(qreal amount, Qt::MouseButtons buttons);
58+ void pushedBottomRightCorner(qreal amount, Qt::MouseButtons buttons);
59+ void pushStopped();
4960 void mouseMoved();
5061
62+ void topBoundaryOffsetChanged(int topBoundaryOffset);
63+
5164protected:
5265 void itemChange(ItemChange change, const ItemChangeData &value) override;
5366
@@ -64,6 +77,8 @@ private:
6477
6578 // Accumulated, unapplied, mouse movement.
6679 QPointF m_accumulatedMovement;
80+
81+ int m_topBoundaryOffset{0};
6782};
6883
6984#endif // MOUSEPOINTER_H
plugins/Ubuntu/Gestures/TouchGestureArea.h+2−2
@@ -76,7 +76,7 @@ public:
7676 return *this;
7777 }
7878
79− bool operator=(const GestureTouchPoint& rhs) const {
79+ bool operator==(const GestureTouchPoint& rhs) const {
8080 if (&rhs == this) return true;
8181 return m_id == rhs.m_id &&
8282 m_pressed == rhs.m_pressed &&
@@ -84,7 +84,7 @@ public:
8484 m_y == rhs.m_y &&
8585 m_dragging == rhs.m_dragging;
8686 }
87− bool operator!=(const GestureTouchPoint& rhs) const { return !operator=(rhs); }
87+ bool operator!=(const GestureTouchPoint& rhs) const { return !operator==(rhs); }
8888
8989 void setPos(const QPointF &pos);
9090
plugins/Utils/windowstatestorage.cpp+5−4
@@ -76,7 +76,7 @@ WindowStateStorage::WindowState WindowStateStorage::getState(const QString &wind
7676 return (WindowState)query.value(QStringLiteral("state")).toInt();
7777}
7878
79−void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
79+void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
8080{
8181 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
8282 .arg(sanitiseString(windowId))
@@ -92,7 +92,7 @@ void WindowStateStorage::saveStage(const QString &appId, int stage)
9292{
9393 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
9494 .arg(sanitiseString(appId))
95− .arg((int)stage);
95+ .arg(stage);
9696
9797 saveValue(queryString);
9898}
@@ -123,7 +123,7 @@ void WindowStateStorage::executeAsyncQuery(const QString &queryString)
123123 }
124124}
125125
126−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defaultValue) const
126+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
127127{
128128 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
129129 .arg(sanitiseString(windowId));
@@ -133,7 +133,8 @@ QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defau
133133 if (!query.first()) {
134134 return defaultValue;
135135 }
136− return QRect(query.value(QStringLiteral("x")).toInt(), query.value(QStringLiteral("y")).toInt(), query.value(QStringLiteral("width")).toInt(), query.value(QStringLiteral("height")).toInt());
136+ return QRect(query.value(QStringLiteral("x")).toInt(), query.value(QStringLiteral("y")).toInt(),
137+ query.value(QStringLiteral("width")).toInt(), query.value(QStringLiteral("height")).toInt());
137138}
138139
139140void WindowStateStorage::initdb()
plugins/Utils/windowstatestorage.h+8−3
@@ -32,7 +32,12 @@ public:
3232 WindowStateMaximizedLeft = 1 << 4,
3333 WindowStateMaximizedRight = 1 << 5,
3434 WindowStateMaximizedHorizontally = 1 << 6,
35− WindowStateMaximizedVertically = 1 << 7
35+ WindowStateMaximizedVertically = 1 << 7,
36+ WindowStateMaximizedTopLeft = 1 << 8,
37+ WindowStateMaximizedTopRight = 1 << 9,
38+ WindowStateMaximizedBottomLeft = 1 << 10,
39+ WindowStateMaximizedBottomRight = 1 << 11,
40+ WindowStateRestored = 1 << 12
3641 };
3742 Q_DECLARE_FLAGS(WindowStates, WindowState)
3843#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
@@ -45,8 +50,8 @@ public:
4550 Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
4651 Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
4752
48− Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect rect);
49− Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect defaultValue) const;
53+ Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
54+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
5055
5156 Q_INVOKABLE void saveStage(const QString &appId, int stage);
5257 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
qml/Components/PanelState/PanelState.qml+1
@@ -26,6 +26,7 @@ QtObject {
2626 property bool closeButtonShown: true
2727 property bool dropShadow: false
2828 property int panelHeight: 0
29+ property var maximizedAppDelegate: null
2930
3031 signal closeClicked()
3132 signal minimizeClicked()
qml/Components/WindowControlButtons.qml−1
@@ -25,7 +25,6 @@ Row {
2525 }
2626
2727 // to be set from outside
28− property Item target
2928 property bool active: false
3029 property bool windowIsMaximized: false
3130 property bool closeButtonShown: true
qml/OrientedShell.qml+1−1
@@ -260,7 +260,7 @@ Item {
260260 nativeWidth: root.width
261261 nativeHeight: root.height
262262 mode: applicationArguments.mode
263− hasMouse: miceModel.count + touchPadModel.count > 0
263+ hasMouse: pointerInputDevices > 0
264264 // TODO: Factor in if the current screen is a touch screen and if the user wants to
265265 // have multiple keyboards around. For now we only enable one keyboard at a time
266266 // thus hiding it here if there is a physical one around or if we have a second
qml/Panel/Panel.qml+22−6
@@ -103,12 +103,28 @@ Item {
103103 onClicked: callHint.visible ? callHint.showLiveCall() : PanelState.focusMaximizedApp()
104104 onDoubleClicked: PanelState.restoreClicked()
105105
106− property bool mouseWasPressed: false
107− onPressed: mouseWasPressed = containsPress
108− onMouseYChanged: {
109− if (mouseWasPressed && mouseY > panelHeight) {
110− PanelState.restoreClicked(); // restore the window when "dragging" the panel down
111− mouseWasPressed = false;
106+ property Item delegate: null // appDelegate that's used to drag-and-restore a maximized window
107+
108+ onPressedChanged: {
109+ if (!delegate && PanelState.maximizedAppDelegate && pressed) {
110+ delegate = PanelState.maximizedAppDelegate;
111+ delegate.moveHandler.handlePressedChanged(true, pressedButtons, mouseX, mouseY);
112+ }
113+ }
114+ onPositionChanged: {
115+ if (delegate && delegate.moveHandler.dragging) {
116+ delegate.focus = true;
117+ delegate.restoredX = Math.round(mouse.x - delegate.moveHandler.buttonsWidth);
118+ delegate.restoredY = Math.round(Math.max(mouse.y, PanelState.panelHeight));
119+ delegate.restoreFromMaximized(false);
120+ }
121+ }
122+ onReleased: {
123+ if (delegate) {
124+ delegate.moveHandler.handlePressedChanged(false, pressedButtons);
125+ delegate.moveHandler.handleReleased();
126+ delegate.moveHandler.shouldCommitSnapWindow();
127+ delegate = null;
112128 }
113129 }
114130
qml/Shell.qml+40
@@ -764,6 +764,7 @@ StyledItem {
764764 id: cursor
765765 visible: shell.hasMouse
766766 z: itemGrabber.z + 1
767+ topBoundaryOffset: panel.panelHeight
767768
768769 property bool mouseNeverMoved: true
769770 Binding {
@@ -777,9 +778,15 @@ StyledItem {
777778
778779 height: units.gu(3)
779780
781+ readonly property var previewRectangle: applicationsDisplayLoader.item && applicationsDisplayLoader.item.previewRectangle &&
782+ applicationsDisplayLoader.item.previewRectangle.targetDragging ?
783+ applicationsDisplayLoader.item.previewRectangle : null
784+
780785 onPushedLeftBoundary: {
781786 if (buttons === Qt.NoButton) {
782787 launcher.pushEdge(amount);
788+ } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
789+ previewRectangle.maximizeLeft(amount);
783790 }
784791 }
785792
@@ -787,6 +794,39 @@ StyledItem {
787794 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
788795 && applicationsDisplayLoader.item.pushRightEdge) {
789796 applicationsDisplayLoader.item.pushRightEdge(amount);
797+ } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
798+ previewRectangle.maximizeRight(amount);
799+ }
800+ }
801+
802+ onPushedTopBoundary: {
803+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
804+ previewRectangle.maximize(amount);
805+ }
806+ }
807+ onPushedTopLeftCorner: {
808+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
809+ previewRectangle.maximizeTopLeft(amount);
810+ }
811+ }
812+ onPushedTopRightCorner: {
813+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
814+ previewRectangle.maximizeTopRight(amount);
815+ }
816+ }
817+ onPushedBottomLeftCorner: {
818+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
819+ previewRectangle.maximizeBottomLeft(amount);
820+ }
821+ }
822+ onPushedBottomRightCorner: {
823+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
824+ previewRectangle.maximizeBottomRight(amount);
825+ }
826+ }
827+ onPushStopped: {
828+ if (previewRectangle) {
829+ previewRectangle.stop();
790830 }
791831 }
792832
qml/Stages/DecoratedWindow.qml+21
@@ -52,6 +52,9 @@ FocusScope {
5252 readonly property int heightIncrement: !counterRotate ? applicationWindow.heightIncrement : applicationWindow.widthIncrement
5353
5454 property alias overlayShown: decoration.overlayShown
55+ property alias stageWidth: moveHandler.stageWidth
56+ property alias stageHeight: moveHandler.stageHeight
57+ readonly property alias moveHandler: moveHandler
5558
5659 signal closeClicked()
5760 signal maximizeClicked()
@@ -60,6 +63,8 @@ FocusScope {
6063 signal minimizeClicked()
6164 signal decorationPressed()
6265
66+ signal shouldCommitSnapWindow()
67+
6368 Rectangle {
6469 id: selectionHighlight
6570 anchors.fill: parent
@@ -101,6 +106,22 @@ FocusScope {
101106 onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
102107 onMinimizeClicked: root.minimizeClicked();
103108 onPressed: root.decorationPressed();
109+
110+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
111+ onPositionChanged: moveHandler.handlePositionChanged(mouse)
112+ onReleased: {
113+ root.shouldCommitSnapWindow();
114+ moveHandler.handleReleased();
115+ }
116+ }
117+
118+ MoveHandler {
119+ id: moveHandler
120+ anchors.fill: decoration
121+ objectName: "moveHandler"
122+ target: root.parent
123+ buttonsWidth: decoration.buttonsWidth
124+ onShouldCommitSnapWindow: root.shouldCommitSnapWindow()
104125 }
105126
106127 ApplicationWindow {
qml/Stages/DesktopStage.qml+145−67
@@ -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
qml/Stages/FakeMaximizeDelegate.qmlnew file+235
@@ -0,0 +1,235 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Utils 0.1 // For EdgeBarrierSettings
20+import "../Components/PanelState"
21+
22+Rectangle {
23+ id: fakeRectangle
24+ visible: opacity > 0 && target && !target.anyMaximized // we go from 0.2 to 0.5
25+ enabled: visible
26+ color: "#ffffff"
27+ border.width: units.dp(2)
28+ border.color: "#99ffffff"
29+
30+ scale: progress > 0 && progress <= hintThreshold ? MathUtils.projectValue(progress, 0.0, 1.0, 1, 2) : 1
31+ opacity: progress > 0 ? MathUtils.projectValue(progress, 0.0, 1.0, 0.2, 0.5) : 0
32+
33+ property int edge: -1 // Item.TransformOrigin
34+ property var target // appDelegate
35+ property int leftMargin
36+ property real appContainerWidth
37+ property real appContainerHeight
38+
39+ readonly property real hintThreshold: 0.1
40+ readonly property bool targetDragging: target && target.moveHandler.dragging
41+
42+ // Edge push progress
43+ // Value range is [0.0, 1.0]
44+ readonly property real progress: priv.directProgress != -1 ? priv.directProgress : priv.accumulatedProgress
45+
46+ signal passed(int origin)
47+
48+ QtObject {
49+ id: priv
50+
51+ readonly property real accumulatedProgress: MathUtils.clamp(accumulatedPush / EdgeBarrierSettings.pushThreshold, 0.0, 1.0)
52+ property real directProgress: -1
53+ property real accumulatedPush: 0
54+
55+ function push(amount) {
56+ if (accumulatedPush === EdgeBarrierSettings.pushThreshold) {
57+ // NO-OP
58+ return;
59+ }
60+
61+ if (accumulatedPush + amount > EdgeBarrierSettings.pushThreshold) {
62+ accumulatedPush = EdgeBarrierSettings.pushThreshold;
63+ } else {
64+ accumulatedPush += amount;
65+ }
66+
67+ if (accumulatedPush === EdgeBarrierSettings.pushThreshold) {
68+ passed(edge);
69+ // commit(); // NB: uncomment to have automatic maximization on 100% progress
70+ }
71+ }
72+
73+ function setup(edge) {
74+ if (edge !== fakeRectangle.edge) {
75+ stop(); // a different edge, start anew
76+ }
77+ fakeRectangle.x = target.x;
78+ fakeRectangle.y = target.y;
79+ fakeRectangle.width = target.width;
80+ fakeRectangle.height = target.height;
81+ fakeRectangle.edge = edge;
82+ fakeRectangle.transformOrigin = edge;
83+ }
84+
85+ function processAnimation(amount, animation, isProgress) {
86+ if (isProgress) {
87+ priv.directProgress = amount;
88+ } else {
89+ priv.directProgress = -1;
90+ priv.push(amount);
91+ }
92+
93+ if (progress > hintThreshold) { // above 10% we start the full preview animation
94+ animation.start();
95+ }
96+ }
97+ }
98+
99+ function commit() {
100+ if (progress > hintThreshold && edge != -1) {
101+ target.moveHandler.handlePressedChanged(false, Qt.LeftButton); // cancel the drag
102+ if (edge == Item.Top) {
103+ target.maximize();
104+ } else if (edge == Item.Left) {
105+ target.maximizeLeft();
106+ } else if (edge == Item.Right) {
107+ target.maximizeRight();
108+ } else if (edge == Item.TopLeft) {
109+ target.maximizeTopLeft();
110+ } else if (edge == Item.TopRight) {
111+ target.maximizeTopRight();
112+ } else if (edge == Item.BottomLeft) {
113+ target.maximizeBottomLeft();
114+ } else if (edge == Item.BottomRight) {
115+ target.maximizeBottomRight();
116+ }
117+ } else {
118+ stop();
119+ }
120+ }
121+
122+ function stop() {
123+ priv.accumulatedPush = 0;
124+ priv.directProgress = -1;
125+ edge = -1;
126+ }
127+
128+ function maximize(amount, isProgress) {
129+ if (fakeRectangle.edge != Item.Top) {
130+ priv.setup(Item.Top);
131+ }
132+ priv.processAnimation(amount, fakeMaximizeAnimation, isProgress);
133+ }
134+
135+ function maximizeLeft(amount, isProgress) {
136+ if (fakeRectangle.edge != Item.Left) {
137+ priv.setup(Item.Left);
138+ }
139+ priv.processAnimation(amount, fakeMaximizeLeftAnimation, isProgress);
140+ }
141+
142+ function maximizeRight(amount, isProgress) {
143+ if (fakeRectangle.edge != Item.Right) {
144+ priv.setup(Item.Right);
145+ }
146+ priv.processAnimation(amount, fakeMaximizeRightAnimation, isProgress);
147+ }
148+
149+ function maximizeTopLeft(amount, isProgress) {
150+ if (fakeRectangle.edge != Item.TopLeft) {
151+ priv.setup(Item.TopLeft);
152+ }
153+ priv.processAnimation(amount, fakeMaximizeTopLeftAnimation, isProgress);
154+ }
155+
156+ function maximizeTopRight(amount, isProgress) {
157+ if (fakeRectangle.edge != Item.TopRight) {
158+ priv.setup(Item.TopRight);
159+ }
160+ priv.processAnimation(amount, fakeMaximizeTopRightAnimation, isProgress);
161+ }
162+
163+ function maximizeBottomLeft(amount, isProgress) {
164+ if (fakeRectangle.edge != Item.BottomLeft) {
165+ priv.setup(Item.BottomLeft);
166+ }
167+ priv.processAnimation(amount, fakeMaximizeBottomLeftAnimation, isProgress);
168+ }
169+
170+ function maximizeBottomRight(amount, isProgress) {
171+ if (fakeRectangle.edge != Item.BottomRight) {
172+ priv.setup(Item.BottomRight);
173+ }
174+ priv.processAnimation(amount, fakeMaximizeBottomRightAnimation, isProgress);
175+ }
176+
177+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
178+ Behavior on scale { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
179+
180+ ParallelAnimation {
181+ id: fakeMaximizeAnimation
182+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
183+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
184+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
185+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
186+ }
187+
188+ ParallelAnimation {
189+ id: fakeMaximizeLeftAnimation
190+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
191+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
192+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
193+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
194+ }
195+
196+ ParallelAnimation {
197+ id: fakeMaximizeRightAnimation
198+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
199+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
200+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
201+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
202+ }
203+
204+ ParallelAnimation {
205+ id: fakeMaximizeTopLeftAnimation
206+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
207+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
208+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
209+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
210+ }
211+
212+ ParallelAnimation {
213+ id: fakeMaximizeTopRightAnimation
214+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
215+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
216+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
217+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
218+ }
219+
220+ ParallelAnimation {
221+ id: fakeMaximizeBottomLeftAnimation
222+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
223+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
224+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
225+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
226+ }
227+
228+ ParallelAnimation {
229+ id: fakeMaximizeBottomRightAnimation
230+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
231+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
232+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
233+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
234+ }
235+}
qml/Stages/MoveHandler.qmlnew file+183
@@ -0,0 +1,183 @@
1+/*
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+import QtQuick 2.4
18+import Unity.Application 0.1 // For Mir singleton
19+import Ubuntu.Components 1.3
20+import Utils 0.1
21+import "../Components"
22+import "../Components/PanelState"
23+
24+Item {
25+ id: root
26+
27+ property Item target // appDelegate
28+ property int stageWidth
29+ property int stageHeight
30+ property real buttonsWidth: 0
31+
32+ readonly property alias dragging: priv.dragging
33+
34+ signal fakeMaximizeAnimationRequested(real amount)
35+ signal fakeMaximizeLeftAnimationRequested(real amount)
36+ signal fakeMaximizeRightAnimationRequested(real amount)
37+ signal fakeMaximizeTopLeftAnimationRequested(real amount)
38+ signal fakeMaximizeTopRightAnimationRequested(real amount)
39+ signal fakeMaximizeBottomLeftAnimationRequested(real amount)
40+ signal fakeMaximizeBottomRightAnimationRequested(real amount)
41+ signal stopFakeAnimation()
42+
43+ signal shouldCommitSnapWindow()
44+
45+ QtObject {
46+ id: priv
47+ property real distanceX
48+ property real distanceY
49+ property bool dragging
50+
51+ readonly property int triggerArea: units.gu(8)
52+ property bool nearLeftEdge: target.maximizedLeft
53+ property bool nearTopEdge: target.maximized
54+ property bool nearRightEdge: target.maximizedRight
55+ property bool nearTopLeftCorner: target.maximizedTopLeft
56+ property bool nearTopRightCorner: target.maximizedTopRight
57+ property bool nearBottomLeftCorner: target.maximizedBottomLeft
58+ property bool nearBottomRightCorner: target.maximizedBottomRight
59+
60+ function resetEdges() {
61+ nearLeftEdge = false;
62+ nearRightEdge = false;
63+ nearTopEdge = false;
64+ nearTopLeftCorner = false;
65+ nearTopRightCorner = false;
66+ nearBottomLeftCorner = false;
67+ nearBottomRightCorner = false;
68+ }
69+
70+ // return the progress of mouse pointer movement from 0 to 1 within a corner square of the screen
71+ // 0 -> before the mouse enters the square
72+ // 1 -> mouse is in the outer corner
73+ // a is the corner, b is the mouse pos
74+ function progressInCorner(ax, ay, bx, by) {
75+ // distance of two points, a and b, in pixels
76+ var distance = Math.sqrt(Math.pow(bx-ax, 2) + Math.pow(by-ay, 2));
77+ // length of the triggerArea square diagonal
78+ var diagLength = Math.sqrt(2 * priv.triggerArea * priv.triggerArea);
79+ var ratio = 1 - (distance / diagLength);
80+ return bx > 0 && bx <= stageWidth && by > 0 && by <= stageHeight ? ratio : 1; // everything "outside" of our square from the center is 1
81+ }
82+ property real progress: 0
83+ }
84+
85+ function handlePressedChanged(pressed, pressedButtons, mouseX, mouseY) {
86+ if (pressed && pressedButtons === Qt.LeftButton) {
87+ var pos = mapToItem(target, mouseX, mouseY);
88+ if (target.anyMaximized) {
89+ // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
90+ // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
91+ priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.resizeArea.normalWidth);
92+ priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.resizeArea.normalHeight);
93+ } else {
94+ priv.distanceX = pos.x;
95+ priv.distanceY = pos.y;
96+ }
97+
98+ priv.dragging = true;
99+ } else {
100+ priv.dragging = false;
101+ Mir.cursorName = "";
102+ }
103+ }
104+
105+ function handlePositionChanged(mouse, sensingPoints) {
106+ if (priv.dragging) {
107+ Mir.cursorName = "grabbing";
108+
109+ if (target.anyMaximized) { // restore from maximized when dragging away from edges/corners
110+ priv.progress = 0;
111+ target.restore(false, WindowStateStorage.WindowStateNormal);
112+ }
113+
114+ var pos = mapToItem(target.parent, mouse.x, mouse.y);
115+ // Use integer coordinate values to ensure that target is left in a pixel-aligned
116+ // position. Mouse movement could have subpixel precision, yielding a fractional
117+ // mouse position.
118+ target.requestedX = Math.round(pos.x - priv.distanceX);
119+ target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
120+
121+ if (sensingPoints) { // edge/corner detection when dragging via the touch overlay
122+ if (sensingPoints.topLeft.x < priv.triggerArea && sensingPoints.topLeft.y < PanelState.panelHeight + priv.triggerArea
123+ && target.canBeCornerMaximized) { // top left
124+ priv.progress = priv.progressInCorner(0, PanelState.panelHeight, sensingPoints.topLeft.x, sensingPoints.topLeft.y);
125+ priv.resetEdges();
126+ priv.nearTopLeftCorner = true;
127+ root.fakeMaximizeTopLeftAnimationRequested(priv.progress);
128+ } else if (sensingPoints.topRight.x > stageWidth - priv.triggerArea && sensingPoints.topRight.y < PanelState.panelHeight + priv.triggerArea
129+ && target.canBeCornerMaximized) { // top right
130+ priv.progress = priv.progressInCorner(stageWidth, PanelState.panelHeight, sensingPoints.topRight.x, sensingPoints.topRight.y);
131+ priv.resetEdges();
132+ priv.nearTopRightCorner = true;
133+ root.fakeMaximizeTopRightAnimationRequested(priv.progress);
134+ } else if (sensingPoints.bottomLeft.x < priv.triggerArea && sensingPoints.bottomLeft.y > stageHeight - priv.triggerArea
135+ && target.canBeCornerMaximized) { // bottom left
136+ priv.progress = priv.progressInCorner(0, stageHeight, sensingPoints.bottomLeft.x, sensingPoints.bottomLeft.y);
137+ priv.resetEdges();
138+ priv.nearBottomLeftCorner = true;
139+ root.fakeMaximizeBottomLeftAnimationRequested(priv.progress);
140+ } else if (sensingPoints.bottomRight.x > stageWidth - priv.triggerArea && sensingPoints.bottomRight.y > stageHeight - priv.triggerArea
141+ && target.canBeCornerMaximized) { // bottom right
142+ priv.progress = priv.progressInCorner(stageWidth, stageHeight, sensingPoints.bottomRight.x, sensingPoints.bottomRight.y);
143+ priv.resetEdges();
144+ priv.nearBottomRightCorner = true;
145+ root.fakeMaximizeBottomRightAnimationRequested(priv.progress);
146+ } else if (sensingPoints.left.x < priv.triggerArea && target.canBeMaximizedLeftRight) { // left
147+ priv.progress = MathUtils.clampAndProject(sensingPoints.left.x, priv.triggerArea, 0, 0, 1);
148+ priv.resetEdges();
149+ priv.nearLeftEdge = true;
150+ root.fakeMaximizeLeftAnimationRequested(priv.progress);
151+ } else if (sensingPoints.right.x > stageWidth - priv.triggerArea && target.canBeMaximizedLeftRight) { // right
152+ priv.progress = MathUtils.clampAndProject(sensingPoints.right.x, stageWidth - priv.triggerArea, stageWidth, 0, 1);
153+ priv.resetEdges();
154+ priv.nearRightEdge = true;
155+ root.fakeMaximizeRightAnimationRequested(priv.progress);
156+ } else if (sensingPoints.top.y < PanelState.panelHeight + priv.triggerArea && target.canBeMaximized) { // top
157+ priv.progress = MathUtils.clampAndProject(sensingPoints.top.y, PanelState.panelHeight + priv.triggerArea, 0, 0, 1);
158+ priv.resetEdges();
159+ priv.nearTopEdge = true;
160+ root.fakeMaximizeAnimationRequested(priv.progress);
161+ } else if (priv.nearLeftEdge || priv.nearRightEdge || priv.nearTopEdge || priv.nearTopLeftCorner || priv.nearTopRightCorner ||
162+ priv.nearBottomLeftCorner || priv.nearBottomRightCorner) {
163+ priv.progress = 0;
164+ priv.resetEdges();
165+ root.stopFakeAnimation();
166+ }
167+ }
168+ }
169+ }
170+
171+ function handleReleased(touchMode) {
172+ if (touchMode) {
173+ root.shouldCommitSnapWindow();
174+ priv.progress = 0;
175+ priv.resetEdges();
176+ }
177+ if ((target.state == "normal" || target.state == "restored") && priv.progress == 0) {
178+ // save the x/y to restore to
179+ target.restoredX = target.x;
180+ target.restoredY = target.y;
181+ }
182+ }
183+}
qml/Stages/WindowControlsOverlay.qml+76−38
@@ -23,12 +23,26 @@ import "../Components/PanelState"
2323Item {
2424 id: root
2525 enabled: target && !target.fullscreen
26+ anchors.fill: target
2627
2728 // to be set from outside
2829 property Item target // appDelegate
30+ property alias stageWidth: moveHandler.stageWidth
31+ property alias stageHeight: moveHandler.stageHeight
2932
3033 // to be read from outside
3134 readonly property alias overlayShown: overlay.visible
35+ readonly property alias moveHandler: moveHandler
36+
37+ signal fakeMaximizeAnimationRequested(real amount)
38+ signal fakeMaximizeLeftAnimationRequested(real amount)
39+ signal fakeMaximizeRightAnimationRequested(real amount)
40+ signal fakeMaximizeTopLeftAnimationRequested(real amount)
41+ signal fakeMaximizeTopRightAnimationRequested(real amount)
42+ signal fakeMaximizeBottomLeftAnimationRequested(real amount)
43+ signal fakeMaximizeBottomRightAnimationRequested(real amount)
44+ signal stopFakeAnimation()
45+ signal shouldCommitSnapWindow()
3246
3347 TouchGestureArea {
3448 id: gestureArea
@@ -51,16 +65,17 @@ Item {
5165 readonly property bool recognizedDrag: recognizedPress && dragging
5266 onRecognizedDragChanged: {
5367 if (recognizedDrag) {
54− priv.handlePressedChanged(true, tp.x, tp.y);
55− } else if (!moveHandler.containsPress) { // prevent interfering with the central piece drag/move
56− priv.dragging = false;
68+ moveHandler.handlePressedChanged(true, Qt.LeftButton, tp.x, tp.y);
69+ } else if (!mouseArea.containsPress) { // prevent interfering with the central piece drag/move
70+ moveHandler.handlePressedChanged(false, Qt.LeftButton);
71+ moveHandler.handleReleased(true);
5772 }
5873 }
5974
6075 readonly property point tp: recognizedPress ? Qt.point(touchPoints[0].x, touchPoints[0].y) : Qt.point(-1, -1)
6176 onUpdated: {
6277 if (recognizedDrag) {
63− priv.handlePositionChanged(tp.x, tp.y);
78+ moveHandler.handlePositionChanged(tp, priv.getSensingPoints());
6479 }
6580 }
6681 }
@@ -69,33 +84,36 @@ Item {
6984 Timer {
7085 id: overlayTimer
7186 interval: 2000
72− repeat: priv.dragging || (priv.resizeArea && priv.resizeArea.dragging)
87+ repeat: moveHandler.dragging || (priv.resizeArea && priv.resizeArea.dragging)
7388 }
7489
7590 QtObject {
7691 id: priv
77− property real distanceX
78− property real distanceY
79− property bool dragging
80−
8192 readonly property var resizeArea: root.target && root.target.resizeArea ? root.target.resizeArea : null
93+ readonly property bool ensureWindow: root.target.state == "normal" || root.target.state == "restored"
8294
83− function handlePressedChanged(pressed, mouseX, mouseY) {
84− if (pressed) {
85− var pos = mapToItem(root.target, mouseX, mouseY);
86− priv.distanceX = pos.x;
87− priv.distanceY = pos.y;
88− priv.dragging = true;
89− } else {
90− priv.dragging = false;
95+ function getSensingPoints() {
96+ var xPoints = [];
97+ var yPoints = [];
98+ for (var i = 0; i < gestureArea.touchPoints.length; i++) {
99+ var pt = gestureArea.touchPoints[i];
100+ xPoints.push(pt.x);
101+ yPoints.push(pt.y);
91102 }
92− }
93103
94− function handlePositionChanged(mouseX, mouseY) {
95− if (priv.dragging) {
96− var pos = mapToItem(root.target.parent, mouseX, mouseY);
97− root.target.requestedX = Math.round(pos.x - priv.distanceX);
98− root.target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
104+ var leftmost = Math.min.apply(Math, xPoints);
105+ var rightmost = Math.max.apply(Math, xPoints);
106+ var topmost = Math.min.apply(Math, yPoints);
107+ var bottommost = Math.max.apply(Math, yPoints);
108+
109+ return {
110+ left: mapToItem(target.parent, leftmost, (topmost+bottommost)/2),
111+ top: mapToItem(target.parent, (leftmost+rightmost)/2, topmost),
112+ right: mapToItem(target.parent, rightmost, (topmost+bottommost)/2),
113+ topLeft: mapToItem(target.parent, leftmost, topmost),
114+ topRight: mapToItem(target.parent, rightmost, topmost),
115+ bottomLeft: mapToItem(target.parent, leftmost, bottommost),
116+ bottomRight: mapToItem(target.parent, rightmost, bottommost)
99117 }
100118 }
101119 }
@@ -113,8 +131,6 @@ Item {
113131 UbuntuNumberAnimation {}
114132 }
115133
116− readonly property bool anyMaximized: target && (target.maximized || target.maximizedLeft || target.maximizedRight)
117−
118134 Image {
119135 source: "graphics/arrows-centre.png"
120136 width: units.gu(10)
@@ -125,15 +141,35 @@ Item {
125141
126142 // move handler
127143 MouseArea {
128− id: moveHandler
144+ id: mouseArea
129145 anchors.fill: parent
130146 visible: overlay.visible
131147 enabled: visible
132148 hoverEnabled: true
133− cursorShape: priv.dragging ? Qt.ClosedHandCursor : (overlay.visible ? Qt.OpenHandCursor : Qt.ArrowCursor)
134149
135− onPressedChanged: priv.handlePressedChanged(pressed, mouseX, mouseY)
136− onPositionChanged: priv.handlePositionChanged(mouseX, mouseY)
150+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
151+ onPositionChanged: moveHandler.handlePositionChanged(mouse)
152+ onReleased: {
153+ root.shouldCommitSnapWindow();
154+ moveHandler.handleReleased();
155+ }
156+ }
157+
158+ MoveHandler {
159+ id: moveHandler
160+ anchors.fill: mouseArea
161+ objectName: "moveHandler"
162+ target: root.target
163+
164+ onFakeMaximizeAnimationRequested: root.fakeMaximizeAnimationRequested(amount)
165+ onFakeMaximizeLeftAnimationRequested: root.fakeMaximizeLeftAnimationRequested(amount)
166+ onFakeMaximizeRightAnimationRequested: root.fakeMaximizeRightAnimationRequested(amount)
167+ onFakeMaximizeTopLeftAnimationRequested: root.fakeMaximizeTopLeftAnimationRequested(amount)
168+ onFakeMaximizeTopRightAnimationRequested: root.fakeMaximizeTopRightAnimationRequested(amount)
169+ onFakeMaximizeBottomLeftAnimationRequested: root.fakeMaximizeBottomLeftAnimationRequested(amount)
170+ onFakeMaximizeBottomRightAnimationRequested: root.fakeMaximizeBottomRightAnimationRequested(amount)
171+ onStopFakeAnimation: root.stopFakeAnimation()
172+ onShouldCommitSnapWindow: root.shouldCommitSnapWindow();
137173 }
138174
139175 // dismiss area
@@ -148,7 +184,7 @@ Item {
148184 }
149185
150186 overlayTimer.stop();
151− mouse.accepted = root.contains(Qt.point(mouse.x, mouse.y));
187+ mouse.accepted = root.contains(mapToItem(root.target, mouse.x, mouse.y));
152188 }
153189 propagateComposedEvents: true
154190 }
@@ -157,7 +193,7 @@ Item {
157193 ResizeGrip { // top left
158194 anchors.horizontalCenter: parent.left
159195 anchors.verticalCenter: parent.top
160− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
196+ visible: priv.ensureWindow || target.maximizedBottomRight
161197 resizeTarget: priv.resizeArea
162198 }
163199
@@ -165,7 +201,7 @@ Item {
165201 anchors.horizontalCenter: parent.horizontalCenter
166202 anchors.verticalCenter: parent.top
167203 rotation: 45
168− visible: target && !overlay.anyMaximized && !target.maximizedVertically
204+ visible: priv.ensureWindow || target.maximizedHorizontally || target.maximizedBottomLeft || target.maximizedBottomRight
169205 resizeTarget: priv.resizeArea
170206 }
171207
@@ -173,7 +209,7 @@ Item {
173209 anchors.horizontalCenter: parent.right
174210 anchors.verticalCenter: parent.top
175211 rotation: 90
176− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
212+ visible: priv.ensureWindow || target.maximizedBottomLeft
177213 resizeTarget: priv.resizeArea
178214 }
179215
@@ -181,14 +217,15 @@ Item {
181217 anchors.horizontalCenter: parent.right
182218 anchors.verticalCenter: parent.verticalCenter
183219 rotation: 135
184− visible: target && !target.maximizedRight && !target.maximized && !target.maximizedHorizontally
220+ visible: priv.ensureWindow || target.maximizedVertically || target.maximizedLeft ||
221+ target.maximizedTopLeft || target.maximizedBottomLeft
185222 resizeTarget: priv.resizeArea
186223 }
187224
188225 ResizeGrip { // bottom right
189226 anchors.horizontalCenter: parent.right
190227 anchors.verticalCenter: parent.bottom
191− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
228+ visible: priv.ensureWindow || target.maximizedTopLeft
192229 resizeTarget: priv.resizeArea
193230 }
194231
@@ -196,7 +233,7 @@ Item {
196233 anchors.horizontalCenter: parent.horizontalCenter
197234 anchors.verticalCenter: parent.bottom
198235 rotation: 45
199− visible: target && !overlay.anyMaximized && !target.maximizedVertically
236+ visible: priv.ensureWindow || target.maximizedHorizontally || target.maximizedTopLeft || target.maximizedTopRight
200237 resizeTarget: priv.resizeArea
201238 }
202239
@@ -204,7 +241,7 @@ Item {
204241 anchors.horizontalCenter: parent.left
205242 anchors.verticalCenter: parent.bottom
206243 rotation: 90
207− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
244+ visible: priv.ensureWindow || target.maximizedTopRight
208245 resizeTarget: priv.resizeArea
209246 }
210247
@@ -212,7 +249,8 @@ Item {
212249 anchors.horizontalCenter: parent.left
213250 anchors.verticalCenter: parent.verticalCenter
214251 rotation: 135
215− visible: target && !target.maximizedLeft && !target.maximized && !target.maximizedHorizontally
252+ visible: priv.ensureWindow || target.maximizedVertically || target.maximizedRight ||
253+ target.maximizedTopRight || target.maximizedBottomRight
216254 resizeTarget: priv.resizeArea
217255 }
218256 }
qml/Stages/WindowDecoration.qml+12−41
@@ -15,22 +15,24 @@
1515 */
1616
1717import QtQuick 2.4
18−import Unity.Application 0.1 // For Mir singleton
1918import Ubuntu.Components 1.3
2019import "../Components"
21−import "../Components/PanelState"
2220
2321MouseArea {
2422 id: root
2523 clip: true
2624
27− property Item target
25+ property Item target // appDelegate
2826 property alias title: titleLabel.text
2927 property alias maximizeButtonShown: buttons.maximizeButtonShown
3028 property bool active: false
31− acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
3229 property alias overlayShown: buttons.overlayShown
3330
31+ readonly property real buttonsWidth: buttons.width + row.spacing
32+
33+ acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
34+ hoverEnabled: true
35+
3436 signal closeClicked()
3537 signal minimizeClicked()
3638 signal maximizeClicked()
@@ -38,42 +40,11 @@ MouseArea {
3840 signal maximizeVerticallyClicked()
3941
4042 onDoubleClicked: {
41− if (maximizeButtonShown && mouse.button == Qt.LeftButton) {
43+ if (target.canBeMaximized && mouse.button == Qt.LeftButton) {
4244 root.maximizeClicked();
4345 }
4446 }
4547
46− QtObject {
47− id: priv
48− property real distanceX
49− property real distanceY
50− property bool dragging
51− }
52−
53− onPressedChanged: {
54− if (pressed && pressedButtons == Qt.LeftButton) {
55− var pos = mapToItem(root.target, mouseX, mouseY);
56− priv.distanceX = pos.x;
57− priv.distanceY = pos.y;
58− priv.dragging = true;
59− } else {
60− priv.dragging = false;
61− Mir.cursorName = "";
62− }
63− }
64−
65− onPositionChanged: {
66− if (priv.dragging) {
67− Mir.cursorName = "grabbing";
68− var pos = mapToItem(root.target.parent, mouseX, mouseY);
69− // Use integer coordinate values to ensure that target is left in a pixel-aligned
70− // position. Mouse movement could have subpixel precision, yielding a fractional
71− // mouse position.
72− root.target.requestedX = Math.round(pos.x - priv.distanceX);
73− root.target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
74− }
75− }
76−
7748 // do not let unhandled wheel event pass thru the decoration
7849 onWheel: wheel.accepted = true;
7950
@@ -85,6 +56,7 @@ MouseArea {
8556 }
8657
8758 Row {
59+ id: row
8860 anchors {
8961 fill: parent
9062 leftMargin: overlayShown ? units.gu(5) : units.gu(1)
@@ -105,10 +77,9 @@ MouseArea {
10577 onCloseClicked: root.closeClicked();
10678 onMinimizeClicked: root.minimizeClicked();
10779 onMaximizeClicked: root.maximizeClicked();
108− onMaximizeHorizontallyClicked: root.maximizeHorizontallyClicked();
109− onMaximizeVerticallyClicked: root.maximizeVerticallyClicked();
80+ onMaximizeHorizontallyClicked: if (root.target.canBeMaximizedHorizontally) root.maximizeHorizontallyClicked();
81+ onMaximizeVerticallyClicked: if (root.target.canBeMaximizedVertically) root.maximizeVerticallyClicked();
11082 closeButtonShown: root.target.application.appId !== "unity8-dash"
111− target: root.target
11283 }
11384
11485 Label {
@@ -122,9 +93,9 @@ MouseArea {
12293 font.weight: root.active ? Font.Light : Font.Medium
12394 elide: Text.ElideRight
12495 opacity: overlayShown ? 0 : 1
125− visible: opacity == 1
96+ visible: opacity != 0
12697 Behavior on opacity {
127− OpacityAnimator { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
98+ UbuntuNumberAnimation {}
12899 }
129100 }
130101 }
qml/Stages/WindowResizeArea.qml+13−24
@@ -30,6 +30,8 @@ MouseArea {
3030
3131 property var windowStateStorage: WindowStateStorage
3232 readonly property alias dragging: d.dragging
33+ readonly property alias normalWidth: priv.normalWidth
34+ readonly property alias normalHeight: priv.normalHeight
3335
3436 // The target item managed by this. Must be a parent or a sibling
3537 // The area will anchor to it and manage resize events
@@ -54,7 +56,7 @@ MouseArea {
5456 property int normalHeight: 0
5557
5658 function updateNormalGeometry() {
57− if (root.target.state == "normal") {
59+ if (root.target.state == "normal" || root.target.state == "restored") {
5860 normalX = root.target.requestedX
5961 normalY = root.target.requestedY
6062 normalWidth = root.target.width
@@ -83,34 +85,21 @@ MouseArea {
8385 target.requestedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight); });
8486
8587 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
86− switch (windowState) {
87− case WindowStateStorage.WindowStateNormal:
88− break;
89− case WindowStateStorage.WindowStateMaximized:
90− target.maximize(false);
91− break;
92− case WindowStateStorage.WindowStateMaximizedLeft:
93− target.maximizeLeft(false);
94− break;
95− case WindowStateStorage.WindowStateMaximizedRight:
96− target.maximizeRight(false);
97− break;
98− case WindowStateStorage.WindowStateMaximizedHorizontally:
99− target.maximizeHorizontally(false);
100− break;
101− case WindowStateStorage.WindowStateMaximizedVertically:
102− target.maximizeVertically(false);
103− break;
104− default:
105− console.warn("Unsupported window state");
106− break;
107− }
88+ target.restore(false /* animated */, windowState);
10889
10990 priv.updateNormalGeometry();
91+
92+ // initialize the x/y to restore to
93+ target.restoredX = priv.normalX;
94+ target.restoredY = priv.normalY;
11095 }
11196
11297 function saveWindowState() {
113− windowStateStorage.saveState(root.windowId, target.windowState & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
98+ var state = target.windowState;
99+ if (state === WindowStateStorage.WindowStateRestored) {
100+ state = WindowStateStorage.WindowStateNormal;
101+ }
102+ windowStateStorage.saveState(root.windowId, state & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
114103 windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight));
115104 }
116105
tests/mocks/Cursor/CMakeLists.txt+1−1
@@ -1 +1 @@
1−add_unity8_mock(Cursor 1.0 Cursor PREFIX mocks)
1+add_unity8_mock(Cursor 1.1 Cursor PREFIX mocks)
tests/mocks/Cursor/Cursor.qml+9−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -17,7 +17,15 @@
1717import QtQuick 2.4
1818
1919Item {
20+ property int topBoundaryOffset // effectively panel height
21+
2022 signal pushedLeftBoundary(real amount, int buttons)
2123 signal pushedRightBoundary(real amount, int buttons)
24+ signal pushedTopBoundary(real amount, int buttons)
25+ signal pushedTopLeftCorner(real amount, int buttons)
26+ signal pushedTopRightCorner(real amount, int buttons)
27+ signal pushedBottomLeftCorner(real amount, int buttons)
28+ signal pushedBottomRightCorner(real amount, int buttons)
29+ signal pushStopped()
2230 signal mouseMoved()
2331}
tests/mocks/Utils/windowstatestorage.cpp+2−2
@@ -41,7 +41,7 @@ void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect
4141 m_geometry[windowId] = rect;
4242}
4343
44−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
44+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
4545{
4646 if (!m_geometry.contains(windowId)) return defaultValue;
4747 return m_geometry.value(windowId).toRect();
@@ -70,7 +70,7 @@ void WindowStateStorage::saveState(const QString &windowId, WindowState state)
7070 m_state[windowId] = state;
7171}
7272
73−WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue)
73+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
7474{
7575 if (!m_state.contains(windowId)) return defaultValue;
7676 return m_state.value(windowId);
tests/mocks/Utils/windowstatestorage.h+8−3
@@ -32,7 +32,12 @@ public:
3232 WindowStateMaximizedLeft = 1 << 4,
3333 WindowStateMaximizedRight = 1 << 5,
3434 WindowStateMaximizedHorizontally = 1 << 6,
35− WindowStateMaximizedVertically = 1 << 7
35+ WindowStateMaximizedVertically = 1 << 7,
36+ WindowStateMaximizedTopLeft = 1 << 8,
37+ WindowStateMaximizedTopRight = 1 << 9,
38+ WindowStateMaximizedBottomLeft = 1 << 10,
39+ WindowStateMaximizedBottomRight = 1 << 11,
40+ WindowStateRestored = 1 << 12
3641 };
3742 Q_DECLARE_FLAGS(WindowStates, WindowState)
3843#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
@@ -42,10 +47,10 @@ public:
4247 WindowStateStorage(QObject *parent = 0);
4348
4449 Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
45− Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue);
50+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
4651
4752 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
48− Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
53+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
4954
5055 Q_INVOKABLE void saveStage(const QString &appId, int stage);
5156 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
tests/qmltests/Stages/tst_DesktopStage.qml+2−2
@@ -487,7 +487,7 @@ Item {
487487
488488 // click again to restore
489489 mouseClick(dialerMaximizeButton);
490− tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateNormal);
490+ tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateRestored);
491491 }
492492
493493 function test_windowMaximizeVertically() {
@@ -502,7 +502,7 @@ Item {
502502
503503 // click again to restore
504504 mouseClick(dialerMaximizeButton);
505− tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateNormal);
505+ tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateRestored);
506506 }
507507
508508 function test_smashCursorKeys() {
tests/qmltests/Stages/tst_WindowResizeArea.qml+9−1
@@ -60,9 +60,12 @@ Item {
6060 property real heightIncrement: 0
6161
6262 property int windowState: WindowStateStorage.WindowStateNormal
63+ property real restoredX
64+ property real restoredY
6365
6466 states: [
6567 State { name: "normal"; when: windowState == WindowStateStorage.WindowStateNormal },
68+ State { name: "restored"; when: windowState == WindowStateStorage.WindowStateRestored },
6669 State { name: "maximized"; when: windowState == WindowStateStorage.WindowStateMaximized }
6770 ]
6871
@@ -71,7 +74,12 @@ Item {
7174 }
7275
7376 function restoreFromMaximized() {
74− windowState = WindowStateStorage.WindowStateNormal
77+ windowState = WindowStateStorage.WindowStateRestored
78+ }
79+
80+ function restore(animated,state) {
81+ windowState = state || WindowStateStorage.WindowStateRestored;
82+ windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
7583 }
7684
7785 WindowResizeArea {