Squashed commit of the following:

commit 97486ba2fe
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Mar 7 01:12:50 2012 +0200

    Bumped version to 0.3

commit 764ca1522f
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Mar 7 01:05:04 2012 +0200

    Readme updates

commit 2efa95b85c
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 6 23:28:12 2012 +0200

    Experimental requireGestureToFail API implemented

commit 4e02d4ae63
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 6 23:27:39 2012 +0200

    Reformat condition

commit 7cdec34be4
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 6 23:17:23 2012 +0200

    Swipe gesture algorithm rewritten for better recognition and failing

commit 9edcd04878
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 6 12:00:38 2012 +0200

    Tiny cleanup

commit b922057845
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 6 01:22:42 2012 +0200

    New gesture for free transformation

    more precise and performant then combination of 3

commit 5f28227c75
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 6 01:12:46 2012 +0200

    Using custom GestureEvent and TransformGestureEvent from now on

commit 06df91ce04
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 6 00:52:50 2012 +0200

    Custom GestureEvent and TransformGestureEvent

    because native one have useless phase and stupid constants

commit 398e41f610
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 6 00:51:25 2012 +0200

    TouchesManager should not clone touches

    because they must persist during touch session. also good for performance.

commit 49b1139b4f
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 6 00:50:34 2012 +0200

    Touch properties update

commit 242966790a
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Sat Mar 3 17:41:29 2012 +0200

    New gesture state

commit 4d5bef0252
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Mar 2 20:33:16 2012 +0200

    Touch properties updates (and corresponding gestures fixes)

commit b56107e059
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Thu Mar 1 23:56:45 2012 +0200

    Minor cleanup for Gesture class

commit 51e8435b2d
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Thu Mar 1 18:12:35 2012 +0200

    Input adapters initialization and disposing

commit 895e662bd5
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Feb 29 22:20:09 2012 +0200

    Minor performance fix for SwipeGesture

commit 3dcc78c267
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Feb 29 13:54:59 2012 +0200

    Added direction for PanGesture

commit 09dc1ddfc4
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Feb 21 20:14:13 2012 +0200

    Touch#time fix (affects SwipeGesture)

commit 0532f4bfbb
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Mon Feb 20 17:43:43 2012 +0200

    Moved some IGesturesManager methods under gestouch_internal namespace

commit 3ba8a3df86
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Feb 17 17:53:15 2012 +0200

    Put back automatic input adapter initialization

commit 6d8b733d51
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Feb 3 15:57:11 2012 +0200

    Moved input logic out to separate classes

commit ad767a4937
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Thu Feb 2 16:57:16 2012 +0200

    Bugfix for mouse/finger release out of stage

commit 47f2f848e4
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Dec 30 18:26:16 2011 +0200

    Optimized event dispatching

commit 3e1b5948b2
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Dec 30 16:47:52 2011 +0200

    Small optimization for PanGesture

commit d950550d16
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Dec 30 03:19:56 2011 +0200

    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.

commit 9d9fcd20ba
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Nov 22 10:40:42 2011 +0200

    Fix central point calculation for more precise transformations

commit a036db1aef
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Nov 22 02:54:11 2011 +0200

    Initial commit for the new architecture

commit 9144538e46
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Nov 1 14:10:05 2011 +0200

    Added Gesture#enabled property

commit d3ddb825b5
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Oct 25 15:19:05 2011 +0300

    Fix condition for dispatching GestureTrackingEvent.GESTURE_TRACKING_END

commit fbc4ab7422
Merge: dc489ba e508862
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Oct 25 13:47:51 2011 +0300

    Merge branch 'refs/heads/master' into develop
This commit is contained in:
Pavel fljot 2012-03-07 01:20:58 +02:00
parent e508862f42
commit 99278b8ae0
45 changed files with 2827 additions and 2055 deletions

View file

@ -1,56 +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:
<pre><code>var doubleTap:TapGesture = new TapGesture(myButton);
doubleTap.numTapsRequired = 2;
doubleTap.addEventListener(TapGestureEvent.GESTURE_TAP, onDoubleTap);
...
private function onDoubleTap(event:TapGestureEvent):void
{
// handle double tap!
}
</code></pre>
or
<pre><code>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!
}
</code></pre>
* 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
* "Gestouch Examples":http://github.com/fljot/GestouchExamples
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
* "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

View file

@ -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";
}
}

View file

@ -0,0 +1,17 @@
package org.gestouch.core
{
/**
* @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
}
}

View file

@ -1,371 +1,402 @@
package org.gestouch.core
{
import org.gestouch.events.MouseTouchEvent;
import org.gestouch.utils.ObjectPool;
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.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;
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.<IInputAdapter> = new Vector.<IInputAdapter>();
protected var _stage:Stage;
protected var _gestures:Vector.<IGesture> = new Vector.<IGesture>();
protected var _currGestures:Vector.<IGesture> = new Vector.<IGesture>();
/**
* Maps (Dictionary[target] = gesture) by gesture type.
*/
protected var _gestureMapsByType:Dictionary = new Dictionary();
protected var _touchPoints:Vector.<TouchPoint> = new Vector.<TouchPoint>(Multitouch.maxTouchPoints);
protected var _touchPointsPool:ObjectPool = new ObjectPool(TouchPoint);
protected var _gestures:Vector.<Gesture> = new Vector.<Gesture>();
protected var _gesturesForTouchMap:Array = [];
protected var _gesturesForTargetMap:Dictionary = new Dictionary(true);
protected var _dirtyGestures:Vector.<Gesture> = new Vector.<Gesture>();
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);
}
gestouch_internal static function removeGesture(gesture:IGesture):IGesture
public function get inputAdapters():Vector.<IInputAdapter>
{
return _impl.removeGesture(gesture);
return _inputAdapters.concat();
}
gestouch_internal static function removeGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture
public static function setImplementation(value:IGesturesManager):void
{
return _impl.removeGestureByTarget(gestureType, target);
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 cancelGesture(gesture:IGesture):void
public static function getInstance():IGesturesManager
{
_impl.cancelGesture(gesture);
if (!_instance)
{
_allowInstantiation = true;
_instance = new GesturesManager();
_allowInstantiation = false;
}
return _instance;
}
gestouch_internal static function addCurrentGesture(gesture:IGesture):void
public function addInputAdapter(inputAdapter:IInputAdapter):void
{
_impl.addCurrentGesture(gesture);
if (!inputAdapter)
{
throw new Error("Input adapter must be non null.");
}
if (_inputAdapters.indexOf(inputAdapter) > -1)
return;//TODO: throw Error or ignore?
_inputAdapters.push(inputAdapter);
inputAdapter.touchesManager = _touchesManager;
inputAdapter.gesturesManager = this;
inputAdapter.init();
}
gestouch_internal static function updateGestureTarget(gesture:IGesture, oldTarget:InteractiveObject, newTarget:InteractiveObject):void
public function removeInputAdapter(inputAdapter:IInputAdapter, dispose:Boolean = true):void
{
_impl.updateGestureTarget(gesture, oldTarget, newTarget);
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 init(stage:Stage):void
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
//--------------------------------------------------------------------------
//
// Private methods
//
//--------------------------------------------------------------------------
protected function installStage(stage:Stage):void
{
_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);
if (Multitouch.supportsTouchEvents)
{
addInputAdapter(new TouchInputAdapter(stage));
}
else
{
addInputAdapter(new MouseInputAdapter(stage));
}
}
public static function getTouchPoint(touchPointID:int):TouchPoint
protected function resetDirtyGestures():void
{
return _impl.getTouchPoint(touchPointID);
for each (var gesture:Gesture in _dirtyGestures)
{
gesture.reset();
}
_dirtyGestures.length = 0;
_dirtyGesturesLength = 0;
_dirtyGesturesMap = new Dictionary(true);
_frameTickerShape.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
public function addGesture(gesture:IGesture):IGesture
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 IllegalOperationError("Gesture instace '" + gesture + "' is already registered.");
throw new Error("This gesture is already registered.. something wrong.");
}
var targetGestures:Vector.<Gesture> = _gesturesForTargetMap[gesture.target] as Vector.<Gesture>;
if (!targetGestures)
{
targetGestures = _gesturesForTargetMap[gesture.target] = new Vector.<Gesture>();
}
targetGestures.push(gesture);
_gestures.push(gesture);
return 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:IGesture):IGesture
gestouch_internal function removeGesture(gesture:Gesture):void
{
var index:int = _gestures.indexOf(gesture);
if (index == -1)
if (!gesture)
{
throw new IllegalOperationError("Gesture instace '" + gesture + "' is not registered.");
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.<Gesture> = _gesturesForTargetMap[target] as Vector.<Gesture>;
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)
{
_currGestures.splice(index, 1);
_gestures.splice(index, 1);
}
gesture.dispose();
return gesture;
//TODO: decide about gesture state and _dirtyGestures
}
public function removeGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture
gestouch_internal function scheduleGestureStateReset(gesture:Gesture):void
{
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;
}
public function cancelGesture(gesture:IGesture):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++;
_frameTickerShape.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
}
public function updateGestureTarget(gesture:IGesture, oldTarget:InteractiveObject, newTarget:InteractiveObject):void
gestouch_internal function onGestureRecognized(gesture:Gesture):void
{
if (!_initialized && newTarget)
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;
// 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)
{
resetDirtyGestures();
}
var gesture:Gesture;
var i:uint;
// This vector will contain active gestures for specific touch (ID) during all touch session.
var gesturesForTouch:Vector.<Gesture> = _gesturesForTouchMap[touch.id] as Vector.<Gesture>;
if (!gesturesForTouch)
{
gesturesForTouch = new Vector.<Gesture>();
_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.<Gesture>;
while (target)
{
gesturesForTarget = _gesturesForTargetMap[target] as Vector.<Gesture>;
if (gesturesForTarget)
{
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);
}
else
{
newTarget.addEventListener(Event.ADDED_TO_STAGE, target_addedToStageHandler, false, 0, true);
}
}
var gesturesOfTypeByTarget:Dictionary = _gestureMapsByType[gesture.reflect()] as Dictionary;
if (!gesturesOfTypeByTarget)
{
gesturesOfTypeByTarget = _gestureMapsByType[gesture.reflect()] = new Dictionary();
}
else if (gesturesOfTypeByTarget[newTarget])
{
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;
}
}
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
{
if (Multitouch.supportsTouchEvents)
{
return;
}
_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)
{
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.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);
gesturesForTouch.splice(i, 1);
}
}
}
protected function stage_touchMoveHandler(event:TouchEvent):void
gestouch_internal function onTouchMove(touch:Touch):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 (_dirtyGesturesLength > 0)
{
if (gesture.isTracking(tp.id))
{
gesture.onTouchMove(tp);
}
}
}
protected function stage_touchEndHandler(event:TouchEvent):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)
{
if (gesture.isTracking(tp.id))
{
gesture.onTouchEnd(tp);
}
resetDirtyGestures();
}
var i:uint = 0;
for each (gesture in _currGestures.concat())
var gesturesForTouch:Vector.<Gesture> = _gesturesForTouchMap[touch.id] as Vector.<Gesture>;
var gesture:Gesture;
var i:int = gesturesForTouch.length;
while (i-- > 0)
{
if (gesture.trackingPointsCount == 0)
gesture = gesturesForTouch[i] as Gesture;
if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id))
{
_currGestures.splice(i, 1);
gesture.gestouch_internal::touchMoveHandler(touch);
}
else
{
i++;
// gesture is no more interested in this touch (e.g. ignoreTouch was called)
gesturesForTouch.splice(i, 1);
}
}
}
gestouch_internal function onTouchEnd(touch:Touch):void
{
if (_dirtyGesturesLength > 0)
{
resetDirtyGestures();
}
var gesturesForTouch:Vector.<Gesture> = _gesturesForTouchMap[touch.id] as Vector.<Gesture>;
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::touchEndHandler(touch);
}
}
}
gestouch_internal function onTouchCancel(touch:Touch):void
{
//TODO
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
protected function gestureTarget_addedToStageHandler(event:Event):void
{
var target:DisplayObject = event.target as DisplayObject;
target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler);
if (!_stage && GesturesManager.initDefaultInputAdapter)
{
installStage(target.stage);
}
}
private function enterFrameHandler(event:Event):void
{
resetDirtyGestures();
}
}
}

View file

@ -1,29 +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.<TouchPoint>;
function get trackingPointsCount():uint;
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;
}
}

View file

@ -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;
}
}

View file

@ -1,24 +1,17 @@
package org.gestouch.core
{
import flash.display.InteractiveObject;
import flash.display.Stage;
/**
* @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 init(stage:Stage):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 updateGestureTarget(gesture:IGesture, oldTarget:InteractiveObject, newTarget:InteractiveObject):void;
function getTouchPoint(touchPointID:int):TouchPoint;
function addInputAdapter(inputAdapter:IInputAdapter):void;
function removeInputAdapter(inputAdapter:IInputAdapter, dispose:Boolean = true):void;
}
}

View file

@ -0,0 +1,14 @@
package org.gestouch.core
{
/**
* @author Pavel fljot
*/
public interface IInputAdapter
{
function set touchesManager(value:ITouchesManager):void;
function set gesturesManager(value:IGesturesManager):void;
function init():void;
function dispose():void;
}
}

View file

@ -0,0 +1,16 @@
package org.gestouch.core
{
/**
* @author Pavel fljot
*/
public interface ITouchesManager
{
function get activeTouchesCount():uint;
function createTouch():Touch;
function addTouch(touch:Touch):Touch;
function removeTouch(touch:Touch):Touch;
function getTouch(touchPointID:int):Touch;
function hasTouch(touchPointID:int):Boolean;
}
}

View file

@ -0,0 +1,127 @@
package org.gestouch.core
{
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
* 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 sizeX:Number;
public var sizeY:Number;
public var pressure:Number;
// public var lastMove:Point;
public function Touch(id:uint = 0)
{
this.id = id;
}
protected var _location:Point;
public function get location():Point
{
return _location.clone();
}
gestouch_internal function setLocation(value:Point):void
{
_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;
}
else
{
gestouch_internal::setLocation(new Point(x, y));
}
}
protected var _previousLocation:Point;
public function get previousLocation():Point
{
return _previousLocation.clone();
}
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._location = _location;
touch._beginLocation = _beginLocation;
touch.target = target;
touch.sizeX = sizeX;
touch.sizeY = sizeY;
touch.pressure = pressure;
touch._time = _time;
touch._beginTime = _beginTime;
return touch;
}
public function toString():String
{
return "Touch [id: " + id + ", location: " + location + ", ...]";
}
}
}

View file

@ -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 + ", ...]";
}
}
}

View file

@ -0,0 +1,109 @@
package org.gestouch.core
{
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
/**
* @author Pavel fljot
*/
public class TouchesManager implements ITouchesManager
{
private static var _instance:ITouchesManager;
private static var _allowInstantiation:Boolean;
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 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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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");
}
}
}

View file

@ -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);
}
}
}

View file

@ -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");
}
}
}

View file

@ -1,6 +1,6 @@
package org.gestouch.events
{
import flash.events.GestureEvent;
import flash.events.Event;
/**
@ -10,10 +10,25 @@ 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,
gestureState:uint = 0,
stageX:Number = 0, stageY:Number = 0,
localX:Number = 0, localY:Number = 0)
{
super(type, bubbles, cancelable, phase, localX, localY, ctrlKey, altKey, shiftKey);
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");
}
}
}

View file

@ -1,55 +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
{
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;
}
protected var _target:Object;
override public function get target():Object
{
return _target;
}
protected var _stageX:Number;
override public function get stageX():Number
{
return _stageX;
}
protected var _stageY:Number;
override public function get stageY():Number
{
return _stageY;
}
override public function clone():Event
{
return super.clone();
}
override public function toString():String
{
return super.toString() + " *faked";
}
}
}

View file

@ -0,0 +1,35 @@
package org.gestouch.events
{
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:uint = 0,
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");
}
}
}

View file

@ -1,6 +1,6 @@
package org.gestouch.events
{
import flash.events.TransformGestureEvent;
import flash.events.Event;
/**
@ -11,9 +11,25 @@ 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,
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, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey);
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");
}
}
}

View file

@ -1,6 +1,6 @@
package org.gestouch.events
{
import flash.events.TransformGestureEvent;
import flash.events.Event;
/**
@ -11,9 +11,25 @@ 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,
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, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey);
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");
}
}
}

View file

@ -0,0 +1,33 @@
package org.gestouch.events
{
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:uint = 0, 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");
}
}
}

View file

@ -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");
}
}
}

View file

@ -1,6 +1,6 @@
package org.gestouch.events
{
import flash.events.TransformGestureEvent;
import flash.events.Event;
/**
@ -11,9 +11,25 @@ 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,
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, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey);
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");
}
}
}

View file

@ -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).
*
* <p>Gesture-specific configuratin properties:<br/><br/>
* timeThreshold time between first touchBegin and second touchEnd events,<br/><br/>
* moveThreshold maximum allowed distance between two taps.</p>
*
*
* @author Pavel fljot
*/
public class DoubleTapGesture extends Gesture
{
/**
* Time in milliseconds between touchBegin and touchEnd events for gesture to be detected.
*
* <p>For multitouch usage this is a bit more complex then "first touchBeing and second touchEnd":
* Taps are counted once <code>minTouchPointsCount</code> 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.</p>
*
* @default 400
*/
public var timeThreshold:uint = 400;
/**
* Maximum allowed distance between two taps for gesture to be detected.
*
* @default Gesture.DEFAULT_SLOP &#42; 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();
}
}
}

View file

@ -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.
*
* <p>DragGestureEvent has 3 possible phases: GesturePhase.BEGIN, GesturePhase.UPDATE, GesturePhase.END</p>
*
* @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));
}
}
}
}

View file

@ -1,30 +1,33 @@
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.EventDispatcher;
import flash.events.GestureEvent;
import flash.events.TouchEvent;
import flash.geom.Point;
import flash.system.Capabilities;
import flash.utils.Dictionary;
[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:
* -
*
* @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
@ -33,81 +36,32 @@ package org.gestouch.gestures
*/
public static const DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI);
/**
* 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;
/**
* 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, settings:Object = null)
public function Gesture(target:InteractiveObject = null)
{
// Check if gesture reflects it's class properly
reflect();
super();
_preinit();
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,66 +85,72 @@ 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 */
protected var _enabled:Boolean = true;
/**
* Storage for the trackingPoints property.
* @default true
*/
protected var _trackingPoints:Vector.<TouchPoint> = new Vector.<TouchPoint>();
/**
* Vector of tracking touch points touch points this gesture is interested in.
*
* <p>For the most gestures these points are which on top of the target.</p>
*
* @see #isTracking()
* @see #shouldTrackPoint()
*/
public function get trackingPoints():Vector.<TouchPoint>
public function get enabled():Boolean
{
return _trackingPoints.concat();
return _enabled;
}
/**
* Amount of currently tracked touch points. Cached value of trackingPoints.length
*
* @see #trackingPoints
*/
public function get trackingPointsCount():uint
public function set enabled(value:Boolean):void
{
return _trackingPointsCount;
if (_enabled == value)
return;
_enabled = value;
//TODO
if (!_enabled && state != GestureState.IDLE)
{
setState(GestureState.CANCELLED);
reset();
}
}
protected var _state:uint = GestureState.IDLE;
public function get state():uint
{
return _state;
}
protected var _touchesCount:uint = 0;
/**
* Storage for centralPoint property.
* Amount of currently tracked touch points.
*
* @see #_touches
*/
protected var _centralPoint:TouchPoint;
public function get touchesCount():uint
{
return _touchesCount;
}
protected var _location:Point = new Point();
/**
* Virtual central touch point among all tracking touch points (geometrical center).
*
* <p>Designed for multitouch gestures, where center could be used for
* approximation or anchor. Use _adjustCentralPoint() method for updating centralPoint.</p>
*
* @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
@ -211,44 +171,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).
*
* <p>Most of the gestures check, if event.target is target or target contains event.target.</p>
*
* <p>No need to use it directly.</p>
*
* @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);
}
@ -257,23 +182,22 @@ package org.gestouch.gestures
*
* <p>Could be useful to "stop" gesture for the current interaction cycle.</p>
*/
public function cancel():void
public function reset():void
{
GesturesManager.gestouch_internal::cancelGesture(this);
}
//FIXME: proper state change?
_location.x = 0;
_location.y = 0;
_touchesMap = {};
_touchesCount = 0;
/**
* TODO: write description, decide wethere this API is good.
*/
public function pickAndContinue(gesture:IGesture):void
{
GesturesManager.gestouch_internal::addCurrentGesture(this);
for each (var tp:TouchPoint in gesture.trackingPoints)
for (var key:* in _gesturesToFail)
{
onTouchBegin(tp);
var gestureToFail:Gesture = key as Gesture;
gestureToFail.removeEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler);
}
_pendingRecognizedState = 0;
setState(GestureState.IDLE);
}
@ -284,66 +208,32 @@ 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?
}
_gesturesToFail = null;
}
[Abstract]
/**
* Internal method, used by GesturesManager.
*
* <p><b>NB!</b> This is abstract method and must be overridden.</p>
*/
public function onTouchBegin(touchPoint:TouchPoint):void
public function canBePreventedByGesture(preventingGesture:Gesture):Boolean
{
return true;
}
[Abstract]
/**
* Internal method, used by GesturesManager.
*
* <p><b>NB!</b> This is abstract method and must be overridden.</p>
*/
public function onTouchMove(touchPoint:TouchPoint):void
public function canPreventGesture(preventedGesture:Gesture):Boolean
{
}
[Abstract]
/**
* Internal method, used by GesturesManager.
*
* <p><b>NB!</b> This is abstract method and must be overridden.</p>
*/
public function onTouchEnd(touchPoint:TouchPoint):void
{
return true;
}
/**
* Internal method, used by GesturesManager. Called upon gesture is cancelled.
*
* @see #cancel()
* <b>NB! Current implementation is highly experimental!</b> See examples for more info.
*/
public function onCancel():void
public function requireGestureToFail(gesture:Gesture):void
{
_reset();
//TODO
_gesturesToFail[gesture] = true;
}
@ -357,14 +247,8 @@ package org.gestouch.gestures
/**
* First method, called in constructor.
*
* <p>Good place to put gesture configuration related code. For example (abstract):</p>
* <listing version="3.0">
minTouchPointsCount = 2;
_propertyNames.push("timeThreshold", "moveThreshold");
* </listing>
*/
protected function _preinit():void
protected function preinit():void
{
}
@ -374,214 +258,265 @@ _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.gestouch_internal::addGesture(this);
}
}
/**
* Called internally when changing the target.
*
* <p>You should remove all listeners from target here.</p>
*
* @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.gestouch_internal::removeGesture(this);
}
}
/**
* Dispatches gesture event on gesture and on target.
*
* <p>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.</p>
*
* <p>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.</p>
*
* @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):void
{
if (hasEventListener(event.type))
if (_touchesMap.hasOwnProperty(touch.id))
{
dispatchEvent(event);
delete _touchesMap[touch.id];
_touchesCount--;
}
}
[Abstract]
/**
* <p><b>NB!</b> This is abstract method and must be overridden.</p>
*/
protected function onTouchBegin(touch:Touch):void
{
}
[Abstract]
/**
* <p><b>NB!</b> This is abstract method and must be overridden.</p>
*/
protected function onTouchMove(touch:Touch):void
{
}
[Abstract]
/**
* <p><b>NB!</b> This is abstract method and must be overridden.</p>
*/
protected function onTouchEnd(touch:Touch):void
{
}
protected function setState(newState:uint):Boolean
{
if (_state == newState && _state == GestureState.CHANGED)
{
return true;
}
// event is almost always bubbles, so no point for optimization
target.dispatchEvent(event);
}
//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
//...
/**
* 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 (newState == GestureState.BEGAN || newState == GestureState.RECOGNIZED)
{
if (settings.hasOwnProperty(propertyName))
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)
{
this[propertyName] = settings[propertyName];
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);
return false;
}
}
var oldState:uint = _state;
_state = newState;
if (((GestureState.CANCELLED | GestureState.RECOGNIZED | GestureState.ENDED | GestureState.FAILED) & _state) > 0)
{
_gesturesManager.gestouch_internal::scheduleGestureStateReset(this);
}
//TODO: what if RTE happens in event handlers?
if (hasEventListener(GestureStateEvent.STATE_CHANGE))
{
dispatchEvent(new GestureStateEvent(GestureStateEvent.STATE_CHANGE, _state, oldState));
}
if (_state == GestureState.BEGAN || _state == GestureState.RECOGNIZED)
{
_gesturesManager.gestouch_internal::onGestureRecognized(this);
}
return true;
}
/**
* Saves touchPoint for tracking for the current gesture cycle.
*
* <p>If this is the first touch point, it updates _firstTouchPoint and _centralPoint.</p>
*
* @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.
*
* <p>If this is the first touch point, it updates _firstTouchPoint and _centralPoint.</p>
*
* @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 touchLocation:Point;
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;
touchLocation = (_touchesMap[int(touchID)] as Touch).location;
x += touchLocation.x;
y += touchLocation.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);
_centralPoint.x = x / _touchesCount;
_centralPoint.y = y / _touchesCount;
}
protected function _adjustCentralPoint():void
protected function updateLocation():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;
updateCentralPoint();
_location.x = _centralPoint.x;
_location.y = _centralPoint.y;
_localLocation = target.globalToLocal(_location);
}
/**
* Reset data for the current tracking (interaction) cycle.
*
* <p>Clears up _trackingPointsMap, _trackingPoints, _trackingPointsCount
* and other custom gestures-specific things.</p>
*
* <p>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)</p>
*
* @see #trackingPoints
* @see #trackingPointsCount
* @see #onCancel()
* Executed once requiredToFail gestures have been failed and
* pending (delayed) recognized state has been entered.
* You must dispatch gesture event here.
*/
protected function _reset():void
protected function onDelayedRecognize():void
{
// forget all touch points
_trackingPointsMap = {};
_trackingPoints.length = 0;
_trackingPointsCount = 0;
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
gestouch_internal function touchBeginHandler(touch:Touch):void
{
_touchesMap[touch.id] = touch;
_touchesCount++;
onTouchBegin(touch);
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);
}
}
gestouch_internal function touchMoveHandler(touch:Touch):void
{
_touchesMap[touch.id] = touch;
onTouchMove(touch);
}
gestouch_internal function touchEndHandler(touch:Touch):void
{
delete _touchesMap[touch.id];
_touchesCount--;
onTouchEnd(touch);
}
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);
}
}
}
}

View file

@ -1,149 +1,164 @@
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.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 _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 onTouchBegin(touchPoint:TouchPoint):void
override public function reset():void
{
// No need to track more points than we need
if (_trackingPointsCount == maxTouchPointsCount)
super.reset();
_numTouchesRequiredReached = false;
_timer.reset();
}
// --------------------------------------------------------------------------
//
// Protected methods
//
// --------------------------------------------------------------------------
override protected function preinit():void
{
super.preinit();
_timer = new Timer(minPressDuration, 1);
_timer.addEventListener(TimerEvent.TIMER_COMPLETE, timer_timerCompleteHandler);
}
override protected function onTouchBegin(touch:Touch):void
{
if (touchesCount > numTouchesRequired)
{
if (state == GestureState.BEGAN || state == GestureState.CHANGED)
{
ignoreTouch(touch);
}
else
{
setState(GestureState.FAILED);
}
return;
}
_trackPoint(touchPoint);
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
override protected function onTouchMove(touch:Touch):void
{
_forgetPoint(touchPoint);
var held:Boolean = (_thresholdTimer.currentCount > 0);
_thresholdTimer.reset();
if (held)
if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length > slop)
{
_updateCentralPoint();
_reset();
_dispatch(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y));
setState(GestureState.FAILED);
}
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));
}
}
}
//--------------------------------------------------------------------------
//
// Protected methods
//
//--------------------------------------------------------------------------
override protected function _preinit():void
override protected function onTouchEnd(touch:Touch):void
{
super._preinit();
_thresholdTimer = new Timer(timeThreshold, 1);
_thresholdTimer.addEventListener(TimerEvent.TIMER_COMPLETE, _onThresholdTimerComplete);
_propertyNames.push("timeThreshold", "slop");
//TODO: check proper condition (behavior) on iOS native
if (_numTouchesRequiredReached)
{
if (((GestureState.BEGAN | GestureState.CHANGED) & state) > 0)
{
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));
}
}
else
{
setState(GestureState.FAILED);
}
}
else
{
setState(GestureState.FAILED);
}
}
override protected function _reset():void
override protected function onDelayedRecognize():void
{
super._reset();
_thresholdTimer.reset();
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));
}
}
@ -155,10 +170,17 @@ package org.gestouch.gestures
//
//--------------------------------------------------------------------------
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();
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));
}
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,218 @@
package org.gestouch.gestures
{
import org.gestouch.core.GestureState;
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")]
/**
* TODO:
* -location
* -check native behavior on iDevice
*
* @author Pavel fljot
*/
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 _gestureBeginOffsetX:Number;
protected var _gestureBeginOffsetY:Number;
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
{
_gestureBeginOffsetX = NaN;
_gestureBeginOffsetY = NaN;
super.reset();
}
// --------------------------------------------------------------------------
//
// Protected methods
//
// --------------------------------------------------------------------------
override protected function onTouchBegin(touch:Touch):void
{
if (touchesCount > maxNumTouchesRequired)
{
//TODO
ignoreTouch(touch);
return;
}
if (touchesCount >= minNumTouchesRequired)
{
updateLocation();
}
}
override protected function onTouchMove(touch:Touch):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 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;
updateLocation();
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;
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));
}
}
}
else if (state == GestureState.BEGAN || state == GestureState.CHANGED)
{
prevLocationX = _location.x;
prevLocationY = _location.y;
updateLocation();
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));
}
}
}
override protected function onTouchEnd(touch:Touch):void
{
if (touchesCount < minNumTouchesRequired)
{
if (state == GestureState.POSSIBLE)
{
setState(GestureState.FAILED);
}
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));
}
}
}
else
{
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));
}
}
}
}

View file

@ -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;
}
}

View file

@ -1,68 +1,42 @@
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.geom.Point;
[Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")]
/**
* TODO:
* -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 _touch1:Touch;
protected var _touch2:Touch;
protected var _transformVector:Point;
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
{
@ -70,70 +44,113 @@ package org.gestouch.gestures
}
override public function onTouchBegin(touchPoint:TouchPoint):void
// --------------------------------------------------------------------------
//
// Protected methods
//
// --------------------------------------------------------------------------
override protected function onTouchBegin(touch:Touch):void
{
// No need to track more points than we need
if (_trackingPointsCount == maxTouchPointsCount)
if (touchesCount > 2)
{
//TODO
ignoreTouch(touch);
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;
_touch1 = touch;
}
else
{
_touch2 = touch;
_updateCentralPoint();
_dispatch(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y));
_transformVector = _touch2.location.subtract(_touch1.location);
}
}
override public function onTouchMove(touchPoint:TouchPoint):void
override protected function onTouchMove(touch:Touch):void
{
// do calculations only when we track enough points
if (_trackingPointsCount < minTouchPointsCount)
{
if (touchesCount < 2)
return;
}
_updateCentralPoint();
var recognized:Boolean = true;
_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)
if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop)
{
_dispatch(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y));
recognized = false;
}
if (recognized)
{
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))
{
dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.BEGAN,
_location.x, _location.y, _localLocation.x, _localLocation.y, rotation));
}
}
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));
}
}
}
}
override protected function _preinit():void
override protected function onTouchEnd(touch:Touch):void
{
super._preinit();
if (touchesCount == 0)
{
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));
}
}
else if (state == GestureState.POSSIBLE)
{
setState(GestureState.FAILED);
}
}
else// == 1
{
if (touch == _touch1)
{
_touch1 = _touch2;
}
_touch2 = null;
minTouchPointsCount = 2;
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));
}
}
}
}
}
}

View file

@ -1,72 +1,49 @@
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.geom.Point;
import flash.utils.getTimer;
[Event(name="gestureSwipe", type="org.gestouch.events.SwipeGestureEvent")]
/**
* SwipeGesture detects <i>swipe</i> motion (also known as <i>flick</i> or <i>flig</i>).
*
* <p>I couldn't find any certain definition of <i>Swipe</i> except for it's defined as <i>quick</i>.
* 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.</p>
* 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 slop:Number = Gesture.DEFAULT_SLOP;
public var numTouchesRequired:uint = 1;
public var minVelocity:Number = 0.8;
public var minOffset:Number = Gesture.DEFAULT_SLOP;
public var direction:uint = SwipeGestureDirection.ORTHOGONAL;
public var maxDirectionalOffset:Number = Gesture.DEFAULT_SLOP << 1;
protected var _startTime:uint;
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, 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
{
@ -74,107 +51,193 @@ package org.gestouch.gestures
}
override public function onTouchBegin(touchPoint:TouchPoint):void
override public function reset():void
{
// No need to track more points than we need
if (_trackingPointsCount == maxTouchPointsCount)
{
return;
}
_startTime = 0;
_offset.x = 0;
_offset.y = 0;
_decelerationCounter = 0;
_trackPoint(touchPoint);
super.reset();
}
override public function onTouchMove(touchPoint:TouchPoint):void
// --------------------------------------------------------------------------
//
// Protected methods
//
// --------------------------------------------------------------------------
override protected function onTouchBegin(touch:Touch):void
{
// do calculations only when we track enought points
if (_trackingPointsCount < minTouchPointsCount)
if (touchesCount > numTouchesRequired)
{
//TODO: or ignore?
setState(GestureState.FAILED);
return;
}
_updateCentralPoint();
if (!_slopPassed)
if (touchesCount == 1)
{
_slopPassed = _checkSlop(_centralPoint.moveOffset);
// Because we want to fail as quick as possible
_startTime = touch.time;
}
if (touchesCount == numTouchesRequired)
{
updateLocation();
_avrgVel.x = _avrgVel.y = 0;
// cache direction condition for performance
_noDirection = (SwipeGestureDirection.ORTHOGONAL & direction) == 0;
}
}
override protected function onTouchMove(touch:Touch):void
{
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;
if (offsetLength < slop)
{
// no need in processing - we're in the very beginning of movement
return;
}
if (_slopPassed)
// 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)
{
var velocity:Point = _centralPoint.velocity;
_decelerationCounter++;
}
if (_decelerationCounter > 5 || avrgVel < 0.1)
{
setState(GestureState.FAILED);
return;
}
var foo:Number = _centralPoint.moveOffset.length;//FIXME!
var swipeDetected:Boolean = false;
if (_noDirection)
{
// We should quickly fail if we have noticable deceleration
// or first movement happend way later after touch
if (getTimer() - _startTime > minTimeThreshold && foo > 10)
if (avrgVel >= minVelocity && (minOffset != minOffset || offsetLength >= minOffset))
{
var lastMoveX:Number = 0;
var lastMoveY:Number = 0;
if (_canMoveHorizontally && _canMoveVertically)
if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
lastMoveX = _centralPoint.lastMove.x;
lastMoveY = _centralPoint.lastMove.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));
}
}
}
else
{
var recentOffsetX:Number = _centralPoint.x - prevCentralPointX;
var recentOffsetY:Number = _centralPoint.y - prevCentralPointY;
//faster Math.abs()
var absVelX:Number = _avrgVel.x > 0 ? _avrgVel.x : -_avrgVel.x;
var absVelY:Number = _avrgVel.y > 0 ? _avrgVel.y : -_avrgVel.y;
if (direction == Direction.STRAIGHT_AXES)
{
// go to logic below: if (!swipeDetected && _canMove*)..
}
else if (direction == Direction.OCTO)
{
swipeDetected = velocity.length >= velocityThreshold;
if (absVelX > absVelY)
{
var absOffsetX:Number = _offset.x > 0 ? _offset.x : -_offset.x;
if (Math.abs(velocity.y) < sideVelocityThreshold)
{
// horizontal swipe
lastMoveY = 0;
}
else if (Math.abs(velocity.x) < sideVelocityThreshold)
{
// vertical swipe
lastMoveX = 0;
}
}
else
if ((SwipeGestureDirection.HORIZONTAL & direction) == 0)
{
// horizontal velocity is greater then vertical, but we're not interested in any horizontal direction
setState(GestureState.FAILED);
}
else if (recentOffsetX < 0 && (direction & SwipeGestureDirection.LEFT) == 0 ||
recentOffsetX > 0 && (direction & SwipeGestureDirection.RIGHT) == 0 ||
Math.abs(_offset.y) > maxDirectionalOffset)
{
// movement in opposite direction
// or too much diagonally
setState(GestureState.FAILED);
}
else if (absVelX >= minVelocity && (minOffset != minOffset || absOffsetX >= minOffset))
{
_offset.y = 0;
if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
// free direction swipe
swipeDetected = velocity.length >= velocityThreshold;
_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));
}
}
}
else if (absVelY > absVelX)
{
var absOffsetY:Number = _offset.y > 0 ? _offset.y : -_offset.y;
if (!swipeDetected && _canMoveHorizontally)
if ((SwipeGestureDirection.VERTICAL & direction) == 0)
{
swipeDetected = Math.abs(velocity.x) >= velocityThreshold &&
Math.abs(velocity.y) < sideVelocityThreshold;
lastMoveX = _centralPoint.lastMove.x;
lastMoveY = 0;
// horizontal velocity is greater then vertical, but we're not interested in any horizontal direction
setState(GestureState.FAILED);
}
if (!swipeDetected && _canMoveVertically)
else if (recentOffsetY < 0 && (direction & SwipeGestureDirection.UP) == 0 ||
recentOffsetY > 0 && (direction & SwipeGestureDirection.DOWN) == 0 ||
Math.abs(_offset.x) > maxDirectionalOffset)
{
swipeDetected = Math.abs(velocity.y) >= velocityThreshold &&
Math.abs(velocity.x) < sideVelocityThreshold;
lastMoveX = 0;
lastMoveY = _centralPoint.lastMove.y;
// movement in opposite direction
// or too much diagonally
setState(GestureState.FAILED);
}
if (swipeDetected)
else if (absVelY >= minVelocity && (minOffset != minOffset || absOffsetY >= minOffset))
{
_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));
_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, _offset.x, _offset.y));
}
}
}
else
{
setState(GestureState.FAILED);
}
}
}
override public function onTouchEnd(touchPoint:TouchPoint):void
override protected function onTouchEnd(touch:Touch):void
{
_forgetPoint(touchPoint);
if (touchesCount < numTouchesRequired)
{
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));
}
}
}
}

View file

@ -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;
}
}

View file

@ -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.TimerEvent;
import flash.utils.Timer;
[Event(name="gestureTap", type="org.gestouch.events.TapGestureEvent")]
/**
* 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 _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
{
_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):void
{
if (touchesCount > numTouchesRequired)
{
// We put more fingers then required at the same time,
// so treat that as failed
setState(GestureState.FAILED);
return;
}
if (touchesCount == 1)
{
_timer.reset();
_timer.delay = maxTapDuration;
_timer.start();
}
if (touchesCount == numTouchesRequired)
{
_numTouchesRequiredReached = true;
}
}
override protected function onTouchMove(touch:Touch):void
{
if (slop >= 0 && touch.locationOffset.length > slop)
{
setState(GestureState.FAILED);
}
}
override protected function onTouchEnd(touch:Touch):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();
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));
}
}
else
{
_timer.delay = maxTapDelay;
_timer.start();
}
}
}
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));
}
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
protected function timer_timerCompleteHandler(event:TimerEvent):void
{
if (state == GestureState.POSSIBLE)
{
setState(GestureState.FAILED);
}
}
}
}

View file

@ -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));
}
}
}
}
}
}

View file

@ -1,60 +1,42 @@
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.geom.Point;
[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 lockAspectRatio:Boolean = true;
protected var _currVector:Point = new Point();
protected var _lastVector:Point = new Point();
protected var _touch1:Touch;
protected var _touch2:Touch;
protected var _transformVector:Point;
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
{
@ -62,79 +44,123 @@ package org.gestouch.gestures
}
override public function onTouchBegin(touchPoint:TouchPoint):void
// --------------------------------------------------------------------------
//
// Protected methods
//
// --------------------------------------------------------------------------
override protected function onTouchBegin(touch:Touch):void
{
// No need to track more points than we need
if (_trackingPointsCount == maxTouchPointsCount)
if (touchesCount > 2)
{
//TODO
ignoreTouch(touch);
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;
_touch1 = touch;
}
else// == 2
{
_touch2 = touch;
_updateCentralPoint();
_dispatch(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y));
_transformVector = _touch2.location.subtract(_touch1.location);
}
}
override public function onTouchMove(touchPoint:TouchPoint):void
override protected function onTouchMove(touch:Touch):void
{
// do calculations only when we track enought points
if (_trackingPointsCount < minTouchPointsCount)
{
if (touchesCount < 2)
return;
var recognized:Boolean = true;
if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop)
{
recognized = false;
}
_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)
if (recognized)
{
scaleX = scaleY = _currVector.length / _lastVector.length;
}
else
{
scaleX = _currVector.x / _lastVector.x;
scaleY = _currVector.y / _lastVector.y;
}
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;
}
_lastVector.x = _currVector.x;
_lastVector.y = _currVector.y;
_transformVector.x = currTransformVector.x;
_transformVector.y = currTransformVector.y;
_dispatch(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, true, false, GesturePhase.UPDATE, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, scaleX, scaleY));
}
updateLocation();
override public function onTouchEnd(touchPoint:TouchPoint):void
{
var ending:Boolean = (_trackingPointsCount == minTouchPointsCount);
_forgetPoint(touchPoint);
if (ending)
{
_dispatch(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y));
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));
}
}
}
}
override protected function _preinit():void
override protected function onTouchEnd(touch:Touch):void
{
super._preinit();
if (touchesCount == 0)
{
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));
}
}
else if (state == GestureState.POSSIBLE)
{
setState(GestureState.FAILED);
}
}
else//== 1
{
if (touch == _touch1)
{
_touch1 = _touch2;
}
_touch2 = null;
minTouchPointsCount = 2;
_propertyNames.push("lockAspectRatio");
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));
}
}
}
}
}
}

View file

@ -0,0 +1,51 @@
package org.gestouch.input
{
import org.gestouch.core.IGesturesManager;
import org.gestouch.core.IInputAdapter;
import org.gestouch.core.ITouchesManager;
/**
* @author Pavel fljot
*/
public class AbstractInputAdapter implements IInputAdapter
{
protected var _touchesManager:ITouchesManager;
protected var _gesturesManager:IGesturesManager;
public function AbstractInputAdapter()
{
if (Object(this).constructor == AbstractInputAdapter)
{
throw new Error("This is abstract class and should not be directly instantiated.");
}
}
public function set touchesManager(value:ITouchesManager):void
{
_touchesManager = value;
}
public function set gesturesManager(value:IGesturesManager):void
{
_gesturesManager = value;
}
[Abstract]
public function init():void
{
throw new Error("This is abstract method.");
}
[Abstract]
public function dispose():void
{
throw new Error("This is abstract method.");
}
}
}

View file

@ -0,0 +1,133 @@
package org.gestouch.input
{
import org.gestouch.core.Touch;
import org.gestouch.core.gestouch_internal;
import flash.display.InteractiveObject;
import flash.display.Stage;
import flash.events.EventPhase;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.utils.getTimer;
/**
* @author Pavel fljot
*/
public class MouseInputAdapter extends AbstractInputAdapter
{
private static const PRIMARY_TOUCH_POINT_ID:uint = 0;
protected var _stage:Stage;
public function MouseInputAdapter(stage:Stage)
{
super();
if (!stage)
{
throw new Error("Stage must be not null.");
}
_stage = stage;
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true);
}
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
_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.gestouch_internal::setLocation(new Point(event.stageX, event.stageY));
touch.gestouch_internal::setTime(getTimer());
touch.gestouch_internal::setBeginTime(getTimer());
_touchesManager.addTouch(touch);
_gesturesManager.gestouch_internal::onTouchBegin(touch);
}
protected function mouseMoveHandler(event:MouseEvent):void
{
// Way to prevent MouseEvent/TouchEvent collisions.
// Also helps to ignore possible fake events.
if (!_touchesManager.hasTouch(PRIMARY_TOUCH_POINT_ID))
return;
var touch:Touch = _touchesManager.getTouch(PRIMARY_TOUCH_POINT_ID);
touch.gestouch_internal::updateLocation(event.stageX, event.stageY);
touch.gestouch_internal::setTime(getTimer());
_gesturesManager.gestouch_internal::onTouchMove(touch);
}
protected function mouseUpHandler(event:MouseEvent):void
{
// If event 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.gestouch_internal::updateLocation(event.stageX, event.stageY);
touch.gestouch_internal::setTime(getTimer());
_gesturesManager.gestouch_internal::onTouchEnd(touch);
_touchesManager.removeTouch(touch);
if (_touchesManager.activeTouchesCount == 0)
{
uninstallStageListeners();
}
}
}
}

View file

@ -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
}
}
}

View file

@ -0,0 +1,174 @@
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.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;
}
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();
}
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.gestouch_internal::setLocation(new Point(event.stageX, event.stageY));
touch.sizeX = event.sizeX;
touch.sizeY = event.sizeY;
touch.pressure = event.pressure;
//TODO: conditional compilation?
if (event.hasOwnProperty("timestamp"))
{
touch.gestouch_internal::setTime(event["timestamp"]);
touch.gestouch_internal::setBeginTime(event["timestamp"]);
}
else
{
touch.gestouch_internal::setTime(getTimer());
touch.gestouch_internal::setBeginTime(getTimer());
}
_touchesManager.addTouch(touch);
_touchesMap[touch.id] = true;
_gesturesManager.gestouch_internal::onTouchBegin(touch);
}
protected function touchMoveHandler(event:TouchEvent):void
{
// Way to prevent MouseEvent/TouchEvent collisions.
// Also helps to ignore possible fake events.
if (!_touchesManager.hasTouch(event.touchPointID) || !_touchesMap.hasOwnProperty(event.touchPointID))
return;
var touch:Touch = _touchesManager.getTouch(event.touchPointID);
touch.gestouch_internal::updateLocation(event.stageX, event.stageY);
touch.sizeX = event.sizeX;
touch.sizeY = event.sizeY;
touch.pressure = event.pressure;
//TODO: conditional compilation?
if (event.hasOwnProperty("timestamp"))
{
touch.gestouch_internal::setTime(event["timestamp"]);
}
else
{
touch.gestouch_internal::setTime(getTimer());
}
_gesturesManager.gestouch_internal::onTouchMove(touch);
}
protected function touchEndHandler(event:TouchEvent):void
{
// If event 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.gestouch_internal::updateLocation(event.stageX, event.stageY);
touch.sizeX = event.sizeX;
touch.sizeY = event.sizeY;
touch.pressure = event.pressure;
//TODO: conditional compilation?
if (event.hasOwnProperty("timestamp"))
{
touch.gestouch_internal::setTime(event["timestamp"]);
}
else
{
touch.gestouch_internal::setTime(getTimer());
}
_gesturesManager.gestouch_internal::onTouchEnd(touch);
_touchesManager.removeTouch(touch);
delete _touchesMap[touch.id];
if (_touchesManager.activeTouchesCount == 0)
{
uninstallStageListeners();
}
// TODO: handle cancelled touch:
// if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"] && ...
}
}
}

View file

@ -1,4 +1,4 @@
package org.gestouch
package org.gestouch.utils
{
import flash.system.Capabilities;
/**

View file

@ -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());
}
}
}
}

View file

@ -1 +1 @@
project.version = 0.2
project.version = 0.3