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