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