1010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis// Use of this source code is governed by a BSD-style license that can be 3010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis// found in the LICENSE file. 4010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 5010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis/** 6010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @fileoverview Touch Handler. Class that handles all touch events and 7010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * uses them to interpret higher level gestures and behaviors. TouchEvent is a 8010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * built in mobile safari type: 9010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * http://developer.apple.com/safari/library/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html. 10010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * This class is intended to work with all webkit browsers, tested on Chrome and 11010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * iOS. 12010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * 13010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The following types of gestures are currently supported. See the definition 14010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * of TouchHandler.EventType for details. 15010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * 16010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Single Touch: 17010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * This provides simple single-touch events. Any secondary touch is 18010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * ignored. 19010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * 20010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Drag: 21010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * A single touch followed by some movement. This behavior will handle all 22010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * of the required events and report the properties of the drag to you 23010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * while the touch is happening and at the end of the drag sequence. This 24010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * behavior will NOT perform the actual dragging (redrawing the element) 25010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * for you, this responsibility is left to the client code. 26010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * 27010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Long press: 28010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * When your element is touched and held without any drag occuring, the 29010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * LONG_PRESS event will fire. 30010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 31010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 32010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis// Use an anonymous function to enable strict mode just for this file (which 33010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis// will be concatenated with other files when embedded in Chrome) 34010583560e0e6db74fe50b840bce46ba6537de63Jamie Genniscr.define('cr.ui', function() { 35010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 'use strict'; 36010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 37010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 38010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * A TouchHandler attaches to an Element, listents for low-level touch (or 39010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * mouse) events and dispatching higher-level events on the element. 40010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {!Element} element The element to listen on and fire events 41010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * for. 42010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @constructor 43010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 44010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis function TouchHandler(element) { 45010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 46010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {!Element} 47010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 48010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 49010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.element_ = element; 50010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 51010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 52010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The absolute sum of all touch y deltas. 53010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 54010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 55010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 56010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.totalMoveY_ = 0; 57010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 58010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 59010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The absolute sum of all touch x deltas. 60010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 61010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 62010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 63010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.totalMoveX_ = 0; 64010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 65010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 66010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * An array of tuples where the first item is the horizontal component of a 67010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * recent relevant touch and the second item is the touch's time stamp. Old 68010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * touches are removed based on the max tracking time and when direction 69010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * changes. 70010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {!Array.<number>} 71010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 72010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 73010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.recentTouchesX_ = []; 74010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 75010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 76010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * An array of tuples where the first item is the vertical component of a 77010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * recent relevant touch and the second item is the touch's time stamp. Old 78010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * touches are removed based on the max tracking time and when direction 79010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * changes. 80010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {!Array.<number>} 81010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 82010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 83010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.recentTouchesY_ = []; 84010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 85010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 86010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Used to keep track of all events we subscribe to so we can easily clean 87010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * up 88010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {EventTracker} 89010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 90010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 91010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_ = new EventTracker(); 92010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 93010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 94010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 95010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 96010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * DOM Events that may be fired by the TouchHandler at the element 97010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 98010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.EventType = { 99010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Fired whenever the element is touched as the only touch to the device. 100010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // enableDrag defaults to false, set to true to permit dragging. 101010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TOUCH_START: 'touchHandler:touch_start', 102010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 103010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Fired when an element is held for a period of time. Prevents dragging 104010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // from occuring (even if enableDrag was set to true). 105010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis LONG_PRESS: 'touchHandler:long_press', 106010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 107010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // If enableDrag was set to true at TOUCH_START, DRAG_START will fire when 108010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // the touch first moves sufficient distance. enableDrag is set to true but 109010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // can be reset to false to cancel the drag. 110010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis DRAG_START: 'touchHandler:drag_start', 111010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 112010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // If enableDrag was true after DRAG_START, DRAG_MOVE will fire whenever the 113010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // touch is moved. 114010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis DRAG_MOVE: 'touchHandler:drag_move', 115010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 116010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Fired just before TOUCH_END when a drag is released. Correlates 1:1 with 117010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // a DRAG_START. 118010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis DRAG_END: 'touchHandler:drag_end', 119010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 120010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Fired whenever a touch that is being tracked has been released. 121010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Correlates 1:1 with a TOUCH_START. 122010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TOUCH_END: 'touchHandler:touch_end', 123010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 124010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Fired whenever the element is tapped in a short time and no dragging is 125010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // detected. 126010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TAP: 'touchHandler:tap' 127010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }; 128010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 129010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 130010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 131010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The type of event sent by TouchHandler 132010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @constructor 133010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {string} type The type of event (one of cr.ui.Grabber.EventType). 134010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {boolean} bubbles Whether or not the event should bubble. 135010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {number} clientX The X location of the touch. 136010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {number} clientY The Y location of the touch. 137010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {!Element} touchedElement The element at the current location of the 138010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * touch. 139010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 140010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.Event = function(type, bubbles, clientX, clientY, 141010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis touchedElement) { 142010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var event = document.createEvent('Event'); 143010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis event.initEvent(type, bubbles, true); 144010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis event.__proto__ = TouchHandler.Event.prototype; 145010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 146010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 147010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The X location of the touch affected 148010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 149010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 150010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis event.clientX = clientX; 151010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 152010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 153010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The Y location of the touch affected 154010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 155010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 156010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis event.clientY = clientY; 157010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 158010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 159010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The element at the current location of the touch. 160010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {!Element} 161010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 162010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis event.touchedElement = touchedElement; 163010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 164010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return event; 165010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }; 166010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 167010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.Event.prototype = { 168010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis __proto__: Event.prototype, 169010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 170010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 171010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * For TOUCH_START and DRAG START events, set to true to enable dragging or 172010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * false to disable dragging. 173010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {boolean|undefined} 174010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 175010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis enableDrag: undefined, 176010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 177010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 178010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * For DRAG events, provides the horizontal component of the 179010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * drag delta. Drag delta is defined as the delta of the start touch 180010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * position and the current drag position. 181010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 182010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 183010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis dragDeltaX: undefined, 184010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 185010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 186010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * For DRAG events, provides the vertical component of the 187010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * drag delta. 188010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 189010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 190010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis dragDeltaY: undefined 191010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }; 192010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 193010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 194010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Minimum movement of touch required to be considered a drag. 195010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 196010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 197010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 198010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.MIN_TRACKING_FOR_DRAG_ = 8; 199010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 200010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 201010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 202010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The maximum number of ms to track a touch event. After an event is older 203010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * than this value, it will be ignored in velocity calculations. 204010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 205010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 206010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 207010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.MAX_TRACKING_TIME_ = 250; 208010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 209010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 210010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 211010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The maximum number of touches to track. 212010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 213010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 214010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 215010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.MAX_TRACKING_TOUCHES_ = 5; 216010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 217010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 218010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 219010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The maximum velocity to return, in pixels per millisecond, that is used 220010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * to guard against errors in calculating end velocity of a drag. This is a 221010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * very fast drag velocity. 222010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 223010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 224010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 225010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.MAXIMUM_VELOCITY_ = 5; 226010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 227010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 228010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 229010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The velocity to return, in pixel per millisecond, when the time stamps on 230010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * the events are erroneous. The browser can return bad time stamps if the 231010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * thread is blocked for the duration of the drag. This is a low velocity to 232010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * prevent the content from moving quickly after a slow drag. It is less 233010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * jarring if the content moves slowly after a fast drag. 234010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 235010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 236010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 237010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ = 1; 238010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 239010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 240010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The time, in milliseconds, that a touch must be held to be considered 241010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * 'long'. 242010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number} 243010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 244010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 245010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.TIME_FOR_LONG_PRESS_ = 500; 246010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 247010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.prototype = { 248010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 249010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * If defined, the identifer of the single touch that is active. Note that 250010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * 0 is a valid touch identifier - it should not be treated equivalently to 251010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * undefined. 252010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 253010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 254010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 255010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis activeTouch_: undefined, 256010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 257010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 258010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {boolean|undefined} 259010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 260010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 261010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis tracking_: undefined, 262010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 263010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 264010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 265010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 266010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 267010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis startTouchX_: undefined, 268010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 269010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 270010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 271010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 272010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 273010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis startTouchY_: undefined, 274010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 275010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 276010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 277010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 278010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 279010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis endTouchX_: undefined, 280010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 281010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 282010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 283010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 284010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 285010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis endTouchY_: undefined, 286010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 287010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 288010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Time of the touchstart event. 289010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 290010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 291010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 292010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis startTime_: undefined, 293010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 294010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 295010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * The time of the touchend event. 296010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 297010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 298010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 299010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis endTime_: undefined, 300010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 301010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 302010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 303010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 304010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 305010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis lastTouchX_: undefined, 306010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 307010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 308010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 309010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 310010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 311010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis lastTouchY_: undefined, 312010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 313010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 314010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 315010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 316010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 317010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis lastMoveX_: undefined, 318010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 319010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 320010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 321010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 322010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 323010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis lastMoveY_: undefined, 324010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 325010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 326010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {number|undefined} 327010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 328010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 329010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis longPressTimeout_: undefined, 330010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 331010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 332010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * If defined and true, the next click event should be swallowed 333010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @type {boolean|undefined} 334010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 335010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 336010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis swallowNextClick_: undefined, 337010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 338010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 339010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Start listenting for events. 340010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {boolean=} opt_capture True if the TouchHandler should listen to 341010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * during the capture phase. 342010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {boolean=} opt_mouse True if the TouchHandler should generate 343010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * events for mouse input (in addition to touch input). 344010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 345010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis enable: function(opt_capture, opt_mouse) { 346010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var capture = !!opt_capture; 347010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 348010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Just listen to start events for now. When a touch is occuring we'll 349010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // want to be subscribed to move and end events on the document, but we 350010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // don't want to incur the cost of lots of no-op handlers on the document. 351010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.add(this.element_, 'touchstart', this.onStart_.bind(this), 352010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis capture); 353010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (opt_mouse) { 354010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.add(this.element_, 'mousedown', 355010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.mouseToTouchCallback_(this.onStart_.bind(this)), 356010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis capture); 357010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 358010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 359010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // If the element is long-pressed, we may need to swallow a click 360010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.add(this.element_, 'click', this.onClick_.bind(this), true); 361010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 362010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 363010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 364010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Stop listening to all events. 365010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 366010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis disable: function() { 367010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.stopTouching_(); 368010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.removeAll(); 369010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 370010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 371010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 372010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Wraps a callback with translations of mouse events to touch events. 373010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * NOTE: These types really should be function(Event) but then we couldn't 374010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * use this with bind (which operates on any type of function). Doesn't 375010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * JSDoc support some sort of polymorphic types? 376010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {Function} callback The event callback. 377010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @return {Function} The wrapping callback. 378010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 379010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 380010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis mouseToTouchCallback_: function(callback) { 381010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return function(e) { 382010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Note that there may be synthesizes mouse events caused by touch 383010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // events (a mouseDown after a touch-click). We leave it up to the 384010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // client to worry about this if it matters to them (typically a short 385010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // mouseDown/mouseUp without a click is no big problem and it's not 386010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // obvious how we identify such synthesized events in a general way). 387010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var touch = { 388010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // any fixed value will do for the identifier - there will only 389010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // ever be a single active 'touch' when using the mouse. 390010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis identifier: 0, 391010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis clientX: e.clientX, 392010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis clientY: e.clientY, 393010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis target: e.target 394010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }; 395010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis e.touches = []; 396010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis e.targetTouches = []; 397010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis e.changedTouches = [touch]; 398010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (e.type != 'mouseup') { 399010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis e.touches[0] = touch; 400010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis e.targetTouches[0] = touch; 401010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 402010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis callback(e); 403010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }; 404010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 405010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 406010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 407010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Begin tracking the touchable element, it is eligible for dragging. 408010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 409010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 410010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis beginTracking_: function() { 411010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.tracking_ = true; 412010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 413010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 414010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 415010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Stop tracking the touchable element, it is no longer dragging. 416010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 417010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 418010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis endTracking_: function() { 419010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.tracking_ = false; 420010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.dragging_ = false; 421010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.totalMoveY_ = 0; 422010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.totalMoveX_ = 0; 423010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 424010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 425010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 426010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Reset the touchable element as if we never saw the touchStart 427010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Doesn't dispatch any end events - be careful of existing listeners. 428010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 429010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis cancelTouch: function() { 430010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.stopTouching_(); 431010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.endTracking_(); 432010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // If clients needed to be aware of this, we could fire a cancel event 433010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // here. 434010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 435010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 436010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 437010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Record that touching has stopped 438010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 439010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 440010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis stopTouching_: function() { 441010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Mark as no longer being touched 442010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.activeTouch_ = undefined; 443010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 444010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // If we're waiting for a long press, stop 445010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis window.clearTimeout(this.longPressTimeout_); 446010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 447010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Stop listening for move/end events until there's another touch. 448010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // We don't want to leave handlers piled up on the document. 449010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Note that there's no harm in removing handlers that weren't added, so 450010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // rather than track whether we're using mouse or touch we do both. 451010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.remove(document, 'touchmove'); 452010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.remove(document, 'touchend'); 453010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.remove(document, 'touchcancel'); 454010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.remove(document, 'mousemove'); 455010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.remove(document, 'mouseup'); 456010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 457010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 458010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 459010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Touch start handler. 460010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {!TouchEvent} e The touchstart event. 461010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 462010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 463010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis onStart_: function(e) { 464010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Only process single touches. If there is already a touch happening, or 465010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // two simultaneous touches then just ignore them. 466010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (e.touches.length > 1) 467010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Note that we could cancel an active touch here. That would make 468010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // simultaneous touch behave similar to near-simultaneous. However, if 469010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // the user is dragging something, an accidental second touch could be 470010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // quite disruptive if it cancelled their drag. Better to just ignore 471010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // it. 472010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return; 473010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 474010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // It's still possible there could be an active "touch" if the user is 475010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // simultaneously using a mouse and a touch input. 476010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (this.activeTouch_ !== undefined) 477010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return; 478010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 479010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var touch = e.targetTouches[0]; 480010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.activeTouch_ = touch.identifier; 481010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 482010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // We've just started touching so shouldn't swallow any upcoming click 483010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (this.swallowNextClick_) 484010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.swallowNextClick_ = false; 485010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 486010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.disableTap_ = false; 487010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 488010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Sign up for end/cancel notifications for this touch. 489010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Note that we do this on the document so that even if the user drags 490010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // their finger off the element, we'll still know what they're doing. 491010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (e.type == 'mousedown') { 492010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.add(document, 'mouseup', 493010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.mouseToTouchCallback_(this.onEnd_.bind(this)), false); 494010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } else { 495010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.add(document, 'touchend', this.onEnd_.bind(this), false); 496010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.add(document, 'touchcancel', this.onEnd_.bind(this), 497010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis false); 498010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 499010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 500010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // This timeout is cleared on touchEnd and onDrag 501010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // If we invoke the function then we have a real long press 502010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis window.clearTimeout(this.longPressTimeout_); 503010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.longPressTimeout_ = window.setTimeout( 504010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.onLongPress_.bind(this), 505010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.TIME_FOR_LONG_PRESS_); 506010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 507010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Dispatch the TOUCH_START event 508010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (!this.dispatchEvent_(TouchHandler.EventType.TOUCH_START, touch)) 509010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Dragging was not enabled, nothing more to do 510010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return; 511010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 512010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // We want dragging notifications 513010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (e.type == 'mousedown') { 514010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.add(document, 'mousemove', 515010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.mouseToTouchCallback_(this.onMove_.bind(this)), false); 516010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } else { 517010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.events_.add(document, 'touchmove', this.onMove_.bind(this), false); 518010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 519010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 520010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.startTouchX_ = this.lastTouchX_ = touch.clientX; 521010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.startTouchY_ = this.lastTouchY_ = touch.clientY; 522010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.startTime_ = e.timeStamp; 523010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 524010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.recentTouchesX_ = []; 525010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.recentTouchesY_ = []; 526010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.recentTouchesX_.push(touch.clientX, e.timeStamp); 527010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.recentTouchesY_.push(touch.clientY, e.timeStamp); 528010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 529010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.beginTracking_(); 530010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 531010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 532010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 533010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Given a list of Touches, find the one matching our activeTouch 534010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * identifier. Note that Chrome currently always uses 0 as the identifier. 535010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * In that case we'll end up always choosing the first element in the list. 536010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {TouchList} touches The list of Touch objects to search. 537010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @return {!Touch|undefined} The touch matching our active ID if any. 538010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 539010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 540010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis findActiveTouch_: function(touches) { 541010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis assert(this.activeTouch_ !== undefined, 'Expecting an active touch'); 542010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // A TouchList isn't actually an array, so we shouldn't use 543010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Array.prototype.filter/some, etc. 544010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis for (var i = 0; i < touches.length; i++) { 545010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (touches[i].identifier == this.activeTouch_) 546010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return touches[i]; 547010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 548010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return undefined; 549010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 550010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 551010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 552010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Touch move handler. 553010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {!TouchEvent} e The touchmove event. 554010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 555010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 556010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis onMove_: function(e) { 557010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (!this.tracking_) 558010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return; 559010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 560010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Our active touch should always be in the list of touches still active 561010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis assert(this.findActiveTouch_(e.touches), 'Missing touchEnd'); 562010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 563010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var that = this; 564010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var touch = this.findActiveTouch_(e.changedTouches); 565010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (!touch) 566010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return; 567010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 568010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var clientX = touch.clientX; 569010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var clientY = touch.clientY; 570010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 571010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var moveX = this.lastTouchX_ - clientX; 572010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var moveY = this.lastTouchY_ - clientY; 573010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.totalMoveX_ += Math.abs(moveX); 574010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.totalMoveY_ += Math.abs(moveY); 575010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.lastTouchX_ = clientX; 576010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.lastTouchY_ = clientY; 577010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 578010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (!this.dragging_ && (this.totalMoveY_ > 579010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.MIN_TRACKING_FOR_DRAG_ || 580010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.totalMoveX_ > 581010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.MIN_TRACKING_FOR_DRAG_)) { 582010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // If we're waiting for a long press, stop 583010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis window.clearTimeout(this.longPressTimeout_); 584010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 585010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Dispatch the DRAG_START event and record whether dragging should be 586010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // allowed or not. Note that this relies on the current value of 587010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // startTouchX/Y - handlers may use the initial drag delta to determine 588010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // if dragging should be permitted. 589010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.dragging_ = this.dispatchEvent_( 590010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.EventType.DRAG_START, touch); 591010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 592010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (this.dragging_) { 593010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Update the start position here so that drag deltas have better 594010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // values but don't touch the recent positions so that velocity 595010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // calculations can still use touchstart position in the time and 596010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // distance delta. 597010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.startTouchX_ = clientX; 598010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.startTouchY_ = clientY; 599010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.startTime_ = e.timeStamp; 600010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.disableTap_ = true; 601010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } else { 602010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.endTracking_(); 603010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 604010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 605010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 606010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (this.dragging_) { 607010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.dispatchEvent_(TouchHandler.EventType.DRAG_MOVE, touch); 608010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 609010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.removeTouchesInWrongDirection_(this.recentTouchesX_, 610010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.lastMoveX_, moveX); 611010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.removeTouchesInWrongDirection_(this.recentTouchesY_, 612010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.lastMoveY_, moveY); 613010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.removeOldTouches_(this.recentTouchesX_, e.timeStamp); 614010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.removeOldTouches_(this.recentTouchesY_, e.timeStamp); 615010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.recentTouchesX_.push(clientX, e.timeStamp); 616010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.recentTouchesY_.push(clientY, e.timeStamp); 617010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 618010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 619010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.lastMoveX_ = moveX; 620010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.lastMoveY_ = moveY; 621010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 622010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 623010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 624010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Filters the provided recent touches array to remove all touches except 625010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * the last if the move direction has changed. 626010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {!Array.<number>} recentTouches An array of tuples where the first 627010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * item is the x or y component of the recent touch and the second item 628010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * is the touch time stamp. 629010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {number|undefined} lastMove The x or y component of the previous 630010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * move. 631010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {number} recentMove The x or y component of the most recent move. 632010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 633010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 634010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis removeTouchesInWrongDirection_: function(recentTouches, lastMove, 635010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis recentMove) { 636010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (lastMove && recentMove && recentTouches.length > 2 && 637010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis (lastMove > 0 ^ recentMove > 0)) { 638010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis recentTouches.splice(0, recentTouches.length - 2); 639010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 640010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 641010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 642010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 643010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Filters the provided recent touches array to remove all touches older 644010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * than the max tracking time or the 5th most recent touch. 645010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {!Array.<number>} recentTouches An array of tuples where the first 646010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * item is the x or y component of the recent touch and the second item 647010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * is the touch time stamp. 648010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {number} recentTime The time of the most recent event. 649010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 650010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 651010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis removeOldTouches_: function(recentTouches, recentTime) { 652010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis while (recentTouches.length && recentTime - recentTouches[1] > 653010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.MAX_TRACKING_TIME_ || 654010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis recentTouches.length > 655010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.MAX_TRACKING_TOUCHES_ * 2) { 656010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis recentTouches.splice(0, 2); 657010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 658010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 659010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 660010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 661010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Touch end handler. 662010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {!TouchEvent} e The touchend event. 663010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 664010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 665010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis onEnd_: function(e) { 666010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var that = this; 667010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis assert(this.activeTouch_ !== undefined, 'Expect to already be touching'); 668010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 669010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // If the touch we're tracking isn't changing here, ignore this touch end. 670010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var touch = this.findActiveTouch_(e.changedTouches); 671010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (!touch) { 672010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // In most cases, our active touch will be in the 'touches' collection, 673010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // but we can't assert that because occasionally two touchend events can 674010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // occur at almost the same time with both having empty 'touches' lists. 675010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // I.e., 'touches' seems like it can be a bit more up-to-date than the 676010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // current event. 677010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return; 678010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 679010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 680010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // This is touchEnd for the touch we're monitoring 681010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis assert(!this.findActiveTouch_(e.touches), 682010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 'Touch ended also still active'); 683010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 684010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Indicate that touching has finished 685010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.stopTouching_(); 686010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 687010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (this.tracking_) { 688010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var clientX = touch.clientX; 689010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var clientY = touch.clientY; 690010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 691010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (this.dragging_) { 692010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.endTime_ = e.timeStamp; 693010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.endTouchX_ = clientX; 694010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.endTouchY_ = clientY; 695010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 696010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.removeOldTouches_(this.recentTouchesX_, e.timeStamp); 697010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.removeOldTouches_(this.recentTouchesY_, e.timeStamp); 698010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 699010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.dispatchEvent_(TouchHandler.EventType.DRAG_END, touch); 700010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 701010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Note that in some situations we can get a click event here as well. 702010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // For now this isn't a problem, but we may want to consider having 703010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // some logic that hides clicks that appear to be caused by a touchEnd 704010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // used for dragging. 705010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 706010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 707010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.endTracking_(); 708010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 709010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 710010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Note that we dispatch the touchEnd event last so that events at 711010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // different levels of semantics nest nicely (similar to how DOM 712010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // drag-and-drop events are nested inside of the mouse events that trigger 713010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // them). 714010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.dispatchEvent_(TouchHandler.EventType.TOUCH_END, touch); 715010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (!this.disableTap_) 716010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.dispatchEvent_(TouchHandler.EventType.TAP, touch); 717010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 718010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 719010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 720010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Get end velocity of the drag. This method is specific to drag behavior, 721010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * so if touch behavior and drag behavior is split then this should go with 722010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * drag behavior. End velocity is defined as deltaXY / deltaTime where 723010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * deltaXY is the difference between endPosition and the oldest recent 724010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * position, and deltaTime is the difference between endTime and the oldest 725010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * recent time stamp. 726010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @return {Object} The x and y velocity. 727010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 728010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis getEndVelocity: function() { 729010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Note that we could move velocity to just be an end-event parameter. 730010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var velocityX = this.recentTouchesX_.length ? 731010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis (this.endTouchX_ - this.recentTouchesX_[0]) / 732010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis (this.endTime_ - this.recentTouchesX_[1]) : 0; 733010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var velocityY = this.recentTouchesY_.length ? 734010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis (this.endTouchY_ - this.recentTouchesY_[0]) / 735010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis (this.endTime_ - this.recentTouchesY_[1]) : 0; 736010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 737010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis velocityX = this.correctVelocity_(velocityX); 738010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis velocityY = this.correctVelocity_(velocityY); 739010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 740010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return { 741010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis x: velocityX, 742010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis y: velocityY 743010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }; 744010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 745010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 746010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 747010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Correct erroneous velocities by capping the velocity if we think it's too 748010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * high, or setting it to a default velocity if know that the event data is 749010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * bad. 750010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {number} velocity The x or y velocity component. 751010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @return {number} The corrected velocity. 752010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 753010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 754010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis correctVelocity_: function(velocity) { 755010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var absVelocity = Math.abs(velocity); 756010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 757010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // We add to recent touches for each touchstart and touchmove. If we have 758010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // fewer than 3 touches (6 entries), we assume that the thread was blocked 759010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // for the duration of the drag and we received events in quick succession 760010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // with the wrong time stamps. 761010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (absVelocity > TouchHandler.MAXIMUM_VELOCITY_) { 762010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis absVelocity = this.recentTouchesY_.length < 3 ? 763010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ : 764010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler.MAXIMUM_VELOCITY_; 765010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 766010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return absVelocity * (velocity < 0 ? -1 : 1); 767010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 768010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 769010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 770010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Handler when an element has been pressed for a long time 771010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 772010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 773010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis onLongPress_: function() { 774010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Swallow any click that occurs on this element without an intervening 775010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // touch start event. This simple click-busting technique should be 776010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // sufficient here since a real click should have a touchstart first. 777010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.swallowNextClick_ = true; 778010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.disableTap_ = true; 779010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 780010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Dispatch to the LONG_PRESS 781010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.dispatchEventXY_(TouchHandler.EventType.LONG_PRESS, this.element_, 782010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.startTouchX_, this.startTouchY_); 783010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 784010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 785010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 786010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Click handler - used to swallow clicks after a long-press 787010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {!Event} e The click event. 788010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 789010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 790010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis onClick_: function(e) { 791010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (this.swallowNextClick_) { 792010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis e.preventDefault(); 793010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis e.stopPropagation(); 794010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.swallowNextClick_ = false; 795010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 796010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 797010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 798010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 799010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Dispatch a TouchHandler event to the element 800010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {string} eventType The event to dispatch. 801010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {Touch} touch The touch triggering this event. 802010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @return {boolean|undefined} The value of enableDrag after dispatching 803010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * the event. 804010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 805010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 806010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis dispatchEvent_: function(eventType, touch) { 807010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 808010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Determine which element was touched. For mouse events, this is always 809010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // the event/touch target. But for touch events, the target is always the 810010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // target of the touchstart (and it's unlikely we can change this 811010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // since the common implementation of touch dragging relies on it). Since 812010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // touch is our primary scenario (which we want to emulate with mouse), 813010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // we'll treat both cases the same and not depend on the target. 814010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var touchedElement; 815010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (eventType == TouchHandler.EventType.TOUCH_START) { 816010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis touchedElement = touch.target; 817010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } else { 818010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis touchedElement = this.element_.ownerDocument. 819010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis elementFromPoint(touch.clientX, touch.clientY); 820010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 821010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 822010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return this.dispatchEventXY_(eventType, touchedElement, touch.clientX, 823010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis touch.clientY); 824010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }, 825010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 826010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis /** 827010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * Dispatch a TouchHandler event to the element 828010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @param {string} eventType The event to dispatch. 829010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis @param {number} clientX The X location for the event. 830010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis @param {number} clientY The Y location for the event. 831010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @return {boolean|undefined} The value of enableDrag after dispatching 832010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * the event. 833010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @private 834010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */ 835010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis dispatchEventXY_: function(eventType, touchedElement, clientX, clientY) { 836010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var isDrag = (eventType == TouchHandler.EventType.DRAG_START || 837010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis eventType == TouchHandler.EventType.DRAG_MOVE || 838010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis eventType == TouchHandler.EventType.DRAG_END); 839010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 840010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Drag events don't bubble - we're really just dragging the element, 841010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // not affecting its parent at all. 842010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var bubbles = !isDrag; 843010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 844010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis var event = new TouchHandler.Event(eventType, bubbles, clientX, clientY, 845010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis touchedElement); 846010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 847010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis // Set enableDrag when it can be overridden 848010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (eventType == TouchHandler.EventType.TOUCH_START) 849010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis event.enableDrag = false; 850010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis else if (eventType == TouchHandler.EventType.DRAG_START) 851010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis event.enableDrag = true; 852010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 853010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis if (isDrag) { 854010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis event.dragDeltaX = clientX - this.startTouchX_; 855010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis event.dragDeltaY = clientY - this.startTouchY_; 856010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 857010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 858010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis this.element_.dispatchEvent(event); 859010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return event.enableDrag; 860010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis } 861010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }; 862010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis 863010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis return { 864010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis TouchHandler: TouchHandler 865010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis }; 866010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis}); 867