Unity 8
SpreadDelegateInputArea.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.12
18 import Ubuntu.Components 1.3
19 import Ubuntu.Gestures 0.1
20 import "../../Components"
21 
22 Item {
23  id: root
24 
25  property bool closeable: true
26  readonly property real minSpeedToClose: units.gu(40)
27  property bool zeroVelocityCounts: false
28 
29  readonly property alias distance: d.distance
30 
31  signal clicked()
32  signal close()
33 
34  QtObject {
35  id: d
36  property real distance: 0
37  property bool moving: false
38  property var dragEvents: []
39  property real dragVelocity: 0
40  property int threshold: units.gu(2)
41 
42  // Can be replaced with a fake implementation during tests
43  // property var __getCurrentTimeMs: function () { return new Date().getTime() }
44  property var __dateTime: new function() {
45  this.getCurrentTimeMs = function() {return new Date().getTime()}
46  }
47 
48  function pushDragEvent(event) {
49  var currentTime = __dateTime.getCurrentTimeMs()
50  dragEvents.push([currentTime, event.x - event.startX, event.y - event.startY, getEventSpeed(currentTime, event)])
51  cullOldDragEvents(currentTime)
52  updateSpeed()
53  }
54 
55  function cullOldDragEvents(currentTime) {
56  // cull events older than 50 ms but always keep the latest 2 events
57  for (var numberOfCulledEvents = 0; numberOfCulledEvents < dragEvents.length-2; numberOfCulledEvents++) {
58  // dragEvents[numberOfCulledEvents][0] is the dragTime
59  if (currentTime - dragEvents[numberOfCulledEvents][0] <= 50) break
60  }
61 
62  dragEvents.splice(0, numberOfCulledEvents)
63  }
64 
65  function updateSpeed() {
66  var totalSpeed = 0
67  for (var i = 0; i < dragEvents.length; i++) {
68  totalSpeed += dragEvents[i][3]
69  }
70 
71  if (zeroVelocityCounts || Math.abs(totalSpeed) > 0.001) {
72  dragVelocity = totalSpeed / dragEvents.length * 1000
73  }
74  }
75 
76  function getEventSpeed(currentTime, event) {
77  if (dragEvents.length != 0) {
78  var lastDrag = dragEvents[dragEvents.length-1]
79  var duration = Math.max(1, currentTime - lastDrag[0])
80  return (event.y - event.startY - lastDrag[2]) / duration
81  } else {
82  return 0
83  }
84  }
85  }
86 
87  // Event eater
88  MouseArea {
89  anchors.fill: parent
90  onClicked: root.clicked()
91  onWheel: wheel.accepted = true
92  }
93 
94  MultiPointTouchArea {
95  anchors.fill: parent
96  mouseEnabled: false
97  maximumTouchPoints: 1
98  property int offset: 0
99 
100  touchPoints: [
101  TouchPoint {
102  id: tp
103  }
104  ]
105 
106  onCanceled: {
107  d.moving = false
108  animation.animate("center");
109  }
110 
111  onTouchUpdated: {
112  if (!d.moving) {
113  if (Math.abs(tp.startY - tp.y) > d.threshold) {
114  d.moving = true;
115  d.dragEvents = []
116  offset = tp.y - tp.startY;
117  } else {
118  return;
119  }
120  }
121 
122  if (root.closeable) {
123  d.distance = tp.y - tp.startY - offset
124  } else {
125  var value = tp.y - tp.startY - offset;
126  d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
127  }
128 
129  d.pushDragEvent(tp);
130  }
131 
132  onReleased: {
133  if (!d.moving) {
134  root.clicked()
135  }
136 
137  if (!root.closeable) {
138  animation.animate("center")
139  return;
140  }
141 
142  var touchPoint = touchPoints[0];
143 
144  if ((d.dragVelocity < -root.minSpeedToClose && d.distance < -units.gu(8)) || d.distance < -root.height / 2) {
145  animation.animate("up")
146  } else if ((d.dragVelocity > root.minSpeedToClose && d.distance > units.gu(8)) || d.distance > root.height / 2) {
147  animation.animate("down")
148  } else {
149  animation.animate("center")
150  }
151  }
152  }
153 
154  UbuntuNumberAnimation {
155  id: animation
156  objectName: "closeAnimation"
157  target: d
158  property: "distance"
159  property bool requestClose: false
160 
161  function animate(direction) {
162  animation.from = dragArea.distance;
163  switch (direction) {
164  case "up":
165  animation.to = -root.height * 1.5;
166  requestClose = true;
167  break;
168  case "down":
169  animation.to = root.height * 1.5;
170  requestClose = true;
171  break;
172  default:
173  animation.to = 0
174  }
175  animation.start();
176  }
177 
178  onRunningChanged: {
179  if (!running) {
180  d.moving = false;
181  if (requestClose) {
182  root.close();
183  } else {
184  d.distance = 0;
185  }
186  }
187  }
188  }
189 }