// ========================================================================== // Project: SproutCore
- JavaScript Application Framework // Copyright: ©2010 Strobe Inc. All rights reserved. // Author: Peter Wagenet // License: Licensed under MIT license (see license.js) // ==========================================================================
sc_require(“system/gesture”);
/**
## What is a "tap"? A tap is a touch that starts and ends in a short amount of time without moving along either axis. A tap may consist of more than one touch, provided that the touches start and end together. The time allowed for touches to start and end together is defined by `touchUnityDelay`. Again, to be considered a tap, there should be very little movement of any touches on either axis while still touching. The amount of movement allowed is defined by `tapWiggle`. @class @extends SC.Gesture
*/ SC
.TapGesture = SC
.Gesture.extend( /** @scope SC
.TapGesture.prototype */{
/** @private The time that the first touch started at. */ _sc_firstTouchAddedAt: null, /** @private The time that the first touch ended at. */ _sc_firstTouchEndedAt: null, /** @private A flag used to track when the touch was long enough to register tapStart (and tapEnd). */ _sc_isTapping: false, /** @private The number of touches in the current tap. */ _sc_numberOfTouches: 0, /** @private A timer started after the first touch starts. */ _sc_tapStartTimer: null, /** @type String @default "tap" @readOnly */ name: "tap", /** The amount of time in milliseconds between when the first touch starts and the last touch ends that should be considered a short enough time to constitute a tap. @type Number @default 250 */ tapLengthDelay: 250, /** The amount of time in milliseconds after the first touch starts at which, *if the tap hasn't ended in that time*, the `tapStart` event should trigger. Because taps may be very short or because movement of the touch may invalidate a tap gesture entirely, you generally won't want to update the state of the view immediately when a touch starts. @type Number @default 150 */ tapStartDelay: 150, /** The number of pixels that a touch may move before it will no longer be considered a tap. If any of the touches move more than this amount, the gesture will give up. @type Number @default 10 */ tapWiggle: 10, /** The number of milliseconds that touches must start and end together in in order to be considered a tap. If the touches start too far apart in time or end too far apart in time based on this value, the gesture will give up. @type Number @default 75 */ touchUnityDelay: 75, /** @private Calculates the distance a touch has moved. */ _sc_calculateDragDistance: function (touch) { return Math.sqrt(Math.pow(touch.pageX - touch.startX, 2) + Math.pow(touch.pageY - touch.startY, 2)); }, /** @private Cleans up the touch session. */ _sc_cleanUpTouchSession: function (wasCancelled) { if (this._sc_isTapping) { // Trigger the gesture, 'tapCancelled'. if (wasCancelled) { this.cancel(); // Trigger the gesture, 'tapEnd'. } else { this.end(); } this._sc_isTapping = false; } // Clean up. this._sc_tapStartTimer.invalidate(); this._sc_numberOfTouches = 0; this._sc_tapStartTimer = this._sc_firstTouchAddedAt = this._sc_firstTouchEndedAt = null; }, /** @private Triggers the tapStart event. Should *not* be reachable unless the tap is still valid. */ _sc_triggerTapStart: function () { // Trigger the gesture, 'tapStart'. this.start(); this._sc_isTapping = true; }, /** The tap gesture only remains interested in a touch session as long as none of the touches have started too long after the first touch (value of `touchUnityDelay`). Once any touch has started too late, the tap gesture gives up for the entire touch session and won't attempt to re-engage (i.e. even if an extra touch "taps" cleanly in the same touch session, it won't trigger any further tap callbacks). @param {SC.Touch} touch The touch to be added to the session. @param {Array} touchesInSession The touches already in the session. @returns {Boolean} True as long as the new touch doesn't start too late after the first touch. @see SC.Gesture#touchAddedToSession */ touchAddedToSession: function (touch, touchesInSession) { var stillInterestedInSession, delay; // If the new touch came in too late after the first touch was added. delay = Date.now() - this._sc_firstTouchAddedAt; stillInterestedInSession = delay < this.get('touchUnityDelay'); return stillInterestedInSession; }, /** If a touch cancels, the tap doesn't care and remains interested. @param {SC.Touch} touch The touch to be removed from the session. @param {Array} touchesInSession The touches in the session. @returns {Boolean} True @see SC.Gesture#touchCancelledInSession */ touchCancelledInSession: function (touch, touchesInSession) { return true; }, /** The tap gesture only remains interested in a touch session as long as none of the touches have ended too long after the first touch ends (value of `touchUnityDelay`). Once any touch has ended too late, the tap gesture gives up for the entire touch session and won't attempt to re-engage (i.e. even if an extra touch "taps" cleanly in the same touch session, it won't trigger any further tap callbacks). @param {SC.Touch} touch The touch to be removed from the session. @param {Array} touchesInSession The touches in the session. @returns {Boolean} True if it is the first touch to end or a subsequent touch that ends not too long after the first touch ended. @see SC.Gesture#touchEndedInSession */ touchEndedInSession: function (touch, touchesInSession) { var stillInterestedInSession; // Increment the number of touches in the tap. this._sc_numberOfTouches += 1; // If it's the first touch to end, remain interested unless tapLengthDelay has passed. if (this._sc_firstTouchEndedAt === null) { this._sc_firstTouchEndedAt = Date.now(); stillInterestedInSession = this._sc_firstTouchEndedAt - this._sc_firstTouchAddedAt < this.get('tapLengthDelay'); // If the touch ended too late after the first touch ended, give up entirely. } else { stillInterestedInSession = Date.now() - this._sc_firstTouchEndedAt < this.get('touchUnityDelay'); } return stillInterestedInSession; }, /** The tap gesture only remains interested in a touch session as long as none of the touches have moved too far (value of `tapWiggle`). Once any touch has moved too far, the tap gesture gives up for the entire touch session and won't attempt to re-engage (i.e. even if an extra touch "taps" cleanly in the same touch session, it won't trigger any further tap callbacks). @param {Array} touchesInSession All touches in the session. @returns {Boolean} True as long as none of the touches have moved too far to be a clean tap. @see SC.Gesture#touchesMovedInSession */ touchesMovedInSession: function (touchesInSession) { var stillInterestedInSession = true; for (var i = 0, len = touchesInSession.length; i < len; i++) { var touch = touchesInSession[i], movedTooFar = this._sc_calculateDragDistance(touch) > this.get('tapWiggle'); // If any touch has gone too far, we don't want to consider any further tap actions for this // session. No need to continue. if (movedTooFar) { stillInterestedInSession = false; break; } } return stillInterestedInSession; }, /** Cleans up all touch session variables. @returns {void} @see SC.Gesture#touchSessionCancelled */ touchSessionCancelled: function () { // Clean up (will fire tapCancelled if _sc_isTapping is true). this._sc_cleanUpTouchSession(true); }, /** Cleans up all touch session variables and triggers the gesture. @returns {void} @see SC.Gesture#touchSessionEnded */ touchSessionEnded: function () { // Trigger the gesture, 'tap'. this.trigger(this._sc_numberOfTouches); // Clean up (will fire tapEnd if _sc_isTapping is true). this._sc_cleanUpTouchSession(false); }, /** Registers when the first touch started. @param {SC.Touch} touch The touch that started the session. @returns {void} @see SC.Gesture#touchSessionStarted */ touchSessionStarted: function (touch) { // Initialize. this._sc_firstTouchAddedAt = Date.now(); this._sc_tapStartTimer = SC.Timer.schedule({ target: this, action: this._sc_triggerTapStart, interval: this.get('tapStartDelay') }); }
});