Unity 8
DragHandle.qml
1 /*
2  * Copyright (C) 2013 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.0
18 import Ubuntu.Components 0.1
19 import Ubuntu.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: DirectionalDragArea::Leftwards
42  }
43  }
44 
45  */
46 EdgeDragArea {
47  id: dragArea
48  objectName: "dragHandle"
49 
50  // Disable gesture recognition by default when hinting is used as
51  // it conflicts with the hinting idea.
52  distanceThreshold: hintDisplacement > 0 ? 0 : defaultDistanceThreshold
53  maxSilenceTime: hintDisplacement > 0 ? 60*60*1000 : defaultMaxSilenceTime
54  maxDeviation: hintDisplacement > 0 ? 999999 : defaultMaxDeviation
55  compositionTime: hintDisplacement > 0 ? 0 : defaultCompositionTime
56 
57  property bool stretch: false
58 
59  property alias autoCompleteDragThreshold: dragEvaluator.dragThreshold
60 
61  // How far you can drag
62  property real maxTotalDragDistance: {
63  if (stretch) {
64  0; // not enough context information to set a sensible default
65  } else {
66  Direction.isHorizontal(direction) ? parent.width : parent.height;
67  }
68  }
69 
70  property real hintDisplacement: 0
71  property var overrideStartValue: undefined
72  SmoothedAnimation {
73  id: hintingAnimation
74  target: hintingAnimation
75  property: "targetValue"
76  duration: 150
77  velocity: -1
78  to: Direction.isPositive(direction) ? d.startValue + hintDisplacement
79  : d.startValue - hintDisplacement
80  property real targetValue
81  onTargetValueChanged: {
82  if (!running) {
83  return;
84  }
85 
86  if (Direction.isPositive(direction)) {
87  if (parent[d.targetProp] < targetValue) {
88  parent[d.targetProp] = targetValue;
89  }
90  } else {
91  if (parent[d.targetProp] > targetValue) {
92  parent[d.targetProp] = targetValue;
93  }
94  }
95  }
96  }
97 
98  // Private stuff
99  QtObject {
100  id: d
101  property var previousStatus: DirectionalDragArea.WaitingForTouch
102  property real startValue
103  property real minValue: {
104  if (direction == Direction.Horizontal) {
105  return startValue - maxTotalDragDistance;
106  } else if (Direction.isPositive(direction)) {
107  return startValue;
108  } else {
109  return startValue - maxTotalDragDistance;
110  }
111  }
112 
113  property real maxValue: Direction.isPositive(direction) ? startValue + maxTotalDragDistance
114  : startValue
115 
116  property var dragParent: dragArea.parent
117 
118  // The property of DragHandle's parent that will be modified
119  property string targetProp: {
120  if (stretch) {
121  Direction.isHorizontal(direction) ? "width" : "height";
122  } else {
123  Direction.isHorizontal(direction) ? "x" : "y";
124  }
125  }
126 
127  function limitMovement(inputStep) {
128  var targetValue = MathUtils.clamp(dragParent[targetProp] + inputStep, minValue, maxValue);
129  var step = targetValue - dragParent[targetProp];
130 
131  if (hintDisplacement == 0) {
132  return step;
133  }
134 
135  // we should not go behind hintingAnimation's current value
136  if (Direction.isPositive(direction)) {
137  if (dragParent[targetProp] + step < hintingAnimation.targetValue) {
138  step = hintingAnimation.targetValue - dragParent[targetProp];
139  }
140  } else {
141  if (dragParent[targetProp] + step > hintingAnimation.targetValue) {
142  step = hintingAnimation.targetValue - dragParent[targetProp];
143  }
144  }
145 
146  return step;
147  }
148 
149  function onFinishedRecognizedGesture() {
150  if (dragEvaluator.shouldAutoComplete()) {
151  completeDrag();
152  } else {
153  rollbackDrag();
154  }
155  }
156 
157  function completeDrag() {
158  if (dragParent.shown) {
159  dragParent.hide();
160  } else {
161  dragParent.show();
162  }
163  }
164 
165  function rollbackDrag() {
166  if (dragParent.shown) {
167  dragParent.show();
168  } else {
169  dragParent.hide();
170  }
171  }
172  }
173 
174  property alias edgeDragEvaluator: dragEvaluator
175 
176  EdgeDragEvaluator {
177  objectName: "edgeDragEvaluator"
178  id: dragEvaluator
179  // Effectively convert distance into the drag position projected onto the gesture direction axis
180  trackedPosition: Direction.isPositive(dragArea.direction) ? sceneDistance : -sceneDistance
181  maxDragDistance: maxTotalDragDistance
182  direction: dragArea.direction
183  }
184 
185  onDistanceChanged: {
186  if (status === DirectionalDragArea.Recognized) {
187  // don't go the whole distance in order to smooth out the movement
188  var step = distance * 0.3;
189 
190  step = d.limitMovement(step);
191 
192  parent[d.targetProp] += step;
193  }
194  }
195 
196  onStatusChanged: {
197  if (status === DirectionalDragArea.WaitingForTouch) {
198  hintingAnimation.stop();
199  if (d.previousStatus === DirectionalDragArea.Recognized) {
200  d.onFinishedRecognizedGesture();
201  } else /* d.previousStatus === DirectionalDragArea.Undecided */ {
202  // Gesture was rejected.
203  d.rollbackDrag();
204  }
205  } else /* Undecided || Recognized */ {
206  if (d.previousStatus === DirectionalDragArea.WaitingForTouch) {
207  dragEvaluator.reset();
208  if (overrideStartValue !== undefined) {
209  d.startValue = overrideStartValue;
210  } else {
211  d.startValue = parent[d.targetProp];
212  }
213 
214  if (hintDisplacement > 0) {
215  hintingAnimation.targetValue = d.startValue;
216  hintingAnimation.start();
217  }
218  }
219  }
220 
221  d.previousStatus = status;
222  }
223 }