Unity 8
WindowControlsOverlay.qml
1 /*
2  * Copyright (C) 2016 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 Ubuntu.Gestures 0.1
20 import Unity.Application 0.1
21 import "../Components/PanelState"
22 
23 Item {
24  id: root
25  enabled: target && !target.fullscreen
26  anchors.fill: target
27 
28  // to be set from outside
29  property Item target // appDelegate
30  property alias stageWidth: moveHandler.stageWidth
31  property alias stageHeight: moveHandler.stageHeight
32 
33  // to be read from outside
34  readonly property alias overlayShown: overlay.visible
35  readonly property alias dragging: priv.dragging
36 
37  signal fakeMaximizeAnimationRequested(real amount)
38  signal fakeMaximizeLeftAnimationRequested(real amount)
39  signal fakeMaximizeRightAnimationRequested(real amount)
40  signal fakeMaximizeTopLeftAnimationRequested(real amount)
41  signal fakeMaximizeTopRightAnimationRequested(real amount)
42  signal fakeMaximizeBottomLeftAnimationRequested(real amount)
43  signal fakeMaximizeBottomRightAnimationRequested(real amount)
44  signal stopFakeAnimation()
45  signal dragReleased()
46 
47  TouchGestureArea {
48  id: gestureArea
49  anchors.fill: parent
50 
51  // NB: for testing set to 2, not to clash with unity7 touch overlay controls
52  minimumTouchPoints: 3
53  maximumTouchPoints: minimumTouchPoints
54 
55  readonly property bool recognizedPress: status == TouchGestureArea.Recognized &&
56  touchPoints.length >= minimumTouchPoints &&
57  touchPoints.length <= maximumTouchPoints
58  onRecognizedPressChanged: {
59  if (recognizedPress) {
60  target.activate();
61  overlayTimer.start();
62  }
63  }
64 
65  readonly property bool recognizedDrag: recognizedPress && dragging
66  onRecognizedDragChanged: {
67  if (recognizedDrag) {
68  moveHandler.handlePressedChanged(true, Qt.LeftButton, tp.x, tp.y);
69  } else if (!mouseArea.containsPress) { // prevent interfering with the central piece drag/move
70  moveHandler.handlePressedChanged(false, Qt.LeftButton);
71  root.dragReleased();
72  moveHandler.handleReleased(true);
73  }
74  }
75 
76  readonly property point tp: recognizedPress ? Qt.point(touchPoints[0].x, touchPoints[0].y) : Qt.point(-1, -1)
77  onUpdated: {
78  if (recognizedDrag) {
79  moveHandler.handlePositionChanged(tp, priv.getSensingPoints());
80  }
81  }
82  }
83 
84  // dismiss timer
85  Timer {
86  id: overlayTimer
87  interval: 2000
88  repeat: priv.dragging
89  }
90 
91  QtObject {
92  id: priv
93  readonly property var resizeArea: root.target && root.target.resizeArea ? root.target.resizeArea : null
94  readonly property bool ensureWindow: root.target.state == "normal" || root.target.state == "restored"
95  readonly property bool dragging: moveHandler.dragging || (resizeArea && resizeArea.dragging)
96 
97  function getSensingPoints() {
98  var xPoints = [];
99  var yPoints = [];
100  for (var i = 0; i < gestureArea.touchPoints.length; i++) {
101  var pt = gestureArea.touchPoints[i];
102  xPoints.push(pt.x);
103  yPoints.push(pt.y);
104  }
105 
106  var leftmost = Math.min.apply(Math, xPoints);
107  var rightmost = Math.max.apply(Math, xPoints);
108  var topmost = Math.min.apply(Math, yPoints);
109  var bottommost = Math.max.apply(Math, yPoints);
110 
111  return {
112  left: mapToItem(target.parent, leftmost, (topmost+bottommost)/2),
113  top: mapToItem(target.parent, (leftmost+rightmost)/2, topmost),
114  right: mapToItem(target.parent, rightmost, (topmost+bottommost)/2),
115  topLeft: mapToItem(target.parent, leftmost, topmost),
116  topRight: mapToItem(target.parent, rightmost, topmost),
117  bottomLeft: mapToItem(target.parent, leftmost, bottommost),
118  bottomRight: mapToItem(target.parent, rightmost, bottommost)
119  }
120  }
121  }
122 
123  // the visual overlay
124  Item {
125  id: overlay
126  objectName: "windowControlsOverlay"
127  anchors.fill: parent
128  enabled: overlayTimer.running
129  visible: opacity > 0
130  opacity: enabled ? 0.95 : 0
131 
132  Behavior on opacity {
133  UbuntuNumberAnimation {}
134  }
135 
136  Image {
137  source: "graphics/arrows-centre.png"
138  width: units.gu(10)
139  height: width
140  sourceSize: Qt.size(width, height)
141  anchors.centerIn: parent
142  visible: target && target.width > units.gu(12) && target.height > units.gu(12)
143 
144  // move handler
145  MouseArea {
146  id: mouseArea
147  anchors.fill: parent
148  visible: overlay.visible
149  enabled: visible
150  hoverEnabled: true
151 
152  onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
153  onPositionChanged: moveHandler.handlePositionChanged(mouse)
154  onReleased: {
155  root.dragReleased();
156  moveHandler.handleReleased();
157  }
158  }
159 
160  MoveHandler {
161  id: moveHandler
162  objectName: "moveHandler"
163  target: root.target
164 
165  onFakeMaximizeAnimationRequested: root.fakeMaximizeAnimationRequested(amount)
166  onFakeMaximizeLeftAnimationRequested: root.fakeMaximizeLeftAnimationRequested(amount)
167  onFakeMaximizeRightAnimationRequested: root.fakeMaximizeRightAnimationRequested(amount)
168  onFakeMaximizeTopLeftAnimationRequested: root.fakeMaximizeTopLeftAnimationRequested(amount)
169  onFakeMaximizeTopRightAnimationRequested: root.fakeMaximizeTopRightAnimationRequested(amount)
170  onFakeMaximizeBottomLeftAnimationRequested: root.fakeMaximizeBottomLeftAnimationRequested(amount)
171  onFakeMaximizeBottomRightAnimationRequested: root.fakeMaximizeBottomRightAnimationRequested(amount)
172  onStopFakeAnimation: root.stopFakeAnimation()
173  }
174 
175  // dismiss area
176  InverseMouseArea {
177  anchors.fill: parent
178  visible: overlay.visible
179  enabled: visible
180  onPressed: {
181  if (gestureArea.recognizedPress || gestureArea.recognizedDrag) {
182  mouse.accepted = false;
183  return;
184  }
185 
186  overlayTimer.stop();
187  mouse.accepted = root.contains(mapToItem(root.target, mouse.x, mouse.y));
188  }
189  propagateComposedEvents: true
190  }
191  }
192 
193  ResizeGrip { // top left
194  anchors.horizontalCenter: parent.left
195  anchors.verticalCenter: parent.top
196  visible: priv.ensureWindow || target.maximizedBottomRight
197  resizeTarget: priv.resizeArea
198  }
199 
200  ResizeGrip { // top center
201  anchors.horizontalCenter: parent.horizontalCenter
202  anchors.verticalCenter: parent.top
203  rotation: 45
204  visible: priv.ensureWindow || target.maximizedHorizontally || target.maximizedBottomLeft || target.maximizedBottomRight
205  resizeTarget: priv.resizeArea
206  }
207 
208  ResizeGrip { // top right
209  anchors.horizontalCenter: parent.right
210  anchors.verticalCenter: parent.top
211  rotation: 90
212  visible: priv.ensureWindow || target.maximizedBottomLeft
213  resizeTarget: priv.resizeArea
214  }
215 
216  ResizeGrip { // right
217  anchors.horizontalCenter: parent.right
218  anchors.verticalCenter: parent.verticalCenter
219  rotation: 135
220  visible: priv.ensureWindow || target.maximizedVertically || target.maximizedLeft ||
221  target.maximizedTopLeft || target.maximizedBottomLeft
222  resizeTarget: priv.resizeArea
223  }
224 
225  ResizeGrip { // bottom right
226  anchors.horizontalCenter: parent.right
227  anchors.verticalCenter: parent.bottom
228  visible: priv.ensureWindow || target.maximizedTopLeft
229  resizeTarget: priv.resizeArea
230  }
231 
232  ResizeGrip { // bottom center
233  anchors.horizontalCenter: parent.horizontalCenter
234  anchors.verticalCenter: parent.bottom
235  rotation: 45
236  visible: priv.ensureWindow || target.maximizedHorizontally || target.maximizedTopLeft || target.maximizedTopRight
237  resizeTarget: priv.resizeArea
238  }
239 
240  ResizeGrip { // bottom left
241  anchors.horizontalCenter: parent.left
242  anchors.verticalCenter: parent.bottom
243  rotation: 90
244  visible: priv.ensureWindow || target.maximizedTopRight
245  resizeTarget: priv.resizeArea
246  }
247 
248  ResizeGrip { // left
249  anchors.horizontalCenter: parent.left
250  anchors.verticalCenter: parent.verticalCenter
251  rotation: 135
252  visible: priv.ensureWindow || target.maximizedVertically || target.maximizedRight ||
253  target.maximizedTopRight || target.maximizedBottomRight
254  resizeTarget: priv.resizeArea
255  }
256  }
257 }