diff --git a/.settings/com.powerflasher.fdt.classpath b/.settings/com.powerflasher.fdt.classpath index 75e92cc..9292307 100644 --- a/.settings/com.powerflasher.fdt.classpath +++ b/.settings/com.powerflasher.fdt.classpath @@ -1,5 +1,6 @@ + libs src frameworks/libs/air/airglobal.swc frameworks/libs/mobile/mobilecomponents.swc @@ -10,4 +11,5 @@ frameworks/libs/air/servicemonitor.swc frameworks/themes/Mobile/mobile.swc frameworks/libs/mx/mx.swc + libs/starling.swc diff --git a/README.textile b/README.textile index 9076e0a..70152a2 100644 --- a/README.textile +++ b/README.textile @@ -1,6 +1,6 @@ -h1. Gestouch: NUI gestures detection framework for mouse, touch and multitouch AS3 development. +h1. Gestouch: multitouch gesture recognition library for Flash (ActionScript) development. -Gestouch is a ActionScript library/framework that helps you to deal with single- and multitouch gestures for building better NUI (Natural User Interface). +Gestouch is a ActionScript (AS3) library that helps you to deal with single- and multitouch gestures for building better NUI (Natural User Interface). h3. Why? There's already gesture support in Flash/AIR! @@ -13,9 +13,9 @@ _Upd:_ With "native way" you also won't get anything out of Stage3D and of custo h3. What Gestouch does in short? Well basically there's 3 distinctive tasks to solve. -# To provide various input. It can be standard MouseEvents, TouchEvents or more complex things like custom input via TUIO protocol for your hand-made installation. So what we get here is Touches (touch points). -# To recognize gesture out of touch points. Each type of Gesture has it's own inner algorithms that ... -# To manage gestures relations. Because they may "overlap" and once some has been recognized probably we don't want other to do so. +# To provide various input. It can be native MouseEvents, TouchEvents or more complex things like custom input via TUIO protocol for your hand-made installation. So what we get here is Touches (touch points). +# To recognize gesture analyzing touches. Each type of Gesture has it's own inner algorithms that ... +# To manage gestures conflicts. As multiple gestures may be recognized simultaneously, we need to be able to control whether it's allowed or some of them should not be recognized (fail). Gestouch solves these 3 tasks. I was hardly inspired by Apple team, how they solved this (quite recently to my big surprise! I thought they had it right from the beginning) in they Cocoa-touch UIKit framework. Gestouch is very similar in many ways. But I wouldn't call it "direct port" because 1) the whole architecture was implemented based just on conference videos and user documentation 2) flash platform is a different platform with own specialization, needs, etc. @@ -23,12 +23,11 @@ So I want Gestouch to go far beyond that. Features: * Pretty neat architecture! Very similar to Apple's UIGestureRecognizers (Cocoa-Touch UIKit) +* Works with any display list hierarchy structures: native DisplayList (pure AS3/Flex/your UI framework), Starling or ND2D (Stage3D) and 3D libs... * Doesn't require any additional software (may use runtime's build-in touch support) * Works across all platforms (where Flash Player or AIR run of course) in exactly same way -* Doesn't break your DisplayList architecture (could be easily used for Flex development) * Extendable. You can write your own application-specific gestures * Open-source and free -* *_+Planning to make it work with Stage3D. Hello Starling!+_* @@ -69,9 +68,40 @@ private function onFreeTransform(event:TransformGestureEvent):void +h3. Advanced usage: Starling, ... + +Recent changes made it possible to work with "Starling":http://www.starling-framework.org display list objects as well as any other display list hierarchical structures, e.g. other Stage3D frameworks that have display objects hierarchy like "ND2D":https://github.com/nulldesign/nd2d or even 3D libraries. +In order to use Gestouch with Starling do the following: +
starling = new Starling(MyStarlingRootClass, stage);
+/* setup & start your Starling instance here */
+
+// Gestouch initialization step 1 of 3:
+// Initialize native (default) input adapter. Needed for non-DisplayList usage.
+Gestouch.inputAdapter ||= new NativeInputAdapter(stage);
+
+// Gestouch initialization step 2 of 3:
+// Register instance of StarlingDisplayListAdapter to be used for objects of type starling.display.DisplayObject.
+// What it does: helps to build hierarchy (chain of parents) for any Starling display object and
+// acts as a adapter for gesture target to provide strong-typed access to methods like globalToLocal() and contains().
+Gestouch.addDisplayListAdapter(starling.display.DisplayObject, new StarlingDisplayListAdapter());
+
+// Gestouch initialization step 3 of 3:
+// Initialize and register StarlingTouchHitTester.
+// What it does: finds appropriate target for the new touches (uses Starling Stage#hitTest() method)
+// What does "-1" mean: priority for this hit-tester. Since Stage3D layer sits behind native DisplayList
+// we give it lower priority in the sense of interactivity.
+Gestouch.addTouchHitTester(new StarlingTouchHitTester(starling), -1);
+// NB! Use Gestouch#removeTouchHitTester() method if you manage multiple Starling instances during
+// your application lifetime.
+
+ +Now you can register gesture in familiar, exactly same way: +
var tap:TapGesture = new TapGesture(starlingSprite);
+ + + h3. Roadmap, TODOs -* *Stage3D support.* Hello Starling! Must move away from target as InteractiveObject to some abstract adapters. * "Massive gestures" & Clusters. For bigger form-factor multitouch usage, when gestures must be a bit less about separate fingers but rather touch clusters (massive multitouch) * -Simulator (for testing multitouch gestures without special devices)- With new architecture it must be relatively easy to create SimulatorInputAdapter * Chained gestures concept? To transfer touches from one gesture to another. Example: press/hold for circular menu, then drag it around. diff --git a/build.xml b/build.xml index a094265..63664da 100644 --- a/build.xml +++ b/build.xml @@ -21,6 +21,8 @@ + + @@ -44,6 +46,7 @@ + diff --git a/libs/starling.swc b/libs/starling.swc new file mode 100644 index 0000000..6fc810d Binary files /dev/null and b/libs/starling.swc differ diff --git a/src/org/gestouch/core/Gestouch.as b/src/org/gestouch/core/Gestouch.as new file mode 100644 index 0000000..718f811 --- /dev/null +++ b/src/org/gestouch/core/Gestouch.as @@ -0,0 +1,125 @@ +package org.gestouch.core +{ + import org.gestouch.extensions.native.NativeDisplayListAdapter; + import org.gestouch.extensions.native.NativeTouchHitTester; + + import flash.display.DisplayObject; + import flash.utils.Dictionary; + import flash.utils.getQualifiedClassName; + + + /** + * @author Pavel fljot + */ + final public class Gestouch + { + private static const _displayListAdaptersMap:Dictionary = new Dictionary(); + + { + initClass(); + } + + + /** @private */ + private static var _inputAdapter:IInputAdapter; + + /** + * + */ + public static function get inputAdapter():IInputAdapter + { + return _inputAdapter; + } + public static function set inputAdapter(value:IInputAdapter):void + { + if (_inputAdapter == value) + return; + + _inputAdapter = value; + if (inputAdapter) + { + inputAdapter.touchesManager = touchesManager; + inputAdapter.init(); + } + } + + + private static var _touchesManager:TouchesManager; + /** + * + */ + public static function get touchesManager():TouchesManager + { + return _touchesManager ||= new TouchesManager(gesturesManager); + } + + + private static var _gesturesManager:GesturesManager; + public static function get gesturesManager():GesturesManager + { + return _gesturesManager ||= new GesturesManager(); + } + + + public static function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void + { + if (!targetClass || !adapter) + { + throw new Error("Argument error: both arguments required."); + } + + _displayListAdaptersMap[targetClass] = adapter; + } + + + public static function addTouchHitTester(hitTester:ITouchHitTester, priority:int = 0):void + { + touchesManager.gestouch_internal::addTouchHitTester(hitTester, priority); + } + + + public static function removeTouchHitTester(hitTester:ITouchHitTester):void + { + touchesManager.gestouch_internal::removeInputAdapter(hitTester); + } + + +// public static function getTouches(target:Object = null):Array +// { +// return touchesManager.getTouches(target); +// } + + gestouch_internal static function createGestureTargetAdapter(target:Object):IDisplayListAdapter + { + const adapter:IDisplayListAdapter = Gestouch.gestouch_internal::getDisplayListAdapter(target); + if (adapter) + { + return new (adapter.reflect())(target); + } + + throw new Error("Cannot create adapter for target " + target + " of type " + getQualifiedClassName(target) + "."); + } + + + gestouch_internal static function getDisplayListAdapter(object:Object):IDisplayListAdapter + { + for (var key:Object in _displayListAdaptersMap) + { + var targetClass:Class = key as Class; + if (object is targetClass) + { + return _displayListAdaptersMap[key] as IDisplayListAdapter; + } + } + + return null; + } + + + private static function initClass():void + { + addTouchHitTester(new NativeTouchHitTester()); + addDisplayListAdapter(DisplayObject, new NativeDisplayListAdapter()); + } + } +} diff --git a/src/org/gestouch/core/GestureState.as b/src/org/gestouch/core/GestureState.as index 07c9e75..eca6adc 100644 --- a/src/org/gestouch/core/GestureState.as +++ b/src/org/gestouch/core/GestureState.as @@ -1,17 +1,100 @@ package org.gestouch.core { + import flash.errors.IllegalOperationError; + + /** * @author Pavel fljot */ public class GestureState { - public static const IDLE:uint = 1 << 0;//1 - public static const POSSIBLE:uint = 1 << 1;//2 - public static const RECOGNIZED:uint = 1 << 2;//4 - public static const BEGAN:uint = 1 << 3;//8 - public static const CHANGED:uint = 1 << 4;//16 - public static const ENDED:uint = 1 << 5;//32 - public static const CANCELLED:uint = 1 << 6;//64 - public static const FAILED:uint = 1 << 7;//128 + public static const IDLE:GestureState = new GestureState(1 << 0, "IDLE"); + public static const POSSIBLE:GestureState = new GestureState(1 << 1, "POSSIBLE"); + public static const RECOGNIZED:GestureState = new GestureState(1 << 2, "RECOGNIZED"); + public static const BEGAN:GestureState = new GestureState(1 << 3, "BEGAN"); + public static const CHANGED:GestureState = new GestureState(1 << 4, "CHANGED"); + public static const ENDED:GestureState = new GestureState(1 << 5, "ENDED"); + public static const CANCELLED:GestureState = new GestureState(1 << 6, "CANCELLED"); + public static const FAILED:GestureState = new GestureState(1 << 7, "FAILED"); + + private static const endStatesBitMask:uint = + GestureState.CANCELLED.toUint() | + GestureState.RECOGNIZED.toUint() | + GestureState.ENDED.toUint() | + GestureState.FAILED.toUint(); + + private static var allStatesInitialized:Boolean; + + + private var value:uint; + private var name:String; + private var validTransitionsBitMask:uint; + + { + _initClass(); + } + + + public function GestureState(value:uint, name:String) + { + if (allStatesInitialized) + { + throw new IllegalOperationError("You cannot create gesture states." + + "Use predefined constats like GestureState.RECOGNIZED"); + } + + this.value = value; + this.name = name; + } + + + private static function _initClass():void + { + IDLE.setValidNextStates(POSSIBLE); + POSSIBLE.setValidNextStates(RECOGNIZED, BEGAN, FAILED); + RECOGNIZED.setValidNextStates(IDLE); + BEGAN.setValidNextStates(CHANGED, ENDED, CANCELLED); + CHANGED.setValidNextStates(CHANGED, ENDED, CANCELLED); + ENDED.setValidNextStates(IDLE); + FAILED.setValidNextStates(IDLE); + CANCELLED.setValidNextStates(IDLE); + + allStatesInitialized = true; + } + + + public function toString():String + { + return "GestureState." + name; + } + + + public function toUint():uint + { + return value; + } + + + private function setValidNextStates(...states):void + { + var mask:uint; + for each (var state:GestureState in states) + { + mask = mask | state.value; + } + validTransitionsBitMask = mask; + } + + + gestouch_internal function canTransitionTo(state:GestureState):Boolean + { + return (validTransitionsBitMask & state.value) > 0; + } + + + gestouch_internal function get isEndState():Boolean + { + return (endStatesBitMask & value) > 0; + } } -} \ No newline at end of file +} diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 8cae7a2..1820be0 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -1,117 +1,37 @@ package org.gestouch.core { + import flash.errors.IllegalOperationError; import org.gestouch.gestures.Gesture; - import org.gestouch.input.MouseInputAdapter; - import org.gestouch.input.TouchInputAdapter; + import org.gestouch.input.NativeInputAdapter; import flash.display.DisplayObject; - import flash.display.DisplayObjectContainer; - import flash.display.InteractiveObject; import flash.display.Shape; import flash.display.Stage; import flash.events.Event; - import flash.ui.Multitouch; + import flash.events.IEventDispatcher; import flash.utils.Dictionary; + import flash.utils.getQualifiedClassName; /** * @author Pavel fljot */ - public class GesturesManager implements IGesturesManager + public class GesturesManager { - public static var initDefaultInputAdapter:Boolean = true; - private static var _instance:IGesturesManager; - private static var _allowInstantiation:Boolean; - - protected const _touchesManager:ITouchesManager = TouchesManager.getInstance(); protected const _frameTickerShape:Shape = new Shape(); protected var _inputAdapters:Vector. = new Vector.(); - protected var _stage:Stage; protected var _gesturesMap:Dictionary = new Dictionary(true); - protected var _gesturesForTouchMap:Array = []; + protected var _gesturesForTouchMap:Dictionary = new Dictionary(); protected var _gesturesForTargetMap:Dictionary = new Dictionary(true); - protected var _dirtyGestures:Vector. = new Vector.(); - protected var _dirtyGesturesLength:uint = 0; + protected var _dirtyGesturesCount:uint = 0; protected var _dirtyGesturesMap:Dictionary = new Dictionary(true); + protected var _stage:Stage; + + use namespace gestouch_internal; public function GesturesManager() { - if (Object(this).constructor == GesturesManager && !_allowInstantiation) - { - throw new Error("Do not instantiate GesturesManager directly."); - } - } - - - public function get inputAdapters():Vector. - { - return _inputAdapters.concat(); - } - - - public static function setImplementation(value:IGesturesManager):void - { - if (!value) - { - throw new ArgumentError("value cannot be null."); - } - if (_instance) - { - throw new Error("Instance of GesturesManager is already created. If you want to have own implementation of single GesturesManager instace, you should set it earlier."); - } - _instance = value; - } - - - public static function getInstance():IGesturesManager - { - if (!_instance) - { - _allowInstantiation = true; - _instance = new GesturesManager(); - _allowInstantiation = false; - } - - return _instance; - } - - - - public function addInputAdapter(inputAdapter:IInputAdapter):void - { - if (!inputAdapter) - { - throw new Error("Input adapter must be non null."); - } - - if (_inputAdapters.indexOf(inputAdapter) > -1) - return;//TODO: throw Error or ignore? - - _inputAdapters.push(inputAdapter); - inputAdapter.touchesManager = _touchesManager; - inputAdapter.gesturesManager = this; - inputAdapter.init(); - } - - - public function removeInputAdapter(inputAdapter:IInputAdapter, dispose:Boolean = true):void - { - if (!inputAdapter) - { - throw new Error("Input adapter must be non null."); - } - var index:int = _inputAdapters.indexOf(inputAdapter); - if (index == -1) - { - throw new Error("This input manager is not registered."); - } - - _inputAdapters.splice(index, 1); - if (dispose) - { - inputAdapter.dispose(); - } } @@ -123,29 +43,21 @@ package org.gestouch.core // //-------------------------------------------------------------------------- - protected function installStage(stage:Stage):void + protected function onStageAvailable(stage:Stage):void { _stage = stage; - if (Multitouch.supportsTouchEvents) - { - addInputAdapter(new TouchInputAdapter(stage)); - } - else - { - addInputAdapter(new MouseInputAdapter(stage)); - } + Gestouch.inputAdapter ||= new NativeInputAdapter(stage); } protected function resetDirtyGestures():void { - for each (var gesture:Gesture in _dirtyGestures) + for (var gesture:Object in _dirtyGesturesMap) { - gesture.reset(); + (gesture as Gesture).reset(); } - _dirtyGestures.length = 0; - _dirtyGesturesLength = 0; + _dirtyGesturesCount = 0; _dirtyGesturesMap = new Dictionary(true); _frameTickerShape.removeEventListener(Event.ENTER_FRAME, enterFrameHandler); } @@ -157,30 +69,43 @@ package org.gestouch.core { throw new ArgumentError("Argument 'gesture' must be not null."); } - if (_gesturesMap[gesture]) + + const target:Object = gesture.target; + if (!target) { - throw new Error("This gesture is already registered.. something wrong."); + throw new IllegalOperationError("Gesture must have target."); } - var target:Object = gesture.target; var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; - if (!targetGestures) + if (targetGestures) { - targetGestures = _gesturesForTargetMap[gesture.target] = new Vector.(); + if (targetGestures.indexOf(gesture) == -1) + { + targetGestures.push(gesture); + } } - targetGestures.push(gesture); + else + { + targetGestures = _gesturesForTargetMap[target] = new Vector.(); + targetGestures[0] = gesture; + } + _gesturesMap[gesture] = true; - if (GesturesManager.initDefaultInputAdapter) + if (!_stage) { - if (!_stage && gesture.target.stage) + var targetAsDO:DisplayObject = target as DisplayObject; + if (targetAsDO) { - installStage(gesture.target.stage); - } - else - { - gesture.target.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + if (targetAsDO.stage) + { + onStageAvailable(targetAsDO.stage); + } + else + { + targetAsDO.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + } } } } @@ -194,21 +119,28 @@ package org.gestouch.core } - var target:InteractiveObject = gesture.target; - var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; - if (targetGestures.length > 1) + var target:Object = gesture.target; + // check for target because it could be already GC-ed (since target reference is weak) + if (target) { - targetGestures.splice(targetGestures.indexOf(gesture), 1); - } - else - { - delete _gesturesForTargetMap[target]; - target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; + if (targetGestures.length > 1) + { + targetGestures.splice(targetGestures.indexOf(gesture), 1); + } + else + { + delete _gesturesForTargetMap[target]; + if (target is IEventDispatcher) + { + (target as IEventDispatcher).removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + } + } } delete _gesturesMap[gesture]; - //TODO: decide about gesture state and _dirtyGestures + gesture.reset(); } @@ -216,8 +148,8 @@ package org.gestouch.core { if (!_dirtyGesturesMap[gesture]) { - _dirtyGestures.push(gesture); - _dirtyGesturesLength++; + _dirtyGesturesMap[gesture] = true; + _dirtyGesturesCount++; _frameTickerShape.addEventListener(Event.ENTER_FRAME, enterFrameHandler); } } @@ -225,11 +157,12 @@ package org.gestouch.core gestouch_internal function onGestureRecognized(gesture:Gesture):void { + const target:Object = gesture.target; + for (var key:Object in _gesturesMap) { var otherGesture:Gesture = key as Gesture; - var target:DisplayObject = gesture.target; - var otherTarget:DisplayObject = otherGesture.target; + var otherTarget:Object = otherGesture.target; // conditions for otherGesture "own properties" if (otherGesture != gesture && @@ -237,10 +170,10 @@ package org.gestouch.core otherGesture.enabled && otherGesture.state == GestureState.POSSIBLE) { - // conditions for otherGesture target if (otherTarget == target || - (target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(otherTarget)) || - (otherTarget is DisplayObjectContainer && (otherTarget as DisplayObjectContainer).contains(target))) + gesture.targetAdapter.contains(otherTarget) || + otherGesture.targetAdapter.contains(target) + ) { var gestureDelegate:IGestureDelegate = gesture.delegate; var otherGestureDelegate:IGestureDelegate = otherGesture.delegate; @@ -250,9 +183,9 @@ package org.gestouch.core (!gestureDelegate || !gestureDelegate.gesturesShouldRecognizeSimultaneously(gesture, otherGesture)) && (!otherGestureDelegate || !otherGestureDelegate.gesturesShouldRecognizeSimultaneously(otherGesture, gesture))) { - otherGesture.gestouch_internal::setState_internal(GestureState.FAILED); + otherGesture.setState_internal(GestureState.FAILED); } - } + } } } } @@ -260,32 +193,46 @@ package org.gestouch.core gestouch_internal function onTouchBegin(touch:Touch):void { - if (_dirtyGesturesLength > 0) - { - resetDirtyGestures(); - } - var gesture:Gesture; var i:uint; - // This vector will contain active gestures for specific touch (ID) during all touch session. - var gesturesForTouch:Vector. = _gesturesForTouchMap[touch.id] as Vector.; + // This vector will contain active gestures for specific touch during all touch session. + var gesturesForTouch:Vector. = _gesturesForTouchMap[touch] as Vector.; if (!gesturesForTouch) { - gesturesForTouch = new Vector.(); - _gesturesForTouchMap[touch.id] = gesturesForTouch; + gesturesForTouch = new Vector.(); + _gesturesForTouchMap[touch] = gesturesForTouch; } else { + // touch object may be pooled in the future gesturesForTouch.length = 0; - } + } + var target:Object = touch.target; + const displayListAdapter:IDisplayListAdapter = Gestouch.gestouch_internal::getDisplayListAdapter(target); + if (!displayListAdapter) + { + throw new Error("Display list adapter not found for target of type '" + getQualifiedClassName(target) + "'."); + } + const hierarchy:Vector. = displayListAdapter.getHierarchy(target); + const hierarchyLength:uint = hierarchy.length; + if (hierarchyLength == 0) + { + throw new Error("No hierarchy build for target '" + target +"'. Something is wrong with that IDisplayListAdapter."); + } + if (_stage && !(hierarchy[hierarchyLength - 1] is Stage)) + { + // Looks like some non-native (non DisplayList) hierarchy + // but we must always handle gestures with Stage target + // since Stage is anyway the top-most parent + hierarchy[hierarchyLength] = _stage; + } // Create a sorted(!) list of gestures which are interested in this touch. // Sorting priority: deeper target has higher priority, recently added gesture has higher priority. - var target:InteractiveObject = touch.target; var gesturesForTarget:Vector.; - while (target) + for each (target in hierarchy) { gesturesForTarget = _gesturesForTargetMap[target] as Vector.; if (gesturesForTarget) @@ -293,7 +240,7 @@ package org.gestouch.core i = gesturesForTarget.length; while (i-- > 0) { - gesture = gesturesForTarget[i] as Gesture; + gesture = gesturesForTarget[i]; if (gesture.enabled && (!gesture.delegate || gesture.delegate.gestureShouldReceiveTouch(gesture, touch))) { @@ -302,8 +249,6 @@ package org.gestouch.core } } } - - target = target.parent; } // Then we populate them with this touch and event. @@ -311,11 +256,11 @@ package org.gestouch.core i = gesturesForTouch.length; while (i-- > 0) { - gesture = gesturesForTouch[i] as Gesture; + gesture = gesturesForTouch[i]; // Check for state because previous (i+1) gesture may already abort current (i) one - if (gesture.state != GestureState.FAILED) + if (!_dirtyGesturesMap[gesture]) { - gesture.gestouch_internal::touchBeginHandler(touch); + gesture.touchBeginHandler(touch); } else { @@ -327,21 +272,16 @@ package org.gestouch.core gestouch_internal function onTouchMove(touch:Touch):void { - if (_dirtyGesturesLength > 0) - { - resetDirtyGestures(); - } - - var gesturesForTouch:Vector. = _gesturesForTouchMap[touch.id] as Vector.; + var gesturesForTouch:Vector. = _gesturesForTouchMap[touch] as Vector.; var gesture:Gesture; - var i:int = gesturesForTouch.length; + var i:uint = gesturesForTouch.length; while (i-- > 0) { - gesture = gesturesForTouch[i] as Gesture; + gesture = gesturesForTouch[i]; - if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) + if (!_dirtyGesturesMap[gesture] && gesture.isTrackingTouch(touch.id)) { - gesture.gestouch_internal::touchMoveHandler(touch); + gesture.touchMoveHandler(touch); } else { @@ -354,29 +294,39 @@ package org.gestouch.core gestouch_internal function onTouchEnd(touch:Touch):void { - if (_dirtyGesturesLength > 0) - { - resetDirtyGestures(); - } - - var gesturesForTouch:Vector. = _gesturesForTouchMap[touch.id] as Vector.; + var gesturesForTouch:Vector. = _gesturesForTouchMap[touch] as Vector.; var gesture:Gesture; - var i:int = gesturesForTouch.length; + var i:uint = gesturesForTouch.length; while (i-- > 0) { - gesture = gesturesForTouch[i] as Gesture; - - if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) - { - gesture.gestouch_internal::touchEndHandler(touch); + gesture = gesturesForTouch[i]; + + if (!_dirtyGesturesMap[gesture] && gesture.isTrackingTouch(touch.id)) + { + gesture.touchEndHandler(touch); } } + + gesturesForTouch.length = 0;// release for GC } gestouch_internal function onTouchCancel(touch:Touch):void { - //TODO + var gesturesForTouch:Vector. = _gesturesForTouchMap[touch] as Vector.; + var gesture:Gesture; + var i:uint = gesturesForTouch.length; + while (i-- > 0) + { + gesture = gesturesForTouch[i]; + + if (!_dirtyGesturesMap[gesture] && gesture.isTrackingTouch(touch.id)) + { + gesture.touchCancelHandler(touch); + } + } + + gesturesForTouch.length = 0;// release for GC } @@ -386,15 +336,15 @@ package org.gestouch.core // // Event handlers // - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- protected function gestureTarget_addedToStageHandler(event:Event):void { var target:DisplayObject = event.target as DisplayObject; target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); - if (!_stage && GesturesManager.initDefaultInputAdapter) + if (!_stage) { - installStage(target.stage); + onStageAvailable(target.stage); } } diff --git a/src/org/gestouch/core/IDisplayListAdapter.as b/src/org/gestouch/core/IDisplayListAdapter.as new file mode 100644 index 0000000..d5f69e2 --- /dev/null +++ b/src/org/gestouch/core/IDisplayListAdapter.as @@ -0,0 +1,12 @@ +package org.gestouch.core +{ + /** + * @author Pavel fljot + */ + public interface IDisplayListAdapter extends IGestureTargetAdapter + { + function getHierarchy(target:Object):Vector.; + + function reflect():Class; + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/IGestureTargetAdapter.as b/src/org/gestouch/core/IGestureTargetAdapter.as new file mode 100644 index 0000000..fdf70e5 --- /dev/null +++ b/src/org/gestouch/core/IGestureTargetAdapter.as @@ -0,0 +1,15 @@ +package org.gestouch.core +{ + import flash.geom.Point; + /** + * @author Pavel fljot + */ + public interface IGestureTargetAdapter + { + function get target():Object; + + function globalToLocal(point:Point):Point; + + function contains(object:Object):Boolean; + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/IGesturesManager.as b/src/org/gestouch/core/IGesturesManager.as deleted file mode 100644 index 297f39a..0000000 --- a/src/org/gestouch/core/IGesturesManager.as +++ /dev/null @@ -1,17 +0,0 @@ -package org.gestouch.core -{ - /** - * The class that implements this interface must also - * implement next methods under gestouch_internal namespace: - * - * function addGesture(gesture:Gesture):void; - * function removeGesture(gesture:Gesture):void; - * function scheduleGestureStateReset(gesture:Gesture):void; - * function onGestureRecognized(gesture:Gesture):void; - */ - public interface IGesturesManager - { - function addInputAdapter(inputAdapter:IInputAdapter):void; - function removeInputAdapter(inputAdapter:IInputAdapter, dispose:Boolean = true):void; - } -} \ No newline at end of file diff --git a/src/org/gestouch/core/IInputAdapter.as b/src/org/gestouch/core/IInputAdapter.as index 337ff0e..7d42732 100644 --- a/src/org/gestouch/core/IInputAdapter.as +++ b/src/org/gestouch/core/IInputAdapter.as @@ -5,10 +5,14 @@ package org.gestouch.core */ public interface IInputAdapter { - function set touchesManager(value:ITouchesManager):void; - function set gesturesManager(value:IGesturesManager):void; + /** + * @private + */ + function set touchesManager(value:TouchesManager):void; + /** + * Called when input adapter is set. + */ function init():void; - function dispose():void; } -} \ No newline at end of file +} diff --git a/src/org/gestouch/core/ITouchHitTester.as b/src/org/gestouch/core/ITouchHitTester.as new file mode 100644 index 0000000..b56cbe4 --- /dev/null +++ b/src/org/gestouch/core/ITouchHitTester.as @@ -0,0 +1,14 @@ +package org.gestouch.core +{ + import flash.display.InteractiveObject; + import flash.geom.Point; + + + /** + * @author Pavel fljot + */ + public interface ITouchHitTester + { + function hitTest(point:Point, nativeTarget:InteractiveObject):Object; + } +} diff --git a/src/org/gestouch/core/ITouchesManager.as b/src/org/gestouch/core/ITouchesManager.as deleted file mode 100644 index 8ae9639..0000000 --- a/src/org/gestouch/core/ITouchesManager.as +++ /dev/null @@ -1,16 +0,0 @@ -package org.gestouch.core -{ - /** - * @author Pavel fljot - */ - public interface ITouchesManager - { - function get activeTouchesCount():uint; - - function createTouch():Touch; - function addTouch(touch:Touch):Touch; - function removeTouch(touch:Touch):Touch; - function getTouch(touchPointID:int):Touch; - function hasTouch(touchPointID:int):Boolean; - } -} \ No newline at end of file diff --git a/src/org/gestouch/core/Touch.as b/src/org/gestouch/core/Touch.as index 9a707fa..0679792 100644 --- a/src/org/gestouch/core/Touch.as +++ b/src/org/gestouch/core/Touch.as @@ -1,6 +1,5 @@ package org.gestouch.core { - import flash.display.InteractiveObject; import flash.geom.Point; @@ -19,13 +18,15 @@ package org.gestouch.core /** * The original event target for this touch (touch began with). */ - public var target:InteractiveObject; + public var target:Object; public var sizeX:Number; public var sizeY:Number; public var pressure:Number; // public var lastMove:Point; + + use namespace gestouch_internal; public function Touch(id:uint = 0) @@ -39,13 +40,16 @@ package org.gestouch.core { return _location.clone(); } - gestouch_internal function setLocation(value:Point):void + gestouch_internal function setLocation(x:Number, y:Number, time:uint):void { - _location = value; + _location = new Point(x, y); _beginLocation = _location.clone(); _previousLocation = _location.clone(); + + _time = time; + _beginTime = time; } - gestouch_internal function updateLocation(x:Number, y:Number):void + gestouch_internal function updateLocation(x:Number, y:Number, time:uint):void { if (_location) { @@ -53,10 +57,11 @@ package org.gestouch.core _previousLocation.y = _location.y; _location.x = x; _location.y = y; + _time = time; } else { - gestouch_internal::setLocation(new Point(x, y)); + setLocation(x, y, time); } } diff --git a/src/org/gestouch/core/TouchesManager.as b/src/org/gestouch/core/TouchesManager.as index aa42691..fb8b262 100644 --- a/src/org/gestouch/core/TouchesManager.as +++ b/src/org/gestouch/core/TouchesManager.as @@ -1,22 +1,28 @@ package org.gestouch.core { + import flash.display.InteractiveObject; + import flash.display.Stage; + import flash.geom.Point; + import flash.utils.Dictionary; + import flash.utils.getTimer; + + /** * @author Pavel fljot */ - public class TouchesManager implements ITouchesManager + public class TouchesManager { - private static var _instance:ITouchesManager; - private static var _allowInstantiation:Boolean; - + protected var _gesturesManager:GesturesManager; protected var _touchesMap:Object = {}; + protected var _hitTesters:Vector. = new Vector.(); + protected var _hitTesterPrioritiesMap:Dictionary = new Dictionary(true); + + use namespace gestouch_internal; - public function TouchesManager() + public function TouchesManager(gesturesManager:GesturesManager) { - if (Object(this).constructor == TouchesManager && !_allowInstantiation) - { - throw new Error("Do not instantiate TouchesManager directly."); - } + _gesturesManager = gesturesManager; } @@ -27,77 +33,192 @@ package org.gestouch.core } - public static function setImplementation(value:ITouchesManager):void + public function getTouches(target:Object = null):Array { - if (!value) + const touches:Array = []; + if (!target || target is Stage) { - throw new ArgumentError("value cannot be null."); + // return all touches + var i:uint = 0; + for each (var touch:Touch in _touchesMap) + { + touches[i++] = touch; + } } - if (_instance) + else { - throw new Error("Instance of TouchesManager is already created. If you want to have own implementation of single TouchesManager instace, you should set it earlier."); + //TODO } - _instance = value; - } - - - public static function getInstance():ITouchesManager - { - if (!_instance) - { - _allowInstantiation = true; - _instance = new TouchesManager(); - _allowInstantiation = false; - } - - return _instance; + + return touches; } - public function createTouch():Touch + gestouch_internal function addTouchHitTester(touchHitTester:ITouchHitTester, priority:int = 0):void + { + if (!touchHitTester) + { + throw new ArgumentError("Argument must be non null."); + } + + if (_hitTesters.indexOf(touchHitTester) == -1) + { + _hitTesters.push(touchHitTester); + } + + _hitTesterPrioritiesMap[touchHitTester] = priority; + // Sort hit testers using their priorities + _hitTesters.sort(hitTestersSorter); + } + + + gestouch_internal function removeInputAdapter(touchHitTester:ITouchHitTester):void + { + if (!touchHitTester) + { + throw new ArgumentError("Argument must be non null."); + } + + var index:int = _hitTesters.indexOf(touchHitTester); + if (index == -1) + { + throw new Error("This touchHitTester is not registered."); + } + + _hitTesters.splice(index, 1); + delete _hitTesterPrioritiesMap[touchHitTester]; + } + + + gestouch_internal function onTouchBegin(touchID:uint, x:Number, y:Number, nativeTarget:InteractiveObject = null):Boolean + { + if (touchID in _touchesMap) + return false;// touch with specified ID is already registered and being tracked + + const location:Point = new Point(x, y); + + for each (var registeredTouch:Touch in _touchesMap) + { + // Check if touch at the same location exists. + // In case we listen to both TouchEvents and MouseEvents, one of them will come first + // (right now looks like MouseEvent dispatched first, but who know what Adobe will + // do tomorrow). This check helps to filter out the one comes after. + + // NB! According to the tests with some IR multitouch frame and Windows computer + // TouchEvent comes first, but the following MouseEvent has slightly offset location + // (1px both axis). That is why Point#distance() used instead of Point#equals() + + if (Point.distance(registeredTouch.location, location) < 2) + return false; + } + + const touch:Touch = createTouch(); + touch.id = touchID; + + var target:Object; + var altTarget:Object; + for each (var hitTester:ITouchHitTester in _hitTesters) + { + target = hitTester.hitTest(location, nativeTarget); + if (target) + { + if ((target is Stage)) + { + // NB! Target is flash.display::Stage is a special case. If it is true, we want + // to give a try to a lower-priority (Stage3D) hit-testers. + altTarget = target; + continue; + } + else + { + // We found a target. + break; + } + } + } + if (!target && !altTarget) + { + throw new Error("Not touch target found (hit test)." + + "Something is wrong, at least flash.display::Stage should be found." + + "See Gestouch#addTouchHitTester() and Gestouch#inputAdapter."); + } + + touch.target = target || altTarget; + touch.setLocation(x, y, getTimer()); + + _touchesMap[touchID] = touch; + _activeTouchesCount++; + + _gesturesManager.onTouchBegin(touch); + + return true; + } + + + gestouch_internal function onTouchMove(touchID:uint, x:Number, y:Number):void + { + const touch:Touch = _touchesMap[touchID] as Touch; + if (!touch) + return;// touch with specified ID isn't registered + + touch.updateLocation(x, y, getTimer()); + + _gesturesManager.onTouchMove(touch); + } + + + gestouch_internal function onTouchEnd(touchID:uint, x:Number, y:Number):void + { + const touch:Touch = _touchesMap[touchID] as Touch; + if (!touch) + return;// touch with specified ID isn't registered + + touch.updateLocation(x, y, getTimer()); + + delete _touchesMap[touchID]; + _activeTouchesCount--; + + _gesturesManager.onTouchEnd(touch); + } + + + gestouch_internal function onTouchCancel(touchID:uint, x:Number, y:Number):void + { + const touch:Touch = _touchesMap[touchID] as Touch; + if (!touch) + return;// touch with specified ID isn't registered + + touch.updateLocation(x, y, getTimer()); + + delete _touchesMap[touchID]; + _activeTouchesCount--; + + _gesturesManager.onTouchCancel(touch); + } + + + protected function createTouch():Touch { //TODO: pool return new Touch(); } - public function addTouch(touch:Touch):Touch + /** + * Sorts from higher priority to lower. Items with the same priority keep the order + * of addition, e.g.: + * add(a), add(b), add(c, -1), add(d, 1) will be ordered to + * d, a, b, c + */ + protected function hitTestersSorter(x:ITouchHitTester, y:ITouchHitTester):Number { - if (_touchesMap.hasOwnProperty(touch.id)) - { - throw new Error("Touch with id " + touch.id + " is already registered."); - } + const d:int = int(_hitTesterPrioritiesMap[x]) - int(_hitTesterPrioritiesMap[y]); + if (d > 0) + return -1; + else if (d < 0) + return 1; - _touchesMap[touch.id] = touch; - _activeTouchesCount++; - - return touch; - } - - - public function removeTouch(touch:Touch):Touch - { - if (!_touchesMap.hasOwnProperty(touch.id)) - { - throw new Error("Touch with id " + touch.id + " is not registered."); - } - - delete _touchesMap[touch.id]; - _activeTouchesCount--; - - return touch; - } - - - public function hasTouch(touchPointID:int):Boolean - { - return _touchesMap.hasOwnProperty(touchPointID); - } - - - public function getTouch(touchPointID:int):Touch - { - return _touchesMap[touchPointID] as Touch; + return _hitTesters.indexOf(x) > _hitTesters.indexOf(y) ? 1 : -1; } } -} \ No newline at end of file +} diff --git a/src/org/gestouch/events/GestureEvent.as b/src/org/gestouch/events/GestureEvent.as index 253258d..4dbdd74 100644 --- a/src/org/gestouch/events/GestureEvent.as +++ b/src/org/gestouch/events/GestureEvent.as @@ -1,5 +1,7 @@ package org.gestouch.events { + import org.gestouch.core.GestureState; + import flash.events.Event; @@ -8,7 +10,7 @@ package org.gestouch.events */ public class GestureEvent extends Event { - public var gestureState:uint; + public var gestureState:GestureState; public var stageX:Number; public var stageY:Number; public var localX:Number; @@ -16,7 +18,7 @@ package org.gestouch.events public function GestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:uint = 0, + gestureState:GestureState = null, stageX:Number = 0, stageY:Number = 0, localX:Number = 0, localY:Number = 0) { diff --git a/src/org/gestouch/events/GestureStateEvent.as b/src/org/gestouch/events/GestureStateEvent.as index 3791f4a..29e6cea 100644 --- a/src/org/gestouch/events/GestureStateEvent.as +++ b/src/org/gestouch/events/GestureStateEvent.as @@ -1,5 +1,7 @@ package org.gestouch.events { + import org.gestouch.core.GestureState; + import flash.events.Event; @@ -10,11 +12,11 @@ package org.gestouch.events { public static const STATE_CHANGE:String = "stateChange"; - public var newState:uint; - public var oldState:uint; + public var newState:GestureState; + public var oldState:GestureState; - public function GestureStateEvent(type:String, newState:uint, oldState:uint) + public function GestureStateEvent(type:String, newState:GestureState, oldState:GestureState) { super(type, false, false); diff --git a/src/org/gestouch/events/LongPressGestureEvent.as b/src/org/gestouch/events/LongPressGestureEvent.as index 790b979..e880ddf 100644 --- a/src/org/gestouch/events/LongPressGestureEvent.as +++ b/src/org/gestouch/events/LongPressGestureEvent.as @@ -1,5 +1,7 @@ package org.gestouch.events { + import org.gestouch.core.GestureState; + import flash.events.Event; @@ -12,7 +14,7 @@ package org.gestouch.events public function LongPressGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:uint = 0, + gestureState:GestureState = null, stageX:Number = 0, stageY:Number = 0, localX:Number = 0, localY:Number = 0) { diff --git a/src/org/gestouch/events/PanGestureEvent.as b/src/org/gestouch/events/PanGestureEvent.as index ad6bfba..1802800 100644 --- a/src/org/gestouch/events/PanGestureEvent.as +++ b/src/org/gestouch/events/PanGestureEvent.as @@ -1,5 +1,7 @@ package org.gestouch.events { + import org.gestouch.core.GestureState; + import flash.events.Event; @@ -12,7 +14,7 @@ package org.gestouch.events public function PanGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:uint = 0, + gestureState:GestureState = null, stageX:Number = 0, stageY:Number = 0, localX:Number = 0, localY:Number = 0, offsetX:Number = 0, offsetY:Number = 0) diff --git a/src/org/gestouch/events/RotateGestureEvent.as b/src/org/gestouch/events/RotateGestureEvent.as index 44a2074..2d96c4b 100644 --- a/src/org/gestouch/events/RotateGestureEvent.as +++ b/src/org/gestouch/events/RotateGestureEvent.as @@ -1,5 +1,7 @@ package org.gestouch.events { + import org.gestouch.core.GestureState; + import flash.events.Event; @@ -12,7 +14,7 @@ package org.gestouch.events public function RotateGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:uint = 0, + gestureState:GestureState = null, stageX:Number = 0, stageY:Number = 0, localX:Number = 0, localY:Number = 0, rotation:Number = 0) diff --git a/src/org/gestouch/events/SwipeGestureEvent.as b/src/org/gestouch/events/SwipeGestureEvent.as index d591b01..b3ac3b8 100644 --- a/src/org/gestouch/events/SwipeGestureEvent.as +++ b/src/org/gestouch/events/SwipeGestureEvent.as @@ -1,5 +1,7 @@ package org.gestouch.events { + import org.gestouch.core.GestureState; + import flash.events.Event; @@ -12,7 +14,7 @@ package org.gestouch.events public function SwipeGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:uint = 0, + gestureState:GestureState = null, stageX:Number = 0, stageY:Number = 0, localX:Number = 0, localY:Number = 0, offsetX:Number = 0, offsetY:Number = 0) diff --git a/src/org/gestouch/events/TapGestureEvent.as b/src/org/gestouch/events/TapGestureEvent.as index 6539013..3951357 100644 --- a/src/org/gestouch/events/TapGestureEvent.as +++ b/src/org/gestouch/events/TapGestureEvent.as @@ -1,5 +1,7 @@ package org.gestouch.events { + import org.gestouch.core.GestureState; + import flash.events.Event; @@ -12,7 +14,8 @@ package org.gestouch.events public function TapGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:uint = 0, stageX:Number = 0, stageY:Number = 0, + gestureState:GestureState = null, + stageX:Number = 0, stageY:Number = 0, localX:Number = 0, localY:Number = 0) { super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); diff --git a/src/org/gestouch/events/TransformGestureEvent.as b/src/org/gestouch/events/TransformGestureEvent.as index 1af2a5e..49942e0 100644 --- a/src/org/gestouch/events/TransformGestureEvent.as +++ b/src/org/gestouch/events/TransformGestureEvent.as @@ -1,5 +1,7 @@ package org.gestouch.events { + import org.gestouch.core.GestureState; + import flash.events.Event; @@ -18,7 +20,7 @@ package org.gestouch.events public function TransformGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:uint = 0, + gestureState:GestureState = null, stageX:Number = 0, stageY:Number = 0, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0, diff --git a/src/org/gestouch/events/ZoomGestureEvent.as b/src/org/gestouch/events/ZoomGestureEvent.as index cd2c09b..6e23f16 100644 --- a/src/org/gestouch/events/ZoomGestureEvent.as +++ b/src/org/gestouch/events/ZoomGestureEvent.as @@ -1,5 +1,7 @@ package org.gestouch.events { + import org.gestouch.core.GestureState; + import flash.events.Event; @@ -12,7 +14,7 @@ package org.gestouch.events public function ZoomGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:uint = 0, + gestureState:GestureState = null, stageX:Number = 0, stageY:Number = 0, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0) diff --git a/src/org/gestouch/extensions/native/NativeDisplayListAdapter.as b/src/org/gestouch/extensions/native/NativeDisplayListAdapter.as new file mode 100644 index 0000000..1498c90 --- /dev/null +++ b/src/org/gestouch/extensions/native/NativeDisplayListAdapter.as @@ -0,0 +1,104 @@ +package org.gestouch.extensions.native +{ + import flash.display.DisplayObject; + import flash.display.DisplayObjectContainer; + import flash.display.Stage; + import flash.geom.Point; + import flash.utils.Dictionary; + import org.gestouch.core.IDisplayListAdapter; + + + /** + * @author Pavel fljot + */ + final public class NativeDisplayListAdapter implements IDisplayListAdapter + { + private var _targetWeekStorage:Dictionary; + + + public function NativeDisplayListAdapter(target:DisplayObject = null) + { + if (target) + { + _targetWeekStorage = new Dictionary(true); + _targetWeekStorage[target] = true; + } + } + + + public function get target():Object + { + for (var key:Object in _targetWeekStorage) + { + return key; + } + return null; + } + + + public function globalToLocal(point:Point):Point + { + return (target as DisplayObject).globalToLocal(point); + } + + + public function contains(object:Object):Boolean + { + const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; + if (targetAsDOC is Stage) + { + return true; + } + const objectAsDO:DisplayObject = object as DisplayObject; + if (objectAsDO) + { + return (targetAsDOC && targetAsDOC.contains(objectAsDO)); + } + /** + * There might be case when we use some old "software" 3D library for instace, + * which viewport is added to classic Display List. So native stage, root and some other + * sprites will actually be parents of 3D objects. To ensure all gestures (both for + * native and 3D objects) work correctly with each other contains() method should be + * a bit more sophisticated. + * But as all 3D engines (at least it looks like that) are moving towards Stage3D layer + * this task doesn't seem significant anymore. So I leave this implementation as + * comments in case someone will actually need it. + * Just uncomment this and it should work. + + // else: more complex case. + // object is not of the same type as this.target (flash.display::DisplayObject) + // it might we some 3D library object in it's viewport (which itself is in DisplayList). + // So we perform more general check: + const adapter:IDisplayListAdapter = Gestouch.gestouch_internal::getDisplayListAdapter(object); + if (adapter) + { + return adapter.getHierarchy(object).indexOf(this.target) > -1; + } + */ + + return false; + } + + + public function getHierarchy(genericTarget:Object):Vector. + { + var list:Vector. = new Vector.(); + var i:uint = 0; + var target:DisplayObject = genericTarget as DisplayObject; + while (target) + { + list[i] = target; + target = target.parent; + i++; + } + + return list; + } + + + public function reflect():Class + { + return NativeDisplayListAdapter; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/extensions/native/NativeTouchHitTester.as b/src/org/gestouch/extensions/native/NativeTouchHitTester.as new file mode 100644 index 0000000..f7c9273 --- /dev/null +++ b/src/org/gestouch/extensions/native/NativeTouchHitTester.as @@ -0,0 +1,19 @@ +package org.gestouch.extensions.native +{ + import org.gestouch.core.ITouchHitTester; + + import flash.display.InteractiveObject; + import flash.geom.Point; + + + /** + * @author Pavel fljot + */ + final public class NativeTouchHitTester implements ITouchHitTester + { + public function hitTest(point:Point, nativeTarget:InteractiveObject):Object + { + return nativeTarget; + } + } +} diff --git a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as new file mode 100644 index 0000000..adc4e13 --- /dev/null +++ b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as @@ -0,0 +1,77 @@ +package org.gestouch.extensions.starling +{ + import starling.core.Starling; + import starling.display.DisplayObject; + import starling.display.DisplayObjectContainer; + + import org.gestouch.core.IDisplayListAdapter; + + import flash.geom.Point; + import flash.utils.Dictionary; + + + /** + * @author Pavel fljot + */ + final public class StarlingDisplayListAdapter implements IDisplayListAdapter + { + private var targetWeekStorage:Dictionary; + + + public function StarlingDisplayListAdapter(target:DisplayObject = null) + { + if (target) + { + targetWeekStorage = new Dictionary(true); + targetWeekStorage[target] = true; + } + } + + + public function get target():Object + { + for (var key:Object in targetWeekStorage) + { + return key; + } + return null; + } + + + public function globalToLocal(point:Point):Point + { + point = StarlingUtils.adjustGlobalPoint(Starling.current, point); + return (target as DisplayObject).globalToLocal(point); + } + + + public function contains(object:Object):Boolean + { + const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; + const objectAsDO:DisplayObject = object as DisplayObject; + return (targetAsDOC && objectAsDO && targetAsDOC.contains(objectAsDO)); + } + + + public function getHierarchy(genericTarget:Object):Vector. + { + var list:Vector. = new Vector.(); + var i:uint = 0; + var target:DisplayObject = genericTarget as DisplayObject; + while (target) + { + list[i] = target; + target = target.parent; + i++; + } + + return list; + } + + + public function reflect():Class + { + return StarlingDisplayListAdapter; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/extensions/starling/StarlingTouchHitTester.as b/src/org/gestouch/extensions/starling/StarlingTouchHitTester.as new file mode 100644 index 0000000..3dbb079 --- /dev/null +++ b/src/org/gestouch/extensions/starling/StarlingTouchHitTester.as @@ -0,0 +1,36 @@ +package org.gestouch.extensions.starling +{ + import starling.core.Starling; + + import org.gestouch.core.ITouchHitTester; + + import flash.display.InteractiveObject; + import flash.geom.Point; + + + /** + * @author Pavel fljot + */ + final public class StarlingTouchHitTester implements ITouchHitTester + { + private var starling:Starling; + + + public function StarlingTouchHitTester(starling:Starling) + { + if (!starling) + { + throw ArgumentError("Missing starling argument."); + } + + this.starling = starling; + } + + + public function hitTest(point:Point, nativeTarget:InteractiveObject):Object + { + point = StarlingUtils.adjustGlobalPoint(starling, point); + return starling.stage.hitTest(point, true); + } + } +} diff --git a/src/org/gestouch/extensions/starling/StarlingUtils.as b/src/org/gestouch/extensions/starling/StarlingUtils.as new file mode 100644 index 0000000..e5c40d0 --- /dev/null +++ b/src/org/gestouch/extensions/starling/StarlingUtils.as @@ -0,0 +1,38 @@ +package org.gestouch.extensions.starling +{ + import starling.display.Stage; + import starling.core.Starling; + + import flash.geom.Point; + import flash.geom.Rectangle; + + + /** + * @author Pavel fljot + */ + final public class StarlingUtils + { + /** + * Transforms real global point (in the scope of flash.display::Stage) into + * starling stage "global" coordinates. + */ + public static function adjustGlobalPoint(starling:Starling, point:Point):Point + { + const vp:Rectangle = starling.viewPort; + const stStage:Stage = starling.stage; + + if (vp.x != 0 || vp.y != 0 || + stStage.stageWidth != vp.width || stStage.stageHeight != vp.height) + { + point = point.clone(); + + // Same transformation they do in Starling + // WTF!? https://github.com/PrimaryFeather/Starling-Framework/issues/72 + point.x = stStage.stageWidth * (point.x - vp.x) / vp.width; + point.y = stStage.stageHeight * (point.y - vp.y) / vp.height; + } + + return point; + } + } +} diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index c2b6cd1..bd12afb 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -1,28 +1,32 @@ package org.gestouch.gestures { + import org.gestouch.core.Gestouch; import org.gestouch.core.GestureState; import org.gestouch.core.GesturesManager; import org.gestouch.core.IGestureDelegate; - import org.gestouch.core.IGesturesManager; + import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.core.gestouch_internal; import org.gestouch.events.GestureStateEvent; - import flash.display.InteractiveObject; + import flash.errors.IllegalOperationError; import flash.events.EventDispatcher; import flash.geom.Point; import flash.system.Capabilities; import flash.utils.Dictionary; + /** + * Dispatched when the state of the gesture changes. + * + * @eventType org.gestouch.events.GestureStateEvent + * @see #state + */ [Event(name="stateChange", type="org.gestouch.events.GestureStateEvent")] /** * 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. * - * TODO: - * - - * * @author Pavel fljot */ public class Gesture extends EventDispatcher @@ -32,10 +36,10 @@ package org.gestouch.gestures * (not an accidental offset on touch), * based on 20 pixels on a 252ppi device. */ - public static const DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI); + public static var DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI); - protected const _gesturesManager:IGesturesManager = GesturesManager.getInstance(); + protected const _gesturesManager:GesturesManager = Gestouch.gesturesManager; /** * Map (generic object) of tracking touch points, where keys are touch points IDs. */ @@ -47,10 +51,12 @@ package org.gestouch.gestures * @see requireGestureToFail() */ protected var _gesturesToFail:Dictionary = new Dictionary(true); - protected var _pendingRecognizedState:uint; + protected var _pendingRecognizedState:GestureState; + + use namespace gestouch_internal; - public function Gesture(target:InteractiveObject = null) + public function Gesture(target:Object = null) { super(); @@ -61,9 +67,22 @@ package org.gestouch.gestures /** @private */ - private var _targetWeekStorage:Dictionary; + protected var _targetAdapter:IGestureTargetAdapter; + /** + * + */ + gestouch_internal function get targetAdapter():IGestureTargetAdapter + { + return _targetAdapter; + } + 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.

@@ -74,29 +93,18 @@ package org.gestouch.gestures * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html */ - public function get target():InteractiveObject + public function get target():Object { - for (var key:Object in _targetWeekStorage) - { - return key as InteractiveObject; - } - return null; + return _targetAdapter ? _targetAdapter.target : null; } - public function set target(value:InteractiveObject):void + public function set target(value:Object):void { - var target:InteractiveObject = this.target; + var target:Object = this.target; if (target == value) return; uninstallTarget(target); - for (var key:Object in _targetWeekStorage) - { - delete _targetWeekStorage[key]; - } - if (value) - { - (_targetWeekStorage ||= new Dictionary(true))[value] = true; - } + _targetAdapter = value ? Gestouch.createGestureTargetAdapter(value) : null; installTarget(value); } @@ -117,11 +125,18 @@ package org.gestouch.gestures return; _enabled = value; - //TODO - if (!_enabled && state != GestureState.IDLE) + + if (!_enabled) { - setState(GestureState.CANCELLED); - reset(); + if (state == GestureState.POSSIBLE) + { + setState(GestureState.FAILED); + } + else + if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + setState(GestureState.CANCELLED); + } } } @@ -148,8 +163,8 @@ package org.gestouch.gestures } - protected var _state:uint = GestureState.IDLE; - public function get state():uint + protected var _state:GestureState = GestureState.IDLE; + public function get state():GestureState { return _state; } @@ -173,7 +188,6 @@ package org.gestouch.gestures */ public function get location():Point { - //TODO: to clone or not clone? performance & convention or ... return _location.clone(); } @@ -186,6 +200,17 @@ package org.gestouch.gestures // //-------------------------------------------------------------------------- + override public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void + { + if (!eventTypeIsValid(type)) + { + throw new ArgumentError("Event type does not match any of allowed values."); + } + + super.addEventListener(type, listener, useCapture, priority, useWeakReference); + } + + [Abstract] /** * Reflects gesture class (for better perfomance). @@ -213,7 +238,7 @@ package org.gestouch.gestures */ public function reset():void { - var state:uint = this.state;//caching getter + var state:GestureState = this.state;//caching getter if (state == GestureState.IDLE) return;// Do nothing as we're in IDLE and nothing to reset @@ -228,14 +253,14 @@ package org.gestouch.gestures var gestureToFail:Gesture = key as Gesture; gestureToFail.removeEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler); } - _pendingRecognizedState = 0; + _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.RECOGNIZED) + 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); @@ -285,6 +310,11 @@ package org.gestouch.gestures public function requireGestureToFail(gesture:Gesture):void { //TODO + if (!gesture) + { + throw new ArgumentError(); + } + _gesturesToFail[gesture] = true; } @@ -310,11 +340,11 @@ package org.gestouch.gestures * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html */ - protected function installTarget(target:InteractiveObject):void + protected function installTarget(target:Object):void { if (target) { - _gesturesManager.gestouch_internal::addGesture(this); + _gesturesManager.addGesture(this); } } @@ -326,11 +356,11 @@ package org.gestouch.gestures * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html */ - protected function uninstallTarget(target:InteractiveObject):void + protected function uninstallTarget(target:Object):void { if (target) { - _gesturesManager.gestouch_internal::removeGesture(this); + _gesturesManager.removeGesture(this); } } @@ -348,6 +378,19 @@ package org.gestouch.gestures } + protected function failOrIgnoreTouch(touch:Touch):void + { + if (state == GestureState.POSSIBLE) + { + setState(GestureState.FAILED); + } + else if (state != GestureState.IDLE) + { + ignoreTouch(touch); + } + } + + [Abstract] /** *

NB! This is abstract method and must be overridden.

@@ -375,30 +418,47 @@ package org.gestouch.gestures } - protected function setState(newState:uint):Boolean + /** + * + */ + protected function onTouchCancel(touch:Touch):void + { + } + + + protected function setState(newState:GestureState):Boolean { if (_state == newState && _state == GestureState.CHANGED) { + // shortcut for better performance + if (hasEventListener(GestureStateEvent.STATE_CHANGE)) + { + dispatchEvent(new GestureStateEvent(GestureStateEvent.STATE_CHANGE, _state, _state)); + } + return true; } - //TODO: is state sequence validation needed? e.g.: - //POSSIBLE should be followed by BEGAN or RECOGNIZED or FAILED - //BEGAN should be follwed by CHANGED or ENDED or CANCELLED - //CHANGED should be followed by CHANGED or ENDED or CANCELLED - //... + if (!_state.canTransitionTo(newState)) + { + throw new IllegalOperationError("You cannot change from state " + + _state + " to state " + newState + "."); + } + 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 (var key:* in _gesturesToFail) + for (key in _gesturesToFail) { gestureToFail = key as Gesture; - if (gestureToFail.state != GestureState.IDLE && gestureToFail.state != GestureState.POSSIBLE - && gestureToFail.state != GestureState.FAILED) + if (gestureToFail.state != GestureState.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 @@ -406,7 +466,7 @@ package org.gestouch.gestures return false; } } - // then we check of other required-to-fail gestures are actually tracked (not IDLE) + // 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) { @@ -415,6 +475,13 @@ package org.gestouch.gestures { // Other gesture might fail soon, so we postpone state change _pendingRecognizedState = newState; + + for (key in _gesturesToFail) + { + gestureToFail = key as Gesture; + gestureToFail.addEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler, false, 0, true); + } + return false; } // else if gesture is in IDLE state it means it doesn't track anything, @@ -430,12 +497,12 @@ package org.gestouch.gestures } } - var oldState:uint = _state; + var oldState:GestureState = _state; _state = newState; - if (((GestureState.CANCELLED | GestureState.RECOGNIZED | GestureState.ENDED | GestureState.FAILED) & _state) > 0) + if (_state.isEndState) { - _gesturesManager.gestouch_internal::scheduleGestureStateReset(this); + _gesturesManager.scheduleGestureStateReset(this); } //TODO: what if RTE happens in event handlers? @@ -447,14 +514,14 @@ package org.gestouch.gestures if (_state == GestureState.BEGAN || _state == GestureState.RECOGNIZED) { - _gesturesManager.gestouch_internal::onGestureRecognized(this); + _gesturesManager.onGestureRecognized(this); } return true; } - gestouch_internal function setState_internal(state:uint):void + gestouch_internal function setState_internal(state:GestureState):void { setState(state); } @@ -481,7 +548,7 @@ package org.gestouch.gestures updateCentralPoint(); _location.x = _centralPoint.x; _location.y = _centralPoint.y; - _localLocation = target.globalToLocal(_location); + _localLocation = targetAdapter.globalToLocal(_location); } @@ -496,6 +563,13 @@ package org.gestouch.gestures } + protected function eventTypeIsValid(type:String):Boolean + { + // propertyChange just in case for bindings? + return type == GestureStateEvent.STATE_CHANGE || type == "propertyChange"; + } + + //-------------------------------------------------------------------------- @@ -513,12 +587,6 @@ package org.gestouch.gestures if (_touchesCount == 1 && state == GestureState.IDLE) { - for (var key:* in _gesturesToFail) - { - var gestureToFail:Gesture = key as Gesture; - gestureToFail.addEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler, false, 0, true); - } - setState(GestureState.POSSIBLE); } } @@ -540,12 +608,30 @@ package org.gestouch.gestures } + 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); + } + } + } + + protected function gestureToFail_stateChangeHandler(event:GestureStateEvent):void { - if (state != GestureState.POSSIBLE) - return;//just in case..FIXME? - - if (!_pendingRecognizedState) + if (!_pendingRecognizedState || state != GestureState.POSSIBLE) return; if (event.newState == GestureState.FAILED) @@ -560,15 +646,20 @@ package org.gestouch.gestures } } + // at this point all gestures-to-fail are either in IDLE or in FAILED states if (setState(_pendingRecognizedState)) { onDelayedRecognize(); } } - else if (event.newState != GestureState.POSSIBLE) + else if (event.newState != GestureState.IDLE && event.newState != GestureState.POSSIBLE) { + // NB: _other_ gesture may switch to IDLE state if it was in FAILED when + // _this_ gesture initially attempted to switch to one of recognized state. + // ...and that's OK (we ignore that) + setState(GestureState.FAILED); } } } -} \ No newline at end of file +} diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index a99ea6b..7ef5632 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -4,14 +4,18 @@ package org.gestouch.gestures import org.gestouch.core.Touch; import org.gestouch.events.LongPressGestureEvent; - import flash.display.InteractiveObject; import flash.events.TimerEvent; import flash.utils.Timer; /** - * TODO: -location - * - check on iOS (Obj-C) what happens when numTouchesRequired=2, two finger down, then quickly release one. + * + * @eventType org.gestouch.events.LongPressGestureEvent + */ + [Event(name="gestureLongPress", type="org.gestouch.events.LongPressGestureEvent")] + /** + * TODO: + * - add numTapsRequired * * @author Pavel fljot */ @@ -21,16 +25,16 @@ package org.gestouch.gestures /** * The minimum time interval in millisecond fingers must press on the target for the gesture to be recognized. * - * @default 500 - */ - public var minPressDuration:uint = 500; + * @default 500 + */ + public var minPressDuration:uint = 500; public var slop:Number = Gesture.DEFAULT_SLOP; protected var _timer:Timer; protected var _numTouchesRequiredReached:Boolean; - public function LongPressGesture(target:InteractiveObject = null) + public function LongPressGesture(target:Object = null) { super(target); } @@ -49,7 +53,7 @@ package org.gestouch.gestures return TapGesture; } - + override public function reset():void { super.reset(); @@ -67,6 +71,12 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- + override protected function eventTypeIsValid(type:String):Boolean + { + return type == LongPressGestureEvent.GESTURE_LONG_PRESS || super.eventTypeIsValid(type); + } + + override protected function preinit():void { super.preinit(); @@ -80,14 +90,7 @@ package org.gestouch.gestures { if (touchesCount > numTouchesRequired) { - if (state == GestureState.BEGAN || state == GestureState.CHANGED) - { - ignoreTouch(touch); - } - else - { - setState(GestureState.FAILED); - } + failOrIgnoreTouch(touch); return; } @@ -121,10 +124,9 @@ package org.gestouch.gestures override protected function onTouchEnd(touch:Touch):void { - //TODO: check proper condition (behavior) on iOS native if (_numTouchesRequiredReached) { - if (((GestureState.BEGAN | GestureState.CHANGED) & state) > 0) + if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); if (setState(GestureState.ENDED) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) @@ -144,7 +146,7 @@ package org.gestouch.gestures } } - + override protected function onDelayedRecognize():void { if (hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index ddf74bf..6dbf6e2 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -4,9 +4,13 @@ package org.gestouch.gestures import org.gestouch.core.Touch; import org.gestouch.events.PanGestureEvent; - import flash.display.InteractiveObject; import flash.geom.Point; + + /** + * + * @eventType org.gestouch.events.PanGestureEvent + */ [Event(name="gesturePan", type="org.gestouch.events.PanGestureEvent")] /** * TODO: @@ -27,14 +31,14 @@ package org.gestouch.gestures protected var _gestureBeginOffsetY:Number; - public function PanGesture(target:InteractiveObject = null) + public function PanGesture(target:Object = null) { super(target); } /** @private */ - private var _maxNumTouchesRequired:uint = 1; + private var _maxNumTouchesRequired:uint = uint.MAX_VALUE; /** * @@ -108,19 +112,24 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- + override protected function eventTypeIsValid(type:String):Boolean + { + return type == PanGestureEvent.GESTURE_PAN || super.eventTypeIsValid(type); + } + + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > maxNumTouchesRequired) { - //TODO - ignoreTouch(touch); + failOrIgnoreTouch(touch); return; } if (touchesCount >= minNumTouchesRequired) { updateLocation(); - } + } } @@ -136,6 +145,10 @@ package org.gestouch.gestures if (state == GestureState.POSSIBLE) { + prevLocationX = _location.x; + prevLocationY = _location.y; + updateLocation(); + // Check if finger moved enough for gesture to be recognized var locationOffset:Point = touch.locationOffset; if (direction == PanGestureDirection.VERTICAL) @@ -149,9 +162,6 @@ package org.gestouch.gestures if (locationOffset.length > slop || slop != slop)//faster isNaN(slop) { - prevLocationX = _location.x; - prevLocationY = _location.y; - updateLocation(); offsetX = _location.x - prevLocationX; offsetY = _location.y - prevLocationY; // acummulate begin offsets for the case when this gesture recognition is delayed by requireGestureToFail diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index da1665b..1766c6a 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -3,11 +3,14 @@ package org.gestouch.gestures import org.gestouch.core.GestureState; import org.gestouch.core.Touch; import org.gestouch.events.RotateGestureEvent; - import org.gestouch.utils.GestureUtils; - import flash.display.InteractiveObject; import flash.geom.Point; + + /** + * + * @eventType org.gestouch.events.RotateGestureEvent + */ [Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")] /** * TODO: @@ -17,14 +20,15 @@ package org.gestouch.gestures */ public class RotateGesture extends Gesture { - public var slop:Number = Gesture.DEFAULT_SLOP >> 1; + public var slop:Number = Gesture.DEFAULT_SLOP; protected var _touch1:Touch; protected var _touch2:Touch; protected var _transformVector:Point; + protected var _thresholdAngle:Number; - public function RotateGesture(target:InteractiveObject = null) + public function RotateGesture(target:Object = null) { super(target); } @@ -52,12 +56,17 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- + override protected function eventTypeIsValid(type:String):Boolean + { + return type == RotateGestureEvent.GESTURE_ROTATE || super.eventTypeIsValid(type); + } + + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > 2) { - //TODO - ignoreTouch(touch); + failOrIgnoreTouch(touch); return; } @@ -70,6 +79,9 @@ package org.gestouch.gestures _touch2 = touch; _transformVector = _touch2.location.subtract(_touch1.location); + + // @see chord length formula + _thresholdAngle = Math.asin(slop / (2 * _transformVector.length)) * 2; } } @@ -79,38 +91,41 @@ package org.gestouch.gestures if (touchesCount < 2) return; - var recognized:Boolean = true; + var currTransformVector:Point = _touch2.location.subtract(_touch1.location); + var rotation:Number = Math.atan2(currTransformVector.y, currTransformVector.x) - Math.atan2(_transformVector.y, _transformVector.x); - if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop) + if (state == GestureState.POSSIBLE) { - recognized = false; + const absRotation:Number = rotation >= 0 ? rotation : -rotation; + if (absRotation < _thresholdAngle) + { + // not recognized yet + return; + } + + // adjust angle to avoid initial "jump" + rotation = rotation > 0 ? rotation - _thresholdAngle : rotation + _thresholdAngle; } - if (recognized) + _transformVector.x = currTransformVector.x; + _transformVector.y = currTransformVector.y; + + updateLocation(); + + if (state == GestureState.POSSIBLE) { - var currTransformVector:Point = _touch2.location.subtract(_touch1.location); - var rotation:Number = Math.atan2(currTransformVector.y, currTransformVector.x) - Math.atan2(_transformVector.y, _transformVector.x); - rotation *= GestureUtils.RADIANS_TO_DEGREES; - _transformVector.x = currTransformVector.x; - _transformVector.y = currTransformVector.y; - - updateLocation(); - - if (state == GestureState.POSSIBLE) + if (setState(GestureState.BEGAN) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) { - if (setState(GestureState.BEGAN) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) - { - dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.BEGAN, - _location.x, _location.y, _localLocation.x, _localLocation.y, rotation)); - } + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.BEGAN, + _location.x, _location.y, _localLocation.x, _localLocation.y, rotation)); } - else + } + else + { + if (setState(GestureState.CHANGED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) { - if (setState(GestureState.CHANGED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) - { - dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.CHANGED, - _location.x, _location.y, _localLocation.x, _localLocation.y, rotation)); - } + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.CHANGED, + _location.x, _location.y, _localLocation.x, _localLocation.y, rotation)); } } } diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 9665493..e464206 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -3,35 +3,85 @@ package org.gestouch.gestures import org.gestouch.core.GestureState; import org.gestouch.core.Touch; import org.gestouch.events.SwipeGestureEvent; + import org.gestouch.utils.GestureUtils; - import flash.display.InteractiveObject; + import flash.events.TimerEvent; import flash.geom.Point; + import flash.system.Capabilities; + import flash.utils.Timer; + + /** + * + * @eventType org.gestouch.events.SwipeGestureEvent + */ [Event(name="gestureSwipe", type="org.gestouch.events.SwipeGestureEvent")] /** - * TODO: - * -check native behavior on iDevice + * Recognition logic:
+ * 1. should be recognized during maxDuration period
+ * 2. velocity >= minVelocity OR offset >= minOffset + * * * @author Pavel fljot */ public class SwipeGesture extends Gesture { - public var slop:Number = Gesture.DEFAULT_SLOP; + private static const ANGLE:Number = 40 * GestureUtils.DEGREES_TO_RADIANS; + private static const MAX_DURATION:uint = 500; + private static const MIN_OFFSET:Number = Capabilities.screenDPI / 6; + private static const MIN_VELOCITY:Number = 2 * MIN_OFFSET / MAX_DURATION; + + /** + * "Dirty" region around touch begin location which does not taken into account + * for gesture failing conditions. It might be useful in case your device is too sensitive + * (so when you just touch the screen you get undesired accidental movement).
+ * NB! Unlike in other gestures, default value for slop is 0 here. + * + * @default 0 + */ + public var slop:Number = 0; public var numTouchesRequired:uint = 1; - public var minVelocity:Number = 0.8; - public var minOffset:Number = Gesture.DEFAULT_SLOP; public var direction:uint = SwipeGestureDirection.ORTHOGONAL; - public var maxDirectionalOffset:Number = Gesture.DEFAULT_SLOP << 1; + + /** + * The duration of period (in milliseconds) in which SwipeGesture must be recognized. + * If gesture is not recognized during this period it fails. Default value is 500 (half a + * second) and generally should not be changed. You can change it though for some special + * cases, most likely together with minVelocity and minOffset + * to achieve really custom behavior. + * + * @default 500 + * + * @see #minVelocity + * @see #minOffset + */ + public var maxDuration:uint = MAX_DURATION; + + /** + * Minimum offset (in pixels) for gesture to be recognized. + * Default value is Capabilities.screenDPI / 6 and generally should not + * be changed. + */ + public var minOffset:Number = MIN_OFFSET; + + /** + * Minimum velocity (in pixels per millisecond) for gesture to be recognized. + * Default value is 2 * minOffset / maxDuration and generally should not + * be changed. + * + * @see #minOffset + * @see #minDuration + */ + public var minVelocity:Number = MIN_VELOCITY; protected var _offset:Point = new Point(); protected var _startTime:int; protected var _noDirection:Boolean; protected var _avrgVel:Point = new Point(); - protected var _prevAvrgVel:Point = new Point(); - protected var _decelerationCounter:uint = 0; + protected var _timer:Timer; - public function SwipeGesture(target:InteractiveObject = null) + public function SwipeGesture(target:Object = null) { super(target); } @@ -50,14 +100,14 @@ package org.gestouch.gestures return SwipeGesture; } - + override public function reset():void { _startTime = 0; _offset.x = 0; _offset.y = 0; - _decelerationCounter = 0; - + _timer.reset(); + super.reset(); } @@ -70,12 +120,26 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- + override protected function eventTypeIsValid(type:String):Boolean + { + return type == SwipeGestureEvent.GESTURE_SWIPE || super.eventTypeIsValid(type); + } + + + override protected function preinit():void + { + super.preinit(); + + _timer = new Timer(maxDuration, 1); + _timer.addEventListener(TimerEvent.TIMER_COMPLETE, timer_timerCompleteHandler); + } + + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > numTouchesRequired) { - //TODO: or ignore? - setState(GestureState.FAILED); + failOrIgnoreTouch(touch); return; } @@ -83,6 +147,10 @@ package org.gestouch.gestures { // Because we want to fail as quick as possible _startTime = touch.time; + + _timer.reset(); + _timer.delay = maxDuration; + _timer.start(); } if (touchesCount == numTouchesRequired) { @@ -100,6 +168,10 @@ package org.gestouch.gestures if (touchesCount < numTouchesRequired) return; + var totalTime:int = touch.time - _startTime; + if (totalTime == 0) + return;//It was somehow THAT MUCH performant on one Android tablet + var prevCentralPointX:Number = _centralPoint.x; var prevCentralPointY:Number = _centralPoint.y; updateCentralPoint(); @@ -107,43 +179,19 @@ package org.gestouch.gestures _offset.x = _centralPoint.x - _location.x; _offset.y = _centralPoint.y - _location.y; var offsetLength:Number = _offset.length; - - if (offsetLength < slop) - { - // no need in processing - we're in the very beginning of movement - return; - } // average velocity (total offset to total duration) - _prevAvrgVel.x = _avrgVel.x; - _prevAvrgVel.y = _avrgVel.y; - var absPrevAvrgVel:Number = _prevAvrgVel.length; - var totalTime:int = touch.time - _startTime; _avrgVel.x = _offset.x / totalTime; _avrgVel.y = _offset.y / totalTime; var avrgVel:Number = _avrgVel.length; - - if (avrgVel * 0.95 < absPrevAvrgVel) - { - _decelerationCounter++; - } - if (_decelerationCounter > 5 || avrgVel < 0.1) - { - setState(GestureState.FAILED); - return; - } - if (_noDirection) { - // We should quickly fail if we have noticable deceleration - // or first movement happend way later after touch - - if (avrgVel >= minVelocity && (minOffset != minOffset || offsetLength >= minOffset)) + if (avrgVel >= minVelocity || offsetLength >= minOffset) { if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { - _localLocation = target.globalToLocal(_location);//refresh local location in case target moved + _localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED, _location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); } @@ -161,25 +209,25 @@ package org.gestouch.gestures { var absOffsetX:Number = _offset.x > 0 ? _offset.x : -_offset.x; - if ((SwipeGestureDirection.HORIZONTAL & direction) == 0) - { - // horizontal velocity is greater then vertical, but we're not interested in any horizontal direction - setState(GestureState.FAILED); - } - else if (recentOffsetX < 0 && (direction & SwipeGestureDirection.LEFT) == 0 || - recentOffsetX > 0 && (direction & SwipeGestureDirection.RIGHT) == 0 || - Math.abs(_offset.y) > maxDirectionalOffset) + if ((recentOffsetX < 0 && (direction & SwipeGestureDirection.LEFT) == 0) || + (recentOffsetX > 0 && (direction & SwipeGestureDirection.RIGHT) == 0) || + Math.abs(Math.atan(_offset.y/_offset.x)) > ANGLE) { // movement in opposite direction // or too much diagonally - setState(GestureState.FAILED); + + // Give some tolerance for accidental offset on finger press (slop) + if (offsetLength > slop || slop != slop)//faster isNaN() + { + setState(GestureState.FAILED); + } } - else if (absVelX >= minVelocity && (minOffset != minOffset || absOffsetX >= minOffset)) + else if (absVelX >= minVelocity || absOffsetX >= minOffset) { _offset.y = 0; if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { - _localLocation = target.globalToLocal(_location);//refresh local location in case target moved + _localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED, _location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); } @@ -189,36 +237,37 @@ package org.gestouch.gestures { var absOffsetY:Number = _offset.y > 0 ? _offset.y : -_offset.y; - if ((SwipeGestureDirection.VERTICAL & direction) == 0) - { - // horizontal velocity is greater then vertical, but we're not interested in any horizontal direction - setState(GestureState.FAILED); - } - else if (recentOffsetY < 0 && (direction & SwipeGestureDirection.UP) == 0 || - recentOffsetY > 0 && (direction & SwipeGestureDirection.DOWN) == 0 || - Math.abs(_offset.x) > maxDirectionalOffset) + if ((recentOffsetY < 0 && (direction & SwipeGestureDirection.UP) == 0) || + (recentOffsetY > 0 && (direction & SwipeGestureDirection.DOWN) == 0) || + Math.abs(Math.atan(_offset.x/_offset.y)) > ANGLE) { // movement in opposite direction // or too much diagonally - setState(GestureState.FAILED); + + // Give some tolerance for accidental offset on finger press (slop) + if (offsetLength > slop || slop != slop)//faster isNaN() + { + setState(GestureState.FAILED); + } } - else if (absVelY >= minVelocity && (minOffset != minOffset || absOffsetY >= minOffset)) + else if (absVelY >= minVelocity || absOffsetY >= minOffset) { _offset.x = 0; if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { - _localLocation = target.globalToLocal(_location);//refresh local location in case target moved + _localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED, _location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); } } } - else + // Give some tolerance for accidental offset on finger press (slop) + else if (offsetLength > slop || slop != slop)//faster isNaN() { setState(GestureState.FAILED); } } - } + } override protected function onTouchEnd(touch:Touch):void @@ -234,10 +283,27 @@ package org.gestouch.gestures { if (hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { - _localLocation = target.globalToLocal(_location);//refresh local location in case target moved + _localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED, _location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); } } + + + + + //-------------------------------------------------------------------------- + // + // Event handlers + // + //-------------------------------------------------------------------------- + + protected function timer_timerCompleteHandler(event:TimerEvent):void + { + if (state == GestureState.POSSIBLE) + { + setState(GestureState.FAILED); + } + } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index fc5881b..5d5b6a3 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -4,14 +4,16 @@ package org.gestouch.gestures import org.gestouch.core.Touch; import org.gestouch.events.TapGestureEvent; - import flash.display.InteractiveObject; import flash.events.TimerEvent; import flash.utils.Timer; + /** + * + * @eventType org.gestouch.events.TapGestureEvent + */ [Event(name="gestureTap", type="org.gestouch.events.TapGestureEvent")] /** - * TODO: check failing conditions (iDevice) * * @author Pavel fljot */ @@ -19,7 +21,7 @@ package org.gestouch.gestures { public var numTouchesRequired:uint = 1; public var numTapsRequired:uint = 1; - public var slop:Number = Gesture.DEFAULT_SLOP; + public var slop:Number = Gesture.DEFAULT_SLOP << 2;//iOS has 45px for 132 dpi screen public var maxTapDelay:uint = 400; public var maxTapDuration:uint = 1500; @@ -28,7 +30,7 @@ package org.gestouch.gestures protected var _tapCounter:uint = 0; - public function TapGesture(target:InteractiveObject = null) + public function TapGesture(target:Object = null) { super(target); } @@ -47,17 +49,17 @@ package org.gestouch.gestures return TapGesture; } - + override public function reset():void { _numTouchesRequiredReached = false; _tapCounter = 0; _timer.reset(); - + super.reset(); } - + override public function canPreventGesture(preventedGesture:Gesture):Boolean { if (preventedGesture is TapGesture && @@ -77,6 +79,12 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- + override protected function eventTypeIsValid(type:String):Boolean + { + return type == TapGestureEvent.GESTURE_TAP || super.eventTypeIsValid(type); + } + + override protected function preinit():void { super.preinit(); @@ -90,9 +98,7 @@ package org.gestouch.gestures { if (touchesCount > numTouchesRequired) { - // We put more fingers then required at the same time, - // so treat that as failed - setState(GestureState.FAILED); + failOrIgnoreTouch(touch); return; } @@ -105,7 +111,8 @@ package org.gestouch.gestures if (touchesCount == numTouchesRequired) { - _numTouchesRequiredReached = true; + _numTouchesRequiredReached = true; + updateLocation(); } } @@ -123,7 +130,6 @@ package org.gestouch.gestures { if (!_numTouchesRequiredReached) { - //TODO: check this condition on iDevice setState(GestureState.FAILED); } else if (touchesCount == 0) @@ -136,7 +142,6 @@ package org.gestouch.gestures if (_tapCounter == numTapsRequired) { - updateLocation(); if (setState(GestureState.RECOGNIZED) && hasEventListener(TapGestureEvent.GESTURE_TAP)) { dispatchEvent(new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GestureState.RECOGNIZED, diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index b3a2daa..909eb14 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -3,12 +3,14 @@ package org.gestouch.gestures import org.gestouch.core.GestureState; import org.gestouch.core.Touch; import org.gestouch.events.TransformGestureEvent; - import org.gestouch.utils.GestureUtils; - import flash.display.InteractiveObject; import flash.geom.Point; - - + + + /** + * + * @eventType org.gestouch.events.TransformGestureEvent + */ [Event(name="gestureTransform", type="org.gestouch.events.TransformGestureEvent")] /** * @author Pavel fljot @@ -22,7 +24,7 @@ package org.gestouch.gestures protected var _transformVector:Point; - public function TransformGesture(target:InteractiveObject = null) + public function TransformGesture(target:Object = null) { super(target); } @@ -59,12 +61,17 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- + override protected function eventTypeIsValid(type:String):Boolean + { + return type == TransformGestureEvent.GESTURE_TRANSFORM || super.eventTypeIsValid(type); + } + + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > 2) { - //TODO: to ignore or to keep this touch somewhere? - ignoreTouch(touch); + failOrIgnoreTouch(touch); return; } @@ -97,51 +104,60 @@ package org.gestouch.gestures var prevLocation:Point = _location.clone(); updateLocation(); - var recognized:Boolean = true; + var currTransformVector:Point; - if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop) + if (state == GestureState.POSSIBLE) { - recognized = false; + if (slop > 0 && touch.locationOffset.length < slop) + { + // Not recognized yet + if (_touch2) + { + // Recalculate _transformVector to avoid initial "jump" on recognize + _transformVector = _touch2.location.subtract(_touch1.location); + } + return; + } } - if (recognized) + if (_touch2 && !currTransformVector) { - var prevLocalLocation:Point; - var offsetX:Number = _location.x - prevLocation.x; - var offsetY:Number = _location.y - prevLocation.y; - var scale:Number = 1; - var rotation:Number = 0; - if (_touch2) + currTransformVector = _touch2.location.subtract(_touch1.location); + } + + var prevLocalLocation:Point; + var offsetX:Number = _location.x - prevLocation.x; + var offsetY:Number = _location.y - prevLocation.y; + var scale:Number = 1; + var rotation:Number = 0; + if (_touch2) + { + rotation = Math.atan2(currTransformVector.y, currTransformVector.x) - Math.atan2(_transformVector.y, _transformVector.x); + scale = currTransformVector.length / _transformVector.length; + _transformVector = _touch2.location.subtract(_touch1.location); + } + + + if (state == GestureState.POSSIBLE) + { + if (setState(GestureState.BEGAN) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) { - var currTransformVector:Point = _touch2.location.subtract(_touch1.location); - rotation = Math.atan2(currTransformVector.y, currTransformVector.x) - Math.atan2(_transformVector.y, _transformVector.x); - rotation *= GestureUtils.RADIANS_TO_DEGREES; - scale = currTransformVector.length / _transformVector.length; - _transformVector = _touch2.location.subtract(_touch1.location); + // Note that we dispatch previous location point which gives a way to perform + // accurate UI redraw. See examples project for more info. + prevLocalLocation = targetAdapter.globalToLocal(prevLocation); + dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.BEGAN, + prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY)); } - - - if (state == GestureState.POSSIBLE) + } + else + { + if (setState(GestureState.CHANGED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) { - if (setState(GestureState.BEGAN) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) - { - // Note that we dispatch previous location point which gives a way to perform - // accurate UI redraw. See examples project for more info. - prevLocalLocation = target.globalToLocal(prevLocation); - dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.BEGAN, - prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY)); - } - } - else - { - if (setState(GestureState.CHANGED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) - { - // Note that we dispatch previous location point which gives a way to perform - // accurate UI redraw. See examples project for more info. - prevLocalLocation = target.globalToLocal(prevLocation); - dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.CHANGED, - prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY)); - } + // Note that we dispatch previous location point which gives a way to perform + // accurate UI redraw. See examples project for more info. + prevLocalLocation = targetAdapter.globalToLocal(prevLocation); + dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.CHANGED, + prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY)); } } } diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 09608b3..49f7197 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -4,27 +4,30 @@ package org.gestouch.gestures import org.gestouch.core.Touch; import org.gestouch.events.ZoomGestureEvent; - import flash.display.InteractiveObject; import flash.geom.Point; + + /** + * + * @eventType org.gestouch.events.ZoomGestureEvent + */ [Event(name="gestureZoom", type="org.gestouch.events.ZoomGestureEvent")] /** - * TODO: - * -check native behavior on iDevice * * @author Pavel fljot */ public class ZoomGesture extends Gesture { - public var slop:Number = Gesture.DEFAULT_SLOP >> 1; + public var slop:Number = Gesture.DEFAULT_SLOP; public var lockAspectRatio:Boolean = true; protected var _touch1:Touch; protected var _touch2:Touch; protected var _transformVector:Point; + protected var _initialDistance:Number; - public function ZoomGesture(target:InteractiveObject = null) + public function ZoomGesture(target:Object = null) { super(target); } @@ -52,12 +55,17 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- + override protected function eventTypeIsValid(type:String):Boolean + { + return type == ZoomGestureEvent.GESTURE_ZOOM || super.eventTypeIsValid(type); + } + + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > 2) { - //TODO - ignoreTouch(touch); + failOrIgnoreTouch(touch); return; } @@ -70,6 +78,7 @@ package org.gestouch.gestures _touch2 = touch; _transformVector = _touch2.location.subtract(_touch1.location); + _initialDistance = _transformVector.length; } } @@ -79,48 +88,59 @@ package org.gestouch.gestures if (touchesCount < 2) return; - var recognized:Boolean = true; + var currTransformVector:Point = _touch2.location.subtract(_touch1.location); + var scaleX:Number; + var scaleY:Number; - if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop) + if (state == GestureState.POSSIBLE) { - recognized = false; + const d:Number = currTransformVector.length - _initialDistance; + const absD:Number = d >= 0 ? d : -d; + if (absD < slop) + { + // Not recognized yet + return; + } + + if (slop > 0) + { + // adjust _transformVector to avoid initial "jump" + const slopVector:Point = currTransformVector.clone(); + slopVector.normalize(_initialDistance + (d >= 0 ? slop : -slop)); + _transformVector = slopVector; + } } - if (recognized) + + if (lockAspectRatio) { - var currTransformVector:Point = _touch2.location.subtract(_touch1.location); - var scaleX:Number; - var scaleY:Number; - if (lockAspectRatio) + scaleX = scaleY = currTransformVector.length / _transformVector.length; + } + else + { + scaleX = currTransformVector.x / _transformVector.x; + scaleY = currTransformVector.y / _transformVector.y; + } + + _transformVector.x = currTransformVector.x; + _transformVector.y = currTransformVector.y; + + updateLocation(); + + if (state == GestureState.POSSIBLE) + { + if (setState(GestureState.BEGAN) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) { - scaleX = scaleY = currTransformVector.length / _transformVector.length; + dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.BEGAN, + _location.x, _location.y, _localLocation.x, _localLocation.y, scaleX, scaleY)); } - else + } + else + { + if (setState(GestureState.CHANGED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) { - scaleX = currTransformVector.x / _transformVector.x; - scaleY = currTransformVector.y / _transformVector.y; - } - - _transformVector.x = currTransformVector.x; - _transformVector.y = currTransformVector.y; - - updateLocation(); - - if (state == GestureState.POSSIBLE) - { - if (setState(GestureState.BEGAN) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) - { - dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.BEGAN, - _location.x, _location.y, _localLocation.x, _localLocation.y, scaleX, scaleY)); - } - } - else - { - if (setState(GestureState.CHANGED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) - { - dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.CHANGED, - _location.x, _location.y, _localLocation.x, _localLocation.y, scaleX, scaleY)); - } + dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.CHANGED, + _location.x, _location.y, _localLocation.x, _localLocation.y, scaleX, scaleY)); } } } @@ -141,7 +161,7 @@ package org.gestouch.gestures else if (state == GestureState.POSSIBLE) { setState(GestureState.FAILED); - } + } } else//== 1 { diff --git a/src/org/gestouch/input/AbstractInputAdapter.as b/src/org/gestouch/input/AbstractInputAdapter.as deleted file mode 100644 index 8543af5..0000000 --- a/src/org/gestouch/input/AbstractInputAdapter.as +++ /dev/null @@ -1,51 +0,0 @@ -package org.gestouch.input -{ - import org.gestouch.core.IGesturesManager; - import org.gestouch.core.IInputAdapter; - import org.gestouch.core.ITouchesManager; - - - /** - * @author Pavel fljot - */ - public class AbstractInputAdapter implements IInputAdapter - { - protected var _touchesManager:ITouchesManager; - protected var _gesturesManager:IGesturesManager; - - - public function AbstractInputAdapter() - { - if (Object(this).constructor == AbstractInputAdapter) - { - throw new Error("This is abstract class and should not be directly instantiated."); - } - } - - - public function set touchesManager(value:ITouchesManager):void - { - _touchesManager = value; - } - - - public function set gesturesManager(value:IGesturesManager):void - { - _gesturesManager = value; - } - - - [Abstract] - public function init():void - { - throw new Error("This is abstract method."); - } - - - [Abstract] - public function dispose():void - { - throw new Error("This is abstract method."); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/input/MouseInputAdapter.as b/src/org/gestouch/input/MouseInputAdapter.as deleted file mode 100644 index 4a3e8ea..0000000 --- a/src/org/gestouch/input/MouseInputAdapter.as +++ /dev/null @@ -1,136 +0,0 @@ -package org.gestouch.input -{ - import org.gestouch.core.Touch; - import org.gestouch.core.gestouch_internal; - - import flash.display.InteractiveObject; - import flash.display.Stage; - import flash.events.EventPhase; - import flash.events.MouseEvent; - import flash.geom.Point; - import flash.utils.getTimer; - - - /** - * @author Pavel fljot - */ - public class MouseInputAdapter extends AbstractInputAdapter - { - private static const PRIMARY_TOUCH_POINT_ID:uint = 0; - - protected var _stage:Stage; - - - public function MouseInputAdapter(stage:Stage) - { - super(); - - if (!stage) - { - throw new Error("Stage must be not null."); - } - - _stage = stage; - } - - - override public function init():void - { - _stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); - _stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);// to catch with EventPhase.AT_TARGET - } - - - override public function dispose():void - { - _stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); - _stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); - uninstallStageListeners(); - } - - - protected function installStageListeners():void - { - // Maximum priority to prevent event hijacking - _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true, int.MAX_VALUE); - _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false, int.MAX_VALUE); - _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, int.MAX_VALUE); - _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, int.MAX_VALUE); - } - - - protected function uninstallStageListeners():void - { - _stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); - _stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler); - _stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); - _stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler); - } - - - protected function mouseDownHandler(event:MouseEvent):void - { - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return;//we listen in capture or at_target (to catch on empty stage) - // Way to prevent MouseEvent/TouchEvent collisions. - // Also helps to ignore possible fake events. - if (_touchesManager.hasTouch(PRIMARY_TOUCH_POINT_ID)) - return; - - installStageListeners(); - - var touch:Touch = _touchesManager.createTouch(); - touch.target = event.target as InteractiveObject; - touch.id = PRIMARY_TOUCH_POINT_ID; - touch.gestouch_internal::setLocation(new Point(event.stageX, event.stageY)); - touch.gestouch_internal::setTime(getTimer()); - touch.gestouch_internal::setBeginTime(getTimer()); - - _touchesManager.addTouch(touch); - - _gesturesManager.gestouch_internal::onTouchBegin(touch); - } - - - protected function mouseMoveHandler(event:MouseEvent):void - { - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return;//we listen in capture or at_target (to catch on empty stage) - // Way to prevent MouseEvent/TouchEvent collisions. - // Also helps to ignore possible fake events. - if (!_touchesManager.hasTouch(PRIMARY_TOUCH_POINT_ID)) - return; - - var touch:Touch = _touchesManager.getTouch(PRIMARY_TOUCH_POINT_ID); - touch.gestouch_internal::updateLocation(event.stageX, event.stageY); - touch.gestouch_internal::setTime(getTimer()); - - _gesturesManager.gestouch_internal::onTouchMove(touch); - } - - - protected function mouseUpHandler(event:MouseEvent):void - { - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return;//we listen in capture or at_target (to catch on empty stage) - - // Way to prevent MouseEvent/TouchEvent collisions. - // Also helps to ignore possible fake events. - if (!_touchesManager.hasTouch(PRIMARY_TOUCH_POINT_ID)) - return; - - var touch:Touch = _touchesManager.getTouch(PRIMARY_TOUCH_POINT_ID); - touch.gestouch_internal::updateLocation(event.stageX, event.stageY); - touch.gestouch_internal::setTime(getTimer()); - - _gesturesManager.gestouch_internal::onTouchEnd(touch); - - _touchesManager.removeTouch(touch); - - if (_touchesManager.activeTouchesCount == 0) - { - uninstallStageListeners(); - } - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/input/NativeInputAdapter.as b/src/org/gestouch/input/NativeInputAdapter.as new file mode 100644 index 0000000..7b6e2f5 --- /dev/null +++ b/src/org/gestouch/input/NativeInputAdapter.as @@ -0,0 +1,224 @@ +package org.gestouch.input +{ + import org.gestouch.core.IInputAdapter; + import org.gestouch.core.TouchesManager; + import org.gestouch.core.gestouch_internal; + + import flash.display.InteractiveObject; + import flash.display.Stage; + import flash.events.EventPhase; + import flash.events.MouseEvent; + import flash.events.TouchEvent; + import flash.ui.Multitouch; + import flash.ui.MultitouchInputMode; + + + /** + * @author Pavel fljot + */ + public class NativeInputAdapter implements IInputAdapter + { + protected static const MOUSE_TOUCH_POINT_ID:uint = 0; + + protected var _stage:Stage; + protected var _explicitlyHandleTouchEvents:Boolean; + protected var _explicitlyHandleMouseEvents:Boolean; + + use namespace gestouch_internal; + + + { + Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT; + } + + + public function NativeInputAdapter(stage:Stage, + explicitlyHandleTouchEvents:Boolean = false, + explicitlyHandleMouseEvents:Boolean = false) + { + super(); + + if (!stage) + { + throw new ArgumentError("Stage must be not null."); + } + + _stage = stage; + + _explicitlyHandleTouchEvents = explicitlyHandleTouchEvents; + _explicitlyHandleMouseEvents = explicitlyHandleMouseEvents; + } + + + protected var _touchesManager:TouchesManager; + public function set touchesManager(value:TouchesManager):void + { + _touchesManager = value; + } + + + + + //-------------------------------------------------------------------------- + // + // Public methods + // + //-------------------------------------------------------------------------- + + public function init():void + { + if (Multitouch.supportsTouchEvents || _explicitlyHandleTouchEvents) + { + _stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); + _stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, false); + _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); + _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false); + // Maximum priority to prevent event hijacking and loosing the touch + _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true, int.MAX_VALUE); + _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, false, int.MAX_VALUE); + } + + if (!Multitouch.supportsTouchEvents || _explicitlyHandleMouseEvents) + { + _stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); + _stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, false); + } + } + + + public function onDispose():void + { + _touchesManager = null; + + _stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); + _stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, false); + _stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); + _stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false); + _stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); + _stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, false); + + _stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); + _stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, false); + unstallMouseListeners(); + } + + + + + //-------------------------------------------------------------------------- + // + // Private methods + // + //-------------------------------------------------------------------------- + + protected function installMouseListeners():void + { + _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); + _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false); + // Maximum priority to prevent event hijacking + _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, int.MAX_VALUE); + _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, int.MAX_VALUE); + } + + + protected function unstallMouseListeners():void + { + _stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); + _stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false); + // Maximum priority to prevent event hijacking + _stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); + _stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false); + } + + + + + //-------------------------------------------------------------------------- + // + // Event handlers + // + //-------------------------------------------------------------------------- + + protected function touchBeginHandler(event:TouchEvent):void + { + // We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET + // (to catch on empty stage) phases only + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + _touchesManager.onTouchBegin(event.touchPointID, event.stageX, event.stageY, event.target as InteractiveObject); + } + + + protected function touchMoveHandler(event:TouchEvent):void + { + // We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET + // (to catch on empty stage) phases only + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + _touchesManager.onTouchMove(event.touchPointID, event.stageX, event.stageY); + } + + + protected function touchEndHandler(event:TouchEvent):void + { + // We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET + // (to catch on empty stage) phases only + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"]) + { + _touchesManager.onTouchCancel(event.touchPointID, event.stageX, event.stageY); + } + else + { + _touchesManager.onTouchEnd(event.touchPointID, event.stageX, event.stageY); + } + } + + + protected function mouseDownHandler(event:MouseEvent):void + { + // We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET + // (to catch on empty stage) phases only + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + const touchAccepted:Boolean = _touchesManager.onTouchBegin(MOUSE_TOUCH_POINT_ID, event.stageX, event.stageY, event.target as InteractiveObject); + + if (touchAccepted) + { + installMouseListeners(); + } + } + + + protected function mouseMoveHandler(event:MouseEvent):void + { + // We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET + // (to catch on empty stage) phases only + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + _touchesManager.onTouchMove(MOUSE_TOUCH_POINT_ID, event.stageX, event.stageY); + } + + + protected function mouseUpHandler(event:MouseEvent):void + { + // We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET + // (to catch on empty stage) phases only + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + _touchesManager.onTouchEnd(MOUSE_TOUCH_POINT_ID, event.stageX, event.stageY); + + if (_touchesManager.activeTouchesCount == 0) + { + unstallMouseListeners(); + } + } + } +} diff --git a/src/org/gestouch/input/TUIOInputAdapter.as b/src/org/gestouch/input/TUIOInputAdapter.as index 5dae3e9..fa90562 100644 --- a/src/org/gestouch/input/TUIOInputAdapter.as +++ b/src/org/gestouch/input/TUIOInputAdapter.as @@ -1,17 +1,33 @@ package org.gestouch.input { - import org.gestouch.input.AbstractInputAdapter; + import org.gestouch.core.IInputAdapter; + import org.gestouch.core.TouchesManager; /** + * TODO: You can implement your own TUIO Input Adapter (and supply touchesManager with + * touch info), but IMHO it is way easier to use NativeInputAdapter and any TUIO library + * and manually dispatch native TouchEvents using DisplayObjectContainer#getObjectsUnderPoint() + * + * @see NativeInputAdapter + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/DisplayObjectContainer.html#getObjectsUnderPoint() DisplayObjectContainer#getObjectsUnderPoint() + * * @author Pavel fljot */ - public class TUIOInputAdapter extends AbstractInputAdapter + public class TUIOInputAdapter implements IInputAdapter { - public function TUIOInputAdapter() + public function init():void + { + } + + + public function onDispose():void + { + } + + + public function set touchesManager(value:TouchesManager):void { - super(); - //TODO } } -} \ No newline at end of file +} diff --git a/src/org/gestouch/input/TouchInputAdapter.as b/src/org/gestouch/input/TouchInputAdapter.as deleted file mode 100644 index 99f82f4..0000000 --- a/src/org/gestouch/input/TouchInputAdapter.as +++ /dev/null @@ -1,186 +0,0 @@ -package org.gestouch.input -{ - import org.gestouch.core.Touch; - import org.gestouch.core.gestouch_internal; - - import flash.display.InteractiveObject; - import flash.display.Stage; - import flash.events.EventPhase; - import flash.events.TouchEvent; - import flash.geom.Point; - import flash.ui.Multitouch; - import flash.ui.MultitouchInputMode; - import flash.utils.getTimer; - - - /** - * @author Pavel fljot - */ - public class TouchInputAdapter extends AbstractInputAdapter - { - protected var _stage:Stage; - /** - * The hash map of touches instantiated via TouchEvent. - * Used to avoid collisions (double processing) with MouseInputAdapter. - * - * TODO: any better way? - */ - protected var _touchesMap:Object = {}; - - { - Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT; - } - - - public function TouchInputAdapter(stage:Stage) - { - super(); - - if (!stage) - { - throw new Error("Stage must be not null."); - } - - _stage = stage; - } - - - override public function init():void - { - _stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); - _stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);// to catch with EventPhase.AT_TARGET - } - - - override public function dispose():void - { - _stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); - _stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler); - uninstallStageListeners(); - } - - - protected function installStageListeners():void - { - // Maximum priority to prevent event hijacking - _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true, int.MAX_VALUE); - _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false, int.MAX_VALUE); - _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true, int.MAX_VALUE); - _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, false, int.MAX_VALUE); - } - - - protected function uninstallStageListeners():void - { - _stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); - _stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler); - _stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); - _stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler); - } - - - protected function touchBeginHandler(event:TouchEvent):void - { - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return;//we listen in capture or at_target (to catch on empty stage) - // Way to prevent MouseEvent/TouchEvent collisions. - // Also helps to ignore possible fake events. - if (_touchesManager.hasTouch(event.touchPointID)) - return; - - installStageListeners(); - - var touch:Touch = _touchesManager.createTouch(); - touch.id = event.touchPointID; - touch.target = event.target as InteractiveObject; - touch.gestouch_internal::setLocation(new Point(event.stageX, event.stageY)); - touch.sizeX = event.sizeX; - touch.sizeY = event.sizeY; - touch.pressure = event.pressure; - //TODO: conditional compilation? - if (event.hasOwnProperty("timestamp")) - { - touch.gestouch_internal::setTime(event["timestamp"]); - touch.gestouch_internal::setBeginTime(event["timestamp"]); - } - else - { - touch.gestouch_internal::setTime(getTimer()); - touch.gestouch_internal::setBeginTime(getTimer()); - } - - _touchesManager.addTouch(touch); - _touchesMap[touch.id] = true; - - _gesturesManager.gestouch_internal::onTouchBegin(touch); - } - - - protected function touchMoveHandler(event:TouchEvent):void - { - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return;//we listen in capture or at_target (to catch on empty stage) - // Way to prevent MouseEvent/TouchEvent collisions. - // Also helps to ignore possible fake events. - if (!_touchesManager.hasTouch(event.touchPointID) || !_touchesMap.hasOwnProperty(event.touchPointID)) - return; - - var touch:Touch = _touchesManager.getTouch(event.touchPointID); - touch.gestouch_internal::updateLocation(event.stageX, event.stageY); - touch.sizeX = event.sizeX; - touch.sizeY = event.sizeY; - touch.pressure = event.pressure; - //TODO: conditional compilation? - if (event.hasOwnProperty("timestamp")) - { - touch.gestouch_internal::setTime(event["timestamp"]); - } - else - { - touch.gestouch_internal::setTime(getTimer()); - } - - _gesturesManager.gestouch_internal::onTouchMove(touch); - } - - - protected function touchEndHandler(event:TouchEvent):void - { - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return;//we listen in capture or at_target (to catch on empty stage) - - // Way to prevent MouseEvent/TouchEvent collisions. - // Also helps to ignore possible fake events. - if (!_touchesManager.hasTouch(event.touchPointID)) - return; - - var touch:Touch = _touchesManager.getTouch(event.touchPointID); - touch.gestouch_internal::updateLocation(event.stageX, event.stageY); - touch.sizeX = event.sizeX; - touch.sizeY = event.sizeY; - touch.pressure = event.pressure; - //TODO: conditional compilation? - if (event.hasOwnProperty("timestamp")) - { - touch.gestouch_internal::setTime(event["timestamp"]); - } - else - { - touch.gestouch_internal::setTime(getTimer()); - } - - _gesturesManager.gestouch_internal::onTouchEnd(touch); - - _touchesManager.removeTouch(touch); - delete _touchesMap[touch.id]; - - if (_touchesManager.activeTouchesCount == 0) - { - uninstallStageListeners(); - } - - // TODO: handle cancelled touch: - // if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"] && ... - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/utils/GestureUtils.as b/src/org/gestouch/utils/GestureUtils.as index 4d57d69..13eef7b 100644 --- a/src/org/gestouch/utils/GestureUtils.as +++ b/src/org/gestouch/utils/GestureUtils.as @@ -1,5 +1,6 @@ package org.gestouch.utils { + import flash.geom.Point; import flash.system.Capabilities; /** * Set of constants. @@ -24,5 +25,6 @@ package org.gestouch.utils * Precalculated coefficient Math.PI * 2 */ public static const PI_DOUBLE:Number = Math.PI * 2; + public static const GLOBAL_ZERO:Point = new Point(); } } \ No newline at end of file diff --git a/version.properties b/version.properties index 2354cd1..a01ef79 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -project.version = 0.3.1 \ No newline at end of file +project.version = 0.4 \ No newline at end of file