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