on xenial help sign in
Implement edge maximizing (aka window snapping) (LP: #1602628, #1608465, #1611859)
fc5ef5eLukáš Tinkl
plugins/Cursor/CMakeLists.txt+2−2
@@ -28,6 +28,6 @@ target_link_libraries(Cursor-qml
28 ${QT5PLATFORM_SUPPORT_LDFLAGS}
28 ${QT5PLATFORM_SUPPORT_LDFLAGS}
29)
29)
30
30
31−qt5_use_modules(Cursor-qml Qml Quick DBus Network Gui Sql Concurrent Svg)
31+qt5_use_modules(Cursor-qml Qml Quick Svg)
32
32
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+50−14
@@ -22,6 +22,7 @@
22
22
23#include <QQuickWindow>
23#include <QQuickWindow>
24#include <QGuiApplication>
24#include <QGuiApplication>
25+#include <QtMath>
25
26
26#include <qpa/qwindowsysteminterface.h>
27#include <qpa/qwindowsysteminterface.h>
27
28
@@ -49,25 +50,46 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
49 m_accumulatedMovement -= appliedMovement;
50 m_accumulatedMovement -= appliedMovement;
50
51
51 qreal newX = x() + appliedMovement.x();
52 qreal newX = x() + appliedMovement.x();
52− if (newX < 0) {
53− 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;
58− }
59−
60 qreal newY = y() + appliedMovement.y();
53 qreal newY = y() + appliedMovement.y();
61− if (newY < 0) {
54+ const qreal sceneWidth = parentItem()->width();
62− newY = 0;
55+ const qreal sceneHeight = parentItem()->height();
63− } else if (newY >= parentItem()->height()) {
56+
64− newY = parentItem()->height() - 1;
57+ if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
58+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
59+ Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
60+ m_pushing = true;
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+ m_pushing = true;
65+ } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
66+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
67+ Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
68+ m_pushing = true;
69+ } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
70+ const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
71+ Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
72+ m_pushing = true;
73+ } else if (newX < 0) { // left edge
74+ Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
75+ m_pushing = true;
76+ } else if (newX >= sceneWidth) { // right edge
77+ Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
78+ m_pushing = true;
79+ } else if (newY < m_topBoundaryOffset) { // top edge
80+ Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
81+ m_pushing = true;
82+ } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
83+ if (m_pushing) {
84+ Q_EMIT pushStopped();
85+ m_pushing = false;
86+ }
65 }
87 }
66
88
67 applyItemConfinement(newX, newY);
89 applyItemConfinement(newX, newY);
68
90
69− setX(newX);
91+ setX(qBound(0.0, newX, sceneWidth - 1));
70− setY(newY);
92+ setY(qBound(0.0, newY, sceneHeight - 1));
71
93
72 QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
94 QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
73 QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
95 QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
@@ -110,6 +132,20 @@ void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::Keyb
110 QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
132 QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
111}
133}
112
134
135+int MousePointer::topBoundaryOffset() const
136+{
137+ return m_topBoundaryOffset;
138+}
139+
140+void MousePointer::setTopBoundaryOffset(int topBoundaryOffset)
141+{
142+ if (m_topBoundaryOffset == topBoundaryOffset)
143+ return;
144+
145+ m_topBoundaryOffset = topBoundaryOffset;
146+ Q_EMIT topBoundaryOffsetChanged(topBoundaryOffset);
147+}
148+
113void MousePointer::itemChange(ItemChange change, const ItemChangeData &value)
149void MousePointer::itemChange(ItemChange change, const ItemChangeData &value)
114{
150{
115 if (change == ItemSceneChange) {
151 if (change == ItemSceneChange) {
plugins/Cursor/MousePointer.h+15−2
@@ -27,9 +27,8 @@
27
27
28class MousePointer : public MirMousePointerInterface {
28class MousePointer : public MirMousePointerInterface {
29 Q_OBJECT
29 Q_OBJECT
30−
31 Q_PROPERTY(QQuickItem* confiningItem READ confiningItem WRITE setConfiningItem NOTIFY confiningItemChanged)
30 Q_PROPERTY(QQuickItem* confiningItem READ confiningItem WRITE setConfiningItem NOTIFY confiningItemChanged)
32−
31+ Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
33public:
32public:
34 MousePointer(QQuickItem *parent = nullptr);
33 MousePointer(QQuickItem *parent = nullptr);
35
34
@@ -44,6 +43,9 @@ public:
44 QQuickItem* confiningItem() const;
43 QQuickItem* confiningItem() const;
45 void setConfiningItem(QQuickItem*);
44 void setConfiningItem(QQuickItem*);
46
45
46+ int topBoundaryOffset() const;
47+ void setTopBoundaryOffset(int topBoundaryOffset);
48+
47public Q_SLOTS:
49public Q_SLOTS:
48 void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
50 void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
49 Qt::KeyboardModifiers modifiers) override;
51 Qt::KeyboardModifiers modifiers) override;
@@ -52,9 +54,17 @@ public Q_SLOTS:
52Q_SIGNALS:
54Q_SIGNALS:
53 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
55 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
54 void pushedRightBoundary(qreal amount, Qt::MouseButtons buttons);
56 void pushedRightBoundary(qreal amount, Qt::MouseButtons buttons);
57+ void pushedTopBoundary(qreal amount, Qt::MouseButtons buttons);
58+ void pushedTopLeftCorner(qreal amount, Qt::MouseButtons buttons);
59+ void pushedTopRightCorner(qreal amount, Qt::MouseButtons buttons);
60+ void pushedBottomLeftCorner(qreal amount, Qt::MouseButtons buttons);
61+ void pushedBottomRightCorner(qreal amount, Qt::MouseButtons buttons);
62+ void pushStopped();
55 void mouseMoved();
63 void mouseMoved();
56 void confiningItemChanged();
64 void confiningItemChanged();
57
65
66+ void topBoundaryOffsetChanged(int topBoundaryOffset);
67+
58protected:
68protected:
59 void itemChange(ItemChange change, const ItemChangeData &value) override;
69 void itemChange(ItemChange change, const ItemChangeData &value) override;
60
70
@@ -74,6 +84,9 @@ private:
74 QPointF m_accumulatedMovement;
84 QPointF m_accumulatedMovement;
75
85
76 QPointer<QQuickItem> m_confiningItem;
86 QPointer<QQuickItem> m_confiningItem;
87+
88+ int m_topBoundaryOffset{0};
89+ bool m_pushing{false};
77};
90};
78
91
79#endif // MOUSEPOINTER_H
92#endif // MOUSEPOINTER_H
plugins/Ubuntu/Gestures/TouchGestureArea.h+2−2
@@ -76,7 +76,7 @@ public:
76 return *this;
76 return *this;
77 }
77 }
78
78
79− bool operator=(const GestureTouchPoint& rhs) const {
79+ bool operator==(const GestureTouchPoint& rhs) const {
80 if (&rhs == this) return true;
80 if (&rhs == this) return true;
81 return m_id == rhs.m_id &&
81 return m_id == rhs.m_id &&
82 m_pressed == rhs.m_pressed &&
82 m_pressed == rhs.m_pressed &&
@@ -84,7 +84,7 @@ public:
84 m_y == rhs.m_y &&
84 m_y == rhs.m_y &&
85 m_dragging == rhs.m_dragging;
85 m_dragging == rhs.m_dragging;
86 }
86 }
87− bool operator!=(const GestureTouchPoint& rhs) const { return !operator=(rhs); }
87+ bool operator!=(const GestureTouchPoint& rhs) const { return !operator==(rhs); }
88
88
89 void setPos(const QPointF &pos);
89 void setPos(const QPointF &pos);
90
90
plugins/Utils/windowstatestorage.cpp+5−4
@@ -76,7 +76,7 @@ WindowStateStorage::WindowState WindowStateStorage::getState(const QString &wind
76 return (WindowState)query.value(QStringLiteral("state")).toInt();
76 return (WindowState)query.value(QStringLiteral("state")).toInt();
77}
77}
78
78
79−void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
79+void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
80{
80{
81 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
81 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
82 .arg(sanitiseString(windowId))
82 .arg(sanitiseString(windowId))
@@ -92,7 +92,7 @@ void WindowStateStorage::saveStage(const QString &appId, int stage)
92{
92{
93 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
93 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
94 .arg(sanitiseString(appId))
94 .arg(sanitiseString(appId))
95− .arg((int)stage);
95+ .arg(stage);
96
96
97 saveValue(queryString);
97 saveValue(queryString);
98}
98}
@@ -123,7 +123,7 @@ void WindowStateStorage::executeAsyncQuery(const QString &queryString)
123 }
123 }
124}
124}
125
125
126−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defaultValue) const
126+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
127{
127{
128 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
128 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
129 .arg(sanitiseString(windowId));
129 .arg(sanitiseString(windowId));
@@ -133,7 +133,8 @@ QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defau
133 if (!query.first()) {
133 if (!query.first()) {
134 return defaultValue;
134 return defaultValue;
135 }
135 }
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());
137}
138}
138
139
139void WindowStateStorage::initdb()
140void WindowStateStorage::initdb()
plugins/Utils/windowstatestorage.h+8−3
@@ -32,7 +32,12 @@ public:
32 WindowStateMaximizedLeft = 1 << 4,
32 WindowStateMaximizedLeft = 1 << 4,
33 WindowStateMaximizedRight = 1 << 5,
33 WindowStateMaximizedRight = 1 << 5,
34 WindowStateMaximizedHorizontally = 1 << 6,
34 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
36 };
41 };
37 Q_DECLARE_FLAGS(WindowStates, WindowState)
42 Q_DECLARE_FLAGS(WindowStates, WindowState)
38#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
43#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
@@ -45,8 +50,8 @@ public:
45 Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
50 Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
46 Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
51 Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
47
52
48− Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect rect);
53+ Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
49− Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect defaultValue) const;
54+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
50
55
51 Q_INVOKABLE void saveStage(const QString &appId, int stage);
56 Q_INVOKABLE void saveStage(const QString &appId, int stage);
52 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
57 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
qml/Components/PanelState/PanelState.qml−1
@@ -30,5 +30,4 @@ QtObject {
30 signal closeClicked()
30 signal closeClicked()
31 signal minimizeClicked()
31 signal minimizeClicked()
32 signal restoreClicked()
32 signal restoreClicked()
33− signal focusMaximizedApp()
34}
33}
qml/Panel/Panel.qml+6−9
@@ -100,15 +100,12 @@ Item {
100 }
100 }
101 height: indicators.minimizedPanelHeight
101 height: indicators.minimizedPanelHeight
102 hoverEnabled: true
102 hoverEnabled: true
103− onClicked: callHint.visible ? callHint.showLiveCall() : PanelState.focusMaximizedApp()
103+ onClicked: if (callHint.visible) { callHint.showLiveCall(); }
104− onDoubleClicked: PanelState.restoreClicked()
104+
105−
105+ onPressed: {
106− property bool mouseWasPressed: false
106+ if (!callHint.visible) {
107− onPressed: mouseWasPressed = containsPress
107+ // let it fall through to the window decoration of the maximized window behind, if any
108− onMouseYChanged: {
108+ mouse.accepted = false;
109− if (mouseWasPressed && mouseY > panelHeight) {
110− PanelState.restoreClicked(); // restore the window when "dragging" the panel down
111− mouseWasPressed = false;
112 }
109 }
113 }
110 }
114
111
qml/Shell.qml+41
@@ -812,6 +812,7 @@ StyledItem {
812 id: cursor
812 id: cursor
813 visible: shell.hasMouse
813 visible: shell.hasMouse
814 z: itemGrabber.z + 1
814 z: itemGrabber.z + 1
815+ topBoundaryOffset: panel.panelHeight
815
816
816 confiningItem: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.itemConfiningMouseCursor : null
817 confiningItem: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.itemConfiningMouseCursor : null
817
818
@@ -827,9 +828,16 @@ StyledItem {
827
828
828 height: units.gu(3)
829 height: units.gu(3)
829
830
831+ readonly property var previewRectangle: applicationsDisplayLoader.item && applicationsDisplayLoader.item.previewRectangle &&
832+ applicationsDisplayLoader.item.previewRectangle.target &&
833+ applicationsDisplayLoader.item.previewRectangle.target.dragging ?
834+ applicationsDisplayLoader.item.previewRectangle : null
835+
830 onPushedLeftBoundary: {
836 onPushedLeftBoundary: {
831 if (buttons === Qt.NoButton) {
837 if (buttons === Qt.NoButton) {
832 launcher.pushEdge(amount);
838 launcher.pushEdge(amount);
839+ } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
840+ previewRectangle.maximizeLeft(amount);
833 }
841 }
834 }
842 }
835
843
@@ -837,6 +845,39 @@ StyledItem {
837 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
845 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
838 && applicationsDisplayLoader.item.pushRightEdge) {
846 && applicationsDisplayLoader.item.pushRightEdge) {
839 applicationsDisplayLoader.item.pushRightEdge(amount);
847 applicationsDisplayLoader.item.pushRightEdge(amount);
848+ } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
849+ previewRectangle.maximizeRight(amount);
850+ }
851+ }
852+
853+ onPushedTopBoundary: {
854+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
855+ previewRectangle.maximize(amount);
856+ }
857+ }
858+ onPushedTopLeftCorner: {
859+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
860+ previewRectangle.maximizeTopLeft(amount);
861+ }
862+ }
863+ onPushedTopRightCorner: {
864+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
865+ previewRectangle.maximizeTopRight(amount);
866+ }
867+ }
868+ onPushedBottomLeftCorner: {
869+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
870+ previewRectangle.maximizeBottomLeft(amount);
871+ }
872+ }
873+ onPushedBottomRightCorner: {
874+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
875+ previewRectangle.maximizeBottomRight(amount);
876+ }
877+ }
878+ onPushStopped: {
879+ if (previewRectangle) {
880+ previewRectangle.stop();
840 }
881 }
841 }
882 }
842
883
qml/Stages/DecoratedWindow.qml+18
@@ -52,6 +52,9 @@ FocusScope {
52 readonly property int heightIncrement: !counterRotate ? applicationWindow.heightIncrement : applicationWindow.widthIncrement
52 readonly property int heightIncrement: !counterRotate ? applicationWindow.heightIncrement : applicationWindow.widthIncrement
53
53
54 property alias overlayShown: decoration.overlayShown
54 property alias overlayShown: decoration.overlayShown
55+ property alias stageWidth: moveHandler.stageWidth
56+ property alias stageHeight: moveHandler.stageHeight
57+ readonly property alias dragging: moveHandler.dragging
55
58
56 readonly property Item clientAreaItem: applicationWindow
59 readonly property Item clientAreaItem: applicationWindow
57
60
@@ -61,6 +64,7 @@ FocusScope {
61 signal maximizeVerticallyClicked()
64 signal maximizeVerticallyClicked()
62 signal minimizeClicked()
65 signal minimizeClicked()
63 signal decorationPressed()
66 signal decorationPressed()
67+ signal decorationReleased()
64
68
65 Rectangle {
69 Rectangle {
66 id: selectionHighlight
70 id: selectionHighlight
@@ -103,6 +107,20 @@ FocusScope {
103 onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
107 onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
104 onMinimizeClicked: root.minimizeClicked();
108 onMinimizeClicked: root.minimizeClicked();
105 onPressed: root.decorationPressed();
109 onPressed: root.decorationPressed();
110+
111+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
112+ onPositionChanged: moveHandler.handlePositionChanged(mouse)
113+ onReleased: {
114+ root.decorationReleased();
115+ moveHandler.handleReleased();
116+ }
117+ }
118+
119+ MoveHandler {
120+ id: moveHandler
121+ objectName: "moveHandler"
122+ target: root.parent
123+ buttonsWidth: decoration.buttonsWidth
106 }
124 }
107
125
108 ApplicationWindow {
126 ApplicationWindow {
qml/Stages/DesktopStage.qml+139−69
@@ -45,6 +45,9 @@ AbstractStage {
45 // Used by TutorialRight
45 // Used by TutorialRight
46 property bool spreadShown: spread.state == "altTab"
46 property bool spreadShown: spread.state == "altTab"
47
47
48+ // used by the snap windows (edge maximize) feature
49+ readonly property alias previewRectangle: fakeRectangle
50+
48 mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
51 mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
49
52
50 // application windows never rotate independently
53 // application windows never rotate independently
@@ -73,28 +76,27 @@ AbstractStage {
73 id: maximizeWindowShortcut
76 id: maximizeWindowShortcut
74 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
77 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
75 onTriggered: priv.focusedAppDelegate.maximize()
78 onTriggered: priv.focusedAppDelegate.maximize()
76− active: priv.focusedAppDelegate !== null
79+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximized
77 }
80 }
78
81
79 GlobalShortcut {
82 GlobalShortcut {
80 id: maximizeWindowLeftShortcut
83 id: maximizeWindowLeftShortcut
81 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
84 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
82 onTriggered: priv.focusedAppDelegate.maximizeLeft()
85 onTriggered: priv.focusedAppDelegate.maximizeLeft()
83− active: priv.focusedAppDelegate !== null
86+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
84 }
87 }
85
88
86 GlobalShortcut {
89 GlobalShortcut {
87 id: maximizeWindowRightShortcut
90 id: maximizeWindowRightShortcut
88 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
91 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
89 onTriggered: priv.focusedAppDelegate.maximizeRight()
92 onTriggered: priv.focusedAppDelegate.maximizeRight()
90− active: priv.focusedAppDelegate !== null
93+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
91 }
94 }
92
95
93 GlobalShortcut {
96 GlobalShortcut {
94 id: minimizeRestoreShortcut
97 id: minimizeRestoreShortcut
95 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
98 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
96− onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight ||
99+ onTriggered: priv.focusedAppDelegate.anyMaximized
97− priv.focusedAppDelegate.maximizedHorizontally || priv.focusedAppDelegate.maximizedVertically
98 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
100 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
99 active: priv.focusedAppDelegate !== null
101 active: priv.focusedAppDelegate !== null
100 }
102 }
@@ -170,11 +172,6 @@ AbstractStage {
170 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
172 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
171 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
173 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
172 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
174 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
173− onFocusMaximizedApp: {
174− if (priv.foregroundMaximizedAppDelegate) {
175− priv.foregroundMaximizedAppDelegate.focus = true;
176− }
177− }
178 }
175 }
179
176
180 Binding {
177 Binding {
@@ -279,7 +276,7 @@ AbstractStage {
279 value: appDelegate.requestedY -
276 value: appDelegate.requestedY -
280 Math.min(appDelegate.requestedY - PanelState.panelHeight,
277 Math.min(appDelegate.requestedY - PanelState.panelHeight,
281 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
278 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
282− when: root.oskEnabled && appDelegate.focus && appDelegate.state == "normal"
279+ when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
283 && SurfaceManager.inputMethodSurface
280 && SurfaceManager.inputMethodSurface
284 && SurfaceManager.inputMethodSurface.state != Mir.HiddenState
281 && SurfaceManager.inputMethodSurface.state != Mir.HiddenState
285 && SurfaceManager.inputMethodSurface.state != Mir.MinimizedState
282 && SurfaceManager.inputMethodSurface.state != Mir.MinimizedState
@@ -294,7 +291,7 @@ AbstractStage {
294 onShellOrientationAngleChanged: {
291 onShellOrientationAngleChanged: {
295 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
292 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
296 if (application && application.rotatesWindowContents) {
293 if (application && application.rotatesWindowContents) {
297− if (state == "normal") {
294+ if (state == "normal" || state == "restored") {
298 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
295 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
299 angleDiff = (360 + angleDiff) % 360;
296 angleDiff = (360 + angleDiff) % 360;
300 if (angleDiff === 90 || angleDiff === 270) {
297 if (angleDiff === 90 || angleDiff === 270) {
@@ -320,14 +317,29 @@ AbstractStage {
320 property int requestedWidth: -1
317 property int requestedWidth: -1
321 property int requestedHeight: -1
318 property int requestedHeight: -1
322
319
323− readonly property bool maximized: windowState & WindowStateStorage.WindowStateMaximized
320+ readonly property bool maximized: windowState === WindowStateStorage.WindowStateMaximized
324− readonly property bool maximizedLeft: windowState & WindowStateStorage.WindowStateMaximizedLeft
321+ readonly property bool maximizedLeft: windowState === WindowStateStorage.WindowStateMaximizedLeft
325− readonly property bool maximizedRight: windowState & WindowStateStorage.WindowStateMaximizedRight
322+ readonly property bool maximizedRight: windowState === WindowStateStorage.WindowStateMaximizedRight
326− readonly property bool maximizedHorizontally: windowState & WindowStateStorage.WindowStateMaximizedHorizontally
323+ readonly property bool maximizedHorizontally: windowState === WindowStateStorage.WindowStateMaximizedHorizontally
327− readonly property bool maximizedVertically: windowState & WindowStateStorage.WindowStateMaximizedVertically
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+
328 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
332 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
329 readonly property alias fullscreen: decoratedWindow.fullscreen
333 readonly property alias fullscreen: decoratedWindow.fullscreen
330
334
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+
331 property int windowState: WindowStateStorage.WindowStateNormal
343 property int windowState: WindowStateStorage.WindowStateNormal
332 property bool animationsEnabled: true
344 property bool animationsEnabled: true
333 property alias title: decoratedWindow.title
345 property alias title: decoratedWindow.title
@@ -338,6 +350,7 @@ AbstractStage {
338 readonly property var surface: model.surface
350 readonly property var surface: model.surface
339 readonly property alias resizeArea: resizeArea
351 readonly property alias resizeArea: resizeArea
340 readonly property alias focusedSurface: decoratedWindow.focusedSurface
352 readonly property alias focusedSurface: decoratedWindow.focusedSurface
353+ readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
341
354
342 readonly property bool isDash: model.application.appId == "unity8-dash"
355 readonly property bool isDash: model.application.appId == "unity8-dash"
343 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
356 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
@@ -346,7 +359,7 @@ AbstractStage {
346 if (spread.state == "altTab") {
359 if (spread.state == "altTab") {
347 spread.cancel();
360 spread.cancel();
348 }
361 }
349− appDelegate.restore();
362+ appDelegate.restore(true /* animated */, appDelegate.windowState);
350 }
363 }
351 Connections {
364 Connections {
352 target: model.surface
365 target: model.surface
@@ -454,28 +467,34 @@ AbstractStage {
454 animationsEnabled = (animated === undefined) || animated;
467 animationsEnabled = (animated === undefined) || animated;
455 windowState = WindowStateStorage.WindowStateMaximizedVertically;
468 windowState = WindowStateStorage.WindowStateMaximizedVertically;
456 }
469 }
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+ }
457 function minimize(animated) {
486 function minimize(animated) {
458 animationsEnabled = (animated === undefined) || animated;
487 animationsEnabled = (animated === undefined) || animated;
459 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
488 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
460 }
489 }
461 function restoreFromMaximized(animated) {
490 function restoreFromMaximized(animated) {
462 animationsEnabled = (animated === undefined) || animated;
491 animationsEnabled = (animated === undefined) || animated;
463− windowState = WindowStateStorage.WindowStateNormal;
492+ windowState = WindowStateStorage.WindowStateRestored;
464 }
493 }
465− function restore(animated) {
494+ function restore(animated,state) {
466 animationsEnabled = (animated === undefined) || animated;
495 animationsEnabled = (animated === undefined) || animated;
496+ windowState = state || WindowStateStorage.WindowStateRestored;
467 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
497 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
468− if (maximized)
469− maximize();
470− else if (maximizedLeft)
471− maximizeLeft();
472− else if (maximizedRight)
473− maximizeRight();
474− else if (maximizedHorizontally)
475− maximizeHorizontally();
476− else if (maximizedVertically)
477− maximizeVertically();
478−
479 focus = true;
498 focus = true;
480 }
499 }
481
500
@@ -492,9 +511,14 @@ AbstractStage {
492 duration: UbuntuAnimation.SnapDuration
511 duration: UbuntuAnimation.SnapDuration
493 }
512 }
494
513
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+
495 states: [
519 states: [
496 State {
520 State {
497− name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
521+ name: "fullscreen"; when: appDelegate.fullscreen && !appDelegate.minimized
498 PropertyChanges {
522 PropertyChanges {
499 target: appDelegate;
523 target: appDelegate;
500 x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
524 x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
@@ -513,61 +537,94 @@ AbstractStage {
513 }
537 }
514 },
538 },
515 State {
539 State {
516− name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
540+ name: "restored";
541+ when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
542+ extend: "normal"
517 PropertyChanges {
543 PropertyChanges {
518 target: appDelegate;
544 target: appDelegate;
519− x: root.leftMargin;
545+ requestedX: restoredX;
520− y: 0;
546+ requestedY: restoredY;
521− visuallyMinimized: false;
522− visuallyMaximized: true
523 }
547 }
548+ },
549+ State {
550+ name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
524 PropertyChanges {
551 PropertyChanges {
525− target: decoratedWindow
552+ target: appDelegate;
553+ requestedX: root.leftMargin;
554+ requestedY: 0;
526 requestedWidth: appContainer.width - root.leftMargin;
555 requestedWidth: appContainer.width - root.leftMargin;
527 requestedHeight: appContainer.height;
556 requestedHeight: appContainer.height;
557+ visuallyMinimized: false;
558+ visuallyMaximized: true
528 }
559 }
529 },
560 },
530 State {
561 State {
531 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
562 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
532 PropertyChanges {
563 PropertyChanges {
533 target: appDelegate
564 target: appDelegate
534− x: root.leftMargin
565+ requestedX: root.leftMargin
535− y: PanelState.panelHeight
566+ requestedY: PanelState.panelHeight
536− }
537− PropertyChanges {
538− target: decoratedWindow
539 requestedWidth: (appContainer.width - root.leftMargin)/2
567 requestedWidth: (appContainer.width - root.leftMargin)/2
540 requestedHeight: appContainer.height - PanelState.panelHeight
568 requestedHeight: appContainer.height - PanelState.panelHeight
541 }
569 }
542 },
570 },
543 State {
571 State {
544 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
572 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
573+ extend: "maximizedLeft"
545 PropertyChanges {
574 PropertyChanges {
546 target: appDelegate;
575 target: appDelegate;
547− x: (appContainer.width + root.leftMargin)/2
576+ requestedX: (appContainer.width + root.leftMargin)/2
548− y: PanelState.panelHeight
549 }
577 }
578+ },
579+ State {
580+ name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
550 PropertyChanges {
581 PropertyChanges {
551− target: decoratedWindow
582+ target: appDelegate
583+ requestedX: root.leftMargin
584+ requestedY: PanelState.panelHeight
552 requestedWidth: (appContainer.width - root.leftMargin)/2
585 requestedWidth: (appContainer.width - root.leftMargin)/2
553− 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
554 }
613 }
555 },
614 },
556 State {
615 State {
557 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
616 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
558− PropertyChanges { target: appDelegate; x: root.leftMargin }
617+ PropertyChanges { target: appDelegate; requestedX: root.leftMargin; requestedY: requestedY; requestedWidth: appContainer.width - root.leftMargin }
559− PropertyChanges { target: decoratedWindow; requestedWidth: appContainer.width - root.leftMargin }
560 },
618 },
561 State {
619 State {
562 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
620 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
563− PropertyChanges { target: appDelegate; y: PanelState.panelHeight }
621+ PropertyChanges { target: appDelegate; requestedX: requestedX; requestedY: PanelState.panelHeight; requestedHeight: appContainer.height - PanelState.panelHeight }
564− PropertyChanges { target: decoratedWindow; requestedHeight: appContainer.height - PanelState.panelHeight }
565 },
622 },
566 State {
623 State {
567 name: "minimized"; when: appDelegate.minimized
624 name: "minimized"; when: appDelegate.minimized
568 PropertyChanges {
625 PropertyChanges {
569 target: appDelegate;
626 target: appDelegate;
570− x: -appDelegate.width / 2;
627+ requestedX: -appDelegate.width / 2;
571 scale: units.gu(5) / appDelegate.width;
628 scale: units.gu(5) / appDelegate.width;
572 opacity: 0;
629 opacity: 0;
573 visuallyMinimized: true;
630 visuallyMinimized: true;
@@ -577,21 +634,17 @@ AbstractStage {
577 ]
634 ]
578 transitions: [
635 transitions: [
579 Transition {
636 Transition {
580− to: "normal"
637+ to: "normal,restored"
581 enabled: appDelegate.animationsEnabled
638 enabled: appDelegate.animationsEnabled
582 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
639 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
583− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
640+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
584− UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
585 },
641 },
586 Transition {
642 Transition {
587 to: "minimized"
643 to: "minimized"
588 enabled: appDelegate.animationsEnabled
644 enabled: appDelegate.animationsEnabled
589 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
645 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
590 SequentialAnimation {
646 SequentialAnimation {
591− ParallelAnimation {
592− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
647+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
593− UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
594− }
595 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
648 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
596 ScriptAction {
649 ScriptAction {
597 script: {
650 script: {
@@ -608,11 +661,9 @@ AbstractStage {
608 enabled: appDelegate.animationsEnabled
661 enabled: appDelegate.animationsEnabled
609 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
662 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
610 SequentialAnimation {
663 SequentialAnimation {
611− ParallelAnimation {
612− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
664+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
613− UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
614− }
615 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
665 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
666+ ScriptAction { script: { fakeRectangle.stop(); } }
616 }
667 }
617 }
668 }
618 ]
669 ]
@@ -678,27 +729,38 @@ AbstractStage {
678 surface: model.surface
729 surface: model.surface
679 active: appDelegate.focus
730 active: appDelegate.focus
680 focus: true
731 focus: true
681− maximizeButtonShown: (maximumWidth == 0 || maximumWidth >= appContainer.width) &&
732+ maximizeButtonShown: appDelegate.canBeMaximized
682− (maximumHeight == 0 || maximumHeight >= appContainer.height)
683 overlayShown: touchControls.overlayShown
733 overlayShown: touchControls.overlayShown
734+ stageWidth: appContainer.width
735+ stageHeight: appContainer.height
684
736
685 requestedWidth: appDelegate.requestedWidth
737 requestedWidth: appDelegate.requestedWidth
686 requestedHeight: appDelegate.requestedHeight
738 requestedHeight: appDelegate.requestedHeight
687
739
688 onCloseClicked: { appDelegate.close(); }
740 onCloseClicked: { appDelegate.close(); }
689− onMaximizeClicked: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
690− || appDelegate.maximizedHorizontally || appDelegate.maximizedVertically
691− ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
741+ onMaximizeClicked: appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
692 onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
742 onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
693 onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
743 onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
694 onMinimizeClicked: appDelegate.minimize()
744 onMinimizeClicked: appDelegate.minimize()
695 onDecorationPressed: { appDelegate.focus = true; }
745 onDecorationPressed: { appDelegate.focus = true; }
746+ onDecorationReleased: fakeRectangle.commit();
696 }
747 }
697
748
698 WindowControlsOverlay {
749 WindowControlsOverlay {
699 id: touchControls
750 id: touchControls
700− anchors.fill: appDelegate
701 target: appDelegate
751 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();
702 }
764 }
703
765
704 WindowedFullscreenPolicy {
766 WindowedFullscreenPolicy {
@@ -710,6 +772,14 @@ AbstractStage {
710 }
772 }
711 }
773 }
712
774
775+ FakeMaximizeDelegate {
776+ id: fakeRectangle
777+ target: priv.focusedAppDelegate
778+ leftMargin: root.leftMargin
779+ appContainerWidth: appContainer.width
780+ appContainerHeight: appContainer.height
781+ }
782+
713 EdgeBarrier {
783 EdgeBarrier {
714 id: edgeBarrier
784 id: edgeBarrier
715
785
qml/Stages/FakeMaximizeDelegate.qmlnew file+233
@@ -0,0 +1,233 @@
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+
41+ // Edge push progress
42+ // Value range is [0.0, 1.0]
43+ readonly property real progress: priv.directProgress != -1 ? priv.directProgress : priv.accumulatedProgress
44+
45+ signal passed(int origin)
46+
47+ QtObject {
48+ id: priv
49+
50+ readonly property real accumulatedProgress: MathUtils.clamp(accumulatedPush / EdgeBarrierSettings.pushThreshold, 0.0, 1.0)
51+ property real directProgress: -1
52+ property real accumulatedPush: 0
53+
54+ function push(amount) {
55+ if (accumulatedPush === EdgeBarrierSettings.pushThreshold) {
56+ // NO-OP
57+ return;
58+ }
59+
60+ if (accumulatedPush + amount > EdgeBarrierSettings.pushThreshold) {
61+ accumulatedPush = EdgeBarrierSettings.pushThreshold;
62+ } else {
63+ accumulatedPush += amount;
64+ }
65+
66+ if (accumulatedPush === EdgeBarrierSettings.pushThreshold) {
67+ passed(edge);
68+ // commit(); // NB: uncomment to have automatic maximization on 100% progress
69+ }
70+ }
71+
72+ function setup(edge) {
73+ if (edge !== fakeRectangle.edge) {
74+ stop(); // a different edge, start anew
75+ }
76+ fakeRectangle.x = target.x;
77+ fakeRectangle.y = target.y;
78+ fakeRectangle.width = target.width;
79+ fakeRectangle.height = target.height;
80+ fakeRectangle.edge = edge;
81+ fakeRectangle.transformOrigin = edge;
82+ }
83+
84+ function processAnimation(amount, animation, isProgress) {
85+ if (isProgress) {
86+ priv.directProgress = amount;
87+ } else {
88+ priv.directProgress = -1;
89+ priv.push(amount);
90+ }
91+
92+ if (progress > hintThreshold) { // above 10% we start the full preview animation
93+ animation.start();
94+ }
95+ }
96+ }
97+
98+ function commit() {
99+ if (progress > hintThreshold && edge != -1) {
100+ if (edge == Item.Top) {
101+ target.maximize();
102+ } else if (edge == Item.Left) {
103+ target.maximizeLeft();
104+ } else if (edge == Item.Right) {
105+ target.maximizeRight();
106+ } else if (edge == Item.TopLeft) {
107+ target.maximizeTopLeft();
108+ } else if (edge == Item.TopRight) {
109+ target.maximizeTopRight();
110+ } else if (edge == Item.BottomLeft) {
111+ target.maximizeBottomLeft();
112+ } else if (edge == Item.BottomRight) {
113+ target.maximizeBottomRight();
114+ }
115+ } else {
116+ stop();
117+ }
118+ }
119+
120+ function stop() {
121+ priv.accumulatedPush = 0;
122+ priv.directProgress = -1;
123+ edge = -1;
124+ }
125+
126+ function maximize(amount, isProgress) {
127+ if (fakeRectangle.edge != Item.Top) {
128+ priv.setup(Item.Top);
129+ }
130+ priv.processAnimation(amount, fakeMaximizeAnimation, isProgress);
131+ }
132+
133+ function maximizeLeft(amount, isProgress) {
134+ if (fakeRectangle.edge != Item.Left) {
135+ priv.setup(Item.Left);
136+ }
137+ priv.processAnimation(amount, fakeMaximizeLeftAnimation, isProgress);
138+ }
139+
140+ function maximizeRight(amount, isProgress) {
141+ if (fakeRectangle.edge != Item.Right) {
142+ priv.setup(Item.Right);
143+ }
144+ priv.processAnimation(amount, fakeMaximizeRightAnimation, isProgress);
145+ }
146+
147+ function maximizeTopLeft(amount, isProgress) {
148+ if (fakeRectangle.edge != Item.TopLeft) {
149+ priv.setup(Item.TopLeft);
150+ }
151+ priv.processAnimation(amount, fakeMaximizeTopLeftAnimation, isProgress);
152+ }
153+
154+ function maximizeTopRight(amount, isProgress) {
155+ if (fakeRectangle.edge != Item.TopRight) {
156+ priv.setup(Item.TopRight);
157+ }
158+ priv.processAnimation(amount, fakeMaximizeTopRightAnimation, isProgress);
159+ }
160+
161+ function maximizeBottomLeft(amount, isProgress) {
162+ if (fakeRectangle.edge != Item.BottomLeft) {
163+ priv.setup(Item.BottomLeft);
164+ }
165+ priv.processAnimation(amount, fakeMaximizeBottomLeftAnimation, isProgress);
166+ }
167+
168+ function maximizeBottomRight(amount, isProgress) {
169+ if (fakeRectangle.edge != Item.BottomRight) {
170+ priv.setup(Item.BottomRight);
171+ }
172+ priv.processAnimation(amount, fakeMaximizeBottomRightAnimation, isProgress);
173+ }
174+
175+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
176+ Behavior on scale { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
177+
178+ ParallelAnimation {
179+ id: fakeMaximizeAnimation
180+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
181+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
182+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
183+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
184+ }
185+
186+ ParallelAnimation {
187+ id: fakeMaximizeLeftAnimation
188+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
189+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
190+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
191+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
192+ }
193+
194+ ParallelAnimation {
195+ id: fakeMaximizeRightAnimation
196+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
197+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
198+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
199+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
200+ }
201+
202+ ParallelAnimation {
203+ id: fakeMaximizeTopLeftAnimation
204+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
205+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
206+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
207+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
208+ }
209+
210+ ParallelAnimation {
211+ id: fakeMaximizeTopRightAnimation
212+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
213+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
214+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
215+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
216+ }
217+
218+ ParallelAnimation {
219+ id: fakeMaximizeBottomLeftAnimation
220+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
221+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
222+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
223+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
224+ }
225+
226+ ParallelAnimation {
227+ id: fakeMaximizeBottomRightAnimation
228+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
229+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
230+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
231+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
232+ }
233+}
qml/Stages/MoveHandler.qmlnew file+179
@@ -0,0 +1,179 @@
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+QtObject {
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 bool 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+ property QtObject priv: QtObject {
44+ property real distanceX
45+ property real distanceY
46+ property bool dragging
47+
48+ readonly property int triggerArea: units.gu(8)
49+ property bool nearLeftEdge: target.maximizedLeft
50+ property bool nearTopEdge: target.maximized
51+ property bool nearRightEdge: target.maximizedRight
52+ property bool nearTopLeftCorner: target.maximizedTopLeft
53+ property bool nearTopRightCorner: target.maximizedTopRight
54+ property bool nearBottomLeftCorner: target.maximizedBottomLeft
55+ property bool nearBottomRightCorner: target.maximizedBottomRight
56+
57+ function resetEdges() {
58+ nearLeftEdge = false;
59+ nearRightEdge = false;
60+ nearTopEdge = false;
61+ nearTopLeftCorner = false;
62+ nearTopRightCorner = false;
63+ nearBottomLeftCorner = false;
64+ nearBottomRightCorner = false;
65+ }
66+
67+ // return the progress of mouse pointer movement from 0 to 1 within a corner square of the screen
68+ // 0 -> before the mouse enters the square
69+ // 1 -> mouse is in the outer corner
70+ // a is the corner, b is the mouse pos
71+ function progressInCorner(ax, ay, bx, by) {
72+ // distance of two points, a and b, in pixels
73+ var distance = Math.sqrt(Math.pow(bx-ax, 2) + Math.pow(by-ay, 2));
74+ // length of the triggerArea square diagonal
75+ var diagLength = Math.sqrt(2 * priv.triggerArea * priv.triggerArea);
76+ var ratio = 1 - (distance / diagLength);
77+ return bx > 0 && bx <= stageWidth && by > 0 && by <= stageHeight ? ratio : 1; // everything "outside" of our square from the center is 1
78+ }
79+ property real progress: 0
80+ }
81+
82+ function handlePressedChanged(pressed, pressedButtons, mouseX, mouseY) {
83+ if (pressed && pressedButtons === Qt.LeftButton) {
84+ var pos = mapToItem(target, mouseX, mouseY);
85+ if (target.anyMaximized) {
86+ // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
87+ // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
88+ priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.resizeArea.normalWidth);
89+ priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.resizeArea.normalHeight);
90+ } else {
91+ priv.distanceX = pos.x;
92+ priv.distanceY = pos.y;
93+ }
94+
95+ priv.dragging = true;
96+ } else {
97+ priv.dragging = false;
98+ Mir.cursorName = "";
99+ }
100+ }
101+
102+ function handlePositionChanged(mouse, sensingPoints) {
103+ if (priv.dragging) {
104+ Mir.cursorName = "grabbing";
105+
106+ if (target.anyMaximized) { // restore from maximized when dragging away from edges/corners
107+ priv.progress = 0;
108+ target.restore(false, WindowStateStorage.WindowStateNormal);
109+ }
110+
111+ var pos = mapToItem(target.parent, mouse.x, mouse.y);
112+ // Use integer coordinate values to ensure that target is left in a pixel-aligned
113+ // position. Mouse movement could have subpixel precision, yielding a fractional
114+ // mouse position.
115+ target.requestedX = Math.round(pos.x - priv.distanceX);
116+ target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
117+
118+ if (sensingPoints) { // edge/corner detection when dragging via the touch overlay
119+ if (sensingPoints.topLeft.x < priv.triggerArea && sensingPoints.topLeft.y < PanelState.panelHeight + priv.triggerArea
120+ && target.canBeCornerMaximized) { // top left
121+ priv.progress = priv.progressInCorner(0, PanelState.panelHeight, sensingPoints.topLeft.x, sensingPoints.topLeft.y);
122+ priv.resetEdges();
123+ priv.nearTopLeftCorner = true;
124+ root.fakeMaximizeTopLeftAnimationRequested(priv.progress);
125+ } else if (sensingPoints.topRight.x > stageWidth - priv.triggerArea && sensingPoints.topRight.y < PanelState.panelHeight + priv.triggerArea
126+ && target.canBeCornerMaximized) { // top right
127+ priv.progress = priv.progressInCorner(stageWidth, PanelState.panelHeight, sensingPoints.topRight.x, sensingPoints.topRight.y);
128+ priv.resetEdges();
129+ priv.nearTopRightCorner = true;
130+ root.fakeMaximizeTopRightAnimationRequested(priv.progress);
131+ } else if (sensingPoints.bottomLeft.x < priv.triggerArea && sensingPoints.bottomLeft.y > stageHeight - priv.triggerArea
132+ && target.canBeCornerMaximized) { // bottom left
133+ priv.progress = priv.progressInCorner(0, stageHeight, sensingPoints.bottomLeft.x, sensingPoints.bottomLeft.y);
134+ priv.resetEdges();
135+ priv.nearBottomLeftCorner = true;
136+ root.fakeMaximizeBottomLeftAnimationRequested(priv.progress);
137+ } else if (sensingPoints.bottomRight.x > stageWidth - priv.triggerArea && sensingPoints.bottomRight.y > stageHeight - priv.triggerArea
138+ && target.canBeCornerMaximized) { // bottom right
139+ priv.progress = priv.progressInCorner(stageWidth, stageHeight, sensingPoints.bottomRight.x, sensingPoints.bottomRight.y);
140+ priv.resetEdges();
141+ priv.nearBottomRightCorner = true;
142+ root.fakeMaximizeBottomRightAnimationRequested(priv.progress);
143+ } else if (sensingPoints.left.x < priv.triggerArea && target.canBeMaximizedLeftRight) { // left
144+ priv.progress = MathUtils.clampAndProject(sensingPoints.left.x, priv.triggerArea, 0, 0, 1);
145+ priv.resetEdges();
146+ priv.nearLeftEdge = true;
147+ root.fakeMaximizeLeftAnimationRequested(priv.progress);
148+ } else if (sensingPoints.right.x > stageWidth - priv.triggerArea && target.canBeMaximizedLeftRight) { // right
149+ priv.progress = MathUtils.clampAndProject(sensingPoints.right.x, stageWidth - priv.triggerArea, stageWidth, 0, 1);
150+ priv.resetEdges();
151+ priv.nearRightEdge = true;
152+ root.fakeMaximizeRightAnimationRequested(priv.progress);
153+ } else if (sensingPoints.top.y < PanelState.panelHeight + priv.triggerArea && target.canBeMaximized) { // top
154+ priv.progress = MathUtils.clampAndProject(sensingPoints.top.y, PanelState.panelHeight + priv.triggerArea, 0, 0, 1);
155+ priv.resetEdges();
156+ priv.nearTopEdge = true;
157+ root.fakeMaximizeAnimationRequested(priv.progress);
158+ } else if (priv.nearLeftEdge || priv.nearRightEdge || priv.nearTopEdge || priv.nearTopLeftCorner || priv.nearTopRightCorner ||
159+ priv.nearBottomLeftCorner || priv.nearBottomRightCorner) {
160+ priv.progress = 0;
161+ priv.resetEdges();
162+ root.stopFakeAnimation();
163+ }
164+ }
165+ }
166+ }
167+
168+ function handleReleased(touchMode) {
169+ if (touchMode) {
170+ priv.progress = 0;
171+ priv.resetEdges();
172+ }
173+ if ((target.state == "normal" || target.state == "restored") && priv.progress == 0) {
174+ // save the x/y to restore to
175+ target.restoredX = target.x;
176+ target.restoredY = target.y;
177+ }
178+ }
179+}
qml/Stages/TopLevelSurfaceRepeater.qml+7−1
@@ -27,7 +27,13 @@ Repeater {
27 if (!startingUp) {
27 if (!startingUp) {
28 // the top-most surface must be the focused one.
28 // the top-most surface must be the focused one.
29 var topmostDelegate = itemAt(0);
29 var topmostDelegate = itemAt(0);
30+ if (topmostDelegate.focus) {
31+ // Delegate got focused while we were still starting up. Not good.
32+ // Force signal handler to run again
33+ topmostDelegate.onFocusChanged(true);
34+ } else {
30− topmostDelegate.focus = true;
35+ topmostDelegate.focus = true;
36+ }
31 }
37 }
32 }
38 }
33
39
qml/Stages/WindowControlsOverlay.qml+76−38
@@ -23,12 +23,26 @@ import "../Components/PanelState"
23Item {
23Item {
24 id: root
24 id: root
25 enabled: target && !target.fullscreen
25 enabled: target && !target.fullscreen
26+ anchors.fill: target
26
27
27 // to be set from outside
28 // to be set from outside
28 property Item target // appDelegate
29 property Item target // appDelegate
30+ property alias stageWidth: moveHandler.stageWidth
31+ property alias stageHeight: moveHandler.stageHeight
29
32
30 // to be read from outside
33 // to be read from outside
31 readonly property alias overlayShown: overlay.visible
34 readonly property alias overlayShown: overlay.visible
35+ readonly property alias dragging: priv.dragging
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 dragReleased()
32
46
33 TouchGestureArea {
47 TouchGestureArea {
34 id: gestureArea
48 id: gestureArea
@@ -51,16 +65,18 @@ Item {
51 readonly property bool recognizedDrag: recognizedPress && dragging
65 readonly property bool recognizedDrag: recognizedPress && dragging
52 onRecognizedDragChanged: {
66 onRecognizedDragChanged: {
53 if (recognizedDrag) {
67 if (recognizedDrag) {
54− priv.handlePressedChanged(true, tp.x, tp.y);
68+ moveHandler.handlePressedChanged(true, Qt.LeftButton, tp.x, tp.y);
55− } else if (!moveHandler.containsPress) { // prevent interfering with the central piece drag/move
69+ } else if (!mouseArea.containsPress) { // prevent interfering with the central piece drag/move
56− priv.dragging = false;
70+ moveHandler.handlePressedChanged(false, Qt.LeftButton);
71+ root.dragReleased();
72+ moveHandler.handleReleased(true);
57 }
73 }
58 }
74 }
59
75
60 readonly property point tp: recognizedPress ? Qt.point(touchPoints[0].x, touchPoints[0].y) : Qt.point(-1, -1)
76 readonly property point tp: recognizedPress ? Qt.point(touchPoints[0].x, touchPoints[0].y) : Qt.point(-1, -1)
61 onUpdated: {
77 onUpdated: {
62 if (recognizedDrag) {
78 if (recognizedDrag) {
63− priv.handlePositionChanged(tp.x, tp.y);
79+ moveHandler.handlePositionChanged(tp, priv.getSensingPoints());
64 }
80 }
65 }
81 }
66 }
82 }
@@ -69,33 +85,37 @@ Item {
69 Timer {
85 Timer {
70 id: overlayTimer
86 id: overlayTimer
71 interval: 2000
87 interval: 2000
72− repeat: priv.dragging || (priv.resizeArea && priv.resizeArea.dragging)
88+ repeat: priv.dragging
73 }
89 }
74
90
75 QtObject {
91 QtObject {
76 id: priv
92 id: priv
77− property real distanceX
78− property real distanceY
79− property bool dragging
80−
81 readonly property var resizeArea: root.target && root.target.resizeArea ? root.target.resizeArea : null
93 readonly property var resizeArea: root.target && root.target.resizeArea ? root.target.resizeArea : null
94+ readonly property bool ensureWindow: root.target.state == "normal" || root.target.state == "restored"
95+ readonly property bool dragging: moveHandler.dragging || (resizeArea && resizeArea.dragging)
82
96
83− function handlePressedChanged(pressed, mouseX, mouseY) {
97+ function getSensingPoints() {
84− if (pressed) {
98+ var xPoints = [];
85− var pos = mapToItem(root.target, mouseX, mouseY);
99+ var yPoints = [];
86− priv.distanceX = pos.x;
100+ for (var i = 0; i < gestureArea.touchPoints.length; i++) {
87− priv.distanceY = pos.y;
101+ var pt = gestureArea.touchPoints[i];
88− priv.dragging = true;
102+ xPoints.push(pt.x);
89− } else {
103+ yPoints.push(pt.y);
90− priv.dragging = false;
91 }
104 }
92− }
93
105
94− function handlePositionChanged(mouseX, mouseY) {
106+ var leftmost = Math.min.apply(Math, xPoints);
95− if (priv.dragging) {
107+ var rightmost = Math.max.apply(Math, xPoints);
108+ var topmost = Math.min.apply(Math, yPoints);
109+ var bottommost = Math.max.apply(Math, yPoints);
110+
111+ return {
112+ left: mapToItem(target.parent, leftmost, (topmost+bottommost)/2),
96− var pos = mapToItem(root.target.parent, mouseX, mouseY);
113+ top: mapToItem(target.parent, (leftmost+rightmost)/2, topmost),
97− root.target.requestedX = Math.round(pos.x - priv.distanceX);
114+ right: mapToItem(target.parent, rightmost, (topmost+bottommost)/2),
98− root.target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
115+ topLeft: mapToItem(target.parent, leftmost, topmost),
116+ topRight: mapToItem(target.parent, rightmost, topmost),
117+ bottomLeft: mapToItem(target.parent, leftmost, bottommost),
118+ bottomRight: mapToItem(target.parent, rightmost, bottommost)
99 }
119 }
100 }
120 }
101 }
121 }
@@ -113,8 +133,6 @@ Item {
113 UbuntuNumberAnimation {}
133 UbuntuNumberAnimation {}
114 }
134 }
115
135
116− readonly property bool anyMaximized: target && (target.maximized || target.maximizedLeft || target.maximizedRight)
117−
118 Image {
136 Image {
119 source: "graphics/arrows-centre.png"
137 source: "graphics/arrows-centre.png"
120 width: units.gu(10)
138 width: units.gu(10)
@@ -125,15 +143,33 @@ Item {
125
143
126 // move handler
144 // move handler
127 MouseArea {
145 MouseArea {
128− id: moveHandler
146+ id: mouseArea
129 anchors.fill: parent
147 anchors.fill: parent
130 visible: overlay.visible
148 visible: overlay.visible
131 enabled: visible
149 enabled: visible
132 hoverEnabled: true
150 hoverEnabled: true
133− cursorShape: priv.dragging ? Qt.ClosedHandCursor : (overlay.visible ? Qt.OpenHandCursor : Qt.ArrowCursor)
134
151
135− onPressedChanged: priv.handlePressedChanged(pressed, mouseX, mouseY)
152+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
136− onPositionChanged: priv.handlePositionChanged(mouseX, mouseY)
153+ onPositionChanged: moveHandler.handlePositionChanged(mouse)
154+ onReleased: {
155+ root.dragReleased();
156+ moveHandler.handleReleased();
157+ }
158+ }
159+
160+ MoveHandler {
161+ id: moveHandler
162+ objectName: "moveHandler"
163+ target: root.target
164+
165+ onFakeMaximizeAnimationRequested: root.fakeMaximizeAnimationRequested(amount)
166+ onFakeMaximizeLeftAnimationRequested: root.fakeMaximizeLeftAnimationRequested(amount)
167+ onFakeMaximizeRightAnimationRequested: root.fakeMaximizeRightAnimationRequested(amount)
168+ onFakeMaximizeTopLeftAnimationRequested: root.fakeMaximizeTopLeftAnimationRequested(amount)
169+ onFakeMaximizeTopRightAnimationRequested: root.fakeMaximizeTopRightAnimationRequested(amount)
170+ onFakeMaximizeBottomLeftAnimationRequested: root.fakeMaximizeBottomLeftAnimationRequested(amount)
171+ onFakeMaximizeBottomRightAnimationRequested: root.fakeMaximizeBottomRightAnimationRequested(amount)
172+ onStopFakeAnimation: root.stopFakeAnimation()
137 }
173 }
138
174
139 // dismiss area
175 // dismiss area
@@ -148,7 +184,7 @@ Item {
148 }
184 }
149
185
150 overlayTimer.stop();
186 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));
152 }
188 }
153 propagateComposedEvents: true
189 propagateComposedEvents: true
154 }
190 }
@@ -157,7 +193,7 @@ Item {
157 ResizeGrip { // top left
193 ResizeGrip { // top left
158 anchors.horizontalCenter: parent.left
194 anchors.horizontalCenter: parent.left
159 anchors.verticalCenter: parent.top
195 anchors.verticalCenter: parent.top
160− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
196+ visible: priv.ensureWindow || target.maximizedBottomRight
161 resizeTarget: priv.resizeArea
197 resizeTarget: priv.resizeArea
162 }
198 }
163
199
@@ -165,7 +201,7 @@ Item {
165 anchors.horizontalCenter: parent.horizontalCenter
201 anchors.horizontalCenter: parent.horizontalCenter
166 anchors.verticalCenter: parent.top
202 anchors.verticalCenter: parent.top
167 rotation: 45
203 rotation: 45
168− visible: target && !overlay.anyMaximized && !target.maximizedVertically
204+ visible: priv.ensureWindow || target.maximizedHorizontally || target.maximizedBottomLeft || target.maximizedBottomRight
169 resizeTarget: priv.resizeArea
205 resizeTarget: priv.resizeArea
170 }
206 }
171
207
@@ -173,7 +209,7 @@ Item {
173 anchors.horizontalCenter: parent.right
209 anchors.horizontalCenter: parent.right
174 anchors.verticalCenter: parent.top
210 anchors.verticalCenter: parent.top
175 rotation: 90
211 rotation: 90
176− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
212+ visible: priv.ensureWindow || target.maximizedBottomLeft
177 resizeTarget: priv.resizeArea
213 resizeTarget: priv.resizeArea
178 }
214 }
179
215
@@ -181,14 +217,15 @@ Item {
181 anchors.horizontalCenter: parent.right
217 anchors.horizontalCenter: parent.right
182 anchors.verticalCenter: parent.verticalCenter
218 anchors.verticalCenter: parent.verticalCenter
183 rotation: 135
219 rotation: 135
184− visible: target && !target.maximizedRight && !target.maximized && !target.maximizedHorizontally
220+ visible: priv.ensureWindow || target.maximizedVertically || target.maximizedLeft ||
221+ target.maximizedTopLeft || target.maximizedBottomLeft
185 resizeTarget: priv.resizeArea
222 resizeTarget: priv.resizeArea
186 }
223 }
187
224
188 ResizeGrip { // bottom right
225 ResizeGrip { // bottom right
189 anchors.horizontalCenter: parent.right
226 anchors.horizontalCenter: parent.right
190 anchors.verticalCenter: parent.bottom
227 anchors.verticalCenter: parent.bottom
191− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
228+ visible: priv.ensureWindow || target.maximizedTopLeft
192 resizeTarget: priv.resizeArea
229 resizeTarget: priv.resizeArea
193 }
230 }
194
231
@@ -196,7 +233,7 @@ Item {
196 anchors.horizontalCenter: parent.horizontalCenter
233 anchors.horizontalCenter: parent.horizontalCenter
197 anchors.verticalCenter: parent.bottom
234 anchors.verticalCenter: parent.bottom
198 rotation: 45
235 rotation: 45
199− visible: target && !overlay.anyMaximized && !target.maximizedVertically
236+ visible: priv.ensureWindow || target.maximizedHorizontally || target.maximizedTopLeft || target.maximizedTopRight
200 resizeTarget: priv.resizeArea
237 resizeTarget: priv.resizeArea
201 }
238 }
202
239
@@ -204,7 +241,7 @@ Item {
204 anchors.horizontalCenter: parent.left
241 anchors.horizontalCenter: parent.left
205 anchors.verticalCenter: parent.bottom
242 anchors.verticalCenter: parent.bottom
206 rotation: 90
243 rotation: 90
207− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
244+ visible: priv.ensureWindow || target.maximizedTopRight
208 resizeTarget: priv.resizeArea
245 resizeTarget: priv.resizeArea
209 }
246 }
210
247
@@ -212,7 +249,8 @@ Item {
212 anchors.horizontalCenter: parent.left
249 anchors.horizontalCenter: parent.left
213 anchors.verticalCenter: parent.verticalCenter
250 anchors.verticalCenter: parent.verticalCenter
214 rotation: 135
251 rotation: 135
215− visible: target && !target.maximizedLeft && !target.maximized && !target.maximizedHorizontally
252+ visible: priv.ensureWindow || target.maximizedVertically || target.maximizedRight ||
253+ target.maximizedTopRight || target.maximizedBottomRight
216 resizeTarget: priv.resizeArea
254 resizeTarget: priv.resizeArea
217 }
255 }
218 }
256 }
qml/Stages/WindowDecoration.qml+10−38
@@ -15,22 +15,24 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18−import Unity.Application 0.1 // For Mir singleton
19import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
20import "../Components"
19import "../Components"
21−import "../Components/PanelState"
22
20
23MouseArea {
21MouseArea {
24 id: root
22 id: root
25 clip: true
23 clip: true
26
24
27− property Item target
25+ property Item target // appDelegate
28 property alias title: titleLabel.text
26 property alias title: titleLabel.text
29 property alias maximizeButtonShown: buttons.maximizeButtonShown
27 property alias maximizeButtonShown: buttons.maximizeButtonShown
30 property bool active: false
28 property bool active: false
31− acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
32 property alias overlayShown: buttons.overlayShown
29 property alias overlayShown: buttons.overlayShown
33
30
31+ readonly property real buttonsWidth: buttons.width + row.spacing
32+
33+ acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
34+ hoverEnabled: true
35+
34 signal closeClicked()
36 signal closeClicked()
35 signal minimizeClicked()
37 signal minimizeClicked()
36 signal maximizeClicked()
38 signal maximizeClicked()
@@ -38,42 +40,11 @@ MouseArea {
38 signal maximizeVerticallyClicked()
40 signal maximizeVerticallyClicked()
39
41
40 onDoubleClicked: {
42 onDoubleClicked: {
41− if (maximizeButtonShown && mouse.button == Qt.LeftButton) {
43+ if (target.canBeMaximized && mouse.button == Qt.LeftButton) {
42 root.maximizeClicked();
44 root.maximizeClicked();
43 }
45 }
44 }
46 }
45
47
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−
77 // do not let unhandled wheel event pass thru the decoration
48 // do not let unhandled wheel event pass thru the decoration
78 onWheel: wheel.accepted = true;
49 onWheel: wheel.accepted = true;
79
50
@@ -85,6 +56,7 @@ MouseArea {
85 }
56 }
86
57
87 Row {
58 Row {
59+ id: row
88 anchors {
60 anchors {
89 fill: parent
61 fill: parent
90 leftMargin: overlayShown ? units.gu(5) : units.gu(1)
62 leftMargin: overlayShown ? units.gu(5) : units.gu(1)
@@ -105,8 +77,8 @@ MouseArea {
105 onCloseClicked: root.closeClicked();
77 onCloseClicked: root.closeClicked();
106 onMinimizeClicked: root.minimizeClicked();
78 onMinimizeClicked: root.minimizeClicked();
107 onMaximizeClicked: root.maximizeClicked();
79 onMaximizeClicked: root.maximizeClicked();
108− onMaximizeHorizontallyClicked: root.maximizeHorizontallyClicked();
80+ onMaximizeHorizontallyClicked: if (root.target.canBeMaximizedHorizontally) root.maximizeHorizontallyClicked();
109− onMaximizeVerticallyClicked: root.maximizeVerticallyClicked();
81+ onMaximizeVerticallyClicked: if (root.target.canBeMaximizedVertically) root.maximizeVerticallyClicked();
110 closeButtonShown: root.target.application.appId !== "unity8-dash"
82 closeButtonShown: root.target.application.appId !== "unity8-dash"
111 }
83 }
112
84
qml/Stages/WindowResizeArea.qml+13−24
@@ -30,6 +30,8 @@ MouseArea {
30
30
31 property var windowStateStorage: WindowStateStorage
31 property var windowStateStorage: WindowStateStorage
32 readonly property alias dragging: d.dragging
32 readonly property alias dragging: d.dragging
33+ readonly property alias normalWidth: priv.normalWidth
34+ readonly property alias normalHeight: priv.normalHeight
33
35
34 // The target item managed by this. Must be a parent or a sibling
36 // The target item managed by this. Must be a parent or a sibling
35 // The area will anchor to it and manage resize events
37 // The area will anchor to it and manage resize events
@@ -54,7 +56,7 @@ MouseArea {
54 property int normalHeight: 0
56 property int normalHeight: 0
55
57
56 function updateNormalGeometry() {
58 function updateNormalGeometry() {
57− if (root.target.state == "normal") {
59+ if (root.target.state == "normal" || root.target.state == "restored") {
58 normalX = root.target.requestedX
60 normalX = root.target.requestedX
59 normalY = root.target.requestedY
61 normalY = root.target.requestedY
60 normalWidth = root.target.width
62 normalWidth = root.target.width
@@ -83,34 +85,21 @@ MouseArea {
83 target.requestedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight); });
85 target.requestedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight); });
84
86
85 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
87 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
86− switch (windowState) {
88+ target.restore(false /* animated */, 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− }
108
89
109 priv.updateNormalGeometry();
90 priv.updateNormalGeometry();
91+
92+ // initialize the x/y to restore to
93+ target.restoredX = priv.normalX;
94+ target.restoredY = priv.normalY;
110 }
95 }
111
96
112 function saveWindowState() {
97 function saveWindowState() {
98+ var state = target.windowState;
99+ if (state === WindowStateStorage.WindowStateRestored) {
100+ state = WindowStateStorage.WindowStateNormal;
101+ }
113− windowStateStorage.saveState(root.windowId, target.windowState & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
102+ windowStateStorage.saveState(root.windowId, state & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
114 windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight));
103 windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight));
115 }
104 }
116
105
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+8
@@ -17,8 +17,16 @@
17import QtQuick 2.4
17import QtQuick 2.4
18
18
19Item {
19Item {
20+ property int topBoundaryOffset // effectively panel height
20 property Item confiningItem
21 property Item confiningItem
22+
21 signal pushedLeftBoundary(real amount, int buttons)
23 signal pushedLeftBoundary(real amount, int buttons)
22 signal pushedRightBoundary(real amount, int buttons)
24 signal pushedRightBoundary(real amount, int buttons)
25+ signal pushedTopBoundary(real amount, int buttons)
26+ signal pushedTopLeftCorner(real amount, int buttons)
27+ signal pushedTopRightCorner(real amount, int buttons)
28+ signal pushedBottomLeftCorner(real amount, int buttons)
29+ signal pushedBottomRightCorner(real amount, int buttons)
30+ signal pushStopped()
23 signal mouseMoved()
31 signal mouseMoved()
24}
32}
tests/mocks/Utils/windowstatestorage.cpp+2−2
@@ -41,7 +41,7 @@ void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect
41 m_geometry[windowId] = rect;
41 m_geometry[windowId] = rect;
42}
42}
43
43
44−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
44+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
45{
45{
46 if (!m_geometry.contains(windowId)) return defaultValue;
46 if (!m_geometry.contains(windowId)) return defaultValue;
47 return m_geometry.value(windowId).toRect();
47 return m_geometry.value(windowId).toRect();
@@ -70,7 +70,7 @@ void WindowStateStorage::saveState(const QString &windowId, WindowState state)
70 m_state[windowId] = state;
70 m_state[windowId] = state;
71}
71}
72
72
73−WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue)
73+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
74{
74{
75 if (!m_state.contains(windowId)) return defaultValue;
75 if (!m_state.contains(windowId)) return defaultValue;
76 return m_state.value(windowId);
76 return m_state.value(windowId);
tests/mocks/Utils/windowstatestorage.h+8−3
@@ -32,7 +32,12 @@ public:
32 WindowStateMaximizedLeft = 1 << 4,
32 WindowStateMaximizedLeft = 1 << 4,
33 WindowStateMaximizedRight = 1 << 5,
33 WindowStateMaximizedRight = 1 << 5,
34 WindowStateMaximizedHorizontally = 1 << 6,
34 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
36 };
41 };
37 Q_DECLARE_FLAGS(WindowStates, WindowState)
42 Q_DECLARE_FLAGS(WindowStates, WindowState)
38#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
43#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
@@ -42,10 +47,10 @@ public:
42 WindowStateStorage(QObject *parent = 0);
47 WindowStateStorage(QObject *parent = 0);
43
48
44 Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
49 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;
46
51
47 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
52 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;
49
54
50 Q_INVOKABLE void saveStage(const QString &appId, int stage);
55 Q_INVOKABLE void saveStage(const QString &appId, int stage);
51 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
56 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
tests/qmltests/Panel/tst_Panel.qml−2
@@ -388,8 +388,6 @@ IndicatorTest {
388 tap(panel, stepLength * i, tapY);
388 tap(panel, stepLength * i, tapY);
389 tryCompare(panel.indicators, "fullyClosed", true);
389 tryCompare(panel.indicators, "fullyClosed", true);
390 }
390 }
391−
392− compare(backgroundPressedSpy.count, 0);
393 }
391 }
394
392
395 function test_darkenedAreaEatsAllEvents() {
393 function test_darkenedAreaEatsAllEvents() {
tests/qmltests/Stages/tst_DesktopStage.qml+2−2
@@ -487,7 +487,7 @@ Item {
487
487
488 // click again to restore
488 // click again to restore
489 mouseClick(dialerMaximizeButton);
489 mouseClick(dialerMaximizeButton);
490− tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateNormal);
490+ tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateRestored);
491 }
491 }
492
492
493 function test_windowMaximizeVertically() {
493 function test_windowMaximizeVertically() {
@@ -502,7 +502,7 @@ Item {
502
502
503 // click again to restore
503 // click again to restore
504 mouseClick(dialerMaximizeButton);
504 mouseClick(dialerMaximizeButton);
505− tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateNormal);
505+ tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateRestored);
506 }
506 }
507
507
508 function test_smashCursorKeys() {
508 function test_smashCursorKeys() {
tests/qmltests/Stages/tst_WindowResizeArea.qml+9−1
@@ -60,9 +60,12 @@ Item {
60 property real heightIncrement: 0
60 property real heightIncrement: 0
61
61
62 property int windowState: WindowStateStorage.WindowStateNormal
62 property int windowState: WindowStateStorage.WindowStateNormal
63+ property real restoredX
64+ property real restoredY
63
65
64 states: [
66 states: [
65 State { name: "normal"; when: windowState == WindowStateStorage.WindowStateNormal },
67 State { name: "normal"; when: windowState == WindowStateStorage.WindowStateNormal },
68+ State { name: "restored"; when: windowState == WindowStateStorage.WindowStateRestored },
66 State { name: "maximized"; when: windowState == WindowStateStorage.WindowStateMaximized }
69 State { name: "maximized"; when: windowState == WindowStateStorage.WindowStateMaximized }
67 ]
70 ]
68
71
@@ -71,7 +74,12 @@ Item {
71 }
74 }
72
75
73 function restoreFromMaximized() {
76 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
75 }
83 }
76
84
77 WindowResizeArea {
85 WindowResizeArea {
tests/qmltests/tst_Shell.qml+1−4
@@ -2664,10 +2664,7 @@ Rectangle {
2664 appDelegate.maximize();
2664 appDelegate.maximize();
2665 tryCompare(appDelegate, "state", "maximized");
2665 tryCompare(appDelegate, "state", "maximized");
2666
2666
2667− mousePress(panel);
2667+ mouseDrag(panel, panel.width/3, panel.height/2, 0, shell.height/3, Qt.LeftButton, Qt.NoModifier, 500);
2668− mouseMove(shell, shell.width/2, shell.height/2);
2669− mouseRelease(shell);
2670−
2671 tryCompare(appDelegate, "state", "normal");
2668 tryCompare(appDelegate, "state", "normal");
2672 }
2669 }
2673
2670