Wed, 31 Dec 2014 07:16:47 +0100
Revert simplistic fix pending revisit of Mozilla integration attempt.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | /* global Components, GestureSettings, XPCOMUtils, Utils, Promise, Logger */ |
michael@0 | 6 | /* exported GestureSettings, GestureTracker */ |
michael@0 | 7 | |
michael@0 | 8 | /****************************************************************************** |
michael@0 | 9 | All gestures have the following pathways when being resolved(v)/rejected(x): |
michael@0 | 10 | Tap -> DoubleTap (v) |
michael@0 | 11 | -> Dwell (x) |
michael@0 | 12 | -> Swipe (x) |
michael@0 | 13 | |
michael@0 | 14 | AndroidTap -> TripleTap (v) |
michael@0 | 15 | -> TapHold (x) |
michael@0 | 16 | -> Swipe (x) |
michael@0 | 17 | |
michael@0 | 18 | DoubleTap -> TripleTap (v) |
michael@0 | 19 | -> TapHold (x) |
michael@0 | 20 | -> Explore (x) |
michael@0 | 21 | |
michael@0 | 22 | TripleTap -> DoubleTapHold (x) |
michael@0 | 23 | -> Explore (x) |
michael@0 | 24 | |
michael@0 | 25 | Dwell -> DwellEnd (v) |
michael@0 | 26 | |
michael@0 | 27 | Swipe -> Explore (x) |
michael@0 | 28 | |
michael@0 | 29 | TapHold -> TapHoldEnd (v) |
michael@0 | 30 | |
michael@0 | 31 | DoubleTapHold -> DoubleTapHoldEnd (v) |
michael@0 | 32 | |
michael@0 | 33 | DwellEnd -> Explore (x) |
michael@0 | 34 | |
michael@0 | 35 | TapHoldEnd -> Explore (x) |
michael@0 | 36 | |
michael@0 | 37 | DoubleTapHoldEnd -> Explore (x) |
michael@0 | 38 | |
michael@0 | 39 | ExploreEnd -> Explore (x) |
michael@0 | 40 | |
michael@0 | 41 | Explore -> ExploreEnd (v) |
michael@0 | 42 | ******************************************************************************/ |
michael@0 | 43 | |
michael@0 | 44 | 'use strict'; |
michael@0 | 45 | |
michael@0 | 46 | const Ci = Components.interfaces; |
michael@0 | 47 | const Cu = Components.utils; |
michael@0 | 48 | |
michael@0 | 49 | this.EXPORTED_SYMBOLS = ['GestureSettings', 'GestureTracker']; // jshint ignore:line |
michael@0 | 50 | |
michael@0 | 51 | Cu.import('resource://gre/modules/XPCOMUtils.jsm'); |
michael@0 | 52 | |
michael@0 | 53 | XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line |
michael@0 | 54 | 'resource://gre/modules/accessibility/Utils.jsm'); |
michael@0 | 55 | XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line |
michael@0 | 56 | 'resource://gre/modules/accessibility/Utils.jsm'); |
michael@0 | 57 | XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout', // jshint ignore:line |
michael@0 | 58 | 'resource://gre/modules/Timer.jsm'); |
michael@0 | 59 | XPCOMUtils.defineLazyModuleGetter(this, 'clearTimeout', // jshint ignore:line |
michael@0 | 60 | 'resource://gre/modules/Timer.jsm'); |
michael@0 | 61 | XPCOMUtils.defineLazyModuleGetter(this, 'Promise', // jshint ignore:line |
michael@0 | 62 | 'resource://gre/modules/Promise.jsm'); |
michael@0 | 63 | |
michael@0 | 64 | // Maximum amount of time allowed for a gesture to be considered a multitouch. |
michael@0 | 65 | const MAX_MULTITOUCH = 250; |
michael@0 | 66 | // Minimal swipe distance in inches |
michael@0 | 67 | const SWIPE_MIN_DISTANCE = 0.4; |
michael@0 | 68 | // Maximum distance the pointer could move during a tap in inches |
michael@0 | 69 | const TAP_MAX_RADIUS = 0.2; |
michael@0 | 70 | // Directness coefficient. It is based on the maximum 15 degree angle between |
michael@0 | 71 | // consequent pointer move lines. |
michael@0 | 72 | const DIRECTNESS_COEFF = 1.44; |
michael@0 | 73 | // An android flag. |
michael@0 | 74 | const IS_ANDROID = Utils.MozBuildApp === 'mobile/android' && |
michael@0 | 75 | Utils.AndroidSdkVersion >= 14; |
michael@0 | 76 | // A single pointer down/up sequence periodically precedes the tripple swipe |
michael@0 | 77 | // gesture on Android. This delay acounts for that. |
michael@0 | 78 | const ANDROID_TRIPLE_SWIPE_DELAY = 50; |
michael@0 | 79 | // The virtual touch ID generated by a mouse event. |
michael@0 | 80 | const MOUSE_ID = 'mouse'; |
michael@0 | 81 | |
michael@0 | 82 | /** |
michael@0 | 83 | * A point object containing distance travelled data. |
michael@0 | 84 | * @param {Object} aPoint A point object that looks like: { |
michael@0 | 85 | * x: x coordinate in pixels, |
michael@0 | 86 | * y: y coordinate in pixels |
michael@0 | 87 | * } |
michael@0 | 88 | */ |
michael@0 | 89 | function Point(aPoint) { |
michael@0 | 90 | this.startX = this.x = aPoint.x; |
michael@0 | 91 | this.startY = this.y = aPoint.y; |
michael@0 | 92 | this.distanceTraveled = 0; |
michael@0 | 93 | this.totalDistanceTraveled = 0; |
michael@0 | 94 | } |
michael@0 | 95 | |
michael@0 | 96 | Point.prototype = { |
michael@0 | 97 | /** |
michael@0 | 98 | * Update the current point coordiates. |
michael@0 | 99 | * @param {Object} aPoint A new point coordinates. |
michael@0 | 100 | */ |
michael@0 | 101 | update: function Point_update(aPoint) { |
michael@0 | 102 | let lastX = this.x; |
michael@0 | 103 | let lastY = this.y; |
michael@0 | 104 | this.x = aPoint.x; |
michael@0 | 105 | this.y = aPoint.y; |
michael@0 | 106 | this.distanceTraveled = this.getDistanceToCoord(lastX, lastY); |
michael@0 | 107 | this.totalDistanceTraveled += this.distanceTraveled; |
michael@0 | 108 | }, |
michael@0 | 109 | |
michael@0 | 110 | reset: function Point_reset() { |
michael@0 | 111 | this.distanceTraveled = 0; |
michael@0 | 112 | this.totalDistanceTraveled = 0; |
michael@0 | 113 | }, |
michael@0 | 114 | |
michael@0 | 115 | /** |
michael@0 | 116 | * Get distance between the current point coordinates and the given ones. |
michael@0 | 117 | * @param {Number} aX A pixel value for the x coordinate. |
michael@0 | 118 | * @param {Number} aY A pixel value for the y coordinate. |
michael@0 | 119 | * @return {Number} A distance between point's current and the given |
michael@0 | 120 | * coordinates. |
michael@0 | 121 | */ |
michael@0 | 122 | getDistanceToCoord: function Point_getDistanceToCoord(aX, aY) { |
michael@0 | 123 | return Math.hypot(this.x - aX, this.y - aY); |
michael@0 | 124 | }, |
michael@0 | 125 | |
michael@0 | 126 | /** |
michael@0 | 127 | * Get the direct distance travelled by the point so far. |
michael@0 | 128 | */ |
michael@0 | 129 | get directDistanceTraveled() { |
michael@0 | 130 | return this.getDistanceToCoord(this.startX, this.startY); |
michael@0 | 131 | } |
michael@0 | 132 | }; |
michael@0 | 133 | |
michael@0 | 134 | /** |
michael@0 | 135 | * An externally accessible collection of settings used in gesture resolition. |
michael@0 | 136 | * @type {Object} |
michael@0 | 137 | */ |
michael@0 | 138 | this.GestureSettings = { // jshint ignore:line |
michael@0 | 139 | /** |
michael@0 | 140 | * Maximum duration of swipe |
michael@0 | 141 | * @type {Number} |
michael@0 | 142 | */ |
michael@0 | 143 | swipeMaxDuration: 400, |
michael@0 | 144 | |
michael@0 | 145 | /** |
michael@0 | 146 | * Maximum consecutive pointer event timeout. |
michael@0 | 147 | * @type {Number} |
michael@0 | 148 | */ |
michael@0 | 149 | maxConsecutiveGestureDelay: 400, |
michael@0 | 150 | |
michael@0 | 151 | /** |
michael@0 | 152 | * Delay before tap turns into dwell |
michael@0 | 153 | * @type {Number} |
michael@0 | 154 | */ |
michael@0 | 155 | dwellThreshold: 500, |
michael@0 | 156 | |
michael@0 | 157 | /** |
michael@0 | 158 | * Minimum distance that needs to be travelled for the pointer move to be |
michael@0 | 159 | * fired. |
michael@0 | 160 | * @type {Number} |
michael@0 | 161 | */ |
michael@0 | 162 | travelThreshold: 0.025 |
michael@0 | 163 | }; |
michael@0 | 164 | |
michael@0 | 165 | /** |
michael@0 | 166 | * An interface that handles the pointer events and calculates the appropriate |
michael@0 | 167 | * gestures. |
michael@0 | 168 | * @type {Object} |
michael@0 | 169 | */ |
michael@0 | 170 | this.GestureTracker = { // jshint ignore:line |
michael@0 | 171 | /** |
michael@0 | 172 | * Reset GestureTracker to its initial state. |
michael@0 | 173 | * @return {[type]} [description] |
michael@0 | 174 | */ |
michael@0 | 175 | reset: function GestureTracker_reset() { |
michael@0 | 176 | if (this.current) { |
michael@0 | 177 | this.current.clearTimer(); |
michael@0 | 178 | } |
michael@0 | 179 | delete this.current; |
michael@0 | 180 | }, |
michael@0 | 181 | |
michael@0 | 182 | /** |
michael@0 | 183 | * Create a new gesture object and attach resolution handler to it as well as |
michael@0 | 184 | * handle the incoming pointer event. |
michael@0 | 185 | * @param {Object} aDetail A new pointer event detail. |
michael@0 | 186 | * @param {Number} aTimeStamp A new pointer event timeStamp. |
michael@0 | 187 | * @param {Function} aGesture A gesture constructor (default: Tap). |
michael@0 | 188 | */ |
michael@0 | 189 | _init: function GestureTracker__init(aDetail, aTimeStamp, aGesture = Tap) { |
michael@0 | 190 | // Only create a new gesture on |pointerdown| event. |
michael@0 | 191 | if (aDetail.type !== 'pointerdown') { |
michael@0 | 192 | return; |
michael@0 | 193 | } |
michael@0 | 194 | let points = aDetail.points; |
michael@0 | 195 | let GestureConstructor = aGesture; |
michael@0 | 196 | if (IS_ANDROID && GestureConstructor === Tap && points.length === 1 && |
michael@0 | 197 | points[0].identifier !== MOUSE_ID) { |
michael@0 | 198 | // Handle Android events when EBT is enabled. Two finger gestures are |
michael@0 | 199 | // translated to one. |
michael@0 | 200 | GestureConstructor = AndroidTap; |
michael@0 | 201 | } |
michael@0 | 202 | this._create(GestureConstructor); |
michael@0 | 203 | this._update(aDetail, aTimeStamp); |
michael@0 | 204 | }, |
michael@0 | 205 | |
michael@0 | 206 | /** |
michael@0 | 207 | * Handle the incoming pointer event with the existing gesture object(if |
michael@0 | 208 | * present) or with the newly created one. |
michael@0 | 209 | * @param {Object} aDetail A new pointer event detail. |
michael@0 | 210 | * @param {Number} aTimeStamp A new pointer event timeStamp. |
michael@0 | 211 | */ |
michael@0 | 212 | handle: function GestureTracker_handle(aDetail, aTimeStamp) { |
michael@0 | 213 | Logger.debug(() => { |
michael@0 | 214 | return ['Pointer event', aDetail.type, 'at:', aTimeStamp, |
michael@0 | 215 | JSON.stringify(aDetail.points)]; |
michael@0 | 216 | }); |
michael@0 | 217 | this[this.current ? '_update' : '_init'](aDetail, aTimeStamp); |
michael@0 | 218 | }, |
michael@0 | 219 | |
michael@0 | 220 | /** |
michael@0 | 221 | * Create a new gesture object and attach resolution handler to it. |
michael@0 | 222 | * @param {Function} aGesture A gesture constructor. |
michael@0 | 223 | * @param {Number} aTimeStamp An original pointer event timeStamp. |
michael@0 | 224 | * @param {Array} aPoints All changed points associated with the new pointer |
michael@0 | 225 | * event. |
michael@0 | 226 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 227 | */ |
michael@0 | 228 | _create: function GestureTracker__create(aGesture, aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 229 | this.current = new aGesture(aTimeStamp, aPoints, aLastEvent); /* A constructor name should start with an uppercase letter. */ // jshint ignore:line |
michael@0 | 230 | this.current.then(this._onFulfill.bind(this)); |
michael@0 | 231 | }, |
michael@0 | 232 | |
michael@0 | 233 | /** |
michael@0 | 234 | * Handle the incoming pointer event with the existing gesture object. |
michael@0 | 235 | * @param {Object} aDetail A new pointer event detail. |
michael@0 | 236 | * @param {Number} aTimeStamp A new pointer event timeStamp. |
michael@0 | 237 | */ |
michael@0 | 238 | _update: function GestureTracker_update(aDetail, aTimeStamp) { |
michael@0 | 239 | this.current[aDetail.type](aDetail.points, aTimeStamp); |
michael@0 | 240 | }, |
michael@0 | 241 | |
michael@0 | 242 | /** |
michael@0 | 243 | * A resolution handler function for the current gesture promise. |
michael@0 | 244 | * @param {Object} aResult A resolution payload with the relevant gesture id |
michael@0 | 245 | * and an optional new gesture contructor. |
michael@0 | 246 | */ |
michael@0 | 247 | _onFulfill: function GestureTracker__onFulfill(aResult) { |
michael@0 | 248 | let {id, gestureType} = aResult; |
michael@0 | 249 | let current = this.current; |
michael@0 | 250 | // Do nothing if there's no existing gesture or there's already a newer |
michael@0 | 251 | // gesture. |
michael@0 | 252 | if (!current || current.id !== id) { |
michael@0 | 253 | return; |
michael@0 | 254 | } |
michael@0 | 255 | // Only create a gesture if we got a constructor. |
michael@0 | 256 | if (gestureType) { |
michael@0 | 257 | this._create(gestureType, current.startTime, current.points, |
michael@0 | 258 | current.lastEvent); |
michael@0 | 259 | } else { |
michael@0 | 260 | delete this.current; |
michael@0 | 261 | } |
michael@0 | 262 | } |
michael@0 | 263 | }; |
michael@0 | 264 | |
michael@0 | 265 | /** |
michael@0 | 266 | * Compile a mozAccessFuGesture detail structure. |
michael@0 | 267 | * @param {String} aType A gesture type. |
michael@0 | 268 | * @param {Object} aPoints Gesture's points. |
michael@0 | 269 | * @param {String} xKey A default key for the x coordinate. Default is |
michael@0 | 270 | * 'startX'. |
michael@0 | 271 | * @param {String} yKey A default key for the y coordinate. Default is |
michael@0 | 272 | * 'startY'. |
michael@0 | 273 | * @return {Object} a mozAccessFuGesture detail structure. |
michael@0 | 274 | */ |
michael@0 | 275 | function compileDetail(aType, aPoints, keyMap = {x: 'startX', y: 'startY'}) { |
michael@0 | 276 | let touches = []; |
michael@0 | 277 | let maxDeltaX = 0; |
michael@0 | 278 | let maxDeltaY = 0; |
michael@0 | 279 | for (let identifier in aPoints) { |
michael@0 | 280 | let point = aPoints[identifier]; |
michael@0 | 281 | let touch = {}; |
michael@0 | 282 | for (let key in keyMap) { |
michael@0 | 283 | touch[key] = point[keyMap[key]]; |
michael@0 | 284 | } |
michael@0 | 285 | touches.push(touch); |
michael@0 | 286 | let deltaX = point.x - point.startX; |
michael@0 | 287 | let deltaY = point.y - point.startY; |
michael@0 | 288 | // Determine the maximum x and y travel intervals. |
michael@0 | 289 | if (Math.abs(maxDeltaX) < Math.abs(deltaX)) { |
michael@0 | 290 | maxDeltaX = deltaX; |
michael@0 | 291 | } |
michael@0 | 292 | if (Math.abs(maxDeltaY) < Math.abs(deltaY)) { |
michael@0 | 293 | maxDeltaY = deltaY; |
michael@0 | 294 | } |
michael@0 | 295 | // Since the gesture is resolving, reset the points' distance information |
michael@0 | 296 | // since they are passed to the next potential gesture. |
michael@0 | 297 | point.reset(); |
michael@0 | 298 | } |
michael@0 | 299 | return { |
michael@0 | 300 | type: aType, |
michael@0 | 301 | touches: touches, |
michael@0 | 302 | deltaX: maxDeltaX, |
michael@0 | 303 | deltaY: maxDeltaY |
michael@0 | 304 | }; |
michael@0 | 305 | } |
michael@0 | 306 | |
michael@0 | 307 | /** |
michael@0 | 308 | * A general gesture object. |
michael@0 | 309 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 310 | * the gesture resolution sequence. |
michael@0 | 311 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 312 | * Default is an empty object. |
michael@0 | 313 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 314 | */ |
michael@0 | 315 | function Gesture(aTimeStamp, aPoints = {}, aLastEvent = undefined) { |
michael@0 | 316 | this.startTime = Date.now(); |
michael@0 | 317 | Logger.debug('Creating', this.id, 'gesture.'); |
michael@0 | 318 | this.points = aPoints; |
michael@0 | 319 | this.lastEvent = aLastEvent; |
michael@0 | 320 | this._deferred = Promise.defer(); |
michael@0 | 321 | // Call this._handleResolve or this._handleReject when the promise is |
michael@0 | 322 | // fulfilled with either resolve or reject. |
michael@0 | 323 | this.promise = this._deferred.promise.then(this._handleResolve.bind(this), |
michael@0 | 324 | this._handleReject.bind(this)); |
michael@0 | 325 | this.startTimer(aTimeStamp); |
michael@0 | 326 | } |
michael@0 | 327 | |
michael@0 | 328 | Gesture.prototype = { |
michael@0 | 329 | /** |
michael@0 | 330 | * Get the gesture timeout delay. |
michael@0 | 331 | * @return {Number} |
michael@0 | 332 | */ |
michael@0 | 333 | _getDelay: function Gesture__getDelay() { |
michael@0 | 334 | // If nothing happens withing the |
michael@0 | 335 | // GestureSettings.maxConsecutiveGestureDelay, we should not wait for any |
michael@0 | 336 | // more pointer events and consider them the part of the same gesture - |
michael@0 | 337 | // reject this gesture promise. |
michael@0 | 338 | return GestureSettings.maxConsecutiveGestureDelay; |
michael@0 | 339 | }, |
michael@0 | 340 | |
michael@0 | 341 | /** |
michael@0 | 342 | * Clear the existing timer. |
michael@0 | 343 | */ |
michael@0 | 344 | clearTimer: function Gesture_clearTimer() { |
michael@0 | 345 | clearTimeout(this._timer); |
michael@0 | 346 | delete this._timer; |
michael@0 | 347 | }, |
michael@0 | 348 | |
michael@0 | 349 | /** |
michael@0 | 350 | * Start the timer for gesture timeout. |
michael@0 | 351 | * @param {Number} aTimeStamp An original pointer event's timeStamp that |
michael@0 | 352 | * started the gesture resolution sequence. |
michael@0 | 353 | */ |
michael@0 | 354 | startTimer: function Gesture_startTimer(aTimeStamp) { |
michael@0 | 355 | this.clearTimer(); |
michael@0 | 356 | let delay = this._getDelay(aTimeStamp); |
michael@0 | 357 | let handler = () => { |
michael@0 | 358 | delete this._timer; |
michael@0 | 359 | if (!this._inProgress) { |
michael@0 | 360 | this._deferred.reject(); |
michael@0 | 361 | } else if (this._rejectToOnWait) { |
michael@0 | 362 | this._deferred.reject(this._rejectToOnWait); |
michael@0 | 363 | } |
michael@0 | 364 | }; |
michael@0 | 365 | if (delay <= 0) { |
michael@0 | 366 | handler(); |
michael@0 | 367 | } else { |
michael@0 | 368 | this._timer = setTimeout(handler, delay); |
michael@0 | 369 | } |
michael@0 | 370 | }, |
michael@0 | 371 | |
michael@0 | 372 | /** |
michael@0 | 373 | * Add a gesture promise resolution callback. |
michael@0 | 374 | * @param {Function} aCallback |
michael@0 | 375 | */ |
michael@0 | 376 | then: function Gesture_then(aCallback) { |
michael@0 | 377 | this.promise.then(aCallback); |
michael@0 | 378 | }, |
michael@0 | 379 | |
michael@0 | 380 | /** |
michael@0 | 381 | * Update gesture's points. Test the points set with the optional gesture test |
michael@0 | 382 | * function. |
michael@0 | 383 | * @param {Array} aPoints An array with the changed points from the new |
michael@0 | 384 | * pointer event. |
michael@0 | 385 | * @param {String} aType Pointer event type. |
michael@0 | 386 | * @param {Boolean} aCanCreate A flag that enables including the new points. |
michael@0 | 387 | * Default is false. |
michael@0 | 388 | * @param {Boolean} aNeedComplete A flag that indicates that the gesture is |
michael@0 | 389 | * completing. Default is false. |
michael@0 | 390 | * @return {Boolean} Indicates whether the gesture can be complete (it is |
michael@0 | 391 | * set to true iff the aNeedComplete is true and there was a change to at |
michael@0 | 392 | * least one point that belongs to the gesture). |
michael@0 | 393 | */ |
michael@0 | 394 | _update: function Gesture__update(aPoints, aType, aCanCreate = false, aNeedComplete = false) { |
michael@0 | 395 | let complete; |
michael@0 | 396 | let lastEvent; |
michael@0 | 397 | for (let point of aPoints) { |
michael@0 | 398 | let identifier = point.identifier; |
michael@0 | 399 | let gesturePoint = this.points[identifier]; |
michael@0 | 400 | if (gesturePoint) { |
michael@0 | 401 | gesturePoint.update(point); |
michael@0 | 402 | if (aNeedComplete) { |
michael@0 | 403 | // Since the gesture is completing and at least one of the gesture |
michael@0 | 404 | // points is updated, set the return value to true. |
michael@0 | 405 | complete = true; |
michael@0 | 406 | } |
michael@0 | 407 | lastEvent = lastEvent || aType; |
michael@0 | 408 | } else if (aCanCreate) { |
michael@0 | 409 | // Only create a new point if aCanCreate is true. |
michael@0 | 410 | this.points[identifier] = |
michael@0 | 411 | new Point(point); |
michael@0 | 412 | lastEvent = lastEvent || aType; |
michael@0 | 413 | } |
michael@0 | 414 | } |
michael@0 | 415 | this.lastEvent = lastEvent || this.lastEvent; |
michael@0 | 416 | // If test function is defined test the points. |
michael@0 | 417 | if (this.test) { |
michael@0 | 418 | this.test(complete); |
michael@0 | 419 | } |
michael@0 | 420 | return complete; |
michael@0 | 421 | }, |
michael@0 | 422 | |
michael@0 | 423 | /** |
michael@0 | 424 | * Emit a mozAccessFuGesture (when the gesture is resolved). |
michael@0 | 425 | * @param {Object} aDetail a compiled mozAccessFuGesture detail structure. |
michael@0 | 426 | */ |
michael@0 | 427 | _emit: function Gesture__emit(aDetail) { |
michael@0 | 428 | let evt = new Utils.win.CustomEvent('mozAccessFuGesture', { |
michael@0 | 429 | bubbles: true, |
michael@0 | 430 | cancelable: true, |
michael@0 | 431 | detail: aDetail |
michael@0 | 432 | }); |
michael@0 | 433 | Utils.win.dispatchEvent(evt); |
michael@0 | 434 | }, |
michael@0 | 435 | |
michael@0 | 436 | /** |
michael@0 | 437 | * Handle the pointer down event. |
michael@0 | 438 | * @param {Array} aPoints A new pointer down points. |
michael@0 | 439 | * @param {Number} aTimeStamp A new pointer down timeStamp. |
michael@0 | 440 | */ |
michael@0 | 441 | pointerdown: function Gesture_pointerdown(aPoints, aTimeStamp) { |
michael@0 | 442 | this._inProgress = true; |
michael@0 | 443 | this._update(aPoints, 'pointerdown', |
michael@0 | 444 | aTimeStamp - this.startTime < MAX_MULTITOUCH); |
michael@0 | 445 | }, |
michael@0 | 446 | |
michael@0 | 447 | /** |
michael@0 | 448 | * Handle the pointer move event. |
michael@0 | 449 | * @param {Array} aPoints A new pointer move points. |
michael@0 | 450 | */ |
michael@0 | 451 | pointermove: function Gesture_pointermove(aPoints) { |
michael@0 | 452 | this._update(aPoints, 'pointermove'); |
michael@0 | 453 | }, |
michael@0 | 454 | |
michael@0 | 455 | /** |
michael@0 | 456 | * Handle the pointer up event. |
michael@0 | 457 | * @param {Array} aPoints A new pointer up points. |
michael@0 | 458 | */ |
michael@0 | 459 | pointerup: function Gesture_pointerup(aPoints) { |
michael@0 | 460 | let complete = this._update(aPoints, 'pointerup', false, true); |
michael@0 | 461 | if (complete) { |
michael@0 | 462 | this._deferred.resolve(); |
michael@0 | 463 | } |
michael@0 | 464 | }, |
michael@0 | 465 | |
michael@0 | 466 | /** |
michael@0 | 467 | * A subsequent gesture constructor to resolve the current one to. E.g. |
michael@0 | 468 | * tap->doubletap, dwell->dwellend, etc. |
michael@0 | 469 | * @type {Function} |
michael@0 | 470 | */ |
michael@0 | 471 | resolveTo: null, |
michael@0 | 472 | |
michael@0 | 473 | /** |
michael@0 | 474 | * A unique id for the gesture. Composed of the type + timeStamp. |
michael@0 | 475 | */ |
michael@0 | 476 | get id() { |
michael@0 | 477 | delete this._id; |
michael@0 | 478 | this._id = this.type + this.startTime; |
michael@0 | 479 | return this._id; |
michael@0 | 480 | }, |
michael@0 | 481 | |
michael@0 | 482 | /** |
michael@0 | 483 | * A gesture promise resolve callback. Compile and emit the gesture. |
michael@0 | 484 | * @return {Object} Returns a structure to the gesture handler that looks like |
michael@0 | 485 | * this: { |
michael@0 | 486 | * id: current gesture id, |
michael@0 | 487 | * gestureType: an optional subsequent gesture constructor. |
michael@0 | 488 | * } |
michael@0 | 489 | */ |
michael@0 | 490 | _handleResolve: function Gesture__handleResolve() { |
michael@0 | 491 | if (this.isComplete) { |
michael@0 | 492 | return; |
michael@0 | 493 | } |
michael@0 | 494 | Logger.debug('Resolving', this.id, 'gesture.'); |
michael@0 | 495 | this.isComplete = true; |
michael@0 | 496 | let detail = this.compile(); |
michael@0 | 497 | if (detail) { |
michael@0 | 498 | this._emit(detail); |
michael@0 | 499 | } |
michael@0 | 500 | return { |
michael@0 | 501 | id: this.id, |
michael@0 | 502 | gestureType: this.resolveTo |
michael@0 | 503 | }; |
michael@0 | 504 | }, |
michael@0 | 505 | |
michael@0 | 506 | /** |
michael@0 | 507 | * A gesture promise reject callback. |
michael@0 | 508 | * @return {Object} Returns a structure to the gesture handler that looks like |
michael@0 | 509 | * this: { |
michael@0 | 510 | * id: current gesture id, |
michael@0 | 511 | * gestureType: an optional subsequent gesture constructor. |
michael@0 | 512 | * } |
michael@0 | 513 | */ |
michael@0 | 514 | _handleReject: function Gesture__handleReject(aRejectTo) { |
michael@0 | 515 | if (this.isComplete) { |
michael@0 | 516 | return; |
michael@0 | 517 | } |
michael@0 | 518 | Logger.debug('Rejecting', this.id, 'gesture.'); |
michael@0 | 519 | this.isComplete = true; |
michael@0 | 520 | return { |
michael@0 | 521 | id: this.id, |
michael@0 | 522 | gestureType: aRejectTo |
michael@0 | 523 | }; |
michael@0 | 524 | }, |
michael@0 | 525 | |
michael@0 | 526 | /** |
michael@0 | 527 | * A default compilation function used to build the mozAccessFuGesture event |
michael@0 | 528 | * detail. The detail always includes the type and the touches associated |
michael@0 | 529 | * with the gesture. |
michael@0 | 530 | * @return {Object} Gesture event detail. |
michael@0 | 531 | */ |
michael@0 | 532 | compile: function Gesture_compile() { |
michael@0 | 533 | return compileDetail(this.type, this.points); |
michael@0 | 534 | } |
michael@0 | 535 | }; |
michael@0 | 536 | |
michael@0 | 537 | /** |
michael@0 | 538 | * A mixin for an explore related object. |
michael@0 | 539 | */ |
michael@0 | 540 | function ExploreGesture() { |
michael@0 | 541 | this.compile = () => { |
michael@0 | 542 | // Unlike most of other gestures explore based gestures compile using the |
michael@0 | 543 | // current point position and not the start one. |
michael@0 | 544 | return compileDetail(this.type, this.points, {x: 'x', y: 'y'}); |
michael@0 | 545 | }; |
michael@0 | 546 | } |
michael@0 | 547 | |
michael@0 | 548 | /** |
michael@0 | 549 | * Check the in progress gesture for completion. |
michael@0 | 550 | */ |
michael@0 | 551 | function checkProgressGesture(aGesture) { |
michael@0 | 552 | aGesture._inProgress = true; |
michael@0 | 553 | if (aGesture.lastEvent === 'pointerup') { |
michael@0 | 554 | if (aGesture.test) { |
michael@0 | 555 | aGesture.test(true); |
michael@0 | 556 | } |
michael@0 | 557 | aGesture._deferred.resolve(); |
michael@0 | 558 | } |
michael@0 | 559 | } |
michael@0 | 560 | |
michael@0 | 561 | /** |
michael@0 | 562 | * A common travel gesture. When the travel gesture is created, all subsequent |
michael@0 | 563 | * pointer events' points are tested for their total distance traveled. If that |
michael@0 | 564 | * distance exceeds the _threshold distance, the gesture will be rejected to a |
michael@0 | 565 | * _travelTo gesture. |
michael@0 | 566 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 567 | * the gesture resolution sequence. |
michael@0 | 568 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 569 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 570 | * @param {Function} aTravelTo A contructor for the gesture to reject to when |
michael@0 | 571 | * travelling (default: Explore). |
michael@0 | 572 | * @param {Number} aThreshold Travel threshold (default: |
michael@0 | 573 | * GestureSettings.travelThreshold). |
michael@0 | 574 | */ |
michael@0 | 575 | function TravelGesture(aTimeStamp, aPoints, aLastEvent, aTravelTo = Explore, aThreshold = GestureSettings.travelThreshold) { |
michael@0 | 576 | Gesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 577 | this._travelTo = aTravelTo; |
michael@0 | 578 | this._threshold = aThreshold; |
michael@0 | 579 | } |
michael@0 | 580 | |
michael@0 | 581 | TravelGesture.prototype = Object.create(Gesture.prototype); |
michael@0 | 582 | |
michael@0 | 583 | /** |
michael@0 | 584 | * Test the gesture points for travel. The gesture will be rejected to |
michael@0 | 585 | * this._travelTo gesture iff at least one point crosses this._threshold. |
michael@0 | 586 | */ |
michael@0 | 587 | TravelGesture.prototype.test = function TravelGesture_test() { |
michael@0 | 588 | for (let identifier in this.points) { |
michael@0 | 589 | let point = this.points[identifier]; |
michael@0 | 590 | if (point.totalDistanceTraveled / Utils.dpi > this._threshold) { |
michael@0 | 591 | this._deferred.reject(this._travelTo); |
michael@0 | 592 | return; |
michael@0 | 593 | } |
michael@0 | 594 | } |
michael@0 | 595 | }; |
michael@0 | 596 | |
michael@0 | 597 | /** |
michael@0 | 598 | * DwellEnd gesture. |
michael@0 | 599 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 600 | * the gesture resolution sequence. |
michael@0 | 601 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 602 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 603 | */ |
michael@0 | 604 | function DwellEnd(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 605 | this._inProgress = true; |
michael@0 | 606 | // If the pointer travels, reject to Explore. |
michael@0 | 607 | TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 608 | checkProgressGesture(this); |
michael@0 | 609 | } |
michael@0 | 610 | |
michael@0 | 611 | DwellEnd.prototype = Object.create(TravelGesture.prototype); |
michael@0 | 612 | DwellEnd.prototype.type = 'dwellend'; |
michael@0 | 613 | |
michael@0 | 614 | /** |
michael@0 | 615 | * TapHoldEnd gesture. This gesture can be represented as the following diagram: |
michael@0 | 616 | * pointerdown-pointerup-pointerdown-*wait*-pointerup. |
michael@0 | 617 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 618 | * the gesture resolution sequence. |
michael@0 | 619 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 620 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 621 | */ |
michael@0 | 622 | function TapHoldEnd(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 623 | this._inProgress = true; |
michael@0 | 624 | // If the pointer travels, reject to Explore. |
michael@0 | 625 | TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 626 | checkProgressGesture(this); |
michael@0 | 627 | } |
michael@0 | 628 | |
michael@0 | 629 | TapHoldEnd.prototype = Object.create(TravelGesture.prototype); |
michael@0 | 630 | TapHoldEnd.prototype.type = 'tapholdend'; |
michael@0 | 631 | |
michael@0 | 632 | /** |
michael@0 | 633 | * DoubleTapHoldEnd gesture. This gesture can be represented as the following |
michael@0 | 634 | * diagram: |
michael@0 | 635 | * pointerdown-pointerup-pointerdown-pointerup-pointerdown-*wait*-pointerup. |
michael@0 | 636 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 637 | * the gesture resolution sequence. |
michael@0 | 638 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 639 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 640 | */ |
michael@0 | 641 | function DoubleTapHoldEnd(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 642 | this._inProgress = true; |
michael@0 | 643 | // If the pointer travels, reject to Explore. |
michael@0 | 644 | TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 645 | checkProgressGesture(this); |
michael@0 | 646 | } |
michael@0 | 647 | |
michael@0 | 648 | DoubleTapHoldEnd.prototype = Object.create(TravelGesture.prototype); |
michael@0 | 649 | DoubleTapHoldEnd.prototype.type = 'doubletapholdend'; |
michael@0 | 650 | |
michael@0 | 651 | /** |
michael@0 | 652 | * A common tap gesture object. |
michael@0 | 653 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 654 | * the gesture resolution sequence. |
michael@0 | 655 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 656 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 657 | * @param {Function} aRejectTo A constructor for the next gesture to reject to |
michael@0 | 658 | * in case no pointermove or pointerup happens within the |
michael@0 | 659 | * GestureSettings.dwellThreshold. |
michael@0 | 660 | * @param {Function} aTravelTo An optional constuctor for the next gesture to |
michael@0 | 661 | * reject to in case the the TravelGesture test fails. |
michael@0 | 662 | */ |
michael@0 | 663 | function TapGesture(aTimeStamp, aPoints, aLastEvent, aRejectTo, aTravelTo) { |
michael@0 | 664 | this._rejectToOnWait = aRejectTo; |
michael@0 | 665 | // If the pointer travels, reject to aTravelTo. |
michael@0 | 666 | TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent, aTravelTo, |
michael@0 | 667 | TAP_MAX_RADIUS); |
michael@0 | 668 | } |
michael@0 | 669 | |
michael@0 | 670 | TapGesture.prototype = Object.create(TravelGesture.prototype); |
michael@0 | 671 | TapGesture.prototype._getDelay = function TapGesture__getDelay() { |
michael@0 | 672 | // If, for TapGesture, no pointermove or pointerup happens within the |
michael@0 | 673 | // GestureSettings.dwellThreshold, reject. |
michael@0 | 674 | // Note: the original pointer event's timeStamp is irrelevant here. |
michael@0 | 675 | return GestureSettings.dwellThreshold; |
michael@0 | 676 | }; |
michael@0 | 677 | |
michael@0 | 678 | /** |
michael@0 | 679 | * Tap gesture. |
michael@0 | 680 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 681 | * the gesture resolution sequence. |
michael@0 | 682 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 683 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 684 | */ |
michael@0 | 685 | function Tap(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 686 | // If the pointer travels, reject to Swipe. |
michael@0 | 687 | TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, Dwell, Swipe); |
michael@0 | 688 | } |
michael@0 | 689 | |
michael@0 | 690 | Tap.prototype = Object.create(TapGesture.prototype); |
michael@0 | 691 | Tap.prototype.type = 'tap'; |
michael@0 | 692 | Tap.prototype.resolveTo = DoubleTap; |
michael@0 | 693 | |
michael@0 | 694 | /** |
michael@0 | 695 | * Tap (multi) gesture on Android. |
michael@0 | 696 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 697 | * the gesture resolution sequence. |
michael@0 | 698 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 699 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 700 | */ |
michael@0 | 701 | function AndroidTap(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 702 | // If the pointer travels, reject to Swipe. On dwell threshold reject to |
michael@0 | 703 | // TapHold. |
michael@0 | 704 | TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, Swipe); |
michael@0 | 705 | } |
michael@0 | 706 | AndroidTap.prototype = Object.create(TapGesture.prototype); |
michael@0 | 707 | // Android double taps are translated to single taps. |
michael@0 | 708 | AndroidTap.prototype.type = 'doubletap'; |
michael@0 | 709 | AndroidTap.prototype.resolveTo = TripleTap; |
michael@0 | 710 | |
michael@0 | 711 | /** |
michael@0 | 712 | * Clear the pointerup handler timer in case of the 3 pointer swipe. |
michael@0 | 713 | */ |
michael@0 | 714 | AndroidTap.prototype.clearThreeFingerSwipeTimer = function AndroidTap_clearThreeFingerSwipeTimer() { |
michael@0 | 715 | clearTimeout(this._threeFingerSwipeTimer); |
michael@0 | 716 | delete this._threeFingerSwipeTimer; |
michael@0 | 717 | }; |
michael@0 | 718 | |
michael@0 | 719 | AndroidTap.prototype.pointerdown = function AndroidTap_pointerdown(aPoints, aTimeStamp) { |
michael@0 | 720 | this.clearThreeFingerSwipeTimer(); |
michael@0 | 721 | TapGesture.prototype.pointerdown.call(this, aPoints, aTimeStamp); |
michael@0 | 722 | }; |
michael@0 | 723 | |
michael@0 | 724 | AndroidTap.prototype.pointermove = function AndroidTap_pointermove(aPoints) { |
michael@0 | 725 | this.clearThreeFingerSwipeTimer(); |
michael@0 | 726 | this._moved = true; |
michael@0 | 727 | TapGesture.prototype.pointermove.call(this, aPoints); |
michael@0 | 728 | }; |
michael@0 | 729 | |
michael@0 | 730 | AndroidTap.prototype.pointerup = function AndroidTap_pointerup(aPoints) { |
michael@0 | 731 | if (this._moved) { |
michael@0 | 732 | // If there was a pointer move - handle the real gesture. |
michael@0 | 733 | TapGesture.prototype.pointerup.call(this, aPoints); |
michael@0 | 734 | } else { |
michael@0 | 735 | // Primptively delay the multi pointer gesture resolution, because Android |
michael@0 | 736 | // sometimes fires a pointerdown/poitnerup sequence before the real events. |
michael@0 | 737 | this._threeFingerSwipeTimer = setTimeout(() => { |
michael@0 | 738 | delete this._threeFingerSwipeTimer; |
michael@0 | 739 | TapGesture.prototype.pointerup.call(this, aPoints); |
michael@0 | 740 | }, ANDROID_TRIPLE_SWIPE_DELAY); |
michael@0 | 741 | } |
michael@0 | 742 | }; |
michael@0 | 743 | |
michael@0 | 744 | /** |
michael@0 | 745 | * Reject an android tap gesture. |
michael@0 | 746 | * @param {?Function} aRejectTo An optional next gesture constructor. |
michael@0 | 747 | * @return {Object} structure that looks like { |
michael@0 | 748 | * id: gesture_id, // Current AndroidTap gesture id. |
michael@0 | 749 | * gestureType: next_gesture // Optional |
michael@0 | 750 | * } |
michael@0 | 751 | */ |
michael@0 | 752 | AndroidTap.prototype._handleReject = function AndroidTap__handleReject(aRejectTo) { |
michael@0 | 753 | let keys = Object.keys(this.points); |
michael@0 | 754 | if (aRejectTo === Swipe && keys.length === 1) { |
michael@0 | 755 | let key = keys[0]; |
michael@0 | 756 | let point = this.points[key]; |
michael@0 | 757 | // Two finger swipe is translated into single swipe. |
michael@0 | 758 | this.points[key + '-copy'] = point; |
michael@0 | 759 | } |
michael@0 | 760 | return TapGesture.prototype._handleReject.call(this, aRejectTo); |
michael@0 | 761 | }; |
michael@0 | 762 | |
michael@0 | 763 | /** |
michael@0 | 764 | * Double Tap gesture. |
michael@0 | 765 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 766 | * the gesture resolution sequence. |
michael@0 | 767 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 768 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 769 | */ |
michael@0 | 770 | function DoubleTap(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 771 | TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold); |
michael@0 | 772 | } |
michael@0 | 773 | |
michael@0 | 774 | DoubleTap.prototype = Object.create(TapGesture.prototype); |
michael@0 | 775 | DoubleTap.prototype.type = 'doubletap'; |
michael@0 | 776 | DoubleTap.prototype.resolveTo = TripleTap; |
michael@0 | 777 | |
michael@0 | 778 | /** |
michael@0 | 779 | * Triple Tap gesture. |
michael@0 | 780 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 781 | * the gesture resolution sequence. |
michael@0 | 782 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 783 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 784 | */ |
michael@0 | 785 | function TripleTap(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 786 | TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, DoubleTapHold); |
michael@0 | 787 | } |
michael@0 | 788 | |
michael@0 | 789 | TripleTap.prototype = Object.create(TapGesture.prototype); |
michael@0 | 790 | TripleTap.prototype.type = 'tripletap'; |
michael@0 | 791 | |
michael@0 | 792 | /** |
michael@0 | 793 | * Common base object for gestures that are created as resolved. |
michael@0 | 794 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 795 | * the gesture resolution sequence. |
michael@0 | 796 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 797 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 798 | */ |
michael@0 | 799 | function ResolvedGesture(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 800 | Gesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 801 | // Resolve the guesture right away. |
michael@0 | 802 | this._deferred.resolve(); |
michael@0 | 803 | } |
michael@0 | 804 | |
michael@0 | 805 | ResolvedGesture.prototype = Object.create(Gesture.prototype); |
michael@0 | 806 | |
michael@0 | 807 | /** |
michael@0 | 808 | * Dwell gesture |
michael@0 | 809 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 810 | * the gesture resolution sequence. |
michael@0 | 811 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 812 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 813 | */ |
michael@0 | 814 | function Dwell(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 815 | ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 816 | } |
michael@0 | 817 | |
michael@0 | 818 | Dwell.prototype = Object.create(ResolvedGesture.prototype); |
michael@0 | 819 | Dwell.prototype.type = 'dwell'; |
michael@0 | 820 | Dwell.prototype.resolveTo = DwellEnd; |
michael@0 | 821 | |
michael@0 | 822 | /** |
michael@0 | 823 | * TapHold gesture |
michael@0 | 824 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 825 | * the gesture resolution sequence. |
michael@0 | 826 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 827 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 828 | */ |
michael@0 | 829 | function TapHold(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 830 | ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 831 | } |
michael@0 | 832 | |
michael@0 | 833 | TapHold.prototype = Object.create(ResolvedGesture.prototype); |
michael@0 | 834 | TapHold.prototype.type = 'taphold'; |
michael@0 | 835 | TapHold.prototype.resolveTo = TapHoldEnd; |
michael@0 | 836 | |
michael@0 | 837 | /** |
michael@0 | 838 | * DoubleTapHold gesture |
michael@0 | 839 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 840 | * the gesture resolution sequence. |
michael@0 | 841 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 842 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 843 | */ |
michael@0 | 844 | function DoubleTapHold(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 845 | ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 846 | } |
michael@0 | 847 | |
michael@0 | 848 | DoubleTapHold.prototype = Object.create(ResolvedGesture.prototype); |
michael@0 | 849 | DoubleTapHold.prototype.type = 'doubletaphold'; |
michael@0 | 850 | DoubleTapHold.prototype.resolveTo = DoubleTapHoldEnd; |
michael@0 | 851 | |
michael@0 | 852 | /** |
michael@0 | 853 | * Explore gesture |
michael@0 | 854 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 855 | * the gesture resolution sequence. |
michael@0 | 856 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 857 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 858 | */ |
michael@0 | 859 | function Explore(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 860 | ExploreGesture.call(this); |
michael@0 | 861 | ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 862 | } |
michael@0 | 863 | |
michael@0 | 864 | Explore.prototype = Object.create(ResolvedGesture.prototype); |
michael@0 | 865 | Explore.prototype.type = 'explore'; |
michael@0 | 866 | Explore.prototype.resolveTo = ExploreEnd; |
michael@0 | 867 | |
michael@0 | 868 | /** |
michael@0 | 869 | * ExploreEnd gesture. |
michael@0 | 870 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 871 | * the gesture resolution sequence. |
michael@0 | 872 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 873 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 874 | */ |
michael@0 | 875 | function ExploreEnd(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 876 | this._inProgress = true; |
michael@0 | 877 | ExploreGesture.call(this); |
michael@0 | 878 | // If the pointer travels, reject to Explore. |
michael@0 | 879 | TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 880 | checkProgressGesture(this); |
michael@0 | 881 | } |
michael@0 | 882 | |
michael@0 | 883 | ExploreEnd.prototype = Object.create(TravelGesture.prototype); |
michael@0 | 884 | ExploreEnd.prototype.type = 'exploreend'; |
michael@0 | 885 | |
michael@0 | 886 | /** |
michael@0 | 887 | * Swipe gesture. |
michael@0 | 888 | * @param {Number} aTimeStamp An original pointer event's timeStamp that started |
michael@0 | 889 | * the gesture resolution sequence. |
michael@0 | 890 | * @param {Object} aPoints An existing set of points (from previous events). |
michael@0 | 891 | * @param {?String} aLastEvent Last pointer event type. |
michael@0 | 892 | */ |
michael@0 | 893 | function Swipe(aTimeStamp, aPoints, aLastEvent) { |
michael@0 | 894 | this._inProgress = true; |
michael@0 | 895 | this._rejectToOnWait = Explore; |
michael@0 | 896 | Gesture.call(this, aTimeStamp, aPoints, aLastEvent); |
michael@0 | 897 | checkProgressGesture(this); |
michael@0 | 898 | } |
michael@0 | 899 | |
michael@0 | 900 | Swipe.prototype = Object.create(Gesture.prototype); |
michael@0 | 901 | Swipe.prototype.type = 'swipe'; |
michael@0 | 902 | Swipe.prototype._getDelay = function Swipe__getDelay(aTimeStamp) { |
michael@0 | 903 | // Swipe should be completed within the GestureSettings.swipeMaxDuration from |
michael@0 | 904 | // the initial pointer down event. |
michael@0 | 905 | return GestureSettings.swipeMaxDuration - this.startTime + aTimeStamp; |
michael@0 | 906 | }; |
michael@0 | 907 | |
michael@0 | 908 | /** |
michael@0 | 909 | * Determine wither the gesture was Swipe or Explore. |
michael@0 | 910 | * @param {Booler} aComplete A flag that indicates whether the gesture is and |
michael@0 | 911 | * will be complete after the test. |
michael@0 | 912 | */ |
michael@0 | 913 | Swipe.prototype.test = function Swipe_test(aComplete) { |
michael@0 | 914 | if (!aComplete) { |
michael@0 | 915 | // No need to test if the gesture is not completing or can't be complete. |
michael@0 | 916 | return; |
michael@0 | 917 | } |
michael@0 | 918 | let reject = true; |
michael@0 | 919 | // If at least one point travelled for more than SWIPE_MIN_DISTANCE and it was |
michael@0 | 920 | // direct enough, consider it a Swipe. |
michael@0 | 921 | for (let identifier in this.points) { |
michael@0 | 922 | let point = this.points[identifier]; |
michael@0 | 923 | let directDistance = point.directDistanceTraveled; |
michael@0 | 924 | if (directDistance / Utils.dpi >= SWIPE_MIN_DISTANCE || |
michael@0 | 925 | directDistance * DIRECTNESS_COEFF >= point.totalDistanceTraveled) { |
michael@0 | 926 | reject = false; |
michael@0 | 927 | } |
michael@0 | 928 | } |
michael@0 | 929 | if (reject) { |
michael@0 | 930 | this._deferred.reject(Explore); |
michael@0 | 931 | } |
michael@0 | 932 | }; |
michael@0 | 933 | |
michael@0 | 934 | /** |
michael@0 | 935 | * Compile a swipe related mozAccessFuGesture event detail. |
michael@0 | 936 | * @return {Object} A mozAccessFuGesture detail object. |
michael@0 | 937 | */ |
michael@0 | 938 | Swipe.prototype.compile = function Swipe_compile() { |
michael@0 | 939 | let type = this.type; |
michael@0 | 940 | let detail = compileDetail(type, this.points, |
michael@0 | 941 | {x1: 'startX', y1: 'startY', x2: 'x', y2: 'y'}); |
michael@0 | 942 | let deltaX = detail.deltaX; |
michael@0 | 943 | let deltaY = detail.deltaY; |
michael@0 | 944 | if (Math.abs(deltaX) > Math.abs(deltaY)) { |
michael@0 | 945 | // Horizontal swipe. |
michael@0 | 946 | detail.type = type + (deltaX > 0 ? 'right' : 'left'); |
michael@0 | 947 | } else { |
michael@0 | 948 | // Vertival swipe. |
michael@0 | 949 | detail.type = type + (deltaY > 0 ? 'down' : 'up'); |
michael@0 | 950 | } |
michael@0 | 951 | return detail; |
michael@0 | 952 | }; |