All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scaffold.libs_as.org.gestouch.gestures.Gesture.as Maven / Gradle / Ivy

package org.gestouch.gestures
{
	import org.gestouch.core.Gestouch;
	import org.gestouch.core.GestureState;
	import org.gestouch.core.GesturesManager;
	import org.gestouch.core.IGestureTargetAdapter;
	import org.gestouch.core.Touch;
	import org.gestouch.core.gestouch_internal;
	import org.gestouch.events.GestureEvent;

	import flash.errors.IllegalOperationError;
	import flash.events.EventDispatcher;
	import flash.geom.Point;
	import flash.system.Capabilities;
	import flash.utils.Dictionary;

	use namespace gestouch_internal;

	/**
	 * Dispatched when the state of the gesture changes.
	 *
	 * @eventType org.gestouch.events.GestureEvent
	 * @see #state
	 */
	[Event(name="gestureStateChange", type="org.gestouch.events.GestureEvent")]
	/**
	 * Dispatched when the state of the gesture changes to GestureState.POSSIBLE.
	 *
	 * @eventType org.gestouch.events.GestureEvent
	 * @see #state
	 */
	[Event(name="gesturePossible", type="org.gestouch.events.GestureEvent")]
	/**
	 * Dispatched when the state of the gesture changes to GestureState.FAILED.
	 *
	 * @eventType org.gestouch.events.GestureEvent
	 * @see #state
	 */
	[Event(name="gestureFailed", type="org.gestouch.events.GestureEvent")]
	/**
	 * Base class for all gestures. Gesture is essentially a detector that tracks touch points
	 * in order detect specific gesture motion and form gesture event on target.
	 *
	 * @author Pavel fljot
	 */ public class Gesture extends EventDispatcher
	{
		/**
		 * Threshold for screen distance they must move to count as valid input
		 * (not an accidental offset on touch),
		 * based on 20 pixels on a 252ppi device.
		 */
		public static var DEFAULT_SLOP : uint = Math.round( 20 / 252 * flash.system.Capabilities.screenDPI );
		/**
		 * Map (generic object) of tracking touch points, where keys are touch points IDs.
		 */
		protected var _touchesMap : Object = {};
		protected var _centralPoint : Point = new Point();
		/**
		 * List of gesture we require to fail.
		 * @see requireGestureToFail()
		 */
		protected var _gesturesToFail : Dictionary = new Dictionary( true );
		protected var _pendingRecognizedState : GestureState;
		/**
		 * If a gesture should receive a touch.
		 * Callback signature: function(gesture:Gesture, touch:Touch):Boolean
		 *
		 * @see Touch
		 */
		public var gestureShouldReceiveTouchCallback : Function;
		/**
		 * If a gesture should be recognized (transition from state POSSIBLE to state RECOGNIZED or BEGAN).
		 * Returning false causes the gesture to transition to the FAILED state.
		 *
		 * Callback signature: function(gesture:Gesture):Boolean
		 *
		 * @see state
		 * @see GestureState
		 */
		public var gestureShouldBeginCallback : Function;
		/**
		 * If two gestures should be allowed to recognize simultaneously.
		 *
		 * Callback signature: function(gesture:Gesture, otherGesture:Gesture):Boolean
		 */
		public var gesturesShouldRecognizeSimultaneouslyCallback : Function;

		/** @private */
		protected var _targetAdapter : IGestureTargetAdapter;

		protected function get targetAdapter() : IGestureTargetAdapter
		{
			return _targetAdapter;
		}

		/**
		 * FIXME
		 * InteractiveObject (DisplayObject) which this gesture is tracking the actual gesture motion on.
		 *
		 * 

Could be some image, component (like map) or the larger view like Stage.

* *

You can change the target in the runtime, e.g. you have a gallery * where only one item is visible at the moment, so use one gesture instance * and change the target to the currently visible item.

* * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html */ public function get target() : Object { return _targetAdapter ? _targetAdapter.target : null; } public function set target( value : Object ) : void { var target : Object = this.target; if( target == value ) return; uninstallTarget( target ); _targetAdapter = value ? Gestouch.createGestureTargetAdapter( value ) : null; installTarget( value ); } /** @private */ protected var _enabled : Boolean = true; /** * @default true */ public function get enabled() : Boolean { return _enabled; } public function set enabled( value : Boolean ) : void { if( _enabled == value ) return; _enabled = value; if( !_enabled ) { if( state == GestureState.POSSIBLE ) { setState( GestureState.FAILED ); } else if( state == GestureState.BEGAN || state == GestureState.CHANGED ) { setState( GestureState.CANCELLED ); } } } protected var _state : GestureState = GestureState.POSSIBLE; public function get state() : GestureState { return _state; } protected var _touchesCount : uint = 0; /** * Amount of currently tracked touch points. * * @see #_touches */ public function get touchesCount() : uint { return _touchesCount; } protected var _location : Point = new Point(); /** * Virtual central touch point among all tracking touch points (geometrical center). */ public function get location() : Point { return _location.clone(); } public function Gesture( target : Object = null ) { super(); preinit(); this.target = target; } [Abstract] /** * Reflects gesture class (for better perfomance). * *

NB! This is abstract method and must be overridden.

* * @see performance optimization tips */ public function reflect() : Class { throw Error( "reflect() is abstract method and must be overridden." ); } public function isTrackingTouch( touchID : uint ) : Boolean { return (_touchesMap[ touchID ] != undefined); } /** * Cancels current tracking (interaction) cycle. * *

Could be useful to "stop" gesture for the current interaction cycle.

*/ public function reset() : void { if( idle ) return;// Do nothing as we are idle and there is nothing to reset const state : GestureState = this.state;// caching getter _location.x = 0; _location.y = 0; _touchesMap = {}; _touchesCount = 0; _idle = true; for( var key : * in _gesturesToFail ) { var gestureToFail : Gesture = key as Gesture; gestureToFail.removeEventListener( GestureEvent.GESTURE_STATE_CHANGE, gestureToFail_stateChangeHandler ); } _pendingRecognizedState = null; if( state == GestureState.POSSIBLE ) { // manual reset() call. Set to FAILED to keep our State Machine clean and stable setState( GestureState.FAILED ); } else if( state == GestureState.BEGAN || state == GestureState.CHANGED ) { // manual reset() call. Set to CANCELLED to keep our State Machine clean and stable setState( GestureState.CANCELLED ); } else { // reset from GesturesManager after reaching one of the 4 final states: // (state == GestureState.RECOGNIZED || // state == GestureState.ENDED || // state == GestureState.FAILED || // state == GestureState.CANCELLED) setState( GestureState.POSSIBLE ); } } /** * Remove gesture and prepare it for GC. * *

The gesture is not able to use after calling this method.

*/ public function dispose() : void { // TODO reset(); target = null; gestureShouldReceiveTouchCallback = null; gestureShouldBeginCallback = null; gesturesShouldRecognizeSimultaneouslyCallback = null; _gesturesToFail = null; } // -------------------------------------------------------------------------- // // Public methods // // -------------------------------------------------------------------------- /** * NB! Current implementation is highly experimental! See examples for more info. */ public function requireGestureToFail( gesture : Gesture ) : void { // TODO if( !gesture ) { throw new ArgumentError(); } _gesturesToFail[ gesture ] = true; } /** * First method, called in constructor. */ protected function preinit() : void { } /** * Called internally when changing the target. * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html */ protected function installTarget( target : Object ) : void { if( target ) { _gesturesManager.addGesture( this ); } } /** * Called internally when changing the target. * *

You should remove all listeners from target here.

* * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html */ protected function uninstallTarget( target : Object ) : void { if( target ) { _gesturesManager.removeGesture( this ); } } /** * TODO: clarify usage. For now it's supported to call this method in onTouchBegin with return. */ protected function ignoreTouch( touch : Touch ) : void { if( touch.id in _touchesMap ) { delete _touchesMap[ touch.id ]; _touchesCount--; } } protected function failOrIgnoreTouch( touch : Touch ) : void { if( state == GestureState.POSSIBLE ) { setState( GestureState.FAILED ); } else { ignoreTouch( touch ); } } [Abstract] /** *

NB! This is abstract method and must be overridden.

*/ protected function onTouchBegin( touch : Touch ) : void { } // -------------------------------------------------------------------------- // // Protected methods // // -------------------------------------------------------------------------- [Abstract] /** *

NB! This is abstract method and must be overridden.

*/ protected function onTouchMove( touch : Touch ) : void { } [Abstract] /** *

NB! This is abstract method and must be overridden.

*/ protected function onTouchEnd( touch : Touch ) : void { } /** * */ protected function onTouchCancel( touch : Touch ) : void { } protected function setState( newState : GestureState ) : Boolean { if( _state == newState && _state == GestureState.CHANGED ) { // shortcut for better performance if( hasEventListener( GestureEvent.GESTURE_STATE_CHANGE ) ) { dispatchEvent( new GestureEvent( GestureEvent.GESTURE_STATE_CHANGE, _state, _state ) ); } if( hasEventListener( GestureEvent.GESTURE_CHANGED ) ) { dispatchEvent( new GestureEvent( GestureEvent.GESTURE_CHANGED, _state, _state ) ); } resetNotificationProperties(); return true; } if( !_state.canTransitionTo( newState ) ) { throw new IllegalOperationError( "You cannot change from state " + _state + " to state " + newState + "." ); } if( newState != GestureState.POSSIBLE ) { // in case instantly switch state in touchBeganHandler() _idle = false; } if( newState == GestureState.BEGAN || newState == GestureState.RECOGNIZED ) { var gestureToFail : Gesture; var key : *; // first we check if other required-to-fail gestures recognized // TODO: is this really necessary? using "requireGestureToFail" API assume that // required-to-fail gesture always recognizes AFTER this one. for( key in _gesturesToFail ) { gestureToFail = key as Gesture; if( !gestureToFail.idle && gestureToFail.state != GestureState.POSSIBLE && gestureToFail.state != GestureState.FAILED ) { // Looks like other gesture won't fail, // which means the required condition will not happen, so we must fail setState( GestureState.FAILED ); return false; } } // then we check if other required-to-fail gestures are actually tracked (not IDLE) // and not still not recognized (e.g. POSSIBLE state) for( key in _gesturesToFail ) { gestureToFail = key as Gesture; if( gestureToFail.state == GestureState.POSSIBLE ) { // Other gesture might fail soon, so we postpone state change _pendingRecognizedState = newState; for( key in _gesturesToFail ) { gestureToFail = key as Gesture; gestureToFail.addEventListener( GestureEvent.GESTURE_STATE_CHANGE, gestureToFail_stateChangeHandler, false, 0, true ); } return false; } // else if gesture is in IDLE state it means it doesn't track anything, // so we simply ignore it as it doesn't seem like conflict from this perspective // (perspective of using "requireGestureToFail" API) } if( gestureShouldBeginCallback != null && !gestureShouldBeginCallback( this ) ) { setState( GestureState.FAILED ); return false; } } var oldState : GestureState = _state; _state = newState; if( _state.isEndState ) { _gesturesManager.scheduleGestureStateReset( this ); } // TODO: what if RTE happens in event handlers? if( hasEventListener( GestureEvent.GESTURE_STATE_CHANGE ) ) { dispatchEvent( new GestureEvent( GestureEvent.GESTURE_STATE_CHANGE, _state, oldState ) ); } if( hasEventListener( _state.toEventType() ) ) { dispatchEvent( new GestureEvent( _state.toEventType(), _state, oldState ) ); } resetNotificationProperties(); if( _state == GestureState.BEGAN || _state == GestureState.RECOGNIZED ) { _gesturesManager.onGestureRecognized( this ); } return true; } protected function updateCentralPoint() : void { var touchLocation : Point; var x : Number = 0; var y : Number = 0; for( var touchID : String in _touchesMap ) { touchLocation = (_touchesMap[ int( touchID ) ] as Touch).location; x += touchLocation.x; y += touchLocation.y; } _centralPoint.x = x / _touchesCount; _centralPoint.y = y / _touchesCount; } protected function updateLocation() : void { updateCentralPoint(); _location.x = _centralPoint.x; _location.y = _centralPoint.y; } protected function resetNotificationProperties() : void { } protected function gestureToFail_stateChangeHandler( event : GestureEvent ) : void { if( !_pendingRecognizedState || state != GestureState.POSSIBLE ) return; if( event.newState == GestureState.FAILED ) { for( var key : * in _gesturesToFail ) { var gestureToFail : Gesture = key as Gesture; if( gestureToFail.state == GestureState.POSSIBLE ) { // we're still waiting for some gesture to fail return; } } // at this point all gestures-to-fail are either in IDLE or in FAILED states setState( _pendingRecognizedState ); } else if( event.newState != GestureState.POSSIBLE ) { // TODO: need to re-think this over setState( GestureState.FAILED ); } } protected const _gesturesManager : GesturesManager = Gestouch.gesturesManager; /** * */ gestouch_internal function get targetAdapter() : IGestureTargetAdapter { return _targetAdapter; } protected var _idle : Boolean = true; gestouch_internal function get idle() : Boolean { return _idle; } gestouch_internal function canBePreventedByGesture( preventingGesture : Gesture ) : Boolean { return true; } gestouch_internal function canPreventGesture( preventedGesture : Gesture ) : Boolean { return true; } // -------------------------------------------------------------------------- // // Event handlers // // -------------------------------------------------------------------------- gestouch_internal function setState_internal( state : GestureState ) : void { setState( state ); } gestouch_internal function touchBeginHandler( touch : Touch ) : void { _touchesMap[ touch.id ] = touch; _touchesCount++; onTouchBegin( touch ); if( _touchesCount == 1 && state == GestureState.POSSIBLE ) { _idle = false; } } gestouch_internal function touchMoveHandler( touch : Touch ) : void { _touchesMap[ touch.id ] = touch; onTouchMove( touch ); } gestouch_internal function touchEndHandler( touch : Touch ) : void { delete _touchesMap[ touch.id ]; _touchesCount--; onTouchEnd( touch ); } gestouch_internal function touchCancelHandler( touch : Touch ) : void { delete _touchesMap[ touch.id ]; _touchesCount--; onTouchCancel( touch ); if( !state.isEndState ) { if( state == GestureState.BEGAN || state == GestureState.CHANGED ) { setState( GestureState.CANCELLED ); } else { setState( GestureState.FAILED ); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy