1ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Use of this source code is governed by a BSD-style license that can be 3ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// found in the LICENSE file. 4ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 5ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen/** 6ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @fileoverview Touch Handler. Class that handles all touch events and 7ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * uses them to interpret higher level gestures and behaviors. TouchEvent is a 8ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * built in mobile safari type: 9ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * http://developer.apple.com/safari/library/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html. 10ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * This class is intended to work with all webkit browsers, tested on Chrome and 11ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * iOS. 12ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * 13ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The following types of gestures are currently supported. See the definition 14ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * of TouchHandler.EventType for details. 15ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * 16ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Single Touch: 17ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * This provides simple single-touch events. Any secondary touch is 18ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * ignored. 19ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * 20ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Drag: 21ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * A single touch followed by some movement. This behavior will handle all 22ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * of the required events and report the properties of the drag to you 23ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * while the touch is happening and at the end of the drag sequence. This 24ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * behavior will NOT perform the actual dragging (redrawing the element) 25ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * for you, this responsibility is left to the client code. 26ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * 27ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Long press: 28ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * When your element is touched and held without any drag occuring, the 29ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * LONG_PRESS event will fire. 30ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 31ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 32ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Use an anonymous function to enable strict mode just for this file (which 33ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// will be concatenated with other files when embedded in Chrome) 34ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvar TouchHandler = (function() { 35ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 'use strict'; 36ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 37ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 38ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * A TouchHandler attaches to an Element, listents for low-level touch (or 39ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * mouse) events and dispatching higher-level events on the element. 40ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {!Element} element The element to listen on and fire events 41ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * for. 42ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @constructor 43ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 44ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen function TouchHandler(element) { 45ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 46ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {!Element} 47ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 48ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 49ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.element_ = element; 50ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 51ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 52ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The absolute sum of all touch y deltas. 53ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 54ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 55ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 56ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.totalMoveY_ = 0; 57ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 58ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 59ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The absolute sum of all touch x deltas. 60ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 61ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 62ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 63ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.totalMoveX_ = 0; 64ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 65ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 66ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * An array of tuples where the first item is the horizontal component of a 67ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * recent relevant touch and the second item is the touch's time stamp. Old 68ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * touches are removed based on the max tracking time and when direction 69ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * changes. 70ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {!Array.<number>} 71ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 72ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 73ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.recentTouchesX_ = []; 74ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 75ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 76ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * An array of tuples where the first item is the vertical component of a 77ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * recent relevant touch and the second item is the touch's time stamp. Old 78ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * touches are removed based on the max tracking time and when direction 79ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * changes. 80ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {!Array.<number>} 81ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 82ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 83ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.recentTouchesY_ = []; 84ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 85ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 86ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Used to keep track of all events we subscribe to so we can easily clean 87ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * up 88ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {EventTracker} 89ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 90ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 91ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_ = new EventTracker(); 92ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 93ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 94ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 95ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 96ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * DOM Events that may be fired by the TouchHandler at the element 97ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 98ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.EventType = { 99ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Fired whenever the element is touched as the only touch to the device. 100ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // enableDrag defaults to false, set to true to permit dragging. 101ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TOUCH_START: 'touchhandler:touch_start', 102ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 103ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Fired when an element is held for a period of time. Prevents dragging 104ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // from occuring (even if enableDrag was set to true). 105ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen LONG_PRESS: 'touchhandler:long_press', 106ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 107ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // If enableDrag was set to true at TOUCH_START, DRAG_START will fire when 108ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // the touch first moves sufficient distance. enableDrag is set to true but 109ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // can be reset to false to cancel the drag. 110ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen DRAG_START: 'touchhandler:drag_start', 111ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 112ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // If enableDrag was true after DRAG_START, DRAG_MOVE will fire whenever the 113ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // touch is moved. 114ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen DRAG_MOVE: 'touchhandler:drag_move', 115ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 116ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Fired just before TOUCH_END when a drag is released. Correlates 1:1 with 117ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // a DRAG_START. 118ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen DRAG_END: 'touchhandler:drag_end', 119ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 120ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Fired whenever a touch that is being tracked has been released. 121ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Correlates 1:1 with a TOUCH_START. 122ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TOUCH_END: 'touchhandler:touch_end' 123ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }; 124ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 125ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 126ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 127ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The type of event sent by TouchHandler 128ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @constructor 129ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {string} type The type of event (one of Grabber.EventType). 130ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {boolean} bubbles Whether or not the event should bubble. 131ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {number} clientX The X location of the touch. 132ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {number} clientY The Y location of the touch. 133ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {!Element} touchedElement The element at the current location of the 134ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * touch. 135ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 136ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.Event = function(type, bubbles, clientX, clientY, 137ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen touchedElement) { 138ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var event = document.createEvent('Event'); 139ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen event.initEvent(type, bubbles, true); 140ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen event.__proto__ = TouchHandler.Event.prototype; 141ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 142ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 143ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The X location of the touch affected 144ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 145ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 146ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen event.clientX = clientX; 147ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 148ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 149ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The Y location of the touch affected 150ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 151ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 152ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen event.clientY = clientY; 153ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 154ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 155ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The element at the current location of the touch. 156ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {!Element} 157ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 158ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen event.touchedElement = touchedElement; 159ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 160ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return event; 161ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }; 162ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 163ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.Event.prototype = { 164ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen __proto__: Event.prototype, 165ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 166ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 167ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * For TOUCH_START and DRAG START events, set to true to enable dragging or 168ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * false to disable dragging. 169ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {boolean|undefined} 170ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 171ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen enableDrag: undefined, 172ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 173ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 174ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * For DRAG events, provides the horizontal component of the 175ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * drag delta. Drag delta is defined as the delta of the start touch 176ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * position and the current drag position. 177ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 178ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 179ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen dragDeltaX: undefined, 180ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 181ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 182ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * For DRAG events, provides the vertical component of the 183ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * drag delta. 184ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 185ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 186ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen dragDeltaY: undefined 187ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }; 188ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 189ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 190ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Minimum movement of touch required to be considered a drag. 191ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 192ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 193ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 194ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.MIN_TRACKING_FOR_DRAG_ = 8; 195ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 196ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 197ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 198ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The maximum number of ms to track a touch event. After an event is older 199ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * than this value, it will be ignored in velocity calculations. 200ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 201ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 202ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 203ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.MAX_TRACKING_TIME_ = 250; 204ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 205ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 206ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 207ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The maximum number of touches to track. 208ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 209ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 210ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 211ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.MAX_TRACKING_TOUCHES_ = 5; 212ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 213ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 214ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 215ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The maximum velocity to return, in pixels per millisecond, that is used 216ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * to guard against errors in calculating end velocity of a drag. This is a 217ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * very fast drag velocity. 218ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 219ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 220ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 221ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.MAXIMUM_VELOCITY_ = 5; 222ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 223ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 224ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 225ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The velocity to return, in pixel per millisecond, when the time stamps on 226ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * the events are erroneous. The browser can return bad time stamps if the 227ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * thread is blocked for the duration of the drag. This is a low velocity to 228ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * prevent the content from moving quickly after a slow drag. It is less 229ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * jarring if the content moves slowly after a fast drag. 230ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 231ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 232ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 233ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ = 1; 234ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 235ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 236ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The time, in milliseconds, that a touch must be held to be considered 237ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * 'long'. 238ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number} 239ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 240ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 241ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.TIME_FOR_LONG_PRESS_ = 500; 242ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 243ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.prototype = { 244ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 245ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * If defined, the identifer of the single touch that is active. Note that 246ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * 0 is a valid touch identifier - it should not be treated equivalently to 247ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * undefined. 248ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 249ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 250ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 251ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen activeTouch_: undefined, 252ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 253ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 254ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {boolean|undefined} 255ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 256ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 257ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen tracking_: undefined, 258ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 259ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 260ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 261ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 262ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 263ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen startTouchX_: undefined, 264ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 265ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 266ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 267ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 268ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 269ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen startTouchY_: undefined, 270ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 271ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 272ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 273ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 274ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 275ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen endTouchX_: undefined, 276ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 277ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 278ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 279ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 280ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 281ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen endTouchY_: undefined, 282ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 283ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 284ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Time of the touchstart event. 285ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 286ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 287ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 288ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen startTime_: undefined, 289ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 290ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 291ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * The time of the touchend event. 292ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 293ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 294ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 295ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen endTime_: undefined, 296ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 297ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 298ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 299ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 300ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 301ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen lastTouchX_: undefined, 302ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 303ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 304ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 305ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 306ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 307ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen lastTouchY_: undefined, 308ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 309ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 310ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 311ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 312ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 313ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen lastMoveX_: undefined, 314ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 315ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 316ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 317ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 318ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 319ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen lastMoveY_: undefined, 320ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 321ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 322ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {number|undefined} 323ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 324ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 325ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen longPressTimeout_: undefined, 326ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 327ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 328ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * If defined and true, the next click event should be swallowed 329ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @type {boolean|undefined} 330ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 331ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 332ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen swallowNextClick_: undefined, 333ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 334ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 335ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Start listenting for events. 336ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {boolean=} opt_capture True if the TouchHandler should listen to 337ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * during the capture phase. 338ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 339ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen enable: function(opt_capture) { 340ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var capture = !!opt_capture; 341ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 342ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Just listen to start events for now. When a touch is occuring we'll 343ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // want to be subscribed to move and end events on the document, but we 344ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // don't want to incur the cost of lots of no-op handlers on the document. 345ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.add(this.element_, 'touchstart', this.onStart_.bind(this), 346ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen capture); 347ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.add(this.element_, 'mousedown', 348ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.mouseToTouchCallback_(this.onStart_.bind(this)), 349ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen capture); 350ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 351ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // If the element is long-pressed, we may need to swallow a click 352ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.add(this.element_, 'click', this.onClick_.bind(this), true); 353ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 354ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 355ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 356ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Stop listening to all events. 357ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 358ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen disable: function() { 359ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.stopTouching_(); 360ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.removeAll(); 361ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 362ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 363ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 364ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Wraps a callback with translations of mouse events to touch events. 365ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * NOTE: These types really should be function(Event) but then we couldn't 366ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * use this with bind (which operates on any type of function). Doesn't 367ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * JSDoc support some sort of polymorphic types? 368ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {Function} callback The event callback. 369ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @return {Function} The wrapping callback. 370ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 371ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 372ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen mouseToTouchCallback_: function(callback) { 373ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return function(e) { 374ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Note that there may be synthesizes mouse events caused by touch 375ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // events (a mouseDown after a touch-click). We leave it up to the 376ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // client to worry about this if it matters to them (typically a short 377ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // mouseDown/mouseUp without a click is no big problem and it's not 378ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // obvious how we identify such synthesized events in a general way). 379ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var touch = { 380ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // any fixed value will do for the identifier - there will only 381ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // ever be a single active 'touch' when using the mouse. 382ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen identifier: 0, 383ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen clientX: e.clientX, 384ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen clientY: e.clientY, 385ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen target: e.target 386ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }; 387ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen e.touches = []; 388ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen e.targetTouches = []; 389ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen e.changedTouches = [touch]; 390ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (e.type != 'mouseup') { 391ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen e.touches[0] = touch; 392ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen e.targetTouches[0] = touch; 393ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 394ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen callback(e); 395ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }; 396ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 397ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 398ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 399ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Begin tracking the touchable element, it is eligible for dragging. 400ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 401ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 402ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen beginTracking_: function() { 403ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.tracking_ = true; 404ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 405ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 406ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 407ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Stop tracking the touchable element, it is no longer dragging. 408ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 409ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 410ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen endTracking_: function() { 411ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.tracking_ = false; 412ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.dragging_ = false; 413ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.totalMoveY_ = 0; 414ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.totalMoveX_ = 0; 415ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 416ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 417ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 418ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Reset the touchable element as if we never saw the touchStart 419ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Doesn't dispatch any end events - be careful of existing listeners. 420ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 421ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen cancelTouch: function() { 422ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.stopTouching_(); 423ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.endTracking_(); 424ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // If clients needed to be aware of this, we could fire a cancel event 425ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // here. 426ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 427ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 428ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 429ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Record that touching has stopped 430ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 431ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 432ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen stopTouching_: function() { 433ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Mark as no longer being touched 434ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.activeTouch_ = undefined; 435ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 436ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // If we're waiting for a long press, stop 437ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen window.clearTimeout(this.longPressTimeout_); 438ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 439ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Stop listening for move/end events until there's another touch. 440ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // We don't want to leave handlers piled up on the document. 441ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Note that there's no harm in removing handlers that weren't added, so 442ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // rather than track whether we're using mouse or touch we do both. 443ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.remove(document, 'touchmove'); 444ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.remove(document, 'touchend'); 445ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.remove(document, 'touchcancel'); 446ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.remove(document, 'mousemove'); 447ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.remove(document, 'mouseup'); 448ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 449ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 450ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 451ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Touch start handler. 452ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {!TouchEvent} e The touchstart event. 453ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 454ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 455ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen onStart_: function(e) { 456ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Only process single touches. If there is already a touch happening, or 457ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // two simultaneous touches then just ignore them. 458ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (e.touches.length > 1) 459ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Note that we could cancel an active touch here. That would make 460ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // simultaneous touch behave similar to near-simultaneous. However, if 461ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // the user is dragging something, an accidental second touch could be 462ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // quite disruptive if it cancelled their drag. Better to just ignore 463ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // it. 464ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return; 465ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 466ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // It's still possible there could be an active "touch" if the user is 467ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // simultaneously using a mouse and a touch input. 468ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (this.activeTouch_ !== undefined) 469ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return; 470ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 471ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var touch = e.targetTouches[0]; 472ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.activeTouch_ = touch.identifier; 473ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 474ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // We've just started touching so shouldn't swallow any upcoming click 475ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (this.swallowNextClick_) 476ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.swallowNextClick_ = false; 477ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 478ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Sign up for end/cancel notifications for this touch. 479ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Note that we do this on the document so that even if the user drags 480ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // their finger off the element, we'll still know what they're doing. 481ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (e.type == 'mousedown') { 482ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.add(document, 'mouseup', 483ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.mouseToTouchCallback_(this.onEnd_.bind(this)), false); 484ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } else { 485ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.add(document, 'touchend', this.onEnd_.bind(this), false); 486ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.add(document, 'touchcancel', this.onEnd_.bind(this), 487ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen false); 488ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 489ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 490ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // This timeout is cleared on touchEnd and onDrag 491ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // If we invoke the function then we have a real long press 492ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen window.clearTimeout(this.longPressTimeout_); 493ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.longPressTimeout_ = window.setTimeout( 494ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.onLongPress_.bind(this), 495ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.TIME_FOR_LONG_PRESS_); 496ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 497ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Dispatch the TOUCH_START event 498ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (!this.dispatchEvent_(TouchHandler.EventType.TOUCH_START, touch)) 499ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Dragging was not enabled, nothing more to do 500ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return; 501ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 502ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // We want dragging notifications 503ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (e.type == 'mousedown') { 504ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.add(document, 'mousemove', 505ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.mouseToTouchCallback_(this.onMove_.bind(this)), false); 506ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } else { 507ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.events_.add(document, 'touchmove', this.onMove_.bind(this), false); 508ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 509ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 510ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.startTouchX_ = this.lastTouchX_ = touch.clientX; 511ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.startTouchY_ = this.lastTouchY_ = touch.clientY; 512ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.startTime_ = e.timeStamp; 513ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 514ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.recentTouchesX_ = []; 515ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.recentTouchesY_ = []; 516ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.recentTouchesX_.push(touch.clientX, e.timeStamp); 517ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.recentTouchesY_.push(touch.clientY, e.timeStamp); 518ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 519ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.beginTracking_(); 520ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 521ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 522ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 523ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Given a list of Touches, find the one matching our activeTouch 524ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * identifier. Note that Chrome currently always uses 0 as the identifier. 525ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * In that case we'll end up always choosing the first element in the list. 526ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {TouchList} touches The list of Touch objects to search. 527ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @return {!Touch|undefined} The touch matching our active ID if any. 528ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 529ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 530ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen findActiveTouch_: function(touches) { 531ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen assert(this.activeTouch_ !== undefined, 'Expecting an active touch'); 532ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // A TouchList isn't actually an array, so we shouldn't use 533ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Array.prototype.filter/some, etc. 534ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen for (var i = 0; i < touches.length; i++) { 535ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (touches[i].identifier == this.activeTouch_) 536ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return touches[i]; 537ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 538ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return undefined; 539ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 540ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 541ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 542ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Touch move handler. 543ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {!TouchEvent} e The touchmove event. 544ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 545ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 546ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen onMove_: function(e) { 547ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (!this.tracking_) 548ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return; 549ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 550ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Our active touch should always be in the list of touches still active 551ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen assert(this.findActiveTouch_(e.touches), 'Missing touchEnd'); 552ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 553ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var that = this; 554ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var touch = this.findActiveTouch_(e.changedTouches); 555ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (!touch) 556ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return; 557ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 558ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var clientX = touch.clientX; 559ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var clientY = touch.clientY; 560ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 561ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var moveX = this.lastTouchX_ - clientX; 562ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var moveY = this.lastTouchY_ - clientY; 563ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.totalMoveX_ += Math.abs(moveX); 564ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.totalMoveY_ += Math.abs(moveY); 565ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.lastTouchX_ = clientX; 566ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.lastTouchY_ = clientY; 567ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 568ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (!this.dragging_ && (this.totalMoveY_ > 569ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.MIN_TRACKING_FOR_DRAG_ || 570ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.totalMoveX_ > 571ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.MIN_TRACKING_FOR_DRAG_)) { 572ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // If we're waiting for a long press, stop 573ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen window.clearTimeout(this.longPressTimeout_); 574ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 575ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Dispatch the DRAG_START event and record whether dragging should be 576ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // allowed or not. Note that this relies on the current value of 577ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // startTouchX/Y - handlers may use the initial drag delta to determine 578ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // if dragging should be permitted. 579ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.dragging_ = this.dispatchEvent_( 580ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.EventType.DRAG_START, touch); 581ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 582ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (this.dragging_) { 583ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Update the start position here so that drag deltas have better 584ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // values but don't touch the recent positions so that velocity 585ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // calculations can still use touchstart position in the time and 586ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // distance delta. 587ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.startTouchX_ = clientX; 588ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.startTouchY_ = clientY; 589ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.startTime_ = e.timeStamp; 590ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } else { 591ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.endTracking_(); 592ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 593ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 594ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 595ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (this.dragging_) { 596ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.dispatchEvent_(TouchHandler.EventType.DRAG_MOVE, touch); 597ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 598ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.removeTouchesInWrongDirection_(this.recentTouchesX_, 599ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.lastMoveX_, moveX); 600ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.removeTouchesInWrongDirection_(this.recentTouchesY_, 601ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.lastMoveY_, moveY); 602ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.removeOldTouches_(this.recentTouchesX_, e.timeStamp); 603ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.removeOldTouches_(this.recentTouchesY_, e.timeStamp); 604ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.recentTouchesX_.push(clientX, e.timeStamp); 605ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.recentTouchesY_.push(clientY, e.timeStamp); 606ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 607ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 608ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.lastMoveX_ = moveX; 609ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.lastMoveY_ = moveY; 610ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 611ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 612ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 613ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Filters the provided recent touches array to remove all touches except 614ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * the last if the move direction has changed. 615ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {!Array.<number>} recentTouches An array of tuples where the first 616ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * item is the x or y component of the recent touch and the second item 617ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * is the touch time stamp. 618ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {number|undefined} lastMove The x or y component of the previous 619ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * move. 620ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {number} recentMove The x or y component of the most recent move. 621ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 622ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 623ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen removeTouchesInWrongDirection_: function(recentTouches, lastMove, 624ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen recentMove) { 625ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (lastMove && recentMove && recentTouches.length > 2 && 626ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen (lastMove > 0 ^ recentMove > 0)) { 627ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen recentTouches.splice(0, recentTouches.length - 2); 628ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 629ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 630ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 631ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 632ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Filters the provided recent touches array to remove all touches older 633ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * than the max tracking time or the 5th most recent touch. 634ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {!Array.<number>} recentTouches An array of tuples where the first 635ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * item is the x or y component of the recent touch and the second item 636ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * is the touch time stamp. 637ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {number} recentTime The time of the most recent event. 638ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 639ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 640ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen removeOldTouches_: function(recentTouches, recentTime) { 641ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen while (recentTouches.length && recentTime - recentTouches[1] > 642ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.MAX_TRACKING_TIME_ || 643ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen recentTouches.length > 644ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.MAX_TRACKING_TOUCHES_ * 2) { 645ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen recentTouches.splice(0, 2); 646ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 647ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 648ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 649ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 650ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Touch end handler. 651ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {!TouchEvent} e The touchend event. 652ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 653ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 654ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen onEnd_: function(e) { 655ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var that = this; 656ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen assert(this.activeTouch_ !== undefined, 'Expect to already be touching'); 657ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 658ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // If the touch we're tracking isn't changing here, ignore this touch end. 659ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var touch = this.findActiveTouch_(e.changedTouches); 660ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (!touch) { 661ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // In most cases, our active touch will be in the 'touches' collection, 662ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // but we can't assert that because occasionally two touchend events can 663ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // occur at almost the same time with both having empty 'touches' lists. 664ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // I.e., 'touches' seems like it can be a bit more up-to-date than the 665ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // current event. 666ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return; 667ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 668ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 669ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // This is touchEnd for the touch we're monitoring 670ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen assert(!this.findActiveTouch_(e.touches), 671ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 'Touch ended also still active'); 672ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 673ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Indicate that touching has finished 674ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.stopTouching_(); 675ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 676ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (this.tracking_) { 677ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var clientX = touch.clientX; 678ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var clientY = touch.clientY; 679ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 680ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (this.dragging_) { 681ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.endTime_ = e.timeStamp; 682ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.endTouchX_ = clientX; 683ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.endTouchY_ = clientY; 684ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 685ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.removeOldTouches_(this.recentTouchesX_, e.timeStamp); 686ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.removeOldTouches_(this.recentTouchesY_, e.timeStamp); 687ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 688ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.dispatchEvent_(TouchHandler.EventType.DRAG_END, touch); 689ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 690ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Note that in some situations we can get a click event here as well. 691ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // For now this isn't a problem, but we may want to consider having 692ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // some logic that hides clicks that appear to be caused by a touchEnd 693ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // used for dragging. 694ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 695ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 696ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.endTracking_(); 697ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 698ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 699ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Note that we dispatch the touchEnd event last so that events at 700ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // different levels of semantics nest nicely (similar to how DOM 701ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // drag-and-drop events are nested inside of the mouse events that trigger 702ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // them). 703ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.dispatchEvent_(TouchHandler.EventType.TOUCH_END, touch); 704ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 705ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 706ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 707ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Get end velocity of the drag. This method is specific to drag behavior, 708ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * so if touch behavior and drag behavior is split then this should go with 709ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * drag behavior. End velocity is defined as deltaXY / deltaTime where 710ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * deltaXY is the difference between endPosition and the oldest recent 711ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * position, and deltaTime is the difference between endTime and the oldest 712ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * recent time stamp. 713ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @return {Object} The x and y velocity. 714ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 715ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen getEndVelocity: function() { 716ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Note that we could move velocity to just be an end-event parameter. 717ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var velocityX = this.recentTouchesX_.length ? 718ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen (this.endTouchX_ - this.recentTouchesX_[0]) / 719ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen (this.endTime_ - this.recentTouchesX_[1]) : 0; 720ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var velocityY = this.recentTouchesY_.length ? 721ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen (this.endTouchY_ - this.recentTouchesY_[0]) / 722ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen (this.endTime_ - this.recentTouchesY_[1]) : 0; 723ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 724ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen velocityX = this.correctVelocity_(velocityX); 725ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen velocityY = this.correctVelocity_(velocityY); 726ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 727ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return { 728ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen x: velocityX, 729ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen y: velocityY 730ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }; 731ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 732ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 733ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 734ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Correct erroneous velocities by capping the velocity if we think it's too 735ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * high, or setting it to a default velocity if know that the event data is 736ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * bad. 737ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {number} velocity The x or y velocity component. 738ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @return {number} The corrected velocity. 739ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 740ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 741ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen correctVelocity_: function(velocity) { 742ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var absVelocity = Math.abs(velocity); 743ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 744ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // We add to recent touches for each touchstart and touchmove. If we have 745ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // fewer than 3 touches (6 entries), we assume that the thread was blocked 746ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // for the duration of the drag and we received events in quick succession 747ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // with the wrong time stamps. 748ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (absVelocity > TouchHandler.MAXIMUM_VELOCITY_) { 749ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen absVelocity = this.recentTouchesY_.length < 3 ? 750ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ : 751ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen TouchHandler.MAXIMUM_VELOCITY_; 752ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 753ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return absVelocity * (velocity < 0 ? -1 : 1); 754ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 755ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 756ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 757ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Handler when an element has been pressed for a long time 758ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 759ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 760ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen onLongPress_: function() { 761ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Swallow any click that occurs on this element without an intervening 762ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // touch start event. This simple click-busting technique should be 763ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // sufficient here since a real click should have a touchstart first. 764ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.swallowNextClick_ = true; 765ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 766ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Dispatch to the LONG_PRESS 767ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.dispatchEventXY_(TouchHandler.EventType.LONG_PRESS, this.element_, 768ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.startTouchX_, this.startTouchY_); 769ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 770ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 771ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 772ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Click handler - used to swallow clicks after a long-press 773ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {!Event} e The click event. 774ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 775ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 776ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen onClick_: function(e) { 777ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (this.swallowNextClick_) { 778ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen e.preventDefault(); 779ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen e.stopPropagation(); 780ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.swallowNextClick_ = false; 781ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 782ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 783ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 784ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 785ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Dispatch a TouchHandler event to the element 786ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {string} eventType The event to dispatch. 787ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {Touch} touch The touch triggering this event. 788ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @return {boolean|undefined} The value of enableDrag after dispatching 789ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * the event. 790ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 791ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 792ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen dispatchEvent_: function(eventType, touch) { 793ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 794ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Determine which element was touched. For mouse events, this is always 795ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // the event/touch target. But for touch events, the target is always the 796ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // target of the touchstart (and it's unlikely we can change this 797ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // since the common implementation of touch dragging relies on it). Since 798ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // touch is our primary scenario (which we want to emulate with mouse), 799ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // we'll treat both cases the same and not depend on the target. 800ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var touchedElement; 801ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (eventType == TouchHandler.EventType.TOUCH_START) { 802ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen touchedElement = touch.target; 803ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } else { 804ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen touchedElement = this.element_.ownerDocument. 805ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen elementFromPoint(touch.clientX, touch.clientY); 806ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 807ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 808ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return this.dispatchEventXY_(eventType, touchedElement, touch.clientX, 809ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen touch.clientY); 810ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }, 811ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 812ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen /** 813ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Dispatch a TouchHandler event to the element 814ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @param {string} eventType The event to dispatch. 815ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen @param {number} clientX The X location for the event. 816ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen @param {number} clientY The Y location for the event. 817ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @return {boolean|undefined} The value of enableDrag after dispatching 818ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * the event. 819ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @private 820ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */ 821ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen dispatchEventXY_: function(eventType, touchedElement, clientX, clientY) { 822ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var isDrag = (eventType == TouchHandler.EventType.DRAG_START || 823ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen eventType == TouchHandler.EventType.DRAG_MOVE || 824ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen eventType == TouchHandler.EventType.DRAG_END); 825ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 826ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Drag events don't bubble - we're really just dragging the element, 827ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // not affecting its parent at all. 828ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var bubbles = !isDrag; 829ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 830ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen var event = new TouchHandler.Event(eventType, bubbles, clientX, clientY, 831ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen touchedElement); 832ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 833ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen // Set enableDrag when it can be overridden 834ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (eventType == TouchHandler.EventType.TOUCH_START) 835ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen event.enableDrag = false; 836ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen else if (eventType == TouchHandler.EventType.DRAG_START) 837ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen event.enableDrag = true; 838ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 839ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen if (isDrag) { 840ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen event.dragDeltaX = clientX - this.startTouchX_; 841ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen event.dragDeltaY = clientY - this.startTouchY_; 842ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 843ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 844ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen this.element_.dispatchEvent(event); 845ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return event.enableDrag; 846ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen } 847ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen }; 848ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen 849ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen return TouchHandler; 850ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen})(); 851