// ========================================================================== // Project: SproutCore
- JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // Portions ©2008-2011 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ==========================================================================
sc_require(“system/gesture”);
/*
TODO Document this class
*/
/**
@class @extends SC.Gesture
*/ SC
.PinchGesture = SC
.Gesture.extend( /** @scope SC
.PinchGesture.prototype */{
/** @private Whether we have started pinching or not. @type Boolean @default false */ _sc_isPinching: false, /** @private The previous distance between touches. @type Number @default null */ _sc_pinchAnchorD: null, /** @private The initial scale of the view before pinching. @type Number @default null */ _sc_pinchAnchorScale: null, /** @type String @default "pinch" */ name: "pinch", /** The amount of time in milliseconds that touches should stop moving before a `pinchEnd` event will fire. When a pinch gesture begins, the `pinchStart` event is fired and as long as the touches continue to change distance, multiple `pinch` events will fire. If the touches remain active but don't change distance any longer, then after `pinchDelay` milliseconds the `pinchEnd` event will fire. @type Number @default 500 */ pinchDelay: 500, /** The number of pixels that multiple touches need to expand or contract in order to trigger the beginning of a pinch. @type Number @default 3 */ // pinchStartThreshold: 3, /** @private Cleans up the touch session. */ _sc_cleanUpTouchSession: function () { // If we were pinching before, end the pinch immediately. if (this._sc_isPinching) { this._sc_pinchingTimer.invalidate(); this._sc_pinchingTimer = null; this._sc_lastPinchTime = null; this._sc_isPinching = false; // Trigger the gesture, 'pinchEnd'. this.end(); } // Clean up. this._sc_pinchAnchorD = null; }, /** @private Shared function for when a touch ends or cancels. */ _sc_touchFinishedInSession: function (touch, touchesInSession) { // If there are more than two touches, keep monitoring for pinches by updating _sc_pinchAnchorD. if (touchesInSession.length > 1) { // Get the averaged touches for the the view. Because pinch is always interested in every touch // the touchesInSession will equal the touches for the view. var avgTouch = touch.averagedTouchesForView(this.view); this._sc_pinchAnchorD = avgTouch.d; // Disregard incoming touches by clearing out _sc_pinchAnchorD and end an active pinch immediately. } else { this._sc_cleanUpTouchSession(); } }, /** @private Triggers pinchEnd and resets _sc_isPinching if enough time has passed. */ _sc_triggerPinchEnd: function () { // If a pinch came in since the time the timer was registered, set up a new timer for the // remaining time. if (this._sc_lastPinchTime) { var timePassed = Date.now() - this._sc_lastPinchTime, pinchDelay = this.get('pinchDelay'); // Prepare to send 'pinchEnd' again. this._sc_pinchingTimer = SC.Timer.schedule({ target: this, action: this._sc_triggerPinchEnd, interval: pinchDelay - timePassed // Trigger the timer the amount of time left since the last pinch }); // Clear out the last pinch time. this._sc_lastPinchTime = null; // No additional pinches appeared in the amount of time. } else { // Trigger the gesture, 'pinchEnd'. this.end(); // Clear out the pinching session. this._sc_isPinching = false; this._sc_pinchingTimer = null; } }, /** The pinch gesture is always interested in the touch session. When a new touch is added, the distance between all of the touches is registered in order to check for distance changes equating to a pinch gesture. @param {SC.Touch} touch The touch to be added to the session. @param {Array} touchesInSession The touches already in the session. @returns {Boolean} True. @see SC.Gesture#touchAddedToSession */ touchAddedToSession: function (touch, touchesInSession) { // Get the averaged touches for the the view. Because pinch is always interested in every touch // the touchesInSession will equal the touches for the view. var avgTouch = touch.averagedTouchesForView(this.view, true); this._sc_pinchAnchorD = avgTouch.d; return true; }, /** If a touch cancels, the pinch remains interested (even if there's only one touch left, because a second touch may appear again), but updates its internal variable for tracking for pinch movements. @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) { this._sc_touchFinishedInSession(touch, touchesInSession); return true; }, /** If a touch ends, the pinch remains interested (even if there's only one touch left, because a second touch may appear again), but updates its internal variable for tracking for pinch movements. @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#touchEndedInSession */ touchEndedInSession: function (touch, touchesInSession) { this._sc_touchFinishedInSession(touch, touchesInSession); return true; }, /** The pinch is only interested in more than one touch moving. If there are multiple touches moving and the distance between the touches has changed then a `pinchStart` event will fire. If the touches keep expanding or contracting, the `pinch` event will repeatedly fire. Finally, if the touch distance stops changing and enough time passes (value of `pinchDelay`), the `pinchEnd` event will fire. Therefore, it's possible for a pinch gesture to start and end more than once in a single touch session. For example, a person may touch two fingers down, expand them to zoom in (`pinchStart` and multiple `pinch` events fire) and then if they stop or move their fingers in one direction in tandem to scroll content (`pinchEnd` event fires after `pinchDelay` exceeded). If the person then starts expanding their fingers again without lifting them, a new set of pinch events will fire. @param {Array} touchesInSession All touches in the session. @returns {Boolean} True. @see SC.Gesture#touchesMovedInSession */ touchesMovedInSession: function (touchesInSession) { // console.log('touchesMovedInSession: %@'.fmt(touchesInSession.length)); // We should pay attention to the movement. if (touchesInSession.length > 1) { // Get the averaged touches for the the view. Because pinch is always interested in every touch // the touchesInSession will equal the touches for the view. var avgTouch = SC.Touch.averagedTouch(touchesInSession); // touchesInSession[0].averagedTouchesForView(this.view); var touchDeltaD = this._sc_pinchAnchorD - avgTouch.d, absDeltaD = Math.abs(touchDeltaD); // console.log(' this._sc_pinchAnchorD, %@ - avgTouch.d, %@ = touchDeltaD, %@'.fmt(this._sc_pinchAnchorD, avgTouch.d, touchDeltaD)); if (absDeltaD > 0) { // Trigger the gesture, 'pinchStart', once. if (!this._sc_isPinching) { this.start(); // Prepare to send 'pinchEnd'. this._sc_pinchingTimer = SC.Timer.schedule({ target: this, action: this._sc_triggerPinchEnd, interval: this.get('pinchDelay') }); // Track that we are pinching. this._sc_isPinching = true; // Update the last pinch time so that when the timer expires, it doesn't fire pinchEnd. // This is faster than invalidating and creating a new timer each time this method is called. } else { this._sc_lastPinchTime = Date.now(); } // The percentage difference in touch distance. var scalePercentChange = avgTouch.d / this._sc_pinchAnchorD, scale = this._sc_pinchAnchorScale * scalePercentChange; // Trigger the gesture, 'pinch'. this.trigger(scale, touchesInSession.length); // Reset the anchor. this._sc_pinchAnchorD = avgTouch.d; this._sc_pinchAnchorScale = scale; } } return true; }, /** Cleans up all touch session variables. @returns {void} @see SC.Gesture#touchSessionCancelled */ touchSessionCancelled: function () { // Clean up. this._sc_cleanUpTouchSession(); }, /** Cleans up all touch session variables and triggers the gesture. @returns {void} @see SC.Gesture#touchSessionEnded */ touchSessionEnded: function () { // Clean up. this._sc_cleanUpTouchSession(); }, /** Registers the scale of the view when it starts. @param {SC.Touch} touch The touch that started the session. @returns {void} @see SC.Gesture#touchSessionStarted */ touchSessionStarted: function (touch) { var viewLayout = this.view.get('layout'); /*jshint eqnull:true*/ this._sc_pinchAnchorScale = viewLayout.scale == null ? 1 : viewLayout.scale; }
});