-
Notifications
You must be signed in to change notification settings - Fork 66
/
Copy pathtouch_gesture.js
328 lines (294 loc) · 9.71 KB
/
touch_gesture.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The class extends Blockly.Gesture to support pinch to zoom
* for both pointer and touch events.
* @author [email protected] (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.TouchGesture');
goog.require('Blockly.browserEvents');
goog.require('Blockly.Gesture');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.object');
goog.requireType('Blockly.WorkspaceSvg');
/*
* Note: In this file "start" refers to touchstart, mousedown, and pointerstart
* events. "End" refers to touchend, mouseup, and pointerend events.
*/
/**
* Class for one gesture.
* @param {!Event} e The event that kicked off this gesture.
* @param {!Blockly.WorkspaceSvg} creatorWorkspace The workspace that created
* this gesture and has a reference to it.
* @extends {Blockly.Gesture}
* @constructor
*/
Blockly.TouchGesture = function(e, creatorWorkspace) {
Blockly.TouchGesture.superClass_.constructor.call(this, e, creatorWorkspace);
/**
* Boolean for whether or not this gesture is a multi-touch gesture.
* @type {boolean}
* @private
*/
this.isMultiTouch_ = false;
/**
* A map of cached points used for tracking multi-touch gestures.
* @type {!Object<number|string, Blockly.utils.Coordinate>}
* @private
*/
this.cachedPoints_ = Object.create(null);
/**
* This is the ratio between the starting distance between the touch points
* and the most recent distance between the touch points.
* Scales between 0 and 1 mean the most recent zoom was a zoom out.
* Scales above 1.0 mean the most recent zoom was a zoom in.
* @type {number}
* @private
*/
this.previousScale_ = 0;
/**
* The starting distance between two touch points.
* @type {number}
* @private
*/
this.startDistance_ = 0;
/**
* A handle to use to unbind the second touch start or pointer down listener
* at the end of a drag.
* Opaque data returned from Blockly.bindEventWithChecks_.
* @type {?Blockly.browserEvents.Data}
* @private
*/
this.onStartWrapper_ = null;
/**
* Boolean for whether or not the workspace supports pinch-zoom.
* @type {?boolean}
* @private
*/
this.isPinchZoomEnabled_ = null;
};
Blockly.utils.object.inherits(Blockly.TouchGesture, Blockly.Gesture);
/**
* A multiplier used to convert the gesture scale to a zoom in delta.
* @const
*/
Blockly.TouchGesture.ZOOM_IN_MULTIPLIER = 5;
/**
* A multiplier used to convert the gesture scale to a zoom out delta.
* @const
*/
Blockly.TouchGesture.ZOOM_OUT_MULTIPLIER = 6;
/**
* Start a gesture: update the workspace to indicate that a gesture is in
* progress and bind mousemove and mouseup handlers.
* @param {!Event} e A mouse down, touch start or pointer down event.
* @package
*/
Blockly.TouchGesture.prototype.doStart = function(e) {
this.isPinchZoomEnabled_ = this.startWorkspace_.options.zoomOptions &&
this.startWorkspace_.options.zoomOptions.pinch;
Blockly.TouchGesture.superClass_.doStart.call(this, e);
if (!this.isEnding_ && Blockly.Touch.isTouchEvent(e)) {
this.handleTouchStart(e);
}
};
/**
* Bind gesture events.
* Overriding the gesture definition of this function, binding the same
* functions for onMoveWrapper_ and onUpWrapper_ but passing
* opt_noCaptureIdentifier.
* In addition, binding a second mouse down event to detect multi-touch events.
* @param {!Event} e A mouse down or touch start event.
* @package
*/
Blockly.TouchGesture.prototype.bindMouseEvents = function(e) {
this.onStartWrapper_ = Blockly.browserEvents.conditionalBind(
document, 'mousedown', null, this.handleStart.bind(this),
/* opt_noCaptureIdentifier */ true);
this.onMoveWrapper_ = Blockly.browserEvents.conditionalBind(
document, 'mousemove', null, this.handleMove.bind(this),
/* opt_noCaptureIdentifier */ true);
this.onUpWrapper_ = Blockly.browserEvents.conditionalBind(
document, 'mouseup', null, this.handleUp.bind(this),
/* opt_noCaptureIdentifier */ true);
e.preventDefault();
e.stopPropagation();
};
/**
* Handle a mouse down, touch start, or pointer down event.
* @param {!Event} e A mouse down, touch start, or pointer down event.
* @package
*/
Blockly.TouchGesture.prototype.handleStart = function(e) {
if (this.isDragging()) {
// A drag has already started, so this can no longer be a pinch-zoom.
return;
}
if (Blockly.Touch.isTouchEvent(e)) {
this.handleTouchStart(e);
if (this.isMultiTouch()) {
Blockly.longStop_();
}
}
};
/**
* Handle a mouse move, touch move, or pointer move event.
* @param {!Event} e A mouse move, touch move, or pointer move event.
* @package
*/
Blockly.TouchGesture.prototype.handleMove = function(e) {
if (this.isDragging()) {
// We are in the middle of a drag, only handle the relevant events
if (Blockly.Touch.shouldHandleEvent(e)) {
Blockly.TouchGesture.superClass_.handleMove.call(this, e);
}
return;
}
if (this.isMultiTouch()) {
if (Blockly.Touch.isTouchEvent(e)) {
this.handleTouchMove(e);
}
Blockly.longStop_();
} else {
Blockly.TouchGesture.superClass_.handleMove.call(this, e);
}
};
/**
* Handle a mouse up, touch end, or pointer up event.
* @param {!Event} e A mouse up, touch end, or pointer up event.
* @package
*/
Blockly.TouchGesture.prototype.handleUp = function(e) {
if (Blockly.Touch.isTouchEvent(e) && !this.isDragging()) {
this.handleTouchEnd(e);
}
if (!this.isMultiTouch() || this.isDragging()) {
if (!Blockly.Touch.shouldHandleEvent(e)) {
return;
}
Blockly.TouchGesture.superClass_.handleUp.call(this, e);
} else {
e.preventDefault();
e.stopPropagation();
this.dispose();
}
};
/**
* Whether this gesture is part of a multi-touch gesture.
* @return {boolean} Whether this gesture is part of a multi-touch gesture.
* @package
*/
Blockly.TouchGesture.prototype.isMultiTouch = function() {
return this.isMultiTouch_;
};
/**
* Sever all links from this object.
* @package
*/
Blockly.TouchGesture.prototype.dispose = function() {
Blockly.TouchGesture.superClass_.dispose.call(this);
if (this.onStartWrapper_) {
Blockly.browserEvents.unbind(this.onStartWrapper_);
}
};
/**
* Handle a touch start or pointer down event and keep track of current
* pointers.
* @param {!Event} e A touch start, or pointer down event.
* @package
*/
Blockly.TouchGesture.prototype.handleTouchStart = function(e) {
var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e);
// store the pointerId in the current list of pointers
this.cachedPoints_[pointerId] = this.getTouchPoint(e);
var pointers = Object.keys(this.cachedPoints_);
// If two pointers are down, store info
if (pointers.length == 2) {
var point0 = /** @type {!Blockly.utils.Coordinate} */ (
this.cachedPoints_[pointers[0]]);
var point1 = /** @type {!Blockly.utils.Coordinate} */ (
this.cachedPoints_[pointers[1]]);
this.startDistance_ = Blockly.utils.Coordinate.distance(point0, point1);
this.isMultiTouch_ = true;
e.preventDefault();
}
};
/**
* Handle a touch move or pointer move event and zoom in/out if two pointers
* are on the screen.
* @param {!Event} e A touch move, or pointer move event.
* @package
*/
Blockly.TouchGesture.prototype.handleTouchMove = function(e) {
var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e);
// Update the cache
this.cachedPoints_[pointerId] = this.getTouchPoint(e);
var pointers = Object.keys(this.cachedPoints_);
if (this.isPinchZoomEnabled_ && pointers.length === 2) {
this.handlePinch_(e);
} else {
Blockly.TouchGesture.superClass_.handleMove.call(this, e);
}
};
/**
* Handle pinch zoom gesture.
* @param {!Event} e A touch move, or pointer move event.
* @private
*/
Blockly.TouchGesture.prototype.handlePinch_ = function(e) {
var pointers = Object.keys(this.cachedPoints_);
// Calculate the distance between the two pointers
var point0 = /** @type {!Blockly.utils.Coordinate} */ (
this.cachedPoints_[pointers[0]]);
var point1 = /** @type {!Blockly.utils.Coordinate} */ (
this.cachedPoints_[pointers[1]]);
var moveDistance = Blockly.utils.Coordinate.distance(point0, point1);
var scale = moveDistance / this.startDistance_;
if (this.previousScale_ > 0 && this.previousScale_ < Infinity) {
var gestureScale = scale - this.previousScale_;
var delta = gestureScale > 0 ?
gestureScale * Blockly.TouchGesture.ZOOM_IN_MULTIPLIER :
gestureScale * Blockly.TouchGesture.ZOOM_OUT_MULTIPLIER;
var workspace = this.startWorkspace_;
var position = Blockly.utils.mouseToSvg(
e, workspace.getParentSvg(), workspace.getInverseScreenCTM());
workspace.zoom(position.x, position.y, delta);
}
this.previousScale_ = scale;
e.preventDefault();
};
/**
* Handle a touch end or pointer end event and end the gesture.
* @param {!Event} e A touch end, or pointer end event.
* @package
*/
Blockly.TouchGesture.prototype.handleTouchEnd = function(e) {
var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e);
if (this.cachedPoints_[pointerId]) {
delete this.cachedPoints_[pointerId];
}
if (Object.keys(this.cachedPoints_).length < 2) {
this.cachedPoints_ = Object.create(null);
this.previousScale_ = 0;
}
};
/**
* Helper function returning the current touch point coordinate.
* @param {!Event} e A touch or pointer event.
* @return {?Blockly.utils.Coordinate} The current touch point coordinate
* @package
*/
Blockly.TouchGesture.prototype.getTouchPoint = function(e) {
if (!this.startWorkspace_) {
return null;
}
return new Blockly.utils.Coordinate(
(e.pageX ? e.pageX : e.changedTouches[0].pageX),
(e.pageY ? e.pageY : e.changedTouches[0].pageY)
);
};