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.15
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 property bool lightMode: false
55
56 property real requestedWidth
57 property real requestedHeight
58 property real scaleToPreviewProgress: 0
59 property int scaleToPreviewSize: units.gu(30)
60
61 property alias surfaceOrientationAngle: applicationWindow.surfaceOrientationAngle
62
63 // Height of the decoration that's actually being displayed at this moment. Will match decorationHeight
64 // when the decoration is being fully displayed
65 readonly property real actualDecorationHeight: Math.min(d.visibleDecorationHeight, d.requestedDecorationHeight)
66
67 readonly property bool counterRotate: surfaceOrientationAngle != 0 && surfaceOrientationAngle != 180
68
69 readonly property int minimumWidth: !counterRotate ? applicationWindow.minimumWidth : applicationWindow.minimumHeight
70 readonly property int minimumHeight: actualDecorationHeight + (!counterRotate ? applicationWindow.minimumHeight : applicationWindow.minimumWidth)
71 readonly property int maximumWidth: !counterRotate ? applicationWindow.maximumWidth : applicationWindow.maximumHeight
72 readonly property int maximumHeight: (root.decorationShown && applicationWindow.maximumHeight > 0 ? decoration.height : 0)
73 + (!counterRotate ? applicationWindow.maximumHeight : applicationWindow.maximumWidth)
74 readonly property int widthIncrement: !counterRotate ? applicationWindow.widthIncrement : applicationWindow.heightIncrement
75 readonly property int heightIncrement: !counterRotate ? applicationWindow.heightIncrement : applicationWindow.widthIncrement
76
77 property alias overlayShown: decoration.overlayShown
78 property alias boundsItem: moveHandler.boundsItem
79 readonly property alias dragging: moveHandler.dragging
80
81 readonly property Item clientAreaItem: applicationWindow
82
83 property alias altDragEnabled: altDragHandler.enabled
84
85 property Item windowMargins
86
87 signal closeClicked()
88 signal maximizeClicked()
89 signal maximizeHorizontallyClicked()
90 signal maximizeVerticallyClicked()
91 signal minimizeClicked()
92 signal decorationPressed()
93 signal decorationReleased()
94 signal dragResizePressed(point mouse)
95 signal dragResizeReleased(point mouse)
96 signal dragResizePositionChanged(point mouse)
97
98 function cancelDrag() {
99 moveHandler.cancelDrag();
100 }
101
102 QtObject {
103 id: d
104 property int requestedDecorationHeight: root.hasDecoration ? decoration.height : 0
105 Behavior on requestedDecorationHeight { enabled: root.animateDecoration; LomiriNumberAnimation { } }
106
107 property int visibleDecorationHeight: root.hasDecoration ? root.showDecoration * decoration.height : 0
108 Behavior on visibleDecorationHeight { enabled: root.animateDecoration; LomiriNumberAnimation { } }
109 }
110
111 StateGroup {
112 states: [
113 State {
114 name: "normal"; when: root.scaleToPreviewProgress <= 0 && root.application.state === ApplicationInfoInterface.Running
115 PropertyChanges {
116 target: root
117 implicitWidth: counterRotate ? applicationWindow.implicitHeight : applicationWindow.implicitWidth
118 implicitHeight: root.actualDecorationHeight + (counterRotate ? applicationWindow.implicitWidth: applicationWindow.implicitHeight)
119 }
120 },
121 State {
122 name: "normalSuspended"; when: root.scaleToPreviewProgress <= 0 && root.application.state !== ApplicationInfoInterface.Running
123 extend: "normal"
124 PropertyChanges {
125 target: root
126 implicitWidth: counterRotate ? applicationWindow.requestedHeight : applicationWindow.requestedWidth
127 implicitHeight: root.actualDecorationHeight + (counterRotate ? applicationWindow.requestedWidth: applicationWindow.requestedHeight)
128 }
129 },
130 State {
131 name: "preview"; when: root.scaleToPreviewProgress > 0
132 PropertyChanges {
133 target: root
134 implicitWidth: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, root.scaleToPreviewSize, root.scaleToPreviewProgress)
135 implicitHeight: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, root.scaleToPreviewSize, root.scaleToPreviewProgress)
136 }
137 PropertyChanges {
138 target: applicationWindow;
139// requestedWidth: applicationWindow.oldRequestedWidth
140// requestedHeight: applicationWindow.oldRequestedHeight
141 width: MathUtils.linearAnimation(0, 1, applicationWindow.requestedWidth, applicationWindow.minSize, root.scaleToPreviewProgress)
142 height: MathUtils.linearAnimation(0, 1, applicationWindow.requestedHeight, applicationWindow.minSize, root.scaleToPreviewProgress)
143 itemScale: root.implicitWidth / width
144 }
145 }
146 ]
147 }
148
149 Rectangle {
150 id: selectionHighlight
151 objectName: "selectionHighlight"
152 anchors.fill: parent
153 anchors.margins: -root.highlightSize
154 color: LomiriColors.porcelain
155 opacity: showHighlight ? 0.3 : 0
156 visible: opacity > 0
157 antialiasing: true
158 }
159
160 BorderImage {
161 id: dropShadow
162 anchors {
163 left: parent.left; top: parent.top; right: parent.right
164 margins: active ? -units.gu(2) : -units.gu(1.5)
165 }
166 height: Math.min(applicationWindow.implicitHeight, applicationWindow.height) * applicationWindow.itemScale
167 + root.actualDecorationHeight * Math.min(1, root.showDecoration) + (active ? units.gu(4) : units.gu(3))
168 source: "../graphics/dropshadow2gu.sci"
169 opacity: root.shadowOpacity
170 }
171
172 ApplicationWindow {
173 id: applicationWindow
174 objectName: "appWindow"
175 anchors.top: parent.top
176 anchors.topMargin: root.actualDecorationHeight * Math.min(1, root.showDecoration)
177 anchors.left: parent.left
178 width: implicitWidth
179 height: implicitHeight
180 requestedHeight: !counterRotate ? root.requestedHeight - d.requestedDecorationHeight : root.requestedWidth
181 requestedWidth: !counterRotate ? root.requestedWidth : root.requestedHeight - d.requestedDecorationHeight
182// property int oldRequestedWidth: requestedWidth
183// property int oldRequestedHeight: requestedHeight
184// onRequestedWidthChanged: oldRequestedWidth = requestedWidth
185// onRequestedHeightChanged: oldRequestedHeight = requestedHeight
186 focus: true
187
188 property real itemScale: 1
189 property real minSize: Math.min(root.scaleToPreviewSize, Math.min(requestedHeight, Math.min(requestedWidth, Math.min(implicitHeight, implicitWidth))))
190
191 transform: [
192 Rotation {
193 id: rotationTransform
194 readonly property int rotationAngle: applicationWindow.application &&
195 applicationWindow.application.rotatesWindowContents
196 ? ((360 - applicationWindow.surfaceOrientationAngle) % 360) : 0
197 origin.x: {
198 if (rotationAngle == 90) return applicationWindow.height / 2;
199 else if (rotationAngle == 270) return applicationWindow.width / 2;
200 else if (rotationAngle == 180) return applicationWindow.width / 2;
201 else return 0;
202 }
203 origin.y: {
204 if (rotationAngle == 90) return applicationWindow.height / 2;
205 else if (rotationAngle == 270) return applicationWindow.width / 2;
206 else if (rotationAngle == 180) return applicationWindow.height / 2;
207 else return 0;
208 }
209 angle: rotationAngle
210 },
211 Scale {
212 xScale: applicationWindow.itemScale
213 yScale: applicationWindow.itemScale
214 }
215 ]
216 }
217
218 WindowDecoration {
219 id: decoration
220 closeButtonVisible: true
221 objectName: "appWindowDecoration"
222
223 property bool wasDoubleClicked: false
224
225 anchors { left: parent.left; top: parent.top; right: parent.right }
226 height: units.gu(3) // a default value. overwritten by root.decorationHeight
227
228 title: applicationWindow.title
229 windowMoving: moveHandler.moving && !altDragHandler.dragging
230 panelState: root.panelState
231 lightMode: root.lightMode
232
233 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
234 Behavior on opacity { LomiriNumberAnimation { } }
235 visible: opacity > 0 // don't eat input when decoration is fully translucent
236
237 onPressed: root.decorationPressed();
238 onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
239 onPressedChangedEx: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
240 onPositionChanged: moveHandler.handlePositionChanged(mouse)
241 onReleased: {
242 if (!wasDoubleClicked) {
243 root.decorationReleased();
244 moveHandler.handleReleased();
245 }
246 wasDoubleClicked = false;
247 }
248 onDoubleClicked: wasDoubleClicked = true;
249
250 onCloseClicked: root.closeClicked();
251 onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
252 onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
253 onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
254 onMinimizeClicked: root.minimizeClicked();
255
256 enableMenus: {
257 return active &&
258 surface &&
259 (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
260 }
261 menu: sharedAppModel.model
262
263 Indicators.SharedLomiriMenuModel {
264 id: sharedAppModel
265 property var menus: surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : []
266 property var menuService: menus.length > 0 ? menus[0] : undefined
267
268 busName: menuService ? menuService.service : ""
269 menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
270 actions: menuService && menuService.actionPath ? { "lomiri": menuService.actionPath } : {}
271 }
272
273 Connections {
274 target: ApplicationMenuRegistry
275 function onSurfaceMenuRegistered(surfaceId) {
276 if (surface && surfaceId === surface.persistentId) {
277 sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
278 }
279 }
280 function onSurfaceMenuUnregistered(surfaceId) {
281 if (surface && surfaceId === surface.persistentId) {
282 sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
283 }
284 }
285 }
286 }
287
288 MouseArea {
289 id: altDragHandler
290 anchors.fill: applicationWindow
291 acceptedButtons: Qt.LeftButton | Qt.RightButton
292 property bool dragging: false
293 property bool draggingForResize: false
294 cursorShape: undefined // don't interfere with the cursor shape set by the underlying MirSurfaceItem
295 visible: enabled
296 onPressed: {
297 if (mouse.button == Qt.LeftButton && mouse.modifiers == Qt.AltModifier) {
298 root.decorationPressed(); // to raise it
299 moveHandler.handlePressedChanged(true, Qt.LeftButton, mouse.x, mouse.y);
300 dragging = true;
301 mouse.accepted = true;
302 } else if (mouse.button == Qt.RightButton && mouse.modifiers & Qt.AltModifier) {
303 root.dragResizePressed(Qt.point(mouse.x, mouse.y))
304 draggingForResize = true;
305 mouse.accepted = true;
306 } else {
307 mouse.accepted = false;
308 }
309 }
310 onPositionChanged: {
311 if (dragging) {
312 moveHandler.handlePositionChanged(mouse);
313 } else if (draggingForResize) {
314 root.dragResizePositionChanged(Qt.point(mouse.x, mouse.y))
315 }
316 }
317 onReleased: {
318 if (dragging) {
319 moveHandler.handlePressedChanged(false, Qt.LeftButton);
320 root.decorationReleased(); // commits the fake preview max rectangle
321 moveHandler.handleReleased();
322 dragging = false;
323 } else if (draggingForResize) {
324 root.dragResizeReleased(Qt.point(mouse.x, mouse.y))
325 draggingForResize = false;
326 }
327 }
328 }
329
330 MoveHandler {
331 id: moveHandler
332 objectName: "moveHandler"
333 target: root.parent
334 buttonsWidth: decoration.buttonsWidth
335 }
336
337 Rectangle {
338 anchors.fill: parent
339 color: LomiriColors.jet
340 antialiasing: true
341 opacity: root.darkening && !root.showHighlight ? 0.5 : 0
342 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
343 }
344}