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 readonly property alias supportsResize: applicationWindow.supportsResize
37 property alias active: decoration.active
38 readonly property alias title: applicationWindow.title
39 property alias maximizeButtonShown: decoration.maximizeButtonShown
40 property alias interactive: applicationWindow.interactive
41 readonly property alias orientationChangesEnabled: applicationWindow.orientationChangesEnabled
42 property alias windowControlButtonsVisible: decoration.windowControlButtonsVisible
43 property PanelState panelState
44
45 // Changing this will actually add/remove a decoration, meaning, requestedHeight will take the decoration into account.
46 property bool hasDecoration: true
47 // This will temporarily show/hide the decoration without actually changing the surface's dimensions
48 property real showDecoration: 1
49 property alias decorationHeight: decoration.height
50 property bool animateDecoration: false
51 property bool showHighlight: false
52 property int highlightSize: units.gu(1)
53 property real shadowOpacity: 0
54 property bool darkening: 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
232 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
233 Behavior on opacity { LomiriNumberAnimation { } }
234 visible: opacity > 0 // don't eat input when decoration is fully translucent
235
236 onPressed: root.decorationPressed();
237 onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
238 onPressedChangedEx: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
239 onPositionChanged: moveHandler.handlePositionChanged(mouse)
240 onReleased: {
241 if (!wasDoubleClicked) {
242 root.decorationReleased();
243 moveHandler.handleReleased();
244 }
245 wasDoubleClicked = false;
246 }
247 onDoubleClicked: wasDoubleClicked = true;
248
249 onCloseClicked: root.closeClicked();
250 onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
251 onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
252 onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
253 onMinimizeClicked: root.minimizeClicked();
254
255 enableMenus: {
256 return active &&
257 surface &&
258 (panelState.focusedPersistentSurfaceId === surface.persistentId && !panelState.decorationsVisible)
259 }
260 menu: sharedAppModel.model
261
262 Indicators.SharedLomiriMenuModel {
263 id: sharedAppModel
264 property var menus: surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : []
265 property var menuService: menus.length > 0 ? menus[0] : undefined
266
267 busName: menuService ? menuService.service : ""
268 menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
269 actions: menuService && menuService.actionPath ? { "lomiri": menuService.actionPath } : {}
270 }
271
272 Connections {
273 target: ApplicationMenuRegistry
274 function onSurfaceMenuRegistered(surfaceId) {
275 if (surface && surfaceId === surface.persistentId) {
276 sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
277 }
278 }
279 function onSurfaceMenuUnregistered(surfaceId) {
280 if (surface && surfaceId === surface.persistentId) {
281 sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
282 }
283 }
284 }
285 }
286
287 MouseArea {
288 id: altDragHandler
289 anchors.fill: applicationWindow
290 acceptedButtons: Qt.LeftButton | Qt.RightButton
291 property bool dragging: false
292 property bool draggingForResize: false
293 cursorShape: undefined // don't interfere with the cursor shape set by the underlying MirSurfaceItem
294 visible: enabled
295 onPressed: {
296 if (mouse.button == Qt.LeftButton && mouse.modifiers == Qt.AltModifier) {
297 root.decorationPressed(); // to raise it
298 moveHandler.handlePressedChanged(true, Qt.LeftButton, mouse.x, mouse.y);
299 dragging = true;
300 mouse.accepted = true;
301 } else if (mouse.button == Qt.RightButton && mouse.modifiers & Qt.AltModifier) {
302 root.dragResizePressed(Qt.point(mouse.x, mouse.y))
303 draggingForResize = true;
304 mouse.accepted = true;
305 } else {
306 mouse.accepted = false;
307 }
308 }
309 onPositionChanged: {
310 if (dragging) {
311 moveHandler.handlePositionChanged(mouse);
312 } else if (draggingForResize) {
313 root.dragResizePositionChanged(Qt.point(mouse.x, mouse.y))
314 }
315 }
316 onReleased: {
317 if (dragging) {
318 moveHandler.handlePressedChanged(false, Qt.LeftButton);
319 root.decorationReleased(); // commits the fake preview max rectangle
320 moveHandler.handleReleased();
321 dragging = false;
322 } else if (draggingForResize) {
323 root.dragResizeReleased(Qt.point(mouse.x, mouse.y))
324 draggingForResize = false;
325 }
326 }
327 }
328
329 MoveHandler {
330 id: moveHandler
331 objectName: "moveHandler"
332 target: root.parent
333 buttonsWidth: decoration.buttonsWidth
334 }
335
336 Rectangle {
337 anchors.fill: parent
338 color: LomiriColors.jet
339 antialiasing: true
340 opacity: root.darkening && !root.showHighlight ? 0.5 : 0
341 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
342 }
343}