on xenial help sign in
merge lp:~lukas-kde/unity8/edgeMaximize
5953e33Daniel d'Andrada
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+42−14
@@ -22,6 +22,8 @@
22
22
23#include <QQuickWindow>
23#include <QQuickWindow>
24#include <QGuiApplication>
24#include <QGuiApplication>
25+#include <QQmlProperty>
26+#include <QtMath>
25
27
26#include <qpa/qwindowsysteminterface.h>
28#include <qpa/qwindowsysteminterface.h>
27
29
@@ -48,23 +50,35 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
48 QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
50 QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
49 m_accumulatedMovement -= appliedMovement;
51 m_accumulatedMovement -= appliedMovement;
50
52
51− qreal newX = x() + appliedMovement.x();
53+ const qreal newX = x() + appliedMovement.x();
52− if (newX < 0) {
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
53 Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
71 Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
54− newX = 0;
72+ } else if (newX >= sceneWidth) { // right edge
55− } else if (newX >= parentItem()->width()) {
56− Q_EMIT pushedRightBoundary(newX - (parentItem()->width() - 1), buttons);
73+ Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
57− newX = parentItem()->width() - 1;
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();
58 }
78 }
59− setX(newX);
60
79
61− qreal newY = y() + appliedMovement.y();
80+ setX(qBound(0.0, newX, sceneWidth - 1));
62− if (newY < 0) {
63− newY = 0;
64− } else if (newY >= parentItem()->height()) {
65− newY = parentItem()->height() - 1;
66− }
67− setY(newY);
81+ setY(qBound(0.0, newY, sceneHeight - 1));
68
82
69 QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
83 QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
70 QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
84 QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
@@ -82,6 +96,20 @@ void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::Keyb
82 QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
96 QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
83}
97}
84
98
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+
85void MousePointer::itemChange(ItemChange change, const ItemChangeData &value)
113void MousePointer::itemChange(ItemChange change, const ItemChangeData &value)
86{
114{
87 if (change == ItemSceneChange) {
115 if (change == ItemSceneChange) {
plugins/Cursor/MousePointer.h+15
@@ -27,6 +27,8 @@
27
27
28class MousePointer : public MirMousePointerInterface {
28class MousePointer : public MirMousePointerInterface {
29 Q_OBJECT
29 Q_OBJECT
30+ Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
31+
30public:
32public:
31 MousePointer(QQuickItem *parent = nullptr);
33 MousePointer(QQuickItem *parent = nullptr);
32
34
@@ -38,6 +40,9 @@ public:
38
40
39 void setCustomCursor(const QCursor &) override;
41 void setCustomCursor(const QCursor &) override;
40
42
43+ int topBoundaryOffset() const;
44+ void setTopBoundaryOffset(int topBoundaryOffset);
45+
41public Q_SLOTS:
46public Q_SLOTS:
42 void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
47 void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
43 Qt::KeyboardModifiers modifiers) override;
48 Qt::KeyboardModifiers modifiers) override;
@@ -46,8 +51,16 @@ public Q_SLOTS:
46Q_SIGNALS:
51Q_SIGNALS:
47 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
52 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
48 void pushedRightBoundary(qreal amount, Qt::MouseButtons buttons);
53 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();
49 void mouseMoved();
60 void mouseMoved();
50
61
62+ void topBoundaryOffsetChanged(int topBoundaryOffset);
63+
51protected:
64protected:
52 void itemChange(ItemChange change, const ItemChangeData &value) override;
65 void itemChange(ItemChange change, const ItemChangeData &value) override;
53
66
@@ -64,6 +77,8 @@ private:
64
77
65 // Accumulated, unapplied, mouse movement.
78 // Accumulated, unapplied, mouse movement.
66 QPointF m_accumulatedMovement;
79 QPointF m_accumulatedMovement;
80+
81+ int m_topBoundaryOffset{0};
67};
82};
68
83
69#endif // MOUSEPOINTER_H
84#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
@@ -26,6 +26,7 @@ QtObject {
26 property bool closeButtonShown: true
26 property bool closeButtonShown: true
27 property bool dropShadow: false
27 property bool dropShadow: false
28 property int panelHeight: 0
28 property int panelHeight: 0
29+ property var maximizedAppDelegate: null
29
30
30 signal closeClicked()
31 signal closeClicked()
31 signal minimizeClicked()
32 signal minimizeClicked()
qml/Components/WindowControlButtons.qml−1
@@ -25,7 +25,6 @@ Row {
25 }
25 }
26
26
27 // to be set from outside
27 // to be set from outside
28− property Item target
29 property bool active: false
28 property bool active: false
30 property bool windowIsMaximized: false
29 property bool windowIsMaximized: false
31 property bool closeButtonShown: true
30 property bool closeButtonShown: true
qml/OrientedShell.qml+1−1
@@ -260,7 +260,7 @@ Item {
260 nativeWidth: root.width
260 nativeWidth: root.width
261 nativeHeight: root.height
261 nativeHeight: root.height
262 mode: applicationArguments.mode
262 mode: applicationArguments.mode
263− hasMouse: miceModel.count + touchPadModel.count > 0
263+ hasMouse: pointerInputDevices > 0
264 // TODO: Factor in if the current screen is a touch screen and if the user wants to
264 // TODO: Factor in if the current screen is a touch screen and if the user wants to
265 // have multiple keyboards around. For now we only enable one keyboard at a time
265 // have multiple keyboards around. For now we only enable one keyboard at a time
266 // thus hiding it here if there is a physical one around or if we have a second
266 // 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 {
103 onClicked: callHint.visible ? callHint.showLiveCall() : PanelState.focusMaximizedApp()
103 onClicked: callHint.visible ? callHint.showLiveCall() : PanelState.focusMaximizedApp()
104 onDoubleClicked: PanelState.restoreClicked()
104 onDoubleClicked: PanelState.restoreClicked()
105
105
106− property bool mouseWasPressed: false
106+ property Item delegate: null // appDelegate that's used to drag-and-restore a maximized window
107− onPressed: mouseWasPressed = containsPress
107+
108− onMouseYChanged: {
108+ onPressedChanged: {
109− if (mouseWasPressed && mouseY > panelHeight) {
109+ if (!delegate && PanelState.maximizedAppDelegate && pressed) {
110− PanelState.restoreClicked(); // restore the window when "dragging" the panel down
110+ delegate = PanelState.maximizedAppDelegate;
111− mouseWasPressed = false;
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;
112 }
128 }
113 }
129 }
114
130
qml/Shell.qml+40
@@ -764,6 +764,7 @@ StyledItem {
764 id: cursor
764 id: cursor
765 visible: shell.hasMouse
765 visible: shell.hasMouse
766 z: itemGrabber.z + 1
766 z: itemGrabber.z + 1
767+ topBoundaryOffset: panel.panelHeight
767
768
768 property bool mouseNeverMoved: true
769 property bool mouseNeverMoved: true
769 Binding {
770 Binding {
@@ -777,9 +778,15 @@ StyledItem {
777
778
778 height: units.gu(3)
779 height: units.gu(3)
779
780
781+ readonly property var previewRectangle: applicationsDisplayLoader.item && applicationsDisplayLoader.item.previewRectangle &&
782+ applicationsDisplayLoader.item.previewRectangle.targetDragging ?
783+ applicationsDisplayLoader.item.previewRectangle : null
784+
780 onPushedLeftBoundary: {
785 onPushedLeftBoundary: {
781 if (buttons === Qt.NoButton) {
786 if (buttons === Qt.NoButton) {
782 launcher.pushEdge(amount);
787 launcher.pushEdge(amount);
788+ } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
789+ previewRectangle.maximizeLeft(amount);
783 }
790 }
784 }
791 }
785
792
@@ -787,6 +794,39 @@ StyledItem {
787 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
794 if (buttons === Qt.NoButton && applicationsDisplayLoader.item
788 && applicationsDisplayLoader.item.pushRightEdge) {
795 && applicationsDisplayLoader.item.pushRightEdge) {
789 applicationsDisplayLoader.item.pushRightEdge(amount);
796 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();
790 }
830 }
791 }
831 }
792
832
qml/Stages/DecoratedWindow.qml+21
@@ -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 moveHandler: moveHandler
55
58
56 signal closeClicked()
59 signal closeClicked()
57 signal maximizeClicked()
60 signal maximizeClicked()
@@ -60,6 +63,8 @@ FocusScope {
60 signal minimizeClicked()
63 signal minimizeClicked()
61 signal decorationPressed()
64 signal decorationPressed()
62
65
66+ signal shouldCommitSnapWindow()
67+
63 Rectangle {
68 Rectangle {
64 id: selectionHighlight
69 id: selectionHighlight
65 anchors.fill: parent
70 anchors.fill: parent
@@ -101,6 +106,22 @@ FocusScope {
101 onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
106 onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
102 onMinimizeClicked: root.minimizeClicked();
107 onMinimizeClicked: root.minimizeClicked();
103 onPressed: root.decorationPressed();
108 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()
104 }
125 }
105
126
106 ApplicationWindow {
127 ApplicationWindow {
qml/Stages/DesktopStage.qml+145−67
@@ -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
@@ -68,28 +71,27 @@ AbstractStage {
68 id: maximizeWindowShortcut
71 id: maximizeWindowShortcut
69 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
72 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
70 onTriggered: priv.focusedAppDelegate.maximize()
73 onTriggered: priv.focusedAppDelegate.maximize()
71− active: priv.focusedAppDelegate !== null
74+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximized
72 }
75 }
73
76
74 GlobalShortcut {
77 GlobalShortcut {
75 id: maximizeWindowLeftShortcut
78 id: maximizeWindowLeftShortcut
76 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
79 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
77 onTriggered: priv.focusedAppDelegate.maximizeLeft()
80 onTriggered: priv.focusedAppDelegate.maximizeLeft()
78− active: priv.focusedAppDelegate !== null
81+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
79 }
82 }
80
83
81 GlobalShortcut {
84 GlobalShortcut {
82 id: maximizeWindowRightShortcut
85 id: maximizeWindowRightShortcut
83 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
86 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
84 onTriggered: priv.focusedAppDelegate.maximizeRight()
87 onTriggered: priv.focusedAppDelegate.maximizeRight()
85− active: priv.focusedAppDelegate !== null
88+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
86 }
89 }
87
90
88 GlobalShortcut {
91 GlobalShortcut {
89 id: minimizeRestoreShortcut
92 id: minimizeRestoreShortcut
90 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
93 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
91− onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight ||
94+ onTriggered: priv.focusedAppDelegate.anyMaximized
92− priv.focusedAppDelegate.maximizedHorizontally || priv.focusedAppDelegate.maximizedVertically
93 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
95 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
94 active: priv.focusedAppDelegate !== null
96 active: priv.focusedAppDelegate !== null
95 }
97 }
@@ -206,6 +208,12 @@ AbstractStage {
206 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
208 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
207 }
209 }
208
210
211+ Binding {
212+ target: PanelState
213+ property: "maximizedAppDelegate"
214+ value: priv.foregroundMaximizedAppDelegate ? priv.foregroundMaximizedAppDelegate : null
215+ }
216+
209 Component.onDestruction: {
217 Component.onDestruction: {
210 PanelState.title = "";
218 PanelState.title = "";
211 PanelState.buttonsVisible = false;
219 PanelState.buttonsVisible = false;
@@ -276,7 +284,7 @@ AbstractStage {
276 value: appDelegate.requestedY -
284 value: appDelegate.requestedY -
277 Math.min(appDelegate.requestedY - PanelState.panelHeight,
285 Math.min(appDelegate.requestedY - PanelState.panelHeight,
278 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
286 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")
280 && SurfaceManager.inputMethodSurface
288 && SurfaceManager.inputMethodSurface
281 && SurfaceManager.inputMethodSurface.state != Mir.HiddenState
289 && SurfaceManager.inputMethodSurface.state != Mir.HiddenState
282 && SurfaceManager.inputMethodSurface.state != Mir.MinimizedState
290 && SurfaceManager.inputMethodSurface.state != Mir.MinimizedState
@@ -291,7 +299,7 @@ AbstractStage {
291 onShellOrientationAngleChanged: {
299 onShellOrientationAngleChanged: {
292 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
300 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
293 if (application && application.rotatesWindowContents) {
301 if (application && application.rotatesWindowContents) {
294− if (state == "normal") {
302+ if (state == "normal" || state == "restored") {
295 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
303 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
296 angleDiff = (360 + angleDiff) % 360;
304 angleDiff = (360 + angleDiff) % 360;
297 if (angleDiff === 90 || angleDiff === 270) {
305 if (angleDiff === 90 || angleDiff === 270) {
@@ -317,14 +325,29 @@ AbstractStage {
317 property int requestedWidth: -1
325 property int requestedWidth: -1
318 property int requestedHeight: -1
326 property int requestedHeight: -1
319
327
320− readonly property bool maximized: windowState & WindowStateStorage.WindowStateMaximized
328+ readonly property bool maximized: windowState === WindowStateStorage.WindowStateMaximized
321− readonly property bool maximizedLeft: windowState & WindowStateStorage.WindowStateMaximizedLeft
329+ readonly property bool maximizedLeft: windowState === WindowStateStorage.WindowStateMaximizedLeft
322− readonly property bool maximizedRight: windowState & WindowStateStorage.WindowStateMaximizedRight
330+ readonly property bool maximizedRight: windowState === WindowStateStorage.WindowStateMaximizedRight
323− readonly property bool maximizedHorizontally: windowState & WindowStateStorage.WindowStateMaximizedHorizontally
331+ readonly property bool maximizedHorizontally: windowState === WindowStateStorage.WindowStateMaximizedHorizontally
324− readonly property bool maximizedVertically: windowState & WindowStateStorage.WindowStateMaximizedVertically
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+
325 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
340 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
326 readonly property alias fullscreen: decoratedWindow.fullscreen
341 readonly property alias fullscreen: decoratedWindow.fullscreen
327
342
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+
328 property int windowState: WindowStateStorage.WindowStateNormal
351 property int windowState: WindowStateStorage.WindowStateNormal
329 property bool animationsEnabled: true
352 property bool animationsEnabled: true
330 property alias title: decoratedWindow.title
353 property alias title: decoratedWindow.title
@@ -335,6 +358,7 @@ AbstractStage {
335 readonly property var surface: model.surface
358 readonly property var surface: model.surface
336 readonly property alias resizeArea: resizeArea
359 readonly property alias resizeArea: resizeArea
337 readonly property alias focusedSurface: decoratedWindow.focusedSurface
360 readonly property alias focusedSurface: decoratedWindow.focusedSurface
361+ readonly property var moveHandler: touchControls.overlayShown ? touchControls.moveHandler : decoratedWindow.moveHandler
338
362
339 readonly property bool isDash: model.application.appId == "unity8-dash"
363 readonly property bool isDash: model.application.appId == "unity8-dash"
340
364
@@ -342,7 +366,7 @@ AbstractStage {
342 if (spread.state == "altTab") {
366 if (spread.state == "altTab") {
343 spread.cancel();
367 spread.cancel();
344 }
368 }
345− appDelegate.restore();
369+ appDelegate.restore(true /* animated */, appDelegate.windowState);
346 }
370 }
347 Connections {
371 Connections {
348 target: model.surface
372 target: model.surface
@@ -362,9 +386,6 @@ AbstractStage {
362 }
386 }
363
387
364 onFocusChanged: {
388 onFocusChanged: {
365− if (appRepeater.startingUp)
366− return;
367−
368 if (focus) {
389 if (focus) {
369 // If we're orphan (!parent) it means this stage is no longer the current one
390 // If we're orphan (!parent) it means this stage is no longer the current one
370 // and will be deleted shortly. So we should no longer have a say over the model
391 // and will be deleted shortly. So we should no longer have a say over the model
@@ -450,28 +471,34 @@ AbstractStage {
450 animationsEnabled = (animated === undefined) || animated;
471 animationsEnabled = (animated === undefined) || animated;
451 windowState = WindowStateStorage.WindowStateMaximizedVertically;
472 windowState = WindowStateStorage.WindowStateMaximizedVertically;
452 }
473 }
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+ }
453 function minimize(animated) {
490 function minimize(animated) {
454 animationsEnabled = (animated === undefined) || animated;
491 animationsEnabled = (animated === undefined) || animated;
455 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
492 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
456 }
493 }
457 function restoreFromMaximized(animated) {
494 function restoreFromMaximized(animated) {
458 animationsEnabled = (animated === undefined) || animated;
495 animationsEnabled = (animated === undefined) || animated;
459− windowState = WindowStateStorage.WindowStateNormal;
496+ windowState = WindowStateStorage.WindowStateRestored;
460 }
497 }
461− function restore(animated) {
498+ function restore(animated,state) {
462 animationsEnabled = (animated === undefined) || animated;
499 animationsEnabled = (animated === undefined) || animated;
500+ windowState = state || WindowStateStorage.WindowStateRestored;
463 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
501 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−
475 focus = true;
502 focus = true;
476 }
503 }
477
504
@@ -488,9 +515,14 @@ AbstractStage {
488 duration: UbuntuAnimation.SnapDuration
515 duration: UbuntuAnimation.SnapDuration
489 }
516 }
490
517
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+
491 states: [
523 states: [
492 State {
524 State {
493− name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
525+ name: "fullscreen"; when: appDelegate.fullscreen && !appDelegate.minimized
494 PropertyChanges {
526 PropertyChanges {
495 target: appDelegate;
527 target: appDelegate;
496 x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
528 x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
@@ -509,61 +541,94 @@ AbstractStage {
509 }
541 }
510 },
542 },
511 State {
543 State {
512− name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
544+ name: "restored";
545+ when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
546+ extend: "normal"
513 PropertyChanges {
547 PropertyChanges {
514 target: appDelegate;
548 target: appDelegate;
515− x: root.leftMargin;
549+ requestedX: restoredX;
516− y: 0;
550+ requestedY: restoredY;
517− visuallyMinimized: false;
518− visuallyMaximized: true
519 }
551 }
552+ },
553+ State {
554+ name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
520 PropertyChanges {
555 PropertyChanges {
521− target: decoratedWindow
556+ target: appDelegate;
557+ requestedX: root.leftMargin;
558+ requestedY: 0;
522 requestedWidth: appContainer.width - root.leftMargin;
559 requestedWidth: appContainer.width - root.leftMargin;
523 requestedHeight: appContainer.height;
560 requestedHeight: appContainer.height;
561+ visuallyMinimized: false;
562+ visuallyMaximized: true
524 }
563 }
525 },
564 },
526 State {
565 State {
527 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
566 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
528 PropertyChanges {
567 PropertyChanges {
529 target: appDelegate
568 target: appDelegate
530− x: root.leftMargin
569+ requestedX: root.leftMargin
531− y: PanelState.panelHeight
570+ requestedY: PanelState.panelHeight
532− }
533− PropertyChanges {
534− target: decoratedWindow
535 requestedWidth: (appContainer.width - root.leftMargin)/2
571 requestedWidth: (appContainer.width - root.leftMargin)/2
536 requestedHeight: appContainer.height - PanelState.panelHeight
572 requestedHeight: appContainer.height - PanelState.panelHeight
537 }
573 }
538 },
574 },
539 State {
575 State {
540 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
576 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
577+ extend: "maximizedLeft"
541 PropertyChanges {
578 PropertyChanges {
542 target: appDelegate;
579 target: appDelegate;
543− x: (appContainer.width + root.leftMargin)/2
580+ requestedX: (appContainer.width + root.leftMargin)/2
544− y: PanelState.panelHeight
545 }
581 }
582+ },
583+ State {
584+ name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
546 PropertyChanges {
585 PropertyChanges {
547− target: decoratedWindow
586+ target: appDelegate
587+ requestedX: root.leftMargin
588+ requestedY: PanelState.panelHeight
548 requestedWidth: (appContainer.width - root.leftMargin)/2
589 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
550 }
617 }
551 },
618 },
552 State {
619 State {
553 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
620 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
554− PropertyChanges { target: appDelegate; x: root.leftMargin }
621+ PropertyChanges { target: appDelegate; requestedX: root.leftMargin; requestedY: requestedY; requestedWidth: appContainer.width - root.leftMargin }
555− PropertyChanges { target: decoratedWindow; requestedWidth: appContainer.width - root.leftMargin }
556 },
622 },
557 State {
623 State {
558 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
624 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
559− PropertyChanges { target: appDelegate; y: PanelState.panelHeight }
625+ PropertyChanges { target: appDelegate; requestedX: requestedX; requestedY: PanelState.panelHeight; requestedHeight: appContainer.height - PanelState.panelHeight }
560− PropertyChanges { target: decoratedWindow; requestedHeight: appContainer.height - PanelState.panelHeight }
561 },
626 },
562 State {
627 State {
563 name: "minimized"; when: appDelegate.minimized
628 name: "minimized"; when: appDelegate.minimized
564 PropertyChanges {
629 PropertyChanges {
565 target: appDelegate;
630 target: appDelegate;
566− x: -appDelegate.width / 2;
631+ requestedX: -appDelegate.width / 2;
567 scale: units.gu(5) / appDelegate.width;
632 scale: units.gu(5) / appDelegate.width;
568 opacity: 0;
633 opacity: 0;
569 visuallyMinimized: true;
634 visuallyMinimized: true;
@@ -573,21 +638,17 @@ AbstractStage {
573 ]
638 ]
574 transitions: [
639 transitions: [
575 Transition {
640 Transition {
576− to: "normal"
641+ to: "normal,restored"
577 enabled: appDelegate.animationsEnabled
642 enabled: appDelegate.animationsEnabled
578 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
643 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
579− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
644+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
580− UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
581 },
645 },
582 Transition {
646 Transition {
583 to: "minimized"
647 to: "minimized"
584 enabled: appDelegate.animationsEnabled
648 enabled: appDelegate.animationsEnabled
585 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
649 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
586 SequentialAnimation {
650 SequentialAnimation {
587− ParallelAnimation {
588− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
651+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
589− UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
590− }
591 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
652 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
592 ScriptAction {
653 ScriptAction {
593 script: {
654 script: {
@@ -604,11 +665,9 @@ AbstractStage {
604 enabled: appDelegate.animationsEnabled
665 enabled: appDelegate.animationsEnabled
605 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
666 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
606 SequentialAnimation {
667 SequentialAnimation {
607− ParallelAnimation {
608− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
668+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
609− UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
610− }
611 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
669 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
670+ ScriptAction { script: { fakeRectangle.stop(); } }
612 }
671 }
613 }
672 }
614 ]
673 ]
@@ -674,27 +733,38 @@ AbstractStage {
674 surface: model.surface
733 surface: model.surface
675 active: appDelegate.focus
734 active: appDelegate.focus
676 focus: true
735 focus: true
677− maximizeButtonShown: (maximumWidth == 0 || maximumWidth >= appContainer.width) &&
736+ maximizeButtonShown: appDelegate.canBeMaximized
678− (maximumHeight == 0 || maximumHeight >= appContainer.height)
679 overlayShown: touchControls.overlayShown
737 overlayShown: touchControls.overlayShown
738+ stageWidth: appContainer.width
739+ stageHeight: appContainer.height
680
740
681 requestedWidth: appDelegate.requestedWidth
741 requestedWidth: appDelegate.requestedWidth
682 requestedHeight: appDelegate.requestedHeight
742 requestedHeight: appDelegate.requestedHeight
683
743
684 onCloseClicked: { appDelegate.close(); }
744 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();
688 onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
746 onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
689 onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
747 onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
690 onMinimizeClicked: appDelegate.minimize()
748 onMinimizeClicked: appDelegate.minimize()
691 onDecorationPressed: { appDelegate.focus = true; }
749 onDecorationPressed: { appDelegate.focus = true; }
750+ onShouldCommitSnapWindow: fakeRectangle.commit();
692 }
751 }
693
752
694 WindowControlsOverlay {
753 WindowControlsOverlay {
695 id: touchControls
754 id: touchControls
696− anchors.fill: appDelegate
697 target: appDelegate
755 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();
698 }
768 }
699
769
700 WindowedFullscreenPolicy {
770 WindowedFullscreenPolicy {
@@ -706,6 +776,14 @@ AbstractStage {
706 }
776 }
707 }
777 }
708
778
779+ FakeMaximizeDelegate {
780+ id: fakeRectangle
781+ target: priv.focusedAppDelegate
782+ leftMargin: root.leftMargin
783+ appContainerWidth: appContainer.width
784+ appContainerHeight: appContainer.height
785+ }
786+
709 EdgeBarrier {
787 EdgeBarrier {
710 id: edgeBarrier
788 id: edgeBarrier
711
789
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"
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 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()
32
46
33 TouchGestureArea {
47 TouchGestureArea {
34 id: gestureArea
48 id: gestureArea
@@ -51,16 +65,17 @@ 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+ moveHandler.handleReleased(true);
57 }
72 }
58 }
73 }
59
74
60 readonly property point tp: recognizedPress ? Qt.point(touchPoints[0].x, touchPoints[0].y) : Qt.point(-1, -1)
75 readonly property point tp: recognizedPress ? Qt.point(touchPoints[0].x, touchPoints[0].y) : Qt.point(-1, -1)
61 onUpdated: {
76 onUpdated: {
62 if (recognizedDrag) {
77 if (recognizedDrag) {
63− priv.handlePositionChanged(tp.x, tp.y);
78+ moveHandler.handlePositionChanged(tp, priv.getSensingPoints());
64 }
79 }
65 }
80 }
66 }
81 }
@@ -69,33 +84,36 @@ Item {
69 Timer {
84 Timer {
70 id: overlayTimer
85 id: overlayTimer
71 interval: 2000
86 interval: 2000
72− repeat: priv.dragging || (priv.resizeArea && priv.resizeArea.dragging)
87+ repeat: moveHandler.dragging || (priv.resizeArea && priv.resizeArea.dragging)
73 }
88 }
74
89
75 QtObject {
90 QtObject {
76 id: priv
91 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
92 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"
82
94
83− function handlePressedChanged(pressed, mouseX, mouseY) {
95+ function getSensingPoints() {
84− if (pressed) {
96+ var xPoints = [];
85− var pos = mapToItem(root.target, mouseX, mouseY);
97+ var yPoints = [];
86− priv.distanceX = pos.x;
98+ for (var i = 0; i < gestureArea.touchPoints.length; i++) {
87− priv.distanceY = pos.y;
99+ var pt = gestureArea.touchPoints[i];
88− priv.dragging = true;
100+ xPoints.push(pt.x);
89− } else {
101+ yPoints.push(pt.y);
90− priv.dragging = false;
91 }
102 }
92− }
93
103
94− function handlePositionChanged(mouseX, mouseY) {
104+ var leftmost = Math.min.apply(Math, xPoints);
95− if (priv.dragging) {
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),
96− var pos = mapToItem(root.target.parent, mouseX, mouseY);
111+ top: mapToItem(target.parent, (leftmost+rightmost)/2, topmost),
97− root.target.requestedX = Math.round(pos.x - priv.distanceX);
112+ right: mapToItem(target.parent, rightmost, (topmost+bottommost)/2),
98− root.target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
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)
99 }
117 }
100 }
118 }
101 }
119 }
@@ -113,8 +131,6 @@ Item {
113 UbuntuNumberAnimation {}
131 UbuntuNumberAnimation {}
114 }
132 }
115
133
116− readonly property bool anyMaximized: target && (target.maximized || target.maximizedLeft || target.maximizedRight)
117−
118 Image {
134 Image {
119 source: "graphics/arrows-centre.png"
135 source: "graphics/arrows-centre.png"
120 width: units.gu(10)
136 width: units.gu(10)
@@ -125,15 +141,35 @@ Item {
125
141
126 // move handler
142 // move handler
127 MouseArea {
143 MouseArea {
128− id: moveHandler
144+ id: mouseArea
129 anchors.fill: parent
145 anchors.fill: parent
130 visible: overlay.visible
146 visible: overlay.visible
131 enabled: visible
147 enabled: visible
132 hoverEnabled: true
148 hoverEnabled: true
133− cursorShape: priv.dragging ? Qt.ClosedHandCursor : (overlay.visible ? Qt.OpenHandCursor : Qt.ArrowCursor)
134
149
135− onPressedChanged: priv.handlePressedChanged(pressed, mouseX, mouseY)
150+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
136− onPositionChanged: priv.handlePositionChanged(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();
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+12−41
@@ -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,10 +77,9 @@ 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− target: root.target
112 }
83 }
113
84
114 Label {
85 Label {
@@ -122,9 +93,9 @@ MouseArea {
122 font.weight: root.active ? Font.Light : Font.Medium
93 font.weight: root.active ? Font.Light : Font.Medium
123 elide: Text.ElideRight
94 elide: Text.ElideRight
124 opacity: overlayShown ? 0 : 1
95 opacity: overlayShown ? 0 : 1
125− visible: opacity == 1
96+ visible: opacity != 0
126 Behavior on opacity {
97 Behavior on opacity {
127− OpacityAnimator { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
98+ UbuntuNumberAnimation {}
128 }
99 }
129 }
100 }
130 }
101 }
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+9−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -17,7 +17,15 @@
17import QtQuick 2.4
17import QtQuick 2.4
18
18
19Item {
19Item {
20+ property int topBoundaryOffset // effectively panel height
21+
20 signal pushedLeftBoundary(real amount, int buttons)
22 signal pushedLeftBoundary(real amount, int buttons)
21 signal pushedRightBoundary(real amount, int buttons)
23 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()
22 signal mouseMoved()
30 signal mouseMoved()
23}
31}
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/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 {