Lomiri
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 
17 import QtQuick 2.4
18 import Lomiri.Components 1.3
19 import QtMir.Application 0.1
20 import "Spread/MathUtils.js" as MathUtils
21 import Lomiri.ApplicationMenu 0.1
22 import Lomiri.Indicators 0.1 as Indicators
23 import "../Components/PanelState"
24 
25 FocusScope {
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 }