Lomiri
Loading...
Searching...
No Matches
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
17import QtQuick 2.12
18import Lomiri.Components 1.3
19import Lomiri.Gestures 0.1
20import "../../Components"
21
22Item {
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 property var stage: null
32 property var dragDelegate: null
33
34 signal clicked()
35 signal close()
36
37 QtObject {
38 id: d
39 property real distance: 0
40 property bool moving: false
41 property var dragEvents: []
42 property real dragVelocity: 0
43 property int threshold: units.gu(2)
44
45 // Can be replaced with a fake implementation during tests
46 // property var __getCurrentTimeMs: function () { return new Date().getTime() }
47 property var __dateTime: new function() {
48 this.getCurrentTimeMs = function() {return new Date().getTime()}
49 }
50
51 function pushDragEvent(event) {
52 var currentTime = __dateTime.getCurrentTimeMs()
53 dragEvents.push([currentTime, event.x - event.startX, event.y - event.startY, getEventSpeed(currentTime, event)])
54 cullOldDragEvents(currentTime)
55 updateSpeed()
56 }
57
58 function cullOldDragEvents(currentTime) {
59 // cull events older than 50 ms but always keep the latest 2 events
60 for (var numberOfCulledEvents = 0; numberOfCulledEvents < dragEvents.length-2; numberOfCulledEvents++) {
61 // dragEvents[numberOfCulledEvents][0] is the dragTime
62 if (currentTime - dragEvents[numberOfCulledEvents][0] <= 50) break
63 }
64
65 dragEvents.splice(0, numberOfCulledEvents)
66 }
67
68 function updateSpeed() {
69 var totalSpeed = 0
70 for (var i = 0; i < dragEvents.length; i++) {
71 totalSpeed += dragEvents[i][3]
72 }
73
74 if (zeroVelocityCounts || Math.abs(totalSpeed) > 0.001) {
75 dragVelocity = totalSpeed / dragEvents.length * 1000
76 }
77 }
78
79 function getEventSpeed(currentTime, event) {
80 if (dragEvents.length != 0) {
81 var lastDrag = dragEvents[dragEvents.length-1]
82 var duration = Math.max(1, currentTime - lastDrag[0])
83 return (event.y - event.startY - lastDrag[2]) / duration
84 } else {
85 return 0
86 }
87 }
88 }
89
90 MultiPointTouchArea {
91 anchors.fill: parent
92 maximumTouchPoints: 1
93 property int offset: 0
94
95 // tp.startY seems to be broken for mouse interaction... lets track it ourselves
96 property int startY: 0
97
98 touchPoints: [
99 TouchPoint {
100 id: tp
101 }
102 ]
103
104 onPressed: {
105 startY = tp.y
106 }
107
108 onTouchUpdated: {
109 if (!tp.pressed) {
110 dragDelegate.Drag.active = false;
111 dragDelegate.surface = null;
112 d.moving = false
113 animation.animate("center");
114 return;
115 } else if (!d.moving) {
116 if (Math.abs(startY - tp.y) > d.threshold) {
117 d.moving = true;
118 d.dragEvents = []
119 offset = tp.y - tp.startY;
120 } else {
121 return;
122 }
123 }
124
125 var value = tp.y - tp.startY - offset;
126 if (value < 0 && stage.workspaceEnabled) {
127 var coords = mapToItem(stage, tp.x, tp.y);
128 dragDelegate.Drag.hotSpot.x = dragDelegate.width / 2
129 dragDelegate.Drag.hotSpot.y = units.gu(2)
130 dragDelegate.x = coords.x - dragDelegate.Drag.hotSpot.x
131 dragDelegate.y = coords.y - dragDelegate.Drag.hotSpot.y
132 dragDelegate.Drag.active = true;
133 dragDelegate.surface = model.window.surface;
134 } else {
135 if (root.closeable) {
136 if (value != 0)
137 d.distance = value
138 } else {
139 d.distance = Math.sqrt(Math.abs(value)) * (value < 0 ? -1 : 1) * 3
140 }
141 }
142
143 d.pushDragEvent(tp);
144 }
145
146 onReleased: {
147 var result = dragDelegate.Drag.drop();
148 dragDelegate.surface = null;
149
150 if (!d.moving) {
151 root.clicked()
152 }
153
154 if (!root.closeable) {
155 animation.animate("center")
156 return;
157 }
158
159 if ((d.dragVelocity < -root.minSpeedToClose && d.distance < -units.gu(8)) || d.distance < -root.height / 2) {
160 animation.animate("up")
161 } else if ((d.dragVelocity > root.minSpeedToClose && d.distance > units.gu(8)) || d.distance > root.height / 2) {
162 animation.animate("down")
163 } else {
164 animation.animate("center")
165 }
166 }
167
168 onCanceled: {
169 dragDelegate.Drag.active = false;
170 dragDelegate.surface = null;
171 d.moving = false
172 animation.animate("center");
173 }
174 }
175
176 LomiriNumberAnimation {
177 id: animation
178 objectName: "closeAnimation"
179 target: d
180 property: "distance"
181 property bool requestClose: false
182
183 function animate(direction) {
184 animation.from = d.distance;
185 switch (direction) {
186 case "up":
187 animation.to = -root.height * 1.5;
188 requestClose = true;
189 break;
190 case "down":
191 animation.to = root.height * 1.5;
192 requestClose = true;
193 break;
194 default:
195 animation.to = 0
196 }
197 animation.start();
198 }
199
200 onRunningChanged: {
201 if (!running) {
202 d.moving = false;
203 if (requestClose) {
204 root.close();
205 } else {
206 d.distance = 0;
207 }
208 }
209 }
210 }
211}