Lomiri
Loading...
Searching...
No Matches
DecoratedWindow.qml
1/*
2 * Copyright (C) 2014-2017 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
17import QtQuick 2.12
18import Lomiri.Components 1.3
19import QtMir.Application 0.1
20import "Spread/MathUtils.js" as MathUtils
21import Lomiri.ApplicationMenu 0.1
22import Lomiri.Indicators 0.1 as Indicators
23import "../Components/PanelState"
24
25FocusScope {
26 id: root
27
28 // The DecoratedWindow takes requestedWidth/requestedHeight and asks its surface to be resized to that
29 // (minus the window decoration size in case hasDecoration and showDecoration are true)
30 // The surface might not be able to resize to the requested values. It will return its actual size
31 // in implicitWidth/implicitHeight.
32
33 property alias application: applicationWindow.application
34 property alias surface: applicationWindow.surface
35 readonly property alias focusedSurface: applicationWindow.focusedSurface
36 property alias active: decoration.active
37 readonly property alias title: applicationWindow.title
38 property alias maximizeButtonShown: decoration.maximizeButtonShown
39 property alias interactive: applicationWindow.interactive
40 readonly property alias orientationChangesEnabled: applicationWindow.orientationChangesEnabled
41 property alias windowControlButtonsVisible: decoration.windowControlButtonsVisible
42 property PanelState panelState
43
44 // Changing this will actually add/remove a decoration, meaning, requestedHeight will take the decoration into account.
45 property bool hasDecoration: true
46 // This will temporarily show/hide the decoration without actually changing the surface's dimensions
47 property real showDecoration: 1
48 property alias decorationHeight: decoration.height
49 property bool animateDecoration: false
50 property bool showHighlight: false
51 property int highlightSize: units.gu(1)
52 property real shadowOpacity: 0
53 property bool darkening: false
54
55 property real requestedWidth
56 property real requestedHeight
57 property real scaleToPreviewProgress: 0
58 property int scaleToPreviewSize: units.gu(30)
59
60 property alias surfaceOrientationAngle: applicationWindow.surfaceOrientationAngle
61
62 // Height of the decoration that's actually being displayed at this moment. Will match decorationHeight
63 // when the decoration is being fully displayed
64 readonly property real actualDecorationHeight: Math.min(d.visibleDecorationHeight, d.requestedDecorationHeight)
65
66 readonly property bool counterRotate: surfaceOrientationAngle != 0 && surfaceOrientationAngle != 180
67
68 readonly property int minimumWidth: !counterRotate ? applicationWindow.minimumWidth : applicationWindow.minimumHeight
69 readonly property int minimumHeight: actualDecorationHeight + (!counterRotate ? applicationWindow.minimumHeight : applicationWindow.minimumWidth)
70 readonly property int maximumWidth: !counterRotate ? applicationWindow.maximumWidth : applicationWindow.maximumHeight
71 readonly property int maximumHeight: (root.decorationShown && applicationWindow.maximumHeight > 0 ? decoration.height : 0)
72 + (!counterRotate ? applicationWindow.maximumHeight : applicationWindow.maximumWidth)
73 readonly property int widthIncrement: !counterRotate ? applicationWindow.widthIncrement : applicationWindow.heightIncrement
74 readonly property int heightIncrement: !counterRotate ? applicationWindow.heightIncrement : applicationWindow.widthIncrement
75
76 property alias overlayShown: decoration.overlayShown
77 property alias boundsItem: moveHandler.boundsItem
78 readonly property alias dragging: moveHandler.dragging
79
80 readonly property Item clientAreaItem: applicationWindow
81
82 property alias altDragEnabled: altDragHandler.enabled
83
84 property Item windowMargins
85
86 signal closeClicked()
87 signal maximizeClicked()
88 signal maximizeHorizontallyClicked()
89 signal maximizeVerticallyClicked()
90 signal minimizeClicked()
91 signal decorationPressed()
92 signal decorationReleased()
93
94 function cancelDrag() {
95 moveHandler.cancelDrag();
96 }
97
98 QtObject {
99 id: d
100 property int requestedDecorationHeight: root.hasDecoration ? decoration.height : 0
101 Behavior on requestedDecorationHeight { enabled: root.animateDecoration; LomiriNumberAnimation { } }
102
103 property int visibleDecorationHeight: root.hasDecoration ? root.showDecoration * decoration.height : 0
104 Behavior on visibleDecorationHeight { enabled: root.animateDecoration; LomiriNumberAnimation { } }
105 }
106
107 StateGroup {
108 states: [
109 State {
110 name: "normal"; when: root.scaleToPreviewProgress <= 0 && root.application.state === ApplicationInfoInterface.Running
111 PropertyChanges {
112 target: root
113 implicitWidth: counterRotate ? applicationWindow.implicitHeight : applicationWindow.implicitWidth
114 implicitHeight: root.actualDecorationHeight + (counterRotate ? applicationWindow.implicitWidth: applicationWindow.implicitHeight)
115 }
116 },
117 State {
118 name: "normalSuspended"; when: root.scaleToPreviewProgress <= 0 && root.application.state !== ApplicationInfoInterface.Running
119 extend: "normal"
120 PropertyChanges {
121 target: root
122 implicitWidth: counterRotate ? applicationWindow.requestedHeight : applicationWindow.requestedWidth
123 implicitHeight: root.actualDecorationHeight + (counterRotate ? applicationWindow.requestedWidth: applicationWindow.requestedHeight)
124 }
125 },
126 State {
127 name: "preview"; when: root.scaleToPreviewProgress > 0
128 PropertyChanges {
129 target: root
130 implicitWidth: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, root.scaleToPreviewSize, root.scaleToPreviewProgress)
131 implicitHeight: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, root.scaleToPreviewSize, root.scaleToPreviewProgress)
132 }
133 PropertyChanges {
134 target: applicationWindow;
135// requestedWidth: applicationWindow.oldRequestedWidth
136// requestedHeight: applicationWindow.oldRequestedHeight
137 width: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, applicationWindow.minSize, root.scaleToPreviewProgress)
138 height: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, applicationWindow.minSize, root.scaleToPreviewProgress)
139 itemScale: root.implicitWidth / width
140 }
141 }
142 ]
143 }
144
145 Rectangle {
146 id: selectionHighlight
147 objectName: "selectionHighlight"
148 anchors.fill: parent
149 anchors.margins: -root.highlightSize
150 color: "white"
151 opacity: showHighlight ? 0.55 : 0
152 visible: opacity > 0
153 }
154
155 BorderImage {
156 id: dropShadow
157 anchors {
158 left: parent.left; top: parent.top; right: parent.right
159 margins: active ? -units.gu(2) : -units.gu(1.5)
160 }
161 height: Math.min(applicationWindow.implicitHeight, applicationWindow.height) * applicationWindow.itemScale
162 + root.actualDecorationHeight * Math.min(1, root.showDecoration) + (active ? units.gu(4) : units.gu(3))
163 source: "../graphics/dropshadow2gu.sci"
164 opacity: root.shadowOpacity
165 }
166
167 ApplicationWindow {
168 id: applicationWindow
169 objectName: "appWindow"
170 anchors.top: parent.top
171 anchors.topMargin: root.actualDecorationHeight * Math.min(1, root.showDecoration)
172 anchors.left: parent.left
173 width: implicitWidth
174 height: implicitHeight
175 requestedHeight: !counterRotate ? root.requestedHeight - d.requestedDecorationHeight : root.requestedWidth
176 requestedWidth: !counterRotate ? root.requestedWidth : root.requestedHeight - d.requestedDecorationHeight
177// property int oldRequestedWidth: requestedWidth
178// property int oldRequestedHeight: requestedHeight
179// onRequestedWidthChanged: oldRequestedWidth = requestedWidth
180// onRequestedHeightChanged: oldRequestedHeight = requestedHeight
181 focus: true
182
183 property real itemScale: 1
184 property real minSize: Math.min(root.scaleToPreviewSize, Math.min(requestedHeight, Math.min(requestedWidth, Math.min(implicitHeight, implicitWidth))))
185
186 transform: [
187 Rotation {
188 id: rotationTransform
189 readonly property int rotationAngle: applicationWindow.application &&
190 applicationWindow.application.rotatesWindowContents
191 ? ((360 - applicationWindow.surfaceOrientationAngle) % 360) : 0
192 origin.x: {
193 if (rotationAngle == 90) return applicationWindow.height / 2;
194 else if (rotationAngle == 270) return applicationWindow.width / 2;
195 else if (rotationAngle == 180) return applicationWindow.width / 2;
196 else return 0;
197 }
198 origin.y: {
199 if (rotationAngle == 90) return applicationWindow.height / 2;
200 else if (rotationAngle == 270) return applicationWindow.width / 2;
201 else if (rotationAngle == 180) return applicationWindow.height / 2;
202 else return 0;
203 }
204 angle: rotationAngle
205 },
206 Scale {
207 xScale: applicationWindow.itemScale
208 yScale: applicationWindow.itemScale
209 }
210 ]
211 }
212
213 WindowDecoration {
214 id: decoration
215 closeButtonVisible: true
216 objectName: "appWindowDecoration"
217
218 anchors { left: parent.left; top: parent.top; right: parent.right }
219 height: units.gu(3) // a default value. overwritten by root.decorationHeight
220
221 title: applicationWindow.title
222 windowMoving: moveHandler.moving && !altDragHandler.dragging
223 panelState: root.panelState
224
225 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
226 Behavior on opacity { LomiriNumberAnimation { } }
227 visible: opacity > 0 // don't eat input when decoration is fully translucent
228
229 onPressed: root.decorationPressed();
230 onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
231 onPressedChangedEx: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
232 onPositionChanged: moveHandler.handlePositionChanged(mouse)
233 onReleased: {
234 root.decorationReleased();
235 moveHandler.handleReleased();
236 }
237
238 onCloseClicked: root.closeClicked();
239 onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
240 onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
241 onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
242 onMinimizeClicked: root.minimizeClicked();
243
244 enableMenus: {
245 return active &&
246 surface &&
247 (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
248 }
249 menu: sharedAppModel.model
250
251 Indicators.SharedLomiriMenuModel {
252 id: sharedAppModel
253 property var menus: surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : []
254 property var menuService: menus.length > 0 ? menus[0] : undefined
255
256 busName: menuService ? menuService.service : ""
257 menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
258 actions: menuService && menuService.actionPath ? { "lomiri": menuService.actionPath } : {}
259 }
260
261 Connections {
262 target: ApplicationMenuRegistry
263 onSurfaceMenuRegistered: {
264 if (surface && surfaceId === surface.persistentId) {
265 sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
266 }
267 }
268 onSurfaceMenuUnregistered: {
269 if (surface && surfaceId === surface.persistentId) {
270 sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
271 }
272 }
273 }
274 }
275
276 MouseArea {
277 id: altDragHandler
278 anchors.fill: applicationWindow
279 acceptedButtons: Qt.LeftButton
280 property bool dragging: false
281 cursorShape: undefined // don't interfere with the cursor shape set by the underlying MirSurfaceItem
282 visible: enabled
283 onPressed: {
284 if (mouse.button == Qt.LeftButton && mouse.modifiers == Qt.AltModifier) {
285 root.decorationPressed(); // to raise it
286 moveHandler.handlePressedChanged(true, Qt.LeftButton, mouse.x, mouse.y);
287 dragging = true;
288 mouse.accepted = true;
289 } else {
290 mouse.accepted = false;
291 }
292 }
293 onPositionChanged: {
294 if (dragging) {
295 moveHandler.handlePositionChanged(mouse);
296 }
297 }
298 onReleased: {
299 if (dragging) {
300 moveHandler.handlePressedChanged(false, Qt.LeftButton);
301 root.decorationReleased(); // commits the fake preview max rectangle
302 moveHandler.handleReleased();
303 dragging = false;
304 }
305 }
306 }
307
308 MoveHandler {
309 id: moveHandler
310 objectName: "moveHandler"
311 target: root.parent
312 buttonsWidth: decoration.buttonsWidth
313 }
314
315 Rectangle {
316 anchors.fill: parent
317 color: "black"
318 opacity: root.darkening && !root.showHighlight ? 0.05 : 0
319 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
320 }
321}