From d3ddb825b5dc6fca959c4a1273223e70347b6e7e Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 25 Oct 2011 15:19:05 +0300 Subject: [PATCH 01/87] Fix condition for dispatching GestureTrackingEvent.GESTURE_TRACKING_END --- src/org/gestouch/gestures/Gesture.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 469bb8e..841568c 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -494,7 +494,7 @@ _propertyNames.push("timeThreshold", "moveThreshold"); _adjustCentralPoint(); - if (_trackingPointsCount == minTouchPointsCount + 1) + if (_trackingPointsCount == minTouchPointsCount - 1) { if (hasEventListener(GestureTrackingEvent.GESTURE_TRACKING_END)) { From 9144538e46378e8a4c7ea560484e0f44530642dd Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 1 Nov 2011 14:10:05 +0200 Subject: [PATCH 02/87] Added Gesture#enabled property --- src/org/gestouch/core/GesturesManager.as | 2 +- src/org/gestouch/core/IGesture.as | 2 ++ src/org/gestouch/gestures/Gesture.as | 22 ++++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index f196ae0..9f22726 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -285,7 +285,7 @@ package org.gestouch.core for each (var gesture:IGesture in _gestures) { - if (gesture.target && gesture.shouldTrackPoint(event, tp)) + if (gesture.enabled && gesture.target && gesture.shouldTrackPoint(event, tp)) { gesture.onTouchBegin(tp); } diff --git a/src/org/gestouch/core/IGesture.as b/src/org/gestouch/core/IGesture.as index ed4e84b..d4457f4 100644 --- a/src/org/gestouch/core/IGesture.as +++ b/src/org/gestouch/core/IGesture.as @@ -12,6 +12,8 @@ package org.gestouch.core function get target():InteractiveObject; function get trackingPoints():Vector.; function get trackingPointsCount():uint; + function get enabled():Boolean; + function set enabled(value:Boolean):void; function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean; function isTracking(touchPointID:uint):Boolean; diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 841568c..6b2aead 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -143,6 +143,28 @@ package org.gestouch.gestures } + /** @private */ + private var _enabled:Boolean = true; + + /** + * @default true + */ + public function get enabled():Boolean + { + return _enabled; + } + public function set enabled(value:Boolean):void + { + if (_enabled == value) return; + + _enabled = value; + if (!_enabled && trackingPointsCount > 0) + { + cancel(); + } + } + + /** * Storage for the trackingPoints property. */ From a036db1aef59660cc46503a9bfaef2acd80d9597 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 22 Nov 2011 02:54:11 +0200 Subject: [PATCH 03/87] Initial commit for the new architecture --- README.textile | 4 + src/org/gestouch/Direction.as | 17 - src/org/gestouch/core/GestureState.as | 16 + src/org/gestouch/core/GesturesManager.as | 604 +++++++++-------- src/org/gestouch/core/IGesture.as | 31 - src/org/gestouch/core/IGestureDelegate.as | 13 + src/org/gestouch/core/IGesturesManager.as | 17 +- src/org/gestouch/core/ITouchesManager.as | 14 + src/org/gestouch/core/Touch.as | 63 ++ src/org/gestouch/core/TouchPoint.as | 68 -- src/org/gestouch/core/TouchesManager.as | 204 ++++++ .../gestouch/events/DoubleTapGestureEvent.as | 19 - src/org/gestouch/events/DragGestureEvent.as | 19 - src/org/gestouch/events/GestureStateEvent.as | 37 ++ .../gestouch/events/GestureTrackingEvent.as | 32 - .../gestouch/events/LongPressGestureEvent.as | 19 +- src/org/gestouch/events/MouseTouchEvent.as | 56 +- src/org/gestouch/events/PanGestureEvent.as | 32 + src/org/gestouch/events/RotateGestureEvent.as | 17 +- src/org/gestouch/events/SwipeGestureEvent.as | 17 +- src/org/gestouch/events/TapGestureEvent.as | 32 + src/org/gestouch/events/ZoomGestureEvent.as | 17 +- src/org/gestouch/gestures/DoubleTapGesture.as | 226 ------- src/org/gestouch/gestures/DragGesture.as | 145 ---- src/org/gestouch/gestures/Gesture.as | 618 +++++++----------- src/org/gestouch/gestures/LongPressGesture.as | 208 +++--- .../gestouch/gestures/MovingGestureBase.as | 178 ----- src/org/gestouch/gestures/PanGesture.as | 196 ++++++ src/org/gestouch/gestures/RotateGesture.as | 208 +++--- src/org/gestouch/gestures/SwipeGesture.as | 258 ++++---- .../gestures/SwipeGestureDirection.as | 18 + src/org/gestouch/gestures/TapGesture.as | 181 +++++ src/org/gestouch/gestures/ZoomGesture.as | 209 +++--- src/org/gestouch/{ => utils}/GestureUtils.as | 2 +- src/org/gestouch/utils/ObjectPool.as | 131 ---- 35 files changed, 1972 insertions(+), 1954 deletions(-) delete mode 100644 src/org/gestouch/Direction.as create mode 100644 src/org/gestouch/core/GestureState.as delete mode 100644 src/org/gestouch/core/IGesture.as create mode 100644 src/org/gestouch/core/IGestureDelegate.as create mode 100644 src/org/gestouch/core/ITouchesManager.as create mode 100644 src/org/gestouch/core/Touch.as delete mode 100644 src/org/gestouch/core/TouchPoint.as create mode 100644 src/org/gestouch/core/TouchesManager.as delete mode 100644 src/org/gestouch/events/DoubleTapGestureEvent.as delete mode 100644 src/org/gestouch/events/DragGestureEvent.as create mode 100644 src/org/gestouch/events/GestureStateEvent.as delete mode 100644 src/org/gestouch/events/GestureTrackingEvent.as create mode 100644 src/org/gestouch/events/PanGestureEvent.as create mode 100644 src/org/gestouch/events/TapGestureEvent.as delete mode 100644 src/org/gestouch/gestures/DoubleTapGesture.as delete mode 100644 src/org/gestouch/gestures/DragGesture.as delete mode 100644 src/org/gestouch/gestures/MovingGestureBase.as create mode 100644 src/org/gestouch/gestures/PanGesture.as create mode 100644 src/org/gestouch/gestures/SwipeGestureDirection.as create mode 100644 src/org/gestouch/gestures/TapGesture.as rename src/org/gestouch/{ => utils}/GestureUtils.as (96%) delete mode 100644 src/org/gestouch/utils/ObjectPool.as diff --git a/README.textile b/README.textile index 4f6d6b4..d62d885 100644 --- a/README.textile +++ b/README.textile @@ -46,6 +46,10 @@ h3. News: h3. Other Resources +* "Apple WWDC 2011: Making the Most of Multi-Touch on iOS":https://developer.apple.com/videos/wwdc/2011/?id=118 +* "Apple WWDC 2010: Simplifying Touch Event Handling with Gesture Recognizers":https://developer.apple.com/videos/wwdc/2010/?id=120 +* "Apple WWDC 2010: Advanced Gesture Recognition":https://developer.apple.com/videos/wwdc/2010/?id=121 + * "GestureWorks":http://www.gestureworks.com * "TUIO":http://www.tuio.org diff --git a/src/org/gestouch/Direction.as b/src/org/gestouch/Direction.as deleted file mode 100644 index 56e8852..0000000 --- a/src/org/gestouch/Direction.as +++ /dev/null @@ -1,17 +0,0 @@ -package org.gestouch -{ - public class Direction - { - public static const NONE:String = "none"; - public static const LEFT:String = "left"; - public static const RIGHT:String = "right"; - public static const UP:String = "up"; - public static const DOWN:String = "down"; - public static const HORIZONTAL:String = "horizontal"; - public static const VERTICAL:String = "vertical"; - public static const STRAIGHT_AXES:String = "straightsAxes"; - public static const DIAGONAL_AXES:String = "diagonalAxes"; - public static const OCTO:String = "octo"; - public static const ALL:String = "all"; - } -} \ No newline at end of file diff --git a/src/org/gestouch/core/GestureState.as b/src/org/gestouch/core/GestureState.as new file mode 100644 index 0000000..c496b0e --- /dev/null +++ b/src/org/gestouch/core/GestureState.as @@ -0,0 +1,16 @@ +package org.gestouch.core +{ + /** + * @author Pavel fljot + */ + public class GestureState + { + public static const POSSIBLE:uint = 1 << 0;//1 + public static const BEGAN:uint = 1 << 1;//2 + public static const CHANGED:uint = 1 << 2;//4 + public static const ENDED:uint = 1 << 3;//8 + public static const CANCELLED:uint = 1 << 4;//16 + public static const FAILED:uint = 1 << 5;//32 + public static const RECOGNIZED:uint = 1 << 6;//64 + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 9f22726..c3566df 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -1,371 +1,419 @@ package org.gestouch.core { import org.gestouch.events.MouseTouchEvent; - import org.gestouch.utils.ObjectPool; + import org.gestouch.gestures.Gesture; + import flash.display.DisplayObject; + import flash.display.DisplayObjectContainer; import flash.display.InteractiveObject; import flash.display.Stage; - import flash.errors.IllegalOperationError; import flash.events.Event; import flash.events.MouseEvent; import flash.events.TouchEvent; import flash.ui.Multitouch; - import flash.ui.MultitouchInputMode; import flash.utils.Dictionary; - import flash.utils.getTimer; - - /** * @author Pavel fljot */ public class GesturesManager implements IGesturesManager { - public static var implementation:IGesturesManager; - - protected static var _impl:IGesturesManager; - protected static var _initialized:Boolean = false; + private static var _instance:IGesturesManager; + private static var _allowInstantiation:Boolean; + protected const _touchesManager:ITouchesManager = TouchesManager.getInstance(); protected var _stage:Stage; - protected var _gestures:Vector. = new Vector.(); - protected var _currGestures:Vector. = new Vector.(); - /** - * Maps (Dictionary[target] = gesture) by gesture type. - */ - protected var _gestureMapsByType:Dictionary = new Dictionary(); - protected var _touchPoints:Vector. = new Vector.(Multitouch.maxTouchPoints); - protected var _touchPointsPool:ObjectPool = new ObjectPool(TouchPoint); + protected var _gestures:Vector. = new Vector.(); + protected var _gesturesForTouchMap:Array = []; + protected var _gesturesForTargetMap:Dictionary = new Dictionary(true); + protected var _dirtyGestures:Vector. = new Vector.(); + protected var _dirtyGesturesLength:uint = 0; + protected var _dirtyGesturesMap:Dictionary = new Dictionary(true); - gestouch_internal static function addGesture(gesture:IGesture):IGesture + public function GesturesManager() { - if (!_impl) + if (Object(this).constructor == GesturesManager && !_allowInstantiation) { - _impl = implementation || new GesturesManager(); + throw new Error("Do not instantiate GesturesManager directly."); } - return _impl.addGesture(gesture); } + + + 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; + } + - - gestouch_internal static function removeGesture(gesture:IGesture):IGesture + public static function getInstance():IGesturesManager { - return _impl.removeGesture(gesture); + if (!_instance) + { + _allowInstantiation = true; + _instance = new GesturesManager(); + _allowInstantiation = false; + } + + return _instance; } - gestouch_internal static function removeGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture - { - return _impl.removeGestureByTarget(gestureType, target); - } - - - gestouch_internal static function cancelGesture(gesture:IGesture):void - { - _impl.cancelGesture(gesture); - } - - - gestouch_internal static function addCurrentGesture(gesture:IGesture):void - { - _impl.addCurrentGesture(gesture); - } - - - gestouch_internal static function updateGestureTarget(gesture:IGesture, oldTarget:InteractiveObject, newTarget:InteractiveObject):void - { - _impl.updateGestureTarget(gesture, oldTarget, newTarget); - } - - - public function init(stage:Stage):void - { - Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT; - - _stage = stage; - _stage.addEventListener(MouseEvent.MOUSE_DOWN, stage_mouseDownHandler); - _stage.addEventListener(TouchEvent.TOUCH_BEGIN, stage_touchBeginHandler); - _stage.addEventListener(TouchEvent.TOUCH_MOVE, stage_touchMoveHandler); - _stage.addEventListener(TouchEvent.TOUCH_END, stage_touchEndHandler, true); - } - - - public static function getTouchPoint(touchPointID:int):TouchPoint - { - return _impl.getTouchPoint(touchPointID); - } - - - public function addGesture(gesture:IGesture):IGesture + + + public function addGesture(gesture:Gesture):void { + if (!gesture) + { + throw new ArgumentError("Argument 'gesture' must be not null."); + } if (_gestures.indexOf(gesture) > -1) { - throw new IllegalOperationError("Gesture instace '" + gesture + "' is already registered."); + throw new Error("This gesture is already registered.. something wrong."); } + var targetGestures:Vector. = _gesturesForTargetMap[gesture.target] as Vector.; + if (!targetGestures) + { + targetGestures = _gesturesForTargetMap[gesture.target] = new Vector.(); + } + targetGestures.push(gesture); + _gestures.push(gesture); - return gesture; - } - - - public function removeGesture(gesture:IGesture):IGesture - { - var index:int = _gestures.indexOf(gesture); - if (index == -1) + if (!_stage && gesture.target.stage) { - throw new IllegalOperationError("Gesture instace '" + gesture + "' is not registered."); + installStage(gesture.target.stage); + } + else + { + gesture.target.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + } + } + + + public function removeGesture(gesture:Gesture):void + { + if (!gesture) + { + throw new ArgumentError("Argument 'gesture' must be not null."); } - _gestures.splice(index, 1); - index = _currGestures.indexOf(gesture); + var target:InteractiveObject = gesture.target; + var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; + targetGestures.splice(targetGestures.indexOf(gesture), 1); + + if (targetGestures.length == 0) + { + delete _gesturesForTargetMap[target]; + gesture.target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + } + + var index:int = _gestures.indexOf(gesture); if (index > -1) { - _currGestures.splice(index, 1); + _gestures.splice(index, 1); } - gesture.dispose(); - - return gesture; - } - - - public function removeGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture - { - var gesture:IGesture = getGestureByTarget(gestureType, target); - return removeGesture(gesture); - } - - - public function getGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture - { - var gesturesOfTypeByTarget:Dictionary = _gestureMapsByType[gestureType] as Dictionary; - var gesture:IGesture = gesturesOfTypeByTarget ? gesturesOfTypeByTarget[target] as IGesture : null; - return gesture; + //TODO: decide about gesture state and _dirtyGestures } - public function cancelGesture(gesture:IGesture):void + public function scheduleGestureStateReset(gesture:Gesture):void { - var index:int = _currGestures.indexOf(gesture); - if (index == -1) + if (!_dirtyGesturesMap[gesture]) { - return;// don't see point in throwing error - } - - _currGestures.splice(index, 1); - gesture.onCancel(); - } - - - public function addCurrentGesture(gesture:IGesture):void - { - if (_currGestures.indexOf(gesture) == -1) - { - _currGestures.push(gesture); + _dirtyGestures.push(gesture); + _dirtyGesturesLength++; + _stage.addEventListener(Event.ENTER_FRAME, stage_enterFrameHandler); } } - public function updateGestureTarget(gesture:IGesture, oldTarget:InteractiveObject, newTarget:InteractiveObject):void - { - if (!_initialized && newTarget) + public function onGestureRecognized(gesture:Gesture):void + { + for each (var otherGesture:Gesture in _gestures) { - var stage:Stage = newTarget.stage; - if (stage) + // conditions for otherGesture "own properties" + if (otherGesture != gesture && + otherGesture.enabled && + otherGesture.state == GestureState.POSSIBLE) { - _impl.init(stage); - _initialized = true; - } - else - { - newTarget.addEventListener(Event.ADDED_TO_STAGE, target_addedToStageHandler, false, 0, true); + // conditions for otherGesture target + if (otherGesture.target == gesture.target || + (gesture.target is DisplayObjectContainer && (gesture.target as DisplayObjectContainer).contains(otherGesture.target)) || + (otherGesture.target is DisplayObjectContainer && (otherGesture.target as DisplayObjectContainer).contains(gesture.target)) + ) + { + // conditions for gestures relations + if (gesture.canPreventGesture(otherGesture) && + otherGesture.canBePreventedByGesture(gesture) && + (!gesture.delegate || !gesture.delegate.gesturesShouldRecognizeSimultaneously(gesture, otherGesture)) && + (!otherGesture.delegate || !otherGesture.delegate.gesturesShouldRecognizeSimultaneously(otherGesture, gesture))) + { + otherGesture.gestouch_internal::setState_internal(GestureState.FAILED); + } + } } } + } + + + + + //-------------------------------------------------------------------------- + // + // Private methods + // + //-------------------------------------------------------------------------- + + protected function installStage(stage:Stage):void + { + _touchesManager.init(stage); + _stage = stage; - var gesturesOfTypeByTarget:Dictionary = _gestureMapsByType[gesture.reflect()] as Dictionary; - if (!gesturesOfTypeByTarget) + if (Multitouch.supportsTouchEvents) { - gesturesOfTypeByTarget = _gestureMapsByType[gesture.reflect()] = new Dictionary(); + stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler); } - else if (gesturesOfTypeByTarget[newTarget]) + else { - throw new IllegalOperationError("You cannot add two gestures of the same type to one target (it makes no sence)."); - } - if (oldTarget) - { - oldTarget.addEventListener(Event.ADDED_TO_STAGE, target_addedToStageHandler); - delete gesturesOfTypeByTarget[oldTarget]; - } - if (newTarget) - { - gesturesOfTypeByTarget[newTarget] = gesture; + stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); } } - public function getTouchPoint(touchPointID:int):TouchPoint - { - var p:TouchPoint = _touchPoints[touchPointID]; - if (!p) - { - throw new ArgumentError("No touch point with ID " + touchPointID + " found."); - } - return p.clone() as TouchPoint; - } - - - private static function target_addedToStageHandler(event:Event):void - { - var target:InteractiveObject = event.currentTarget as InteractiveObject; - target.removeEventListener(Event.ADDED_TO_STAGE, target_addedToStageHandler); - - if (!_initialized) - { - _impl.init(target.stage); - _initialized = true; - } - } - - - protected function stage_mouseDownHandler(event:MouseEvent):void + protected function installStageListeners():void { if (Multitouch.supportsTouchEvents) { - return; + _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler); + _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); } - - _stage.addEventListener(MouseEvent.MOUSE_MOVE, stage_mouseMoveHandler); - _stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler); - - stage_touchBeginHandler(new MouseTouchEvent(TouchEvent.TOUCH_BEGIN, event)); - } - - - protected function stage_mouseMoveHandler(event:MouseEvent):void - { - stage_touchMoveHandler(new MouseTouchEvent(TouchEvent.TOUCH_MOVE, event)); - } - - - protected function stage_mouseUpHandler(event:MouseEvent):void - { - _stage.removeEventListener(MouseEvent.MOUSE_MOVE, stage_mouseMoveHandler); - _stage.removeEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler); - - stage_touchEndHandler(new MouseTouchEvent(TouchEvent.TOUCH_END, event)); - } - - - protected function stage_touchBeginHandler(event:TouchEvent):void - { - var outOfRange:Boolean = (_touchPoints.length <= event.touchPointID); - var tp:TouchPoint = outOfRange ? null : _touchPoints[event.touchPointID]; - if (!tp) + else { - tp = _touchPointsPool.getObject() as TouchPoint; - tp.id = event.touchPointID; - if (outOfRange) - { - _touchPoints.length = tp.id + 1; - } - _touchPoints[tp.id] = tp; - } - tp.reset(); - tp.x = event.stageX; - tp.y = event.stageY; - tp.sizeX = event.sizeX; - tp.sizeY = event.sizeY; - tp.pressure = event.pressure; - tp.touchBeginPos.x = tp.x; - tp.touchBeginPos.y = tp.y; - tp.touchBeginTime = tp.lastTime = getTimer(); - tp.moveOffset.x = tp.moveOffset.y = 0; - tp.lastMove.x = tp.lastMove.y = 0; - tp.velocity.x = tp.velocity.y = 0; - - for each (var gesture:IGesture in _gestures) - { - if (gesture.enabled && gesture.target && gesture.shouldTrackPoint(event, tp)) - { - gesture.onTouchBegin(tp); - } - } - - // add gestures that are being tracked to the current gestures list - var n:uint = _gestures.length; - while (n-- > 0) - { - gesture = _gestures[n]; - //TODO: which condition first (performance-wise)? - if (_currGestures.indexOf(gesture) == -1 && gesture.isTracking(tp.id)) - { - _currGestures.push(gesture); - } + _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler); + _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); } } - protected function stage_touchMoveHandler(event:TouchEvent):void + protected function uninstallStageListeners():void { - var tp:TouchPoint = _touchPoints[event.touchPointID]; - var oldX:Number = tp.x; - var oldY:Number = tp.y; - tp.x = event.stageX; - tp.y = event.stageY; - tp.sizeX = event.sizeX; - tp.sizeY = event.sizeY; - tp.pressure = event.pressure; -// tp.moveOffset = tp.subtract(tp.touchBeginPos); - tp.moveOffset.x = tp.x - tp.touchBeginPos.x; - tp.moveOffset.y = tp.y - tp.touchBeginPos.y; - tp.lastMove.x = tp.x - oldX; - tp.lastMove.y = tp.y - oldY; - var now:uint = getTimer(); - var dt:uint = now - tp.lastTime; - tp.lastTime = now; - tp.velocity.x = tp.lastMove.x / dt; - tp.velocity.y = tp.lastMove.y / dt; - - for each (var gesture:IGesture in _currGestures) + if (Multitouch.supportsTouchEvents) { - if (gesture.isTracking(tp.id)) - { - gesture.onTouchMove(tp); - } + _stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler); + _stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); + } + else + { + _stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler); + _stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); } } - protected function stage_touchEndHandler(event:TouchEvent):void + protected function resetDirtyGestures():void { - var tp:TouchPoint = _touchPoints[event.touchPointID]; - tp.x = event.stageX; - tp.y = event.stageY; - tp.sizeX = event.sizeX; - tp.sizeY = event.sizeY; - tp.pressure = event.pressure; - tp.moveOffset = tp.subtract(tp.touchBeginPos); - - for each (var gesture:IGesture in _currGestures) + for each (var gesture:Gesture in _dirtyGestures) { - if (gesture.isTracking(tp.id)) - { - gesture.onTouchEnd(tp); - } + gesture.reset(); + } + _dirtyGestures.length = 0; + _dirtyGesturesLength = 0; + _dirtyGesturesMap = new Dictionary(true); + _stage.removeEventListener(Event.ENTER_FRAME, stage_enterFrameHandler); + } + + + + + //-------------------------------------------------------------------------- + // + // 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) + { + installStage(target.stage); } - var i:uint = 0; - for each (gesture in _currGestures.concat()) + var depth:uint = 1;//NB! not using 0-based for sorting function + var targetParent:DisplayObjectContainer = target.parent; + while (targetParent) { - if (gesture.trackingPointsCount == 0) + depth++; + targetParent = targetParent.parent; + } + } + + + protected function touchBeginHandler(event:TouchEvent):void + { + if (_dirtyGesturesLength > 0) + { + resetDirtyGestures(); + } + + var touch:Touch = _touchesManager.getTouch(event.touchPointID); + 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.; + if (!gesturesForTouch) + { + gesturesForTouch = new Vector.(); + _gesturesForTouchMap[touch.id] = gesturesForTouch; + } + else + { + gesturesForTouch.length = 0; + } + + + // 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) + { + gesturesForTarget = _gesturesForTargetMap[target] as Vector.; + if (gesturesForTarget) { - _currGestures.splice(i, 1); + i = gesturesForTarget.length; + while (i-- > 0) + { + gesture = gesturesForTarget[i] as Gesture; + if (gesture.enabled && + (!gesture.delegate || gesture.delegate.gestureShouldReceiveTouch(gesture, touch))) + { + //TODO: optimize performance! decide between unshift() vs [i++] = gesture + reverse() + gesturesForTouch.unshift(gesture); + } + } + } + + target = target.parent; + } + + // Then we populate them with this touch and event. + // They might start tracking this touch or ignore it (via Gesture#ignoreTouch()) + i = gesturesForTouch.length; + while (i-- > 0) + { + gesture = gesturesForTouch[i] as Gesture; + // Check for state because previous (i+1) gesture may already abort current (i) one + if (gesture.state != GestureState.FAILED) + { + gesture.gestouch_internal::touchBeginHandler(touch, event); } else - { - i++; + { + gesturesForTouch.splice(i, 1); } } + + installStageListeners(); + } + + + protected function mouseDownHandler(event:MouseEvent):void + { + touchBeginHandler(MouseTouchEvent.createMouseTouchEvent(event)); + } + + + protected function touchMoveHandler(event:TouchEvent):void + { + if (_dirtyGesturesLength > 0) + { + resetDirtyGestures(); + } + + var touch:Touch = _touchesManager.getTouch(event.touchPointID); + + var gesturesForTouch:Vector. = _gesturesForTouchMap[touch.id] as Vector.; + var gesture:Gesture; + var i:int = gesturesForTouch.length; + while (i-- > 0) + { + gesture = gesturesForTouch[i] as Gesture; + + if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) + { + gesture.gestouch_internal::touchMoveHandler(touch, event); + } + else + { + // gesture is no more interested in this touch (e.g. ignoreTouch was called) + gesturesForTouch.splice(i, 1); + } + } + } + + + protected function mouseMoveHandler(event:MouseEvent):void + { + //TODO: copy code from touchMoveHandler: save 1 function call? + touchMoveHandler(MouseTouchEvent.createMouseTouchEvent(event)); + } + + + protected function touchEndHandler(event:TouchEvent):void + { + if (_dirtyGesturesLength > 0) + { + resetDirtyGestures(); + } + + var touch:Touch = _touchesManager.getTouch(event.touchPointID); + + var gesturesForTouch:Vector. = _gesturesForTouchMap[touch.id] as Vector.; + var gesture:Gesture; + var i:int = gesturesForTouch.length; + while (i-- > 0) + { + gesture = gesturesForTouch[i] as Gesture; + + // TODO: handle cancelled touch: + // if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"] && ... + + if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) + { + gesture.gestouch_internal::touchEndHandler(touch, event); + } + } + + if (_touchesManager.activeTouchesCount == 0) + { + uninstallStageListeners(); + } + } + + + protected function mouseUpHandler(event:MouseEvent):void + { + touchEndHandler(MouseTouchEvent.createMouseTouchEvent(event)); + } + + + private function stage_enterFrameHandler(event:Event):void + { + resetDirtyGestures(); } } } \ No newline at end of file diff --git a/src/org/gestouch/core/IGesture.as b/src/org/gestouch/core/IGesture.as deleted file mode 100644 index d4457f4..0000000 --- a/src/org/gestouch/core/IGesture.as +++ /dev/null @@ -1,31 +0,0 @@ -package org.gestouch.core -{ - import flash.events.IEventDispatcher; - import flash.display.InteractiveObject; - import flash.events.TouchEvent; - - /** - * @author Pavel fljot - */ - public interface IGesture extends IEventDispatcher - { - function get target():InteractiveObject; - function get trackingPoints():Vector.; - function get trackingPointsCount():uint; - function get enabled():Boolean; - function set enabled(value:Boolean):void; - - function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean; - function isTracking(touchPointID:uint):Boolean; - - function cancel():void; - function pickAndContinue(gesture:IGesture):void; - function reflect():Class; - function dispose():void; - - function onTouchBegin(touchPoint:TouchPoint):void; - function onTouchMove(touchPoint:TouchPoint):void; - function onTouchEnd(touchPoint:TouchPoint):void; - function onCancel():void; - } -} \ No newline at end of file diff --git a/src/org/gestouch/core/IGestureDelegate.as b/src/org/gestouch/core/IGestureDelegate.as new file mode 100644 index 0000000..013982a --- /dev/null +++ b/src/org/gestouch/core/IGestureDelegate.as @@ -0,0 +1,13 @@ +package org.gestouch.core +{ + import org.gestouch.gestures.Gesture; + /** + * @author Pavel fljot + */ + public interface IGestureDelegate + { + function gestureShouldReceiveTouch(gesture:Gesture, touch:Touch):Boolean; + function gestureShouldBegin(gesture:Gesture):Boolean; + function gesturesShouldRecognizeSimultaneously(gesture:Gesture, otherGesture:Gesture):Boolean; + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/IGesturesManager.as b/src/org/gestouch/core/IGesturesManager.as index 6841c8b..dd3f476 100644 --- a/src/org/gestouch/core/IGesturesManager.as +++ b/src/org/gestouch/core/IGesturesManager.as @@ -1,24 +1,17 @@ package org.gestouch.core { - import flash.display.InteractiveObject; - import flash.display.Stage; - + import org.gestouch.gestures.Gesture; /** * @author Pavel fljot */ public interface IGesturesManager { - function init(stage:Stage):void; + function addGesture(gesture:Gesture):void; - function addGesture(gesture:IGesture):IGesture; - function removeGesture(gesture:IGesture):IGesture; - function removeGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture; - function getGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture; - function cancelGesture(gesture:IGesture):void; - function addCurrentGesture(gesture:IGesture):void; + function removeGesture(gesture:Gesture):void; - function updateGestureTarget(gesture:IGesture, oldTarget:InteractiveObject, newTarget:InteractiveObject):void; + function scheduleGestureStateReset(gesture:Gesture):void; - function getTouchPoint(touchPointID:int):TouchPoint; + function onGestureRecognized(gesture:Gesture):void; } } \ No newline at end of file diff --git a/src/org/gestouch/core/ITouchesManager.as b/src/org/gestouch/core/ITouchesManager.as new file mode 100644 index 0000000..ba7a1f2 --- /dev/null +++ b/src/org/gestouch/core/ITouchesManager.as @@ -0,0 +1,14 @@ +package org.gestouch.core +{ + import flash.display.Stage; + /** + * @author Pavel fljot + */ + public interface ITouchesManager + { + function get activeTouchesCount():uint; + + function init(stage:Stage):void; + function getTouch(touchPointID:int):Touch; + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/Touch.as b/src/org/gestouch/core/Touch.as new file mode 100644 index 0000000..6ed565c --- /dev/null +++ b/src/org/gestouch/core/Touch.as @@ -0,0 +1,63 @@ +package org.gestouch.core +{ + import flash.display.InteractiveObject; + + + /** + * TODO: + * - maybe add "phase" (began, moved, stationary, ended)? + * + * @author Pavel fljot + */ + public class Touch + { + /** + * Touch point ID. + */ + public var id:uint; + /** + * The original event target for this touch (touch began with). + */ + public var target:InteractiveObject; + + public var x:Number; + public var y:Number; + public var sizeX:Number; + public var sizeY:Number; + public var pressure:Number; + public var time:uint; + +// public var touchBeginPos:Point; +// public var touchBeginTime:uint; +// public var moveOffset:Point; +// public var lastMove:Point; +// public var velocity:Point; + + + public function Touch(id:uint = 0) + { + this.id = id; + } + + + public function clone():Touch + { + var touch:Touch = new Touch(id); + touch.x = x; + touch.y = y; + touch.target = target; + touch.sizeX = sizeX; + touch.sizeY = sizeY; + touch.pressure = pressure; + touch.time = time; + + return touch; + } + + + public function toString():String + { + return "Touch [id: " + id + ", x: " + x + ", y: " + y + ", ...]"; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/TouchPoint.as b/src/org/gestouch/core/TouchPoint.as deleted file mode 100644 index c72cd91..0000000 --- a/src/org/gestouch/core/TouchPoint.as +++ /dev/null @@ -1,68 +0,0 @@ -package org.gestouch.core -{ - import flash.geom.Point; - - - /** - * @author Pavel fljot - */ - public class TouchPoint extends Point - { - public var id:uint; - public var localX:Number; - public var localY:Number; - public var sizeX:Number; - public var sizeY:Number; - public var pressure:Number; - public var touchBeginPos:Point; - public var touchBeginTime:uint; - public var moveOffset:Point; - public var lastMove:Point; - public var lastTime:uint; - public var velocity:Point; - - - public function TouchPoint(id:uint = 0, x:Number = 0, y:Number = 0, - sizeX:Number = NaN, sizeY:Number = NaN, - pressure:Number = NaN, - touchBeginPos:Point = null, touchBeginTime:uint = 0, - moveOffset:Point = null, - lastMove:Point = null, lastTime:uint = 0, velocity:Point = null) - { - super(x, y); - - this.id = id; - this.sizeX = sizeX; - this.sizeY = sizeY; - this.pressure = pressure; - this.touchBeginPos = touchBeginPos || new Point(); - this.touchBeginTime = touchBeginTime; - this.moveOffset = moveOffset || new Point(); - this.lastMove = lastMove || new Point(); - this.lastTime = lastTime; - this.velocity = velocity || new Point(); - } - - - override public function clone():Point - { - var p:TouchPoint = new TouchPoint(id, x, y, sizeX, sizeY, pressure, - touchBeginPos.clone(), touchBeginTime, - moveOffset.clone(), - lastMove.clone(), lastTime, velocity.clone()); - return p; - } - - - public function reset():void - { - - } - - - override public function toString():String - { - return "Touch point [id: " + id + ", x: " + x + ", y: " + y + ", ...]"; - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/core/TouchesManager.as b/src/org/gestouch/core/TouchesManager.as new file mode 100644 index 0000000..6502f8b --- /dev/null +++ b/src/org/gestouch/core/TouchesManager.as @@ -0,0 +1,204 @@ +package org.gestouch.core +{ + import flash.display.InteractiveObject; + import flash.display.Stage; + import flash.events.MouseEvent; + import flash.events.TouchEvent; + import flash.ui.Multitouch; + import flash.ui.MultitouchInputMode; + import flash.utils.getTimer; + /** + * @author Pavel fljot + */ + public class TouchesManager implements ITouchesManager + { + private static var _instance:ITouchesManager; + private static var _allowInstantiation:Boolean; + + protected var _stage:Stage; + protected var _touchesMap:Object = {}; + + { + Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT; + } + + + public function TouchesManager() + { + if (Object(this).constructor == TouchesManager && !_allowInstantiation) + { + throw new Error("Do not instantiate TouchesManager directly."); + } + } + + + protected var _activeTouchesCount:uint; + public function get activeTouchesCount():uint + { + return _activeTouchesCount; + } + + + public static function setImplementation(value:ITouchesManager):void + { + if (!value) + { + throw new ArgumentError("value cannot be null."); + } + if (_instance) + { + 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."); + } + _instance = value; + } + + + public static function getInstance():ITouchesManager + { + if (!_instance) + { + _allowInstantiation = true; + _instance = new TouchesManager(); + _allowInstantiation = false; + } + + return _instance; + } + + + public function init(stage:Stage):void + { + _stage = stage; + if (Multitouch.supportsTouchEvents) + { + stage.addEventListener(TouchEvent.TOUCH_BEGIN, stage_touchBeginHandler, true, int.MAX_VALUE); + stage.addEventListener(TouchEvent.TOUCH_MOVE, stage_touchMoveHandler, true, int.MAX_VALUE); + stage.addEventListener(TouchEvent.TOUCH_END, stage_touchEndHandler, true, int.MAX_VALUE); + } + else + { + stage.addEventListener(MouseEvent.MOUSE_DOWN, stage_mouseDownHandler, true, int.MAX_VALUE); + stage.addEventListener(MouseEvent.MOUSE_MOVE, stage_mouseMoveHandler, true, int.MAX_VALUE); + stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler, true, int.MAX_VALUE); + } + } + + + public function getTouch(touchPointID:int):Touch + { + var touch:Touch = _touchesMap[touchPointID] as Touch; + return touch ? touch.clone() : null; + } + + + + //-------------------------------------------------------------------------- + // + // Event handlers + // + //-------------------------------------------------------------------------- + + protected function stage_touchBeginHandler(event:TouchEvent):void + { + var touch:Touch = new Touch(event.touchPointID); + _touchesMap[event.touchPointID] = touch; + + touch.target = event.target as InteractiveObject; + touch.x = event.stageX; + touch.y = event.stageY; + touch.sizeX = event.sizeX; + touch.sizeY = event.sizeY; + touch.pressure = event.pressure; + touch.time = getTimer();//TODO: conditional compilation + event.timestamp + + _activeTouchesCount++; + } + + + protected function stage_mouseDownHandler(event:MouseEvent):void + { + var touch:Touch = new Touch(0); + _touchesMap[0] = touch; + + touch.target = event.target as InteractiveObject; + touch.x = event.stageX; + touch.y = event.stageY; + touch.sizeX = NaN; + touch.sizeY = NaN; + touch.pressure = NaN; + touch.time = getTimer();//TODO: conditional compilation + event.timestamp + + _activeTouchesCount++; + } + + + protected function stage_touchMoveHandler(event:TouchEvent):void + { + var touch:Touch = _touchesMap[event.touchPointID] as Touch; + if (!touch) + { + // some fake event? + return; + } + + touch.x = event.stageX; + touch.y = event.stageY; + touch.sizeX = event.sizeX; + touch.sizeY = event.sizeY; + touch.pressure = event.pressure; + touch.time = getTimer();//TODO: conditional compilation + event.timestamp + } + + + protected function stage_mouseMoveHandler(event:MouseEvent):void + { + var touch:Touch = _touchesMap[0] as Touch; + if (!touch) + { + // some fake event? + return; + } + + touch.x = event.stageX; + touch.y = event.stageY; + touch.time = getTimer();//TODO: conditional compilation + event.timestamp + } + + + protected function stage_touchEndHandler(event:TouchEvent):void + { + var touch:Touch = _touchesMap[event.touchPointID] as Touch; + if (!touch) + { + // some fake event? + return; + } + + touch.x = event.stageX; + touch.y = event.stageY; + touch.sizeX = event.sizeX; + touch.sizeY = event.sizeY; + touch.pressure = event.pressure; + touch.time = getTimer();//TODO: conditional compilation + event.timestamp + + _activeTouchesCount--; + } + + + protected function stage_mouseUpHandler(event:MouseEvent):void + { + var touch:Touch = _touchesMap[0] as Touch; + if (!touch) + { + // some fake event? + return; + } + + touch.x = event.stageX; + touch.y = event.stageY; + touch.time = getTimer();//TODO: conditional compilation + event.timestamp + + _activeTouchesCount--; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/DoubleTapGestureEvent.as b/src/org/gestouch/events/DoubleTapGestureEvent.as deleted file mode 100644 index 552ed36..0000000 --- a/src/org/gestouch/events/DoubleTapGestureEvent.as +++ /dev/null @@ -1,19 +0,0 @@ -package org.gestouch.events -{ - import flash.events.GestureEvent; - - - /** - * @author Pavel fljot - */ - public class DoubleTapGestureEvent extends GestureEvent - { - public static const GESTURE_DOUBLE_TAP:String = "gestureDoubleTap"; - - - public function DoubleTapGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false) - { - super(type, bubbles, cancelable, phase, localX, localY, ctrlKey, altKey, shiftKey); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/DragGestureEvent.as b/src/org/gestouch/events/DragGestureEvent.as deleted file mode 100644 index e74a9e9..0000000 --- a/src/org/gestouch/events/DragGestureEvent.as +++ /dev/null @@ -1,19 +0,0 @@ -package org.gestouch.events -{ - import flash.events.TransformGestureEvent; - - - /** - * @author Pavel fljot - */ - public class DragGestureEvent extends TransformGestureEvent - { - public static const GESTURE_DRAG:String = "gestureDrag"; - - - public function DragGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0, rotation:Number = 0, offsetX:Number = 0, offsetY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, commandKey:Boolean = false, controlKey:Boolean = false) - { - super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/GestureStateEvent.as b/src/org/gestouch/events/GestureStateEvent.as new file mode 100644 index 0000000..1dd6ced --- /dev/null +++ b/src/org/gestouch/events/GestureStateEvent.as @@ -0,0 +1,37 @@ +package org.gestouch.events +{ + import flash.events.Event; + + + /** + * @author Pavel fljot + */ + public class GestureStateEvent extends Event + { + public static const STATE_CHANGE:String = "stateChange"; + + public var newState:uint; + public var oldState:uint; + + + public function GestureStateEvent(type:String, newState:uint, oldState:uint) + { + super(type, false, false); + + this.newState = newState; + this.oldState = oldState; + } + + + override public function clone():Event + { + return new GestureStateEvent(type, newState, oldState); + } + + + override public function toString():String + { + return formatToString("GestureStateEvent", newState, oldState); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/GestureTrackingEvent.as b/src/org/gestouch/events/GestureTrackingEvent.as deleted file mode 100644 index a8b43ff..0000000 --- a/src/org/gestouch/events/GestureTrackingEvent.as +++ /dev/null @@ -1,32 +0,0 @@ -package org.gestouch.events -{ - import flash.events.Event; - - - /** - * @author Pavel fljot - */ - public class GestureTrackingEvent extends Event - { - public static const GESTURE_TRACKING_BEGIN:String = "gestureTrackingBegin"; - public static const GESTURE_TRACKING_END:String = "gestureTrackingEnd"; - - - public function GestureTrackingEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false) - { - super(type, bubbles, cancelable); - } - - - override public function clone():Event - { - return new GestureTrackingEvent(type, bubbles, cancelable); - } - - - override public function toString():String - { - return formatToString("GestureTrackingEvent", "type", "bubbles", "cancelable", "eventPhase"); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/LongPressGestureEvent.as b/src/org/gestouch/events/LongPressGestureEvent.as index 0f2cba0..01cba3b 100644 --- a/src/org/gestouch/events/LongPressGestureEvent.as +++ b/src/org/gestouch/events/LongPressGestureEvent.as @@ -1,5 +1,6 @@ package org.gestouch.events { + import flash.events.Event; import flash.events.GestureEvent; @@ -10,10 +11,22 @@ package org.gestouch.events { public static const GESTURE_LONG_PRESS:String = "gestureLongPress"; - //TODO: default - public function LongPressGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = "begin", localX:Number = 0, localY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false) + + public function LongPressGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0) { - super(type, bubbles, cancelable, phase, localX, localY, ctrlKey, altKey, shiftKey); + super(type, bubbles, cancelable, phase, localX, localY); + } + + + override public function clone():Event + { + return new LongPressGestureEvent(type, bubbles, cancelable, phase, localX, localY); + } + + + override public function toString():String + { + return super.toString().replace("GestureEvent", "LongPressGestureEvent"); } } } \ No newline at end of file diff --git a/src/org/gestouch/events/MouseTouchEvent.as b/src/org/gestouch/events/MouseTouchEvent.as index 6104da6..e1b7f6c 100644 --- a/src/org/gestouch/events/MouseTouchEvent.as +++ b/src/org/gestouch/events/MouseTouchEvent.as @@ -10,34 +10,72 @@ package org.gestouch.events */ public class MouseTouchEvent extends TouchEvent { + private static const typeMap:Object = {}; + + protected var _mouseEvent:MouseEvent; + + { + MouseTouchEvent.typeMap[MouseEvent.MOUSE_DOWN] = TouchEvent.TOUCH_BEGIN; + MouseTouchEvent.typeMap[MouseEvent.MOUSE_MOVE] = TouchEvent.TOUCH_MOVE; + MouseTouchEvent.typeMap[MouseEvent.MOUSE_UP] = TouchEvent.TOUCH_END; + } + + public function MouseTouchEvent(type:String, event:MouseEvent) { super(type, event.bubbles, event.cancelable, 0, true, event.localX, event.localY, NaN, NaN, NaN, event.relatedObject, event.ctrlKey, event.altKey, event.shiftKey); - _target = event.target; - _stageX = event.stageX; - _stageY = event.stageY; + _mouseEvent = event; + } + + + public static function createMouseTouchEvent(event:MouseEvent):MouseTouchEvent + { + var type:String = MouseTouchEvent.typeMap[event.type]; + if (!type) + { + throw new Error("No match found for MouseEvent of type \"" + event.type + "\""); + } + + return new MouseTouchEvent(type, event); } - protected var _target:Object; override public function get target():Object { - return _target; + return _mouseEvent.target; + } + + + override public function get currentTarget():Object + { + return _mouseEvent.currentTarget; } - protected var _stageX:Number; override public function get stageX():Number { - return _stageX; + return _mouseEvent.stageX; } - protected var _stageY:Number; override public function get stageY():Number { - return _stageY; + return _mouseEvent.stageY; + } + + + override public function stopPropagation():void + { + super.stopPropagation(); + _mouseEvent.stopPropagation(); + } + + + override public function stopImmediatePropagation():void + { + super.stopImmediatePropagation(); + _mouseEvent.stopImmediatePropagation(); } diff --git a/src/org/gestouch/events/PanGestureEvent.as b/src/org/gestouch/events/PanGestureEvent.as new file mode 100644 index 0000000..6fb62bf --- /dev/null +++ b/src/org/gestouch/events/PanGestureEvent.as @@ -0,0 +1,32 @@ +package org.gestouch.events +{ + import flash.events.Event; + import flash.events.TransformGestureEvent; + + + /** + * @author Pavel fljot + */ + public class PanGestureEvent extends TransformGestureEvent + { + public static const GESTURE_PAN:String = "gesturePan"; + + + public function PanGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, offsetX:Number = 0, offsetY:Number = 0) + { + super(type, bubbles, cancelable, phase, localX, localY, 1, 1, 0, offsetX, offsetY); + } + + + override public function clone():Event + { + return new PanGestureEvent(type, bubbles, cancelable, phase, localX, localY, offsetX, offsetY); + } + + + override public function toString():String + { + return super.toString().replace("TransformGestureEvent", "PanGestureEvent"); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/RotateGestureEvent.as b/src/org/gestouch/events/RotateGestureEvent.as index 063efea..cb25499 100644 --- a/src/org/gestouch/events/RotateGestureEvent.as +++ b/src/org/gestouch/events/RotateGestureEvent.as @@ -1,5 +1,6 @@ package org.gestouch.events { + import flash.events.Event; import flash.events.TransformGestureEvent; @@ -11,9 +12,21 @@ package org.gestouch.events public static const GESTURE_ROTATE:String = "gestureRotate"; - public function RotateGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0, rotation:Number = 0, offsetX:Number = 0, offsetY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, commandKey:Boolean = false, controlKey:Boolean = false) + public function RotateGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, rotation:Number = 0) { - super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey); + super(type, bubbles, cancelable, phase, localX, localY, 1, 1, rotation, localX, localY); + } + + + override public function clone():Event + { + return new RotateGestureEvent(type, bubbles, cancelable, phase, localX, localY, rotation); + } + + + override public function toString():String + { + return super.toString().replace("TransformGestureEvent", "RotateGestureEvent"); } } } \ No newline at end of file diff --git a/src/org/gestouch/events/SwipeGestureEvent.as b/src/org/gestouch/events/SwipeGestureEvent.as index 469669a..92dd1e8 100644 --- a/src/org/gestouch/events/SwipeGestureEvent.as +++ b/src/org/gestouch/events/SwipeGestureEvent.as @@ -1,5 +1,6 @@ package org.gestouch.events { + import flash.events.Event; import flash.events.TransformGestureEvent; @@ -11,9 +12,21 @@ package org.gestouch.events public static const GESTURE_SWIPE:String = "gestureSwipe"; - public function SwipeGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0, rotation:Number = 0, offsetX:Number = 0, offsetY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, commandKey:Boolean = false, controlKey:Boolean = false) + public function SwipeGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, offsetX:Number = 0, offsetY:Number = 0) { - super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey); + super(type, bubbles, cancelable, phase, localX, localY, 1, 1, 0, offsetX, offsetY); + } + + + override public function clone():Event + { + return new SwipeGestureEvent(type, bubbles, cancelable, phase, localX, localY, offsetX, offsetY); + } + + + override public function toString():String + { + return super.toString().replace("TransformGestureEvent", "SwipeGestureEvent"); } } } \ No newline at end of file diff --git a/src/org/gestouch/events/TapGestureEvent.as b/src/org/gestouch/events/TapGestureEvent.as new file mode 100644 index 0000000..46a4b02 --- /dev/null +++ b/src/org/gestouch/events/TapGestureEvent.as @@ -0,0 +1,32 @@ +package org.gestouch.events +{ + import flash.events.Event; + import flash.events.GestureEvent; + + + /** + * @author Pavel fljot + */ + public class TapGestureEvent extends GestureEvent + { + public static const GESTURE_TAP:String = "gestureTap"; + + + public function TapGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0) + { + super(type, bubbles, cancelable, phase, localX, localY); + } + + + override public function clone():Event + { + return new TapGestureEvent(type, bubbles, cancelable, phase, localX, localY); + } + + + override public function toString():String + { + return super.toString().replace("GestureEvent", "TapGestureEvent"); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/ZoomGestureEvent.as b/src/org/gestouch/events/ZoomGestureEvent.as index 93b3ede..64e98ca 100644 --- a/src/org/gestouch/events/ZoomGestureEvent.as +++ b/src/org/gestouch/events/ZoomGestureEvent.as @@ -1,5 +1,6 @@ package org.gestouch.events { + import flash.events.Event; import flash.events.TransformGestureEvent; @@ -11,9 +12,21 @@ package org.gestouch.events public static const GESTURE_ZOOM:String = "gestureZoom"; - public function ZoomGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0, rotation:Number = 0, offsetX:Number = 0, offsetY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, commandKey:Boolean = false, controlKey:Boolean = false) + public function ZoomGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1, scaleY:Number = 1) { - super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey); + super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY); + } + + + override public function clone():Event + { + return new ZoomGestureEvent(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY); + } + + + override public function toString():String + { + return super.toString().replace("TransformGestureEvent", "ZoomGestureEvent"); } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/DoubleTapGesture.as b/src/org/gestouch/gestures/DoubleTapGesture.as deleted file mode 100644 index abe78f8..0000000 --- a/src/org/gestouch/gestures/DoubleTapGesture.as +++ /dev/null @@ -1,226 +0,0 @@ -package org.gestouch.gestures -{ - import org.gestouch.core.GesturesManager; - import org.gestouch.core.TouchPoint; - import org.gestouch.core.gestouch_internal; - import org.gestouch.events.DoubleTapGestureEvent; - - import flash.display.InteractiveObject; - import flash.events.GesturePhase; - import flash.events.TimerEvent; - import flash.geom.Point; - import flash.utils.Timer; - - - [Event(name="gestureDoubleTap", type="org.gestouch.events.DoubleTapGestureEvent")] - /** - * DoubleTapGesture tracks quick double-tap (double-click). - * - *

Gesture-specific configuratin properties:

- * timeThreshold — time between first touchBegin and second touchEnd events,

- * moveThreshold — maximum allowed distance between two taps.

- * - * - * @author Pavel fljot - */ - public class DoubleTapGesture extends Gesture - { - /** - * Time in milliseconds between touchBegin and touchEnd events for gesture to be detected. - * - *

For multitouch usage this is a bit more complex then "first touchBeing and second touchEnd": - * Taps are counted once minTouchPointsCount of touch points are down and then fully released. - * So it's time in milliseconds between full press and full release events for gesture to be detected.

- * - * @default 400 - */ - public var timeThreshold:uint = 400; - /** - * Maximum allowed distance between two taps for gesture to be detected. - * - * @default Gesture.DEFAULT_SLOP * 3 - * - * @see org.gestouch.gestures.Gesture#DEFAULT_SLOP - */ - public var moveThreshold:Number = Gesture.DEFAULT_SLOP * 3; - - /** - * Timer used to track time between taps. - */ - protected var _thresholdTimer:Timer; - /** - * Count taps (where tap is an action of changing _touchPointsCount from 0 to minTouchPointsCount - * and back to 0. For single touch gesture it would be common tap, for 2-touch gesture it would be - * both fingers down, then both fingers up, etc...) - */ - protected var _tapCounter:int = 0; - /** - * Flag to detect "complex tap". - */ - protected var _minTouchPointsCountReached:Boolean; - /** - * Used to check moveThreshold. - */ - protected var _prevCentralPoint:Point; - /** - * Used to check moveThreshold. - */ - protected var _lastCentralPoint:Point; - - - public function DoubleTapGesture(target:InteractiveObject = null, settings:Object = null) - { - super(target, settings); - } - - - - - //-------------------------------------------------------------------------- - // - // Static methods - // - //-------------------------------------------------------------------------- - - public static function add(target:InteractiveObject, settings:Object = null):DoubleTapGesture - { - return new DoubleTapGesture(target, settings); - } - - - public static function remove(target:InteractiveObject):DoubleTapGesture - { - return GesturesManager.gestouch_internal::removeGestureByTarget(DoubleTapGesture, target) as DoubleTapGesture; - } - - - - - //-------------------------------------------------------------------------- - // - // Public methods - // - //-------------------------------------------------------------------------- - - override public function reflect():Class - { - return DoubleTapGesture; - } - - - override public function onTouchBegin(touchPoint:TouchPoint):void - { - // No need to track more points than we need - if (_trackingPointsCount == maxTouchPointsCount) - { - return; - } - - _trackPoint(touchPoint); - - if (_trackingPointsCount == minTouchPointsCount) - { - if (!_thresholdTimer.running) - { - // first touchBegin combo (all the required fingers are on the screen) - _tapCounter = 0; - _thresholdTimer.reset(); - _thresholdTimer.delay = timeThreshold; - _thresholdTimer.start(); - _updateCentralPoint(); - } - - _minTouchPointsCountReached = true; - - if (moveThreshold > 0) - { - // calculate central point for future moveThreshold comparsion - _updateCentralPoint(); - // save points for later comparsion with moveThreshold - _prevCentralPoint = _lastCentralPoint; - _lastCentralPoint = _centralPoint.clone(); - } - } - } - - - override public function onTouchMove(touchPoint:TouchPoint):void - { - // nothing to do here - } - - - override public function onTouchEnd(touchPoint:TouchPoint):void - { - // As we a here, this means timer hasn't fired yet (and therefore hasn't cancelled this gesture) - - _forgetPoint(touchPoint); - - // if last finger released - if (_trackingPointsCount == 0) - { - if (_minTouchPointsCountReached) - { - _tapCounter++; - // reset for next "all fingers down" - _minTouchPointsCountReached = false; - } - - if (_tapCounter >= 2) - { - // double tap combo recognized - - if ((moveThreshold > 0 && _lastCentralPoint.subtract(_prevCentralPoint).length < moveThreshold) - || isNaN(moveThreshold) || moveThreshold <= 0) - { - _reset(); - _dispatch(new DoubleTapGestureEvent(DoubleTapGestureEvent.GESTURE_DOUBLE_TAP, true, false, GesturePhase.ALL, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); - } - } - } - } - - - - - //-------------------------------------------------------------------------- - // - // Protected methods - // - //-------------------------------------------------------------------------- - - override protected function _preinit():void - { - super._preinit(); - - _thresholdTimer = new Timer(timeThreshold, 1); - _thresholdTimer.addEventListener(TimerEvent.TIMER_COMPLETE, _thresholdTimer_timerCompleteHandler); - - _propertyNames.push("timeThreshold", "moveThreshold"); - } - - - override protected function _reset():void - { - super._reset(); - - _tapCounter = 0; - _minTouchPointsCountReached = false; - _thresholdTimer.reset(); - } - - - - - //-------------------------------------------------------------------------- - // - // Event handlers - // - //-------------------------------------------------------------------------- - - protected function _thresholdTimer_timerCompleteHandler(event:TimerEvent):void - { - cancel(); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/gestures/DragGesture.as b/src/org/gestouch/gestures/DragGesture.as deleted file mode 100644 index f9fbb0c..0000000 --- a/src/org/gestouch/gestures/DragGesture.as +++ /dev/null @@ -1,145 +0,0 @@ -package org.gestouch.gestures -{ - import org.gestouch.core.GesturesManager; - import org.gestouch.core.TouchPoint; - import org.gestouch.core.gestouch_internal; - import org.gestouch.events.DragGestureEvent; - - import flash.display.InteractiveObject; - import flash.events.GesturePhase; - import flash.geom.Point; - - - [Event(name="gestureDrag", type="org.gestouch.events.DragGestureEvent")] - /** - * Tracks the drag. Event works nice with minTouchPointsCount = 1 and maxTouchPoaintsCount > 1. - * - *

DragGestureEvent has 3 possible phases: GesturePhase.BEGIN, GesturePhase.UPDATE, GesturePhase.END

- * - * @see org.gestouch.events.DragGestureEvent - * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/GestureEvent.html#phase - * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/GesturePhase.html - * - * @author Pavel fljot - */ - public class DragGesture extends MovingGestureBase - { - public function DragGesture(target:InteractiveObject = null, settings:Object = null) - { - super(target, settings); - } - - - - - //-------------------------------------------------------------------------- - // - // Static methods - // - //-------------------------------------------------------------------------- - - public static function add(target:InteractiveObject, settings:Object = null):DragGesture - { - return new DragGesture(target, settings); - } - - - public static function remove(target:InteractiveObject):DragGesture - { - return GesturesManager.gestouch_internal::removeGestureByTarget(DragGesture, target) as DragGesture; - } - - - - - //-------------------------------------------------------------------------- - // - // Public methods - // - //-------------------------------------------------------------------------- - - override public function reflect():Class - { - return DragGesture; - } - - - override public function onTouchBegin(touchPoint:TouchPoint):void - { - // No need to track more points than we need - if (_trackingPointsCount == maxTouchPointsCount) - { - return; - } - - _trackPoint(touchPoint); - - if (_trackingPointsCount > 1) - { - _updateCentralPoint(); - _centralPoint.lastMove.x = _centralPoint.lastMove.y = 0; - } - } - - - override public function onTouchMove(touchPoint:TouchPoint):void - { - // do calculations only when we track enough points - if (_trackingPointsCount < minTouchPointsCount) - { - return; - } - - _updateCentralPoint(); - - if (!_slopPassed) - { - _slopPassed = _checkSlop(_centralPoint.moveOffset); - - if (_slopPassed) - { - var slopVector:Point = slop > 0 ? null : new Point(); - if (!slopVector) - { - if (_canMoveHorizontally && _canMoveVertically) - { - slopVector = _centralPoint.moveOffset.clone(); - slopVector.normalize(slop); - slopVector.x = Math.round(slopVector.x); - slopVector.y = Math.round(slopVector.y); - } - else if (_canMoveVertically) - { - slopVector = new Point(0, _centralPoint.moveOffset.y >= slop ? slop : -slop); - } - else if (_canMoveHorizontally) - { - slopVector = new Point(_centralPoint.moveOffset.x >= slop ? slop : -slop, 0); - } - } - _centralPoint.lastMove = _centralPoint.moveOffset.subtract(slopVector); - _dispatch(new DragGestureEvent(DragGestureEvent.GESTURE_DRAG, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, 1, 1, 0, _centralPoint.lastMove.x, _centralPoint.lastMove.y)); - } - } - else - { - _dispatch(new DragGestureEvent(DragGestureEvent.GESTURE_DRAG, true, false, GesturePhase.UPDATE, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, 1, 1, 0, _centralPoint.lastMove.x, _centralPoint.lastMove.y)); - } - } - - - override public function onTouchEnd(touchPoint:TouchPoint):void - { - var ending:Boolean = (_slopPassed && _trackingPointsCount == minTouchPointsCount); - _forgetPoint(touchPoint); - - _updateCentralPoint(); - - if (ending) - { - _reset(); - _dispatch(new DragGestureEvent(DragGestureEvent.GESTURE_DRAG, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, 1, 1, 0, 0, 0)); - } - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 6b2aead..571560b 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -1,14 +1,17 @@ package org.gestouch.gestures { + import org.gestouch.core.GestureState; import org.gestouch.core.GesturesManager; - import org.gestouch.core.IGesture; - import org.gestouch.core.TouchPoint; + import org.gestouch.core.IGestureDelegate; + import org.gestouch.core.IGesturesManager; + import org.gestouch.core.ITouchesManager; + import org.gestouch.core.Touch; + import org.gestouch.core.TouchesManager; import org.gestouch.core.gestouch_internal; - import org.gestouch.events.GestureTrackingEvent; + import org.gestouch.events.GestureStateEvent; - import flash.display.DisplayObjectContainer; import flash.display.InteractiveObject; - import flash.errors.IllegalOperationError; + import flash.events.Event; import flash.events.EventDispatcher; import flash.events.GestureEvent; import flash.events.TouchEvent; @@ -16,15 +19,19 @@ package org.gestouch.gestures import flash.system.Capabilities; - [Event(name="gestureTrackingBegin", type="org.gestouch.events.GestureTrackingEvent")] - [Event(name="gestureTrackingEnd", type="org.gestouch.events.GestureTrackingEvent")] +// [Event(name="gestureTrackingBegin", type="org.gestouch.events.GestureTrackingEvent")] +// [Event(name="gestureTrackingEnd", type="org.gestouch.events.GestureTrackingEvent")] + [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: locationOfTouchPoint(touchPointID):Point + * - ignoreTouch(touch:Touch, event:TouchEvent) ? + * * @author Pavel fljot */ - public class Gesture extends EventDispatcher implements IGesture + public class Gesture extends EventDispatcher { /** * Threshold for screen distance they must move to count as valid input @@ -32,82 +39,28 @@ package org.gestouch.gestures * based on 20 pixels on a 252ppi device. */ public static const DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI); + public static const TOUCH_EVENT_CAPTURE_PRIORITY:int = 10; - /** - * Array of configuration properties (Strings). - */ - protected var _propertyNames:Array = ["minTouchPointsCount", "maxTouchPointsCount"]; + + public var delegate:IGestureDelegate; + + protected const _touchesManager:ITouchesManager = TouchesManager.getInstance(); + protected const _gesturesManager:IGesturesManager = GesturesManager.getInstance(); /** * Map (generic object) of tracking touch points, where keys are touch points IDs. */ - protected var _trackingPointsMap:Object = {}; - protected var _trackingPointsCount:int = 0; - protected var _firstTouchPoint:TouchPoint; - protected var _lastLocalCentralPoint:Point; + protected var _touchesMap:Object = {}; + protected var _centralPoint:Point = new Point(); + protected var _localLocation:Point; - public function Gesture(target:InteractiveObject = null, settings:Object = null) + public function Gesture(target:InteractiveObject = null) { - // Check if gesture reflects it's class properly - reflect(); - - _preinit(); + super(); - GesturesManager.gestouch_internal::addGesture(this); + preinit(); this.target = target; - - if (settings != null) - { - _parseSettings(settings); - } - } - - - /** @private */ - private var _minTouchPointsCount:uint = 1; - /** - * Minimum amount of touch points required for gesture. - * - * @default 1 - */ - public function get minTouchPointsCount():uint - { - return _minTouchPointsCount; - } - public function set minTouchPointsCount(value:uint):void - { - if (_minTouchPointsCount == value) return; - - _minTouchPointsCount = value; - if (maxTouchPointsCount < minTouchPointsCount) - { - maxTouchPointsCount = minTouchPointsCount; - } - } - - - /** @private */ - private var _maxTouchPointsCount:uint = 1; - - /** - * Maximum amount of touch points required for gesture. - * - * @default 1 - */ - public function get maxTouchPointsCount():uint - { - return _maxTouchPointsCount; - } - public function set maxTouchPointsCount(value:uint):void - { - if (value < minTouchPointsCount) - { - throw new IllegalOperationError("maxTouchPointsCount can not be less then minTouchPointsCount"); - } - if (_maxTouchPointsCount == value) return; - - _maxTouchPointsCount = value; } @@ -131,22 +84,19 @@ package org.gestouch.gestures } public function set target(value:InteractiveObject):void { - if (target == value) return; + if (_target == value) + return; - GesturesManager.gestouch_internal::updateGestureTarget(this, target, value); - - // if GesturesManager hasn't thrown any error we can safely continue - - _uninstallTarget(target); + uninstallTarget(target); _target = value; - _installTarget(target); + installTarget(target); } /** @private */ - private var _enabled:Boolean = true; + protected var _enabled:Boolean = true; - /** + /** * @default true */ public function get enabled():Boolean @@ -155,70 +105,68 @@ package org.gestouch.gestures } public function set enabled(value:Boolean):void { - if (_enabled == value) return; + if (_enabled == value) + return; _enabled = value; - if (!_enabled && trackingPointsCount > 0) + //TODO + if (!_enabled && touchesCount > 0) { - cancel(); + setState(GestureState.CANCELLED); + reset(); } } - /** - * Storage for the trackingPoints property. - */ - protected var _trackingPoints:Vector. = new Vector.(); - /** - * Vector of tracking touch points — touch points this gesture is interested in. - * - *

For the most gestures these points are which on top of the target.

- * - * @see #isTracking() - * @see #shouldTrackPoint() - */ - public function get trackingPoints():Vector. + protected var _state:uint = GestureState.POSSIBLE; + public function get state():uint { - return _trackingPoints.concat(); + return _state; } + protected var _touchesCount:uint = 0; /** - * Amount of currently tracked touch points. Cached value of trackingPoints.length + * Amount of currently tracked touch points. * - * @see #trackingPoints + * @see #_touches */ - public function get trackingPointsCount():uint + public function get touchesCount():uint { - return _trackingPointsCount; + return _touchesCount; } - /** - * Storage for centralPoint property. - */ - protected var _centralPoint:TouchPoint; + protected var _location:Point = new Point(); /** * Virtual central touch point among all tracking touch points (geometrical center). - * - *

Designed for multitouch gestures, where center could be used for - * approximation or anchor. Use _adjustCentralPoint() method for updating centralPoint.

- * - * @see #_adjustCentralPoint() */ - public function get centralPoint():TouchPoint + public function get location():Point { - return _centralPoint; + //TODO: to clone or not clone? performance & convention or ... + return _location.clone(); } + //-------------------------------------------------------------------------- // // Public methods // //-------------------------------------------------------------------------- - + + override public function dispatchEvent(event:Event):Boolean + { + if (hasEventListener(event.type)) + { + return super.dispatchEvent(event); + } + + return true; + } + + [Abstract] /** * Reflects gesture class (for better perfomance). @@ -233,44 +181,9 @@ package org.gestouch.gestures } - /** - * Used by GesturesManager to check wether this gesture is interested in - * tracking this touch point upon this event (of type TouchEvent.TOUCH_BEGIN). - * - *

Most of the gestures check, if event.target is target or target contains event.target.

- * - *

No need to use it directly.

- * - * @see org.gestouch.core.GesturesManager - * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/TouchEvent.html - */ - public function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean + public function isTrackingTouch(touchID:uint):Boolean { - // No need to track more points than we need - if (_trackingPointsCount == maxTouchPointsCount) - { - return false; - } - //By default gesture is interested only in those touchpoints on top of target - var touchTarget:InteractiveObject = event.target as InteractiveObject; - if (touchTarget != target && !(target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(touchTarget))) - { - return false; - } - - return true; - } - - - /** - * Used by GesturesManager to check wether this gesture is tracking this touch point. - * (Not to invoke onTouchBegin, onTouchMove and onTouchEnd methods with no need) - * - * @see org.gestouch.core.GesturesManager - */ - public function isTracking(touchPointID:uint):Boolean - { - return (_trackingPointsMap[touchPointID] === true); + return (_touchesMap[touchID] != undefined); } @@ -279,23 +192,15 @@ package org.gestouch.gestures * *

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

*/ - public function cancel():void + public function reset():void { - GesturesManager.gestouch_internal::cancelGesture(this); - } - - - /** - * TODO: write description, decide wethere this API is good. - */ - public function pickAndContinue(gesture:IGesture):void - { - GesturesManager.gestouch_internal::addCurrentGesture(this); + //TODO + _location.x = 0; + _location.y = 0; + _touchesMap = {}; + _touchesCount = 0; - for each (var tp:TouchPoint in gesture.trackingPoints) - { - onTouchBegin(tp); - } + setState(GestureState.POSSIBLE); } @@ -306,69 +211,30 @@ package org.gestouch.gestures */ public function dispose():void { - _reset(); + //TODO + reset(); target = null; - try - { - GesturesManager.gestouch_internal::removeGesture(this); - } - catch (err:Error) - { - // do nothing - // GesturesManager may throw Error if this gesture is already removed: - // in case dispose() is called by GesturesManager upon GestureClass.remove(target) - - // this part smells a bit, eh? - } } - [Abstract] - /** - * Internal method, used by GesturesManager. - * - *

NB! This is abstract method and must be overridden.

- */ - public function onTouchBegin(touchPoint:TouchPoint):void + public function canBePreventedByGesture(preventingGesture:Gesture):Boolean { - + return true; } - [Abstract] - /** - * Internal method, used by GesturesManager. - * - *

NB! This is abstract method and must be overridden.

- */ - public function onTouchMove(touchPoint:TouchPoint):void + public function canPreventGesture(preventedGesture:Gesture):Boolean { + return true; } - [Abstract] - /** - * Internal method, used by GesturesManager. - * - *

NB! This is abstract method and must be overridden.

- */ - public function onTouchEnd(touchPoint:TouchPoint):void + public function requireGestureToFail(gesture:Gesture):void { - + //TODO } - /** - * Internal method, used by GesturesManager. Called upon gesture is cancelled. - * - * @see #cancel() - */ - public function onCancel():void - { - _reset(); - } - - // -------------------------------------------------------------------------- @@ -379,14 +245,8 @@ package org.gestouch.gestures /** * First method, called in constructor. - * - *

Good place to put gesture configuration related code. For example (abstract):

- * -minTouchPointsCount = 2; -_propertyNames.push("timeThreshold", "moveThreshold"); - * */ - protected function _preinit():void + protected function preinit():void { } @@ -396,214 +256,188 @@ _propertyNames.push("timeThreshold", "moveThreshold"); * * @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:InteractiveObject):void { - + if (target) + { + _gesturesManager.addGesture(this); + } } /** * Called internally when changing the target. * + *

You should remove all listeners from target here.

+ * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html */ - protected function _uninstallTarget(target:InteractiveObject):void + protected function uninstallTarget(target:InteractiveObject):void { - + if (target) + { + _gesturesManager.removeGesture(this); + } } /** - * Dispatches gesture event on gesture and on target. - * - *

Why dispatching event on gesture? Because it make sense to dispatch event from - * detector object (gesture) and we can add [Event] metatag for better autocompletion.

- * - *

Why dispatching event on target? Becase it supposed to be like this in - * comparsion to native way, and it also make sense as similar to mouse and touch events.

- * - * @param event GestureEvent to be dispatched - * - * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/GestureEvent.html + * TODO: clarify usage. For now it's supported to call this method in onTouchBegin with return. */ - protected function _dispatch(event:GestureEvent):void + protected function ignoreTouch(touch:Touch, event:TouchEvent):void { - if (hasEventListener(event.type)) + if (_touchesMap.hasOwnProperty(touch.id)) + { + delete _touchesMap[touch.id]; + _touchesCount--; + } + } + + + [Abstract] + /** + * Internal method, used by GesturesManager. + * + *

NB! This is abstract method and must be overridden.

+ */ + protected function onTouchBegin(touch:Touch, event:TouchEvent):void + { + } + + + [Abstract] + /** + * Internal method, used by GesturesManager. + * + *

NB! This is abstract method and must be overridden.

+ */ + protected function onTouchMove(touch:Touch, event:TouchEvent):void + { + } + + + [Abstract] + /** + * Internal method, used by GesturesManager. + * + *

NB! This is abstract method and must be overridden.

+ */ + protected function onTouchEnd(touch:Touch, event:TouchEvent):void + { + } + + + protected function setState(newState:uint, event:GestureEvent = null):void + { + if (_state == newState) + { + if (_state == GestureState.CHANGED && event) + { + dispatchEvent(event); + } + return; + } + + //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 (newState == GestureState.BEGAN || newState == GestureState.RECOGNIZED) + { + if (delegate && !delegate.gestureShouldBegin(this)) + { + setState(GestureState.FAILED); + return; + } + } + + var oldState:uint = _state; + _state = newState; + + if (((GestureState.CANCELLED | GestureState.RECOGNIZED | GestureState.ENDED | GestureState.FAILED) & _state) > 0) + { + _gesturesManager.scheduleGestureStateReset(this); + } + + //TODO: what if RTE happens in event handlers? + + dispatchEvent(new GestureStateEvent(GestureStateEvent.STATE_CHANGE, _state, oldState)); + if (event) { dispatchEvent(event); } - // event is almost always bubbles, so no point for optimization - target.dispatchEvent(event); - } - - - /** - * Parses settings and configures the gesture. - * - * @param settings Generic object with configuration properties - */ - protected function _parseSettings(settings:Object):void - { - for each (var propertyName:String in _propertyNames) + if (_state == GestureState.BEGAN || _state == GestureState.RECOGNIZED) { - if (settings.hasOwnProperty(propertyName)) - { - this[propertyName] = settings[propertyName]; - } + _gesturesManager.onGestureRecognized(this); } } - /** - * Saves touchPoint for tracking for the current gesture cycle. - * - *

If this is the first touch point, it updates _firstTouchPoint and _centralPoint.

- * - * @see #_firstTouchPoint - * @see #centralPoint - * @see #trackingPointsCount - */ - protected function _trackPoint(touchPoint:TouchPoint):void + gestouch_internal function setState_internal(state:uint):void { - _trackingPointsMap[touchPoint.id] = true; - var index:uint = _trackingPoints.push(touchPoint); - _trackingPointsCount++; - if (index == 1) - { - _firstTouchPoint = touchPoint; - _centralPoint = touchPoint.clone() as TouchPoint; - } - else if (_trackingPointsCount == minTouchPointsCount) - { - _updateCentralPoint(); - _centralPoint.touchBeginPos.x = _centralPoint.x; - _centralPoint.touchBeginPos.y = _centralPoint.y; - _centralPoint.moveOffset.x = 0; - _centralPoint.moveOffset.y = 0; - _centralPoint.lastMove.x = 0; - _centralPoint.lastMove.y = 0; - } - else if (_trackingPointsCount > minTouchPointsCount) - { - _adjustCentralPoint(); - } - - if (_trackingPointsCount == minTouchPointsCount) - { - if (hasEventListener(GestureTrackingEvent.GESTURE_TRACKING_BEGIN)) - { - dispatchEvent(new GestureTrackingEvent(GestureTrackingEvent.GESTURE_TRACKING_BEGIN)); - } - } + setState(state); } - /** - * Removes touchPoint from the list of tracking points. - * - *

If this is the first touch point, it updates _firstTouchPoint and _centralPoint.

- * - * @see #trackingPoints - * @see #_trackingPointsMap - * @see #trackingPointsCount - */ - protected function _forgetPoint(touchPoint:TouchPoint):void - { - delete _trackingPointsMap[touchPoint.id]; - _trackingPoints.splice(_trackingPoints.indexOf(touchPoint), 1); - _trackingPointsCount--; - - _adjustCentralPoint(); - - if (_trackingPointsCount == minTouchPointsCount - 1) - { - if (hasEventListener(GestureTrackingEvent.GESTURE_TRACKING_END)) - { - dispatchEvent(new GestureTrackingEvent(GestureTrackingEvent.GESTURE_TRACKING_END)); - } - } - } - - - /** - * Updates _centralPoint and all it's properties - * (such as positions, offsets, velocity, etc...). - * Also updates _lastLocalCentralPoint (used for dispatching events). - * - * @see #centralPoint - * @see #_lastLocalCentralPoint - * @see #trackingPoints - */ - protected function _updateCentralPoint():void + protected function updateCentralPoint():void { + var touch:Touch; var x:Number = 0; var y:Number = 0; - var velX:Number = 0; - var velY:Number = 0; - for each (var tp:TouchPoint in _trackingPoints) + for (var touchID:String in _touchesMap) { - x += tp.x; - y += tp.y; - velX += tp.velocity.x; - velY += tp.velocity.y; + touch = _touchesMap[int(touchID)] as Touch; + x += touch.x; + y += touch.y; } - x /= _trackingPointsCount; - y /= _trackingPointsCount; - var lastMoveX:Number = x - _centralPoint.x; - var lastMoveY:Number = y - _centralPoint.y; - velX /= _trackingPointsCount; - velY /= _trackingPointsCount; - - _centralPoint.x = x; - _centralPoint.y = y; - _centralPoint.lastMove.x = lastMoveX; - _centralPoint.lastMove.y = lastMoveY; - _centralPoint.velocity.x = velX; - _centralPoint.velocity.y = velY; - // tp.moveOffset = tp.subtract(tp.touchBeginPos); - _centralPoint.moveOffset.x = x - _centralPoint.touchBeginPos.x; - _centralPoint.moveOffset.y = y - _centralPoint.touchBeginPos.y; - - _lastLocalCentralPoint = target.globalToLocal(_centralPoint); - } - - - protected function _adjustCentralPoint():void - { - var oldCentralPoint:TouchPoint = _centralPoint.clone() as TouchPoint; - _updateCentralPoint(); - var centralPointChange:Point = _centralPoint.subtract(oldCentralPoint); - _centralPoint.touchBeginPos = _centralPoint.touchBeginPos.add(centralPointChange); - // fix moveOffset according to fixed touchBeginPos - _centralPoint.moveOffset.x = _centralPoint.x - _centralPoint.touchBeginPos.x; - _centralPoint.moveOffset.y = _centralPoint.y - _centralPoint.touchBeginPos.y; - // restore original lastMove - _centralPoint.lastMove.x = oldCentralPoint.lastMove.x; - _centralPoint.lastMove.y = oldCentralPoint.lastMove.y; + // faster then Math.round(x / _touchesCount) + _centralPoint.x = x > 0 ? (x / _touchesCount + 0.5) >> 0 : (x / _touchesCount - 0.5) >> 0; + _centralPoint.y = y > 0 ? (y / _touchesCount + 0.5) >> 0 : (y / _touchesCount - 0.5) >> 0; } - /** - * Reset data for the current tracking (interaction) cycle. - * - *

Clears up _trackingPointsMap, _trackingPoints, _trackingPointsCount - * and other custom gestures-specific things.

- * - *

Generally invoked in onCancel method and when certain conditions of gesture - * have been failed and gesture doesn't need to continue processsing - * (e.g. timer has completed in DoubleTapGesture)

- * - * @see #trackingPoints - * @see #trackingPointsCount - * @see #onCancel() - */ - protected function _reset():void + protected function updateLocation():void { - // forget all touch points - _trackingPointsMap = {}; - _trackingPoints.length = 0; - _trackingPointsCount = 0; + updateCentralPoint(); + _location.x = _centralPoint.x; + _location.y = _centralPoint.y; + _localLocation = target.globalToLocal(_location); + } + + + + + //-------------------------------------------------------------------------- + // + // Event handlers + // + //-------------------------------------------------------------------------- + + gestouch_internal function touchBeginHandler(touch:Touch, event:TouchEvent):void + { + _touchesMap[touch.id] = touch; + _touchesCount++; + + onTouchBegin(touch, event); + } + + + gestouch_internal function touchMoveHandler(touch:Touch, event:TouchEvent):void + { + _touchesMap[touch.id] = touch; + onTouchMove(touch, event); + } + + + gestouch_internal function touchEndHandler(touch:Touch, event:TouchEvent):void + { + delete _touchesMap[touch.id]; + _touchesCount--; + + onTouchEnd(touch, event); } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 070069b..4504d8f 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -1,149 +1,162 @@ package org.gestouch.gestures { - import org.gestouch.core.GesturesManager; - import org.gestouch.core.TouchPoint; - import org.gestouch.core.gestouch_internal; + import org.gestouch.core.GestureState; + import org.gestouch.core.Touch; import org.gestouch.events.LongPressGestureEvent; import flash.display.InteractiveObject; import flash.events.GesturePhase; import flash.events.TimerEvent; + import flash.events.TouchEvent; import flash.utils.Timer; - [Event(name="gestureLongPress", type="org.gestouch.events.LongPressGestureEvent")] /** - * + * TODO: -location + * - check on iOS (Obj-C) what happens when numTouchesRequired=2, two finger down, then quickly release one. * * @author Pavel fljot */ public class LongPressGesture extends Gesture { + public var numTouchesRequired:uint = 1; /** - * Default value 1000ms - */ - public var timeThreshold:uint = 500; - /** - * Deafult value is Gesture.DEFAULT_SLOP - * @see org.gestouchers.core.Gesture#DEFAULT_SLOP - */ + * 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; public var slop:Number = Gesture.DEFAULT_SLOP; - protected var _thresholdTimer:Timer; + protected var _timer:Timer; + protected var _touchBeginX:Array = []; + protected var _touchBeginY:Array = []; + protected var _numTouchesRequiredReached:Boolean; - public function LongPressGesture(target:InteractiveObject = null, settings:Object = null) + public function LongPressGesture(target:InteractiveObject = null) { - super(target, settings); + super(target); } - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- // - // Static methods + // Public methods // - //-------------------------------------------------------------------------- - - public static function add(target:InteractiveObject, settings:Object = null):LongPressGesture - { - return new LongPressGesture(target, settings); - } - - - public static function remove(target:InteractiveObject):LongPressGesture - { - return GesturesManager.gestouch_internal::removeGestureByTarget(LongPressGesture, target) as LongPressGesture; - } - - - - - //-------------------------------------------------------------------------- - // - // Public methods - // - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- override public function reflect():Class { - return LongPressGesture; + return TapGesture; + } + + + override public function reset():void + { + super.reset(); + + _touchBeginX.length = 0; + _touchBeginY.length = 0; + _numTouchesRequiredReached = false; + _timer.reset(); } - override public function onTouchBegin(touchPoint:TouchPoint):void + + + // -------------------------------------------------------------------------- + // + // Protected methods + // + // -------------------------------------------------------------------------- + + override protected function preinit():void { - // No need to track more points than we need - if (_trackingPointsCount == maxTouchPointsCount) + super.preinit(); + + _timer = new Timer(minPressDuration, 1); + _timer.addEventListener(TimerEvent.TIMER_COMPLETE, timer_timerCompleteHandler); + } + + + override protected function onTouchBegin(touch:Touch, event:TouchEvent):void + { + if (touchesCount > numTouchesRequired) { + if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + ignoreTouch(touch, event); + } + else + { + setState(GestureState.FAILED); + } return; } - _trackPoint(touchPoint); + _touchBeginX[touch.id] = touch.x; + _touchBeginY[touch.id] = touch.y; - if (_trackingPointsCount == minTouchPointsCount) + if (touchesCount == numTouchesRequired) { - _thresholdTimer.reset(); - _thresholdTimer.delay = timeThreshold; - _thresholdTimer.start(); - } - } - - - override public function onTouchMove(touchPoint:TouchPoint):void - { - // faster isNaN - if (_thresholdTimer.currentCount == 0 && slop == slop) - { - if (touchPoint.moveOffset.length > slop) + _numTouchesRequiredReached = true; + _timer.reset(); + _timer.delay = minPressDuration; + if (minPressDuration > 0) { - cancel(); + _timer.start(); + } + else + { + timer_timerCompleteHandler(); } } } - override public function onTouchEnd(touchPoint:TouchPoint):void - { - _forgetPoint(touchPoint); - - var held:Boolean = (_thresholdTimer.currentCount > 0); - _thresholdTimer.reset(); - - if (held) + override protected function onTouchMove(touch:Touch, event:TouchEvent):void + { + if (state == GestureState.POSSIBLE && slop > 0) { - _updateCentralPoint(); - _reset(); - _dispatch(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + // Fail if touch overcome slop distance + var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; + var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; + if (Math.sqrt(dx*dx + dy*dy) > slop) + { + setState(GestureState.FAILED); + return; + } + } + else if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + updateLocation(); + setState(GestureState.CHANGED, new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y)); } } - - - //-------------------------------------------------------------------------- - // - // Protected methods - // - //-------------------------------------------------------------------------- - - override protected function _preinit():void + override protected function onTouchEnd(touch:Touch, event:TouchEvent):void { - super._preinit(); - - _thresholdTimer = new Timer(timeThreshold, 1); - _thresholdTimer.addEventListener(TimerEvent.TIMER_COMPLETE, _onThresholdTimerComplete); - - _propertyNames.push("timeThreshold", "slop"); - } - - - override protected function _reset():void - { - super._reset(); - - _thresholdTimer.reset(); + //TODO: check proper condition (behavior) on iOS native + if (_numTouchesRequiredReached) + { + if (((GestureState.BEGAN | GestureState.CHANGED) & state) > 0) + { + updateLocation(); + setState(GestureState.ENDED, new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.END, _localLocation.x, _localLocation.y)); + } + else + { + setState(GestureState.FAILED); + } + } + else + { + setState(GestureState.FAILED); + } } @@ -154,11 +167,14 @@ package org.gestouch.gestures // Event handlers // //-------------------------------------------------------------------------- - - protected function _onThresholdTimerComplete(event:TimerEvent):void + + protected function timer_timerCompleteHandler(event:TimerEvent = null):void { - _updateCentralPoint(); - _dispatch(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + if (state == GestureState.POSSIBLE) + { + updateLocation(); + setState(GestureState.BEGAN, new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y)); + } } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/MovingGestureBase.as b/src/org/gestouch/gestures/MovingGestureBase.as deleted file mode 100644 index ae55d1d..0000000 --- a/src/org/gestouch/gestures/MovingGestureBase.as +++ /dev/null @@ -1,178 +0,0 @@ -package org.gestouch.gestures -{ - import flash.display.InteractiveObject; - import flash.geom.Point; - import org.gestouch.Direction; - - - - /** - * Base class for those gestures where you have to move finger/mouse, - * i.e. DragGesture, SwipeGesture - * - * @author Pavel fljot - */ - public class MovingGestureBase extends Gesture - { - /** - * Threshold for screen distance they must move to count as valid input - * (not an accidental offset on touch). Once this distance is passed, - * gesture starts more intensive and specific processing in onTouchMove() method. - * - * @default Gesture.DEFAULT_SLOP - * - * @see org.gestouch.gestures.Gesture#DEFAULT_SLOP - */ - public var slop:Number = Gesture.DEFAULT_SLOP; - - protected var _slopPassed:Boolean = false; - protected var _canMoveHorizontally:Boolean = true; - protected var _canMoveVertically:Boolean = true; - - - public function MovingGestureBase(target:InteractiveObject = null, settings:Object = null) - { - super(target, settings); - - if (reflect() == MovingGestureBase) - { - dispose(); - throw new Error("This is abstract class and cannot be instantiated."); - } - } - - - /** - * @private - * Storage for direction property. - */ - protected var _direction:String = Direction.ALL; - - - /** - * Allowed direction for this gesture. Used to determine slop overcome - * and could be used for specific calculations (as in SwipeGesture for example). - * - * @default Direction.ALL - * - * @see org.gestouch.Direction - * @see org.gestouch.gestures.SwipeGesture - */ - public function get direction():String - { - return _direction; - } - public function set direction(value:String):void - { - if (_direction == value) return; - - _validateDirection(value); - - _direction = value; - - _canMoveHorizontally = (_direction != Direction.VERTICAL); - _canMoveVertically = (_direction != Direction.HORIZONTAL); - } - - - - - //-------------------------------------------------------------------------- - // - // Protected methods - // - //-------------------------------------------------------------------------- - - override protected function _preinit():void - { - super._preinit(); - - _propertyNames.push("slop", "direction"); - } - - - override protected function _reset():void - { - super._reset(); - - _slopPassed = false; - } - - - /** - * Validates direction property (in setter) to help - * developer prevent accidental mistake (Strings suck). - * - * @see org.gestouch.Direction - */ - protected function _validateDirection(value:String):void - { - if (value != Direction.HORIZONTAL && - value != Direction.VERTICAL && - value != Direction.STRAIGHT_AXES && - value != Direction.DIAGONAL_AXES && - value != Direction.OCTO && - value != Direction.ALL) - { - throw new ArgumentError("Invalid direction value \"" + value + "\"."); - } - } - - - /** - * Checks wether slop has been overcome. - * Typically used in onTouchMove() method. - * - * @param moveDelta offset of touch point / central point - * starting from beginning of interaction cycle. - * - * @see #onTouchMove() - */ - protected function _checkSlop(moveDelta:Point):Boolean - { - var slopPassed:Boolean = false; - if (!(slop > 0)) - { - // return true immideately if slop is 0 or NaN - return true; - } - - if (_canMoveHorizontally && _canMoveVertically) - { - slopPassed = moveDelta.length > slop; - } - else if (_canMoveHorizontally) - { - slopPassed = Math.abs(moveDelta.x) > slop; - } - else if (_canMoveVertically) - { - slopPassed = Math.abs(moveDelta.y) > slop; - } - - if (slopPassed) - { - var slopVector:Point; - if (_canMoveHorizontally && _canMoveVertically) - { - slopVector = moveDelta.clone(); - slopVector.normalize(slop); - slopVector.x = Math.round(slopVector.x); - slopVector.y = Math.round(slopVector.y); - } - else if (_canMoveHorizontally) - { - slopVector = new Point(moveDelta.x >= slop ? slop : -slop, 0); - } - else if (_canMoveVertically) - { - slopVector = new Point(0, moveDelta.y >= slop ? slop : -slop); - } -// _gestureAnchorPoint = _touchPoint.add(slopVector); -// startGestureTrack(); - } - - return slopPassed; - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as new file mode 100644 index 0000000..b5c751d --- /dev/null +++ b/src/org/gestouch/gestures/PanGesture.as @@ -0,0 +1,196 @@ +package org.gestouch.gestures +{ + import org.gestouch.events.PanGestureEvent; + import org.gestouch.core.GestureState; + import org.gestouch.core.Touch; + import org.gestouch.events.ZoomGestureEvent; + + import flash.display.InteractiveObject; + import flash.events.GesturePhase; + import flash.events.TouchEvent; + import flash.geom.Point; + + [Event(name="gesturePan", type="org.gestouch.events.PanGestureEvent")] + /** + * TODO: + * -location + * -check native behavior on iDevice + * + * @author Pavel fljot + */ + public class PanGesture extends Gesture + { + public var slop:Number = Gesture.DEFAULT_SLOP; + + protected var _touchBeginX:Array = []; + protected var _touchBeginY:Array = []; + + + public function PanGesture(target:InteractiveObject = null) + { + super(target); + } + + + /** @private */ + private var _maxNumTouchesRequired:uint = 1; + + /** + * + */ + public function get maxNumTouchesRequired():uint + { + return _maxNumTouchesRequired; + } + public function set maxNumTouchesRequired(value:uint):void + { + if (_maxNumTouchesRequired == value) + return; + + if (value < minNumTouchesRequired) + throw ArgumentError("maxNumTouchesRequired must be not less then minNumTouchesRequired"); + + _maxNumTouchesRequired = value; + } + + + /** @private */ + private var _minNumTouchesRequired:uint = 1; + + /** + * + */ + public function get minNumTouchesRequired():uint + { + return _minNumTouchesRequired; + } + public function set minNumTouchesRequired(value:uint):void + { + if (_minNumTouchesRequired == value) + return; + + if (value > maxNumTouchesRequired) + throw ArgumentError("minNumTouchesRequired must be not greater then maxNumTouchesRequired"); + + _minNumTouchesRequired = value; + } + + + + + // -------------------------------------------------------------------------- + // + // Public methods + // + // -------------------------------------------------------------------------- + + override public function reflect():Class + { + return PanGesture; + } + + + override public function reset():void + { + _touchBeginX.length = 0; + _touchBeginY.length = 0; + + super.reset(); + } + + + + + // -------------------------------------------------------------------------- + // + // Protected methods + // + // -------------------------------------------------------------------------- + + override protected function onTouchBegin(touch:Touch, event:TouchEvent):void + { + if (touchesCount > maxNumTouchesRequired) + { + //TODO + ignoreTouch(touch, event); + return; + } + + _touchBeginX[touch.id] = touch.x; + _touchBeginY[touch.id] = touch.y; + + if (touchesCount >= minNumTouchesRequired) + { + updateLocation(); + } + } + + + override protected function onTouchMove(touch:Touch, event:TouchEvent):void + { + if (touchesCount < minNumTouchesRequired) + return; + + var prevLocationX:Number; + var prevLocationY:Number; + var offsetX:Number; + var offsetY:Number; + + if (state == GestureState.POSSIBLE) + { + // Check if finger moved enough for gesture to be recognized + var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; + var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; + if (Math.sqrt(dx*dx + dy*dy) > slop || slop != slop)//faster isNaN(slop) + { + prevLocationX = _location.x; + prevLocationY = _location.y; + updateLocation(); + offsetX = _location.x - prevLocationX; + offsetY = _location.y - prevLocationY; + // Unfortunately we create several new point instances here, + // but thats not a big deal since this code executed only once per recognition session + var offset:Point = new Point(_location.x - prevLocationX, _location.y - prevLocationY); + if (offset.length > slop) + { + var slopVector:Point = offset.clone(); + slopVector.normalize(slop); + offset = offset.subtract(slopVector); + } + + setState(GestureState.BEGAN, new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, offset.x, offset.y)); + } + } + else if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + prevLocationX = _location.x; + prevLocationY = _location.y; + updateLocation(); + offsetX = _location.x - prevLocationX; + offsetY = _location.y - prevLocationY; + + setState(GestureState.CHANGED, new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, offsetX, offsetY)); + } + } + + + override protected function onTouchEnd(touch:Touch, event:TouchEvent):void + { + if (touchesCount < minNumTouchesRequired) + { + if (state == GestureState.POSSIBLE) + { + setState(GestureState.FAILED); + } + else + { + setState(GestureState.ENDED, new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 0, 0)); + } + } + else + { + updateLocation(); + } + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index bf8d552..5c67b29 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -1,139 +1,179 @@ package org.gestouch.gestures { - import org.gestouch.GestureUtils; - import org.gestouch.core.GesturesManager; - import org.gestouch.core.TouchPoint; - import org.gestouch.core.gestouch_internal; + 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.events.GesturePhase; + import flash.events.TouchEvent; import flash.geom.Point; - [Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")] /** + * TODO: + * -location + * -check native behavior on iDevice + * * @author Pavel fljot */ public class RotateGesture extends Gesture - { + { + public var slop:Number = Gesture.DEFAULT_SLOP >> 1; - protected var _currVector:Point = new Point(); - protected var _lastVector:Point = new Point(); + protected var _touchBeginX:Array = []; + protected var _touchBeginY:Array = []; + protected var _rotationVector:Point = new Point(); + protected var _firstTouch:Touch; + protected var _secondTouch:Touch; - public function RotateGesture(target:InteractiveObject, settings:Object = null) + public function RotateGesture(target:InteractiveObject = null) { - super(target, settings); + super(target); } - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- // - // Static methods + // Public methods // - //-------------------------------------------------------------------------- - - public static function add(target:InteractiveObject = null, settings:Object = null):RotateGesture - { - return new RotateGesture(target, settings); - } - - - public static function remove(target:InteractiveObject):RotateGesture - { - return GesturesManager.gestouch_internal::removeGestureByTarget(RotateGesture, target) as RotateGesture; - } - - - - - //-------------------------------------------------------------------------- - // - // Public methods - // - //-------------------------------------------------------------------------- - - override public function onCancel():void - { - super.onCancel(); - - - } - + // -------------------------------------------------------------------------- override public function reflect():Class { return RotateGesture; } + + override public function reset():void + { + _touchBeginX.length = 0; + _touchBeginY.length = 0; + + super.reset(); + } - override public function onTouchBegin(touchPoint:TouchPoint):void + + + + // -------------------------------------------------------------------------- + // + // Protected methods + // + // -------------------------------------------------------------------------- + + override protected function onTouchBegin(touch:Touch, event:TouchEvent):void { - // No need to track more points than we need - if (_trackingPointsCount == maxTouchPointsCount) + if (touchesCount > 2) { + //TODO + ignoreTouch(touch, event); return; } - _trackPoint(touchPoint); - - if (_trackingPointsCount == minTouchPointsCount) + if (touchesCount == 1) { - _lastVector.x = _trackingPoints[1].x - _trackingPoints[0].x; - _lastVector.y = _trackingPoints[1].y - _trackingPoints[0].y; + _firstTouch = touch; + } + else + { + _secondTouch = touch; - _updateCentralPoint(); + _touchBeginX[_firstTouch.id] = _firstTouch.x; + _touchBeginY[_firstTouch.id] = _firstTouch.y; + _touchBeginX[_secondTouch.id] = _secondTouch.x; + _touchBeginY[_secondTouch.id] = _secondTouch.y; - _dispatch(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + _rotationVector.x = _secondTouch.x - _firstTouch.x; + _rotationVector.y = _secondTouch.y - _firstTouch.y; } } - override public function onTouchMove(touchPoint:TouchPoint):void + override protected function onTouchMove(touch:Touch, event:TouchEvent):void { - // do calculations only when we track enough points - if (_trackingPointsCount < minTouchPointsCount) + if (touch.id == _firstTouch.id) { - return; + _firstTouch = touch; } - - _updateCentralPoint(); - - _currVector.x = _trackingPoints[1].x - _trackingPoints[0].x; - _currVector.y = _trackingPoints[1].y - _trackingPoints[0].y; - - var a1:Number = Math.atan2(_lastVector.y, _lastVector.x); - var a2:Number = Math.atan2(_currVector.y, _currVector.x); - var angle:Number = a2 - a1; - angle *= GestureUtils.RADIANS_TO_DEGREES; - - _lastVector.x = _currVector.x; - _lastVector.y = _currVector.y; - - _dispatch(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, true, false, GesturePhase.UPDATE, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, 1, 1, angle)); - } - - - override public function onTouchEnd(touchPoint:TouchPoint):void - { - var ending:Boolean = (_trackingPointsCount == minTouchPointsCount); - _forgetPoint(touchPoint); - - if (ending) + else { - _dispatch(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + _secondTouch = touch; + } + + if (touchesCount == 2) + { + var currRotationVector:Point = new Point(_secondTouch.x - _firstTouch.x, _secondTouch.y - _firstTouch.y); + var recognized:Boolean; + + if (state == GestureState.POSSIBLE) + { + // we start once any finger moved enough + var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; + var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; + if (Math.sqrt(dx*dx + dy*dy) > slop || slop != slop)//faster isNaN(slop) + { + recognized = true; + _rotationVector.x = _secondTouch.x - _firstTouch.x; + _rotationVector.y = _secondTouch.y - _firstTouch.y; + } + } + else + { + recognized = true; + } + + if (recognized) + { + updateLocation(); + + var rotation:Number = Math.atan2(currRotationVector.y, currRotationVector.x) - Math.atan2(_rotationVector.y, _rotationVector.x); + rotation *= GestureUtils.RADIANS_TO_DEGREES; + _rotationVector.x = currRotationVector.x; + _rotationVector.y = currRotationVector.y; + + if (state == GestureState.POSSIBLE) + { + setState(GestureState.BEGAN, new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, rotation)); + } + else + { + setState(GestureState.CHANGED, new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, rotation)); + } + } } } - override protected function _preinit():void + override protected function onTouchEnd(touch:Touch, event:TouchEvent):void { - super._preinit(); - - minTouchPointsCount = 2; + if (touchesCount == 0) + { + if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + setState(GestureState.ENDED, new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 0)); + } + else if (state == GestureState.POSSIBLE) + { + setState(GestureState.FAILED); + } + } + else// == 1 + { + if (touch.id == _firstTouch.id) + { + _firstTouch = _secondTouch; + } + if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + updateLocation(); + setState(GestureState.CHANGED, new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, 0)); + } + } } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index d09d842..2ddc24f 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -1,180 +1,184 @@ package org.gestouch.gestures { - import org.gestouch.Direction; - import org.gestouch.GestureUtils; - import org.gestouch.core.GesturesManager; - import org.gestouch.core.TouchPoint; - import org.gestouch.core.gestouch_internal; + import org.gestouch.core.GestureState; + import org.gestouch.core.Touch; import org.gestouch.events.SwipeGestureEvent; import flash.display.InteractiveObject; import flash.events.GesturePhase; + import flash.events.TouchEvent; import flash.geom.Point; - import flash.utils.getTimer; - + import flash.system.Capabilities; [Event(name="gestureSwipe", type="org.gestouch.events.SwipeGestureEvent")] /** - * SwipeGesture detects swipe motion (also known as flick or flig). - * - *

I couldn't find any certain definition of Swipe except for it's defined as quick. - * So I've implemented detection via two threshold velocities — one is in the direction of the movement, - * and second is the "side"-one (orthogonal). They form a velocity rectangle, where you have to move - * with a velocity greater then velocityThreshold value and less then sideVelocityThreshold.

+ * TODO: + * -check native behavior on iDevice * * @author Pavel fljot */ - public class SwipeGesture extends MovingGestureBase + public class SwipeGesture extends Gesture { - public var moveThreshold:Number = Gesture.DEFAULT_SLOP; - public var minTimeThreshold:uint = 50; - public var velocityThreshold:Number = 7 * GestureUtils.IPS_TO_PPMS; - public var sideVelocityThreshold:Number = 2 * GestureUtils.IPS_TO_PPMS; + public var numTouchesRequired:uint = 1; + public var velocityThreshold:Number = 0.1; + public var minVelocity:Number = 1.5; + public var minDistance:Number = Capabilities.screenDPI * 0.5; - protected var _startTime:uint; + public var direction:uint = SwipeGestureDirection.ORTHOGONAL; + + protected var _offset:Point = new Point(); + protected var _startTime:int; + protected var _noDirection:Boolean; - public function SwipeGesture(target:InteractiveObject = null, settings:Object = null) + public function SwipeGesture(target:InteractiveObject = null) { - super(target, settings); + super(target); } - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- // - // Static methods + // Public methods // - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- - public static function add(target:InteractiveObject, settings:Object = null):SwipeGesture - { - return new SwipeGesture(target, settings); - } - - - public static function remove(target:InteractiveObject):SwipeGesture - { - return GesturesManager.gestouch_internal::removeGestureByTarget(SwipeGesture, target) as SwipeGesture; - } - - - - - //-------------------------------------------------------------------------- - // - // Public methods - // - //-------------------------------------------------------------------------- - override public function reflect():Class { return SwipeGesture; } - - override public function onTouchBegin(touchPoint:TouchPoint):void - { - // No need to track more points than we need - if (_trackingPointsCount == maxTouchPointsCount) - { - return; - } - _trackPoint(touchPoint); + override public function reset():void + { + _startTime = 0; + _offset.x = 0; + _offset.y = 0; + + super.reset(); } - override public function onTouchMove(touchPoint:TouchPoint):void + + + // -------------------------------------------------------------------------- + // + // Protected methods + // + // -------------------------------------------------------------------------- + + override protected function onTouchBegin(touch:Touch, event:TouchEvent):void { - // do calculations only when we track enought points - if (_trackingPointsCount < minTouchPointsCount) + if (touchesCount > numTouchesRequired) { + setState(GestureState.FAILED); return; } - _updateCentralPoint(); - - if (!_slopPassed) + if (touchesCount == numTouchesRequired) { - _slopPassed = _checkSlop(_centralPoint.moveOffset); + updateLocation(); + _startTime = touch.time; + + // cache direction condition for performance + _noDirection = (SwipeGestureDirection.ORTHOGONAL & direction) == 0; + } + } + + + override protected function onTouchMove(touch:Touch, event:TouchEvent):void + { + if (touchesCount < numTouchesRequired) + return; + + updateCentralPoint(); + + _offset.x = _centralPoint.x - _location.x; + _offset.y = _centralPoint.y - _location.y; + var offsetLength:Number = _offset.length; + var timeDelta:int = touch.time - _startTime; + var vel:Number = offsetLength / timeDelta; + var absVel:Number = vel > 0 ? vel : -vel;//faster Math.abs() +// trace(_offset, _offset.length, ".....velocity:", vel); + + if (offsetLength > Gesture.DEFAULT_SLOP && absVel < velocityThreshold) + { + setState(GestureState.FAILED); + return; } - if (_slopPassed) + var velX:Number = _offset.x / timeDelta; + var velY:Number = _offset.y / timeDelta; + + + if (_noDirection) { - var velocity:Point = _centralPoint.velocity; - - var foo:Number = _centralPoint.moveOffset.length;//FIXME! - var swipeDetected:Boolean = false; - - if (getTimer() - _startTime > minTimeThreshold && foo > 10) + if (absVel >= minVelocity || (minDistance != minDistance || offsetLength >= minDistance)) { - var lastMoveX:Number = 0; - var lastMoveY:Number = 0; - - if (_canMoveHorizontally && _canMoveVertically) - { - lastMoveX = _centralPoint.lastMove.x; - lastMoveY = _centralPoint.lastMove.y; - - if (direction == Direction.STRAIGHT_AXES) - { - // go to logic below: if (!swipeDetected && _canMove*).. - } - else if (direction == Direction.OCTO) - { - swipeDetected = velocity.length >= velocityThreshold; - - if (Math.abs(velocity.y) < sideVelocityThreshold) - { - // horizontal swipe - lastMoveY = 0; - } - else if (Math.abs(velocity.x) < sideVelocityThreshold) - { - // vertical swipe - lastMoveX = 0; - } - } - else - { - // free direction swipe - swipeDetected = velocity.length >= velocityThreshold; - } - } - - if (!swipeDetected && _canMoveHorizontally) - { - swipeDetected = Math.abs(velocity.x) >= velocityThreshold && - Math.abs(velocity.y) < sideVelocityThreshold; - - lastMoveX = _centralPoint.lastMove.x; - lastMoveY = 0; - } - if (!swipeDetected && _canMoveVertically) - { - swipeDetected = Math.abs(velocity.y) >= velocityThreshold && - Math.abs(velocity.x) < sideVelocityThreshold; - - lastMoveX = 0; - lastMoveY = _centralPoint.lastMove.y; - } - - if (swipeDetected) - { - _reset(); -// trace("swipe detected:", lastMoveX, lastMoveY); - _dispatch(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, true, false, GesturePhase.ALL, target.mouseX, target.mouseY, 1, 1, 0, lastMoveX, lastMoveY)); - } + setState(GestureState.RECOGNIZED, new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); } } - } + else + { + //faster Math.abs() + var absVelX:Number = velX > 0 ? velX : -velX; + var absVelY:Number = velY > 0 ? velY : -velY; + var absOffsetX:Number = _offset.x > 0 ? _offset.x : -_offset.x; + var absOffsetY:Number = _offset.y > 0 ? _offset.y : -_offset.y; + + if (absVelX > absVelY) + { + 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 (velX < 0 && (direction & SwipeGestureDirection.LEFT) == 0) + { + setState(GestureState.FAILED); + } + else if (velX > 0 && (direction & SwipeGestureDirection.RIGHT) == 0) + { + setState(GestureState.FAILED); + } + else if (absVelX >= minVelocity || (minDistance != minDistance || absOffsetX >= minDistance)) + { + setState(GestureState.RECOGNIZED, new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, _offset.x, 0)); + } + } + else if (absVelY > absVelX) + { + 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 (velY < 0 && (direction & SwipeGestureDirection.UP) == 0) + { + setState(GestureState.FAILED); + } + else if (velY > 0 && (direction & SwipeGestureDirection.DOWN) == 0) + { + setState(GestureState.FAILED); + } + else if (absVelY >= minVelocity || (minDistance != minDistance || absOffsetY >= minDistance)) + { + setState(GestureState.RECOGNIZED, new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, 0, _offset.y)); + } + } + else + { + setState(GestureState.FAILED); + } + } + } - override public function onTouchEnd(touchPoint:TouchPoint):void + override protected function onTouchEnd(touch:Touch, event:TouchEvent):void { - _forgetPoint(touchPoint); + setState(GestureState.FAILED); } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/SwipeGestureDirection.as b/src/org/gestouch/gestures/SwipeGestureDirection.as new file mode 100644 index 0000000..20d2095 --- /dev/null +++ b/src/org/gestouch/gestures/SwipeGestureDirection.as @@ -0,0 +1,18 @@ +package org.gestouch.gestures +{ + /** + * @author Pavel fljot + */ + public class SwipeGestureDirection + { + public static const RIGHT:uint = 1 << 0; + public static const LEFT:uint = 1 << 1; + public static const UP:uint = 1 << 2; + public static const DOWN:uint = 1 << 3; + + public static const NO_DIRECTION:uint = 0; + public static const HORIZONTAL:uint = RIGHT | LEFT; + public static const VERTICAL:uint = UP | DOWN; + public static const ORTHOGONAL:uint = RIGHT | LEFT | UP | DOWN; + } +} \ No newline at end of file diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as new file mode 100644 index 0000000..e6254d4 --- /dev/null +++ b/src/org/gestouch/gestures/TapGesture.as @@ -0,0 +1,181 @@ +package org.gestouch.gestures +{ + import org.gestouch.core.GestureState; + import org.gestouch.core.Touch; + import org.gestouch.events.TapGestureEvent; + + import flash.display.InteractiveObject; + import flash.events.GesturePhase; + import flash.events.TimerEvent; + import flash.events.TouchEvent; + import flash.utils.Timer; + + + /** + * TODO: check failing conditions (iDevice) + * + * @author Pavel fljot + */ + public class TapGesture extends Gesture + { + public var numTouchesRequired:uint = 1; + public var numTapsRequired:uint = 1; + public var slop:Number = Gesture.DEFAULT_SLOP; + public var maxTapDelay:uint = 400; + public var maxTapDuration:uint = 1500; + + protected var _timer:Timer; + protected var _touchBeginX:Array = []; + protected var _touchBeginY:Array = []; + protected var _numTouchesRequiredReached:Boolean; + protected var _tapCounter:uint = 0; + + + public function TapGesture(target:InteractiveObject = null) + { + super(target); + } + + + + + // -------------------------------------------------------------------------- + // + // Public methods + // + // -------------------------------------------------------------------------- + + override public function reflect():Class + { + return TapGesture; + } + + + override public function reset():void + { + _touchBeginX.length = 0; + _touchBeginY.length = 0; + _numTouchesRequiredReached = false; + _tapCounter = 0; + _timer.reset(); + + super.reset(); + } + + + override public function canPreventGesture(preventedGesture:Gesture):Boolean + { + if (preventedGesture is TapGesture && + (preventedGesture as TapGesture).numTapsRequired > this.numTapsRequired) + { + return false; + } + return true; + } + + + + + // -------------------------------------------------------------------------- + // + // Protected methods + // + // -------------------------------------------------------------------------- + + override protected function preinit():void + { + super.preinit(); + + _timer = new Timer(maxTapDelay, 1); + _timer.addEventListener(TimerEvent.TIMER_COMPLETE, timer_timerCompleteHandler); + } + + + override protected function onTouchBegin(touch:Touch, event:TouchEvent):void + { + if (touchesCount > numTouchesRequired) + { + // We put more fingers then required at the same time, + // so treat that as failed + setState(GestureState.FAILED); + return; + } + + _touchBeginX[touch.id] = touch.x; + _touchBeginY[touch.id] = touch.y; + + if (touchesCount == 1) + { + _timer.reset(); + _timer.delay = maxTapDuration; + _timer.start(); + } + + if (touchesCount == numTouchesRequired) + { + _numTouchesRequiredReached = true; + } + } + + + override protected function onTouchMove(touch:Touch, event:TouchEvent):void + { + if (slop >= 0) + { + // Fail if touch overcome slop distance + var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; + var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; + if (Math.sqrt(dx*dx + dy*dy) > slop) + { + setState(GestureState.FAILED); + } + } + } + + + override protected function onTouchEnd(touch:Touch, event:TouchEvent):void + { + if (!_numTouchesRequiredReached) + { + //TODO: check this condition on iDevice + setState(GestureState.FAILED); + } + else if (touchesCount == 0) + { + // reset flag for the next "full press" cycle + _numTouchesRequiredReached = false; + + _tapCounter++; + _timer.reset(); + + if (_tapCounter == numTapsRequired) + { + updateLocation(); + setState(GestureState.RECOGNIZED, new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y)); + } + else + { + _timer.delay = maxTapDelay; + _timer.start(); + } + } + } + + + + + //-------------------------------------------------------------------------- + // + // 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/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 2508432..5a47d48 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -1,140 +1,189 @@ package org.gestouch.gestures { - import org.gestouch.core.GesturesManager; - import org.gestouch.core.TouchPoint; - import org.gestouch.core.gestouch_internal; + import org.gestouch.core.GestureState; + import org.gestouch.core.Touch; import org.gestouch.events.ZoomGestureEvent; import flash.display.InteractiveObject; import flash.events.GesturePhase; + import flash.events.TouchEvent; import flash.geom.Point; - [Event(name="gestureZoom", type="org.gestouch.events.ZoomGestureEvent")] /** + * TODO: + * -location + * -check native behavior on iDevice + * * @author Pavel fljot */ public class ZoomGesture extends Gesture - { + { + public var slop:Number = Gesture.DEFAULT_SLOP >> 1; public var lockAspectRatio:Boolean = true; - protected var _currVector:Point = new Point(); - protected var _lastVector:Point = new Point(); + protected var _touchBeginX:Array = []; + protected var _touchBeginY:Array = []; + protected var _scaleVector:Point = new Point(); + protected var _firstTouch:Touch; + protected var _secondTouch:Touch; - public function ZoomGesture(target:InteractiveObject, settings:Object = null) + public function ZoomGesture(target:InteractiveObject = null) { - super(target, settings); + super(target); } - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- // - // Static methods + // Public methods // - //-------------------------------------------------------------------------- - - public static function add(target:InteractiveObject = null, settings:Object = null):ZoomGesture - { - return new ZoomGesture(target, settings); - } - - - public static function remove(target:InteractiveObject):ZoomGesture - { - return GesturesManager.gestouch_internal::removeGestureByTarget(ZoomGesture, target) as ZoomGesture; - } - - - - - //-------------------------------------------------------------------------- - // - // Public methods - // - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- override public function reflect():Class { return ZoomGesture; } + + override public function reset():void + { + _touchBeginX.length = 0; + _touchBeginY.length = 0; + + super.reset(); + } - override public function onTouchBegin(touchPoint:TouchPoint):void + + + + // -------------------------------------------------------------------------- + // + // Protected methods + // + // -------------------------------------------------------------------------- + + override protected function onTouchBegin(touch:Touch, event:TouchEvent):void { - // No need to track more points than we need - if (_trackingPointsCount == maxTouchPointsCount) + if (touchesCount > 2) { + //TODO + ignoreTouch(touch, event); return; } - _trackPoint(touchPoint); - - if (_trackingPointsCount == minTouchPointsCount) + if (touchesCount == 1) { - _lastVector.x = _trackingPoints[1].x - _trackingPoints[0].x; - _lastVector.y = _trackingPoints[1].y - _trackingPoints[0].y; + _firstTouch = touch; + } + else// == 2 + { + _secondTouch = touch; - _updateCentralPoint(); + _touchBeginX[_firstTouch.id] = _firstTouch.x; + _touchBeginY[_firstTouch.id] = _firstTouch.y; + _touchBeginX[_secondTouch.id] = _secondTouch.x; + _touchBeginY[_secondTouch.id] = _secondTouch.y; - _dispatch(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + _scaleVector.x = _secondTouch.x - _firstTouch.x; + _scaleVector.y = _secondTouch.y - _firstTouch.y; } } - override public function onTouchMove(touchPoint:TouchPoint):void + override protected function onTouchMove(touch:Touch, event:TouchEvent):void { - // do calculations only when we track enought points - if (_trackingPointsCount < minTouchPointsCount) + if (touch.id == _firstTouch.id) { - return; - } - - _updateCentralPoint(); - - _currVector.x = _trackingPoints[1].x - _trackingPoints[0].x; - _currVector.y = _trackingPoints[1].y - _trackingPoints[0].y; - - var scaleX:Number = _currVector.x / _lastVector.x; - var scaleY:Number = _currVector.y / _lastVector.y; - if (lockAspectRatio) - { - scaleX = scaleY = _currVector.length / _lastVector.length; + _firstTouch = touch; } else { - scaleX = _currVector.x / _lastVector.x; - scaleY = _currVector.y / _lastVector.y; + _secondTouch = touch; } - _lastVector.x = _currVector.x; - _lastVector.y = _currVector.y; - - _dispatch(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, true, false, GesturePhase.UPDATE, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, scaleX, scaleY)); - } - - - override public function onTouchEnd(touchPoint:TouchPoint):void - { - var ending:Boolean = (_trackingPointsCount == minTouchPointsCount); - _forgetPoint(touchPoint); - - if (ending) + if (touchesCount == 2) { - _dispatch(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + var currScaleVector:Point = new Point(_secondTouch.x - _firstTouch.x, _secondTouch.y - _firstTouch.y); + var recognized:Boolean; + + if (state == GestureState.POSSIBLE) + { + // Check if finger moved enough for gesture to be recognized + var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; + var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; + if (Math.sqrt(dx*dx + dy*dy) > slop || slop != slop)//faster isNaN(slop) + { + recognized = true; + _scaleVector.x = _secondTouch.x - _firstTouch.x; + _scaleVector.y = _secondTouch.y - _firstTouch.y; + } + } + else + { + recognized = true; + } + + if (recognized) + { + updateLocation(); + + var scaleX:Number; + var scaleY:Number; + if (lockAspectRatio) + { + scaleX = scaleY = currScaleVector.length / _scaleVector.length; + } + else + { + scaleX = currScaleVector.x / _scaleVector.x; + scaleY = currScaleVector.y / _scaleVector.y; + } + + _scaleVector.x = currScaleVector.x; + _scaleVector.y = currScaleVector.y; + + if (state == GestureState.POSSIBLE) + { + setState(GestureState.BEGAN, new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, scaleX, scaleY)); + } + else + { + setState(GestureState.CHANGED, new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, scaleX, scaleY)); + } + } } } - override protected function _preinit():void + override protected function onTouchEnd(touch:Touch, event:TouchEvent):void { - super._preinit(); - - minTouchPointsCount = 2; - - _propertyNames.push("lockAspectRatio"); + if (touchesCount == 0) + { + if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + setState(GestureState.ENDED, new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 1, 1)); + } + else if (state == GestureState.POSSIBLE) + { + setState(GestureState.FAILED); + } + } + else//== 1 + { + if (touch.id == _firstTouch.id) + { + _firstTouch = _secondTouch; + } + if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + updateLocation(); + setState(GestureState.CHANGED, new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, 1, 1)); + } + } } } } \ No newline at end of file diff --git a/src/org/gestouch/GestureUtils.as b/src/org/gestouch/utils/GestureUtils.as similarity index 96% rename from src/org/gestouch/GestureUtils.as rename to src/org/gestouch/utils/GestureUtils.as index abbc265..4d57d69 100644 --- a/src/org/gestouch/GestureUtils.as +++ b/src/org/gestouch/utils/GestureUtils.as @@ -1,4 +1,4 @@ -package org.gestouch +package org.gestouch.utils { import flash.system.Capabilities; /** diff --git a/src/org/gestouch/utils/ObjectPool.as b/src/org/gestouch/utils/ObjectPool.as deleted file mode 100644 index 8c920c0..0000000 --- a/src/org/gestouch/utils/ObjectPool.as +++ /dev/null @@ -1,131 +0,0 @@ -package org.gestouch.utils -{ - import flash.utils.getQualifiedClassName; - - /** - * @author Pavel fljot - * - * "inspired" by Jonnie Hallman - * @link http://destroytoday.com - * @link https://github.com/destroytoday - * - * Added some optimization and changes. - */ - public class ObjectPool - { - // -------------------------------------------------------------------------- - // - // Properties - // - // -------------------------------------------------------------------------- - - protected var _type:Class; - protected var objectList:Array = []; - - - // -------------------------------------------------------------------------- - // - // Constructor - // - // -------------------------------------------------------------------------- - - public function ObjectPool(type:Class, size:uint = 0) - { - _type = type; - - if (size > 0) - { - allocate(size); - } - } - - - - - // -------------------------------------------------------------------------- - // - // Getters / Setters - // - // -------------------------------------------------------------------------- - - public function get type():Class - { - return _type; - } - - - public function get numObjects():uint - { - return objectList.length; - } - - - - // -------------------------------------------------------------------------- - // - // Public Methods - // - // -------------------------------------------------------------------------- - - public function hasObject(object:Object):Boolean - { - return objectList.indexOf(object) > -1; - } - - - public function getObject():* - { - return numObjects > 0 ? objectList.pop() : createObject(); - } - - - public function disposeObject(object:Object):void - { - if (!(object is type)) - { - throw new TypeError("Disposed object type mismatch. Expected " + type + ", got " + getQualifiedClassName(object)); - } - - addObject(object); - } - - - public function empty():void - { - objectList.length = 0; - } - - - - //-------------------------------------------------------------------------- - // - // Protected methods - // - //-------------------------------------------------------------------------- - - protected function addObject(object:Object):* - { - if (!hasObject(object)) - objectList[objectList.length] = object; - - return object; - } - - - protected function createObject():* - { - return new type(); - } - - - protected function allocate(value:uint):void - { - var n:int = value - numObjects; - - while (n-- > 0) - { - addObject(createObject()); - } - } - } -} \ No newline at end of file From 9d9fcd20bad796c5ab136b5b010616ed63038695 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 22 Nov 2011 10:40:42 +0200 Subject: [PATCH 04/87] Fix central point calculation for more precise transformations --- src/org/gestouch/gestures/Gesture.as | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 571560b..4caa21a 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -393,9 +393,8 @@ package org.gestouch.gestures x += touch.x; y += touch.y; } - // faster then Math.round(x / _touchesCount) - _centralPoint.x = x > 0 ? (x / _touchesCount + 0.5) >> 0 : (x / _touchesCount - 0.5) >> 0; - _centralPoint.y = y > 0 ? (y / _touchesCount + 0.5) >> 0 : (y / _touchesCount - 0.5) >> 0; + _centralPoint.x = x / _touchesCount; + _centralPoint.y = y / _touchesCount; } From d950550d160becb703f39d726d452e3f14f5d5e8 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 30 Dec 2011 03:19:56 +0200 Subject: [PATCH 05/87] Catch all the Touch/Mouse events in capture phase GesturesManager now captures TOUCH_BEGIN or MOUSE_DOWN from the stage in capture phase to be able to prevent their propagation at the target without affecting the gesture regoznition. --- src/org/gestouch/core/GesturesManager.as | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index c3566df..d1e8a68 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -182,25 +182,26 @@ package org.gestouch.core if (Multitouch.supportsTouchEvents) { - stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler); + stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); } else { - stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); + stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); } } protected function installStageListeners():void { + //TODO: maximum priority to prevent event hijacking? if (Multitouch.supportsTouchEvents) { - _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler); + _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); } else { - _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler); + _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); } } @@ -210,12 +211,12 @@ package org.gestouch.core { if (Multitouch.supportsTouchEvents) { - _stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler); + _stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); _stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); } else { - _stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler); + _stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); _stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); } } From 3e1b5948b2d898da78baa1e3a0f6ea26c33d3805 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 30 Dec 2011 16:47:52 +0200 Subject: [PATCH 06/87] Small optimization for PanGesture --- src/org/gestouch/gestures/PanGesture.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index b5c751d..fcb3c49 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -150,7 +150,7 @@ package org.gestouch.gestures offsetY = _location.y - prevLocationY; // Unfortunately we create several new point instances here, // but thats not a big deal since this code executed only once per recognition session - var offset:Point = new Point(_location.x - prevLocationX, _location.y - prevLocationY); + var offset:Point = new Point(offsetX, offsetY); if (offset.length > slop) { var slopVector:Point = offset.clone(); From 47f2f848e43518f4ce61158df2b5c42cacb8d036 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 30 Dec 2011 18:26:16 +0200 Subject: [PATCH 07/87] Optimized event dispatching --- src/org/gestouch/gestures/Gesture.as | 30 +++++-------------- src/org/gestouch/gestures/LongPressGesture.as | 15 ++++++++-- src/org/gestouch/gestures/PanGesture.as | 15 ++++++++-- src/org/gestouch/gestures/RotateGesture.as | 20 ++++++++++--- src/org/gestouch/gestures/SwipeGesture.as | 15 ++++++++-- src/org/gestouch/gestures/TapGesture.as | 5 +++- src/org/gestouch/gestures/ZoomGesture.as | 20 ++++++++++--- 7 files changed, 80 insertions(+), 40 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 4caa21a..56c2c6d 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -156,17 +156,6 @@ package org.gestouch.gestures // //-------------------------------------------------------------------------- - override public function dispatchEvent(event:Event):Boolean - { - if (hasEventListener(event.type)) - { - return super.dispatchEvent(event); - } - - return true; - } - - [Abstract] /** * Reflects gesture class (for better perfomance). @@ -327,15 +316,11 @@ package org.gestouch.gestures } - protected function setState(newState:uint, event:GestureEvent = null):void + protected function setState(newState:uint):Boolean { - if (_state == newState) + if (_state == newState && _state == GestureState.CHANGED) { - if (_state == GestureState.CHANGED && event) - { - dispatchEvent(event); - } - return; + return true; } //TODO: is state sequence validation needed? e.g.: @@ -349,7 +334,7 @@ package org.gestouch.gestures if (delegate && !delegate.gestureShouldBegin(this)) { setState(GestureState.FAILED); - return; + return false; } } @@ -363,16 +348,17 @@ package org.gestouch.gestures //TODO: what if RTE happens in event handlers? - dispatchEvent(new GestureStateEvent(GestureStateEvent.STATE_CHANGE, _state, oldState)); - if (event) + if (hasEventListener(GestureStateEvent.STATE_CHANGE)) { - dispatchEvent(event); + dispatchEvent(new GestureStateEvent(GestureStateEvent.STATE_CHANGE, _state, oldState)); } if (_state == GestureState.BEGAN || _state == GestureState.RECOGNIZED) { _gesturesManager.onGestureRecognized(this); } + + return true; } diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 4504d8f..9307ef9 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -133,7 +133,10 @@ package org.gestouch.gestures else if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); - setState(GestureState.CHANGED, new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y)); + if (setState(GestureState.CHANGED) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) + { + dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y)); + } } } @@ -146,7 +149,10 @@ package org.gestouch.gestures if (((GestureState.BEGAN | GestureState.CHANGED) & state) > 0) { updateLocation(); - setState(GestureState.ENDED, new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.END, _localLocation.x, _localLocation.y)); + if (setState(GestureState.ENDED) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) + { + dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.END, _localLocation.x, _localLocation.y)); + } } else { @@ -173,7 +179,10 @@ package org.gestouch.gestures if (state == GestureState.POSSIBLE) { updateLocation(); - setState(GestureState.BEGAN, new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y)); + if (setState(GestureState.BEGAN) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) + { + dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y)); + } } } } diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index fcb3c49..1830e03 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -158,7 +158,10 @@ package org.gestouch.gestures offset = offset.subtract(slopVector); } - setState(GestureState.BEGAN, new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, offset.x, offset.y)); + if (setState(GestureState.BEGAN) && hasEventListener(PanGestureEvent.GESTURE_PAN)) + { + dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, offset.x, offset.y)); + } } } else if (state == GestureState.BEGAN || state == GestureState.CHANGED) @@ -169,7 +172,10 @@ package org.gestouch.gestures offsetX = _location.x - prevLocationX; offsetY = _location.y - prevLocationY; - setState(GestureState.CHANGED, new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, offsetX, offsetY)); + if (setState(GestureState.CHANGED) && hasEventListener(PanGestureEvent.GESTURE_PAN)) + { + dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, offsetX, offsetY)); + } } } @@ -184,7 +190,10 @@ package org.gestouch.gestures } else { - setState(GestureState.ENDED, new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 0, 0)); + if (setState(GestureState.ENDED) && hasEventListener(PanGestureEvent.GESTURE_PAN)) + { + dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 0, 0)); + } } } else diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 5c67b29..c3c3960 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -138,11 +138,17 @@ package org.gestouch.gestures if (state == GestureState.POSSIBLE) { - setState(GestureState.BEGAN, new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, rotation)); + if (setState(GestureState.BEGAN) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) + { + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, rotation)); + } } else { - setState(GestureState.CHANGED, new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, rotation)); + if (setState(GestureState.CHANGED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) + { + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, rotation)); + } } } } @@ -155,7 +161,10 @@ package org.gestouch.gestures { if (state == GestureState.BEGAN || state == GestureState.CHANGED) { - setState(GestureState.ENDED, new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 0)); + if (setState(GestureState.ENDED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) + { + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 0)); + } } else if (state == GestureState.POSSIBLE) { @@ -171,7 +180,10 @@ package org.gestouch.gestures if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); - setState(GestureState.CHANGED, new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, 0)); + if (setState(GestureState.CHANGED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) + { + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, 0)); + } } } } diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 2ddc24f..06ec8f1 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -117,7 +117,10 @@ package org.gestouch.gestures { if (absVel >= minVelocity || (minDistance != minDistance || offsetLength >= minDistance)) { - setState(GestureState.RECOGNIZED, new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); + if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) + { + dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); + } } } else @@ -145,7 +148,10 @@ package org.gestouch.gestures } else if (absVelX >= minVelocity || (minDistance != minDistance || absOffsetX >= minDistance)) { - setState(GestureState.RECOGNIZED, new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, _offset.x, 0)); + if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) + { + dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, _offset.x, 0)); + } } } else if (absVelY > absVelX) @@ -165,7 +171,10 @@ package org.gestouch.gestures } else if (absVelY >= minVelocity || (minDistance != minDistance || absOffsetY >= minDistance)) { - setState(GestureState.RECOGNIZED, new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, 0, _offset.y)); + if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) + { + dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, 0, _offset.y)); + } } } else diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index e6254d4..afbf9a6 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -151,7 +151,10 @@ package org.gestouch.gestures if (_tapCounter == numTapsRequired) { updateLocation(); - setState(GestureState.RECOGNIZED, new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y)); + if (setState(GestureState.RECOGNIZED) && hasEventListener(TapGestureEvent.GESTURE_TAP)) + { + dispatchEvent(new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y)); + } } else { diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 5a47d48..5ac4edd 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -148,11 +148,17 @@ package org.gestouch.gestures if (state == GestureState.POSSIBLE) { - setState(GestureState.BEGAN, new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, scaleX, scaleY)); + if (setState(GestureState.BEGAN) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) + { + dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, scaleX, scaleY)); + } } else { - setState(GestureState.CHANGED, new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, scaleX, scaleY)); + if (setState(GestureState.CHANGED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) + { + dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, scaleX, scaleY)); + } } } } @@ -165,7 +171,10 @@ package org.gestouch.gestures { if (state == GestureState.BEGAN || state == GestureState.CHANGED) { - setState(GestureState.ENDED, new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 1, 1)); + if (setState(GestureState.ENDED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) + { + dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 1, 1)); + } } else if (state == GestureState.POSSIBLE) { @@ -181,7 +190,10 @@ package org.gestouch.gestures if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); - setState(GestureState.CHANGED, new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, 1, 1)); + if (setState(GestureState.CHANGED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) + { + dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, 1, 1)); + } } } } From ad767a4937f853d028097a1620ed517a14f34fbe Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Thu, 2 Feb 2012 16:57:16 +0200 Subject: [PATCH 08/87] Bugfix for mouse/finger release out of stage --- src/org/gestouch/core/GesturesManager.as | 7 +++++++ src/org/gestouch/core/TouchesManager.as | 8 ++++++++ src/org/gestouch/events/MouseTouchEvent.as | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index d1e8a68..509481d 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -1,5 +1,6 @@ package org.gestouch.core { + import flash.events.EventPhase; import org.gestouch.events.MouseTouchEvent; import org.gestouch.gestures.Gesture; @@ -198,11 +199,13 @@ package org.gestouch.core { _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); + _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler);//to catch event out of stage } else { _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); + _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);//to catch event out of stage } } @@ -376,6 +379,10 @@ package org.gestouch.core protected function touchEndHandler(event:TouchEvent):void { + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + if (_dirtyGesturesLength > 0) { resetDirtyGestures(); diff --git a/src/org/gestouch/core/TouchesManager.as b/src/org/gestouch/core/TouchesManager.as index 6502f8b..a9a4a44 100644 --- a/src/org/gestouch/core/TouchesManager.as +++ b/src/org/gestouch/core/TouchesManager.as @@ -1,5 +1,6 @@ package org.gestouch.core { + import flash.events.EventPhase; import flash.display.InteractiveObject; import flash.display.Stage; import flash.events.MouseEvent; @@ -74,12 +75,16 @@ package org.gestouch.core stage.addEventListener(TouchEvent.TOUCH_BEGIN, stage_touchBeginHandler, true, int.MAX_VALUE); stage.addEventListener(TouchEvent.TOUCH_MOVE, stage_touchMoveHandler, true, int.MAX_VALUE); stage.addEventListener(TouchEvent.TOUCH_END, stage_touchEndHandler, true, int.MAX_VALUE); + // if mouse/finger leaves the stage we will get only AT_TARGET phase + stage.addEventListener(TouchEvent.TOUCH_END, stage_touchEndHandler, false, int.MAX_VALUE); } else { stage.addEventListener(MouseEvent.MOUSE_DOWN, stage_mouseDownHandler, true, int.MAX_VALUE); stage.addEventListener(MouseEvent.MOUSE_MOVE, stage_mouseMoveHandler, true, int.MAX_VALUE); stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler, true, int.MAX_VALUE); + // if mouse/finger leaves the stage we will get only AT_TARGET phase + stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler, false, int.MAX_VALUE); } } @@ -187,6 +192,9 @@ package org.gestouch.core protected function stage_mouseUpHandler(event:MouseEvent):void { + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + var touch:Touch = _touchesMap[0] as Touch; if (!touch) { diff --git a/src/org/gestouch/events/MouseTouchEvent.as b/src/org/gestouch/events/MouseTouchEvent.as index e1b7f6c..bff0d64 100644 --- a/src/org/gestouch/events/MouseTouchEvent.as +++ b/src/org/gestouch/events/MouseTouchEvent.as @@ -53,6 +53,12 @@ package org.gestouch.events } + override public function get eventPhase():uint + { + return _mouseEvent.eventPhase; + } + + override public function get stageX():Number { return _mouseEvent.stageX; From 6d8b733d51bb4cc90dc71065c5a4853fd39fd302 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 3 Feb 2012 15:57:11 +0200 Subject: [PATCH 09/87] Moved input logic out to separate classes --- src/org/gestouch/core/GesturesManager.as | 200 ++++++------------ src/org/gestouch/core/IGesturesManager.as | 3 + src/org/gestouch/core/IInputAdapter.as | 11 + src/org/gestouch/core/ITouchesManager.as | 6 +- src/org/gestouch/core/TouchesManager.as | 168 +++------------ src/org/gestouch/events/MouseTouchEvent.as | 99 --------- src/org/gestouch/gestures/Gesture.as | 26 +-- src/org/gestouch/gestures/LongPressGesture.as | 9 +- src/org/gestouch/gestures/PanGesture.as | 12 +- src/org/gestouch/gestures/RotateGesture.as | 9 +- src/org/gestouch/gestures/SwipeGesture.as | 7 +- src/org/gestouch/gestures/TapGesture.as | 7 +- src/org/gestouch/gestures/ZoomGesture.as | 9 +- .../gestouch/input/AbstractInputAdapter.as | 37 ++++ src/org/gestouch/input/MouseInputAdapter.as | 121 +++++++++++ src/org/gestouch/input/TUIOInputAdapter.as | 17 ++ src/org/gestouch/input/TouchInputAdapter.as | 147 +++++++++++++ 17 files changed, 468 insertions(+), 420 deletions(-) create mode 100644 src/org/gestouch/core/IInputAdapter.as delete mode 100644 src/org/gestouch/events/MouseTouchEvent.as create mode 100644 src/org/gestouch/input/AbstractInputAdapter.as create mode 100644 src/org/gestouch/input/MouseInputAdapter.as create mode 100644 src/org/gestouch/input/TUIOInputAdapter.as create mode 100644 src/org/gestouch/input/TouchInputAdapter.as diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 509481d..7b86e3b 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -1,18 +1,13 @@ package org.gestouch.core { - import flash.events.EventPhase; - import org.gestouch.events.MouseTouchEvent; import org.gestouch.gestures.Gesture; - import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.InteractiveObject; - import flash.display.Stage; + import flash.display.Shape; import flash.events.Event; - import flash.events.MouseEvent; - import flash.events.TouchEvent; - import flash.ui.Multitouch; import flash.utils.Dictionary; + /** * @author Pavel fljot */ @@ -22,7 +17,8 @@ package org.gestouch.core private static var _allowInstantiation:Boolean; protected const _touchesManager:ITouchesManager = TouchesManager.getInstance(); - protected var _stage:Stage; + protected const _frameTickerShape:Shape = new Shape(); + protected var _inputAdapters:Vector. = new Vector.(); protected var _gestures:Vector. = new Vector.(); protected var _gesturesForTouchMap:Array = []; protected var _gesturesForTargetMap:Dictionary = new Dictionary(true); @@ -40,6 +36,12 @@ package org.gestouch.core } + public function get inputAdapters():Vector. + { + return _inputAdapters.concat(); + } + + public static function setImplementation(value:IGesturesManager):void { if (!value) @@ -65,7 +67,7 @@ package org.gestouch.core return _instance; } - + @@ -88,15 +90,6 @@ package org.gestouch.core targetGestures.push(gesture); _gestures.push(gesture); - - if (!_stage && gesture.target.stage) - { - installStage(gesture.target.stage); - } - else - { - gesture.target.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); - } } @@ -115,7 +108,6 @@ package org.gestouch.core if (targetGestures.length == 0) { delete _gesturesForTargetMap[target]; - gesture.target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); } var index:int = _gestures.indexOf(gesture); @@ -134,7 +126,7 @@ package org.gestouch.core { _dirtyGestures.push(gesture); _dirtyGesturesLength++; - _stage.addEventListener(Event.ENTER_FRAME, stage_enterFrameHandler); + _frameTickerShape.addEventListener(Event.ENTER_FRAME, enterFrameHandler); } } @@ -168,6 +160,38 @@ package org.gestouch.core } + 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; + } + + + public function removeInputAdapter(inputAdapter:IInputAdapter):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); + } + + //-------------------------------------------------------------------------- @@ -176,55 +200,6 @@ package org.gestouch.core // //-------------------------------------------------------------------------- - protected function installStage(stage:Stage):void - { - _touchesManager.init(stage); - _stage = stage; - - if (Multitouch.supportsTouchEvents) - { - stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); - } - else - { - stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); - } - } - - - protected function installStageListeners():void - { - //TODO: maximum priority to prevent event hijacking? - if (Multitouch.supportsTouchEvents) - { - _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); - _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); - _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler);//to catch event out of stage - } - else - { - _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); - _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); - _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);//to catch event out of stage - } - } - - - protected function uninstallStageListeners():void - { - if (Multitouch.supportsTouchEvents) - { - _stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); - _stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); - } - else - { - _stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); - _stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); - } - } - - protected function resetDirtyGestures():void { for each (var gesture:Gesture in _dirtyGestures) @@ -234,45 +209,17 @@ package org.gestouch.core _dirtyGestures.length = 0; _dirtyGesturesLength = 0; _dirtyGesturesMap = new Dictionary(true); - _stage.removeEventListener(Event.ENTER_FRAME, stage_enterFrameHandler); + _frameTickerShape.removeEventListener(Event.ENTER_FRAME, enterFrameHandler); } - - - //-------------------------------------------------------------------------- - // - // 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) - { - installStage(target.stage); - } - - var depth:uint = 1;//NB! not using 0-based for sorting function - var targetParent:DisplayObjectContainer = target.parent; - while (targetParent) - { - depth++; - targetParent = targetParent.parent; - } - } - - - protected function touchBeginHandler(event:TouchEvent):void + gestouch_internal function onTouchBegin(touch:Touch):void { if (_dirtyGesturesLength > 0) { resetDirtyGestures(); } - var touch:Touch = _touchesManager.getTouch(event.touchPointID); var gesture:Gesture; var i:uint; @@ -323,33 +270,23 @@ package org.gestouch.core // Check for state because previous (i+1) gesture may already abort current (i) one if (gesture.state != GestureState.FAILED) { - gesture.gestouch_internal::touchBeginHandler(touch, event); + gesture.gestouch_internal::touchBeginHandler(touch); } else { gesturesForTouch.splice(i, 1); } } - - installStageListeners(); } - protected function mouseDownHandler(event:MouseEvent):void - { - touchBeginHandler(MouseTouchEvent.createMouseTouchEvent(event)); - } - - - protected function touchMoveHandler(event:TouchEvent):void + gestouch_internal function onTouchMove(touch:Touch):void { if (_dirtyGesturesLength > 0) { resetDirtyGestures(); } - var touch:Touch = _touchesManager.getTouch(event.touchPointID); - var gesturesForTouch:Vector. = _gesturesForTouchMap[touch.id] as Vector.; var gesture:Gesture; var i:int = gesturesForTouch.length; @@ -359,7 +296,7 @@ package org.gestouch.core if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) { - gesture.gestouch_internal::touchMoveHandler(touch, event); + gesture.gestouch_internal::touchMoveHandler(touch); } else { @@ -370,56 +307,43 @@ package org.gestouch.core } - protected function mouseMoveHandler(event:MouseEvent):void + gestouch_internal function onTouchEnd(touch:Touch):void { - //TODO: copy code from touchMoveHandler: save 1 function call? - touchMoveHandler(MouseTouchEvent.createMouseTouchEvent(event)); - } - - - protected function touchEndHandler(event:TouchEvent):void - { - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return; - - if (_dirtyGesturesLength > 0) { resetDirtyGestures(); } - var touch:Touch = _touchesManager.getTouch(event.touchPointID); - var gesturesForTouch:Vector. = _gesturesForTouchMap[touch.id] as Vector.; var gesture:Gesture; var i:int = gesturesForTouch.length; while (i-- > 0) { gesture = gesturesForTouch[i] as Gesture; - - // TODO: handle cancelled touch: - // if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"] && ... if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) { - gesture.gestouch_internal::touchEndHandler(touch, event); + gesture.gestouch_internal::touchEndHandler(touch); } } - - if (_touchesManager.activeTouchesCount == 0) - { - uninstallStageListeners(); - } } - protected function mouseUpHandler(event:MouseEvent):void + gestouch_internal function onTouchCancel(touch:Touch):void { - touchEndHandler(MouseTouchEvent.createMouseTouchEvent(event)); + //TODO } - private function stage_enterFrameHandler(event:Event):void + + + //-------------------------------------------------------------------------- + // + // Event handlers + // + //-------------------------------------------------------------------------- + + private function enterFrameHandler(event:Event):void { resetDirtyGestures(); } diff --git a/src/org/gestouch/core/IGesturesManager.as b/src/org/gestouch/core/IGesturesManager.as index dd3f476..b8c6808 100644 --- a/src/org/gestouch/core/IGesturesManager.as +++ b/src/org/gestouch/core/IGesturesManager.as @@ -6,6 +6,9 @@ package org.gestouch.core */ public interface IGesturesManager { + function addInputAdapter(inputAdapter:IInputAdapter):void; + function removeInputAdapter(inputAdapter:IInputAdapter):void; + function addGesture(gesture:Gesture):void; function removeGesture(gesture:Gesture):void; diff --git a/src/org/gestouch/core/IInputAdapter.as b/src/org/gestouch/core/IInputAdapter.as new file mode 100644 index 0000000..07171f1 --- /dev/null +++ b/src/org/gestouch/core/IInputAdapter.as @@ -0,0 +1,11 @@ +package org.gestouch.core +{ + /** + * @author Pavel fljot + */ + public interface IInputAdapter + { + function set touchesManager(value:ITouchesManager):void; + function set gesturesManager(value:IGesturesManager):void; + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/ITouchesManager.as b/src/org/gestouch/core/ITouchesManager.as index ba7a1f2..8ae9639 100644 --- a/src/org/gestouch/core/ITouchesManager.as +++ b/src/org/gestouch/core/ITouchesManager.as @@ -1,6 +1,5 @@ package org.gestouch.core { - import flash.display.Stage; /** * @author Pavel fljot */ @@ -8,7 +7,10 @@ package org.gestouch.core { function get activeTouchesCount():uint; - function init(stage:Stage):void; + 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/TouchesManager.as b/src/org/gestouch/core/TouchesManager.as index a9a4a44..bc482ce 100644 --- a/src/org/gestouch/core/TouchesManager.as +++ b/src/org/gestouch/core/TouchesManager.as @@ -1,13 +1,7 @@ package org.gestouch.core { - import flash.events.EventPhase; - import flash.display.InteractiveObject; - import flash.display.Stage; - import flash.events.MouseEvent; - import flash.events.TouchEvent; import flash.ui.Multitouch; import flash.ui.MultitouchInputMode; - import flash.utils.getTimer; /** * @author Pavel fljot */ @@ -16,7 +10,6 @@ package org.gestouch.core private static var _instance:ITouchesManager; private static var _allowInstantiation:Boolean; - protected var _stage:Stage; protected var _touchesMap:Object = {}; { @@ -67,25 +60,44 @@ package org.gestouch.core } - public function init(stage:Stage):void + public function createTouch():Touch { - _stage = stage; - if (Multitouch.supportsTouchEvents) + //TODO: pool + return new Touch(); + } + + + public function addTouch(touch:Touch):Touch + { + if (_touchesMap.hasOwnProperty(touch.id)) { - stage.addEventListener(TouchEvent.TOUCH_BEGIN, stage_touchBeginHandler, true, int.MAX_VALUE); - stage.addEventListener(TouchEvent.TOUCH_MOVE, stage_touchMoveHandler, true, int.MAX_VALUE); - stage.addEventListener(TouchEvent.TOUCH_END, stage_touchEndHandler, true, int.MAX_VALUE); - // if mouse/finger leaves the stage we will get only AT_TARGET phase - stage.addEventListener(TouchEvent.TOUCH_END, stage_touchEndHandler, false, int.MAX_VALUE); + throw new Error("Touch with id " + touch.id + " is already registered."); } - else + + _touchesMap[touch.id] = touch; + _activeTouchesCount++; + + return touch; + } + + + public function removeTouch(touch:Touch):Touch + { + if (!_touchesMap.hasOwnProperty(touch.id)) { - stage.addEventListener(MouseEvent.MOUSE_DOWN, stage_mouseDownHandler, true, int.MAX_VALUE); - stage.addEventListener(MouseEvent.MOUSE_MOVE, stage_mouseMoveHandler, true, int.MAX_VALUE); - stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler, true, int.MAX_VALUE); - // if mouse/finger leaves the stage we will get only AT_TARGET phase - stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler, false, int.MAX_VALUE); + 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); } @@ -93,120 +105,6 @@ package org.gestouch.core { var touch:Touch = _touchesMap[touchPointID] as Touch; return touch ? touch.clone() : null; - } - - - - //-------------------------------------------------------------------------- - // - // Event handlers - // - //-------------------------------------------------------------------------- - - protected function stage_touchBeginHandler(event:TouchEvent):void - { - var touch:Touch = new Touch(event.touchPointID); - _touchesMap[event.touchPointID] = touch; - - touch.target = event.target as InteractiveObject; - touch.x = event.stageX; - touch.y = event.stageY; - touch.sizeX = event.sizeX; - touch.sizeY = event.sizeY; - touch.pressure = event.pressure; - touch.time = getTimer();//TODO: conditional compilation + event.timestamp - - _activeTouchesCount++; - } - - - protected function stage_mouseDownHandler(event:MouseEvent):void - { - var touch:Touch = new Touch(0); - _touchesMap[0] = touch; - - touch.target = event.target as InteractiveObject; - touch.x = event.stageX; - touch.y = event.stageY; - touch.sizeX = NaN; - touch.sizeY = NaN; - touch.pressure = NaN; - touch.time = getTimer();//TODO: conditional compilation + event.timestamp - - _activeTouchesCount++; - } - - - protected function stage_touchMoveHandler(event:TouchEvent):void - { - var touch:Touch = _touchesMap[event.touchPointID] as Touch; - if (!touch) - { - // some fake event? - return; - } - - touch.x = event.stageX; - touch.y = event.stageY; - touch.sizeX = event.sizeX; - touch.sizeY = event.sizeY; - touch.pressure = event.pressure; - touch.time = getTimer();//TODO: conditional compilation + event.timestamp - } - - - protected function stage_mouseMoveHandler(event:MouseEvent):void - { - var touch:Touch = _touchesMap[0] as Touch; - if (!touch) - { - // some fake event? - return; - } - - touch.x = event.stageX; - touch.y = event.stageY; - touch.time = getTimer();//TODO: conditional compilation + event.timestamp - } - - - protected function stage_touchEndHandler(event:TouchEvent):void - { - var touch:Touch = _touchesMap[event.touchPointID] as Touch; - if (!touch) - { - // some fake event? - return; - } - - touch.x = event.stageX; - touch.y = event.stageY; - touch.sizeX = event.sizeX; - touch.sizeY = event.sizeY; - touch.pressure = event.pressure; - touch.time = getTimer();//TODO: conditional compilation + event.timestamp - - _activeTouchesCount--; - } - - - protected function stage_mouseUpHandler(event:MouseEvent):void - { - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return; - - var touch:Touch = _touchesMap[0] as Touch; - if (!touch) - { - // some fake event? - return; - } - - touch.x = event.stageX; - touch.y = event.stageY; - touch.time = getTimer();//TODO: conditional compilation + event.timestamp - - _activeTouchesCount--; } } } \ No newline at end of file diff --git a/src/org/gestouch/events/MouseTouchEvent.as b/src/org/gestouch/events/MouseTouchEvent.as deleted file mode 100644 index bff0d64..0000000 --- a/src/org/gestouch/events/MouseTouchEvent.as +++ /dev/null @@ -1,99 +0,0 @@ -package org.gestouch.events -{ - import flash.events.Event; - import flash.events.MouseEvent; - import flash.events.TouchEvent; - - - /** - * @author Pavel fljot - */ - public class MouseTouchEvent extends TouchEvent - { - private static const typeMap:Object = {}; - - protected var _mouseEvent:MouseEvent; - - { - MouseTouchEvent.typeMap[MouseEvent.MOUSE_DOWN] = TouchEvent.TOUCH_BEGIN; - MouseTouchEvent.typeMap[MouseEvent.MOUSE_MOVE] = TouchEvent.TOUCH_MOVE; - MouseTouchEvent.typeMap[MouseEvent.MOUSE_UP] = TouchEvent.TOUCH_END; - } - - - public function MouseTouchEvent(type:String, event:MouseEvent) - { - super(type, event.bubbles, event.cancelable, 0, true, event.localX, event.localY, NaN, NaN, NaN, event.relatedObject, event.ctrlKey, event.altKey, event.shiftKey); - - _mouseEvent = event; - } - - - public static function createMouseTouchEvent(event:MouseEvent):MouseTouchEvent - { - var type:String = MouseTouchEvent.typeMap[event.type]; - if (!type) - { - throw new Error("No match found for MouseEvent of type \"" + event.type + "\""); - } - - return new MouseTouchEvent(type, event); - } - - - override public function get target():Object - { - return _mouseEvent.target; - } - - - override public function get currentTarget():Object - { - return _mouseEvent.currentTarget; - } - - - override public function get eventPhase():uint - { - return _mouseEvent.eventPhase; - } - - - override public function get stageX():Number - { - return _mouseEvent.stageX; - } - - - override public function get stageY():Number - { - return _mouseEvent.stageY; - } - - - override public function stopPropagation():void - { - super.stopPropagation(); - _mouseEvent.stopPropagation(); - } - - - override public function stopImmediatePropagation():void - { - super.stopImmediatePropagation(); - _mouseEvent.stopImmediatePropagation(); - } - - - override public function clone():Event - { - return super.clone(); - } - - - override public function toString():String - { - return super.toString() + " *faked"; - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 56c2c6d..01eb83a 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -273,7 +273,7 @@ package org.gestouch.gestures /** * TODO: clarify usage. For now it's supported to call this method in onTouchBegin with return. */ - protected function ignoreTouch(touch:Touch, event:TouchEvent):void + protected function ignoreTouch(touch:Touch):void { if (_touchesMap.hasOwnProperty(touch.id)) { @@ -285,33 +285,27 @@ package org.gestouch.gestures [Abstract] /** - * Internal method, used by GesturesManager. - * *

NB! This is abstract method and must be overridden.

*/ - protected function onTouchBegin(touch:Touch, event:TouchEvent):void + protected function onTouchBegin(touch:Touch):void { } [Abstract] /** - * Internal method, used by GesturesManager. - * *

NB! This is abstract method and must be overridden.

*/ - protected function onTouchMove(touch:Touch, event:TouchEvent):void + protected function onTouchMove(touch:Touch):void { } [Abstract] /** - * Internal method, used by GesturesManager. - * *

NB! This is abstract method and must be overridden.

*/ - protected function onTouchEnd(touch:Touch, event:TouchEvent):void + protected function onTouchEnd(touch:Touch):void { } @@ -401,28 +395,28 @@ package org.gestouch.gestures // //-------------------------------------------------------------------------- - gestouch_internal function touchBeginHandler(touch:Touch, event:TouchEvent):void + gestouch_internal function touchBeginHandler(touch:Touch):void { _touchesMap[touch.id] = touch; _touchesCount++; - onTouchBegin(touch, event); + onTouchBegin(touch); } - gestouch_internal function touchMoveHandler(touch:Touch, event:TouchEvent):void + gestouch_internal function touchMoveHandler(touch:Touch):void { _touchesMap[touch.id] = touch; - onTouchMove(touch, event); + onTouchMove(touch); } - gestouch_internal function touchEndHandler(touch:Touch, event:TouchEvent):void + gestouch_internal function touchEndHandler(touch:Touch):void { delete _touchesMap[touch.id]; _touchesCount--; - onTouchEnd(touch, event); + onTouchEnd(touch); } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 9307ef9..550d154 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -7,7 +7,6 @@ package org.gestouch.gestures import flash.display.InteractiveObject; import flash.events.GesturePhase; import flash.events.TimerEvent; - import flash.events.TouchEvent; import flash.utils.Timer; @@ -82,13 +81,13 @@ package org.gestouch.gestures } - override protected function onTouchBegin(touch:Touch, event:TouchEvent):void + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > numTouchesRequired) { if (state == GestureState.BEGAN || state == GestureState.CHANGED) { - ignoreTouch(touch, event); + ignoreTouch(touch); } else { @@ -117,7 +116,7 @@ package org.gestouch.gestures } - override protected function onTouchMove(touch:Touch, event:TouchEvent):void + override protected function onTouchMove(touch:Touch):void { if (state == GestureState.POSSIBLE && slop > 0) { @@ -141,7 +140,7 @@ package org.gestouch.gestures } - override protected function onTouchEnd(touch:Touch, event:TouchEvent):void + override protected function onTouchEnd(touch:Touch):void { //TODO: check proper condition (behavior) on iOS native if (_numTouchesRequiredReached) diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index 1830e03..d11e47e 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -1,13 +1,11 @@ package org.gestouch.gestures { - import org.gestouch.events.PanGestureEvent; import org.gestouch.core.GestureState; import org.gestouch.core.Touch; - import org.gestouch.events.ZoomGestureEvent; + import org.gestouch.events.PanGestureEvent; import flash.display.InteractiveObject; import flash.events.GesturePhase; - import flash.events.TouchEvent; import flash.geom.Point; [Event(name="gesturePan", type="org.gestouch.events.PanGestureEvent")] @@ -107,12 +105,12 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- - override protected function onTouchBegin(touch:Touch, event:TouchEvent):void + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > maxNumTouchesRequired) { //TODO - ignoreTouch(touch, event); + ignoreTouch(touch); return; } @@ -126,7 +124,7 @@ package org.gestouch.gestures } - override protected function onTouchMove(touch:Touch, event:TouchEvent):void + override protected function onTouchMove(touch:Touch):void { if (touchesCount < minNumTouchesRequired) return; @@ -180,7 +178,7 @@ package org.gestouch.gestures } - override protected function onTouchEnd(touch:Touch, event:TouchEvent):void + override protected function onTouchEnd(touch:Touch):void { if (touchesCount < minNumTouchesRequired) { diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index c3c3960..1e52c4d 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -7,7 +7,6 @@ package org.gestouch.gestures import flash.display.InteractiveObject; import flash.events.GesturePhase; - import flash.events.TouchEvent; import flash.geom.Point; [Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")] @@ -66,12 +65,12 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- - override protected function onTouchBegin(touch:Touch, event:TouchEvent):void + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > 2) { //TODO - ignoreTouch(touch, event); + ignoreTouch(touch); return; } @@ -94,7 +93,7 @@ package org.gestouch.gestures } - override protected function onTouchMove(touch:Touch, event:TouchEvent):void + override protected function onTouchMove(touch:Touch):void { if (touch.id == _firstTouch.id) { @@ -155,7 +154,7 @@ package org.gestouch.gestures } - override protected function onTouchEnd(touch:Touch, event:TouchEvent):void + override protected function onTouchEnd(touch:Touch):void { if (touchesCount == 0) { diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 06ec8f1..de13cbe 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -6,7 +6,6 @@ package org.gestouch.gestures import flash.display.InteractiveObject; import flash.events.GesturePhase; - import flash.events.TouchEvent; import flash.geom.Point; import flash.system.Capabilities; @@ -69,7 +68,7 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- - override protected function onTouchBegin(touch:Touch, event:TouchEvent):void + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > numTouchesRequired) { @@ -88,7 +87,7 @@ package org.gestouch.gestures } - override protected function onTouchMove(touch:Touch, event:TouchEvent):void + override protected function onTouchMove(touch:Touch):void { if (touchesCount < numTouchesRequired) return; @@ -185,7 +184,7 @@ package org.gestouch.gestures } - override protected function onTouchEnd(touch:Touch, event:TouchEvent):void + override protected function onTouchEnd(touch:Touch):void { setState(GestureState.FAILED); } diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index afbf9a6..b73a3c0 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -7,7 +7,6 @@ package org.gestouch.gestures import flash.display.InteractiveObject; import flash.events.GesturePhase; import flash.events.TimerEvent; - import flash.events.TouchEvent; import flash.utils.Timer; @@ -91,7 +90,7 @@ package org.gestouch.gestures } - override protected function onTouchBegin(touch:Touch, event:TouchEvent):void + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > numTouchesRequired) { @@ -118,7 +117,7 @@ package org.gestouch.gestures } - override protected function onTouchMove(touch:Touch, event:TouchEvent):void + override protected function onTouchMove(touch:Touch):void { if (slop >= 0) { @@ -133,7 +132,7 @@ package org.gestouch.gestures } - override protected function onTouchEnd(touch:Touch, event:TouchEvent):void + override protected function onTouchEnd(touch:Touch):void { if (!_numTouchesRequiredReached) { diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 5ac4edd..4d0cf12 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -6,7 +6,6 @@ package org.gestouch.gestures import flash.display.InteractiveObject; import flash.events.GesturePhase; - import flash.events.TouchEvent; import flash.geom.Point; [Event(name="gestureZoom", type="org.gestouch.events.ZoomGestureEvent")] @@ -66,12 +65,12 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- - override protected function onTouchBegin(touch:Touch, event:TouchEvent):void + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > 2) { //TODO - ignoreTouch(touch, event); + ignoreTouch(touch); return; } @@ -94,7 +93,7 @@ package org.gestouch.gestures } - override protected function onTouchMove(touch:Touch, event:TouchEvent):void + override protected function onTouchMove(touch:Touch):void { if (touch.id == _firstTouch.id) { @@ -165,7 +164,7 @@ package org.gestouch.gestures } - override protected function onTouchEnd(touch:Touch, event:TouchEvent):void + override protected function onTouchEnd(touch:Touch):void { if (touchesCount == 0) { diff --git a/src/org/gestouch/input/AbstractInputAdapter.as b/src/org/gestouch/input/AbstractInputAdapter.as new file mode 100644 index 0000000..5c3bcfc --- /dev/null +++ b/src/org/gestouch/input/AbstractInputAdapter.as @@ -0,0 +1,37 @@ +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; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/input/MouseInputAdapter.as b/src/org/gestouch/input/MouseInputAdapter.as new file mode 100644 index 0000000..ebd7420 --- /dev/null +++ b/src/org/gestouch/input/MouseInputAdapter.as @@ -0,0 +1,121 @@ +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.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; + + stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); + } + + + protected function installStageListeners():void + { + // Maximum priority to prevent event hijacking + _stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true, int.MAX_VALUE); + _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, int.MAX_VALUE); + // To catch event out of stage + _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_UP, mouseUpHandler, true); + _stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler); + } + + + protected function mouseDownHandler(event:MouseEvent):void + { + // 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.id = 0; + touch.target = event.target as InteractiveObject; + touch.x = event.stageX; + touch.y = event.stageY; + touch.time = getTimer(); + + _touchesManager.addTouch(touch); + + _gesturesManager.gestouch_internal::onTouchBegin(touch); + } + + + protected function mouseMoveHandler(event:MouseEvent):void + { + // 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.x = event.stageX; + touch.y = event.stageY; + //TODO: time update vs begin time + + _gesturesManager.gestouch_internal::onTouchMove(touch); + } + + + protected function mouseUpHandler(event:MouseEvent):void + { + // If event happens outside of stage it will be with AT_TARGET phase + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + + // 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.x = event.stageX; + touch.y = event.stageY; + //TODO: time update vs begin time + + _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/TUIOInputAdapter.as b/src/org/gestouch/input/TUIOInputAdapter.as new file mode 100644 index 0000000..5dae3e9 --- /dev/null +++ b/src/org/gestouch/input/TUIOInputAdapter.as @@ -0,0 +1,17 @@ +package org.gestouch.input +{ + import org.gestouch.input.AbstractInputAdapter; + + + /** + * @author Pavel fljot + */ + public class TUIOInputAdapter extends AbstractInputAdapter + { + public function TUIOInputAdapter() + { + super(); + //TODO + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/input/TouchInputAdapter.as b/src/org/gestouch/input/TouchInputAdapter.as new file mode 100644 index 0000000..f4ca4c6 --- /dev/null +++ b/src/org/gestouch/input/TouchInputAdapter.as @@ -0,0 +1,147 @@ +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.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 = {}; + + + public function TouchInputAdapter(stage:Stage) + { + super(); + + if (!stage) + { + throw new Error("Stage must be not null."); + } + + _stage = stage; + + stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); + } + + + protected function installStageListeners():void + { + // Maximum priority to prevent event hijacking + _stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true, int.MAX_VALUE); + _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true, int.MAX_VALUE); + // To catch event out of stage + _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_END, touchEndHandler, true); + _stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler); + } + + + protected function touchBeginHandler(event:TouchEvent):void + { + // 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.x = event.stageX; + touch.y = event.stageY; + touch.sizeX = event.sizeX; + touch.sizeY = event.sizeY; + touch.pressure = event.pressure; + //TODO: conditional compilation? + if (event.hasOwnProperty("timestamp")) + { + touch.time = event["timestamp"]; + } + else + { + touch.time = getTimer(); + } + + _touchesManager.addTouch(touch); + _touchesMap[touch.id] = true; + + _gesturesManager.gestouch_internal::onTouchBegin(touch); + } + + + protected function touchMoveHandler(event:TouchEvent):void + { + // 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.x = event.stageX; + touch.y = event.stageY; + touch.sizeX = event.sizeX; + touch.sizeY = event.sizeY; + touch.pressure = event.pressure; + //TODO: time update vs begin time + + _gesturesManager.gestouch_internal::onTouchMove(touch); + } + + + protected function touchEndHandler(event:TouchEvent):void + { + // If event happens outside of stage it will be with AT_TARGET phase + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + // 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.x = event.stageX; + touch.y = event.stageY; + touch.sizeX = event.sizeX; + touch.sizeY = event.sizeY; + touch.pressure = event.pressure; + //TODO: time update vs begin time + + _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 From 3ba8a3df861baf03c4798bcf4c504a9cc376ec52 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 17 Feb 2012 17:53:15 +0200 Subject: [PATCH 10/87] Put back automatic input adapter initialization --- src/org/gestouch/core/GesturesManager.as | 48 +++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 7b86e3b..0c0868a 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -1,11 +1,16 @@ package org.gestouch.core { import org.gestouch.gestures.Gesture; + import org.gestouch.input.MouseInputAdapter; + import org.gestouch.input.TouchInputAdapter; + 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.utils.Dictionary; /** @@ -13,12 +18,14 @@ package org.gestouch.core */ public class GesturesManager implements IGesturesManager { + 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 _gestures:Vector. = new Vector.(); protected var _gesturesForTouchMap:Array = []; protected var _gesturesForTargetMap:Dictionary = new Dictionary(true); @@ -89,7 +96,19 @@ package org.gestouch.core } targetGestures.push(gesture); - _gestures.push(gesture); + _gestures.push(gesture); + + if (GesturesManager.initDefaultInputAdapter) + { + if (!_stage && gesture.target.stage) + { + installStage(gesture.target.stage); + } + else + { + gesture.target.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + } + } } @@ -108,6 +127,7 @@ package org.gestouch.core if (targetGestures.length == 0) { delete _gesturesForTargetMap[target]; + target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); } var index:int = _gestures.indexOf(gesture); @@ -200,6 +220,21 @@ package org.gestouch.core // //-------------------------------------------------------------------------- + protected function installStage(stage:Stage):void + { + _stage = stage; + + if (Multitouch.supportsTouchEvents) + { + addInputAdapter(new TouchInputAdapter(stage)); + } + else + { + addInputAdapter(new MouseInputAdapter(stage)); + } + } + + protected function resetDirtyGestures():void { for each (var gesture:Gesture in _dirtyGestures) @@ -343,6 +378,17 @@ package org.gestouch.core // //-------------------------------------------------------------------------- + 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) + { + installStage(target.stage); + } + } + + private function enterFrameHandler(event:Event):void { resetDirtyGestures(); From 0532f4bfbbba9cb66fd00add2a48f6a0fa9a2fcd Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Mon, 20 Feb 2012 17:43:43 +0200 Subject: [PATCH 11/87] Moved some IGesturesManager methods under gestouch_internal namespace --- src/org/gestouch/core/GesturesManager.as | 204 +++++++++++----------- src/org/gestouch/core/IGesturesManager.as | 17 +- src/org/gestouch/gestures/Gesture.as | 10 +- 3 files changed, 114 insertions(+), 117 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 0c0868a..8f872ed 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -76,108 +76,6 @@ package org.gestouch.core } - - - public function addGesture(gesture:Gesture):void - { - if (!gesture) - { - throw new ArgumentError("Argument 'gesture' must be not null."); - } - if (_gestures.indexOf(gesture) > -1) - { - throw new Error("This gesture is already registered.. something wrong."); - } - - var targetGestures:Vector. = _gesturesForTargetMap[gesture.target] as Vector.; - if (!targetGestures) - { - targetGestures = _gesturesForTargetMap[gesture.target] = new Vector.(); - } - targetGestures.push(gesture); - - _gestures.push(gesture); - - if (GesturesManager.initDefaultInputAdapter) - { - if (!_stage && gesture.target.stage) - { - installStage(gesture.target.stage); - } - else - { - gesture.target.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); - } - } - } - - - public function removeGesture(gesture:Gesture):void - { - if (!gesture) - { - throw new ArgumentError("Argument 'gesture' must be not null."); - } - - - var target:InteractiveObject = gesture.target; - var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; - targetGestures.splice(targetGestures.indexOf(gesture), 1); - - if (targetGestures.length == 0) - { - delete _gesturesForTargetMap[target]; - target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); - } - - var index:int = _gestures.indexOf(gesture); - if (index > -1) - { - _gestures.splice(index, 1); - } - - //TODO: decide about gesture state and _dirtyGestures - } - - - public function scheduleGestureStateReset(gesture:Gesture):void - { - if (!_dirtyGesturesMap[gesture]) - { - _dirtyGestures.push(gesture); - _dirtyGesturesLength++; - _frameTickerShape.addEventListener(Event.ENTER_FRAME, enterFrameHandler); - } - } - - - public function onGestureRecognized(gesture:Gesture):void - { - for each (var otherGesture:Gesture in _gestures) - { - // conditions for otherGesture "own properties" - if (otherGesture != gesture && - otherGesture.enabled && - otherGesture.state == GestureState.POSSIBLE) - { - // conditions for otherGesture target - if (otherGesture.target == gesture.target || - (gesture.target is DisplayObjectContainer && (gesture.target as DisplayObjectContainer).contains(otherGesture.target)) || - (otherGesture.target is DisplayObjectContainer && (otherGesture.target as DisplayObjectContainer).contains(gesture.target)) - ) - { - // conditions for gestures relations - if (gesture.canPreventGesture(otherGesture) && - otherGesture.canBePreventedByGesture(gesture) && - (!gesture.delegate || !gesture.delegate.gesturesShouldRecognizeSimultaneously(gesture, otherGesture)) && - (!otherGesture.delegate || !otherGesture.delegate.gesturesShouldRecognizeSimultaneously(otherGesture, gesture))) - { - otherGesture.gestouch_internal::setState_internal(GestureState.FAILED); - } - } - } - } - } public function addInputAdapter(inputAdapter:IInputAdapter):void @@ -248,6 +146,108 @@ package org.gestouch.core } + gestouch_internal function addGesture(gesture:Gesture):void + { + if (!gesture) + { + throw new ArgumentError("Argument 'gesture' must be not null."); + } + if (_gestures.indexOf(gesture) > -1) + { + throw new Error("This gesture is already registered.. something wrong."); + } + + var targetGestures:Vector. = _gesturesForTargetMap[gesture.target] as Vector.; + if (!targetGestures) + { + targetGestures = _gesturesForTargetMap[gesture.target] = new Vector.(); + } + targetGestures.push(gesture); + + _gestures.push(gesture); + + if (GesturesManager.initDefaultInputAdapter) + { + if (!_stage && gesture.target.stage) + { + installStage(gesture.target.stage); + } + else + { + gesture.target.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + } + } + } + + + gestouch_internal function removeGesture(gesture:Gesture):void + { + if (!gesture) + { + throw new ArgumentError("Argument 'gesture' must be not null."); + } + + + var target:InteractiveObject = gesture.target; + var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; + targetGestures.splice(targetGestures.indexOf(gesture), 1); + + if (targetGestures.length == 0) + { + delete _gesturesForTargetMap[target]; + target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + } + + var index:int = _gestures.indexOf(gesture); + if (index > -1) + { + _gestures.splice(index, 1); + } + + //TODO: decide about gesture state and _dirtyGestures + } + + + gestouch_internal function scheduleGestureStateReset(gesture:Gesture):void + { + if (!_dirtyGesturesMap[gesture]) + { + _dirtyGestures.push(gesture); + _dirtyGesturesLength++; + _frameTickerShape.addEventListener(Event.ENTER_FRAME, enterFrameHandler); + } + } + + + gestouch_internal function onGestureRecognized(gesture:Gesture):void + { + for each (var otherGesture:Gesture in _gestures) + { + // conditions for otherGesture "own properties" + if (otherGesture != gesture && + otherGesture.enabled && + otherGesture.state == GestureState.POSSIBLE) + { + // conditions for otherGesture target + if (otherGesture.target == gesture.target || + (gesture.target is DisplayObjectContainer && (gesture.target as DisplayObjectContainer).contains(otherGesture.target)) || + (otherGesture.target is DisplayObjectContainer && (otherGesture.target as DisplayObjectContainer).contains(gesture.target)) + ) + { + // conditions for gestures relations + if (gesture.canPreventGesture(otherGesture) && + otherGesture.canBePreventedByGesture(gesture) && + (!gesture.delegate || !gesture.delegate.gesturesShouldRecognizeSimultaneously(gesture, otherGesture)) && + (!otherGesture.delegate || !otherGesture.delegate.gesturesShouldRecognizeSimultaneously(otherGesture, gesture))) + { + otherGesture.gestouch_internal::setState_internal(GestureState.FAILED); + } + } + } + } + } + + gestouch_internal function onTouchBegin(touch:Touch):void { if (_dirtyGesturesLength > 0) diff --git a/src/org/gestouch/core/IGesturesManager.as b/src/org/gestouch/core/IGesturesManager.as index b8c6808..5c08bfe 100644 --- a/src/org/gestouch/core/IGesturesManager.as +++ b/src/org/gestouch/core/IGesturesManager.as @@ -1,20 +1,17 @@ package org.gestouch.core { - import org.gestouch.gestures.Gesture; /** - * @author Pavel fljot + * 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):void; - - function addGesture(gesture:Gesture):void; - - function removeGesture(gesture:Gesture):void; - - function scheduleGestureStateReset(gesture:Gesture):void; - - function onGestureRecognized(gesture:Gesture):void; } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 01eb83a..6bd03df 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -248,8 +248,8 @@ package org.gestouch.gestures protected function installTarget(target:InteractiveObject):void { if (target) - { - _gesturesManager.addGesture(this); + { + _gesturesManager.gestouch_internal::addGesture(this); } } @@ -265,7 +265,7 @@ package org.gestouch.gestures { if (target) { - _gesturesManager.removeGesture(this); + _gesturesManager.gestouch_internal::removeGesture(this); } } @@ -337,7 +337,7 @@ package org.gestouch.gestures if (((GestureState.CANCELLED | GestureState.RECOGNIZED | GestureState.ENDED | GestureState.FAILED) & _state) > 0) { - _gesturesManager.scheduleGestureStateReset(this); + _gesturesManager.gestouch_internal::scheduleGestureStateReset(this); } //TODO: what if RTE happens in event handlers? @@ -349,7 +349,7 @@ package org.gestouch.gestures if (_state == GestureState.BEGAN || _state == GestureState.RECOGNIZED) { - _gesturesManager.onGestureRecognized(this); + _gesturesManager.gestouch_internal::onGestureRecognized(this); } return true; From 09dc1ddfc42a39cb892df06faf371df344cbc7fc Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 21 Feb 2012 20:14:13 +0200 Subject: [PATCH 12/87] Touch#time fix (affects SwipeGesture) --- src/org/gestouch/input/MouseInputAdapter.as | 4 ++-- src/org/gestouch/input/TouchInputAdapter.as | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/org/gestouch/input/MouseInputAdapter.as b/src/org/gestouch/input/MouseInputAdapter.as index ebd7420..0a34d89 100644 --- a/src/org/gestouch/input/MouseInputAdapter.as +++ b/src/org/gestouch/input/MouseInputAdapter.as @@ -85,7 +85,7 @@ package org.gestouch.input var touch:Touch = _touchesManager.getTouch(PRIMARY_TOUCH_POINT_ID); touch.x = event.stageX; touch.y = event.stageY; - //TODO: time update vs begin time + touch.time = getTimer(); _gesturesManager.gestouch_internal::onTouchMove(touch); } @@ -106,7 +106,7 @@ package org.gestouch.input var touch:Touch = _touchesManager.getTouch(PRIMARY_TOUCH_POINT_ID); touch.x = event.stageX; touch.y = event.stageY; - //TODO: time update vs begin time + touch.time = getTimer(); _gesturesManager.gestouch_internal::onTouchEnd(touch); diff --git a/src/org/gestouch/input/TouchInputAdapter.as b/src/org/gestouch/input/TouchInputAdapter.as index f4ca4c6..e1b7263 100644 --- a/src/org/gestouch/input/TouchInputAdapter.as +++ b/src/org/gestouch/input/TouchInputAdapter.as @@ -105,7 +105,15 @@ package org.gestouch.input touch.sizeX = event.sizeX; touch.sizeY = event.sizeY; touch.pressure = event.pressure; - //TODO: time update vs begin time + //TODO: conditional compilation? + if (event.hasOwnProperty("timestamp")) + { + touch.time = event["timestamp"]; + } + else + { + touch.time = getTimer(); + } _gesturesManager.gestouch_internal::onTouchMove(touch); } @@ -128,7 +136,15 @@ package org.gestouch.input touch.sizeX = event.sizeX; touch.sizeY = event.sizeY; touch.pressure = event.pressure; - //TODO: time update vs begin time + //TODO: conditional compilation? + if (event.hasOwnProperty("timestamp")) + { + touch.time = event["timestamp"]; + } + else + { + touch.time = getTimer(); + } _gesturesManager.gestouch_internal::onTouchEnd(touch); From 3dcc78c2674c1878f4a1ad0b1b9cd54d2c0fa8d1 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 29 Feb 2012 13:54:59 +0200 Subject: [PATCH 13/87] Added direction for PanGesture --- src/org/gestouch/gestures/PanGesture.as | 8 ++++++-- src/org/gestouch/gestures/PanGestureDirection.as | 12 ++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/org/gestouch/gestures/PanGestureDirection.as diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index d11e47e..c89eac3 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -19,6 +19,10 @@ package org.gestouch.gestures public class PanGesture extends Gesture { public var slop:Number = Gesture.DEFAULT_SLOP; + /** + * Used for initial slop overcome calculations only. + */ + public var direction:uint = PanGestureDirection.NO_DIRECTION; protected var _touchBeginX:Array = []; protected var _touchBeginY:Array = []; @@ -137,8 +141,8 @@ package org.gestouch.gestures if (state == GestureState.POSSIBLE) { // Check if finger moved enough for gesture to be recognized - var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; - var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; + var dx:Number = (direction == PanGestureDirection.VERTICAL) ? 0 : Number(_touchBeginX[touch.id]) - touch.x; + var dy:Number = (direction == PanGestureDirection.HORIZONTAL) ? 0 : Number(_touchBeginY[touch.id]) - touch.y; if (Math.sqrt(dx*dx + dy*dy) > slop || slop != slop)//faster isNaN(slop) { prevLocationX = _location.x; diff --git a/src/org/gestouch/gestures/PanGestureDirection.as b/src/org/gestouch/gestures/PanGestureDirection.as new file mode 100644 index 0000000..8925601 --- /dev/null +++ b/src/org/gestouch/gestures/PanGestureDirection.as @@ -0,0 +1,12 @@ +package org.gestouch.gestures +{ + /** + * @author Pavel fljot + */ + public class PanGestureDirection + { + public static const NO_DIRECTION:uint = 0; + public static const VERTICAL:uint = 1 << 0; + public static const HORIZONTAL:uint = 1 << 1; + } +} \ No newline at end of file From 895e662bd54bdff630c3cc4a2ccf1a2e18274ab8 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 29 Feb 2012 22:20:09 +0200 Subject: [PATCH 14/87] Minor performance fix for SwipeGesture --- src/org/gestouch/gestures/SwipeGesture.as | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index de13cbe..1121d51 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -102,7 +102,12 @@ package org.gestouch.gestures var absVel:Number = vel > 0 ? vel : -vel;//faster Math.abs() // trace(_offset, _offset.length, ".....velocity:", vel); - if (offsetLength > Gesture.DEFAULT_SLOP && absVel < velocityThreshold) + if (offsetLength < Gesture.DEFAULT_SLOP) + { + // no need in processing - we're in the very beginning of movement + return; + } + else if (absVel < velocityThreshold) { setState(GestureState.FAILED); return; From 51e8435b2dfc2eb9ff4fb6d3e4cedb663feabc67 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Thu, 1 Mar 2012 18:12:35 +0200 Subject: [PATCH 15/87] Input adapters initialization and disposing --- src/org/gestouch/core/GesturesManager.as | 7 ++++++- src/org/gestouch/core/IGesturesManager.as | 2 +- src/org/gestouch/core/IInputAdapter.as | 3 +++ src/org/gestouch/input/AbstractInputAdapter.as | 14 ++++++++++++++ src/org/gestouch/input/MouseInputAdapter.as | 13 +++++++++++++ src/org/gestouch/input/TouchInputAdapter.as | 13 ++++++++++++- 6 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 8f872ed..9dc5a02 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -91,10 +91,11 @@ package org.gestouch.core _inputAdapters.push(inputAdapter); inputAdapter.touchesManager = _touchesManager; inputAdapter.gesturesManager = this; + inputAdapter.init(); } - public function removeInputAdapter(inputAdapter:IInputAdapter):void + public function removeInputAdapter(inputAdapter:IInputAdapter, dispose:Boolean = true):void { if (!inputAdapter) { @@ -107,6 +108,10 @@ package org.gestouch.core } _inputAdapters.splice(index, 1); + if (dispose) + { + inputAdapter.dispose(); + } } diff --git a/src/org/gestouch/core/IGesturesManager.as b/src/org/gestouch/core/IGesturesManager.as index 5c08bfe..297f39a 100644 --- a/src/org/gestouch/core/IGesturesManager.as +++ b/src/org/gestouch/core/IGesturesManager.as @@ -12,6 +12,6 @@ package org.gestouch.core public interface IGesturesManager { function addInputAdapter(inputAdapter:IInputAdapter):void; - function removeInputAdapter(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 07171f1..337ff0e 100644 --- a/src/org/gestouch/core/IInputAdapter.as +++ b/src/org/gestouch/core/IInputAdapter.as @@ -7,5 +7,8 @@ package org.gestouch.core { function set touchesManager(value:ITouchesManager):void; function set gesturesManager(value:IGesturesManager):void; + + function init():void; + function dispose():void; } } \ No newline at end of file diff --git a/src/org/gestouch/input/AbstractInputAdapter.as b/src/org/gestouch/input/AbstractInputAdapter.as index 5c3bcfc..8543af5 100644 --- a/src/org/gestouch/input/AbstractInputAdapter.as +++ b/src/org/gestouch/input/AbstractInputAdapter.as @@ -33,5 +33,19 @@ package org.gestouch.input { _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 index 0a34d89..51ce265 100644 --- a/src/org/gestouch/input/MouseInputAdapter.as +++ b/src/org/gestouch/input/MouseInputAdapter.as @@ -35,6 +35,19 @@ package org.gestouch.input } + override public function init():void + { + _stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); + } + + + override public function dispose():void + { + _stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); + uninstallStageListeners(); + } + + protected function installStageListeners():void { // Maximum priority to prevent event hijacking diff --git a/src/org/gestouch/input/TouchInputAdapter.as b/src/org/gestouch/input/TouchInputAdapter.as index e1b7263..f4d87a7 100644 --- a/src/org/gestouch/input/TouchInputAdapter.as +++ b/src/org/gestouch/input/TouchInputAdapter.as @@ -35,8 +35,19 @@ package org.gestouch.input } _stage = stage; + } + - stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); + override public function init():void + { + _stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); + } + + + override public function dispose():void + { + _stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); + uninstallStageListeners(); } From b56107e059974878b1822fdf6717b232a40498e3 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Thu, 1 Mar 2012 23:56:45 +0200 Subject: [PATCH 16/87] Minor cleanup for Gesture class --- src/org/gestouch/gestures/Gesture.as | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 6bd03df..f2a459c 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -11,23 +11,18 @@ package org.gestouch.gestures import org.gestouch.events.GestureStateEvent; import flash.display.InteractiveObject; - import flash.events.Event; import flash.events.EventDispatcher; - import flash.events.GestureEvent; - import flash.events.TouchEvent; import flash.geom.Point; import flash.system.Capabilities; -// [Event(name="gestureTrackingBegin", type="org.gestouch.events.GestureTrackingEvent")] -// [Event(name="gestureTrackingEnd", type="org.gestouch.events.GestureTrackingEvent")] [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: locationOfTouchPoint(touchPointID):Point - * - ignoreTouch(touch:Touch, event:TouchEvent) ? + * TODO: + * - * * @author Pavel fljot */ From 4d5bef0252291f6c77a2f2726636547bac7fda6d Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 2 Mar 2012 20:33:16 +0200 Subject: [PATCH 17/87] Touch properties updates (and corresponding gestures fixes) --- src/org/gestouch/core/Touch.as | 76 ++++++++++++++++--- src/org/gestouch/gestures/Gesture.as | 8 +- src/org/gestouch/gestures/LongPressGesture.as | 12 +-- src/org/gestouch/gestures/PanGesture.as | 29 +++---- src/org/gestouch/gestures/RotateGesture.as | 31 ++------ src/org/gestouch/gestures/TapGesture.as | 19 +---- src/org/gestouch/gestures/ZoomGesture.as | 31 ++------ src/org/gestouch/input/MouseInputAdapter.as | 17 ++--- src/org/gestouch/input/TouchInputAdapter.as | 24 +++--- 9 files changed, 114 insertions(+), 133 deletions(-) diff --git a/src/org/gestouch/core/Touch.as b/src/org/gestouch/core/Touch.as index 6ed565c..dddcb81 100644 --- a/src/org/gestouch/core/Touch.as +++ b/src/org/gestouch/core/Touch.as @@ -1,6 +1,7 @@ package org.gestouch.core { import flash.display.InteractiveObject; + import flash.geom.Point; /** @@ -20,18 +21,11 @@ package org.gestouch.core */ public var target:InteractiveObject; - public var x:Number; - public var y:Number; public var sizeX:Number; public var sizeY:Number; public var pressure:Number; - public var time:uint; -// public var touchBeginPos:Point; -// public var touchBeginTime:uint; -// public var moveOffset:Point; // public var lastMove:Point; -// public var velocity:Point; public function Touch(id:uint = 0) @@ -40,16 +34,76 @@ package org.gestouch.core } + protected var _location:Point; + public function get location():Point + { + return _location.clone(); + } + gestouch_internal function setLocation(value:Point):void + { + _location = value; + _beginLocation = _location.clone(); + } + gestouch_internal function updateLocation(x:Number, y:Number):void + { + if (_location) + { + _location.x = x; + _location.y = y; + } + else + { + gestouch_internal::setLocation(new Point(x, y)); + } + } + + + protected var _beginLocation:Point; + public function get beginLocation():Point + { + return _beginLocation.clone(); + } + + + public function get locationOffset():Point + { + return _location.subtract(_beginLocation); + } + + + protected var _time:uint; + public function get time():uint + { + return _time; + } + gestouch_internal function setTime(value:uint):void + { + _time = value; + } + + + protected var _beginTime:uint; + public function get beginTime():uint + { + return _beginTime; + } + gestouch_internal function setBeginTime(value:uint):void + { + _beginTime = value; + } + + public function clone():Touch { var touch:Touch = new Touch(id); - touch.x = x; - touch.y = y; + touch._location = _location; + touch._beginLocation = _beginLocation; touch.target = target; touch.sizeX = sizeX; touch.sizeY = sizeY; touch.pressure = pressure; - touch.time = time; + touch._time = _time; + touch._beginTime = _beginTime; return touch; } @@ -57,7 +111,7 @@ package org.gestouch.core public function toString():String { - return "Touch [id: " + id + ", x: " + x + ", y: " + y + ", ...]"; + return "Touch [id: " + id + ", location: " + location + ", ...]"; } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index f2a459c..6c859c2 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -359,14 +359,14 @@ package org.gestouch.gestures protected function updateCentralPoint():void { - var touch:Touch; + var touchLocation:Point; var x:Number = 0; var y:Number = 0; for (var touchID:String in _touchesMap) { - touch = _touchesMap[int(touchID)] as Touch; - x += touch.x; - y += touch.y; + touchLocation = (_touchesMap[int(touchID)] as Touch).location; + x += touchLocation.x; + y += touchLocation.y; } _centralPoint.x = x / _touchesCount; _centralPoint.y = y / _touchesCount; diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 550d154..cc6679f 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -28,8 +28,6 @@ package org.gestouch.gestures public var slop:Number = Gesture.DEFAULT_SLOP; protected var _timer:Timer; - protected var _touchBeginX:Array = []; - protected var _touchBeginY:Array = []; protected var _numTouchesRequiredReached:Boolean; @@ -57,8 +55,6 @@ package org.gestouch.gestures { super.reset(); - _touchBeginX.length = 0; - _touchBeginY.length = 0; _numTouchesRequiredReached = false; _timer.reset(); } @@ -96,9 +92,6 @@ package org.gestouch.gestures return; } - _touchBeginX[touch.id] = touch.x; - _touchBeginY[touch.id] = touch.y; - if (touchesCount == numTouchesRequired) { _numTouchesRequiredReached = true; @@ -120,10 +113,7 @@ package org.gestouch.gestures { if (state == GestureState.POSSIBLE && slop > 0) { - // Fail if touch overcome slop distance - var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; - var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; - if (Math.sqrt(dx*dx + dy*dy) > slop) + if (touch.locationOffset.length > slop) { setState(GestureState.FAILED); return; diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index c89eac3..d449856 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -24,9 +24,6 @@ package org.gestouch.gestures */ public var direction:uint = PanGestureDirection.NO_DIRECTION; - protected var _touchBeginX:Array = []; - protected var _touchBeginY:Array = []; - public function PanGesture(target:InteractiveObject = null) { @@ -91,15 +88,6 @@ package org.gestouch.gestures return PanGesture; } - - override public function reset():void - { - _touchBeginX.length = 0; - _touchBeginY.length = 0; - - super.reset(); - } - @@ -118,9 +106,6 @@ package org.gestouch.gestures return; } - _touchBeginX[touch.id] = touch.x; - _touchBeginY[touch.id] = touch.y; - if (touchesCount >= minNumTouchesRequired) { updateLocation(); @@ -141,9 +126,17 @@ package org.gestouch.gestures if (state == GestureState.POSSIBLE) { // Check if finger moved enough for gesture to be recognized - var dx:Number = (direction == PanGestureDirection.VERTICAL) ? 0 : Number(_touchBeginX[touch.id]) - touch.x; - var dy:Number = (direction == PanGestureDirection.HORIZONTAL) ? 0 : Number(_touchBeginY[touch.id]) - touch.y; - if (Math.sqrt(dx*dx + dy*dy) > slop || slop != slop)//faster isNaN(slop) + var locationOffset:Point = touch.locationOffset; + if (direction == PanGestureDirection.VERTICAL) + { + locationOffset.x = 0; + } + else if (direction == PanGestureDirection.HORIZONTAL) + { + locationOffset.y = 0; + } + + if (locationOffset.length > slop || slop != slop)//faster isNaN(slop) { prevLocationX = _location.x; prevLocationY = _location.y; diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 1e52c4d..add1e79 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -21,8 +21,6 @@ package org.gestouch.gestures { public var slop:Number = Gesture.DEFAULT_SLOP >> 1; - protected var _touchBeginX:Array = []; - protected var _touchBeginY:Array = []; protected var _rotationVector:Point = new Point(); protected var _firstTouch:Touch; protected var _secondTouch:Touch; @@ -47,15 +45,6 @@ package org.gestouch.gestures return RotateGesture; } - - override public function reset():void - { - _touchBeginX.length = 0; - _touchBeginY.length = 0; - - super.reset(); - } - @@ -82,13 +71,7 @@ package org.gestouch.gestures { _secondTouch = touch; - _touchBeginX[_firstTouch.id] = _firstTouch.x; - _touchBeginY[_firstTouch.id] = _firstTouch.y; - _touchBeginX[_secondTouch.id] = _secondTouch.x; - _touchBeginY[_secondTouch.id] = _secondTouch.y; - - _rotationVector.x = _secondTouch.x - _firstTouch.x; - _rotationVector.y = _secondTouch.y - _firstTouch.y; + _rotationVector = _secondTouch.location.subtract(_firstTouch.location); } } @@ -106,19 +89,14 @@ package org.gestouch.gestures if (touchesCount == 2) { - var currRotationVector:Point = new Point(_secondTouch.x - _firstTouch.x, _secondTouch.y - _firstTouch.y); var recognized:Boolean; if (state == GestureState.POSSIBLE) { // we start once any finger moved enough - var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; - var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; - if (Math.sqrt(dx*dx + dy*dy) > slop || slop != slop)//faster isNaN(slop) + if (touch.locationOffset.length > slop || slop != slop)//faster isNaN(slop) { recognized = true; - _rotationVector.x = _secondTouch.x - _firstTouch.x; - _rotationVector.y = _secondTouch.y - _firstTouch.y; } } else @@ -128,13 +106,14 @@ package org.gestouch.gestures if (recognized) { - updateLocation(); - + var currRotationVector:Point = _secondTouch.location.subtract(_firstTouch.location); var rotation:Number = Math.atan2(currRotationVector.y, currRotationVector.x) - Math.atan2(_rotationVector.y, _rotationVector.x); rotation *= GestureUtils.RADIANS_TO_DEGREES; _rotationVector.x = currRotationVector.x; _rotationVector.y = currRotationVector.y; + updateLocation(); + if (state == GestureState.POSSIBLE) { if (setState(GestureState.BEGAN) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index b73a3c0..52f31c7 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -24,8 +24,6 @@ package org.gestouch.gestures public var maxTapDuration:uint = 1500; protected var _timer:Timer; - protected var _touchBeginX:Array = []; - protected var _touchBeginY:Array = []; protected var _numTouchesRequiredReached:Boolean; protected var _tapCounter:uint = 0; @@ -51,9 +49,7 @@ package org.gestouch.gestures override public function reset():void - { - _touchBeginX.length = 0; - _touchBeginY.length = 0; + { _numTouchesRequiredReached = false; _tapCounter = 0; _timer.reset(); @@ -100,9 +96,6 @@ package org.gestouch.gestures return; } - _touchBeginX[touch.id] = touch.x; - _touchBeginY[touch.id] = touch.y; - if (touchesCount == 1) { _timer.reset(); @@ -119,15 +112,9 @@ package org.gestouch.gestures override protected function onTouchMove(touch:Touch):void { - if (slop >= 0) + if (slop >= 0 && touch.locationOffset.length > slop) { - // Fail if touch overcome slop distance - var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; - var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; - if (Math.sqrt(dx*dx + dy*dy) > slop) - { - setState(GestureState.FAILED); - } + setState(GestureState.FAILED); } } diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 4d0cf12..c3e1cd4 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -21,8 +21,6 @@ package org.gestouch.gestures public var slop:Number = Gesture.DEFAULT_SLOP >> 1; public var lockAspectRatio:Boolean = true; - protected var _touchBeginX:Array = []; - protected var _touchBeginY:Array = []; protected var _scaleVector:Point = new Point(); protected var _firstTouch:Touch; protected var _secondTouch:Touch; @@ -47,15 +45,6 @@ package org.gestouch.gestures return ZoomGesture; } - - override public function reset():void - { - _touchBeginX.length = 0; - _touchBeginY.length = 0; - - super.reset(); - } - @@ -82,13 +71,7 @@ package org.gestouch.gestures { _secondTouch = touch; - _touchBeginX[_firstTouch.id] = _firstTouch.x; - _touchBeginY[_firstTouch.id] = _firstTouch.y; - _touchBeginX[_secondTouch.id] = _secondTouch.x; - _touchBeginY[_secondTouch.id] = _secondTouch.y; - - _scaleVector.x = _secondTouch.x - _firstTouch.x; - _scaleVector.y = _secondTouch.y - _firstTouch.y; + _scaleVector = _secondTouch.location.subtract(_firstTouch.location); } } @@ -106,19 +89,14 @@ package org.gestouch.gestures if (touchesCount == 2) { - var currScaleVector:Point = new Point(_secondTouch.x - _firstTouch.x, _secondTouch.y - _firstTouch.y); var recognized:Boolean; if (state == GestureState.POSSIBLE) { // Check if finger moved enough for gesture to be recognized - var dx:Number = Number(_touchBeginX[touch.id]) - touch.x; - var dy:Number = Number(_touchBeginY[touch.id]) - touch.y; - if (Math.sqrt(dx*dx + dy*dy) > slop || slop != slop)//faster isNaN(slop) + if (touch.locationOffset.length > slop || slop != slop)//faster isNaN(slop) { recognized = true; - _scaleVector.x = _secondTouch.x - _firstTouch.x; - _scaleVector.y = _secondTouch.y - _firstTouch.y; } } else @@ -128,8 +106,7 @@ package org.gestouch.gestures if (recognized) { - updateLocation(); - + var currScaleVector:Point = _secondTouch.location.subtract(_firstTouch.location); var scaleX:Number; var scaleY:Number; if (lockAspectRatio) @@ -145,6 +122,8 @@ package org.gestouch.gestures _scaleVector.x = currScaleVector.x; _scaleVector.y = currScaleVector.y; + updateLocation(); + if (state == GestureState.POSSIBLE) { if (setState(GestureState.BEGAN) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) diff --git a/src/org/gestouch/input/MouseInputAdapter.as b/src/org/gestouch/input/MouseInputAdapter.as index 51ce265..38094fe 100644 --- a/src/org/gestouch/input/MouseInputAdapter.as +++ b/src/org/gestouch/input/MouseInputAdapter.as @@ -7,6 +7,7 @@ package org.gestouch.input import flash.display.Stage; import flash.events.EventPhase; import flash.events.MouseEvent; + import flash.geom.Point; import flash.utils.getTimer; @@ -78,9 +79,9 @@ package org.gestouch.input var touch:Touch = _touchesManager.createTouch(); touch.id = 0; touch.target = event.target as InteractiveObject; - touch.x = event.stageX; - touch.y = event.stageY; - touch.time = getTimer(); + touch.gestouch_internal::setLocation(new Point(event.stageX, event.stageY)); + touch.gestouch_internal::setTime(getTimer()); + touch.gestouch_internal::setBeginTime(getTimer()); _touchesManager.addTouch(touch); @@ -96,9 +97,8 @@ package org.gestouch.input return; var touch:Touch = _touchesManager.getTouch(PRIMARY_TOUCH_POINT_ID); - touch.x = event.stageX; - touch.y = event.stageY; - touch.time = getTimer(); + touch.gestouch_internal::updateLocation(event.stageX, event.stageY); + touch.gestouch_internal::setTime(getTimer()); _gesturesManager.gestouch_internal::onTouchMove(touch); } @@ -117,9 +117,8 @@ package org.gestouch.input return; var touch:Touch = _touchesManager.getTouch(PRIMARY_TOUCH_POINT_ID); - touch.x = event.stageX; - touch.y = event.stageY; - touch.time = getTimer(); + touch.gestouch_internal::updateLocation(event.stageX, event.stageY); + touch.gestouch_internal::setTime(getTimer()); _gesturesManager.gestouch_internal::onTouchEnd(touch); diff --git a/src/org/gestouch/input/TouchInputAdapter.as b/src/org/gestouch/input/TouchInputAdapter.as index f4d87a7..b86afab 100644 --- a/src/org/gestouch/input/TouchInputAdapter.as +++ b/src/org/gestouch/input/TouchInputAdapter.as @@ -7,6 +7,7 @@ package org.gestouch.input import flash.display.Stage; import flash.events.EventPhase; import flash.events.TouchEvent; + import flash.geom.Point; import flash.utils.getTimer; @@ -81,19 +82,20 @@ package org.gestouch.input var touch:Touch = _touchesManager.createTouch(); touch.id = event.touchPointID; touch.target = event.target as InteractiveObject; - touch.x = event.stageX; - touch.y = event.stageY; + 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.time = event["timestamp"]; + touch.gestouch_internal::setTime(event["timestamp"]); + touch.gestouch_internal::setBeginTime(event["timestamp"]); } else { - touch.time = getTimer(); + touch.gestouch_internal::setTime(getTimer()); + touch.gestouch_internal::setBeginTime(getTimer()); } _touchesManager.addTouch(touch); @@ -111,19 +113,18 @@ package org.gestouch.input return; var touch:Touch = _touchesManager.getTouch(event.touchPointID); - touch.x = event.stageX; - touch.y = event.stageY; + 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.time = event["timestamp"]; + touch.gestouch_internal::setTime(event["timestamp"]); } else { - touch.time = getTimer(); + touch.gestouch_internal::setTime(getTimer()); } _gesturesManager.gestouch_internal::onTouchMove(touch); @@ -142,19 +143,18 @@ package org.gestouch.input return; var touch:Touch = _touchesManager.getTouch(event.touchPointID); - touch.x = event.stageX; - touch.y = event.stageY; + 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.time = event["timestamp"]; + touch.gestouch_internal::setTime(event["timestamp"]); } else { - touch.time = getTimer(); + touch.gestouch_internal::setTime(getTimer()); } _gesturesManager.gestouch_internal::onTouchEnd(touch); From 242966790a281b89acb2e68a34549c61ac8aede2 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Sat, 3 Mar 2012 17:41:29 +0200 Subject: [PATCH 18/87] New gesture state --- src/org/gestouch/core/GestureState.as | 15 ++++++++------- src/org/gestouch/gestures/Gesture.as | 11 ++++++++--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/org/gestouch/core/GestureState.as b/src/org/gestouch/core/GestureState.as index c496b0e..07c9e75 100644 --- a/src/org/gestouch/core/GestureState.as +++ b/src/org/gestouch/core/GestureState.as @@ -5,12 +5,13 @@ package org.gestouch.core */ public class GestureState { - public static const POSSIBLE:uint = 1 << 0;//1 - public static const BEGAN:uint = 1 << 1;//2 - public static const CHANGED:uint = 1 << 2;//4 - public static const ENDED:uint = 1 << 3;//8 - public static const CANCELLED:uint = 1 << 4;//16 - public static const FAILED:uint = 1 << 5;//32 - public static const RECOGNIZED:uint = 1 << 6;//64 + 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 } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 6c859c2..4581676 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -105,7 +105,7 @@ package org.gestouch.gestures _enabled = value; //TODO - if (!_enabled && touchesCount > 0) + if (!_enabled && state != GestureState.IDLE) { setState(GestureState.CANCELLED); reset(); @@ -113,7 +113,7 @@ package org.gestouch.gestures } - protected var _state:uint = GestureState.POSSIBLE; + protected var _state:uint = GestureState.IDLE; public function get state():uint { return _state; @@ -184,7 +184,7 @@ package org.gestouch.gestures _touchesMap = {}; _touchesCount = 0; - setState(GestureState.POSSIBLE); + setState(GestureState.IDLE); } @@ -396,6 +396,11 @@ package org.gestouch.gestures _touchesCount++; onTouchBegin(touch); + + if (_touchesCount == 1 && state == GestureState.IDLE) + { + setState(GestureState.POSSIBLE); + } } From 49b1139b4f3717a82651b699d0a215e21a1d520f Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 6 Mar 2012 00:50:34 +0200 Subject: [PATCH 19/87] Touch properties update --- src/org/gestouch/core/Touch.as | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/org/gestouch/core/Touch.as b/src/org/gestouch/core/Touch.as index dddcb81..9a707fa 100644 --- a/src/org/gestouch/core/Touch.as +++ b/src/org/gestouch/core/Touch.as @@ -43,11 +43,14 @@ package org.gestouch.core { _location = value; _beginLocation = _location.clone(); + _previousLocation = _location.clone(); } gestouch_internal function updateLocation(x:Number, y:Number):void { if (_location) { + _previousLocation.x = _location.x; + _previousLocation.y = _location.y; _location.x = x; _location.y = y; } @@ -58,6 +61,13 @@ package org.gestouch.core } + protected var _previousLocation:Point; + public function get previousLocation():Point + { + return _previousLocation.clone(); + } + + protected var _beginLocation:Point; public function get beginLocation():Point { From 398e41f610814539d3757ccafefe60896ab732a6 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 6 Mar 2012 00:51:25 +0200 Subject: [PATCH 20/87] TouchesManager should not clone touches because they must persist during touch session. also good for performance. --- src/org/gestouch/core/TouchesManager.as | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/org/gestouch/core/TouchesManager.as b/src/org/gestouch/core/TouchesManager.as index bc482ce..1e96a8f 100644 --- a/src/org/gestouch/core/TouchesManager.as +++ b/src/org/gestouch/core/TouchesManager.as @@ -103,8 +103,7 @@ package org.gestouch.core public function getTouch(touchPointID:int):Touch { - var touch:Touch = _touchesMap[touchPointID] as Touch; - return touch ? touch.clone() : null; + return _touchesMap[touchPointID] as Touch; } } } \ No newline at end of file From 06df91ce04f4ed3bb6710f8538b33856733db9fb Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 6 Mar 2012 00:52:50 +0200 Subject: [PATCH 21/87] Custom GestureEvent and TransformGestureEvent because native one have useless phase and stupid constants --- src/org/gestouch/events/GestureEvent.as | 45 ++++++++++++++++ .../gestouch/events/TransformGestureEvent.as | 51 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/org/gestouch/events/GestureEvent.as create mode 100644 src/org/gestouch/events/TransformGestureEvent.as diff --git a/src/org/gestouch/events/GestureEvent.as b/src/org/gestouch/events/GestureEvent.as new file mode 100644 index 0000000..253258d --- /dev/null +++ b/src/org/gestouch/events/GestureEvent.as @@ -0,0 +1,45 @@ +package org.gestouch.events +{ + import flash.events.Event; + + + /** + * @author Pavel fljot + */ + public class GestureEvent extends Event + { + public var gestureState:uint; + public var stageX:Number; + public var stageY:Number; + public var localX:Number; + public var localY:Number; + + + public function GestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, + gestureState:uint = 0, + stageX:Number = 0, stageY:Number = 0, + localX:Number = 0, localY:Number = 0) + { + super(type, bubbles, cancelable); + + this.gestureState = gestureState; + this.stageX = stageX; + this.stageY = stageY; + this.localX = localX; + this.localY = localY; + } + + + override public function clone():Event + { + return new GestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); + } + + + override public function toString():String + { + return formatToString("org.gestouch.events.GestureEvent", "bubbles", "cancelable", + "gestureState", "stageX", "stageY", "localX", "localY"); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/TransformGestureEvent.as b/src/org/gestouch/events/TransformGestureEvent.as new file mode 100644 index 0000000..1af2a5e --- /dev/null +++ b/src/org/gestouch/events/TransformGestureEvent.as @@ -0,0 +1,51 @@ +package org.gestouch.events +{ + import flash.events.Event; + + + /** + * @author Pavel fljot + */ + public class TransformGestureEvent extends GestureEvent + { + public static const GESTURE_TRANSFORM:String = "gestureTransform"; + + public var scaleX:Number; + public var scaleY:Number; + public var rotation:Number; + public var offsetX:Number; + public var offsetY:Number; + + + public function TransformGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, + gestureState:uint = 0, + stageX:Number = 0, stageY:Number = 0, + localX:Number = 0, localY:Number = 0, + scaleX:Number = 1.0, scaleY:Number = 1.0, + rotation:Number = 0, + offsetX:Number = 0, offsetY:Number = 0) + { + super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); + + this.scaleX = scaleX; + this.scaleY = scaleY; + this.rotation = rotation; + this.offsetX = offsetX; + this.offsetY = offsetY; + } + + + override public function clone():Event + { + return new TransformGestureEvent(type, bubbles, cancelable, gestureState, + stageX, stageY, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY); + } + + + override public function toString():String + { + return formatToString("org.gestouch.events.TransformGestureEvent", "bubbles", "cancelable", + "gestureState", "stageX", "stageY", "localX", "localY", "scaleX", "scaleY", "offsetX", "offsetY", "rotation"); + } + } +} \ No newline at end of file From 5f28227c75b0c937f239dfa725bb190183eabd17 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 6 Mar 2012 01:12:46 +0200 Subject: [PATCH 22/87] Using custom GestureEvent and TransformGestureEvent from now on --- .../gestouch/events/LongPressGestureEvent.as | 10 +- src/org/gestouch/events/PanGestureEvent.as | 11 +- src/org/gestouch/events/RotateGestureEvent.as | 11 +- src/org/gestouch/events/SwipeGestureEvent.as | 11 +- src/org/gestouch/events/TapGestureEvent.as | 9 +- src/org/gestouch/events/ZoomGestureEvent.as | 11 +- src/org/gestouch/gestures/LongPressGesture.as | 10 +- src/org/gestouch/gestures/PanGesture.as | 10 +- src/org/gestouch/gestures/RotateGesture.as | 83 ++++++-------- src/org/gestouch/gestures/SwipeGesture.as | 21 +++- src/org/gestouch/gestures/TapGesture.as | 5 +- src/org/gestouch/gestures/ZoomGesture.as | 103 ++++++++---------- 12 files changed, 149 insertions(+), 146 deletions(-) diff --git a/src/org/gestouch/events/LongPressGestureEvent.as b/src/org/gestouch/events/LongPressGestureEvent.as index 01cba3b..790b979 100644 --- a/src/org/gestouch/events/LongPressGestureEvent.as +++ b/src/org/gestouch/events/LongPressGestureEvent.as @@ -1,7 +1,6 @@ package org.gestouch.events { import flash.events.Event; - import flash.events.GestureEvent; /** @@ -12,15 +11,18 @@ package org.gestouch.events public static const GESTURE_LONG_PRESS:String = "gestureLongPress"; - public function LongPressGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0) + public function LongPressGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, + gestureState:uint = 0, + stageX:Number = 0, stageY:Number = 0, + localX:Number = 0, localY:Number = 0) { - super(type, bubbles, cancelable, phase, localX, localY); + super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); } override public function clone():Event { - return new LongPressGestureEvent(type, bubbles, cancelable, phase, localX, localY); + return new LongPressGestureEvent(type, bubbles, cancelable, gestureState, localX, localY); } diff --git a/src/org/gestouch/events/PanGestureEvent.as b/src/org/gestouch/events/PanGestureEvent.as index 6fb62bf..ad6bfba 100644 --- a/src/org/gestouch/events/PanGestureEvent.as +++ b/src/org/gestouch/events/PanGestureEvent.as @@ -1,7 +1,6 @@ package org.gestouch.events { import flash.events.Event; - import flash.events.TransformGestureEvent; /** @@ -12,15 +11,19 @@ package org.gestouch.events public static const GESTURE_PAN:String = "gesturePan"; - public function PanGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, offsetX:Number = 0, offsetY:Number = 0) + public function PanGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, + gestureState:uint = 0, + stageX:Number = 0, stageY:Number = 0, + localX:Number = 0, localY:Number = 0, + offsetX:Number = 0, offsetY:Number = 0) { - super(type, bubbles, cancelable, phase, localX, localY, 1, 1, 0, offsetX, offsetY); + super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, 1, 1, 0, offsetX, offsetY); } override public function clone():Event { - return new PanGestureEvent(type, bubbles, cancelable, phase, localX, localY, offsetX, offsetY); + return new PanGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, offsetX, offsetY); } diff --git a/src/org/gestouch/events/RotateGestureEvent.as b/src/org/gestouch/events/RotateGestureEvent.as index cb25499..44a2074 100644 --- a/src/org/gestouch/events/RotateGestureEvent.as +++ b/src/org/gestouch/events/RotateGestureEvent.as @@ -1,7 +1,6 @@ package org.gestouch.events { import flash.events.Event; - import flash.events.TransformGestureEvent; /** @@ -12,15 +11,19 @@ package org.gestouch.events public static const GESTURE_ROTATE:String = "gestureRotate"; - public function RotateGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, rotation:Number = 0) + public function RotateGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, + gestureState:uint = 0, + stageX:Number = 0, stageY:Number = 0, + localX:Number = 0, localY:Number = 0, + rotation:Number = 0) { - super(type, bubbles, cancelable, phase, localX, localY, 1, 1, rotation, localX, localY); + super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, 1, 1, rotation); } override public function clone():Event { - return new RotateGestureEvent(type, bubbles, cancelable, phase, localX, localY, rotation); + return new RotateGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, rotation); } diff --git a/src/org/gestouch/events/SwipeGestureEvent.as b/src/org/gestouch/events/SwipeGestureEvent.as index 92dd1e8..d591b01 100644 --- a/src/org/gestouch/events/SwipeGestureEvent.as +++ b/src/org/gestouch/events/SwipeGestureEvent.as @@ -1,7 +1,6 @@ package org.gestouch.events { import flash.events.Event; - import flash.events.TransformGestureEvent; /** @@ -12,15 +11,19 @@ package org.gestouch.events public static const GESTURE_SWIPE:String = "gestureSwipe"; - public function SwipeGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, offsetX:Number = 0, offsetY:Number = 0) + public function SwipeGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, + gestureState:uint = 0, + stageX:Number = 0, stageY:Number = 0, + localX:Number = 0, localY:Number = 0, + offsetX:Number = 0, offsetY:Number = 0) { - super(type, bubbles, cancelable, phase, localX, localY, 1, 1, 0, offsetX, offsetY); + super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, 1, 1, 0, offsetX, offsetY); } override public function clone():Event { - return new SwipeGestureEvent(type, bubbles, cancelable, phase, localX, localY, offsetX, offsetY); + return new SwipeGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, offsetX, offsetY); } diff --git a/src/org/gestouch/events/TapGestureEvent.as b/src/org/gestouch/events/TapGestureEvent.as index 46a4b02..6539013 100644 --- a/src/org/gestouch/events/TapGestureEvent.as +++ b/src/org/gestouch/events/TapGestureEvent.as @@ -1,7 +1,6 @@ package org.gestouch.events { import flash.events.Event; - import flash.events.GestureEvent; /** @@ -12,15 +11,17 @@ package org.gestouch.events public static const GESTURE_TAP:String = "gestureTap"; - public function TapGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0) + public function TapGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, + gestureState:uint = 0, stageX:Number = 0, stageY:Number = 0, + localX:Number = 0, localY:Number = 0) { - super(type, bubbles, cancelable, phase, localX, localY); + super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); } override public function clone():Event { - return new TapGestureEvent(type, bubbles, cancelable, phase, localX, localY); + return new TapGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); } diff --git a/src/org/gestouch/events/ZoomGestureEvent.as b/src/org/gestouch/events/ZoomGestureEvent.as index 64e98ca..cd2c09b 100644 --- a/src/org/gestouch/events/ZoomGestureEvent.as +++ b/src/org/gestouch/events/ZoomGestureEvent.as @@ -1,7 +1,6 @@ package org.gestouch.events { import flash.events.Event; - import flash.events.TransformGestureEvent; /** @@ -12,15 +11,19 @@ package org.gestouch.events public static const GESTURE_ZOOM:String = "gestureZoom"; - public function ZoomGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1, scaleY:Number = 1) + public function ZoomGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, + gestureState:uint = 0, + stageX:Number = 0, stageY:Number = 0, + localX:Number = 0, localY:Number = 0, + scaleX:Number = 1.0, scaleY:Number = 1.0) { - super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY); + super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, scaleX, scaleY); } override public function clone():Event { - return new ZoomGestureEvent(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY); + return new ZoomGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, scaleX, scaleY); } diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index cc6679f..0b9a488 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -5,7 +5,6 @@ package org.gestouch.gestures import org.gestouch.events.LongPressGestureEvent; import flash.display.InteractiveObject; - import flash.events.GesturePhase; import flash.events.TimerEvent; import flash.utils.Timer; @@ -124,7 +123,8 @@ package org.gestouch.gestures updateLocation(); if (setState(GestureState.CHANGED) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) { - dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y)); + dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GestureState.CHANGED, + _location.x, _location.y, _localLocation.x, _localLocation.y)); } } } @@ -140,7 +140,8 @@ package org.gestouch.gestures updateLocation(); if (setState(GestureState.ENDED) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) { - dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.END, _localLocation.x, _localLocation.y)); + dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GestureState.ENDED, + _location.x, _location.y, _localLocation.x, _localLocation.y)); } } else @@ -170,7 +171,8 @@ package org.gestouch.gestures updateLocation(); if (setState(GestureState.BEGAN) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) { - dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y)); + dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GestureState.BEGAN, + _location.x, _location.y, _localLocation.x, _localLocation.y)); } } } diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index d449856..d06affa 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -5,7 +5,6 @@ package org.gestouch.gestures import org.gestouch.events.PanGestureEvent; import flash.display.InteractiveObject; - import flash.events.GesturePhase; import flash.geom.Point; [Event(name="gesturePan", type="org.gestouch.events.PanGestureEvent")] @@ -155,7 +154,8 @@ package org.gestouch.gestures if (setState(GestureState.BEGAN) && hasEventListener(PanGestureEvent.GESTURE_PAN)) { - dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, offset.x, offset.y)); + dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GestureState.BEGAN, + _location.x, _location.y, _localLocation.x, _localLocation.y, offset.x, offset.y)); } } } @@ -169,7 +169,8 @@ package org.gestouch.gestures if (setState(GestureState.CHANGED) && hasEventListener(PanGestureEvent.GESTURE_PAN)) { - dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, offsetX, offsetY)); + dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GestureState.CHANGED, + _location.x, _location.y, _localLocation.x, _localLocation.y, offsetX, offsetY)); } } } @@ -187,7 +188,8 @@ package org.gestouch.gestures { if (setState(GestureState.ENDED) && hasEventListener(PanGestureEvent.GESTURE_PAN)) { - dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 0, 0)); + dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GestureState.ENDED, + _location.x, _location.y, _localLocation.x, _localLocation.y, 0, 0)); } } } diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index add1e79..da1665b 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -6,13 +6,11 @@ package org.gestouch.gestures import org.gestouch.utils.GestureUtils; import flash.display.InteractiveObject; - import flash.events.GesturePhase; import flash.geom.Point; [Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")] /** * TODO: - * -location * -check native behavior on iDevice * * @author Pavel fljot @@ -21,9 +19,9 @@ package org.gestouch.gestures { public var slop:Number = Gesture.DEFAULT_SLOP >> 1; - protected var _rotationVector:Point = new Point(); - protected var _firstTouch:Touch; - protected var _secondTouch:Touch; + protected var _touch1:Touch; + protected var _touch2:Touch; + protected var _transformVector:Point; public function RotateGesture(target:InteractiveObject = null) @@ -65,68 +63,53 @@ package org.gestouch.gestures if (touchesCount == 1) { - _firstTouch = touch; + _touch1 = touch; } else { - _secondTouch = touch; + _touch2 = touch; - _rotationVector = _secondTouch.location.subtract(_firstTouch.location); + _transformVector = _touch2.location.subtract(_touch1.location); } } override protected function onTouchMove(touch:Touch):void { - if (touch.id == _firstTouch.id) + if (touchesCount < 2) + return; + + var recognized:Boolean = true; + + if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop) { - _firstTouch = touch; - } - else - { - _secondTouch = touch; + recognized = false; } - if (touchesCount == 2) + if (recognized) { - var recognized:Boolean; + 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) { - // we start once any finger moved enough - if (touch.locationOffset.length > slop || slop != slop)//faster isNaN(slop) + if (setState(GestureState.BEGAN) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) { - recognized = true; + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.BEGAN, + _location.x, _location.y, _localLocation.x, _localLocation.y, rotation)); } } else { - recognized = true; - } - - if (recognized) - { - var currRotationVector:Point = _secondTouch.location.subtract(_firstTouch.location); - var rotation:Number = Math.atan2(currRotationVector.y, currRotationVector.x) - Math.atan2(_rotationVector.y, _rotationVector.x); - rotation *= GestureUtils.RADIANS_TO_DEGREES; - _rotationVector.x = currRotationVector.x; - _rotationVector.y = currRotationVector.y; - - updateLocation(); - - if (state == GestureState.POSSIBLE) + if (setState(GestureState.CHANGED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) { - if (setState(GestureState.BEGAN) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) - { - dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, rotation)); - } - } - else - { - if (setState(GestureState.CHANGED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) - { - dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, rotation)); - } + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.CHANGED, + _location.x, _location.y, _localLocation.x, _localLocation.y, rotation)); } } } @@ -141,7 +124,8 @@ package org.gestouch.gestures { if (setState(GestureState.ENDED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) { - dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 0)); + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.ENDED, + _location.x, _location.y, _localLocation.x, _localLocation.y, 0)); } } else if (state == GestureState.POSSIBLE) @@ -151,16 +135,19 @@ package org.gestouch.gestures } else// == 1 { - if (touch.id == _firstTouch.id) + if (touch == _touch1) { - _firstTouch = _secondTouch; + _touch1 = _touch2; } + _touch2 = null; + if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); if (setState(GestureState.CHANGED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) { - dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, 0)); + dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.CHANGED, + _location.x, _location.y, _localLocation.x, _localLocation.y, 0)); } } } diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 1121d51..2db60c4 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -5,7 +5,6 @@ package org.gestouch.gestures import org.gestouch.events.SwipeGestureEvent; import flash.display.InteractiveObject; - import flash.events.GesturePhase; import flash.geom.Point; import flash.system.Capabilities; @@ -72,14 +71,19 @@ package org.gestouch.gestures { if (touchesCount > numTouchesRequired) { + //TODO: or ignore? setState(GestureState.FAILED); return; } + if (touchesCount == 1) + { + // Because we want to fail as quick as possible + _startTime = touch.time; + } if (touchesCount == numTouchesRequired) { updateLocation(); - _startTime = touch.time; // cache direction condition for performance _noDirection = (SwipeGestureDirection.ORTHOGONAL & direction) == 0; @@ -100,7 +104,6 @@ package org.gestouch.gestures var timeDelta:int = touch.time - _startTime; var vel:Number = offsetLength / timeDelta; var absVel:Number = vel > 0 ? vel : -vel;//faster Math.abs() -// trace(_offset, _offset.length, ".....velocity:", vel); if (offsetLength < Gesture.DEFAULT_SLOP) { @@ -123,7 +126,9 @@ package org.gestouch.gestures { if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { - dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); + _localLocation = target.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)); } } } @@ -154,7 +159,9 @@ package org.gestouch.gestures { if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { - dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, _offset.x, 0)); + _localLocation = target.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, 0)); } } } @@ -177,7 +184,9 @@ package org.gestouch.gestures { if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { - dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y, 0, _offset.y)); + _localLocation = target.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, 0, _offset.y)); } } } diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index 52f31c7..9d7507c 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -5,11 +5,11 @@ package org.gestouch.gestures import org.gestouch.events.TapGestureEvent; import flash.display.InteractiveObject; - import flash.events.GesturePhase; import flash.events.TimerEvent; import flash.utils.Timer; + [Event(name="gestureTap", type="org.gestouch.events.TapGestureEvent")] /** * TODO: check failing conditions (iDevice) * @@ -139,7 +139,8 @@ package org.gestouch.gestures updateLocation(); if (setState(GestureState.RECOGNIZED) && hasEventListener(TapGestureEvent.GESTURE_TAP)) { - dispatchEvent(new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GesturePhase.ALL, _localLocation.x, _localLocation.y)); + dispatchEvent(new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GestureState.RECOGNIZED, + _location.x, _location.y, _localLocation.x, _localLocation.y)); } } else diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index c3e1cd4..09608b3 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -5,13 +5,11 @@ package org.gestouch.gestures import org.gestouch.events.ZoomGestureEvent; import flash.display.InteractiveObject; - import flash.events.GesturePhase; import flash.geom.Point; [Event(name="gestureZoom", type="org.gestouch.events.ZoomGestureEvent")] /** * TODO: - * -location * -check native behavior on iDevice * * @author Pavel fljot @@ -21,9 +19,9 @@ package org.gestouch.gestures public var slop:Number = Gesture.DEFAULT_SLOP >> 1; public var lockAspectRatio:Boolean = true; - protected var _scaleVector:Point = new Point(); - protected var _firstTouch:Touch; - protected var _secondTouch:Touch; + protected var _touch1:Touch; + protected var _touch2:Touch; + protected var _transformVector:Point; public function ZoomGesture(target:InteractiveObject = null) @@ -65,78 +63,63 @@ package org.gestouch.gestures if (touchesCount == 1) { - _firstTouch = touch; + _touch1 = touch; } else// == 2 { - _secondTouch = touch; + _touch2 = touch; - _scaleVector = _secondTouch.location.subtract(_firstTouch.location); + _transformVector = _touch2.location.subtract(_touch1.location); } } override protected function onTouchMove(touch:Touch):void { - if (touch.id == _firstTouch.id) + if (touchesCount < 2) + return; + + var recognized:Boolean = true; + + if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop) { - _firstTouch = touch; - } - else - { - _secondTouch = touch; + recognized = false; } - if (touchesCount == 2) + if (recognized) { - var recognized:Boolean; + 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) { - // Check if finger moved enough for gesture to be recognized - if (touch.locationOffset.length > slop || slop != slop)//faster isNaN(slop) + if (setState(GestureState.BEGAN) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) { - recognized = true; + dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.BEGAN, + _location.x, _location.y, _localLocation.x, _localLocation.y, scaleX, scaleY)); } } else { - recognized = true; - } - - if (recognized) - { - var currScaleVector:Point = _secondTouch.location.subtract(_firstTouch.location); - var scaleX:Number; - var scaleY:Number; - if (lockAspectRatio) + if (setState(GestureState.CHANGED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) { - scaleX = scaleY = currScaleVector.length / _scaleVector.length; - } - else - { - scaleX = currScaleVector.x / _scaleVector.x; - scaleY = currScaleVector.y / _scaleVector.y; - } - - _scaleVector.x = currScaleVector.x; - _scaleVector.y = currScaleVector.y; - - updateLocation(); - - if (state == GestureState.POSSIBLE) - { - if (setState(GestureState.BEGAN) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) - { - dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.BEGIN, _localLocation.x, _localLocation.y, scaleX, scaleY)); - } - } - else - { - if (setState(GestureState.CHANGED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) - { - dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.UPDATE, _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)); } } } @@ -151,7 +134,8 @@ package org.gestouch.gestures { if (setState(GestureState.ENDED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) { - dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.END, _localLocation.x, _localLocation.y, 1, 1)); + dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.ENDED, + _location.x, _location.y, _localLocation.x, _localLocation.y, 1, 1)); } } else if (state == GestureState.POSSIBLE) @@ -161,16 +145,19 @@ package org.gestouch.gestures } else//== 1 { - if (touch.id == _firstTouch.id) + if (touch == _touch1) { - _firstTouch = _secondTouch; + _touch1 = _touch2; } + _touch2 = null; + if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); if (setState(GestureState.CHANGED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) { - dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GesturePhase.UPDATE, _localLocation.x, _localLocation.y, 1, 1)); + dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.CHANGED, + _location.x, _location.y, _localLocation.x, _localLocation.y, 1, 1)); } } } From b92205784589ce7ec9efa108e393173bf2d23a0a Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 6 Mar 2012 01:22:42 +0200 Subject: [PATCH 23/87] New gesture for free transformation more precise and performant then combination of 3 --- src/org/gestouch/gestures/TransformGesture.as | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 src/org/gestouch/gestures/TransformGesture.as diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as new file mode 100644 index 0000000..b3a2daa --- /dev/null +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -0,0 +1,187 @@ +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; + + + [Event(name="gestureTransform", type="org.gestouch.events.TransformGestureEvent")] + /** + * @author Pavel fljot + */ + public class TransformGesture extends Gesture + { + public var slop:Number = Gesture.DEFAULT_SLOP; + + protected var _touch1:Touch; + protected var _touch2:Touch; + protected var _transformVector:Point; + + + public function TransformGesture(target:InteractiveObject = null) + { + super(target); + } + + + + + // -------------------------------------------------------------------------- + // + // Public methods + // + // -------------------------------------------------------------------------- + + override public function reflect():Class + { + return TransformGesture; + } + + + override public function reset():void + { + _touch1 = null; + _touch2 = null; + + super.reset(); + } + + + + + // -------------------------------------------------------------------------- + // + // Protected methods + // + // -------------------------------------------------------------------------- + + override protected function onTouchBegin(touch:Touch):void + { + if (touchesCount > 2) + { + //TODO: to ignore or to keep this touch somewhere? + ignoreTouch(touch); + return; + } + + if (touchesCount == 1) + { + _touch1 = touch; + } + else + { + _touch2 = touch; + + _transformVector = _touch2.location.subtract(_touch1.location); + } + + updateLocation(); + + if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + if (setState(GestureState.CHANGED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) + { + dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.CHANGED, + _location.x, _location.y, _localLocation.x, _localLocation.y)); + } + } + } + + + override protected function onTouchMove(touch:Touch):void + { + var prevLocation:Point = _location.clone(); + updateLocation(); + + var recognized:Boolean = true; + + if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop) + { + recognized = false; + } + + if (recognized) + { + 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) + { + 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); + } + + + if (state == GestureState.POSSIBLE) + { + 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)); + } + } + } + } + + + override protected function onTouchEnd(touch:Touch):void + { + if (touchesCount == 0) + { + if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + if (setState(GestureState.ENDED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) + { + dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.ENDED, + _location.x, _location.y, _localLocation.x, _localLocation.y)); + } + } + else if (state == GestureState.POSSIBLE) + { + setState(GestureState.FAILED); + } + } + else// == 1 + { + if (touch == _touch1) + { + _touch1 = _touch2; + } + _touch2 = null; + + if (state == GestureState.BEGAN || state == GestureState.CHANGED) + { + updateLocation(); + if (setState(GestureState.CHANGED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) + { + dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.CHANGED, + _location.x, _location.y, _localLocation.x, _localLocation.y)); + } + } + } + } + } +} \ No newline at end of file From 9edcd048781582850a589d66bf3d49c2f5aa7566 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 6 Mar 2012 12:00:38 +0200 Subject: [PATCH 24/87] Tiny cleanup --- src/org/gestouch/gestures/Gesture.as | 1 - 1 file changed, 1 deletion(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 4581676..767e981 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -34,7 +34,6 @@ package org.gestouch.gestures * based on 20 pixels on a 252ppi device. */ public static const DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI); - public static const TOUCH_EVENT_CAPTURE_PRIORITY:int = 10; public var delegate:IGestureDelegate; From 7cdec34be4427a06fa7acfdb1393cd0fab492a35 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 6 Mar 2012 23:17:23 +0200 Subject: [PATCH 25/87] Swipe gesture algorithm rewritten for better recognition and failing --- src/org/gestouch/gestures/SwipeGesture.as | 92 ++++++++++++++--------- 1 file changed, 58 insertions(+), 34 deletions(-) diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 2db60c4..5f83b13 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -6,7 +6,6 @@ package org.gestouch.gestures import flash.display.InteractiveObject; import flash.geom.Point; - import flash.system.Capabilities; [Event(name="gestureSwipe", type="org.gestouch.events.SwipeGestureEvent")] /** @@ -17,16 +16,19 @@ package org.gestouch.gestures */ public class SwipeGesture extends Gesture { + public var slop:Number = Gesture.DEFAULT_SLOP; public var numTouchesRequired:uint = 1; - public var velocityThreshold:Number = 0.1; - public var minVelocity:Number = 1.5; - public var minDistance:Number = Capabilities.screenDPI * 0.5; - + 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; 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; public function SwipeGesture(target:InteractiveObject = null) @@ -54,6 +56,7 @@ package org.gestouch.gestures _startTime = 0; _offset.x = 0; _offset.y = 0; + _decelerationCounter = 0; super.reset(); } @@ -66,7 +69,7 @@ package org.gestouch.gestures // Protected methods // // -------------------------------------------------------------------------- - + override protected function onTouchBegin(touch:Touch):void { if (touchesCount > numTouchesRequired) @@ -84,6 +87,7 @@ package org.gestouch.gestures if (touchesCount == numTouchesRequired) { updateLocation(); + _avrgVel.x = _avrgVel.y = 0; // cache direction condition for performance _noDirection = (SwipeGestureDirection.ORTHOGONAL & direction) == 0; @@ -96,33 +100,46 @@ package org.gestouch.gestures if (touchesCount < numTouchesRequired) return; + var prevCentralPointX:Number = _centralPoint.x; + var prevCentralPointY:Number = _centralPoint.y; updateCentralPoint(); _offset.x = _centralPoint.x - _location.x; _offset.y = _centralPoint.y - _location.y; var offsetLength:Number = _offset.length; - var timeDelta:int = touch.time - _startTime; - var vel:Number = offsetLength / timeDelta; - var absVel:Number = vel > 0 ? vel : -vel;//faster Math.abs() - - if (offsetLength < Gesture.DEFAULT_SLOP) + + if (offsetLength < slop) { // no need in processing - we're in the very beginning of movement return; } - else if (absVel < velocityThreshold) + + // 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; } - var velX:Number = _offset.x / timeDelta; - var velY:Number = _offset.y / timeDelta; - - if (_noDirection) { - if (absVel >= minVelocity || (minDistance != minDistance || offsetLength >= minDistance)) + // 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 (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { @@ -134,28 +151,30 @@ package org.gestouch.gestures } else { + var recentOffsetX:Number = _centralPoint.x - prevCentralPointX; + var recentOffsetY:Number = _centralPoint.y - prevCentralPointY; //faster Math.abs() - var absVelX:Number = velX > 0 ? velX : -velX; - var absVelY:Number = velY > 0 ? velY : -velY; - var absOffsetX:Number = _offset.x > 0 ? _offset.x : -_offset.x; - var absOffsetY:Number = _offset.y > 0 ? _offset.y : -_offset.y; + var absVelX:Number = _avrgVel.x > 0 ? _avrgVel.x : -_avrgVel.x; + var absVelY:Number = _avrgVel.y > 0 ? _avrgVel.y : -_avrgVel.y; if (absVelX > absVelY) { + 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 (velX < 0 && (direction & SwipeGestureDirection.LEFT) == 0) + else if (recentOffsetX < 0 && (direction & SwipeGestureDirection.LEFT) == 0 || + recentOffsetX > 0 && (direction & SwipeGestureDirection.RIGHT) == 0 || + Math.abs(_offset.y) > maxDirectionalOffset) { + // movement in opposite direction + // or too much diagonally setState(GestureState.FAILED); } - else if (velX > 0 && (direction & SwipeGestureDirection.RIGHT) == 0) - { - setState(GestureState.FAILED); - } - else if (absVelX >= minVelocity || (minDistance != minDistance || absOffsetX >= minDistance)) + else if (absVelX >= minVelocity && (minOffset != minOffset || absOffsetX >= minOffset)) { if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { @@ -167,20 +186,22 @@ package org.gestouch.gestures } else if (absVelY > absVelX) { + 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 (velY < 0 && (direction & SwipeGestureDirection.UP) == 0) + else if (recentOffsetY < 0 && (direction & SwipeGestureDirection.UP) == 0 || + recentOffsetY > 0 && (direction & SwipeGestureDirection.DOWN) == 0 || + Math.abs(_offset.x) > maxDirectionalOffset) { + // movement in opposite direction + // or too much diagonally setState(GestureState.FAILED); } - else if (velY > 0 && (direction & SwipeGestureDirection.DOWN) == 0) - { - setState(GestureState.FAILED); - } - else if (absVelY >= minVelocity || (minDistance != minDistance || absOffsetY >= minDistance)) + else if (absVelY >= minVelocity && (minOffset != minOffset || absOffsetY >= minOffset)) { if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { @@ -200,7 +221,10 @@ package org.gestouch.gestures override protected function onTouchEnd(touch:Touch):void { - setState(GestureState.FAILED); + if (touchesCount < numTouchesRequired) + { + setState(GestureState.FAILED); + } } } } \ No newline at end of file From 4e02d4ae63dddcbaccdde8cb4d4d918706497bdc Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 6 Mar 2012 23:27:39 +0200 Subject: [PATCH 26/87] Reformat condition --- src/org/gestouch/gestures/LongPressGesture.as | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 0b9a488..1318fa6 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -110,13 +110,9 @@ package org.gestouch.gestures override protected function onTouchMove(touch:Touch):void { - if (state == GestureState.POSSIBLE && slop > 0) + if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length > slop) { - if (touch.locationOffset.length > slop) - { - setState(GestureState.FAILED); - return; - } + setState(GestureState.FAILED); } else if (state == GestureState.BEGAN || state == GestureState.CHANGED) { From 2efa95b85c7b580db3640723a7f48c4b8f239b8b Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 6 Mar 2012 23:28:12 +0200 Subject: [PATCH 27/87] Experimental requireGestureToFail API implemented --- src/org/gestouch/gestures/Gesture.as | 103 +++++++++++++++++- src/org/gestouch/gestures/LongPressGesture.as | 10 ++ src/org/gestouch/gestures/PanGesture.as | 36 ++++-- src/org/gestouch/gestures/SwipeGesture.as | 17 ++- src/org/gestouch/gestures/TapGesture.as | 10 ++ 5 files changed, 163 insertions(+), 13 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 767e981..c3b9b7f 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -14,6 +14,7 @@ package org.gestouch.gestures import flash.events.EventDispatcher; import flash.geom.Point; import flash.system.Capabilities; + import flash.utils.Dictionary; [Event(name="stateChange", type="org.gestouch.events.GestureStateEvent")] @@ -46,6 +47,12 @@ package org.gestouch.gestures protected var _touchesMap:Object = {}; protected var _centralPoint:Point = new Point(); protected var _localLocation:Point; + /** + * List of gesture we require to fail. + * @see requireGestureToFail() + */ + protected var _gesturesToFail:Dictionary = new Dictionary(true); + protected var _pendingRecognizedState:uint; public function Gesture(target:InteractiveObject = null) @@ -177,12 +184,19 @@ package org.gestouch.gestures */ public function reset():void { - //TODO + //FIXME: proper state change? _location.x = 0; _location.y = 0; _touchesMap = {}; _touchesCount = 0; + for (var key:* in _gesturesToFail) + { + var gestureToFail:Gesture = key as Gesture; + gestureToFail.removeEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler); + } + _pendingRecognizedState = 0; + setState(GestureState.IDLE); } @@ -197,6 +211,7 @@ package org.gestouch.gestures //TODO reset(); target = null; + _gesturesToFail = null; } @@ -212,9 +227,13 @@ package org.gestouch.gestures } + /** + * NB! Current implementation is highly experimental! See examples for more info. + */ public function requireGestureToFail(gesture:Gesture):void { //TODO + _gesturesToFail[gesture] = true; } @@ -319,6 +338,39 @@ package org.gestouch.gestures if (newState == GestureState.BEGAN || newState == GestureState.RECOGNIZED) { + var gestureToFail:Gesture; + // 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) + { + gestureToFail = key as Gesture; + 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 + setState(GestureState.FAILED); + return false; + } + } + // then we check of other required-to-fail gestures are actually tracked (not IDLE) + // and not still not recognized (e.g. POSSIBLE state) + for (key in _gesturesToFail) + { + gestureToFail = key as Gesture; + if (gestureToFail.state == GestureState.POSSIBLE) + { + // Other gesture might fail soon, so we postpone state change + _pendingRecognizedState = newState; + return false; + } + // else if gesture is in IDLE state it means it doesn't track anything, + // so we simply ignore it as it doesn't seem like conflict from this perspective + // (perspective of using "requireGestureToFail" API) + } + + if (delegate && !delegate.gestureShouldBegin(this)) { setState(GestureState.FAILED); @@ -381,6 +433,17 @@ package org.gestouch.gestures } + /** + * Executed once requiredToFail gestures have been failed and + * pending (delayed) recognized state has been entered. + * You must dispatch gesture event here. + */ + protected function onDelayedRecognize():void + { + + } + + //-------------------------------------------------------------------------- @@ -398,6 +461,12 @@ 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); } } @@ -417,5 +486,37 @@ package org.gestouch.gestures onTouchEnd(touch); } + + + protected function gestureToFail_stateChangeHandler(event:GestureStateEvent):void + { + if (state != GestureState.POSSIBLE) + return;//just in case..FIXME? + + if (!_pendingRecognizedState) + return; + + if (event.newState == GestureState.FAILED) + { + for (var key:* in _gesturesToFail) + { + var gestureToFail:Gesture = key as Gesture; + if (gestureToFail.state == GestureState.POSSIBLE) + { + // we're still waiting for some gesture to fail + return; + } + } + + if (setState(_pendingRecognizedState)) + { + onDelayedRecognize(); + } + } + else if (event.newState != GestureState.POSSIBLE) + { + 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 1318fa6..932aa17 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -151,6 +151,16 @@ package org.gestouch.gestures } } + + override protected function onDelayedRecognize():void + { + if (hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) + { + dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GestureState.BEGAN, + _location.x, _location.y, _localLocation.x, _localLocation.y)); + } + } + diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index d06affa..ddf74bf 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -23,6 +23,9 @@ package org.gestouch.gestures */ public var direction:uint = PanGestureDirection.NO_DIRECTION; + protected var _gestureBeginOffsetX:Number; + protected var _gestureBeginOffsetY:Number; + public function PanGesture(target:InteractiveObject = null) { @@ -87,6 +90,15 @@ package org.gestouch.gestures return PanGesture; } + + override public function reset():void + { + _gestureBeginOffsetX = NaN; + _gestureBeginOffsetY = NaN; + + super.reset(); + } + @@ -142,20 +154,14 @@ package org.gestouch.gestures updateLocation(); offsetX = _location.x - prevLocationX; offsetY = _location.y - prevLocationY; - // Unfortunately we create several new point instances here, - // but thats not a big deal since this code executed only once per recognition session - var offset:Point = new Point(offsetX, offsetY); - if (offset.length > slop) - { - var slopVector:Point = offset.clone(); - slopVector.normalize(slop); - offset = offset.subtract(slopVector); - } + // acummulate begin offsets for the case when this gesture recognition is delayed by requireGestureToFail + _gestureBeginOffsetX = (_gestureBeginOffsetX != _gestureBeginOffsetX) ? offsetX : _gestureBeginOffsetX + offsetX; + _gestureBeginOffsetY = (_gestureBeginOffsetY != _gestureBeginOffsetY) ? offsetY : _gestureBeginOffsetY + offsetY; if (setState(GestureState.BEGAN) && hasEventListener(PanGestureEvent.GESTURE_PAN)) { dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GestureState.BEGAN, - _location.x, _location.y, _localLocation.x, _localLocation.y, offset.x, offset.y)); + _location.x, _location.y, _localLocation.x, _localLocation.y, offsetX, offsetY)); } } } @@ -198,5 +204,15 @@ package org.gestouch.gestures updateLocation(); } } + + + override protected function onDelayedRecognize():void + { + if (hasEventListener(PanGestureEvent.GESTURE_PAN)) + { + dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GestureState.BEGAN, + _location.x, _location.y, _localLocation.x, _localLocation.y, _gestureBeginOffsetX, _gestureBeginOffsetY)); + } + } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 5f83b13..9665493 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -176,11 +176,12 @@ package org.gestouch.gestures } else if (absVelX >= minVelocity && (minOffset != minOffset || absOffsetX >= minOffset)) { + _offset.y = 0; if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { _localLocation = target.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, 0)); + _location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); } } } @@ -203,11 +204,12 @@ package org.gestouch.gestures } else if (absVelY >= minVelocity && (minOffset != minOffset || absOffsetY >= minOffset)) { + _offset.x = 0; if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { _localLocation = target.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, 0, _offset.y)); + _location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y)); } } } @@ -226,5 +228,16 @@ package org.gestouch.gestures setState(GestureState.FAILED); } } + + + override protected function onDelayedRecognize():void + { + if (hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) + { + _localLocation = target.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)); + } + } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index 9d7507c..fc5881b 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -151,6 +151,16 @@ package org.gestouch.gestures } } + + override protected function onDelayedRecognize():void + { + if (hasEventListener(TapGestureEvent.GESTURE_TAP)) + { + dispatchEvent(new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GestureState.RECOGNIZED, + _location.x, _location.y, _localLocation.x, _localLocation.y)); + } + } + From 764ca1522f48d8230889e25a991d6fbb30c2ebd2 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 7 Mar 2012 01:05:04 +0200 Subject: [PATCH 28/87] Readme updates --- README.textile | 99 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 25 deletions(-) diff --git a/README.textile b/README.textile index d62d885..9076e0a 100644 --- a/README.textile +++ b/README.textile @@ -1,60 +1,109 @@ -h2. Gestouch: NUI gestures detection framework for mouse, touch and multitouch AS3 development. +h1. Gestouch: NUI gestures detection framework for mouse, touch and multitouch AS3 development. -Gestouch is a very basic framework that helps you to detect gestures when you develop NUI (Natural User Interface). -Last versions of Flash Player and AIR have built-in touch and multitouch support, but the gestures support is quite poor: only small set of gestures are supported, they depend on OS, they are not customizable, only one can be processed at the same time and, finally, you are forced to use either raw TouchEvents, or gestures (@see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/ui/Multitouch.html#inputMode). +Gestouch is a ActionScript library/framework that helps you to deal with single- and multitouch gestures for building better NUI (Natural User Interface). -This framework is aimed to simplify the process of detecting raw TouchEvents and processing them into a specific gesture(s). There are several built-in common gestures, but you are welcome write your own. + +h3. Why? There's already gesture support in Flash/AIR! + +Yes, last versions of Flash Player and AIR runtimes have built-in touch and multitouch support, but the gestures support is very poor: only small set of gestures are supported, they depend on OS, they are not customizable in any way, only one can be processed at the same time and, finally, you are forced to use either raw TouchEvents, or gestures (@see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/ui/Multitouch.html#inputMode). +_Upd:_ With "native way" you also won't get anything out of Stage3D and of custom input like TUIO protocol. + + + +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. + +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. +So I want Gestouch to go far beyond that. Features: +* Pretty neat architecture! Very similar to Apple's UIGestureRecognizers (Cocoa-Touch UIKit) +* 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!+_* + + + +h3. Current state of the project + +v0.3 introduces "new architecture". I'm planning to develop everything in develop branch and merge to master only release versions. Release versions suppose to be pretty stable. As much as I test them on the examples project. +Current plan is to fix possible bugs in v0.3.#, and I really want to introduce Stage3D support in v0.4. So watch both branches. +And I hope people to become giving some real feedback at least. -* Doesn't require any additional software (uses runtimes build-in touch support); -* Doesn't break your DisplayList architecture (doesn't require any wrappers, so could be easily used for Flex development); -* Basically allows you to write multi-user interfaces; -* Extendable. You can write your own application-specific gestures; -* Open-source and easy to use. h3. Getting Started -* "Introduction video":http://www.youtube.com/watch?v=NjkmB8rfQjY -* "Disclaimer and Architecture Overview":http://github.com/fljot/Gestouch/wiki/Overview -* "Usage":http://github.com/fljot/Gestouch/wiki/Usage +Like so: +
var doubleTap:TapGesture = new TapGesture(myButton);
+doubleTap.numTapsRequired = 2;
+doubleTap.addEventListener(TapGestureEvent.GESTURE_TAP, onDoubleTap);
+...
+private function onDoubleTap(event:TapGestureEvent):void
+{
+	// handle double tap!
+}
+
+or +
var freeTransform:TransformGesture = new TransformGesture(myImage);
+freeTransform.addEventListener(TransformGestureEvent.GESTURE_TRANSFORM, onFreeTransform);
+...
+private function onFreeTransform(event:TransformGestureEvent):void
+{
+	// move, rotate, scale — all at once for better performance!
+}
+
+* Check the "Gestouch Examples":http://github.com/fljot/GestouchExamples project for a quick jump-in +* *+Highly recommended+* to watch videos from Apple WWDC conferences as they explain all the concepts and show more or less real-life examples. @see links below +* "Introduction video":http://www.youtube.com/watch?v=NjkmB8rfQjY - my first video, currently outdated +* TODO: wiki -h3. Code - -* "Gestouch Framework":http://github.com/fljot/Gestouch -* "Gestouch Examples":http://github.com/fljot/GestouchExamples h3. Roadmap, TODOs -* Simulator (for testing multitouch gestures without special devices) -* Chained gestures concept / behaviors. +* *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. * 3-fingers (3D) gestures (two fingers still, one moving) + +h3. News + +* "Follow me on Twitter":http://twitter.com/fljot for latest updates +* Don't forget about "issues":https://github.com/fljot/Gestouch/issues section as good platform for discussions. + + + h3. Contribution, Donations Contribute, share. Found it useful, nothing to add? Hire me for some project. -h3. News: -* "Follow me on Twitter":http://twitter.com/fljot for latest updates +h3. Links - -h3. Other Resources +* "Gestouch Examples":http://github.com/fljot/GestouchExamples * "Apple WWDC 2011: Making the Most of Multi-Touch on iOS":https://developer.apple.com/videos/wwdc/2011/?id=118 * "Apple WWDC 2010: Simplifying Touch Event Handling with Gesture Recognizers":https://developer.apple.com/videos/wwdc/2010/?id=120 * "Apple WWDC 2010: Advanced Gesture Recognition":https://developer.apple.com/videos/wwdc/2010/?id=121 +* "Event Handling Guide for iOS":https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/ +* "UIGestureRecognizer Class Reference":https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizer_Class/ -* "GestureWorks":http://www.gestureworks.com * "TUIO":http://www.tuio.org -* "Google: Flash + Multitouch":http://www.google.com/search?q=flash+multitouch - h2. License From 97486ba2fe984549fe6b9cc078387bbe0e42b8cc Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 7 Mar 2012 01:12:50 +0200 Subject: [PATCH 29/87] Bumped version to 0.3 --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 7791a91..b8fb300 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -project.version = 0.2 \ No newline at end of file +project.version = 0.3 \ No newline at end of file From e14bbd11bbe3bae3653914aedb4ef0b43f063962 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Thu, 8 Mar 2012 13:40:04 +0200 Subject: [PATCH 30/87] Input adapters fix to catch events on empty stage --- src/org/gestouch/core/TouchesManager.as | 6 ------ src/org/gestouch/input/MouseInputAdapter.as | 17 ++++++++++------- src/org/gestouch/input/TouchInputAdapter.as | 18 +++++++++++++++--- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/org/gestouch/core/TouchesManager.as b/src/org/gestouch/core/TouchesManager.as index 1e96a8f..aa42691 100644 --- a/src/org/gestouch/core/TouchesManager.as +++ b/src/org/gestouch/core/TouchesManager.as @@ -1,7 +1,5 @@ package org.gestouch.core { - import flash.ui.Multitouch; - import flash.ui.MultitouchInputMode; /** * @author Pavel fljot */ @@ -12,10 +10,6 @@ package org.gestouch.core protected var _touchesMap:Object = {}; - { - Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT; - } - public function TouchesManager() { diff --git a/src/org/gestouch/input/MouseInputAdapter.as b/src/org/gestouch/input/MouseInputAdapter.as index 38094fe..4a3e8ea 100644 --- a/src/org/gestouch/input/MouseInputAdapter.as +++ b/src/org/gestouch/input/MouseInputAdapter.as @@ -31,20 +31,20 @@ package org.gestouch.input } _stage = stage; - - stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); } 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(); } @@ -53,8 +53,8 @@ package org.gestouch.input { // 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); - // To catch event out of stage _stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, int.MAX_VALUE); } @@ -62,6 +62,7 @@ package org.gestouch.input 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); } @@ -69,6 +70,8 @@ package org.gestouch.input 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)) @@ -77,8 +80,8 @@ package org.gestouch.input installStageListeners(); var touch:Touch = _touchesManager.createTouch(); - touch.id = 0; 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()); @@ -91,6 +94,8 @@ package org.gestouch.input 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)) @@ -106,10 +111,8 @@ package org.gestouch.input protected function mouseUpHandler(event:MouseEvent):void { - // If event happens outside of stage it will be with AT_TARGET phase if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return; - + 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. diff --git a/src/org/gestouch/input/TouchInputAdapter.as b/src/org/gestouch/input/TouchInputAdapter.as index b86afab..99f82f4 100644 --- a/src/org/gestouch/input/TouchInputAdapter.as +++ b/src/org/gestouch/input/TouchInputAdapter.as @@ -8,6 +8,8 @@ package org.gestouch.input import flash.events.EventPhase; import flash.events.TouchEvent; import flash.geom.Point; + import flash.ui.Multitouch; + import flash.ui.MultitouchInputMode; import flash.utils.getTimer; @@ -25,6 +27,10 @@ package org.gestouch.input */ protected var _touchesMap:Object = {}; + { + Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT; + } + public function TouchInputAdapter(stage:Stage) { @@ -42,12 +48,14 @@ package org.gestouch.input 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(); } @@ -56,8 +64,8 @@ package org.gestouch.input { // 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); - // To catch event out of stage _stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, false, int.MAX_VALUE); } @@ -65,6 +73,7 @@ package org.gestouch.input 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); } @@ -72,6 +81,8 @@ package org.gestouch.input 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)) @@ -107,6 +118,8 @@ package org.gestouch.input 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)) @@ -133,9 +146,8 @@ package org.gestouch.input protected function touchEndHandler(event:TouchEvent):void { - // If event happens outside of stage it will be with AT_TARGET phase if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return; + 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. From 82742a84655a4bc4e67f953e088cd56623c67767 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 14 Mar 2012 15:32:51 +0200 Subject: [PATCH 31/87] Made Gesture weak-referencing target --- src/org/gestouch/core/GesturesManager.as | 43 ++++++++++++---------- src/org/gestouch/gestures/Gesture.as | 47 ++++++++++++++++++++---- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 9dc5a02..8cae7a2 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -26,7 +26,7 @@ package org.gestouch.core protected const _frameTickerShape:Shape = new Shape(); protected var _inputAdapters:Vector. = new Vector.(); protected var _stage:Stage; - protected var _gestures:Vector. = new Vector.(); + protected var _gesturesMap:Dictionary = new Dictionary(true); protected var _gesturesForTouchMap:Array = []; protected var _gesturesForTargetMap:Dictionary = new Dictionary(true); protected var _dirtyGestures:Vector. = new Vector.(); @@ -157,19 +157,20 @@ package org.gestouch.core { throw new ArgumentError("Argument 'gesture' must be not null."); } - if (_gestures.indexOf(gesture) > -1) + if (_gesturesMap[gesture]) { throw new Error("This gesture is already registered.. something wrong."); } - var targetGestures:Vector. = _gesturesForTargetMap[gesture.target] as Vector.; + var target:Object = gesture.target; + var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; if (!targetGestures) { targetGestures = _gesturesForTargetMap[gesture.target] = new Vector.(); } targetGestures.push(gesture); - _gestures.push(gesture); + _gesturesMap[gesture] = true; if (GesturesManager.initDefaultInputAdapter) { @@ -195,19 +196,17 @@ package org.gestouch.core var target:InteractiveObject = gesture.target; var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; - targetGestures.splice(targetGestures.indexOf(gesture), 1); - - if (targetGestures.length == 0) + if (targetGestures.length > 1) + { + targetGestures.splice(targetGestures.indexOf(gesture), 1); + } + else { delete _gesturesForTargetMap[target]; target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); } - var index:int = _gestures.indexOf(gesture); - if (index > -1) - { - _gestures.splice(index, 1); - } + delete _gesturesMap[gesture]; //TODO: decide about gesture state and _dirtyGestures } @@ -226,24 +225,30 @@ package org.gestouch.core gestouch_internal function onGestureRecognized(gesture:Gesture):void { - for each (var otherGesture:Gesture in _gestures) + for (var key:Object in _gesturesMap) { + var otherGesture:Gesture = key as Gesture; + var target:DisplayObject = gesture.target; + var otherTarget:DisplayObject = otherGesture.target; + // conditions for otherGesture "own properties" if (otherGesture != gesture && + target && otherTarget &&//in case GC worked half way through otherGesture.enabled && otherGesture.state == GestureState.POSSIBLE) { // conditions for otherGesture target - if (otherGesture.target == gesture.target || - (gesture.target is DisplayObjectContainer && (gesture.target as DisplayObjectContainer).contains(otherGesture.target)) || - (otherGesture.target is DisplayObjectContainer && (otherGesture.target as DisplayObjectContainer).contains(gesture.target)) - ) + if (otherTarget == target || + (target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(otherTarget)) || + (otherTarget is DisplayObjectContainer && (otherTarget as DisplayObjectContainer).contains(target))) { + var gestureDelegate:IGestureDelegate = gesture.delegate; + var otherGestureDelegate:IGestureDelegate = otherGesture.delegate; // conditions for gestures relations if (gesture.canPreventGesture(otherGesture) && otherGesture.canBePreventedByGesture(gesture) && - (!gesture.delegate || !gesture.delegate.gesturesShouldRecognizeSimultaneously(gesture, otherGesture)) && - (!otherGesture.delegate || !otherGesture.delegate.gesturesShouldRecognizeSimultaneously(otherGesture, gesture))) + (!gestureDelegate || !gestureDelegate.gesturesShouldRecognizeSimultaneously(gesture, otherGesture)) && + (!otherGestureDelegate || !otherGestureDelegate.gesturesShouldRecognizeSimultaneously(otherGesture, gesture))) { otherGesture.gestouch_internal::setState_internal(GestureState.FAILED); } diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index c3b9b7f..6d2ad42 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -37,8 +37,6 @@ package org.gestouch.gestures public static const DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI); - public var delegate:IGestureDelegate; - protected const _touchesManager:ITouchesManager = TouchesManager.getInstance(); protected const _gesturesManager:IGesturesManager = GesturesManager.getInstance(); /** @@ -66,7 +64,7 @@ package org.gestouch.gestures /** @private */ - protected var _target:InteractiveObject; + private var _targetWeekStorage:Dictionary; /** * InteractiveObject (DisplayObject) which this gesture is tracking the actual gesture motion on. @@ -81,16 +79,28 @@ package org.gestouch.gestures */ public function get target():InteractiveObject { - return _target; + for (var key:Object in _targetWeekStorage) + { + return key as InteractiveObject; + } + return null; } public function set target(value:InteractiveObject):void { - if (_target == value) + var target:InteractiveObject = this.target; + if (target == value) return; uninstallTarget(target); - _target = value; - installTarget(target); + for (var key:Object in _targetWeekStorage) + { + delete _targetWeekStorage[key]; + } + if (value) + { + (_targetWeekStorage ||= new Dictionary(true))[value] = true; + } + installTarget(value); } @@ -119,6 +129,28 @@ package org.gestouch.gestures } + private var _delegateWeekStorage:Dictionary; + public function get delegate():IGestureDelegate + { + for (var key:Object in _delegateWeekStorage) + { + return key as IGestureDelegate; + } + return null; + } + public function set delegate(value:IGestureDelegate):void + { + for (var key:Object in _delegateWeekStorage) + { + delete _delegateWeekStorage[key]; + } + if (value) + { + (_delegateWeekStorage ||= new Dictionary(true))[value] = true; + } + } + + protected var _state:uint = GestureState.IDLE; public function get state():uint { @@ -211,6 +243,7 @@ package org.gestouch.gestures //TODO reset(); target = null; + delegate = null; _gesturesToFail = null; } From bcb3dfb61ff2cb0c38aaa37fcfbe4a05a77ddb63 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 14 Mar 2012 16:58:39 +0200 Subject: [PATCH 32/87] Removed some redundant code --- src/org/gestouch/gestures/Gesture.as | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 6d2ad42..d6cafb2 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -4,9 +4,7 @@ package org.gestouch.gestures import org.gestouch.core.GesturesManager; import org.gestouch.core.IGestureDelegate; import org.gestouch.core.IGesturesManager; - import org.gestouch.core.ITouchesManager; import org.gestouch.core.Touch; - import org.gestouch.core.TouchesManager; import org.gestouch.core.gestouch_internal; import org.gestouch.events.GestureStateEvent; @@ -37,7 +35,6 @@ package org.gestouch.gestures public static const DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI); - protected const _touchesManager:ITouchesManager = TouchesManager.getInstance(); protected const _gesturesManager:IGesturesManager = GesturesManager.getInstance(); /** * Map (generic object) of tracking touch points, where keys are touch points IDs. From a449965e39256fcb1bd48328681006bd8bb01cd1 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 14 Mar 2012 17:34:24 +0200 Subject: [PATCH 33/87] Fix to output GestureStateEvent#toString() propely --- src/org/gestouch/events/GestureStateEvent.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/gestouch/events/GestureStateEvent.as b/src/org/gestouch/events/GestureStateEvent.as index 1dd6ced..3791f4a 100644 --- a/src/org/gestouch/events/GestureStateEvent.as +++ b/src/org/gestouch/events/GestureStateEvent.as @@ -31,7 +31,7 @@ package org.gestouch.events override public function toString():String { - return formatToString("GestureStateEvent", newState, oldState); + return formatToString("GestureStateEvent", "type", "oldState", "newState"); } } } \ No newline at end of file From 13231a4708f10c749b85eb1e5d36866161c7de2f Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 14 Mar 2012 17:49:49 +0200 Subject: [PATCH 34/87] Fix for LongPressGesture to work correctly with minPressDuration of zero (and the new IDLE state) --- src/org/gestouch/gestures/LongPressGesture.as | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 932aa17..a99ea6b 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -95,15 +95,8 @@ package org.gestouch.gestures { _numTouchesRequiredReached = true; _timer.reset(); - _timer.delay = minPressDuration; - if (minPressDuration > 0) - { - _timer.start(); - } - else - { - timer_timerCompleteHandler(); - } + _timer.delay = minPressDuration || 1; + _timer.start(); } } From ffb8ce1ec346a9b33946fed6e273cfd2ea68115e Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 14 Mar 2012 18:00:38 +0200 Subject: [PATCH 35/87] Proper state changing on calling Gesture#reset() --- src/org/gestouch/gestures/Gesture.as | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index d6cafb2..c2b6cd1 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -213,7 +213,11 @@ package org.gestouch.gestures */ public function reset():void { - //FIXME: proper state change? + var state:uint = this.state;//caching getter + + if (state == GestureState.IDLE) + return;// Do nothing as we're in IDLE and nothing to reset + _location.x = 0; _location.y = 0; _touchesMap = {}; @@ -226,7 +230,25 @@ package org.gestouch.gestures } _pendingRecognizedState = 0; - setState(GestureState.IDLE); + 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) + { + // manual reset() call. Set to CANCELLED to keep our State Machine clean and stable + setState(GestureState.CANCELLED); + } + else + { + // reset from GesturesManager after reaching one of the 4 final states: + // (state == GestureState.RECOGNIZED || + // state == GestureState.ENDED || + // state == GestureState.FAILED || + // state == GestureState.CANCELLED) + setState(GestureState.IDLE); + } } From e15c7e731827ae1172a75105f62b5eee8f8f938a Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 14 Mar 2012 18:36:05 +0200 Subject: [PATCH 36/87] Bumped version to 0.3.1 Releasing bunch of fixes --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index b8fb300..2354cd1 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -project.version = 0.3 \ No newline at end of file +project.version = 0.3.1 \ No newline at end of file From 13dbd61014447b28b47cd8cefa5270f4f435ff4f Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 13 Mar 2012 14:05:50 +0200 Subject: [PATCH 37/87] starling initial commit --- .settings/com.powerflasher.fdt.classpath | 2 + build.xml | 3 + libs/starling.swc | Bin 0 -> 94776 bytes src/org/gestouch/core/DisplayListAdapter.as | 27 +++ src/org/gestouch/core/DisplayObjectAdapter.as | 46 +++++ src/org/gestouch/core/GesturesManager.as | 90 ++++++--- src/org/gestouch/core/IDisplayListAdapter.as | 10 + .../gestouch/core/IGestureTargetAdapter.as | 15 ++ src/org/gestouch/core/IGesturesManager.as | 2 + src/org/gestouch/core/IInputAdapter.as | 1 - src/org/gestouch/core/ITouchesManager.as | 12 +- src/org/gestouch/core/Touch.as | 13 +- src/org/gestouch/core/TouchesManager.as | 154 +++++++++++---- .../starling/StarlingDisplayListAdapter.as | 27 +++ .../starling/StarlingDisplayObjectAdapter.as | 47 +++++ .../starling/StarlingInputAdapter.as | 185 ++++++++++++++++++ src/org/gestouch/gestures/Gesture.as | 63 +++--- src/org/gestouch/gestures/LongPressGesture.as | 4 +- src/org/gestouch/gestures/PanGesture.as | 4 +- src/org/gestouch/gestures/RotateGesture.as | 4 +- src/org/gestouch/gestures/SwipeGesture.as | 4 +- src/org/gestouch/gestures/TapGesture.as | 4 +- src/org/gestouch/gestures/TransformGesture.as | 4 +- src/org/gestouch/gestures/ZoomGesture.as | 4 +- .../gestouch/input/AbstractInputAdapter.as | 8 - src/org/gestouch/input/MouseInputAdapter.as | 51 +---- src/org/gestouch/input/TouchInputAdapter.as | 85 +------- src/org/gestouch/utils/GestureUtils.as | 2 + 28 files changed, 617 insertions(+), 254 deletions(-) create mode 100644 libs/starling.swc create mode 100644 src/org/gestouch/core/DisplayListAdapter.as create mode 100644 src/org/gestouch/core/DisplayObjectAdapter.as create mode 100644 src/org/gestouch/core/IDisplayListAdapter.as create mode 100644 src/org/gestouch/core/IGestureTargetAdapter.as create mode 100644 src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as create mode 100644 src/org/gestouch/extensions/starling/StarlingDisplayObjectAdapter.as create mode 100644 src/org/gestouch/extensions/starling/StarlingInputAdapter.as 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/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 0000000000000000000000000000000000000000..6fc810d696c6788883ce1ff047fa1f6e5a8f9bb5 GIT binary patch literal 94776 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

$w*hcE55x-efr}3kL(}s zDEJ%LLTE}yU$E=^f@XaQr!=tV z$bO)K%~K7Cj{I5r32Qh%-h#<_1Tgz;;#lQy{EFaM0FIlSOa5J(Bg*7h0f;v{#9tG{ z^tG*ah`(+VqfCzFgLsQW{4GJe1H?5B@gLa4D3fD5K)lr<{*EAC2jXq?IQns6-xIrb zMRvz`KZ5>xKt#^9ZjYRZ+!Vhl6geJ0E;8Y>Re>W?V?R_ZRq&29Vr7keN&zBN z_nPt={&HX^ZU23dT|?rRI)6nCd4?ZMmaSqJ>N4rxp`2VR-j^+5U8*xqHRZY#=2r?E zmnzIt+^$1&Zc4|JOv{D!eJiS&$b_M#c?}i}*D#}4E~M%bqA6+vNeg2$OV!K>%}QE0 zh+ua)qhn>Ie5w;x>1lFgb6Bx)TOsYJ-mlb|t=uO|W5)`ep9gX=!M8gDb=tn^tK6mr z=M+N#c3mN(HDP=Lf4dBcs_3HSN>ih|udlyvsZvx*6@B8!>7hfXj}CS3?O#n4>p2pg zIujF`@VtSkbASmPwRN7NT}{{9;m+lB&yn=nBf3R6&}BntF8W6P%CKwbo=f9pU!$`! z&f>Op13%J#;L!2@zJOhx4j(ymKwfL>^)@<#ZTFmK7+`u+qLryQyVo!|NRPJ+P8g=w z-D^(Ybk7M1@pV_{oY!-Klp>~7b~r{A*M0d87kSOo6Ee2&Qk#s@gHVY(YH%pM=f=jN z-FDQ_*IgA91L;9viqX+1izb+dz|XQ#7EQj{yP{_WDf+Q{E{(KzjS-T08e!WB7Tt5# z-J_Z3chBWe5D(~l97uTqnRoqdfw6>h2`T)WRTTMIBN8)R&=+L>NDVHR-QBZkhq!BW zhI2xu1yF_=Oxk2@H3OcsvzK&(T;`EXzNxmdUBWe`ja&4|@u{2i$>@1x<5MST>!fY) z;XcrP)9K^g`;YY-GvjB*=Ah)6o130&yxyl3cU2nDtYlte3a!;mvN-8zX42Xi8_(WR zbzS$%-d2q*wSln>6J&AHlZIAG_nS18xB^qD{(3JKqaR>QS2LrLMludw|JI(F>FDSP z%-G8DlnM}kU>}uBl}gbuE=$>&^v{6^?)9TWzssj)eG$-7u@Z)t7J%6$6)Kk#g8d%RiMJ`W-& z*_+HYsf9ebNf{8f?_eqd2xxxc?stL6e{9;1Lu|8vyr>2cn)xcsyPRs zPz~o>xf|#}wVXF_-pF|q=PjJKa^A-I4uTBw?B=|O^8wCp;GTn=ALgzbIUnNcO`M

y_fU* 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%0DrGFom95F$@_?>@yhh!cGWz=9t_ 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

nI11M&x3u5^>(od09Mf*?_ihF2ZS*kfFg#v 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 S|G5?9XFC1-YaM}qzW)bu`ma0y literal 0 HcmV?d00001 diff --git a/src/org/gestouch/core/DisplayListAdapter.as b/src/org/gestouch/core/DisplayListAdapter.as new file mode 100644 index 0000000..5151cf9 --- /dev/null +++ b/src/org/gestouch/core/DisplayListAdapter.as @@ -0,0 +1,27 @@ +package org.gestouch.core +{ + import flash.display.DisplayObject; + import org.gestouch.core.IDisplayListAdapter; + + + /** + * @author Pavel fljot + */ + public class DisplayListAdapter implements IDisplayListAdapter + { + 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; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/DisplayObjectAdapter.as b/src/org/gestouch/core/DisplayObjectAdapter.as new file mode 100644 index 0000000..cb0e3ae --- /dev/null +++ b/src/org/gestouch/core/DisplayObjectAdapter.as @@ -0,0 +1,46 @@ +package org.gestouch.core +{ + import flash.display.DisplayObject; + import flash.display.DisplayObjectContainer; + import flash.geom.Point; + import flash.utils.Dictionary; + + + + /** + * @author Pavel fljot + */ + final public class DisplayObjectAdapter implements IGestureTargetAdapter + { + private var _targetWeekStorage:Dictionary = new Dictionary(true); + + + public function DisplayObjectAdapter(target:DisplayObject) + { + _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(target:Object):Boolean + { + const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; + return (targetAsDOC && targetAsDOC.contains(target as DisplayObject)); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 8cae7a2..831092d 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -5,11 +5,10 @@ package org.gestouch.core import org.gestouch.input.TouchInputAdapter; 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.events.IEventDispatcher; import flash.ui.Multitouch; import flash.utils.Dictionary; @@ -25,9 +24,10 @@ package org.gestouch.core protected const _touchesManager:ITouchesManager = TouchesManager.getInstance(); protected const _frameTickerShape:Shape = new Shape(); protected var _inputAdapters:Vector. = new Vector.(); + protected var _displayListAdaptersMap:Dictionary = new Dictionary(); 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; @@ -40,6 +40,9 @@ package org.gestouch.core { throw new Error("Do not instantiate GesturesManager directly."); } + + _touchesManager.gesturesManager = this; + _displayListAdaptersMap[DisplayObject] = new DisplayListAdapter(); } @@ -90,7 +93,6 @@ package org.gestouch.core _inputAdapters.push(inputAdapter); inputAdapter.touchesManager = _touchesManager; - inputAdapter.gesturesManager = this; inputAdapter.init(); } @@ -115,6 +117,16 @@ package org.gestouch.core } + public function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void + { + if (!targetClass || !adapter) + { + throw new Error("Argument error: both arguments required."); + } + _displayListAdaptersMap[targetClass] = adapter; + } + + //-------------------------------------------------------------------------- @@ -166,7 +178,7 @@ package org.gestouch.core var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; if (!targetGestures) { - targetGestures = _gesturesForTargetMap[gesture.target] = new Vector.(); + targetGestures = _gesturesForTargetMap[target] = new Vector.(); } targetGestures.push(gesture); @@ -174,13 +186,17 @@ package org.gestouch.core if (GesturesManager.initDefaultInputAdapter) { - 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 (!_stage && targetAsDO.stage) + { + installStage(targetAsDO.stage); + } + else + { + targetAsDO.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + } } } } @@ -194,7 +210,7 @@ package org.gestouch.core } - var target:InteractiveObject = gesture.target; + var target:Object = gesture.target; var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; if (targetGestures.length > 1) { @@ -203,7 +219,10 @@ package org.gestouch.core else { delete _gesturesForTargetMap[target]; - target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + if (target is IEventDispatcher) + { + (target as IEventDispatcher).removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + } } delete _gesturesMap[gesture]; @@ -228,8 +247,8 @@ package org.gestouch.core for (var key:Object in _gesturesMap) { var otherGesture:Gesture = key as Gesture; - var target:DisplayObject = gesture.target; - var otherTarget:DisplayObject = otherGesture.target; + var target:Object = gesture.target; + var otherTarget:Object = otherGesture.target; // conditions for otherGesture "own properties" if (otherGesture != gesture && @@ -237,10 +256,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; @@ -268,24 +287,37 @@ package org.gestouch.core 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 = _gesturesForTouchMap[touch] = new Vector.(); } else { gesturesForTouch.length = 0; - } + } + var target:Object = touch.target; + var hierarchy:Vector.; + for (var key:* in _displayListAdaptersMap) + { + var targetClass:Class = key as Class; + if (target is targetClass) + { + hierarchy = (_displayListAdaptersMap[key] as IDisplayListAdapter).getHierarchy(target); + break; + } + } + if (!hierarchy) + { + throw new Error("Display list adapter not found for target of type '" + targetClass + "'."); + } // 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) @@ -302,8 +334,6 @@ package org.gestouch.core } } } - - target = target.parent; } // Then we populate them with this touch and event. @@ -332,7 +362,7 @@ package org.gestouch.core resetDirtyGestures(); } - var gesturesForTouch:Vector. = _gesturesForTouchMap[touch.id] as Vector.; + var gesturesForTouch:Vector. = _gesturesForTouchMap[touch] as Vector.; var gesture:Gesture; var i:int = gesturesForTouch.length; while (i-- > 0) @@ -359,7 +389,7 @@ package org.gestouch.core resetDirtyGestures(); } - var gesturesForTouch:Vector. = _gesturesForTouchMap[touch.id] as Vector.; + var gesturesForTouch:Vector. = _gesturesForTouchMap[touch] as Vector.; var gesture:Gesture; var i:int = gesturesForTouch.length; while (i-- > 0) @@ -371,6 +401,8 @@ package org.gestouch.core gesture.gestouch_internal::touchEndHandler(touch); } } + + gesturesForTouch.length = 0;// release for GC } diff --git a/src/org/gestouch/core/IDisplayListAdapter.as b/src/org/gestouch/core/IDisplayListAdapter.as new file mode 100644 index 0000000..3a77af8 --- /dev/null +++ b/src/org/gestouch/core/IDisplayListAdapter.as @@ -0,0 +1,10 @@ +package org.gestouch.core +{ + /** + * @author Pavel fljot + */ + public interface IDisplayListAdapter + { + function getHierarchy(target:Object):Vector.; + } +} \ 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..c66eb0f --- /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(target:Object):Boolean; + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/IGesturesManager.as b/src/org/gestouch/core/IGesturesManager.as index 297f39a..c73412a 100644 --- a/src/org/gestouch/core/IGesturesManager.as +++ b/src/org/gestouch/core/IGesturesManager.as @@ -13,5 +13,7 @@ package org.gestouch.core { function addInputAdapter(inputAdapter:IInputAdapter):void; function removeInputAdapter(inputAdapter:IInputAdapter, dispose:Boolean = true):void; + + function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):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..83d9f3d 100644 --- a/src/org/gestouch/core/IInputAdapter.as +++ b/src/org/gestouch/core/IInputAdapter.as @@ -6,7 +6,6 @@ package org.gestouch.core public interface IInputAdapter { function set touchesManager(value:ITouchesManager):void; - function set gesturesManager(value:IGesturesManager):void; function init():void; function dispose():void; diff --git a/src/org/gestouch/core/ITouchesManager.as b/src/org/gestouch/core/ITouchesManager.as index 8ae9639..2243c21 100644 --- a/src/org/gestouch/core/ITouchesManager.as +++ b/src/org/gestouch/core/ITouchesManager.as @@ -5,12 +5,14 @@ package org.gestouch.core */ public interface ITouchesManager { + function set gesturesManager(value:IGesturesManager):void; + 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; + function onTouchBegin(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number, target:Object):void; + function onTouchMove(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void; + function onTouchEnd(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void; + + function onInputAdapterDispose(inputAdapter:IInputAdapter):void; } } \ No newline at end of file diff --git a/src/org/gestouch/core/Touch.as b/src/org/gestouch/core/Touch.as index 9a707fa..1f2a372 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,7 +18,7 @@ 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; @@ -39,13 +38,16 @@ package org.gestouch.core { return _location.clone(); } - gestouch_internal function setLocation(value:Point):void + gestouch_internal function setLocation(value:Point, time:uint):void { _location = value; _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 +55,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)); + gestouch_internal::setLocation(new Point(x, y), time); } } diff --git a/src/org/gestouch/core/TouchesManager.as b/src/org/gestouch/core/TouchesManager.as index aa42691..b4d2526 100644 --- a/src/org/gestouch/core/TouchesManager.as +++ b/src/org/gestouch/core/TouchesManager.as @@ -1,5 +1,8 @@ package org.gestouch.core { + import flash.geom.Point; + import flash.utils.Dictionary; + import flash.utils.getTimer; /** * @author Pavel fljot */ @@ -20,6 +23,13 @@ package org.gestouch.core } + protected var _gesturesManager:IGesturesManager; + public function set gesturesManager(value:IGesturesManager):void + { + _gesturesManager = value; + } + + protected var _activeTouchesCount:uint; public function get activeTouchesCount():uint { @@ -54,50 +64,112 @@ package org.gestouch.core } - public function createTouch():Touch + public function onTouchBegin(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number, target:Object):void + { + var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary; + if (overlappingTouches) + { + // In case we listen to both TouchEvents and MouseEvents, one of them will come first + // (right now looks like MouseEvent dispatches first, but who know what Adobe will + // do tomorrow). This check is to filter out the one comes second. + for each (var registeredTouch:Touch in overlappingTouches) + { + if (registeredTouch.target == target) + return; + } + } + else + { + overlappingTouches = _touchesMap[touchID] = new Dictionary(); + _activeTouchesCount++; + } + + var touch:Touch = createTouch(); + touch.id = touchID; + touch.target = target; + touch.gestouch_internal::setLocation(new Point(x, y), getTimer()); + overlappingTouches[inputAdapter] = touch; + + _gesturesManager.gestouch_internal::onTouchBegin(touch); + } + + + public function onTouchMove(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void + { + var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary; + if (!overlappingTouches) + return;//this touch isn't properly registered.. some fake + + var touch:Touch = overlappingTouches[inputAdapter] as Touch; + if (!touch) + return;//touch with this ID from this inputAdapter is not registered. see workaround reason above + + touch.gestouch_internal::updateLocation(x, y, getTimer()); + + _gesturesManager.gestouch_internal::onTouchMove(touch); + } + + + public function onTouchEnd(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void + { + var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary; + if (!overlappingTouches) + return;//this touch isn't properly registered.. some fake + + var touch:Touch = overlappingTouches[inputAdapter] as Touch; + if (!touch) + return;//touch with this ID from this inputAdapter is not registered. see workaround reason above + + touch.gestouch_internal::updateLocation(x, y, getTimer()); + + delete overlappingTouches[inputAdapter]; + var empty:Boolean = true; + for (var key:Object in overlappingTouches) + { + empty = false; + break; + } + if (empty) + { + delete _touchesMap[touchID]; + _activeTouchesCount--; + } + + _gesturesManager.gestouch_internal::onTouchEnd(touch); + } + + + /** + * Must be called by IInputAdapter#dispose() to remove all the touches invoked by it. + */ + public function onInputAdapterDispose(inputAdapter:IInputAdapter):void + { + for (var touchID:Object in _touchesMap) + { + var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary; + if (overlappingTouches[inputAdapter]) + { + delete overlappingTouches[inputAdapter]; + var empty:Boolean = true; + for (var key:Object in overlappingTouches) + { + empty = false; + break; + } + if (empty) + { + delete _touchesMap[touchID]; + _activeTouchesCount--; + } + } + } + } + + + protected function createTouch():Touch { //TODO: pool return new Touch(); } - - - public function addTouch(touch:Touch):Touch - { - if (_touchesMap.hasOwnProperty(touch.id)) - { - throw new Error("Touch with id " + touch.id + " is already registered."); - } - - _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; - } } } \ No newline at end of file diff --git a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as new file mode 100644 index 0000000..bd860b8 --- /dev/null +++ b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as @@ -0,0 +1,27 @@ +package org.gestouch.extensions.starling +{ + import starling.display.DisplayObject; + import org.gestouch.core.IDisplayListAdapter; + + + /** + * @author Pavel fljot + */ + public class StarlingDisplayListAdapter implements IDisplayListAdapter + { + 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; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/extensions/starling/StarlingDisplayObjectAdapter.as b/src/org/gestouch/extensions/starling/StarlingDisplayObjectAdapter.as new file mode 100644 index 0000000..e53f829 --- /dev/null +++ b/src/org/gestouch/extensions/starling/StarlingDisplayObjectAdapter.as @@ -0,0 +1,47 @@ +package org.gestouch.extensions.starling +{ + import org.gestouch.core.IGestureTargetAdapter; + import starling.display.DisplayObject; + import starling.display.DisplayObjectContainer; + import flash.geom.Point; + import flash.utils.Dictionary; + + + + /** + * @author Pavel fljot + */ + final public class StarlingDisplayObjectAdapter implements IGestureTargetAdapter + { + private var _targetWeekStorage:Dictionary = new Dictionary(true); + + + public function StarlingDisplayObjectAdapter(target:DisplayObject) + { + _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(target:Object):Boolean + { + const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; + return (targetAsDOC && targetAsDOC.contains(target as DisplayObject)); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/extensions/starling/StarlingInputAdapter.as b/src/org/gestouch/extensions/starling/StarlingInputAdapter.as new file mode 100644 index 0000000..213ef05 --- /dev/null +++ b/src/org/gestouch/extensions/starling/StarlingInputAdapter.as @@ -0,0 +1,185 @@ +package org.gestouch.extensions.starling +{ + import flash.events.EventPhase; + import flash.events.MouseEvent; + import flash.events.TouchEvent; + import flash.geom.Point; + import flash.ui.Mouse; + import org.gestouch.input.AbstractInputAdapter; + import starling.core.Starling; + + + + /** + * @author Pavel fljot + */ + public class StarlingInputAdapter extends AbstractInputAdapter + { + private static const PRIMARY_TOUCH_POINT_ID:uint = 0; + + protected var _starling:Starling; + + + public function StarlingInputAdapter(starling:Starling) + { + super(); + + if (!starling) + { + throw new Error("Argument error."); + } + + _starling = starling; + } + + + protected function get supportsTouchEvents():Boolean + { + // just like in Starling (starling.core::Starling) + return !(Mouse.supportsCursor || !Starling.multitouchEnabled); + } + + + override public function init():void + { + // We want to begin tracking only those touches that happen on Stage3D layer, + // e.g. event.target == nativeStage. That's we don't listen for touch begin + // in capture phase (as we do for native display list). + if (supportsTouchEvents) + { + _starling.nativeStage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler); + } + else + { + _starling.nativeStage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); + } + } + + + override public function dispose():void + { + _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler); + _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); + uninstallStageListeners(); + _starling = null; + _touchesManager.onInputAdapterDispose(this); + _touchesManager = null; + } + + + protected function installStageListeners():void + { + // Maximum priority to prevent event hijacking + if (supportsTouchEvents) + { + _starling.nativeStage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true, int.MAX_VALUE); + _starling.nativeStage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false, int.MAX_VALUE); + _starling.nativeStage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true, int.MAX_VALUE); + _starling.nativeStage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, false, int.MAX_VALUE); + } + else + { + _starling.nativeStage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true, int.MAX_VALUE); + _starling.nativeStage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false, int.MAX_VALUE); + _starling.nativeStage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, int.MAX_VALUE); + _starling.nativeStage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, int.MAX_VALUE); + } + } + + + protected function uninstallStageListeners():void + { + _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); + _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false); + _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); + _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, false); + _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); + _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false); + _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); + _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false); + } + + + protected function mouseDownHandler(event:MouseEvent):void + { + // We ignore event with bubbling phase because it happened on some native InteractiveObject, + // which basically hovers Stage3D layer. So we treat it as if Starling wouldn't recieve any input. + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + var target:Object = _starling.stage.hitTest(new Point(event.stageX, event.stageY), true); + _touchesManager.onTouchBegin(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY, target); + + if (_touchesManager.activeTouchesCount > 0) + { + installStageListeners(); + } + } + + + 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) + + _touchesManager.onTouchMove(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY); + } + + + 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) + + _touchesManager.onTouchEnd(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY); + + if (_touchesManager.activeTouchesCount == 0) + { + uninstallStageListeners(); + } + } + + + protected function touchBeginHandler(event:TouchEvent):void + { + // We ignore event with bubbling phase because it happened on some native InteractiveObject, + // which basically hovers Stage3D layer. So we treat it as if Starling wouldn't recieve any input. + if (event.eventPhase == EventPhase.BUBBLING_PHASE) + return; + + var target:Object = _starling.stage.hitTest(new Point(event.stageX, event.stageY), true); + _touchesManager.onTouchBegin(this, event.touchPointID, event.stageX, event.stageY, target); + + if (_touchesManager.activeTouchesCount > 0) + { + installStageListeners(); + } + } + + + 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) + + _touchesManager.onTouchMove(this, event.touchPointID, event.stageX, event.stageY); + } + + + 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) + + _touchesManager.onTouchEnd(this, event.touchPointID, event.stageX, event.stageY); + + 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/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index c2b6cd1..72766ea 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -3,12 +3,12 @@ package org.gestouch.gestures import org.gestouch.core.GestureState; import org.gestouch.core.GesturesManager; import org.gestouch.core.IGestureDelegate; + import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.IGesturesManager; import org.gestouch.core.Touch; import org.gestouch.core.gestouch_internal; import org.gestouch.events.GestureStateEvent; - import flash.display.InteractiveObject; import flash.events.EventDispatcher; import flash.geom.Point; import flash.system.Capabilities; @@ -50,20 +50,29 @@ package org.gestouch.gestures protected var _pendingRecognizedState:uint; - public function Gesture(target:InteractiveObject = null) + public function Gesture(targetAdapter:IGestureTargetAdapter = null) { super(); preinit(); - this.target = target; + setTarget(targetAdapter); } /** @private */ - private var _targetWeekStorage:Dictionary; + protected var _targetAdapter:IGestureTargetAdapter; + /** + * + */ + public 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,30 +83,9 @@ 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; - } - public function set target(value:InteractiveObject):void - { - var target:InteractiveObject = 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; - } - installTarget(value); + return _targetAdapter ? _targetAdapter.target : null; } @@ -185,6 +173,17 @@ package org.gestouch.gestures // Public methods // //-------------------------------------------------------------------------- + + public function setTarget(targetAdapter:IGestureTargetAdapter):void + { + if (_targetAdapter == targetAdapter) + return; + + uninstallTarget(this.targetAdapter); + _targetAdapter = targetAdapter; + installTarget(this.targetAdapter); + } + [Abstract] /** @@ -261,7 +260,7 @@ package org.gestouch.gestures { //TODO reset(); - target = null; + setTarget(null); delegate = null; _gesturesToFail = null; } @@ -310,9 +309,9 @@ 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(targetAdapter:IGestureTargetAdapter):void { - if (target) + if (targetAdapter) { _gesturesManager.gestouch_internal::addGesture(this); } @@ -326,9 +325,9 @@ 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(targetAdapter:IGestureTargetAdapter):void { - if (target) + if (targetAdapter) { _gesturesManager.gestouch_internal::removeGesture(this); } diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index a99ea6b..716d277 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -1,10 +1,10 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; + import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.LongPressGestureEvent; - import flash.display.InteractiveObject; import flash.events.TimerEvent; import flash.utils.Timer; @@ -30,7 +30,7 @@ package org.gestouch.gestures protected var _numTouchesRequiredReached:Boolean; - public function LongPressGesture(target:InteractiveObject = null) + public function LongPressGesture(target:IGestureTargetAdapter = null) { super(target); } diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index ddf74bf..f592a93 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -1,10 +1,10 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; + import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.PanGestureEvent; - import flash.display.InteractiveObject; import flash.geom.Point; [Event(name="gesturePan", type="org.gestouch.events.PanGestureEvent")] @@ -27,7 +27,7 @@ package org.gestouch.gestures protected var _gestureBeginOffsetY:Number; - public function PanGesture(target:InteractiveObject = null) + public function PanGesture(target:IGestureTargetAdapter = null) { super(target); } diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index da1665b..4045053 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -1,11 +1,11 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; + import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.RotateGestureEvent; import org.gestouch.utils.GestureUtils; - import flash.display.InteractiveObject; import flash.geom.Point; [Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")] @@ -24,7 +24,7 @@ package org.gestouch.gestures protected var _transformVector:Point; - public function RotateGesture(target:InteractiveObject = null) + public function RotateGesture(target:IGestureTargetAdapter = null) { super(target); } diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 9665493..249ee13 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -1,10 +1,10 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; + import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.SwipeGestureEvent; - import flash.display.InteractiveObject; import flash.geom.Point; [Event(name="gestureSwipe", type="org.gestouch.events.SwipeGestureEvent")] @@ -31,7 +31,7 @@ package org.gestouch.gestures protected var _decelerationCounter:uint = 0; - public function SwipeGesture(target:InteractiveObject = null) + public function SwipeGesture(target:IGestureTargetAdapter = null) { super(target); } diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index fc5881b..ba33ca8 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -1,10 +1,10 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; + import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.TapGestureEvent; - import flash.display.InteractiveObject; import flash.events.TimerEvent; import flash.utils.Timer; @@ -28,7 +28,7 @@ package org.gestouch.gestures protected var _tapCounter:uint = 0; - public function TapGesture(target:InteractiveObject = null) + public function TapGesture(target:IGestureTargetAdapter = null) { super(target); } diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index b3a2daa..3c9c20c 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -1,11 +1,11 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; + import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.TransformGestureEvent; import org.gestouch.utils.GestureUtils; - import flash.display.InteractiveObject; import flash.geom.Point; @@ -22,7 +22,7 @@ package org.gestouch.gestures protected var _transformVector:Point; - public function TransformGesture(target:InteractiveObject = null) + public function TransformGesture(target:IGestureTargetAdapter = null) { super(target); } diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 09608b3..ebc367e 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -1,10 +1,10 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; + import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.ZoomGestureEvent; - import flash.display.InteractiveObject; import flash.geom.Point; [Event(name="gestureZoom", type="org.gestouch.events.ZoomGestureEvent")] @@ -24,7 +24,7 @@ package org.gestouch.gestures protected var _transformVector:Point; - public function ZoomGesture(target:InteractiveObject = null) + public function ZoomGesture(target:IGestureTargetAdapter = null) { super(target); } diff --git a/src/org/gestouch/input/AbstractInputAdapter.as b/src/org/gestouch/input/AbstractInputAdapter.as index 8543af5..a870a62 100644 --- a/src/org/gestouch/input/AbstractInputAdapter.as +++ b/src/org/gestouch/input/AbstractInputAdapter.as @@ -1,6 +1,5 @@ package org.gestouch.input { - import org.gestouch.core.IGesturesManager; import org.gestouch.core.IInputAdapter; import org.gestouch.core.ITouchesManager; @@ -11,7 +10,6 @@ package org.gestouch.input public class AbstractInputAdapter implements IInputAdapter { protected var _touchesManager:ITouchesManager; - protected var _gesturesManager:IGesturesManager; public function AbstractInputAdapter() @@ -29,12 +27,6 @@ package org.gestouch.input } - public function set gesturesManager(value:IGesturesManager):void - { - _gesturesManager = value; - } - - [Abstract] public function init():void { diff --git a/src/org/gestouch/input/MouseInputAdapter.as b/src/org/gestouch/input/MouseInputAdapter.as index 4a3e8ea..9a7e4b0 100644 --- a/src/org/gestouch/input/MouseInputAdapter.as +++ b/src/org/gestouch/input/MouseInputAdapter.as @@ -1,14 +1,8 @@ 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; /** @@ -46,6 +40,8 @@ package org.gestouch.input _stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true); _stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); uninstallStageListeners(); + _touchesManager.onInputAdapterDispose(this); + _touchesManager = null; } @@ -72,23 +68,13 @@ package org.gestouch.input { 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(); + _touchesManager.onTouchBegin(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY, event.target); - 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); + if (_touchesManager.activeTouchesCount > 0) + { + installStageListeners(); + } } @@ -96,16 +82,8 @@ package org.gestouch.input { 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); + _touchesManager.onTouchMove(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY); } @@ -114,18 +92,7 @@ package org.gestouch.input 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); + _touchesManager.onTouchEnd(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY); if (_touchesManager.activeTouchesCount == 0) { diff --git a/src/org/gestouch/input/TouchInputAdapter.as b/src/org/gestouch/input/TouchInputAdapter.as index 99f82f4..920cd2d 100644 --- a/src/org/gestouch/input/TouchInputAdapter.as +++ b/src/org/gestouch/input/TouchInputAdapter.as @@ -1,16 +1,10 @@ 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; /** @@ -57,6 +51,8 @@ package org.gestouch.input _stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true); _stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler); uninstallStageListeners(); + _touchesManager.onInputAdapterDispose(this); + _touchesManager = null; } @@ -83,36 +79,13 @@ package org.gestouch.input { 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; + + _touchesManager.onTouchBegin(this, event.touchPointID, event.stageX, event.stageY, event.target); - 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")) + if (_touchesManager.activeTouchesCount > 0) { - touch.gestouch_internal::setTime(event["timestamp"]); - touch.gestouch_internal::setBeginTime(event["timestamp"]); + installStageListeners(); } - else - { - touch.gestouch_internal::setTime(getTimer()); - touch.gestouch_internal::setBeginTime(getTimer()); - } - - _touchesManager.addTouch(touch); - _touchesMap[touch.id] = true; - - _gesturesManager.gestouch_internal::onTouchBegin(touch); } @@ -120,27 +93,8 @@ package org.gestouch.input { 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); + _touchesManager.onTouchMove(this, event.touchPointID, event.stageX, event.stageY); } @@ -149,30 +103,7 @@ package org.gestouch.input 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]; + _touchesManager.onTouchEnd(this, event.touchPointID, event.stageX, event.stageY); if (_touchesManager.activeTouchesCount == 0) { 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 From c983ebe32aabd6116764ac87c9739cf45ef1f696 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 16 Mar 2012 01:03:40 +0200 Subject: [PATCH 38/87] Readme quick update for Starling --- README.textile | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/README.textile b/README.textile index 9076e0a..03f8859 100644 --- a/README.textile +++ b/README.textile @@ -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!+_* @@ -43,7 +42,7 @@ And I hope people to become giving some real feedback at least. h3. Getting Started Like so: -
var doubleTap:TapGesture = new TapGesture(myButton);
+
var doubleTap:TapGesture = new TapGesture(new DisplayObjectAdapter(myButton));
 doubleTap.numTapsRequired = 2;
 doubleTap.addEventListener(TapGestureEvent.GESTURE_TAP, onDoubleTap);
 ...
@@ -69,9 +68,30 @@ 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 one 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 detect gestures with Starling do following:
+
starling = new Starling(MyStarlingRootClass, stage);
+/* setup & start your Starling instance here */
+
+var gesturesManager:IGesturesManager = GesturesManager.getInstance();
+// Register instance of StarlingDisplayListAdapter to be used for objects of type starling.display.DisplayObject.
+// What StarlingDisplayListAdapter does: helps to build hierarchy (chain of parents) for any Starling display object.
+gesturesManager.addDisplayListAdapter(starling.display.DisplayObject, new StarlingDisplayListAdapter());
+
+// Initialize and register StarlingInputAdapter.
+// What StarlingInputAdapter does: populates library with touches for Starling "layer" 
+gesturesManager.addInputAdapter(new StarlingInputAdapter(starling));
+
+ +Now you can register gesture in familiar way: +
var tap:TapGesture = new TapGesture(new StarlingDisplayObjectAdapter(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. From 63a5df87618fdc6dc9f97a14b3ff0b2964dc6ba1 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 16 Mar 2012 01:04:50 +0200 Subject: [PATCH 39/87] Bumped version to 0.4-alpha --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 2354cd1..5cfc482 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -project.version = 0.3.1 \ No newline at end of file +project.version = 0.4-alpha \ No newline at end of file From d1150b2a35bf6cd4a40cf527e9984602d9efd886 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Sun, 18 Mar 2012 12:17:01 +0200 Subject: [PATCH 40/87] Simplified API for Gesture target --- src/org/gestouch/core/DisplayListAdapter.as | 48 +++++++++++++++++- src/org/gestouch/core/DisplayObjectAdapter.as | 46 ----------------- src/org/gestouch/core/GesturesManager.as | 23 +++++++-- src/org/gestouch/core/IDisplayListAdapter.as | 4 +- .../starling/StarlingDisplayListAdapter.as | 49 ++++++++++++++++++- .../starling/StarlingDisplayObjectAdapter.as | 47 ------------------ src/org/gestouch/gestures/Gesture.as | 43 ++++++++-------- src/org/gestouch/gestures/LongPressGesture.as | 3 +- src/org/gestouch/gestures/PanGesture.as | 3 +- src/org/gestouch/gestures/RotateGesture.as | 3 +- src/org/gestouch/gestures/SwipeGesture.as | 11 ++--- src/org/gestouch/gestures/TapGesture.as | 3 +- src/org/gestouch/gestures/TransformGesture.as | 7 ++- src/org/gestouch/gestures/ZoomGesture.as | 3 +- 14 files changed, 153 insertions(+), 140 deletions(-) delete mode 100644 src/org/gestouch/core/DisplayObjectAdapter.as delete mode 100644 src/org/gestouch/extensions/starling/StarlingDisplayObjectAdapter.as diff --git a/src/org/gestouch/core/DisplayListAdapter.as b/src/org/gestouch/core/DisplayListAdapter.as index 5151cf9..6f224cd 100644 --- a/src/org/gestouch/core/DisplayListAdapter.as +++ b/src/org/gestouch/core/DisplayListAdapter.as @@ -1,14 +1,52 @@ package org.gestouch.core { import flash.display.DisplayObject; - import org.gestouch.core.IDisplayListAdapter; + import flash.display.DisplayObjectContainer; + import flash.geom.Point; + import flash.utils.Dictionary; /** * @author Pavel fljot */ - public class DisplayListAdapter implements IDisplayListAdapter + final public class DisplayListAdapter implements IDisplayListAdapter { + private var _targetWeekStorage:Dictionary; + + + public function DisplayListAdapter(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(target:Object):Boolean + { + const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; + return (targetAsDOC && targetAsDOC.contains(target as DisplayObject)); + } + + public function getHierarchy(genericTarget:Object):Vector. { var list:Vector. = new Vector.(); @@ -23,5 +61,11 @@ package org.gestouch.core return list; } + + + public function reflect():Class + { + return DisplayListAdapter; + } } } \ No newline at end of file diff --git a/src/org/gestouch/core/DisplayObjectAdapter.as b/src/org/gestouch/core/DisplayObjectAdapter.as deleted file mode 100644 index cb0e3ae..0000000 --- a/src/org/gestouch/core/DisplayObjectAdapter.as +++ /dev/null @@ -1,46 +0,0 @@ -package org.gestouch.core -{ - import flash.display.DisplayObject; - import flash.display.DisplayObjectContainer; - import flash.geom.Point; - import flash.utils.Dictionary; - - - - /** - * @author Pavel fljot - */ - final public class DisplayObjectAdapter implements IGestureTargetAdapter - { - private var _targetWeekStorage:Dictionary = new Dictionary(true); - - - public function DisplayObjectAdapter(target:DisplayObject) - { - _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(target:Object):Boolean - { - const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; - return (targetAsDOC && targetAsDOC.contains(target as DisplayObject)); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 831092d..3f8c1a4 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -1,5 +1,6 @@ package org.gestouch.core { + import flash.utils.getQualifiedClassName; import org.gestouch.gestures.Gesture; import org.gestouch.input.MouseInputAdapter; import org.gestouch.input.TouchInputAdapter; @@ -163,6 +164,22 @@ package org.gestouch.core } + gestouch_internal function createGestureTargetAdapter(target:Object):IDisplayListAdapter + { + for (var key:Object in _displayListAdaptersMap) + { + var targetClass:Class = key as Class; + if (target is targetClass) + { + var adapter:IDisplayListAdapter = _displayListAdaptersMap[key] as IDisplayListAdapter; + return new (adapter.reflect())(target); + } + } + + throw new Error("Cannot create adapter for target " + target + " of type " + getQualifiedClassName(target) + "."); + } + + gestouch_internal function addGesture(gesture:Gesture):void { if (!gesture) @@ -257,8 +274,8 @@ package org.gestouch.core otherGesture.state == GestureState.POSSIBLE) { if (otherTarget == target || - gesture.targetAdapter.contains(otherTarget) || - otherGesture.targetAdapter.contains(target) + gesture.gestouch_internal::targetAdapter.contains(otherTarget) || + otherGesture.gestouch_internal::targetAdapter.contains(target) ) { var gestureDelegate:IGestureDelegate = gesture.delegate; @@ -300,7 +317,7 @@ package org.gestouch.core var target:Object = touch.target; var hierarchy:Vector.; - for (var key:* in _displayListAdaptersMap) + for (var key:Object in _displayListAdaptersMap) { var targetClass:Class = key as Class; if (target is targetClass) diff --git a/src/org/gestouch/core/IDisplayListAdapter.as b/src/org/gestouch/core/IDisplayListAdapter.as index 3a77af8..d5f69e2 100644 --- a/src/org/gestouch/core/IDisplayListAdapter.as +++ b/src/org/gestouch/core/IDisplayListAdapter.as @@ -3,8 +3,10 @@ package org.gestouch.core /** * @author Pavel fljot */ - public interface IDisplayListAdapter + 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/extensions/starling/StarlingDisplayListAdapter.as b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as index bd860b8..3b0a9fe 100644 --- a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as +++ b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as @@ -1,14 +1,55 @@ package org.gestouch.extensions.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 */ - public class StarlingDisplayListAdapter implements IDisplayListAdapter + 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 + { + return (target as DisplayObject).globalToLocal(point); + } + + + public function contains(target:Object):Boolean + { + const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; + return (targetAsDOC && targetAsDOC.contains(target as DisplayObject)); + } + + public function getHierarchy(genericTarget:Object):Vector. { var list:Vector. = new Vector.(); @@ -23,5 +64,11 @@ package org.gestouch.extensions.starling return list; } + + + public function reflect():Class + { + return StarlingDisplayListAdapter; + } } } \ No newline at end of file diff --git a/src/org/gestouch/extensions/starling/StarlingDisplayObjectAdapter.as b/src/org/gestouch/extensions/starling/StarlingDisplayObjectAdapter.as deleted file mode 100644 index e53f829..0000000 --- a/src/org/gestouch/extensions/starling/StarlingDisplayObjectAdapter.as +++ /dev/null @@ -1,47 +0,0 @@ -package org.gestouch.extensions.starling -{ - import org.gestouch.core.IGestureTargetAdapter; - import starling.display.DisplayObject; - import starling.display.DisplayObjectContainer; - import flash.geom.Point; - import flash.utils.Dictionary; - - - - /** - * @author Pavel fljot - */ - final public class StarlingDisplayObjectAdapter implements IGestureTargetAdapter - { - private var _targetWeekStorage:Dictionary = new Dictionary(true); - - - public function StarlingDisplayObjectAdapter(target:DisplayObject) - { - _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(target:Object):Boolean - { - const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; - return (targetAsDOC && targetAsDOC.contains(target as DisplayObject)); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 72766ea..3e15a45 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -50,13 +50,13 @@ package org.gestouch.gestures protected var _pendingRecognizedState:uint; - public function Gesture(targetAdapter:IGestureTargetAdapter = null) + public function Gesture(target:Object = null) { super(); preinit(); - setTarget(targetAdapter); + this.target = target; } @@ -65,7 +65,11 @@ package org.gestouch.gestures /** * */ - public function get targetAdapter():IGestureTargetAdapter + gestouch_internal function get targetAdapter():IGestureTargetAdapter + { + return _targetAdapter; + } + protected function get targetAdapter():IGestureTargetAdapter { return _targetAdapter; } @@ -87,6 +91,16 @@ package org.gestouch.gestures { return _targetAdapter ? _targetAdapter.target : null; } + public function set target(value:Object):void + { + var target:Object = this.target; + if (target == value) + return; + + uninstallTarget(target); + _targetAdapter = value ? _gesturesManager.gestouch_internal::createGestureTargetAdapter(value) : null; + installTarget(value); + } /** @private */ @@ -172,18 +186,7 @@ package org.gestouch.gestures // // Public methods // - //-------------------------------------------------------------------------- - - public function setTarget(targetAdapter:IGestureTargetAdapter):void - { - if (_targetAdapter == targetAdapter) - return; - - uninstallTarget(this.targetAdapter); - _targetAdapter = targetAdapter; - installTarget(this.targetAdapter); - } - + //-------------------------------------------------------------------------- [Abstract] /** @@ -260,7 +263,7 @@ package org.gestouch.gestures { //TODO reset(); - setTarget(null); + target = null; delegate = null; _gesturesToFail = null; } @@ -309,9 +312,9 @@ package org.gestouch.gestures * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html */ - protected function installTarget(targetAdapter:IGestureTargetAdapter):void + protected function installTarget(target:Object):void { - if (targetAdapter) + if (target) { _gesturesManager.gestouch_internal::addGesture(this); } @@ -325,9 +328,9 @@ package org.gestouch.gestures * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html */ - protected function uninstallTarget(targetAdapter:IGestureTargetAdapter):void + protected function uninstallTarget(target:Object):void { - if (targetAdapter) + if (target) { _gesturesManager.gestouch_internal::removeGesture(this); } diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 716d277..dc892e7 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -1,7 +1,6 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; - import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.LongPressGestureEvent; @@ -30,7 +29,7 @@ package org.gestouch.gestures protected var _numTouchesRequiredReached:Boolean; - public function LongPressGesture(target:IGestureTargetAdapter = null) + public function LongPressGesture(target:Object = null) { super(target); } diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index f592a93..e8750e0 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -1,7 +1,6 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; - import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.PanGestureEvent; @@ -27,7 +26,7 @@ package org.gestouch.gestures protected var _gestureBeginOffsetY:Number; - public function PanGesture(target:IGestureTargetAdapter = null) + public function PanGesture(target:Object = null) { super(target); } diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 4045053..3b3d56c 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -1,7 +1,6 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; - import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.RotateGestureEvent; import org.gestouch.utils.GestureUtils; @@ -24,7 +23,7 @@ package org.gestouch.gestures protected var _transformVector:Point; - public function RotateGesture(target:IGestureTargetAdapter = null) + public function RotateGesture(target:Object = null) { super(target); } diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 249ee13..967b5bd 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -1,7 +1,6 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; - import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.SwipeGestureEvent; @@ -31,7 +30,7 @@ package org.gestouch.gestures protected var _decelerationCounter:uint = 0; - public function SwipeGesture(target:IGestureTargetAdapter = null) + public function SwipeGesture(target:Object = null) { super(target); } @@ -143,7 +142,7 @@ package org.gestouch.gestures { 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)); } @@ -179,7 +178,7 @@ package org.gestouch.gestures _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)); } @@ -207,7 +206,7 @@ package org.gestouch.gestures _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)); } @@ -234,7 +233,7 @@ 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)); } diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index ba33ca8..14ca26d 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -1,7 +1,6 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; - import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.TapGestureEvent; @@ -28,7 +27,7 @@ package org.gestouch.gestures protected var _tapCounter:uint = 0; - public function TapGesture(target:IGestureTargetAdapter = null) + public function TapGesture(target:Object = null) { super(target); } diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index 3c9c20c..9618bc4 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -1,7 +1,6 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; - import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.TransformGestureEvent; import org.gestouch.utils.GestureUtils; @@ -22,7 +21,7 @@ package org.gestouch.gestures protected var _transformVector:Point; - public function TransformGesture(target:IGestureTargetAdapter = null) + public function TransformGesture(target:Object = null) { super(target); } @@ -127,7 +126,7 @@ package org.gestouch.gestures { // 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); + 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)); } @@ -138,7 +137,7 @@ package org.gestouch.gestures { // 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); + 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 ebc367e..bbba6e1 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -1,7 +1,6 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; - import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.events.ZoomGestureEvent; @@ -24,7 +23,7 @@ package org.gestouch.gestures protected var _transformVector:Point; - public function ZoomGesture(target:IGestureTargetAdapter = null) + public function ZoomGesture(target:Object = null) { super(target); } From 86439e76273dcb1c1329b88f5e5ddfda43cff24e Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Sun, 18 Mar 2012 12:19:43 +0200 Subject: [PATCH 41/87] README update for simplified API --- README.textile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.textile b/README.textile index 03f8859..9014360 100644 --- a/README.textile +++ b/README.textile @@ -42,7 +42,7 @@ And I hope people to become giving some real feedback at least. h3. Getting Started Like so: -
var doubleTap:TapGesture = new TapGesture(new DisplayObjectAdapter(myButton));
+
var doubleTap:TapGesture = new TapGesture(myButton);
 doubleTap.numTapsRequired = 2;
 doubleTap.addEventListener(TapGestureEvent.GESTURE_TAP, onDoubleTap);
 ...
@@ -77,7 +77,8 @@ In order to use detect gestures with Starling do following:
 
 var gesturesManager:IGesturesManager = GesturesManager.getInstance();
 // Register instance of StarlingDisplayListAdapter to be used for objects of type starling.display.DisplayObject.
-// What StarlingDisplayListAdapter does: helps to build hierarchy (chain of parents) for any Starling display object.
+// What StarlingDisplayListAdapter 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().
 gesturesManager.addDisplayListAdapter(starling.display.DisplayObject, new StarlingDisplayListAdapter());
 
 // Initialize and register StarlingInputAdapter.
@@ -86,7 +87,7 @@ gesturesManager.addInputAdapter(new StarlingInputAdapter(starling));
 
Now you can register gesture in familiar way: -
var tap:TapGesture = new TapGesture(new StarlingDisplayObjectAdapter(starlingSprite));
+
var tap:TapGesture = new TapGesture(starlingSprite);
From 696b6367f2e12ff7e3efc9b6153db89409dcf4d7 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Mon, 2 Apr 2012 17:16:48 +0300 Subject: [PATCH 42/87] Fixed location for TapGesture --- src/org/gestouch/gestures/TapGesture.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index fc5881b..fbdcf25 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -106,6 +106,7 @@ package org.gestouch.gestures if (touchesCount == numTouchesRequired) { _numTouchesRequiredReached = true; + updateLocation(); } } @@ -136,7 +137,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, From 62fa492d663c45972e6f232c9dc2a4daa4328fd0 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Mon, 2 Apr 2012 17:17:48 +0300 Subject: [PATCH 43/87] Fixed initial offset calculation for PanGesture --- src/org/gestouch/gestures/PanGesture.as | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index ddf74bf..d864b74 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -136,6 +136,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 +153,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 From 0adfac4c5d0227967c8dd64a4de9f51033867cf6 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 4 May 2012 14:54:25 +0300 Subject: [PATCH 44/87] Fixed touch/mouse event handling condition in StarlingInputAdapter --- .../starling/StarlingInputAdapter.as | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/org/gestouch/extensions/starling/StarlingInputAdapter.as b/src/org/gestouch/extensions/starling/StarlingInputAdapter.as index 213ef05..241932d 100644 --- a/src/org/gestouch/extensions/starling/StarlingInputAdapter.as +++ b/src/org/gestouch/extensions/starling/StarlingInputAdapter.as @@ -1,12 +1,14 @@ package org.gestouch.extensions.starling { + import starling.core.Starling; + + import org.gestouch.input.AbstractInputAdapter; + import flash.events.EventPhase; import flash.events.MouseEvent; import flash.events.TouchEvent; import flash.geom.Point; - import flash.ui.Mouse; - import org.gestouch.input.AbstractInputAdapter; - import starling.core.Starling; + import flash.ui.Multitouch; @@ -33,19 +35,12 @@ package org.gestouch.extensions.starling } - protected function get supportsTouchEvents():Boolean - { - // just like in Starling (starling.core::Starling) - return !(Mouse.supportsCursor || !Starling.multitouchEnabled); - } - - override public function init():void { // We want to begin tracking only those touches that happen on Stage3D layer, // e.g. event.target == nativeStage. That's we don't listen for touch begin // in capture phase (as we do for native display list). - if (supportsTouchEvents) + if (Multitouch.supportsTouchEvents) { _starling.nativeStage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler); } @@ -70,7 +65,7 @@ package org.gestouch.extensions.starling protected function installStageListeners():void { // Maximum priority to prevent event hijacking - if (supportsTouchEvents) + if (Multitouch.supportsTouchEvents) { _starling.nativeStage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true, int.MAX_VALUE); _starling.nativeStage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false, int.MAX_VALUE); From e0a892654dd019c399316e489a6484f93410c329 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 29 May 2012 17:01:54 +0300 Subject: [PATCH 45/87] Untyped globalToLocal call fix --- src/org/gestouch/gestures/Gesture.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 3e15a45..951aec2 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -483,7 +483,7 @@ package org.gestouch.gestures updateCentralPoint(); _location.x = _centralPoint.x; _location.y = _centralPoint.y; - _localLocation = target.globalToLocal(_location); + _localLocation = targetAdapter.globalToLocal(_location); } From e9132fec9bd6c03d02999c8fc81996818c307ed3 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 29 May 2012 17:03:16 +0300 Subject: [PATCH 46/87] Massive refactoring of input layer --- src/org/gestouch/core/Gestouch.as | 86 +++++ src/org/gestouch/core/GesturesManager.as | 136 ++------ src/org/gestouch/core/IGesturesManager.as | 19 -- src/org/gestouch/core/IInputAdapter.as | 11 +- src/org/gestouch/core/ITouchHitTester.as | 14 + src/org/gestouch/core/ITouchesManager.as | 18 - src/org/gestouch/core/Touch.as | 8 +- src/org/gestouch/core/TouchesManager.as | 314 +++++++++++------- .../starling/StarlingDisplayListAdapter.as | 10 +- .../starling/StarlingInputAdapter.as | 180 ---------- .../starling/StarlingTouchHitTester.as | 36 ++ .../extensions/starling/StarlingUtils.as | 38 +++ src/org/gestouch/gestures/Gesture.as | 8 +- .../gestouch/input/AbstractInputAdapter.as | 43 --- src/org/gestouch/input/MouseInputAdapter.as | 103 ------ src/org/gestouch/input/NativeInputAdapter.as | 224 +++++++++++++ src/org/gestouch/input/TUIOInputAdapter.as | 28 +- src/org/gestouch/input/TouchInputAdapter.as | 117 ------- 18 files changed, 653 insertions(+), 740 deletions(-) create mode 100644 src/org/gestouch/core/Gestouch.as delete mode 100644 src/org/gestouch/core/IGesturesManager.as create mode 100644 src/org/gestouch/core/ITouchHitTester.as delete mode 100644 src/org/gestouch/core/ITouchesManager.as delete mode 100644 src/org/gestouch/extensions/starling/StarlingInputAdapter.as create mode 100644 src/org/gestouch/extensions/starling/StarlingTouchHitTester.as create mode 100644 src/org/gestouch/extensions/starling/StarlingUtils.as delete mode 100644 src/org/gestouch/input/AbstractInputAdapter.as delete mode 100644 src/org/gestouch/input/MouseInputAdapter.as create mode 100644 src/org/gestouch/input/NativeInputAdapter.as delete mode 100644 src/org/gestouch/input/TouchInputAdapter.as diff --git a/src/org/gestouch/core/Gestouch.as b/src/org/gestouch/core/Gestouch.as new file mode 100644 index 0000000..98309b3 --- /dev/null +++ b/src/org/gestouch/core/Gestouch.as @@ -0,0 +1,86 @@ +package org.gestouch.core +{ + import flash.display.DisplayObject; + + + /** + * @author Pavel fljot + */ + public class Gestouch + { + { + 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 + { + gesturesManager.gestouch_internal::addDisplayListAdapter(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); +// } + + + private static function initClass():void + { + addDisplayListAdapter(DisplayObject, new DisplayListAdapter()); + } + } +} diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 3f8c1a4..35b9f84 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -1,32 +1,24 @@ package org.gestouch.core { - import flash.utils.getQualifiedClassName; 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.Shape; import flash.display.Stage; import flash.events.Event; import flash.events.IEventDispatcher; - import flash.ui.Multitouch; 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 _displayListAdaptersMap:Dictionary = new Dictionary(); protected const _frameTickerShape:Shape = new Shape(); protected var _inputAdapters:Vector. = new Vector.(); - protected var _displayListAdaptersMap:Dictionary = new Dictionary(); - protected var _stage:Stage; protected var _gesturesMap:Dictionary = new Dictionary(true); protected var _gesturesForTouchMap:Dictionary = new Dictionary(); protected var _gesturesForTargetMap:Dictionary = new Dictionary(true); @@ -34,120 +26,36 @@ package org.gestouch.core protected var _dirtyGesturesLength:uint = 0; protected var _dirtyGesturesMap:Dictionary = new Dictionary(true); + use namespace gestouch_internal; + public function GesturesManager() - { - if (Object(this).constructor == GesturesManager && !_allowInstantiation) - { - throw new Error("Do not instantiate GesturesManager directly."); - } + { - _touchesManager.gesturesManager = this; - _displayListAdaptersMap[DisplayObject] = new DisplayListAdapter(); } - 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.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(); - } - } - - - public function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void + gestouch_internal function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void { if (!targetClass || !adapter) { throw new Error("Argument error: both arguments required."); } + _displayListAdaptersMap[targetClass] = adapter; } - //-------------------------------------------------------------------------- // // Private methods // //-------------------------------------------------------------------------- - protected function installStage(stage:Stage):void + protected function installDefaultInputAdapter(stage:Stage):void { - _stage = stage; - - if (Multitouch.supportsTouchEvents) - { - addInputAdapter(new TouchInputAdapter(stage)); - } - else - { - addInputAdapter(new MouseInputAdapter(stage)); - } + Gestouch.inputAdapter ||= new NativeInputAdapter(stage); } @@ -201,14 +109,14 @@ package org.gestouch.core _gesturesMap[gesture] = true; - if (GesturesManager.initDefaultInputAdapter) + if (!Gestouch.inputAdapter) { var targetAsDO:DisplayObject = target as DisplayObject; if (targetAsDO) { - if (!_stage && targetAsDO.stage) + if (targetAsDO.stage) { - installStage(targetAsDO.stage); + installDefaultInputAdapter(targetAsDO.stage); } else { @@ -274,8 +182,8 @@ package org.gestouch.core otherGesture.state == GestureState.POSSIBLE) { if (otherTarget == target || - gesture.gestouch_internal::targetAdapter.contains(otherTarget) || - otherGesture.gestouch_internal::targetAdapter.contains(target) + gesture.targetAdapter.contains(otherTarget) || + otherGesture.targetAdapter.contains(target) ) { var gestureDelegate:IGestureDelegate = gesture.delegate; @@ -286,7 +194,7 @@ 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); } } } @@ -362,7 +270,7 @@ package org.gestouch.core // Check for state because previous (i+1) gesture may already abort current (i) one if (gesture.state != GestureState.FAILED) { - gesture.gestouch_internal::touchBeginHandler(touch); + gesture.touchBeginHandler(touch); } else { @@ -388,7 +296,7 @@ package org.gestouch.core if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) { - gesture.gestouch_internal::touchMoveHandler(touch); + gesture.touchMoveHandler(touch); } else { @@ -415,7 +323,7 @@ package org.gestouch.core if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) { - gesture.gestouch_internal::touchEndHandler(touch); + gesture.touchEndHandler(touch); } } @@ -441,9 +349,9 @@ package org.gestouch.core { var target:DisplayObject = event.target as DisplayObject; target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); - if (!_stage && GesturesManager.initDefaultInputAdapter) + if (!Gestouch.inputAdapter) { - installStage(target.stage); + installDefaultInputAdapter(target.stage); } } diff --git a/src/org/gestouch/core/IGesturesManager.as b/src/org/gestouch/core/IGesturesManager.as deleted file mode 100644 index c73412a..0000000 --- a/src/org/gestouch/core/IGesturesManager.as +++ /dev/null @@ -1,19 +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; - - function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void; - } -} \ No newline at end of file diff --git a/src/org/gestouch/core/IInputAdapter.as b/src/org/gestouch/core/IInputAdapter.as index 83d9f3d..7d42732 100644 --- a/src/org/gestouch/core/IInputAdapter.as +++ b/src/org/gestouch/core/IInputAdapter.as @@ -5,9 +5,14 @@ package org.gestouch.core */ public interface IInputAdapter { - function set touchesManager(value:ITouchesManager):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 2243c21..0000000 --- a/src/org/gestouch/core/ITouchesManager.as +++ /dev/null @@ -1,18 +0,0 @@ -package org.gestouch.core -{ - /** - * @author Pavel fljot - */ - public interface ITouchesManager - { - function set gesturesManager(value:IGesturesManager):void; - - function get activeTouchesCount():uint; - - function onTouchBegin(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number, target:Object):void; - function onTouchMove(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void; - function onTouchEnd(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void; - - function onInputAdapterDispose(inputAdapter:IInputAdapter):void; - } -} \ No newline at end of file diff --git a/src/org/gestouch/core/Touch.as b/src/org/gestouch/core/Touch.as index 1f2a372..0679792 100644 --- a/src/org/gestouch/core/Touch.as +++ b/src/org/gestouch/core/Touch.as @@ -25,6 +25,8 @@ package org.gestouch.core public var pressure:Number; // public var lastMove:Point; + + use namespace gestouch_internal; public function Touch(id:uint = 0) @@ -38,9 +40,9 @@ package org.gestouch.core { return _location.clone(); } - gestouch_internal function setLocation(value:Point, time:uint):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(); @@ -59,7 +61,7 @@ package org.gestouch.core } else { - gestouch_internal::setLocation(new Point(x, y), time); + setLocation(x, y, time); } } diff --git a/src/org/gestouch/core/TouchesManager.as b/src/org/gestouch/core/TouchesManager.as index b4d2526..f3645d5 100644 --- a/src/org/gestouch/core/TouchesManager.as +++ b/src/org/gestouch/core/TouchesManager.as @@ -1,32 +1,30 @@ 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."); - } - } - - - protected var _gesturesManager:IGesturesManager; - public function set gesturesManager(value:IGesturesManager):void - { - _gesturesManager = value; + _gesturesManager = gesturesManager; + + addTouchHitTester(new DefaultTouchHitTester()); } @@ -37,132 +35,167 @@ 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."); - } - if (_instance) - { - 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."); - } - _instance = value; - } - - - public static function getInstance():ITouchesManager - { - if (!_instance) - { - _allowInstantiation = true; - _instance = new TouchesManager(); - _allowInstantiation = false; - } - - return _instance; - } - - - public function onTouchBegin(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number, target:Object):void - { - var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary; - if (overlappingTouches) - { - // In case we listen to both TouchEvents and MouseEvents, one of them will come first - // (right now looks like MouseEvent dispatches first, but who know what Adobe will - // do tomorrow). This check is to filter out the one comes second. - for each (var registeredTouch:Touch in overlappingTouches) + // return all touches + var i:uint = 0; + for each (var touch:Touch in _touchesMap) { - if (registeredTouch.target == target) - return; + touches[i++] = touch; } } else { - overlappingTouches = _touchesMap[touchID] = new Dictionary(); - _activeTouchesCount++; + //TODO } - var touch:Touch = createTouch(); + return touches; + } + + + 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; - touch.target = target; - touch.gestouch_internal::setLocation(new Point(x, y), getTimer()); - overlappingTouches[inputAdapter] = touch; - _gesturesManager.gestouch_internal::onTouchBegin(touch); - } - - - public function onTouchMove(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void - { - var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary; - if (!overlappingTouches) - return;//this touch isn't properly registered.. some fake - - var touch:Touch = overlappingTouches[inputAdapter] as Touch; - if (!touch) - return;//touch with this ID from this inputAdapter is not registered. see workaround reason above - - touch.gestouch_internal::updateLocation(x, y, getTimer()); - - _gesturesManager.gestouch_internal::onTouchMove(touch); - } - - - public function onTouchEnd(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void - { - var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary; - if (!overlappingTouches) - return;//this touch isn't properly registered.. some fake - - var touch:Touch = overlappingTouches[inputAdapter] as Touch; - if (!touch) - return;//touch with this ID from this inputAdapter is not registered. see workaround reason above - - touch.gestouch_internal::updateLocation(x, y, getTimer()); - - delete overlappingTouches[inputAdapter]; - var empty:Boolean = true; - for (var key:Object in overlappingTouches) + var target:Object; + var altTarget:Object; + for each (var hitTester:ITouchHitTester in _hitTesters) { - empty = false; - break; - } - if (empty) - { - delete _touchesMap[touchID]; - _activeTouchesCount--; - } - - _gesturesManager.gestouch_internal::onTouchEnd(touch); - } - - - /** - * Must be called by IInputAdapter#dispose() to remove all the touches invoked by it. - */ - public function onInputAdapterDispose(inputAdapter:IInputAdapter):void - { - for (var touchID:Object in _touchesMap) - { - var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary; - if (overlappingTouches[inputAdapter]) + target = hitTester.hitTest(location, nativeTarget); + if (target) { - delete overlappingTouches[inputAdapter]; - var empty:Boolean = true; - for (var key:Object in overlappingTouches) + if ((target is Stage)) { - empty = false; - break; + // 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; } - if (empty) + else { - delete _touchesMap[touchID]; - _activeTouchesCount--; + // 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); } @@ -171,5 +204,38 @@ package org.gestouch.core //TODO: pool return new 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 + { + const d:int = int(_hitTesterPrioritiesMap[x]) - int(_hitTesterPrioritiesMap[y]); + if (d > 0) + return -1; + else if (d < 0) + return 1; + + return _hitTesters.indexOf(x) > _hitTesters.indexOf(y) ? 1 : -1; + } } -} \ No newline at end of file +} + + +import flash.geom.Point; +import flash.display.InteractiveObject; + +import org.gestouch.core.ITouchHitTester; + + +class DefaultTouchHitTester 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 index 3b0a9fe..5063659 100644 --- a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as +++ b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as @@ -1,5 +1,6 @@ package org.gestouch.extensions.starling { + import starling.core.Starling; import starling.display.DisplayObject; import starling.display.DisplayObjectContainer; @@ -14,22 +15,22 @@ package org.gestouch.extensions.starling */ final public class StarlingDisplayListAdapter implements IDisplayListAdapter { - private var _targetWeekStorage:Dictionary; + private var targetWeekStorage:Dictionary; public function StarlingDisplayListAdapter(target:DisplayObject = null) { if (target) { - _targetWeekStorage = new Dictionary(true); - _targetWeekStorage[target] = true; + targetWeekStorage = new Dictionary(true); + targetWeekStorage[target] = true; } } public function get target():Object { - for (var key:Object in _targetWeekStorage) + for (var key:Object in targetWeekStorage) { return key; } @@ -39,6 +40,7 @@ package org.gestouch.extensions.starling public function globalToLocal(point:Point):Point { + point = StarlingUtils.adjustGlobalPoint(Starling.current, point); return (target as DisplayObject).globalToLocal(point); } diff --git a/src/org/gestouch/extensions/starling/StarlingInputAdapter.as b/src/org/gestouch/extensions/starling/StarlingInputAdapter.as deleted file mode 100644 index 241932d..0000000 --- a/src/org/gestouch/extensions/starling/StarlingInputAdapter.as +++ /dev/null @@ -1,180 +0,0 @@ -package org.gestouch.extensions.starling -{ - import starling.core.Starling; - - import org.gestouch.input.AbstractInputAdapter; - - import flash.events.EventPhase; - import flash.events.MouseEvent; - import flash.events.TouchEvent; - import flash.geom.Point; - import flash.ui.Multitouch; - - - - /** - * @author Pavel fljot - */ - public class StarlingInputAdapter extends AbstractInputAdapter - { - private static const PRIMARY_TOUCH_POINT_ID:uint = 0; - - protected var _starling:Starling; - - - public function StarlingInputAdapter(starling:Starling) - { - super(); - - if (!starling) - { - throw new Error("Argument error."); - } - - _starling = starling; - } - - - override public function init():void - { - // We want to begin tracking only those touches that happen on Stage3D layer, - // e.g. event.target == nativeStage. That's we don't listen for touch begin - // in capture phase (as we do for native display list). - if (Multitouch.supportsTouchEvents) - { - _starling.nativeStage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler); - } - else - { - _starling.nativeStage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); - } - } - - - override public function dispose():void - { - _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler); - _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); - uninstallStageListeners(); - _starling = null; - _touchesManager.onInputAdapterDispose(this); - _touchesManager = null; - } - - - protected function installStageListeners():void - { - // Maximum priority to prevent event hijacking - if (Multitouch.supportsTouchEvents) - { - _starling.nativeStage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true, int.MAX_VALUE); - _starling.nativeStage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false, int.MAX_VALUE); - _starling.nativeStage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true, int.MAX_VALUE); - _starling.nativeStage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, false, int.MAX_VALUE); - } - else - { - _starling.nativeStage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true, int.MAX_VALUE); - _starling.nativeStage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false, int.MAX_VALUE); - _starling.nativeStage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, int.MAX_VALUE); - _starling.nativeStage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, int.MAX_VALUE); - } - } - - - protected function uninstallStageListeners():void - { - _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true); - _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false); - _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true); - _starling.nativeStage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, false); - _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true); - _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false); - _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); - _starling.nativeStage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false); - } - - - protected function mouseDownHandler(event:MouseEvent):void - { - // We ignore event with bubbling phase because it happened on some native InteractiveObject, - // which basically hovers Stage3D layer. So we treat it as if Starling wouldn't recieve any input. - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return; - - var target:Object = _starling.stage.hitTest(new Point(event.stageX, event.stageY), true); - _touchesManager.onTouchBegin(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY, target); - - if (_touchesManager.activeTouchesCount > 0) - { - installStageListeners(); - } - } - - - 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) - - _touchesManager.onTouchMove(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY); - } - - - 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) - - _touchesManager.onTouchEnd(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY); - - if (_touchesManager.activeTouchesCount == 0) - { - uninstallStageListeners(); - } - } - - - protected function touchBeginHandler(event:TouchEvent):void - { - // We ignore event with bubbling phase because it happened on some native InteractiveObject, - // which basically hovers Stage3D layer. So we treat it as if Starling wouldn't recieve any input. - if (event.eventPhase == EventPhase.BUBBLING_PHASE) - return; - - var target:Object = _starling.stage.hitTest(new Point(event.stageX, event.stageY), true); - _touchesManager.onTouchBegin(this, event.touchPointID, event.stageX, event.stageY, target); - - if (_touchesManager.activeTouchesCount > 0) - { - installStageListeners(); - } - } - - - 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) - - _touchesManager.onTouchMove(this, event.touchPointID, event.stageX, event.stageY); - } - - - 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) - - _touchesManager.onTouchEnd(this, event.touchPointID, event.stageX, event.stageY); - - 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/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 951aec2..1891fd2 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -1,10 +1,10 @@ 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.IGestureTargetAdapter; - import org.gestouch.core.IGesturesManager; import org.gestouch.core.Touch; import org.gestouch.core.gestouch_internal; import org.gestouch.events.GestureStateEvent; @@ -20,9 +20,6 @@ package org.gestouch.gestures * 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 @@ -35,7 +32,7 @@ package org.gestouch.gestures public static const 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. */ @@ -175,7 +172,6 @@ package org.gestouch.gestures */ public function get location():Point { - //TODO: to clone or not clone? performance & convention or ... return _location.clone(); } diff --git a/src/org/gestouch/input/AbstractInputAdapter.as b/src/org/gestouch/input/AbstractInputAdapter.as deleted file mode 100644 index a870a62..0000000 --- a/src/org/gestouch/input/AbstractInputAdapter.as +++ /dev/null @@ -1,43 +0,0 @@ -package org.gestouch.input -{ - import org.gestouch.core.IInputAdapter; - import org.gestouch.core.ITouchesManager; - - - /** - * @author Pavel fljot - */ - public class AbstractInputAdapter implements IInputAdapter - { - protected var _touchesManager:ITouchesManager; - - - 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; - } - - - [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 9a7e4b0..0000000 --- a/src/org/gestouch/input/MouseInputAdapter.as +++ /dev/null @@ -1,103 +0,0 @@ -package org.gestouch.input -{ - import flash.display.Stage; - import flash.events.EventPhase; - import flash.events.MouseEvent; - - - /** - * @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(); - _touchesManager.onInputAdapterDispose(this); - _touchesManager = null; - } - - - 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) - - _touchesManager.onTouchBegin(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY, event.target); - - if (_touchesManager.activeTouchesCount > 0) - { - installStageListeners(); - } - } - - - 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) - - _touchesManager.onTouchMove(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY); - } - - - 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) - - _touchesManager.onTouchEnd(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY); - - 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 920cd2d..0000000 --- a/src/org/gestouch/input/TouchInputAdapter.as +++ /dev/null @@ -1,117 +0,0 @@ -package org.gestouch.input -{ - import flash.display.Stage; - import flash.events.EventPhase; - import flash.events.TouchEvent; - import flash.ui.Multitouch; - import flash.ui.MultitouchInputMode; - - - /** - * @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(); - _touchesManager.onInputAdapterDispose(this); - _touchesManager = null; - } - - - 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) - - _touchesManager.onTouchBegin(this, event.touchPointID, event.stageX, event.stageY, event.target); - - if (_touchesManager.activeTouchesCount > 0) - { - installStageListeners(); - } - } - - - 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) - - _touchesManager.onTouchMove(this, event.touchPointID, event.stageX, event.stageY); - } - - - 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) - - _touchesManager.onTouchEnd(this, event.touchPointID, event.stageX, event.stageY); - - if (_touchesManager.activeTouchesCount == 0) - { - uninstallStageListeners(); - } - - // TODO: handle cancelled touch: - // if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"] && ... - } - } -} \ No newline at end of file From 8d870f4e93e9879fd060ccddb1ae6229083ca114 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 1 Jun 2012 13:49:29 +0300 Subject: [PATCH 47/87] Improved event handling autocompletion (for FD) --- src/org/gestouch/gestures/Gesture.as | 6 ++++++ src/org/gestouch/gestures/LongPressGesture.as | 5 +++++ src/org/gestouch/gestures/PanGesture.as | 5 +++++ src/org/gestouch/gestures/RotateGesture.as | 5 +++++ src/org/gestouch/gestures/SwipeGesture.as | 5 +++++ src/org/gestouch/gestures/TapGesture.as | 4 ++++ src/org/gestouch/gestures/TransformGesture.as | 8 ++++++-- src/org/gestouch/gestures/ZoomGesture.as | 5 +++++ 8 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 1891fd2..1ef7b98 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -15,6 +15,12 @@ package org.gestouch.gestures 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 diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index dc892e7..7f2dea8 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -8,6 +8,11 @@ package org.gestouch.gestures import flash.utils.Timer; + /** + * + * @eventType org.gestouch.events.LongPressGestureEvent + */ + [Event(name="gestureLongPress", type="org.gestouch.events.LongPressGestureEvent")] /** * TODO: -location * - check on iOS (Obj-C) what happens when numTouchesRequired=2, two finger down, then quickly release one. diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index bd25233..0d1eea5 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -6,6 +6,11 @@ package org.gestouch.gestures import flash.geom.Point; + + /** + * + * @eventType org.gestouch.events.PanGestureEvent + */ [Event(name="gesturePan", type="org.gestouch.events.PanGestureEvent")] /** * TODO: diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 3b3d56c..0d1eb39 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -7,6 +7,11 @@ package org.gestouch.gestures import flash.geom.Point; + + /** + * + * @eventType org.gestouch.events.RotateGestureEvent + */ [Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")] /** * TODO: diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 967b5bd..3d00c42 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -6,6 +6,11 @@ package org.gestouch.gestures import flash.geom.Point; + + /** + * + * @eventType org.gestouch.events.SwipeGestureEvent + */ [Event(name="gestureSwipe", type="org.gestouch.events.SwipeGestureEvent")] /** * TODO: diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index fb484ae..f7fbdb1 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -8,6 +8,10 @@ package org.gestouch.gestures import flash.utils.Timer; + /** + * + * @eventType org.gestouch.events.TapGestureEvent + */ [Event(name="gestureTap", type="org.gestouch.events.TapGestureEvent")] /** * TODO: check failing conditions (iDevice) diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index 9618bc4..c4f3433 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -6,8 +6,12 @@ package org.gestouch.gestures import org.gestouch.utils.GestureUtils; import flash.geom.Point; - - + + + /** + * + * @eventType org.gestouch.events.TransformGestureEvent + */ [Event(name="gestureTransform", type="org.gestouch.events.TransformGestureEvent")] /** * @author Pavel fljot diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index bbba6e1..22f9d2e 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -6,6 +6,11 @@ package org.gestouch.gestures import flash.geom.Point; + + /** + * + * @eventType org.gestouch.events.ZoomGestureEvent + */ [Event(name="gestureZoom", type="org.gestouch.events.ZoomGestureEvent")] /** * TODO: From 33108a1bc761f5ff38af68641593a34a4c25b3d4 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 1 Jun 2012 13:51:15 +0300 Subject: [PATCH 48/87] Changed global system gesture slop to variable --- src/org/gestouch/gestures/Gesture.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 1ef7b98..a78fd49 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -35,7 +35,7 @@ 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:GesturesManager = Gestouch.gesturesManager; From d072f6e478b8740793228110c6da5d1950a88cfe Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Sun, 1 Jul 2012 11:48:10 +0300 Subject: [PATCH 49/87] Fix for potential RTE related to "contains" logic --- src/org/gestouch/core/DisplayListAdapter.as | 5 +++-- src/org/gestouch/core/IGestureTargetAdapter.as | 2 +- .../extensions/starling/StarlingDisplayListAdapter.as | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/org/gestouch/core/DisplayListAdapter.as b/src/org/gestouch/core/DisplayListAdapter.as index 6f224cd..3c5e41d 100644 --- a/src/org/gestouch/core/DisplayListAdapter.as +++ b/src/org/gestouch/core/DisplayListAdapter.as @@ -40,10 +40,11 @@ package org.gestouch.core } - public function contains(target:Object):Boolean + public function contains(object:Object):Boolean { const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; - return (targetAsDOC && targetAsDOC.contains(target as DisplayObject)); + const objectAsDO:DisplayObject = object as DisplayObject; + return (targetAsDOC && objectAsDO && targetAsDOC.contains(objectAsDO)); } diff --git a/src/org/gestouch/core/IGestureTargetAdapter.as b/src/org/gestouch/core/IGestureTargetAdapter.as index c66eb0f..fdf70e5 100644 --- a/src/org/gestouch/core/IGestureTargetAdapter.as +++ b/src/org/gestouch/core/IGestureTargetAdapter.as @@ -10,6 +10,6 @@ package org.gestouch.core function globalToLocal(point:Point):Point; - function contains(target:Object):Boolean; + function contains(object:Object):Boolean; } } \ No newline at end of file diff --git a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as index 5063659..6cfd206 100644 --- a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as +++ b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as @@ -45,10 +45,11 @@ package org.gestouch.extensions.starling } - public function contains(target:Object):Boolean + public function contains(object:Object):Boolean { const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; - return (targetAsDOC && targetAsDOC.contains(target as DisplayObject)); + const objectAsDO:DisplayObject = object as DisplayObject; + return (targetAsDOC && objectAsDO && targetAsDOC.contains(objectAsDO)); } From 32e9ff979c543a8636eeede607eadb64c85a8233 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Sun, 1 Jul 2012 12:57:31 +0300 Subject: [PATCH 50/87] Update to always include Stage in hierarchy so that gestures registered on Stage will always react on touch --- src/org/gestouch/core/GesturesManager.as | 25 +++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 35b9f84..194ef64 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -25,6 +25,7 @@ package org.gestouch.core protected var _dirtyGestures:Vector. = new Vector.(); protected var _dirtyGesturesLength:uint = 0; protected var _dirtyGesturesMap:Dictionary = new Dictionary(true); + protected var _stage:Stage; use namespace gestouch_internal; @@ -53,8 +54,10 @@ package org.gestouch.core // //-------------------------------------------------------------------------- - protected function installDefaultInputAdapter(stage:Stage):void + protected function onStageAvailable(stage:Stage):void { + _stage = stage; + Gestouch.inputAdapter ||= new NativeInputAdapter(stage); } @@ -109,14 +112,14 @@ package org.gestouch.core _gesturesMap[gesture] = true; - if (!Gestouch.inputAdapter) + if (!_stage) { var targetAsDO:DisplayObject = target as DisplayObject; if (targetAsDO) { if (targetAsDO.stage) { - installDefaultInputAdapter(targetAsDO.stage); + onStageAvailable(targetAsDO.stage); } else { @@ -238,6 +241,18 @@ package org.gestouch.core { throw new Error("Display list adapter not found for target of type '" + targetClass + "'."); } + 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. @@ -349,9 +364,9 @@ package org.gestouch.core { var target:DisplayObject = event.target as DisplayObject; target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); - if (!Gestouch.inputAdapter) + if (!_stage) { - installDefaultInputAdapter(target.stage); + onStageAvailable(target.stage); } } From cdd90d747932e978d92ce7bb6255064b6e725d43 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Mon, 2 Jul 2012 18:28:30 +0300 Subject: [PATCH 51/87] Moved IDisplayListAdapter creating and retrieving logic to Gestouch class --- src/org/gestouch/core/DisplayListAdapter.as | 28 +++++++++++++- src/org/gestouch/core/Gestouch.as | 41 ++++++++++++++++++-- src/org/gestouch/core/GesturesManager.as | 43 ++------------------- src/org/gestouch/gestures/Gesture.as | 2 +- 4 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/org/gestouch/core/DisplayListAdapter.as b/src/org/gestouch/core/DisplayListAdapter.as index 3c5e41d..bce0103 100644 --- a/src/org/gestouch/core/DisplayListAdapter.as +++ b/src/org/gestouch/core/DisplayListAdapter.as @@ -44,7 +44,33 @@ package org.gestouch.core { const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer; const objectAsDO:DisplayObject = object as DisplayObject; - return (targetAsDOC && objectAsDO && targetAsDOC.contains(objectAsDO)); + 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; } diff --git a/src/org/gestouch/core/Gestouch.as b/src/org/gestouch/core/Gestouch.as index 98309b3..11e77ce 100644 --- a/src/org/gestouch/core/Gestouch.as +++ b/src/org/gestouch/core/Gestouch.as @@ -1,13 +1,17 @@ package org.gestouch.core { + import flash.utils.getQualifiedClassName; import flash.display.DisplayObject; + import flash.utils.Dictionary; /** * @author Pavel fljot */ - public class Gestouch - { + final public class Gestouch + { + private static const _displayListAdaptersMap:Dictionary = new Dictionary(); + { initClass(); } @@ -56,7 +60,12 @@ package org.gestouch.core public static function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void { - gesturesManager.gestouch_internal::addDisplayListAdapter(targetClass, adapter); + if (!targetClass || !adapter) + { + throw new Error("Argument error: both arguments required."); + } + + _displayListAdaptersMap[targetClass] = adapter; } @@ -77,6 +86,32 @@ package org.gestouch.core // 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 { diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 194ef64..0bb1cb5 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -16,7 +16,6 @@ package org.gestouch.core */ public class GesturesManager { - protected const _displayListAdaptersMap:Dictionary = new Dictionary(); protected const _frameTickerShape:Shape = new Shape(); protected var _inputAdapters:Vector. = new Vector.(); protected var _gesturesMap:Dictionary = new Dictionary(true); @@ -31,23 +30,13 @@ package org.gestouch.core public function GesturesManager() - { - - } - - - gestouch_internal function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void { - if (!targetClass || !adapter) - { - throw new Error("Argument error: both arguments required."); - } - _displayListAdaptersMap[targetClass] = adapter; } + //-------------------------------------------------------------------------- // // Private methods @@ -75,22 +64,6 @@ package org.gestouch.core } - gestouch_internal function createGestureTargetAdapter(target:Object):IDisplayListAdapter - { - for (var key:Object in _displayListAdaptersMap) - { - var targetClass:Class = key as Class; - if (target is targetClass) - { - var adapter:IDisplayListAdapter = _displayListAdaptersMap[key] as IDisplayListAdapter; - return new (adapter.reflect())(target); - } - } - - throw new Error("Cannot create adapter for target " + target + " of type " + getQualifiedClassName(target) + "."); - } - - gestouch_internal function addGesture(gesture:Gesture):void { if (!gesture) @@ -227,19 +200,11 @@ package org.gestouch.core } var target:Object = touch.target; - var hierarchy:Vector.; - for (var key:Object in _displayListAdaptersMap) - { - var targetClass:Class = key as Class; - if (target is targetClass) - { - hierarchy = (_displayListAdaptersMap[key] as IDisplayListAdapter).getHierarchy(target); - break; - } - } + const displayListAdapter:IDisplayListAdapter = Gestouch.gestouch_internal::getDisplayListAdapter(target); + const hierarchy:Vector. = displayListAdapter.getHierarchy(target); if (!hierarchy) { - throw new Error("Display list adapter not found for target of type '" + targetClass + "'."); + throw new Error("Display list adapter not found for target of type '" + getQualifiedClassName(target) + "'."); } const hierarchyLength:uint = hierarchy.length; if (hierarchyLength == 0) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index a78fd49..fa3c477 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -101,7 +101,7 @@ package org.gestouch.gestures return; uninstallTarget(target); - _targetAdapter = value ? _gesturesManager.gestouch_internal::createGestureTargetAdapter(value) : null; + _targetAdapter = value ? Gestouch.gestouch_internal::createGestureTargetAdapter(value) : null; installTarget(value); } From 09c35a5d979b9ad0ef1b8c67846aa6635c1a3411 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Mon, 2 Jul 2012 18:29:33 +0300 Subject: [PATCH 52/87] Minor tabs and spaces cleanup --- src/org/gestouch/core/DisplayListAdapter.as | 2 +- src/org/gestouch/core/GesturesManager.as | 17 +++++++++-------- .../starling/StarlingDisplayListAdapter.as | 2 +- src/org/gestouch/gestures/Gesture.as | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/org/gestouch/core/DisplayListAdapter.as b/src/org/gestouch/core/DisplayListAdapter.as index bce0103..77516f2 100644 --- a/src/org/gestouch/core/DisplayListAdapter.as +++ b/src/org/gestouch/core/DisplayListAdapter.as @@ -81,7 +81,7 @@ package org.gestouch.core var target:DisplayObject = genericTarget as DisplayObject; while (target) { - list[i] = target; + list[i] = target; target = target.parent; i++; } diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 0bb1cb5..a3e53bb 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -116,7 +116,7 @@ package org.gestouch.core if (targetGestures.length > 1) { targetGestures.splice(targetGestures.indexOf(gesture), 1); - } + } else { delete _gesturesForTargetMap[target]; @@ -145,11 +145,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:Object = gesture.target; - var otherTarget:Object = otherGesture.target; + var otherTarget:Object = otherGesture.target; // conditions for otherGesture "own properties" if (otherGesture != gesture && @@ -159,7 +160,7 @@ package org.gestouch.core { if (otherTarget == target || gesture.targetAdapter.contains(otherTarget) || - otherGesture.targetAdapter.contains(target) + otherGesture.targetAdapter.contains(target) ) { var gestureDelegate:IGestureDelegate = gesture.delegate; @@ -172,7 +173,7 @@ package org.gestouch.core { otherGesture.setState_internal(GestureState.FAILED); } - } + } } } } @@ -300,9 +301,9 @@ package org.gestouch.core while (i-- > 0) { gesture = gesturesForTouch[i] as Gesture; - + if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) - { + { gesture.touchEndHandler(touch); } } @@ -323,7 +324,7 @@ package org.gestouch.core // // Event handlers // - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- protected function gestureTarget_addedToStageHandler(event:Event):void { diff --git a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as index 6cfd206..adc4e13 100644 --- a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as +++ b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as @@ -60,7 +60,7 @@ package org.gestouch.extensions.starling var target:DisplayObject = genericTarget as DisplayObject; while (target) { - list[i] = target; + list[i] = target; target = target.parent; i++; } diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index fa3c477..cf802ea 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -97,8 +97,8 @@ package org.gestouch.gestures public function set target(value:Object):void { var target:Object = this.target; - if (target == value) - return; + if (target == value) + return; uninstallTarget(target); _targetAdapter = value ? Gestouch.gestouch_internal::createGestureTargetAdapter(value) : null; @@ -434,7 +434,7 @@ package org.gestouch.gestures } } - var oldState:uint = _state; + var oldState:uint = _state; _state = newState; if (((GestureState.CANCELLED | GestureState.RECOGNIZED | GestureState.ENDED | GestureState.FAILED) & _state) > 0) From 039a7d79f41a6c9236cd288db98ea83b1805edc7 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Mon, 2 Jul 2012 22:33:50 +0300 Subject: [PATCH 53/87] Fixed and slightly improved gesture reset --- src/org/gestouch/core/GesturesManager.as | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index a3e53bb..b92dbfe 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -21,8 +21,7 @@ package org.gestouch.core protected var _gesturesMap:Dictionary = new Dictionary(true); 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; @@ -53,12 +52,11 @@ package org.gestouch.core protected function resetDirtyGestures():void { - for each (var gesture:Gesture in _dirtyGestures) + for (var gesture:* 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); } @@ -136,8 +134,8 @@ package org.gestouch.core { if (!_dirtyGesturesMap[gesture]) { - _dirtyGestures.push(gesture); - _dirtyGesturesLength++; + _dirtyGesturesMap[gesture] = true; + _dirtyGesturesCount++; _frameTickerShape.addEventListener(Event.ENTER_FRAME, enterFrameHandler); } } @@ -181,7 +179,7 @@ package org.gestouch.core gestouch_internal function onTouchBegin(touch:Touch):void { - if (_dirtyGesturesLength > 0) + if (_dirtyGesturesCount > 0) { resetDirtyGestures(); } @@ -263,7 +261,7 @@ package org.gestouch.core gestouch_internal function onTouchMove(touch:Touch):void { - if (_dirtyGesturesLength > 0) + if (_dirtyGesturesCount > 0) { resetDirtyGestures(); } @@ -290,7 +288,7 @@ package org.gestouch.core gestouch_internal function onTouchEnd(touch:Touch):void { - if (_dirtyGesturesLength > 0) + if (_dirtyGesturesCount > 0) { resetDirtyGestures(); } From 193332b9d0ce7e818072b9a7411f983b40106cee Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 3 Jul 2012 23:12:38 +0300 Subject: [PATCH 54/87] Change GestureState to "real" enum --- src/org/gestouch/core/GestureState.as | 100 ++++++++++++++++-- src/org/gestouch/events/GestureEvent.as | 6 +- src/org/gestouch/events/GestureStateEvent.as | 8 +- .../gestouch/events/LongPressGestureEvent.as | 4 +- src/org/gestouch/events/PanGestureEvent.as | 4 +- src/org/gestouch/events/RotateGestureEvent.as | 4 +- src/org/gestouch/events/SwipeGestureEvent.as | 4 +- src/org/gestouch/events/TapGestureEvent.as | 5 +- .../gestouch/events/TransformGestureEvent.as | 4 +- src/org/gestouch/events/ZoomGestureEvent.as | 4 +- src/org/gestouch/gestures/Gesture.as | 51 +++++---- src/org/gestouch/gestures/LongPressGesture.as | 2 +- 12 files changed, 154 insertions(+), 42 deletions(-) diff --git a/src/org/gestouch/core/GestureState.as b/src/org/gestouch/core/GestureState.as index 07c9e75..afb2451 100644 --- a/src/org/gestouch/core/GestureState.as +++ b/src/org/gestouch/core/GestureState.as @@ -1,17 +1,99 @@ 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); + + 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/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/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index cf802ea..e9963e7 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -9,6 +9,7 @@ package org.gestouch.gestures import org.gestouch.core.gestouch_internal; import org.gestouch.events.GestureStateEvent; + import flash.errors.IllegalOperationError; import flash.events.EventDispatcher; import flash.geom.Point; import flash.system.Capabilities; @@ -50,7 +51,9 @@ 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:Object = null) @@ -122,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); + } } } @@ -153,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; } @@ -217,7 +227,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 @@ -232,14 +242,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); @@ -379,18 +389,19 @@ package org.gestouch.gestures } - protected function setState(newState:uint):Boolean + protected function setState(newState:GestureState):Boolean { if (_state == newState && _state == GestureState.CHANGED) { 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) { @@ -434,10 +445,10 @@ 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); } @@ -458,7 +469,7 @@ package org.gestouch.gestures } - gestouch_internal function setState_internal(state:uint):void + gestouch_internal function setState_internal(state:GestureState):void { setState(state); } @@ -575,4 +586,4 @@ package org.gestouch.gestures } } } -} \ No newline at end of file +} diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 7f2dea8..4781495 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -128,7 +128,7 @@ package org.gestouch.gestures //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)) From bbfb3fc34c9a49978110b4c2150b0a657c14c197 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 3 Jul 2012 23:23:48 +0300 Subject: [PATCH 55/87] Minor performance improvement via "use namespace" access --- src/org/gestouch/gestures/Gesture.as | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index e9963e7..777deb6 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -104,7 +104,7 @@ package org.gestouch.gestures return; uninstallTarget(target); - _targetAdapter = value ? Gestouch.gestouch_internal::createGestureTargetAdapter(value) : null; + _targetAdapter = value ? Gestouch.createGestureTargetAdapter(value) : null; installTarget(value); } @@ -328,7 +328,7 @@ package org.gestouch.gestures { if (target) { - _gesturesManager.gestouch_internal::addGesture(this); + _gesturesManager.addGesture(this); } } @@ -344,7 +344,7 @@ package org.gestouch.gestures { if (target) { - _gesturesManager.gestouch_internal::removeGesture(this); + _gesturesManager.removeGesture(this); } } @@ -450,7 +450,7 @@ package org.gestouch.gestures if (_state.isEndState) { - _gesturesManager.gestouch_internal::scheduleGestureStateReset(this); + _gesturesManager.scheduleGestureStateReset(this); } //TODO: what if RTE happens in event handlers? @@ -462,7 +462,7 @@ package org.gestouch.gestures if (_state == GestureState.BEGAN || _state == GestureState.RECOGNIZED) { - _gesturesManager.gestouch_internal::onGestureRecognized(this); + _gesturesManager.onGestureRecognized(this); } return true; From 60d9cb6744e498d4986b01373bd30f8c56dbbfea Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 4 Jul 2012 21:34:49 +0300 Subject: [PATCH 56/87] Fixed potential bug with registering gesture target in case when previous gesture target was GC-ed, but gesture instance was reused to set a new target --- src/org/gestouch/core/GesturesManager.as | 44 ++++++++++++++++-------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index b92dbfe..757d12a 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -1,5 +1,6 @@ package org.gestouch.core { + import flash.errors.IllegalOperationError; import org.gestouch.gestures.Gesture; import org.gestouch.input.NativeInputAdapter; @@ -68,18 +69,27 @@ 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) + { + if (targetGestures.indexOf(gesture) == -1) + { + targetGestures.push(gesture); + } + } + else { targetGestures = _gesturesForTargetMap[target] = new Vector.(); + targetGestures[0] = gesture; } - targetGestures.push(gesture); + _gesturesMap[gesture] = true; @@ -110,23 +120,27 @@ package org.gestouch.core var target:Object = gesture.target; - var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; - if (targetGestures.length > 1) + // 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]; - if (target is IEventDispatcher) + var targetGestures:Vector. = _gesturesForTargetMap[target] as Vector.; + if (targetGestures.length > 1) { - (target as IEventDispatcher).removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler); + 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(); } From 2d52729f7c7d273e028feb6de4854a9bb3c3c0f6 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 4 Jul 2012 21:36:55 +0300 Subject: [PATCH 57/87] Update "requireGestureToFail" API implementation --- src/org/gestouch/gestures/Gesture.as | 40 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 777deb6..39ad2aa 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -299,6 +299,11 @@ package org.gestouch.gestures public function requireGestureToFail(gesture:Gesture):void { //TODO + if (!gesture) + { + throw new ArgumentError(); + } + _gesturesToFail[gesture] = true; } @@ -406,14 +411,16 @@ package org.gestouch.gestures 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 @@ -421,7 +428,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) { @@ -430,6 +437,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, @@ -528,12 +542,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); } } @@ -557,10 +565,7 @@ package org.gestouch.gestures 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) @@ -575,13 +580,18 @@ 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); } } From 963c66024ef81cd9f3354089b9ef6d2b11008387 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 4 Jul 2012 21:40:55 +0300 Subject: [PATCH 58/87] Update README --- README.textile | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/README.textile b/README.textile index 9014360..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,7 +23,7 @@ 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... +* 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 * Extendable. You can write your own application-specific gestures @@ -70,23 +70,32 @@ 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 one 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 detect gestures with Starling do following: +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 */
 
-var gesturesManager:IGesturesManager = GesturesManager.getInstance();
-// Register instance of StarlingDisplayListAdapter to be used for objects of type starling.display.DisplayObject.
-// What StarlingDisplayListAdapter 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().
-gesturesManager.addDisplayListAdapter(starling.display.DisplayObject, new StarlingDisplayListAdapter());
+// Gestouch initialization step 1 of 3:
+// Initialize native (default) input adapter. Needed for non-DisplayList usage.
+Gestouch.inputAdapter ||= new NativeInputAdapter(stage);
 
-// Initialize and register StarlingInputAdapter.
-// What StarlingInputAdapter does: populates library with touches for Starling "layer" 
-gesturesManager.addInputAdapter(new StarlingInputAdapter(starling));
+// 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 way: +Now you can register gesture in familiar, exactly same way:
var tap:TapGesture = new TapGesture(starlingSprite);
From 6273cc33e693cea2b9fc0b0bd2b8ff3965bde67f Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 4 Jul 2012 21:55:34 +0300 Subject: [PATCH 59/87] Bumped version to 0.4-beta --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 5cfc482..df63d65 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -project.version = 0.4-alpha \ No newline at end of file +project.version = 0.4-beta \ No newline at end of file From 2e02b13581a68ba508cb4b80cb738c787a678020 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 4 Jul 2012 23:43:01 +0300 Subject: [PATCH 60/87] Improved early failing strategy implementation as in iOS UIGestureRecognizers. Also fixes bugs with new validating state machine. --- src/org/gestouch/gestures/Gesture.as | 13 +++++++++++++ src/org/gestouch/gestures/LongPressGesture.as | 9 +-------- src/org/gestouch/gestures/PanGesture.as | 5 ++--- src/org/gestouch/gestures/RotateGesture.as | 3 +-- src/org/gestouch/gestures/SwipeGesture.as | 3 +-- src/org/gestouch/gestures/TapGesture.as | 4 +--- src/org/gestouch/gestures/TransformGesture.as | 3 +-- src/org/gestouch/gestures/ZoomGesture.as | 3 +-- 8 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 39ad2aa..2772cb8 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -367,6 +367,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.

diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 4781495..3e84901 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -84,14 +84,7 @@ package org.gestouch.gestures { if (touchesCount > numTouchesRequired) { - if (state == GestureState.BEGAN || state == GestureState.CHANGED) - { - ignoreTouch(touch); - } - else - { - setState(GestureState.FAILED); - } + failOrIgnoreTouch(touch); return; } diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index 0d1eea5..c4c53d8 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -116,15 +116,14 @@ package org.gestouch.gestures { if (touchesCount > maxNumTouchesRequired) { - //TODO - ignoreTouch(touch); + failOrIgnoreTouch(touch); return; } if (touchesCount >= minNumTouchesRequired) { updateLocation(); - } + } } diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 0d1eb39..0c6fc05 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -60,8 +60,7 @@ package org.gestouch.gestures { if (touchesCount > 2) { - //TODO - ignoreTouch(touch); + failOrIgnoreTouch(touch); return; } diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 3d00c42..af53390 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -78,8 +78,7 @@ package org.gestouch.gestures { if (touchesCount > numTouchesRequired) { - //TODO: or ignore? - setState(GestureState.FAILED); + failOrIgnoreTouch(touch); return; } diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index f7fbdb1..962d684 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -93,9 +93,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; } diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index c4f3433..c33cd22 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -66,8 +66,7 @@ package org.gestouch.gestures { if (touchesCount > 2) { - //TODO: to ignore or to keep this touch somewhere? - ignoreTouch(touch); + failOrIgnoreTouch(touch); return; } diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 22f9d2e..72e122d 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -60,8 +60,7 @@ package org.gestouch.gestures { if (touchesCount > 2) { - //TODO - ignoreTouch(touch); + failOrIgnoreTouch(touch); return; } From 37f6220eb53761ed2916acdf4ec244929606dc24 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 4 Jul 2012 23:44:16 +0300 Subject: [PATCH 61/87] Changed default PanGesture#maxNumTouchesRequired to uint.MAX_VALUE --- src/org/gestouch/gestures/PanGesture.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index c4c53d8..812044a 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -38,7 +38,7 @@ package org.gestouch.gestures /** @private */ - private var _maxNumTouchesRequired:uint = 1; + private var _maxNumTouchesRequired:uint = uint.MAX_VALUE; /** * From c2d31b743bbe0821d92dd815fc90f2b2b0071f83 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Thu, 5 Jul 2012 10:17:53 +0300 Subject: [PATCH 62/87] Fix Stage + Starling gestures simultaneous recognition --- src/org/gestouch/core/DisplayListAdapter.as | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/org/gestouch/core/DisplayListAdapter.as b/src/org/gestouch/core/DisplayListAdapter.as index 77516f2..9818abc 100644 --- a/src/org/gestouch/core/DisplayListAdapter.as +++ b/src/org/gestouch/core/DisplayListAdapter.as @@ -1,5 +1,6 @@ package org.gestouch.core { + import flash.display.Stage; import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.geom.Point; @@ -43,6 +44,10 @@ package org.gestouch.core 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) { From 0555813e2596c8bb14c6a3566d2171d23d8d3a0e Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Mon, 9 Jul 2012 16:47:09 +0300 Subject: [PATCH 63/87] Hotfix for gesture state machine validation --- src/org/gestouch/core/GestureState.as | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/gestouch/core/GestureState.as b/src/org/gestouch/core/GestureState.as index afb2451..eca6adc 100644 --- a/src/org/gestouch/core/GestureState.as +++ b/src/org/gestouch/core/GestureState.as @@ -57,6 +57,7 @@ package org.gestouch.core CHANGED.setValidNextStates(CHANGED, ENDED, CANCELLED); ENDED.setValidNextStates(IDLE); FAILED.setValidNextStates(IDLE); + CANCELLED.setValidNextStates(IDLE); allStatesInitialized = true; } From fd55468579d05f6a7c3643187b17d3262ec10810 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 10 Jul 2012 17:11:30 +0300 Subject: [PATCH 64/87] Improve internal algorithm for RotateGesture --- src/org/gestouch/gestures/RotateGesture.as | 59 +++++++++++++--------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 0c6fc05..89afa95 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -21,11 +21,12 @@ 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:Object = null) @@ -73,6 +74,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; } } @@ -82,38 +86,43 @@ 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) + // TODO: switch to radians everywhere + rotation *= GestureUtils.RADIANS_TO_DEGREES; + _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)); } } } From f8149935dbc13be9eb2e94f5122618e9e04c163a Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 10 Jul 2012 17:11:46 +0300 Subject: [PATCH 65/87] Improve internal algorithm for ZoomGesture --- src/org/gestouch/gestures/ZoomGesture.as | 87 +++++++++++++----------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 72e122d..4cc8239 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -13,19 +13,18 @@ package org.gestouch.gestures */ [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:Object = null) @@ -73,6 +72,7 @@ package org.gestouch.gestures _touch2 = touch; _transformVector = _touch2.location.subtract(_touch1.location); + _initialDistance = _transformVector.length; } } @@ -82,48 +82,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)); } } } @@ -144,7 +155,7 @@ package org.gestouch.gestures else if (state == GestureState.POSSIBLE) { setState(GestureState.FAILED); - } + } } else//== 1 { From 7be7c8c40a639ba1ad3678f4404a36d3b7b513f3 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 11 Jul 2012 13:28:43 +0300 Subject: [PATCH 66/87] Improve internal algorithm for TransformGesture --- src/org/gestouch/gestures/TransformGesture.as | 84 +++++++++++-------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index c33cd22..4fe1aba 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -99,51 +99,61 @@ 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); + rotation *= GestureUtils.RADIANS_TO_DEGREES; + 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 = 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)); - } - } - 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 = 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)); - } + // 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)); } } } From 850ed9849f2f78ffc9b01ed42b55fc7ea09226a0 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 11 Jul 2012 13:34:50 +0300 Subject: [PATCH 67/87] Change rotation values from degrees to radians --- src/org/gestouch/gestures/RotateGesture.as | 2 -- src/org/gestouch/gestures/TransformGesture.as | 1 - 2 files changed, 3 deletions(-) diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 89afa95..4fd1791 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -102,8 +102,6 @@ package org.gestouch.gestures rotation = rotation > 0 ? rotation - _thresholdAngle : rotation + _thresholdAngle; } - // TODO: switch to radians everywhere - rotation *= GestureUtils.RADIANS_TO_DEGREES; _transformVector.x = currTransformVector.x; _transformVector.y = currTransformVector.y; diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index 4fe1aba..b596623 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -128,7 +128,6 @@ package org.gestouch.gestures if (_touch2) { 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); } From 4a8e6feada2f738c187348f8a55b00eb1afd6a7a Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 11 Jul 2012 22:47:55 +0300 Subject: [PATCH 68/87] Improve internal algorithm for SwipeGesture but still under question --- src/org/gestouch/gestures/SwipeGesture.as | 40 ++++++++++++----------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index af53390..9dd675b 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -1,5 +1,6 @@ package org.gestouch.gestures { + import org.gestouch.utils.GestureUtils; import org.gestouch.core.GestureState; import org.gestouch.core.Touch; import org.gestouch.events.SwipeGestureEvent; @@ -20,12 +21,13 @@ package org.gestouch.gestures */ public class SwipeGesture extends Gesture { + private static const ANGLE:Number = 30 * GestureUtils.DEGREES_TO_RADIANS; + public var slop:Number = Gesture.DEFAULT_SLOP; public var numTouchesRequired:uint = 1; - public var minVelocity:Number = 0.8; + public var minVelocity:Number = 0.6; public var minOffset:Number = Gesture.DEFAULT_SLOP; public var direction:uint = SwipeGestureDirection.ORTHOGONAL; - public var maxDirectionalOffset:Number = Gesture.DEFAULT_SLOP << 1; protected var _offset:Point = new Point(); protected var _startTime:int; @@ -103,6 +105,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(); @@ -110,28 +116,27 @@ 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 + // no need in processing yet - 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) + if (avrgVel * 0.95 < _prevAvrgVel.length) { _decelerationCounter++; } - if (_decelerationCounter > 5 || avrgVel < 0.1) + // We should quickly fail if we have noticable deceleration + // or average velocity is too low + if (_decelerationCounter > 5 || avrgVel < 0.05) { setState(GestureState.FAILED); return; @@ -139,9 +144,6 @@ package org.gestouch.gestures 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 (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) @@ -169,9 +171,9 @@ package org.gestouch.gestures // 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) + else 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 @@ -197,9 +199,9 @@ package org.gestouch.gestures // 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) + else 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 @@ -221,7 +223,7 @@ package org.gestouch.gestures setState(GestureState.FAILED); } } - } + } override protected function onTouchEnd(touch:Touch):void From c2125a06e1c5a1817451cbe95dc4979496678e26 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Thu, 12 Jul 2012 12:46:11 +0300 Subject: [PATCH 69/87] Remove unnecessary imports --- src/org/gestouch/gestures/RotateGesture.as | 1 - src/org/gestouch/gestures/TransformGesture.as | 1 - 2 files changed, 2 deletions(-) diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 4fd1791..73f09fc 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -3,7 +3,6 @@ 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.geom.Point; diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index b596623..aff0cf9 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -3,7 +3,6 @@ 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.geom.Point; From ed2efc19541a9e96f38cf6dbbce94ef8ca81f16f Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 13 Jul 2012 16:51:06 +0300 Subject: [PATCH 70/87] Improve internal algorithm for SwipeGesture --- src/org/gestouch/gestures/SwipeGesture.as | 155 +++++++++++++++------- 1 file changed, 105 insertions(+), 50 deletions(-) diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 9dd675b..6063956 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -1,11 +1,14 @@ package org.gestouch.gestures { - import org.gestouch.utils.GestureUtils; import org.gestouch.core.GestureState; import org.gestouch.core.Touch; import org.gestouch.events.SwipeGestureEvent; + import org.gestouch.utils.GestureUtils; + import flash.events.TimerEvent; import flash.geom.Point; + import flash.system.Capabilities; + import flash.utils.Timer; /** @@ -14,27 +17,68 @@ package org.gestouch.gestures */ [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 { - private static const ANGLE:Number = 30 * GestureUtils.DEGREES_TO_RADIANS; + 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; - public var slop:Number = Gesture.DEFAULT_SLOP; + /** + * "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.6; - public var minOffset:Number = Gesture.DEFAULT_SLOP; public var direction:uint = SwipeGestureDirection.ORTHOGONAL; + /** + * 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:Object = null) @@ -56,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(); } @@ -76,6 +120,15 @@ package org.gestouch.gestures // // -------------------------------------------------------------------------- + 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) @@ -88,6 +141,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) { @@ -117,34 +174,14 @@ package org.gestouch.gestures _offset.y = _centralPoint.y - _location.y; var offsetLength:Number = _offset.length; - if (offsetLength < slop) - { - // no need in processing yet - we're in the very beginning of movement - return; - } - // average velocity (total offset to total duration) - _prevAvrgVel.x = _avrgVel.x; - _prevAvrgVel.y = _avrgVel.y; _avrgVel.x = _offset.x / totalTime; _avrgVel.y = _offset.y / totalTime; var avrgVel:Number = _avrgVel.length; - if (avrgVel * 0.95 < _prevAvrgVel.length) - { - _decelerationCounter++; - } - // We should quickly fail if we have noticable deceleration - // or average velocity is too low - if (_decelerationCounter > 5 || avrgVel < 0.05) - { - setState(GestureState.FAILED); - return; - } - if (_noDirection) { - if (avrgVel >= minVelocity && (minOffset != minOffset || offsetLength >= minOffset)) + if (avrgVel >= minVelocity || offsetLength >= minOffset) { if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) { @@ -166,20 +203,20 @@ 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) || + 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)) @@ -194,20 +231,20 @@ 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) || + 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)) @@ -218,7 +255,8 @@ package org.gestouch.gestures } } } - else + // Give some tolerance for accidental offset on finger press (slop) + else if (offsetLength > slop || slop != slop)//faster isNaN() { setState(GestureState.FAILED); } @@ -244,5 +282,22 @@ package org.gestouch.gestures _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 From c5f91e6de1b11da28ce9798c7f2be01156fb65df Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 13 Jul 2012 18:08:07 +0300 Subject: [PATCH 71/87] Improve internal algorithm for TapGesture --- src/org/gestouch/gestures/TapGesture.as | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index 962d684..971e8a2 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -14,7 +14,6 @@ package org.gestouch.gestures */ [Event(name="gestureTap", type="org.gestouch.events.TapGestureEvent")] /** - * TODO: check failing conditions (iDevice) * * @author Pavel fljot */ @@ -22,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; @@ -50,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 && @@ -106,8 +105,8 @@ package org.gestouch.gestures if (touchesCount == numTouchesRequired) { - _numTouchesRequiredReached = true; - updateLocation(); + _numTouchesRequiredReached = true; + updateLocation(); } } @@ -125,7 +124,6 @@ package org.gestouch.gestures { if (!_numTouchesRequiredReached) { - //TODO: check this condition on iDevice setState(GestureState.FAILED); } else if (touchesCount == 0) From 4bdefd12bba3cea720b64ebbd58d2e6b182b8d40 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 13 Jul 2012 18:27:20 +0300 Subject: [PATCH 72/87] Minor cleanup in LongPressGesture --- src/org/gestouch/gestures/LongPressGesture.as | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 3e84901..065713e 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -14,8 +14,8 @@ package org.gestouch.gestures */ [Event(name="gestureLongPress", type="org.gestouch.events.LongPressGestureEvent")] /** - * TODO: -location - * - check on iOS (Obj-C) what happens when numTouchesRequired=2, two finger down, then quickly release one. + * TODO: + * - add numTapsRequired * * @author Pavel fljot */ @@ -25,9 +25,9 @@ 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; @@ -118,7 +118,6 @@ package org.gestouch.gestures override protected function onTouchEnd(touch:Touch):void { - //TODO: check proper condition (behavior) on iOS native if (_numTouchesRequiredReached) { if (state == GestureState.BEGAN || state == GestureState.CHANGED) @@ -141,7 +140,7 @@ package org.gestouch.gestures } } - + override protected function onDelayedRecognize():void { if (hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) From eab6cb4a1c2bd4e121c14444bed741575d3c9236 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 20 Jul 2012 14:47:52 +0300 Subject: [PATCH 73/87] Remove unnecessary casting --- src/org/gestouch/core/GesturesManager.as | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 757d12a..17fe236 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -243,7 +243,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))) { @@ -259,7 +259,7 @@ 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) { @@ -285,7 +285,7 @@ package org.gestouch.core var i:int = gesturesForTouch.length; while (i-- > 0) { - gesture = gesturesForTouch[i] as Gesture; + gesture = gesturesForTouch[i]; if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) { @@ -312,7 +312,7 @@ package org.gestouch.core var i:int = gesturesForTouch.length; while (i-- > 0) { - gesture = gesturesForTouch[i] as Gesture; + gesture = gesturesForTouch[i]; if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) { From 7278e863d46d69756970f9b9f3fa26e6234a7d2d Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 20 Jul 2012 14:48:52 +0300 Subject: [PATCH 74/87] Refactor dirty gestures reset --- src/org/gestouch/core/GesturesManager.as | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 17fe236..658496a 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -53,7 +53,7 @@ package org.gestouch.core protected function resetDirtyGestures():void { - for (var gesture:* in _dirtyGesturesMap) + for (var gesture:Object in _dirtyGesturesMap) { (gesture as Gesture).reset(); } @@ -193,11 +193,6 @@ package org.gestouch.core gestouch_internal function onTouchBegin(touch:Touch):void { - if (_dirtyGesturesCount > 0) - { - resetDirtyGestures(); - } - var gesture:Gesture; var i:uint; @@ -261,7 +256,7 @@ package org.gestouch.core { 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.touchBeginHandler(touch); } @@ -275,11 +270,6 @@ package org.gestouch.core gestouch_internal function onTouchMove(touch:Touch):void { - if (_dirtyGesturesCount > 0) - { - resetDirtyGestures(); - } - var gesturesForTouch:Vector. = _gesturesForTouchMap[touch] as Vector.; var gesture:Gesture; var i:int = gesturesForTouch.length; @@ -287,7 +277,7 @@ package org.gestouch.core { gesture = gesturesForTouch[i]; - if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) + if (!_dirtyGesturesMap[gesture] && gesture.isTrackingTouch(touch.id)) { gesture.touchMoveHandler(touch); } @@ -302,11 +292,6 @@ package org.gestouch.core gestouch_internal function onTouchEnd(touch:Touch):void { - if (_dirtyGesturesCount > 0) - { - resetDirtyGestures(); - } - var gesturesForTouch:Vector. = _gesturesForTouchMap[touch] as Vector.; var gesture:Gesture; var i:int = gesturesForTouch.length; @@ -314,7 +299,7 @@ package org.gestouch.core { gesture = gesturesForTouch[i]; - if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id)) + if (!_dirtyGesturesMap[gesture] && gesture.isTrackingTouch(touch.id)) { gesture.touchEndHandler(touch); } From 7bacbf087b9368221c363597f95d689acf038daa Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 20 Jul 2012 14:49:48 +0300 Subject: [PATCH 75/87] Minor changes --- src/org/gestouch/core/GesturesManager.as | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 658496a..059b62e 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -200,20 +200,22 @@ package org.gestouch.core var gesturesForTouch:Vector. = _gesturesForTouchMap[touch] as Vector.; if (!gesturesForTouch) { - gesturesForTouch = _gesturesForTouchMap[touch] = new Vector.(); + 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); - const hierarchy:Vector. = displayListAdapter.getHierarchy(target); - if (!hierarchy) + 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) { From 7bfb1fae365a1bee549876a156a02bd45326fef5 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 20 Jul 2012 14:52:18 +0300 Subject: [PATCH 76/87] Minor type fix --- src/org/gestouch/core/GesturesManager.as | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 059b62e..77b4d96 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -274,7 +274,7 @@ package org.gestouch.core { 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]; @@ -296,7 +296,7 @@ package org.gestouch.core { 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]; From 9c817b7472cbf441bf75b2ef0ffc3c0ee9c9be8e Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Wed, 1 Aug 2012 19:41:58 +0300 Subject: [PATCH 77/87] Implement touch cancelation handling --- src/org/gestouch/core/GesturesManager.as | 15 +++++++++++- src/org/gestouch/gestures/Gesture.as | 29 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as index 77b4d96..1820be0 100644 --- a/src/org/gestouch/core/GesturesManager.as +++ b/src/org/gestouch/core/GesturesManager.as @@ -313,7 +313,20 @@ package org.gestouch.core 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 } diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 2772cb8..261f440 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -407,6 +407,14 @@ package org.gestouch.gestures } + /** + * + */ + protected function onTouchCancel(touch:Touch):void + { + } + + protected function setState(newState:GestureState):Boolean { if (_state == newState && _state == GestureState.CHANGED) @@ -576,6 +584,27 @@ 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 (!_pendingRecognizedState || state != GestureState.POSSIBLE) From a3b618e90a86d54984b89346aedcd956992f750c Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 7 Aug 2012 17:38:10 +0300 Subject: [PATCH 78/87] Move native adapters to extensions package --- src/org/gestouch/core/Gestouch.as | 8 ++++++-- src/org/gestouch/core/TouchesManager.as | 17 ----------------- .../native/NativeDisplayListAdapter.as} | 11 ++++++----- .../extensions/native/NativeTouchHitTester.as | 19 +++++++++++++++++++ 4 files changed, 31 insertions(+), 24 deletions(-) rename src/org/gestouch/{core/DisplayListAdapter.as => extensions/native/NativeDisplayListAdapter.as} (90%) create mode 100644 src/org/gestouch/extensions/native/NativeTouchHitTester.as diff --git a/src/org/gestouch/core/Gestouch.as b/src/org/gestouch/core/Gestouch.as index 11e77ce..718f811 100644 --- a/src/org/gestouch/core/Gestouch.as +++ b/src/org/gestouch/core/Gestouch.as @@ -1,8 +1,11 @@ package org.gestouch.core { - import flash.utils.getQualifiedClassName; + import org.gestouch.extensions.native.NativeDisplayListAdapter; + import org.gestouch.extensions.native.NativeTouchHitTester; + import flash.display.DisplayObject; import flash.utils.Dictionary; + import flash.utils.getQualifiedClassName; /** @@ -115,7 +118,8 @@ package org.gestouch.core private static function initClass():void { - addDisplayListAdapter(DisplayObject, new DisplayListAdapter()); + addTouchHitTester(new NativeTouchHitTester()); + addDisplayListAdapter(DisplayObject, new NativeDisplayListAdapter()); } } } diff --git a/src/org/gestouch/core/TouchesManager.as b/src/org/gestouch/core/TouchesManager.as index f3645d5..fb8b262 100644 --- a/src/org/gestouch/core/TouchesManager.as +++ b/src/org/gestouch/core/TouchesManager.as @@ -23,8 +23,6 @@ package org.gestouch.core public function TouchesManager(gesturesManager:GesturesManager) { _gesturesManager = gesturesManager; - - addTouchHitTester(new DefaultTouchHitTester()); } @@ -224,18 +222,3 @@ package org.gestouch.core } } } - - -import flash.geom.Point; -import flash.display.InteractiveObject; - -import org.gestouch.core.ITouchHitTester; - - -class DefaultTouchHitTester implements ITouchHitTester -{ - public function hitTest(point:Point, nativeTarget:InteractiveObject):Object - { - return nativeTarget; - } -} diff --git a/src/org/gestouch/core/DisplayListAdapter.as b/src/org/gestouch/extensions/native/NativeDisplayListAdapter.as similarity index 90% rename from src/org/gestouch/core/DisplayListAdapter.as rename to src/org/gestouch/extensions/native/NativeDisplayListAdapter.as index 9818abc..1498c90 100644 --- a/src/org/gestouch/core/DisplayListAdapter.as +++ b/src/org/gestouch/extensions/native/NativeDisplayListAdapter.as @@ -1,21 +1,22 @@ -package org.gestouch.core +package org.gestouch.extensions.native { - import flash.display.Stage; 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 DisplayListAdapter implements IDisplayListAdapter + final public class NativeDisplayListAdapter implements IDisplayListAdapter { private var _targetWeekStorage:Dictionary; - public function DisplayListAdapter(target:DisplayObject = null) + public function NativeDisplayListAdapter(target:DisplayObject = null) { if (target) { @@ -97,7 +98,7 @@ package org.gestouch.core public function reflect():Class { - return DisplayListAdapter; + 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; + } + } +} From 2678e12de8fb3273b57b9f38c5b73e25076c6db2 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 7 Aug 2012 17:46:12 +0300 Subject: [PATCH 79/87] Add protected from a mistyped event listening --- src/org/gestouch/gestures/Gesture.as | 20 ++++++++++++++++++- src/org/gestouch/gestures/LongPressGesture.as | 8 +++++++- src/org/gestouch/gestures/PanGesture.as | 6 ++++++ src/org/gestouch/gestures/RotateGesture.as | 6 ++++++ src/org/gestouch/gestures/SwipeGesture.as | 6 ++++++ src/org/gestouch/gestures/TapGesture.as | 6 ++++++ src/org/gestouch/gestures/TransformGesture.as | 6 ++++++ src/org/gestouch/gestures/ZoomGesture.as | 6 ++++++ 8 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 261f440..5da26ca 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -198,7 +198,18 @@ package org.gestouch.gestures // // Public methods // - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + + 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] /** @@ -546,6 +557,13 @@ package org.gestouch.gestures } + protected function eventTypeIsValid(type:String):Boolean + { + // propertyChange just in case for bindings? + return type == GestureStateEvent.STATE_CHANGE || type == "propertyChange"; + } + + //-------------------------------------------------------------------------- diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 065713e..7ef5632 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -53,7 +53,7 @@ package org.gestouch.gestures return TapGesture; } - + override public function reset():void { super.reset(); @@ -71,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(); diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index 812044a..6dbf6e2 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -112,6 +112,12 @@ 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) diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 73f09fc..1766c6a 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -56,6 +56,12 @@ 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) diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index 6063956..e464206 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -120,6 +120,12 @@ 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(); diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index 971e8a2..5d5b6a3 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -79,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(); diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index aff0cf9..909eb14 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -61,6 +61,12 @@ 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) diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 4cc8239..49f7197 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -55,6 +55,12 @@ 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) From 3acafd1dfb5208407755528eeace21eb3db8663c Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 7 Aug 2012 17:46:53 +0300 Subject: [PATCH 80/87] Minor fix for Gesture state machine to dispatch STATE_CHANGE even when cycling around CHANGED state --- src/org/gestouch/gestures/Gesture.as | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index 5da26ca..bd12afb 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -430,6 +430,12 @@ package org.gestouch.gestures { 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; } From 1eb3dfa9e10ba56dd1083f06393c6baa14eb2690 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Tue, 7 Aug 2012 17:47:43 +0300 Subject: [PATCH 81/87] Bumped version to 0.4 --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index df63d65..a01ef79 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -project.version = 0.4-beta \ No newline at end of file +project.version = 0.4 \ No newline at end of file From ca5a2efbe6d41b8fb80d66233f663a1c4d581f98 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 17 Aug 2012 17:59:08 +0300 Subject: [PATCH 82/87] Refactor GestureState (internal improvements) --- src/org/gestouch/core/GestureState.as | 59 ++++++++++++--------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/src/org/gestouch/core/GestureState.as b/src/org/gestouch/core/GestureState.as index eca6adc..5821fad 100644 --- a/src/org/gestouch/core/GestureState.as +++ b/src/org/gestouch/core/GestureState.as @@ -1,41 +1,36 @@ package org.gestouch.core { + import flash.utils.Dictionary; import flash.errors.IllegalOperationError; /** * @author Pavel fljot */ - public class GestureState + final public class GestureState { - 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(); + public static const IDLE:GestureState = new GestureState("IDLE"); + public static const POSSIBLE:GestureState = new GestureState("POSSIBLE"); + public static const RECOGNIZED:GestureState = new GestureState("RECOGNIZED", true); + public static const BEGAN:GestureState = new GestureState("BEGAN"); + public static const CHANGED:GestureState = new GestureState("CHANGED"); + public static const ENDED:GestureState = new GestureState("ENDED", true); + public static const CANCELLED:GestureState = new GestureState("CANCELLED", true); + public static const FAILED:GestureState = new GestureState("FAILED", true); private static var allStatesInitialized:Boolean; - private var value:uint; private var name:String; - private var validTransitionsBitMask:uint; + private var eventType:String; + private var validTransitionStateMap:Dictionary = new Dictionary(); { _initClass(); } - public function GestureState(value:uint, name:String) + public function GestureState(name:String, isEndState:Boolean = false) { if (allStatesInitialized) { @@ -43,8 +38,9 @@ package org.gestouch.core "Use predefined constats like GestureState.RECOGNIZED"); } - this.value = value; - this.name = name; + this.name = "GestureState." + name; + this.eventType = "gesture" + name.charAt(0).toUpperCase() + name.substr(1).toLowerCase(); + this._isEndState = isEndState; } @@ -65,36 +61,35 @@ package org.gestouch.core public function toString():String { - return "GestureState." + name; - } - - - public function toUint():uint - { - return value; + return name; } private function setValidNextStates(...states):void { - var mask:uint; for each (var state:GestureState in states) { - mask = mask | state.value; + validTransitionStateMap[state] = true; } - validTransitionsBitMask = mask; + } + + + gestouch_internal function toEventType():String + { + return eventType; } gestouch_internal function canTransitionTo(state:GestureState):Boolean { - return (validTransitionsBitMask & state.value) > 0; + return (state in validTransitionStateMap); } + private var _isEndState:Boolean = false; gestouch_internal function get isEndState():Boolean { - return (endStatesBitMask & value) > 0; + return _isEndState; } } } From 4839b1fa594d2dd6fcc39427aa81414c4a5328c8 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 17 Aug 2012 18:51:18 +0300 Subject: [PATCH 83/87] Remove custom gesture events and standardize dispatching --- src/org/gestouch/events/GestureEvent.as | 40 +++--- src/org/gestouch/events/GestureStateEvent.as | 39 ------ .../gestouch/events/LongPressGestureEvent.as | 36 ------ src/org/gestouch/events/PanGestureEvent.as | 37 ------ src/org/gestouch/events/RotateGestureEvent.as | 37 ------ src/org/gestouch/events/SwipeGestureEvent.as | 37 ------ src/org/gestouch/events/TapGestureEvent.as | 36 ------ .../gestouch/events/TransformGestureEvent.as | 53 -------- src/org/gestouch/events/ZoomGestureEvent.as | 37 ------ .../gestures/AbstractContinuousGesture.as | 44 +++++++ .../gestures/AbstractDiscreteGesture.as | 23 ++++ src/org/gestouch/gestures/Gesture.as | 115 +++++++++++++----- src/org/gestouch/gestures/LongPressGesture.as | 44 +------ src/org/gestouch/gestures/PanGesture.as | 82 ++++--------- src/org/gestouch/gestures/RotateGesture.as | 54 ++++---- src/org/gestouch/gestures/SwipeGesture.as | 58 +++------ src/org/gestouch/gestures/TapGesture.as | 30 +---- src/org/gestouch/gestures/TransformGesture.as | 110 ++++++++--------- src/org/gestouch/gestures/ZoomGesture.as | 69 +++++------ 19 files changed, 326 insertions(+), 655 deletions(-) delete mode 100644 src/org/gestouch/events/GestureStateEvent.as delete mode 100644 src/org/gestouch/events/LongPressGestureEvent.as delete mode 100644 src/org/gestouch/events/PanGestureEvent.as delete mode 100644 src/org/gestouch/events/RotateGestureEvent.as delete mode 100644 src/org/gestouch/events/SwipeGestureEvent.as delete mode 100644 src/org/gestouch/events/TapGestureEvent.as delete mode 100644 src/org/gestouch/events/TransformGestureEvent.as delete mode 100644 src/org/gestouch/events/ZoomGestureEvent.as create mode 100644 src/org/gestouch/gestures/AbstractContinuousGesture.as create mode 100644 src/org/gestouch/gestures/AbstractDiscreteGesture.as diff --git a/src/org/gestouch/events/GestureEvent.as b/src/org/gestouch/events/GestureEvent.as index 4dbdd74..b8a4e72 100644 --- a/src/org/gestouch/events/GestureEvent.as +++ b/src/org/gestouch/events/GestureEvent.as @@ -10,38 +10,40 @@ package org.gestouch.events */ public class GestureEvent extends Event { - public var gestureState:GestureState; - public var stageX:Number; - public var stageY:Number; - public var localX:Number; - public var localY:Number; + public static const GESTURE_IDLE:String = "gestureIdle"; + public static const GESTURE_POSSIBLE:String = "gesturePossible"; + public static const GESTURE_RECOGNIZED:String = "gestureRecognized"; + public static const GESTURE_BEGAN:String = "gestureBegan"; + public static const GESTURE_CHANGED:String = "gestureChanged"; + public static const GESTURE_ENDED:String = "gestureEnded"; + public static const GESTURE_CANCELLED:String = "gestureCancelled"; + public static const GESTURE_FAILED:String = "gestureFailed"; + + public static const GESTURE_STATE_CHANGE:String = "gestureStateChange"; - public function GestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:GestureState = null, - stageX:Number = 0, stageY:Number = 0, - localX:Number = 0, localY:Number = 0) + public var newState:GestureState; + public var oldState:GestureState; + + + public function GestureEvent(type:String, newState:GestureState, oldState:GestureState) { - super(type, bubbles, cancelable); + super(type, false, false); - this.gestureState = gestureState; - this.stageX = stageX; - this.stageY = stageY; - this.localX = localX; - this.localY = localY; + this.newState = newState; + this.oldState = oldState; } override public function clone():Event { - return new GestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); + return new GestureEvent(type, newState, oldState); } override public function toString():String { - return formatToString("org.gestouch.events.GestureEvent", "bubbles", "cancelable", - "gestureState", "stageX", "stageY", "localX", "localY"); + return formatToString("GestureEvent", "type", "oldState", "newState"); } } -} \ No newline at end of file +} diff --git a/src/org/gestouch/events/GestureStateEvent.as b/src/org/gestouch/events/GestureStateEvent.as deleted file mode 100644 index 29e6cea..0000000 --- a/src/org/gestouch/events/GestureStateEvent.as +++ /dev/null @@ -1,39 +0,0 @@ -package org.gestouch.events -{ - import org.gestouch.core.GestureState; - - import flash.events.Event; - - - /** - * @author Pavel fljot - */ - public class GestureStateEvent extends Event - { - public static const STATE_CHANGE:String = "stateChange"; - - public var newState:GestureState; - public var oldState:GestureState; - - - public function GestureStateEvent(type:String, newState:GestureState, oldState:GestureState) - { - super(type, false, false); - - this.newState = newState; - this.oldState = oldState; - } - - - override public function clone():Event - { - return new GestureStateEvent(type, newState, oldState); - } - - - override public function toString():String - { - return formatToString("GestureStateEvent", "type", "oldState", "newState"); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/LongPressGestureEvent.as b/src/org/gestouch/events/LongPressGestureEvent.as deleted file mode 100644 index e880ddf..0000000 --- a/src/org/gestouch/events/LongPressGestureEvent.as +++ /dev/null @@ -1,36 +0,0 @@ -package org.gestouch.events -{ - import org.gestouch.core.GestureState; - - import flash.events.Event; - - - /** - * @author Pavel fljot - */ - public class LongPressGestureEvent extends GestureEvent - { - public static const GESTURE_LONG_PRESS:String = "gestureLongPress"; - - - public function LongPressGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:GestureState = null, - stageX:Number = 0, stageY:Number = 0, - localX:Number = 0, localY:Number = 0) - { - super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); - } - - - override public function clone():Event - { - return new LongPressGestureEvent(type, bubbles, cancelable, gestureState, localX, localY); - } - - - override public function toString():String - { - return super.toString().replace("GestureEvent", "LongPressGestureEvent"); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/PanGestureEvent.as b/src/org/gestouch/events/PanGestureEvent.as deleted file mode 100644 index 1802800..0000000 --- a/src/org/gestouch/events/PanGestureEvent.as +++ /dev/null @@ -1,37 +0,0 @@ -package org.gestouch.events -{ - import org.gestouch.core.GestureState; - - import flash.events.Event; - - - /** - * @author Pavel fljot - */ - public class PanGestureEvent extends TransformGestureEvent - { - public static const GESTURE_PAN:String = "gesturePan"; - - - public function PanGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:GestureState = null, - stageX:Number = 0, stageY:Number = 0, - localX:Number = 0, localY:Number = 0, - offsetX:Number = 0, offsetY:Number = 0) - { - super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, 1, 1, 0, offsetX, offsetY); - } - - - override public function clone():Event - { - return new PanGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, offsetX, offsetY); - } - - - override public function toString():String - { - return super.toString().replace("TransformGestureEvent", "PanGestureEvent"); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/RotateGestureEvent.as b/src/org/gestouch/events/RotateGestureEvent.as deleted file mode 100644 index 2d96c4b..0000000 --- a/src/org/gestouch/events/RotateGestureEvent.as +++ /dev/null @@ -1,37 +0,0 @@ -package org.gestouch.events -{ - import org.gestouch.core.GestureState; - - import flash.events.Event; - - - /** - * @author Pavel fljot - */ - public class RotateGestureEvent extends TransformGestureEvent - { - public static const GESTURE_ROTATE:String = "gestureRotate"; - - - public function RotateGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:GestureState = null, - stageX:Number = 0, stageY:Number = 0, - localX:Number = 0, localY:Number = 0, - rotation:Number = 0) - { - super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, 1, 1, rotation); - } - - - override public function clone():Event - { - return new RotateGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, rotation); - } - - - override public function toString():String - { - return super.toString().replace("TransformGestureEvent", "RotateGestureEvent"); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/SwipeGestureEvent.as b/src/org/gestouch/events/SwipeGestureEvent.as deleted file mode 100644 index b3ac3b8..0000000 --- a/src/org/gestouch/events/SwipeGestureEvent.as +++ /dev/null @@ -1,37 +0,0 @@ -package org.gestouch.events -{ - import org.gestouch.core.GestureState; - - import flash.events.Event; - - - /** - * @author Pavel fljot - */ - public class SwipeGestureEvent extends TransformGestureEvent - { - public static const GESTURE_SWIPE:String = "gestureSwipe"; - - - public function SwipeGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:GestureState = null, - stageX:Number = 0, stageY:Number = 0, - localX:Number = 0, localY:Number = 0, - offsetX:Number = 0, offsetY:Number = 0) - { - super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, 1, 1, 0, offsetX, offsetY); - } - - - override public function clone():Event - { - return new SwipeGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, offsetX, offsetY); - } - - - override public function toString():String - { - return super.toString().replace("TransformGestureEvent", "SwipeGestureEvent"); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/TapGestureEvent.as b/src/org/gestouch/events/TapGestureEvent.as deleted file mode 100644 index 3951357..0000000 --- a/src/org/gestouch/events/TapGestureEvent.as +++ /dev/null @@ -1,36 +0,0 @@ -package org.gestouch.events -{ - import org.gestouch.core.GestureState; - - import flash.events.Event; - - - /** - * @author Pavel fljot - */ - public class TapGestureEvent extends GestureEvent - { - public static const GESTURE_TAP:String = "gestureTap"; - - - public function TapGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:GestureState = null, - stageX:Number = 0, stageY:Number = 0, - localX:Number = 0, localY:Number = 0) - { - super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); - } - - - override public function clone():Event - { - return new TapGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); - } - - - override public function toString():String - { - return super.toString().replace("GestureEvent", "TapGestureEvent"); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/TransformGestureEvent.as b/src/org/gestouch/events/TransformGestureEvent.as deleted file mode 100644 index 49942e0..0000000 --- a/src/org/gestouch/events/TransformGestureEvent.as +++ /dev/null @@ -1,53 +0,0 @@ -package org.gestouch.events -{ - import org.gestouch.core.GestureState; - - import flash.events.Event; - - - /** - * @author Pavel fljot - */ - public class TransformGestureEvent extends GestureEvent - { - public static const GESTURE_TRANSFORM:String = "gestureTransform"; - - public var scaleX:Number; - public var scaleY:Number; - public var rotation:Number; - public var offsetX:Number; - public var offsetY:Number; - - - public function TransformGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:GestureState = null, - stageX:Number = 0, stageY:Number = 0, - localX:Number = 0, localY:Number = 0, - scaleX:Number = 1.0, scaleY:Number = 1.0, - rotation:Number = 0, - offsetX:Number = 0, offsetY:Number = 0) - { - super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY); - - this.scaleX = scaleX; - this.scaleY = scaleY; - this.rotation = rotation; - this.offsetX = offsetX; - this.offsetY = offsetY; - } - - - override public function clone():Event - { - return new TransformGestureEvent(type, bubbles, cancelable, gestureState, - stageX, stageY, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY); - } - - - override public function toString():String - { - return formatToString("org.gestouch.events.TransformGestureEvent", "bubbles", "cancelable", - "gestureState", "stageX", "stageY", "localX", "localY", "scaleX", "scaleY", "offsetX", "offsetY", "rotation"); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/events/ZoomGestureEvent.as b/src/org/gestouch/events/ZoomGestureEvent.as deleted file mode 100644 index 6e23f16..0000000 --- a/src/org/gestouch/events/ZoomGestureEvent.as +++ /dev/null @@ -1,37 +0,0 @@ -package org.gestouch.events -{ - import org.gestouch.core.GestureState; - - import flash.events.Event; - - - /** - * @author Pavel fljot - */ - public class ZoomGestureEvent extends TransformGestureEvent - { - public static const GESTURE_ZOOM:String = "gestureZoom"; - - - public function ZoomGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, - gestureState:GestureState = null, - stageX:Number = 0, stageY:Number = 0, - localX:Number = 0, localY:Number = 0, - scaleX:Number = 1.0, scaleY:Number = 1.0) - { - super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, scaleX, scaleY); - } - - - override public function clone():Event - { - return new ZoomGestureEvent(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY, scaleX, scaleY); - } - - - override public function toString():String - { - return super.toString().replace("TransformGestureEvent", "ZoomGestureEvent"); - } - } -} \ No newline at end of file diff --git a/src/org/gestouch/gestures/AbstractContinuousGesture.as b/src/org/gestouch/gestures/AbstractContinuousGesture.as new file mode 100644 index 0000000..0cfc894 --- /dev/null +++ b/src/org/gestouch/gestures/AbstractContinuousGesture.as @@ -0,0 +1,44 @@ +package org.gestouch.gestures +{ + import org.gestouch.gestures.Gesture; + + + /** + * Dispatched when the state of the gesture changes to GestureState.BEGAN. + * + * @eventType org.gestouch.events.GestureEvent + * @see #state + */ + [Event(name="gestureBegan", type="org.gestouch.events.GestureEvent")] + /** + * Dispatched when the state of the gesture changes to GestureState.CHANGED. + * + * @eventType org.gestouch.events.GestureEvent + * @see #state + */ + [Event(name="gestureChanged", type="org.gestouch.events.GestureEvent")] + /** + * Dispatched when the state of the gesture changes to GestureState.ENDED. + * + * @eventType org.gestouch.events.GestureEvent + * @see #state + */ + [Event(name="gestureEnded", type="org.gestouch.events.GestureEvent")] + /** + * Dispatched when the state of the gesture changes to GestureState.CANCELLED. + * + * @eventType org.gestouch.events.GestureEvent + * @see #state + */ + [Event(name="gestureCancelled", type="org.gestouch.events.GestureEvent")] + /** + * @author Pavel fljot + */ + public class AbstractContinuousGesture extends Gesture + { + public function AbstractContinuousGesture(target:Object = null) + { + super(target); + } + } +} diff --git a/src/org/gestouch/gestures/AbstractDiscreteGesture.as b/src/org/gestouch/gestures/AbstractDiscreteGesture.as new file mode 100644 index 0000000..cb101d8 --- /dev/null +++ b/src/org/gestouch/gestures/AbstractDiscreteGesture.as @@ -0,0 +1,23 @@ +package org.gestouch.gestures +{ + import org.gestouch.gestures.Gesture; + + + /** + * Dispatched when the state of the gesture changes to GestureState.RECOGNIZED. + * + * @eventType org.gestouch.events.GestureEvent + * @see #state + */ + [Event(name="gestureRecognized", type="org.gestouch.events.GestureEvent")] + /** + * @author Pavel fljot + */ + public class AbstractDiscreteGesture extends Gesture + { + public function AbstractDiscreteGesture(target:Object = null) + { + super(target); + } + } +} diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as index bd12afb..5eab1f2 100644 --- a/src/org/gestouch/gestures/Gesture.as +++ b/src/org/gestouch/gestures/Gesture.as @@ -7,7 +7,7 @@ package org.gestouch.gestures import org.gestouch.core.IGestureTargetAdapter; import org.gestouch.core.Touch; import org.gestouch.core.gestouch_internal; - import org.gestouch.events.GestureStateEvent; + import org.gestouch.events.GestureEvent; import flash.errors.IllegalOperationError; import flash.events.EventDispatcher; @@ -19,10 +19,31 @@ package org.gestouch.gestures /** * Dispatched when the state of the gesture changes. * - * @eventType org.gestouch.events.GestureStateEvent + * @eventType org.gestouch.events.GestureEvent * @see #state */ - [Event(name="stateChange", type="org.gestouch.events.GestureStateEvent")] + [Event(name="gestureStateChange", type="org.gestouch.events.GestureEvent")] + /** + * Dispatched when the state of the gesture changes to GestureState.IDLE. + * + * @eventType org.gestouch.events.GestureEvent + * @see #state + */ + [Event(name="gestureIdle", type="org.gestouch.events.GestureEvent")] + /** + * Dispatched when the state of the gesture changes to GestureState.POSSIBLE. + * + * @eventType org.gestouch.events.GestureEvent + * @see #state + */ + [Event(name="gesturePossible", type="org.gestouch.events.GestureEvent")] + /** + * Dispatched when the state of the gesture changes to GestureState.FAILED. + * + * @eventType org.gestouch.events.GestureEvent + * @see #state + */ + [Event(name="gestureFailed", type="org.gestouch.events.GestureEvent")] /** * Base class for all gestures. Gesture is essentially a detector that tracks touch points * in order detect specific gesture motion and form gesture event on target. @@ -45,7 +66,6 @@ package org.gestouch.gestures */ protected var _touchesMap:Object = {}; protected var _centralPoint:Point = new Point(); - protected var _localLocation:Point; /** * List of gesture we require to fail. * @see requireGestureToFail() @@ -53,6 +73,8 @@ package org.gestouch.gestures protected var _gesturesToFail:Dictionary = new Dictionary(true); protected var _pendingRecognizedState:GestureState; + private var eventListeners:Dictionary = new Dictionary(); + use namespace gestouch_internal; @@ -200,14 +222,40 @@ package org.gestouch.gestures // //-------------------------------------------------------------------------- - override public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void + override public function addEventListener(type:String, listener:Function, + useCapture:Boolean = false, priority:int = 0, + useWeakReference:Boolean = false):void { - if (!eventTypeIsValid(type)) + super.addEventListener(type, listener, useCapture, priority, useWeakReference); + + const listenerProps:Array = eventListeners[listener] as Array; + if (listenerProps) { - throw new ArgumentError("Event type does not match any of allowed values."); + listenerProps.push(type, useCapture); + } + else + { + eventListeners[listener] = [type, useCapture]; + } + } + + + public function removeAllEventListeners():void + { + for (var listener:Object in eventListeners) + { + const listenerProps:Array = eventListeners[listener] as Array; + + var n:uint = listenerProps.length; + for (var i:uint = 0; i < n;) + { + super.removeEventListener(listenerProps[i++] as String, listener as Function, listenerProps[i++] as Boolean); + } + + delete eventListeners[listener]; } - super.addEventListener(type, listener, useCapture, priority, useWeakReference); +// eventListeners = new Dictionary(true); } @@ -251,7 +299,7 @@ package org.gestouch.gestures for (var key:* in _gesturesToFail) { var gestureToFail:Gesture = key as Gesture; - gestureToFail.removeEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler); + gestureToFail.removeEventListener(GestureEvent.GESTURE_STATE_CHANGE, gestureToFail_stateChangeHandler); } _pendingRecognizedState = null; @@ -286,9 +334,11 @@ package org.gestouch.gestures { //TODO reset(); + removeAllEventListeners(); target = null; delegate = null; _gesturesToFail = null; + eventListeners = null; } @@ -431,11 +481,19 @@ package org.gestouch.gestures if (_state == newState && _state == GestureState.CHANGED) { // shortcut for better performance - if (hasEventListener(GestureStateEvent.STATE_CHANGE)) + + if (hasEventListener(GestureEvent.GESTURE_STATE_CHANGE)) { - dispatchEvent(new GestureStateEvent(GestureStateEvent.STATE_CHANGE, _state, _state)); + dispatchEvent(new GestureEvent(GestureEvent.GESTURE_STATE_CHANGE, _state, _state)); } + if (hasEventListener(GestureEvent.GESTURE_CHANGED)) + { + dispatchEvent(new GestureEvent(GestureEvent.GESTURE_CHANGED, _state, _state)); + } + + resetNotificationProperties(); + return true; } @@ -479,7 +537,7 @@ package org.gestouch.gestures for (key in _gesturesToFail) { gestureToFail = key as Gesture; - gestureToFail.addEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler, false, 0, true); + gestureToFail.addEventListener(GestureEvent.GESTURE_STATE_CHANGE, gestureToFail_stateChangeHandler, false, 0, true); } return false; @@ -507,11 +565,18 @@ package org.gestouch.gestures //TODO: what if RTE happens in event handlers? - if (hasEventListener(GestureStateEvent.STATE_CHANGE)) + if (hasEventListener(GestureEvent.GESTURE_STATE_CHANGE)) { - dispatchEvent(new GestureStateEvent(GestureStateEvent.STATE_CHANGE, _state, oldState)); + dispatchEvent(new GestureEvent(GestureEvent.GESTURE_STATE_CHANGE, _state, oldState)); } + if (hasEventListener(_state.toEventType())) + { + dispatchEvent(new GestureEvent(_state.toEventType(), _state, oldState)); + } + + resetNotificationProperties(); + if (_state == GestureState.BEGAN || _state == GestureState.RECOGNIZED) { _gesturesManager.onGestureRecognized(this); @@ -548,28 +613,15 @@ package org.gestouch.gestures updateCentralPoint(); _location.x = _centralPoint.x; _location.y = _centralPoint.y; - _localLocation = targetAdapter.globalToLocal(_location); } - /** - * Executed once requiredToFail gestures have been failed and - * pending (delayed) recognized state has been entered. - * You must dispatch gesture event here. - */ - protected function onDelayedRecognize():void + protected function resetNotificationProperties():void { } - protected function eventTypeIsValid(type:String):Boolean - { - // propertyChange just in case for bindings? - return type == GestureStateEvent.STATE_CHANGE || type == "propertyChange"; - } - - //-------------------------------------------------------------------------- @@ -629,7 +681,7 @@ package org.gestouch.gestures } - protected function gestureToFail_stateChangeHandler(event:GestureStateEvent):void + protected function gestureToFail_stateChangeHandler(event:GestureEvent):void { if (!_pendingRecognizedState || state != GestureState.POSSIBLE) return; @@ -647,10 +699,7 @@ package org.gestouch.gestures } // at this point all gestures-to-fail are either in IDLE or in FAILED states - if (setState(_pendingRecognizedState)) - { - onDelayedRecognize(); - } + setState(_pendingRecognizedState); } else if (event.newState != GestureState.IDLE && event.newState != GestureState.POSSIBLE) { diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as index 7ef5632..8aa10dd 100644 --- a/src/org/gestouch/gestures/LongPressGesture.as +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -2,24 +2,18 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; import org.gestouch.core.Touch; - import org.gestouch.events.LongPressGestureEvent; import flash.events.TimerEvent; import flash.utils.Timer; - /** - * - * @eventType org.gestouch.events.LongPressGestureEvent - */ - [Event(name="gestureLongPress", type="org.gestouch.events.LongPressGestureEvent")] /** * TODO: * - add numTapsRequired * * @author Pavel fljot */ - public class LongPressGesture extends Gesture + public class LongPressGesture extends AbstractContinuousGesture { public var numTouchesRequired:uint = 1; /** @@ -50,7 +44,7 @@ package org.gestouch.gestures override public function reflect():Class { - return TapGesture; + return LongPressGesture; } @@ -71,12 +65,6 @@ 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(); @@ -113,11 +101,7 @@ package org.gestouch.gestures else if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); - if (setState(GestureState.CHANGED) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) - { - dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GestureState.CHANGED, - _location.x, _location.y, _localLocation.x, _localLocation.y)); - } + setState(GestureState.CHANGED); } } @@ -129,11 +113,7 @@ package org.gestouch.gestures if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); - if (setState(GestureState.ENDED) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) - { - dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GestureState.ENDED, - _location.x, _location.y, _localLocation.x, _localLocation.y)); - } + setState(GestureState.ENDED); } else { @@ -147,16 +127,6 @@ package org.gestouch.gestures } - override protected function onDelayedRecognize():void - { - if (hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) - { - dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GestureState.BEGAN, - _location.x, _location.y, _localLocation.x, _localLocation.y)); - } - } - - //-------------------------------------------------------------------------- @@ -170,11 +140,7 @@ package org.gestouch.gestures if (state == GestureState.POSSIBLE) { updateLocation(); - if (setState(GestureState.BEGAN) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS)) - { - dispatchEvent(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, false, false, GestureState.BEGAN, - _location.x, _location.y, _localLocation.x, _localLocation.y)); - } + setState(GestureState.BEGAN); } } } diff --git a/src/org/gestouch/gestures/PanGesture.as b/src/org/gestouch/gestures/PanGesture.as index 6dbf6e2..ea8329e 100644 --- a/src/org/gestouch/gestures/PanGesture.as +++ b/src/org/gestouch/gestures/PanGesture.as @@ -2,16 +2,10 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; import org.gestouch.core.Touch; - import org.gestouch.events.PanGestureEvent; import flash.geom.Point; - /** - * - * @eventType org.gestouch.events.PanGestureEvent - */ - [Event(name="gesturePan", type="org.gestouch.events.PanGestureEvent")] /** * TODO: * -location @@ -19,7 +13,7 @@ package org.gestouch.gestures * * @author Pavel fljot */ - public class PanGesture extends Gesture + public class PanGesture extends AbstractContinuousGesture { public var slop:Number = Gesture.DEFAULT_SLOP; /** @@ -27,9 +21,6 @@ package org.gestouch.gestures */ public var direction:uint = PanGestureDirection.NO_DIRECTION; - protected var _gestureBeginOffsetX:Number; - protected var _gestureBeginOffsetY:Number; - public function PanGesture(target:Object = null) { @@ -81,6 +72,20 @@ package org.gestouch.gestures } + protected var _offsetX:Number = 0; + public function get offsetX():Number + { + return _offsetX; + } + + + protected var _offsetY:Number = 0; + public function get offsetY():Number + { + return _offsetY; + } + + // -------------------------------------------------------------------------- @@ -94,15 +99,6 @@ package org.gestouch.gestures return PanGesture; } - - override public function reset():void - { - _gestureBeginOffsetX = NaN; - _gestureBeginOffsetY = NaN; - - super.reset(); - } - @@ -112,12 +108,6 @@ 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) @@ -140,8 +130,6 @@ package org.gestouch.gestures var prevLocationX:Number; var prevLocationY:Number; - var offsetX:Number; - var offsetY:Number; if (state == GestureState.POSSIBLE) { @@ -162,17 +150,11 @@ package org.gestouch.gestures if (locationOffset.length > slop || slop != slop)//faster isNaN(slop) { - offsetX = _location.x - prevLocationX; - offsetY = _location.y - prevLocationY; - // acummulate begin offsets for the case when this gesture recognition is delayed by requireGestureToFail - _gestureBeginOffsetX = (_gestureBeginOffsetX != _gestureBeginOffsetX) ? offsetX : _gestureBeginOffsetX + offsetX; - _gestureBeginOffsetY = (_gestureBeginOffsetY != _gestureBeginOffsetY) ? offsetY : _gestureBeginOffsetY + offsetY; + // NB! += instead of = for the case when this gesture recognition is delayed via requireGestureToFail + _offsetX += _location.x - prevLocationX; + _offsetY += _location.y - prevLocationY; - if (setState(GestureState.BEGAN) && hasEventListener(PanGestureEvent.GESTURE_PAN)) - { - dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GestureState.BEGAN, - _location.x, _location.y, _localLocation.x, _localLocation.y, offsetX, offsetY)); - } + setState(GestureState.BEGAN); } } else if (state == GestureState.BEGAN || state == GestureState.CHANGED) @@ -180,14 +162,10 @@ package org.gestouch.gestures prevLocationX = _location.x; prevLocationY = _location.y; updateLocation(); - offsetX = _location.x - prevLocationX; - offsetY = _location.y - prevLocationY; + _offsetX = _location.x - prevLocationX; + _offsetY = _location.y - prevLocationY; - if (setState(GestureState.CHANGED) && hasEventListener(PanGestureEvent.GESTURE_PAN)) - { - dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GestureState.CHANGED, - _location.x, _location.y, _localLocation.x, _localLocation.y, offsetX, offsetY)); - } + setState(GestureState.CHANGED); } } @@ -202,11 +180,7 @@ package org.gestouch.gestures } else { - if (setState(GestureState.ENDED) && hasEventListener(PanGestureEvent.GESTURE_PAN)) - { - dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GestureState.ENDED, - _location.x, _location.y, _localLocation.x, _localLocation.y, 0, 0)); - } + setState(GestureState.ENDED); } } else @@ -216,13 +190,11 @@ package org.gestouch.gestures } - override protected function onDelayedRecognize():void + override protected function resetNotificationProperties():void { - if (hasEventListener(PanGestureEvent.GESTURE_PAN)) - { - dispatchEvent(new PanGestureEvent(PanGestureEvent.GESTURE_PAN, false, false, GestureState.BEGAN, - _location.x, _location.y, _localLocation.x, _localLocation.y, _gestureBeginOffsetX, _gestureBeginOffsetY)); - } + super.resetNotificationProperties(); + + _offsetX = _offsetY = 0; } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as index 1766c6a..5d99900 100644 --- a/src/org/gestouch/gestures/RotateGesture.as +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -2,23 +2,17 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; import org.gestouch.core.Touch; - import org.gestouch.events.RotateGestureEvent; import flash.geom.Point; - /** - * - * @eventType org.gestouch.events.RotateGestureEvent - */ - [Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")] /** * TODO: * -check native behavior on iDevice * * @author Pavel fljot */ - public class RotateGesture extends Gesture + public class RotateGesture extends AbstractContinuousGesture { public var slop:Number = Gesture.DEFAULT_SLOP; @@ -34,6 +28,13 @@ package org.gestouch.gestures } + protected var _rotation:Number = 0; + public function get rotation():Number + { + return _rotation; + } + + // -------------------------------------------------------------------------- @@ -56,12 +57,6 @@ 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) @@ -109,24 +104,17 @@ package org.gestouch.gestures _transformVector.x = currTransformVector.x; _transformVector.y = currTransformVector.y; + _rotation = rotation; updateLocation(); if (state == GestureState.POSSIBLE) { - 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)); - } + setState(GestureState.BEGAN); } else { - 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)); - } + setState(GestureState.CHANGED); } } @@ -137,11 +125,7 @@ package org.gestouch.gestures { if (state == GestureState.BEGAN || state == GestureState.CHANGED) { - if (setState(GestureState.ENDED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE)) - { - dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.ENDED, - _location.x, _location.y, _localLocation.x, _localLocation.y, 0)); - } + setState(GestureState.ENDED); } else if (state == GestureState.POSSIBLE) { @@ -159,13 +143,17 @@ package org.gestouch.gestures if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); - 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, 0)); - } + setState(GestureState.CHANGED); } } } + + + override protected function resetNotificationProperties():void + { + super.resetNotificationProperties(); + + _rotation = 0; + } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as index e464206..4c77c83 100644 --- a/src/org/gestouch/gestures/SwipeGesture.as +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -2,7 +2,6 @@ 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.events.TimerEvent; @@ -11,11 +10,6 @@ package org.gestouch.gestures import flash.utils.Timer; - /** - * - * @eventType org.gestouch.events.SwipeGestureEvent - */ - [Event(name="gestureSwipe", type="org.gestouch.events.SwipeGestureEvent")] /** * Recognition logic:
* 1. should be recognized during maxDuration period
@@ -24,7 +18,7 @@ package org.gestouch.gestures * * @author Pavel fljot */ - public class SwipeGesture extends Gesture + public class SwipeGesture extends AbstractDiscreteGesture { private static const ANGLE:Number = 40 * GestureUtils.DEGREES_TO_RADIANS; private static const MAX_DURATION:uint = 500; @@ -87,6 +81,18 @@ package org.gestouch.gestures } + public function get offsetX():Number + { + return _offset.x; + } + + + public function get offsetY():Number + { + return _offset.y; + } + + // -------------------------------------------------------------------------- @@ -120,12 +126,6 @@ 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(); @@ -189,12 +189,7 @@ package org.gestouch.gestures { if (avrgVel >= minVelocity || offsetLength >= minOffset) { - if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) - { - _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)); - } + setState(GestureState.RECOGNIZED); } } else @@ -225,12 +220,7 @@ package org.gestouch.gestures else if (absVelX >= minVelocity || absOffsetX >= minOffset) { _offset.y = 0; - if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) - { - _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)); - } + setState(GestureState.RECOGNIZED); } } else if (absVelY > absVelX) @@ -253,12 +243,7 @@ package org.gestouch.gestures else if (absVelY >= minVelocity || absOffsetY >= minOffset) { _offset.x = 0; - if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) - { - _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)); - } + setState(GestureState.RECOGNIZED); } } // Give some tolerance for accidental offset on finger press (slop) @@ -279,14 +264,11 @@ package org.gestouch.gestures } - override protected function onDelayedRecognize():void + override protected function resetNotificationProperties():void { - if (hasEventListener(SwipeGestureEvent.GESTURE_SWIPE)) - { - _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)); - } + super.resetNotificationProperties(); + + _offset.x = _offset.y = 0; } diff --git a/src/org/gestouch/gestures/TapGesture.as b/src/org/gestouch/gestures/TapGesture.as index 5d5b6a3..ad44000 100644 --- a/src/org/gestouch/gestures/TapGesture.as +++ b/src/org/gestouch/gestures/TapGesture.as @@ -2,22 +2,16 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; import org.gestouch.core.Touch; - import org.gestouch.events.TapGestureEvent; import flash.events.TimerEvent; import flash.utils.Timer; - /** - * - * @eventType org.gestouch.events.TapGestureEvent - */ - [Event(name="gestureTap", type="org.gestouch.events.TapGestureEvent")] /** * * @author Pavel fljot */ - public class TapGesture extends Gesture + public class TapGesture extends AbstractDiscreteGesture { public var numTouchesRequired:uint = 1; public var numTapsRequired:uint = 1; @@ -79,12 +73,6 @@ 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(); @@ -142,11 +130,7 @@ package org.gestouch.gestures if (_tapCounter == numTapsRequired) { - if (setState(GestureState.RECOGNIZED) && hasEventListener(TapGestureEvent.GESTURE_TAP)) - { - dispatchEvent(new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GestureState.RECOGNIZED, - _location.x, _location.y, _localLocation.x, _localLocation.y)); - } + setState(GestureState.RECOGNIZED); } else { @@ -156,16 +140,6 @@ package org.gestouch.gestures } } - - override protected function onDelayedRecognize():void - { - if (hasEventListener(TapGestureEvent.GESTURE_TAP)) - { - dispatchEvent(new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GestureState.RECOGNIZED, - _location.x, _location.y, _localLocation.x, _localLocation.y)); - } - } - diff --git a/src/org/gestouch/gestures/TransformGesture.as b/src/org/gestouch/gestures/TransformGesture.as index 909eb14..2e6a20b 100644 --- a/src/org/gestouch/gestures/TransformGesture.as +++ b/src/org/gestouch/gestures/TransformGesture.as @@ -2,20 +2,14 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; import org.gestouch.core.Touch; - import org.gestouch.events.TransformGestureEvent; import flash.geom.Point; - /** - * - * @eventType org.gestouch.events.TransformGestureEvent - */ - [Event(name="gestureTransform", type="org.gestouch.events.TransformGestureEvent")] /** * @author Pavel fljot */ - public class TransformGesture extends Gesture + public class TransformGesture extends AbstractContinuousGesture { public var slop:Number = Gesture.DEFAULT_SLOP; @@ -30,6 +24,34 @@ package org.gestouch.gestures } + protected var _offsetX:Number = 0; + public function get offsetX():Number + { + return _offsetX; + } + + + protected var _offsetY:Number = 0; + public function get offsetY():Number + { + return _offsetY; + } + + + protected var _rotation:Number = 0; + public function get rotation():Number + { + return _rotation; + } + + + protected var _scale:Number = 1; + public function get scale():Number + { + return _scale; + } + + // -------------------------------------------------------------------------- @@ -43,12 +65,12 @@ package org.gestouch.gestures return TransformGesture; } - + override public function reset():void { _touch1 = null; _touch2 = null; - + super.reset(); } @@ -61,12 +83,6 @@ 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) @@ -90,11 +106,8 @@ package org.gestouch.gestures if (state == GestureState.BEGAN || state == GestureState.CHANGED) { - if (setState(GestureState.CHANGED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) - { - dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.CHANGED, - _location.x, _location.y, _localLocation.x, _localLocation.y)); - } + // notify that location (and amount of touches) has changed + setState(GestureState.CHANGED); } } @@ -125,41 +138,16 @@ package org.gestouch.gestures 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; + _offsetX = _location.x - prevLocation.x; + _offsetY = _location.y - prevLocation.y; if (_touch2) { - rotation = Math.atan2(currTransformVector.y, currTransformVector.x) - Math.atan2(_transformVector.y, _transformVector.x); - scale = currTransformVector.length / _transformVector.length; + _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)) - { - // 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)); - } - } - 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 = 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)); - } - } + setState(state == GestureState.POSSIBLE ? GestureState.BEGAN : GestureState.CHANGED); } @@ -169,11 +157,7 @@ package org.gestouch.gestures { if (state == GestureState.BEGAN || state == GestureState.CHANGED) { - if (setState(GestureState.ENDED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) - { - dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.ENDED, - _location.x, _location.y, _localLocation.x, _localLocation.y)); - } + setState(GestureState.ENDED); } else if (state == GestureState.POSSIBLE) { @@ -191,13 +175,19 @@ package org.gestouch.gestures if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); - if (setState(GestureState.CHANGED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM)) - { - dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.CHANGED, - _location.x, _location.y, _localLocation.x, _localLocation.y)); - } + setState(GestureState.CHANGED); } } } + + + override protected function resetNotificationProperties():void + { + super.resetNotificationProperties(); + + _offsetX = _offsetY = 0; + _rotation = 0; + _scale = 1; + } } } \ No newline at end of file diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as index 49f7197..cd0e2e5 100644 --- a/src/org/gestouch/gestures/ZoomGesture.as +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -2,21 +2,15 @@ package org.gestouch.gestures { import org.gestouch.core.GestureState; import org.gestouch.core.Touch; - import org.gestouch.events.ZoomGestureEvent; import flash.geom.Point; - /** - * - * @eventType org.gestouch.events.ZoomGestureEvent - */ - [Event(name="gestureZoom", type="org.gestouch.events.ZoomGestureEvent")] /** * * @author Pavel fljot */ - public class ZoomGesture extends Gesture + public class ZoomGesture extends AbstractContinuousGesture { public var slop:Number = Gesture.DEFAULT_SLOP; public var lockAspectRatio:Boolean = true; @@ -33,6 +27,20 @@ package org.gestouch.gestures } + protected var _scaleX:Number = 1; + public function get scaleX():Number + { + return _scaleX; + } + + + protected var _scaleY:Number = 1; + public function get scaleY():Number + { + return _scaleY; + } + + // -------------------------------------------------------------------------- @@ -55,12 +63,6 @@ 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) @@ -89,8 +91,6 @@ package org.gestouch.gestures return; var currTransformVector:Point = _touch2.location.subtract(_touch1.location); - var scaleX:Number; - var scaleY:Number; if (state == GestureState.POSSIBLE) { @@ -114,12 +114,13 @@ package org.gestouch.gestures if (lockAspectRatio) { - scaleX = scaleY = currTransformVector.length / _transformVector.length; + _scaleX *= currTransformVector.length / _transformVector.length; + _scaleY = _scaleX; } else { - scaleX = currTransformVector.x / _transformVector.x; - scaleY = currTransformVector.y / _transformVector.y; + _scaleX *= currTransformVector.x / _transformVector.x; + _scaleY *= currTransformVector.y / _transformVector.y; } _transformVector.x = currTransformVector.x; @@ -129,19 +130,11 @@ package org.gestouch.gestures 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)); - } + setState(GestureState.BEGAN); } 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)); - } + setState(GestureState.CHANGED); } } @@ -152,11 +145,7 @@ package org.gestouch.gestures { if (state == GestureState.BEGAN || state == GestureState.CHANGED) { - if (setState(GestureState.ENDED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM)) - { - dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.ENDED, - _location.x, _location.y, _localLocation.x, _localLocation.y, 1, 1)); - } + setState(GestureState.ENDED); } else if (state == GestureState.POSSIBLE) { @@ -174,13 +163,17 @@ package org.gestouch.gestures if (state == GestureState.BEGAN || state == GestureState.CHANGED) { updateLocation(); - 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, 1, 1)); - } + setState(GestureState.CHANGED); } } } + + + override protected function resetNotificationProperties():void + { + super.resetNotificationProperties(); + + _scaleX = _scaleY = 1; + } } } \ No newline at end of file From 4d1e467b2d04b07f43412f90d91558bcf3af1875 Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 17 Aug 2012 18:52:24 +0300 Subject: [PATCH 84/87] Remove redundant globalToLocal methods --- src/org/gestouch/core/IGestureTargetAdapter.as | 3 --- .../extensions/native/NativeDisplayListAdapter.as | 6 ------ .../extensions/starling/StarlingDisplayListAdapter.as | 9 --------- .../extensions/starling/StarlingTouchHitTester.as | 2 +- 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/org/gestouch/core/IGestureTargetAdapter.as b/src/org/gestouch/core/IGestureTargetAdapter.as index fdf70e5..c92820c 100644 --- a/src/org/gestouch/core/IGestureTargetAdapter.as +++ b/src/org/gestouch/core/IGestureTargetAdapter.as @@ -1,6 +1,5 @@ package org.gestouch.core { - import flash.geom.Point; /** * @author Pavel fljot */ @@ -8,8 +7,6 @@ package org.gestouch.core { 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/extensions/native/NativeDisplayListAdapter.as b/src/org/gestouch/extensions/native/NativeDisplayListAdapter.as index 1498c90..101a605 100644 --- a/src/org/gestouch/extensions/native/NativeDisplayListAdapter.as +++ b/src/org/gestouch/extensions/native/NativeDisplayListAdapter.as @@ -36,12 +36,6 @@ package org.gestouch.extensions.native } - 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; diff --git a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as index adc4e13..cc19f50 100644 --- a/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as +++ b/src/org/gestouch/extensions/starling/StarlingDisplayListAdapter.as @@ -1,12 +1,10 @@ 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; @@ -38,13 +36,6 @@ package org.gestouch.extensions.starling } - 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; diff --git a/src/org/gestouch/extensions/starling/StarlingTouchHitTester.as b/src/org/gestouch/extensions/starling/StarlingTouchHitTester.as index 3dbb079..9abe7fe 100644 --- a/src/org/gestouch/extensions/starling/StarlingTouchHitTester.as +++ b/src/org/gestouch/extensions/starling/StarlingTouchHitTester.as @@ -29,7 +29,7 @@ package org.gestouch.extensions.starling public function hitTest(point:Point, nativeTarget:InteractiveObject):Object { - point = StarlingUtils.adjustGlobalPoint(starling, point); + point = StarlingUtils.adjustGlobalPoint(starling, point); return starling.stage.hitTest(point, true); } } From d33c6764b60a632a1f8ce0b71db7fe1fec8b249d Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Fri, 17 Aug 2012 19:01:34 +0300 Subject: [PATCH 85/87] Update README for recent changes in API --- README.textile | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/README.textile b/README.textile index 70152a2..42e5944 100644 --- a/README.textile +++ b/README.textile @@ -41,10 +41,26 @@ And I hope people to become giving some real feedback at least. h3. Getting Started -Like so: +All gestures dispatch (if you listen) GestureEvent with the next types: +GestureEvent.GESTURE_STATE_CHANGE +GestureEvent.GESTURE_IDLE +GestureEvent.GESTURE_POSSIBLE +GestureEvent.GESTURE_FAILED + +Discrete gestures also dispatch: +GestureEvent.GESTURE_RECOGNIZED + +Continuous gestures also dispatch: +GestureEvent.GESTURE_BEGAN +GestureEvent.GESTURE_CHANGED +GestureEvent.GESTURE_ENDED + +If you use a good IDE (such as Intellij IDEA, FDT, FlashDevelop, Flash Builder) you should see these events in autocompletion. + +Quick start:
var doubleTap:TapGesture = new TapGesture(myButton);
 doubleTap.numTapsRequired = 2;
-doubleTap.addEventListener(TapGestureEvent.GESTURE_TAP, onDoubleTap);
+doubleTap.addEventListener(GestureEvent.GESTURE_RECOGNIZED, onDoubleTap);
 ...
 private function onDoubleTap(event:TapGestureEvent):void
 {
@@ -53,7 +69,8 @@ private function onDoubleTap(event:TapGestureEvent):void
 
or
var freeTransform:TransformGesture = new TransformGesture(myImage);
-freeTransform.addEventListener(TransformGestureEvent.GESTURE_TRANSFORM, onFreeTransform);
+freeTransform.addEventListener(GestureEvent.GESTURE_BEGAN, onFreeTransform);
+freeTransform.addEventListener(GestureEvent.GESTURE_CHANGED, onFreeTransform);
 ...
 private function onFreeTransform(event:TransformGestureEvent):void
 {

From 63026f362ffc48f81c621959bca19218909a6985 Mon Sep 17 00:00:00 2001
From: Pavel fljot 
Date: Sat, 25 Aug 2012 20:52:27 +0300
Subject: [PATCH 86/87] Fix README

---
 README.textile | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/README.textile b/README.textile
index 42e5944..22ce062 100644
--- a/README.textile
+++ b/README.textile
@@ -31,14 +31,6 @@ Features:
 
 
 
-h3. Current state of the project
-
-v0.3 introduces "new architecture". I'm planning to develop everything in develop branch and merge to master only release versions. Release versions suppose to be pretty stable. As much as I test them on the examples project.
-Current plan is to fix possible bugs in v0.3.#, and I really want to introduce Stage3D support in v0.4. So watch both branches.
-And I hope people to become giving some real feedback at least.
-
-
-
 h3. Getting Started
 
 All gestures dispatch (if you listen) GestureEvent with the next types:
@@ -62,7 +54,7 @@ Quick start:
 doubleTap.numTapsRequired = 2;
 doubleTap.addEventListener(GestureEvent.GESTURE_RECOGNIZED, onDoubleTap);
 ...
-private function onDoubleTap(event:TapGestureEvent):void
+private function onDoubleTap(event:GestureEvent):void
 {
 	// handle double tap!
 }
@@ -72,9 +64,10 @@ or
 freeTransform.addEventListener(GestureEvent.GESTURE_BEGAN, onFreeTransform);
 freeTransform.addEventListener(GestureEvent.GESTURE_CHANGED, onFreeTransform);
 ...
-private function onFreeTransform(event:TransformGestureEvent):void
+private function onFreeTransform(event:GestureEvent):void
 {
 	// move, rotate, scale — all at once for better performance!
+	trace(freeTransform.offsetX, freeTransform.offsetY, freeTransform.rotation, freeTransform.scale);
 }
 
From 3081c80355eb448ff48fd79f91147ec285149bdc Mon Sep 17 00:00:00 2001 From: Pavel fljot Date: Sat, 25 Aug 2012 20:52:33 +0300 Subject: [PATCH 87/87] Bumped version to 0.4.1 --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index a01ef79..bd7325d 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -project.version = 0.4 \ No newline at end of file +project.version = 0.4.1 \ No newline at end of file