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