Lomiri
DragHandle.qml
1 /*
2  * Copyright (C) 2013, 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 
21 /*
22  Put a DragHandle inside a Showable to enable the user to drag it from that handle.
23  Main use case is to drag fullscreen Showables into the screen or off the screen.
24 
25  This example shows a DragHandle placed on the right corner of a Showable, used
26  to slide it away, off the screen.
27 
28  Showable {
29  x: 0
30  y: 0
31  width: ... // screen width
32  height: ... // screen height
33  shown: true
34  ...
35  DragHandle {
36  anchors.right: parent.right
37  anchors.top: parent.top
38  anchors.bottom: parent.bottom
39  width: units.gu(2)
40 
41  direction: SwipeArea::Leftwards
42  }
43  }
44 
45  */
46 SwipeArea {
47  id: dragArea
48 
49  property bool stretch: false
50 
51  property alias autoCompleteDragThreshold: dragEvaluator.dragThreshold
52 
53  // How far you can drag
54  property real maxTotalDragDistance: {
55  if (stretch) {
56  0; // not enough context information to set a sensible default
57  } else {
58  Direction.isHorizontal(direction) ? parent.width : parent.height;
59  }
60  }
61 
62  property real hintDisplacement: 0
63 
64  immediateRecognition: hintDisplacement > 0
65 
66  property var overrideStartValue: undefined
67  SmoothedAnimation {
68  id: hintingAnimation
69  target: hintingAnimation
70  objectName: "hintingAnimation"
71  property: "targetValue"
72  duration: 150
73  velocity: -1
74 
75  to: d.incrementTargetProp ? d.startValue + hintDisplacement
76  : d.startValue - hintDisplacement
77  property real targetValue
78  onTargetValueChanged: {
79  if (!running) {
80  return;
81  }
82 
83  if (d.incrementTargetProp) {
84  if (parent[d.targetProp] < targetValue) {
85  parent[d.targetProp] = targetValue;
86  }
87  } else {
88  if (parent[d.targetProp] > targetValue) {
89  parent[d.targetProp] = targetValue;
90  }
91  }
92  }
93  }
94 
95  // Private stuff
96  QtObject {
97  id: d
98 
99  // Whether movement along the designated direction will increment the value of the target property
100  readonly property bool incrementTargetProp: (Direction.isPositive(direction) && !dragArea.stretch)
101  || (dragArea.stretch && !d.dragParent.shown)
102 
103  property real startValue
104  property real minValue: {
105  if (direction == Direction.Horizontal) {
106  return startValue - maxTotalDragDistance;
107  } else if (incrementTargetProp) {
108  return startValue;
109  } else {
110  return startValue - maxTotalDragDistance;
111  }
112  }
113 
114  property real maxValue: incrementTargetProp ? startValue + maxTotalDragDistance
115  : startValue;
116 
117  property var dragParent: dragArea.parent
118 
119  // The property of DragHandle's parent that will be modified
120  property string targetProp: {
121  if (stretch) {
122  Direction.isHorizontal(direction) ? "width" : "height";
123  } else {
124  Direction.isHorizontal(direction) ? "x" : "y";
125  }
126  }
127 
128  function limitMovement(distance) {
129  var targetValue = MathUtils.clamp(d.startValue + distance, minValue, maxValue);
130  var diff = targetValue - d.startValue;
131 
132  if (hintDisplacement == 0) {
133  return diff;
134  }
135 
136  // we should not go behind hintingAnimation's current value
137  if (d.incrementTargetProp) {
138  if (d.startValue + diff < hintingAnimation.targetValue) {
139  diff = hintingAnimation.targetValue - d.startValue;
140  }
141  } else {
142  if (d.startValue + diff > hintingAnimation.targetValue) {
143  diff = hintingAnimation.targetValue - d.startValue;
144  }
145  }
146 
147  return diff;
148  }
149 
150  function onFinishedRecognizedGesture() {
151  if (dragEvaluator.shouldAutoComplete()) {
152  completeDrag();
153  } else {
154  rollbackDrag();
155  }
156  }
157 
158  function completeDrag() {
159  if (dragParent.shown) {
160  dragParent.hide();
161  } else {
162  dragParent.show();
163  }
164  }
165 
166  function rollbackDrag() {
167  if (dragParent.shown) {
168  dragParent.show();
169  } else {
170  dragParent.hide();
171  }
172  }
173  }
174 
175  property alias edgeDragEvaluator: dragEvaluator
176 
177  EdgeDragEvaluator {
178  objectName: "edgeDragEvaluator"
179  id: dragEvaluator
180  // Effectively convert distance into the drag position projected onto the gesture direction axis
181  trackedPosition: Direction.isPositive(dragArea.direction) ? distance : -distance
182  maxDragDistance: maxTotalDragDistance
183  direction: dragArea.direction
184  }
185 
186  onDistanceChanged: {
187  if (dragging) {
188  if (!Direction.isPositive(direction))
189  distance = -distance;
190 
191  if (dragArea.stretch &&
192  ((!Direction.isPositive(direction) && !d.dragParent.shown)
193  ||
194  (Direction.isPositive(direction) && d.dragParent.shown))
195  )
196  {
197  // This happens when you have a stretching showable being shown from the right or
198  // top edge (and consequently being hidden when dragged towards the right/top edge)
199  // In those situations, dimension expansion/retraction happens in the opposite
200  // sign of the axis direction
201  distance = -distance;
202  }
203 
204  var toAdd = d.limitMovement(distance);
205  parent[d.targetProp] = d.startValue + toAdd;
206  }
207  }
208 
209  onDraggingChanged: {
210  if (dragging) {
211  dragEvaluator.reset();
212  if (overrideStartValue !== undefined) {
213  d.startValue = overrideStartValue;
214  } else {
215  d.startValue = parent[d.targetProp];
216  }
217 
218  if (hintDisplacement > 0) {
219  hintingAnimation.targetValue = d.startValue;
220  hintingAnimation.start();
221  }
222  } else {
223  hintingAnimation.stop();
224  d.onFinishedRecognizedGesture();
225  }
226  }
227 }