Merge branch 'refs/heads/features/starling' into develop

This commit is contained in:
Pavel fljot 2012-07-04 21:41:26 +03:00
commit 00040bb2e2
43 changed files with 1297 additions and 766 deletions

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<AS3Classpath>
<AS3LibraryFolder>libs</AS3LibraryFolder>
<AS3Classpath generateProblems="true" sdkBased="false" type="source" useAsSharedCode="false">src</AS3Classpath>
<AS3Classpath generateProblems="false" sdkBased="true" type="lib" useAsSharedCode="false">frameworks/libs/air/airglobal.swc</AS3Classpath>
<AS3Classpath generateProblems="false" sdkBased="true" type="lib" useAsSharedCode="false">frameworks/libs/mobile/mobilecomponents.swc</AS3Classpath>
@ -10,4 +11,5 @@
<AS3Classpath generateProblems="false" sdkBased="true" type="lib" useAsSharedCode="false">frameworks/libs/air/servicemonitor.swc</AS3Classpath>
<AS3Classpath generateProblems="false" sdkBased="true" type="lib" useAsSharedCode="false">frameworks/themes/Mobile/mobile.swc</AS3Classpath>
<AS3Classpath generateProblems="false" sdkBased="true" type="lib" useAsSharedCode="false">frameworks/libs/mx/mx.swc</AS3Classpath>
<AS3Classpath generateProblems="false" sdkBased="false" type="lib" useAsSharedCode="false">libs/starling.swc</AS3Classpath>
</AS3Classpath>

View file

@ -1,6 +1,6 @@
h1. Gestouch: NUI gestures detection framework for mouse, touch and multitouch AS3 development.
h1. Gestouch: multitouch gesture recognition library for Flash (ActionScript) development.
Gestouch is a ActionScript library/framework that helps you to deal with single- and multitouch gestures for building better NUI (Natural User Interface).
Gestouch is a ActionScript (AS3) library that helps you to deal with single- and multitouch gestures for building better NUI (Natural User Interface).
h3. Why? There's already gesture support in Flash/AIR!
@ -13,9 +13,9 @@ _Upd:_ With "native way" you also won't get anything out of Stage3D and of custo
h3. What Gestouch does in short?
Well basically there's 3 distinctive tasks to solve.
# To provide various input. It can be standard MouseEvents, TouchEvents or more complex things like custom input via TUIO protocol for your hand-made installation. So what we get here is Touches (touch points).
# To recognize gesture out of touch points. Each type of Gesture has it's own inner algorithms that ...
# To manage gestures relations. Because they may "overlap" and once some has been recognized probably we don't want other to do so.
# To provide various input. It can be native MouseEvents, TouchEvents or more complex things like custom input via TUIO protocol for your hand-made installation. So what we get here is Touches (touch points).
# To recognize gesture analyzing touches. Each type of Gesture has it's own inner algorithms that ...
# To manage gestures conflicts. As multiple gestures may be recognized simultaneously, we need to be able to control whether it's allowed or some of them should not be recognized (fail).
Gestouch solves these 3 tasks.
I was hardly inspired by Apple team, how they solved this (quite recently to my big surprise! I thought they had it right from the beginning) in they Cocoa-touch UIKit framework. Gestouch is very similar in many ways. But I wouldn't call it "direct port" because 1) the whole architecture was implemented based just on conference videos and user documentation 2) flash platform is a different platform with own specialization, needs, etc.
@ -23,12 +23,11 @@ So I want Gestouch to go far beyond that.
Features:
* Pretty neat architecture! Very similar to Apple's UIGestureRecognizers (Cocoa-Touch UIKit)
* Works with any display list hierarchy structures: native DisplayList (pure AS3/Flex/your UI framework), Starling or ND2D (Stage3D) and 3D libs...
* Doesn't require any additional software (may use runtime's build-in touch support)
* Works across all platforms (where Flash Player or AIR run of course) in exactly same way
* Doesn't break your DisplayList architecture (could be easily used for Flex development)
* Extendable. You can write your own application-specific gestures
* Open-source and free
* *_+Planning to make it work with Stage3D. Hello Starling!+_*
@ -69,9 +68,40 @@ private function onFreeTransform(event:TransformGestureEvent):void
h3. Advanced usage: Starling, ...
Recent changes made it possible to work with "Starling":http://www.starling-framework.org display list objects as well as any other display list hierarchical structures, e.g. other Stage3D frameworks that have display objects hierarchy like "ND2D":https://github.com/nulldesign/nd2d or even 3D libraries.
In order to use Gestouch with Starling do the following:
<pre><code>starling = new Starling(MyStarlingRootClass, stage);
/* setup & start your Starling instance here */
// Gestouch initialization step 1 of 3:
// Initialize native (default) input adapter. Needed for non-DisplayList usage.
Gestouch.inputAdapter ||= new NativeInputAdapter(stage);
// Gestouch initialization step 2 of 3:
// Register instance of StarlingDisplayListAdapter to be used for objects of type starling.display.DisplayObject.
// What it does: helps to build hierarchy (chain of parents) for any Starling display object and
// acts as a adapter for gesture target to provide strong-typed access to methods like globalToLocal() and contains().
Gestouch.addDisplayListAdapter(starling.display.DisplayObject, new StarlingDisplayListAdapter());
// Gestouch initialization step 3 of 3:
// Initialize and register StarlingTouchHitTester.
// What it does: finds appropriate target for the new touches (uses Starling Stage#hitTest() method)
// What does "-1" mean: priority for this hit-tester. Since Stage3D layer sits behind native DisplayList
// we give it lower priority in the sense of interactivity.
Gestouch.addTouchHitTester(new StarlingTouchHitTester(starling), -1);
// NB! Use Gestouch#removeTouchHitTester() method if you manage multiple Starling instances during
// your application lifetime.
</code></pre>
Now you can register gesture in familiar, exactly same way:
<pre><code>var tap:TapGesture = new TapGesture(starlingSprite);</code></pre>
h3. Roadmap, TODOs
* *Stage3D support.* Hello Starling! Must move away from target as InteractiveObject to some abstract adapters.
* "Massive gestures" & Clusters. For bigger form-factor multitouch usage, when gestures must be a bit less about separate fingers but rather touch clusters (massive multitouch)
* -Simulator (for testing multitouch gestures without special devices)- With new architecture it must be relatively easy to create SimulatorInputAdapter
* Chained gestures concept? To transfer touches from one gesture to another. Example: press/hold for circular menu, then drag it around.

View file

@ -21,6 +21,8 @@
<arg value="-include-sources=${src.dir}"/>
<!-- Exclude Flex Framework classes (some mx events comes from binding). -->
<arg value="-external-library-path+=${flexSDK.dir}/frameworks/libs"/>
<!-- Exclude any external classes (such as Starling framework classes) -->
<arg value="-external-library-path+=${libs.dir}"/>
<!-- Keep the metatags (Apparat?). -->
<arg value="-keep-as3-metadata+=Abstract"/>
<!-- Boolean mosh pit! -->
@ -44,6 +46,7 @@
</delete>
<java jar="${FLEX_HOME}/lib/asdoc.jar" dir="${FLEX_HOME}/frameworks" fork="true" failonerror="true">
<arg line="-source-path ${src.dir}"/>
<arg line="-external-library-path+=${libs.dir}"/>
<arg line="-doc-sources ${src.dir}"/>
<arg line="-output ${asdoc.dir}"/>
<arg value="-keep-xml=true"/>

BIN
libs/starling.swc Normal file

Binary file not shown.

View file

@ -0,0 +1,98 @@
package org.gestouch.core
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.geom.Point;
import flash.utils.Dictionary;
/**
* @author Pavel fljot
*/
final public class DisplayListAdapter implements IDisplayListAdapter
{
private var _targetWeekStorage:Dictionary;
public function DisplayListAdapter(target:DisplayObject = null)
{
if (target)
{
_targetWeekStorage = new Dictionary(true);
_targetWeekStorage[target] = true;
}
}
public function get target():Object
{
for (var key:Object in _targetWeekStorage)
{
return key;
}
return null;
}
public function globalToLocal(point:Point):Point
{
return (target as DisplayObject).globalToLocal(point);
}
public function contains(object:Object):Boolean
{
const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer;
const objectAsDO:DisplayObject = object as DisplayObject;
if (objectAsDO)
{
return (targetAsDOC && targetAsDOC.contains(objectAsDO));
}
/**
* There might be case when we use some old "software" 3D library for instace,
* which viewport is added to classic Display List. So native stage, root and some other
* sprites will actually be parents of 3D objects. To ensure all gestures (both for
* native and 3D objects) work correctly with each other contains() method should be
* a bit more sophisticated.
* But as all 3D engines (at least it looks like that) are moving towards Stage3D layer
* this task doesn't seem significant anymore. So I leave this implementation as
* comments in case someone will actually need it.
* Just uncomment this and it should work.
// else: more complex case.
// object is not of the same type as this.target (flash.display::DisplayObject)
// it might we some 3D library object in it's viewport (which itself is in DisplayList).
// So we perform more general check:
const adapter:IDisplayListAdapter = Gestouch.gestouch_internal::getDisplayListAdapter(object);
if (adapter)
{
return adapter.getHierarchy(object).indexOf(this.target) > -1;
}
*/
return false;
}
public function getHierarchy(genericTarget:Object):Vector.<Object>
{
var list:Vector.<Object> = new Vector.<Object>();
var i:uint = 0;
var target:DisplayObject = genericTarget as DisplayObject;
while (target)
{
list[i] = target;
target = target.parent;
i++;
}
return list;
}
public function reflect():Class
{
return DisplayListAdapter;
}
}
}

View file

@ -0,0 +1,121 @@
package org.gestouch.core
{
import flash.utils.getQualifiedClassName;
import flash.display.DisplayObject;
import flash.utils.Dictionary;
/**
* @author Pavel fljot
*/
final public class Gestouch
{
private static const _displayListAdaptersMap:Dictionary = new Dictionary();
{
initClass();
}
/** @private */
private static var _inputAdapter:IInputAdapter;
/**
*
*/
public static function get inputAdapter():IInputAdapter
{
return _inputAdapter;
}
public static function set inputAdapter(value:IInputAdapter):void
{
if (_inputAdapter == value)
return;
_inputAdapter = value;
if (inputAdapter)
{
inputAdapter.touchesManager = touchesManager;
inputAdapter.init();
}
}
private static var _touchesManager:TouchesManager;
/**
*
*/
public static function get touchesManager():TouchesManager
{
return _touchesManager ||= new TouchesManager(gesturesManager);
}
private static var _gesturesManager:GesturesManager;
public static function get gesturesManager():GesturesManager
{
return _gesturesManager ||= new GesturesManager();
}
public static function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void
{
if (!targetClass || !adapter)
{
throw new Error("Argument error: both arguments required.");
}
_displayListAdaptersMap[targetClass] = adapter;
}
public static function addTouchHitTester(hitTester:ITouchHitTester, priority:int = 0):void
{
touchesManager.gestouch_internal::addTouchHitTester(hitTester, priority);
}
public static function removeTouchHitTester(hitTester:ITouchHitTester):void
{
touchesManager.gestouch_internal::removeInputAdapter(hitTester);
}
// public static function getTouches(target:Object = null):Array
// {
// return touchesManager.getTouches(target);
// }
gestouch_internal static function createGestureTargetAdapter(target:Object):IDisplayListAdapter
{
const adapter:IDisplayListAdapter = Gestouch.gestouch_internal::getDisplayListAdapter(target);
if (adapter)
{
return new (adapter.reflect())(target);
}
throw new Error("Cannot create adapter for target " + target + " of type " + getQualifiedClassName(target) + ".");
}
gestouch_internal static function getDisplayListAdapter(object:Object):IDisplayListAdapter
{
for (var key:Object in _displayListAdaptersMap)
{
var targetClass:Class = key as Class;
if (object is targetClass)
{
return _displayListAdaptersMap[key] as IDisplayListAdapter;
}
}
return null;
}
private static function initClass():void
{
addDisplayListAdapter(DisplayObject, new DisplayListAdapter());
}
}
}

View file

@ -1,17 +1,99 @@
package org.gestouch.core
{
import flash.errors.IllegalOperationError;
/**
* @author Pavel fljot
*/
public class GestureState
{
public static const IDLE:uint = 1 << 0;//1
public static const POSSIBLE:uint = 1 << 1;//2
public static const RECOGNIZED:uint = 1 << 2;//4
public static const BEGAN:uint = 1 << 3;//8
public static const CHANGED:uint = 1 << 4;//16
public static const ENDED:uint = 1 << 5;//32
public static const CANCELLED:uint = 1 << 6;//64
public static const FAILED:uint = 1 << 7;//128
public static const IDLE:GestureState = new GestureState(1 << 0, "IDLE");
public static const POSSIBLE:GestureState = new GestureState(1 << 1, "POSSIBLE");
public static const RECOGNIZED:GestureState = new GestureState(1 << 2, "RECOGNIZED");
public static const BEGAN:GestureState = new GestureState(1 << 3, "BEGAN");
public static const CHANGED:GestureState = new GestureState(1 << 4, "CHANGED");
public static const ENDED:GestureState = new GestureState(1 << 5, "ENDED");
public static const CANCELLED:GestureState = new GestureState(1 << 6, "CANCELLED");
public static const FAILED:GestureState = new GestureState(1 << 7, "FAILED");
private static const endStatesBitMask:uint =
GestureState.CANCELLED.toUint() |
GestureState.RECOGNIZED.toUint() |
GestureState.ENDED.toUint() |
GestureState.FAILED.toUint();
private static var allStatesInitialized:Boolean;
private var value:uint;
private var name:String;
private var validTransitionsBitMask:uint;
{
_initClass();
}
public function GestureState(value:uint, name:String)
{
if (allStatesInitialized)
{
throw new IllegalOperationError("You cannot create gesture states." +
"Use predefined constats like GestureState.RECOGNIZED");
}
this.value = value;
this.name = name;
}
private static function _initClass():void
{
IDLE.setValidNextStates(POSSIBLE);
POSSIBLE.setValidNextStates(RECOGNIZED, BEGAN, FAILED);
RECOGNIZED.setValidNextStates(IDLE);
BEGAN.setValidNextStates(CHANGED, ENDED, CANCELLED);
CHANGED.setValidNextStates(CHANGED, ENDED, CANCELLED);
ENDED.setValidNextStates(IDLE);
FAILED.setValidNextStates(IDLE);
allStatesInitialized = true;
}
public function toString():String
{
return "GestureState." + name;
}
public function toUint():uint
{
return value;
}
private function setValidNextStates(...states):void
{
var mask:uint;
for each (var state:GestureState in states)
{
mask = mask | state.value;
}
validTransitionsBitMask = mask;
}
gestouch_internal function canTransitionTo(state:GestureState):Boolean
{
return (validTransitionsBitMask & state.value) > 0;
}
gestouch_internal function get isEndState():Boolean
{
return (endStatesBitMask & value) > 0;
}
}
}
}

View file

@ -1,117 +1,37 @@
package org.gestouch.core
{
import flash.errors.IllegalOperationError;
import org.gestouch.gestures.Gesture;
import org.gestouch.input.MouseInputAdapter;
import org.gestouch.input.TouchInputAdapter;
import org.gestouch.input.NativeInputAdapter;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.InteractiveObject;
import flash.display.Shape;
import flash.display.Stage;
import flash.events.Event;
import flash.ui.Multitouch;
import flash.events.IEventDispatcher;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;
/**
* @author Pavel fljot
*/
public class GesturesManager implements IGesturesManager
public class GesturesManager
{
public static var initDefaultInputAdapter:Boolean = true;
private static var _instance:IGesturesManager;
private static var _allowInstantiation:Boolean;
protected const _touchesManager:ITouchesManager = TouchesManager.getInstance();
protected const _frameTickerShape:Shape = new Shape();
protected var _inputAdapters:Vector.<IInputAdapter> = new Vector.<IInputAdapter>();
protected var _stage:Stage;
protected var _gesturesMap:Dictionary = new Dictionary(true);
protected var _gesturesForTouchMap:Array = [];
protected var _gesturesForTouchMap:Dictionary = new Dictionary();
protected var _gesturesForTargetMap:Dictionary = new Dictionary(true);
protected var _dirtyGestures:Vector.<Gesture> = new Vector.<Gesture>();
protected var _dirtyGesturesLength:uint = 0;
protected var _dirtyGesturesCount:uint = 0;
protected var _dirtyGesturesMap:Dictionary = new Dictionary(true);
protected var _stage:Stage;
use namespace gestouch_internal;
public function GesturesManager()
{
if (Object(this).constructor == GesturesManager && !_allowInstantiation)
{
throw new Error("Do not instantiate GesturesManager directly.");
}
}
public function get inputAdapters():Vector.<IInputAdapter>
{
return _inputAdapters.concat();
}
public static function setImplementation(value:IGesturesManager):void
{
if (!value)
{
throw new ArgumentError("value cannot be null.");
}
if (_instance)
{
throw new Error("Instance of GesturesManager is already created. If you want to have own implementation of single GesturesManager instace, you should set it earlier.");
}
_instance = value;
}
public static function getInstance():IGesturesManager
{
if (!_instance)
{
_allowInstantiation = true;
_instance = new GesturesManager();
_allowInstantiation = false;
}
return _instance;
}
public function addInputAdapter(inputAdapter:IInputAdapter):void
{
if (!inputAdapter)
{
throw new Error("Input adapter must be non null.");
}
if (_inputAdapters.indexOf(inputAdapter) > -1)
return;//TODO: throw Error or ignore?
_inputAdapters.push(inputAdapter);
inputAdapter.touchesManager = _touchesManager;
inputAdapter.gesturesManager = this;
inputAdapter.init();
}
public function removeInputAdapter(inputAdapter:IInputAdapter, dispose:Boolean = true):void
{
if (!inputAdapter)
{
throw new Error("Input adapter must be non null.");
}
var index:int = _inputAdapters.indexOf(inputAdapter);
if (index == -1)
{
throw new Error("This input manager is not registered.");
}
_inputAdapters.splice(index, 1);
if (dispose)
{
inputAdapter.dispose();
}
}
@ -123,29 +43,21 @@ package org.gestouch.core
//
//--------------------------------------------------------------------------
protected function installStage(stage:Stage):void
protected function onStageAvailable(stage:Stage):void
{
_stage = stage;
if (Multitouch.supportsTouchEvents)
{
addInputAdapter(new TouchInputAdapter(stage));
}
else
{
addInputAdapter(new MouseInputAdapter(stage));
}
Gestouch.inputAdapter ||= new NativeInputAdapter(stage);
}
protected function resetDirtyGestures():void
{
for each (var gesture:Gesture in _dirtyGestures)
for (var gesture:* in _dirtyGesturesMap)
{
gesture.reset();
(gesture as Gesture).reset();
}
_dirtyGestures.length = 0;
_dirtyGesturesLength = 0;
_dirtyGesturesCount = 0;
_dirtyGesturesMap = new Dictionary(true);
_frameTickerShape.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
@ -157,30 +69,43 @@ package org.gestouch.core
{
throw new ArgumentError("Argument 'gesture' must be not null.");
}
if (_gesturesMap[gesture])
const target:Object = gesture.target;
if (!target)
{
throw new Error("This gesture is already registered.. something wrong.");
throw new IllegalOperationError("Gesture must have target.");
}
var target:Object = gesture.target;
var targetGestures:Vector.<Gesture> = _gesturesForTargetMap[target] as Vector.<Gesture>;
if (!targetGestures)
if (targetGestures)
{
targetGestures = _gesturesForTargetMap[gesture.target] = new Vector.<Gesture>();
if (targetGestures.indexOf(gesture) == -1)
{
targetGestures.push(gesture);
}
}
targetGestures.push(gesture);
else
{
targetGestures = _gesturesForTargetMap[target] = new Vector.<Gesture>();
targetGestures[0] = gesture;
}
_gesturesMap[gesture] = true;
if (GesturesManager.initDefaultInputAdapter)
if (!_stage)
{
if (!_stage && gesture.target.stage)
var targetAsDO:DisplayObject = target as DisplayObject;
if (targetAsDO)
{
installStage(gesture.target.stage);
}
else
{
gesture.target.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler);
if (targetAsDO.stage)
{
onStageAvailable(targetAsDO.stage);
}
else
{
targetAsDO.addEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler);
}
}
}
}
@ -194,21 +119,28 @@ package org.gestouch.core
}
var target:InteractiveObject = gesture.target;
var targetGestures:Vector.<Gesture> = _gesturesForTargetMap[target] as Vector.<Gesture>;
if (targetGestures.length > 1)
var target:Object = gesture.target;
// check for target because it could be already GC-ed (since target reference is weak)
if (target)
{
targetGestures.splice(targetGestures.indexOf(gesture), 1);
}
else
{
delete _gesturesForTargetMap[target];
target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler);
var targetGestures:Vector.<Gesture> = _gesturesForTargetMap[target] as Vector.<Gesture>;
if (targetGestures.length > 1)
{
targetGestures.splice(targetGestures.indexOf(gesture), 1);
}
else
{
delete _gesturesForTargetMap[target];
if (target is IEventDispatcher)
{
(target as IEventDispatcher).removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler);
}
}
}
delete _gesturesMap[gesture];
//TODO: decide about gesture state and _dirtyGestures
gesture.reset();
}
@ -216,8 +148,8 @@ package org.gestouch.core
{
if (!_dirtyGesturesMap[gesture])
{
_dirtyGestures.push(gesture);
_dirtyGesturesLength++;
_dirtyGesturesMap[gesture] = true;
_dirtyGesturesCount++;
_frameTickerShape.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
}
@ -225,11 +157,12 @@ package org.gestouch.core
gestouch_internal function onGestureRecognized(gesture:Gesture):void
{
const target:Object = gesture.target;
for (var key:Object in _gesturesMap)
{
var otherGesture:Gesture = key as Gesture;
var target:DisplayObject = gesture.target;
var otherTarget:DisplayObject = otherGesture.target;
var otherTarget:Object = otherGesture.target;
// conditions for otherGesture "own properties"
if (otherGesture != gesture &&
@ -237,10 +170,10 @@ package org.gestouch.core
otherGesture.enabled &&
otherGesture.state == GestureState.POSSIBLE)
{
// conditions for otherGesture target
if (otherTarget == target ||
(target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(otherTarget)) ||
(otherTarget is DisplayObjectContainer && (otherTarget as DisplayObjectContainer).contains(target)))
gesture.targetAdapter.contains(otherTarget) ||
otherGesture.targetAdapter.contains(target)
)
{
var gestureDelegate:IGestureDelegate = gesture.delegate;
var otherGestureDelegate:IGestureDelegate = otherGesture.delegate;
@ -250,9 +183,9 @@ package org.gestouch.core
(!gestureDelegate || !gestureDelegate.gesturesShouldRecognizeSimultaneously(gesture, otherGesture)) &&
(!otherGestureDelegate || !otherGestureDelegate.gesturesShouldRecognizeSimultaneously(otherGesture, gesture)))
{
otherGesture.gestouch_internal::setState_internal(GestureState.FAILED);
otherGesture.setState_internal(GestureState.FAILED);
}
}
}
}
}
}
@ -260,7 +193,7 @@ package org.gestouch.core
gestouch_internal function onTouchBegin(touch:Touch):void
{
if (_dirtyGesturesLength > 0)
if (_dirtyGesturesCount > 0)
{
resetDirtyGestures();
}
@ -268,24 +201,41 @@ package org.gestouch.core
var gesture:Gesture;
var i:uint;
// This vector will contain active gestures for specific touch (ID) during all touch session.
var gesturesForTouch:Vector.<Gesture> = _gesturesForTouchMap[touch.id] as Vector.<Gesture>;
// This vector will contain active gestures for specific touch during all touch session.
var gesturesForTouch:Vector.<Gesture> = _gesturesForTouchMap[touch] as Vector.<Gesture>;
if (!gesturesForTouch)
{
gesturesForTouch = new Vector.<Gesture>();
_gesturesForTouchMap[touch.id] = gesturesForTouch;
gesturesForTouch = _gesturesForTouchMap[touch] = new Vector.<Gesture>();
}
else
{
gesturesForTouch.length = 0;
}
}
var target:Object = touch.target;
const displayListAdapter:IDisplayListAdapter = Gestouch.gestouch_internal::getDisplayListAdapter(target);
const hierarchy:Vector.<Object> = displayListAdapter.getHierarchy(target);
if (!hierarchy)
{
throw new Error("Display list adapter not found for target of type '" + getQualifiedClassName(target) + "'.");
}
const hierarchyLength:uint = hierarchy.length;
if (hierarchyLength == 0)
{
throw new Error("No hierarchy build for target '" + target +"'. Something is wrong with that IDisplayListAdapter.");
}
if (_stage && !(hierarchy[hierarchyLength - 1] is Stage))
{
// Looks like some non-native (non DisplayList) hierarchy
// but we must always handle gestures with Stage target
// since Stage is anyway the top-most parent
hierarchy[hierarchyLength] = _stage;
}
// Create a sorted(!) list of gestures which are interested in this touch.
// Sorting priority: deeper target has higher priority, recently added gesture has higher priority.
var target:InteractiveObject = touch.target;
var gesturesForTarget:Vector.<Gesture>;
while (target)
for each (target in hierarchy)
{
gesturesForTarget = _gesturesForTargetMap[target] as Vector.<Gesture>;
if (gesturesForTarget)
@ -302,8 +252,6 @@ package org.gestouch.core
}
}
}
target = target.parent;
}
// Then we populate them with this touch and event.
@ -315,7 +263,7 @@ package org.gestouch.core
// Check for state because previous (i+1) gesture may already abort current (i) one
if (gesture.state != GestureState.FAILED)
{
gesture.gestouch_internal::touchBeginHandler(touch);
gesture.touchBeginHandler(touch);
}
else
{
@ -327,12 +275,12 @@ package org.gestouch.core
gestouch_internal function onTouchMove(touch:Touch):void
{
if (_dirtyGesturesLength > 0)
if (_dirtyGesturesCount > 0)
{
resetDirtyGestures();
}
var gesturesForTouch:Vector.<Gesture> = _gesturesForTouchMap[touch.id] as Vector.<Gesture>;
var gesturesForTouch:Vector.<Gesture> = _gesturesForTouchMap[touch] as Vector.<Gesture>;
var gesture:Gesture;
var i:int = gesturesForTouch.length;
while (i-- > 0)
@ -341,7 +289,7 @@ package org.gestouch.core
if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id))
{
gesture.gestouch_internal::touchMoveHandler(touch);
gesture.touchMoveHandler(touch);
}
else
{
@ -354,23 +302,25 @@ package org.gestouch.core
gestouch_internal function onTouchEnd(touch:Touch):void
{
if (_dirtyGesturesLength > 0)
if (_dirtyGesturesCount > 0)
{
resetDirtyGestures();
}
var gesturesForTouch:Vector.<Gesture> = _gesturesForTouchMap[touch.id] as Vector.<Gesture>;
var gesturesForTouch:Vector.<Gesture> = _gesturesForTouchMap[touch] 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);
{
gesture.touchEndHandler(touch);
}
}
gesturesForTouch.length = 0;// release for GC
}
@ -386,15 +336,15 @@ package org.gestouch.core
//
// Event handlers
//
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
protected function gestureTarget_addedToStageHandler(event:Event):void
{
var target:DisplayObject = event.target as DisplayObject;
target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler);
if (!_stage && GesturesManager.initDefaultInputAdapter)
if (!_stage)
{
installStage(target.stage);
onStageAvailable(target.stage);
}
}

View file

@ -0,0 +1,12 @@
package org.gestouch.core
{
/**
* @author Pavel fljot
*/
public interface IDisplayListAdapter extends IGestureTargetAdapter
{
function getHierarchy(target:Object):Vector.<Object>;
function reflect():Class;
}
}

View file

@ -0,0 +1,15 @@
package org.gestouch.core
{
import flash.geom.Point;
/**
* @author Pavel fljot
*/
public interface IGestureTargetAdapter
{
function get target():Object;
function globalToLocal(point:Point):Point;
function contains(object:Object):Boolean;
}
}

View file

@ -1,17 +0,0 @@
package org.gestouch.core
{
/**
* The class that implements this interface must also
* implement next methods under gestouch_internal namespace:
*
* function addGesture(gesture:Gesture):void;
* function removeGesture(gesture:Gesture):void;
* function scheduleGestureStateReset(gesture:Gesture):void;
* function onGestureRecognized(gesture:Gesture):void;
*/
public interface IGesturesManager
{
function addInputAdapter(inputAdapter:IInputAdapter):void;
function removeInputAdapter(inputAdapter:IInputAdapter, dispose:Boolean = true):void;
}
}

View file

@ -5,10 +5,14 @@ package org.gestouch.core
*/
public interface IInputAdapter
{
function set touchesManager(value:ITouchesManager):void;
function set gesturesManager(value:IGesturesManager):void;
/**
* @private
*/
function set touchesManager(value:TouchesManager):void;
/**
* Called when input adapter is set.
*/
function init():void;
function dispose():void;
}
}
}

View file

@ -0,0 +1,14 @@
package org.gestouch.core
{
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
* @author Pavel fljot
*/
public interface ITouchHitTester
{
function hitTest(point:Point, nativeTarget:InteractiveObject):Object;
}
}

View file

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

View file

@ -1,6 +1,5 @@
package org.gestouch.core
{
import flash.display.InteractiveObject;
import flash.geom.Point;
@ -19,13 +18,15 @@ package org.gestouch.core
/**
* The original event target for this touch (touch began with).
*/
public var target:InteractiveObject;
public var target:Object;
public var sizeX:Number;
public var sizeY:Number;
public var pressure:Number;
// public var lastMove:Point;
use namespace gestouch_internal;
public function Touch(id:uint = 0)
@ -39,13 +40,16 @@ package org.gestouch.core
{
return _location.clone();
}
gestouch_internal function setLocation(value:Point):void
gestouch_internal function setLocation(x:Number, y:Number, time:uint):void
{
_location = value;
_location = new Point(x, y);
_beginLocation = _location.clone();
_previousLocation = _location.clone();
_time = time;
_beginTime = time;
}
gestouch_internal function updateLocation(x:Number, y:Number):void
gestouch_internal function updateLocation(x:Number, y:Number, time:uint):void
{
if (_location)
{
@ -53,10 +57,11 @@ package org.gestouch.core
_previousLocation.y = _location.y;
_location.x = x;
_location.y = y;
_time = time;
}
else
{
gestouch_internal::setLocation(new Point(x, y));
setLocation(x, y, time);
}
}

View file

@ -1,22 +1,30 @@
package org.gestouch.core
{
import flash.display.InteractiveObject;
import flash.display.Stage;
import flash.geom.Point;
import flash.utils.Dictionary;
import flash.utils.getTimer;
/**
* @author Pavel fljot
*/
public class TouchesManager implements ITouchesManager
public class TouchesManager
{
private static var _instance:ITouchesManager;
private static var _allowInstantiation:Boolean;
protected var _gesturesManager:GesturesManager;
protected var _touchesMap:Object = {};
protected var _hitTesters:Vector.<ITouchHitTester> = new Vector.<ITouchHitTester>();
protected var _hitTesterPrioritiesMap:Dictionary = new Dictionary(true);
use namespace gestouch_internal;
public function TouchesManager()
public function TouchesManager(gesturesManager:GesturesManager)
{
if (Object(this).constructor == TouchesManager && !_allowInstantiation)
{
throw new Error("Do not instantiate TouchesManager directly.");
}
_gesturesManager = gesturesManager;
addTouchHitTester(new DefaultTouchHitTester());
}
@ -27,77 +35,207 @@ package org.gestouch.core
}
public static function setImplementation(value:ITouchesManager):void
public function getTouches(target:Object = null):Array
{
if (!value)
const touches:Array = [];
if (!target || target is Stage)
{
throw new ArgumentError("value cannot be null.");
// return all touches
var i:uint = 0;
for each (var touch:Touch in _touchesMap)
{
touches[i++] = touch;
}
}
if (_instance)
else
{
throw new Error("Instance of TouchesManager is already created. If you want to have own implementation of single TouchesManager instace, you should set it earlier.");
//TODO
}
_instance = value;
}
public static function getInstance():ITouchesManager
{
if (!_instance)
{
_allowInstantiation = true;
_instance = new TouchesManager();
_allowInstantiation = false;
}
return _instance;
return touches;
}
public function createTouch():Touch
gestouch_internal function addTouchHitTester(touchHitTester:ITouchHitTester, priority:int = 0):void
{
if (!touchHitTester)
{
throw new ArgumentError("Argument must be non null.");
}
if (_hitTesters.indexOf(touchHitTester) == -1)
{
_hitTesters.push(touchHitTester);
}
_hitTesterPrioritiesMap[touchHitTester] = priority;
// Sort hit testers using their priorities
_hitTesters.sort(hitTestersSorter);
}
gestouch_internal function removeInputAdapter(touchHitTester:ITouchHitTester):void
{
if (!touchHitTester)
{
throw new ArgumentError("Argument must be non null.");
}
var index:int = _hitTesters.indexOf(touchHitTester);
if (index == -1)
{
throw new Error("This touchHitTester is not registered.");
}
_hitTesters.splice(index, 1);
delete _hitTesterPrioritiesMap[touchHitTester];
}
gestouch_internal function onTouchBegin(touchID:uint, x:Number, y:Number, nativeTarget:InteractiveObject = null):Boolean
{
if (touchID in _touchesMap)
return false;// touch with specified ID is already registered and being tracked
const location:Point = new Point(x, y);
for each (var registeredTouch:Touch in _touchesMap)
{
// Check if touch at the same location exists.
// In case we listen to both TouchEvents and MouseEvents, one of them will come first
// (right now looks like MouseEvent dispatched first, but who know what Adobe will
// do tomorrow). This check helps to filter out the one comes after.
// NB! According to the tests with some IR multitouch frame and Windows computer
// TouchEvent comes first, but the following MouseEvent has slightly offset location
// (1px both axis). That is why Point#distance() used instead of Point#equals()
if (Point.distance(registeredTouch.location, location) < 2)
return false;
}
const touch:Touch = createTouch();
touch.id = touchID;
var target:Object;
var altTarget:Object;
for each (var hitTester:ITouchHitTester in _hitTesters)
{
target = hitTester.hitTest(location, nativeTarget);
if (target)
{
if ((target is Stage))
{
// NB! Target is flash.display::Stage is a special case. If it is true, we want
// to give a try to a lower-priority (Stage3D) hit-testers.
altTarget = target;
continue;
}
else
{
// We found a target.
break;
}
}
}
if (!target && !altTarget)
{
throw new Error("Not touch target found (hit test)." +
"Something is wrong, at least flash.display::Stage should be found." +
"See Gestouch#addTouchHitTester() and Gestouch#inputAdapter.");
}
touch.target = target || altTarget;
touch.setLocation(x, y, getTimer());
_touchesMap[touchID] = touch;
_activeTouchesCount++;
_gesturesManager.onTouchBegin(touch);
return true;
}
gestouch_internal function onTouchMove(touchID:uint, x:Number, y:Number):void
{
const touch:Touch = _touchesMap[touchID] as Touch;
if (!touch)
return;// touch with specified ID isn't registered
touch.updateLocation(x, y, getTimer());
_gesturesManager.onTouchMove(touch);
}
gestouch_internal function onTouchEnd(touchID:uint, x:Number, y:Number):void
{
const touch:Touch = _touchesMap[touchID] as Touch;
if (!touch)
return;// touch with specified ID isn't registered
touch.updateLocation(x, y, getTimer());
delete _touchesMap[touchID];
_activeTouchesCount--;
_gesturesManager.onTouchEnd(touch);
}
gestouch_internal function onTouchCancel(touchID:uint, x:Number, y:Number):void
{
const touch:Touch = _touchesMap[touchID] as Touch;
if (!touch)
return;// touch with specified ID isn't registered
touch.updateLocation(x, y, getTimer());
delete _touchesMap[touchID];
_activeTouchesCount--;
_gesturesManager.onTouchCancel(touch);
}
protected function createTouch():Touch
{
//TODO: pool
return new Touch();
}
public function addTouch(touch:Touch):Touch
/**
* Sorts from higher priority to lower. Items with the same priority keep the order
* of addition, e.g.:
* add(a), add(b), add(c, -1), add(d, 1) will be ordered to
* d, a, b, c
*/
protected function hitTestersSorter(x:ITouchHitTester, y:ITouchHitTester):Number
{
if (_touchesMap.hasOwnProperty(touch.id))
{
throw new Error("Touch with id " + touch.id + " is already registered.");
}
const d:int = int(_hitTesterPrioritiesMap[x]) - int(_hitTesterPrioritiesMap[y]);
if (d > 0)
return -1;
else if (d < 0)
return 1;
_touchesMap[touch.id] = touch;
_activeTouchesCount++;
return touch;
}
public function removeTouch(touch:Touch):Touch
{
if (!_touchesMap.hasOwnProperty(touch.id))
{
throw new Error("Touch with id " + touch.id + " is not registered.");
}
delete _touchesMap[touch.id];
_activeTouchesCount--;
return touch;
}
public function hasTouch(touchPointID:int):Boolean
{
return _touchesMap.hasOwnProperty(touchPointID);
}
public function getTouch(touchPointID:int):Touch
{
return _touchesMap[touchPointID] as Touch;
return _hitTesters.indexOf(x) > _hitTesters.indexOf(y) ? 1 : -1;
}
}
}
}
import flash.geom.Point;
import flash.display.InteractiveObject;
import org.gestouch.core.ITouchHitTester;
class DefaultTouchHitTester implements ITouchHitTester
{
public function hitTest(point:Point, nativeTarget:InteractiveObject):Object
{
return nativeTarget;
}
}

View file

@ -1,5 +1,7 @@
package org.gestouch.events
{
import org.gestouch.core.GestureState;
import flash.events.Event;
@ -8,7 +10,7 @@ package org.gestouch.events
*/
public class GestureEvent extends Event
{
public var gestureState:uint;
public var gestureState:GestureState;
public var stageX:Number;
public var stageY:Number;
public var localX:Number;
@ -16,7 +18,7 @@ package org.gestouch.events
public function GestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
gestureState:uint = 0,
gestureState:GestureState = null,
stageX:Number = 0, stageY:Number = 0,
localX:Number = 0, localY:Number = 0)
{

View file

@ -1,5 +1,7 @@
package org.gestouch.events
{
import org.gestouch.core.GestureState;
import flash.events.Event;
@ -10,11 +12,11 @@ package org.gestouch.events
{
public static const STATE_CHANGE:String = "stateChange";
public var newState:uint;
public var oldState:uint;
public var newState:GestureState;
public var oldState:GestureState;
public function GestureStateEvent(type:String, newState:uint, oldState:uint)
public function GestureStateEvent(type:String, newState:GestureState, oldState:GestureState)
{
super(type, false, false);

View file

@ -1,5 +1,7 @@
package org.gestouch.events
{
import org.gestouch.core.GestureState;
import flash.events.Event;
@ -12,7 +14,7 @@ package org.gestouch.events
public function LongPressGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
gestureState:uint = 0,
gestureState:GestureState = null,
stageX:Number = 0, stageY:Number = 0,
localX:Number = 0, localY:Number = 0)
{

View file

@ -1,5 +1,7 @@
package org.gestouch.events
{
import org.gestouch.core.GestureState;
import flash.events.Event;
@ -12,7 +14,7 @@ package org.gestouch.events
public function PanGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
gestureState:uint = 0,
gestureState:GestureState = null,
stageX:Number = 0, stageY:Number = 0,
localX:Number = 0, localY:Number = 0,
offsetX:Number = 0, offsetY:Number = 0)

View file

@ -1,5 +1,7 @@
package org.gestouch.events
{
import org.gestouch.core.GestureState;
import flash.events.Event;
@ -12,7 +14,7 @@ package org.gestouch.events
public function RotateGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
gestureState:uint = 0,
gestureState:GestureState = null,
stageX:Number = 0, stageY:Number = 0,
localX:Number = 0, localY:Number = 0,
rotation:Number = 0)

View file

@ -1,5 +1,7 @@
package org.gestouch.events
{
import org.gestouch.core.GestureState;
import flash.events.Event;
@ -12,7 +14,7 @@ package org.gestouch.events
public function SwipeGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
gestureState:uint = 0,
gestureState:GestureState = null,
stageX:Number = 0, stageY:Number = 0,
localX:Number = 0, localY:Number = 0,
offsetX:Number = 0, offsetY:Number = 0)

View file

@ -1,5 +1,7 @@
package org.gestouch.events
{
import org.gestouch.core.GestureState;
import flash.events.Event;
@ -12,7 +14,8 @@ package org.gestouch.events
public function TapGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
gestureState:uint = 0, stageX:Number = 0, stageY:Number = 0,
gestureState:GestureState = null,
stageX:Number = 0, stageY:Number = 0,
localX:Number = 0, localY:Number = 0)
{
super(type, bubbles, cancelable, gestureState, stageX, stageY, localX, localY);

View file

@ -1,5 +1,7 @@
package org.gestouch.events
{
import org.gestouch.core.GestureState;
import flash.events.Event;
@ -18,7 +20,7 @@ package org.gestouch.events
public function TransformGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
gestureState:uint = 0,
gestureState:GestureState = null,
stageX:Number = 0, stageY:Number = 0,
localX:Number = 0, localY:Number = 0,
scaleX:Number = 1.0, scaleY:Number = 1.0,

View file

@ -1,5 +1,7 @@
package org.gestouch.events
{
import org.gestouch.core.GestureState;
import flash.events.Event;
@ -12,7 +14,7 @@ package org.gestouch.events
public function ZoomGestureEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false,
gestureState:uint = 0,
gestureState:GestureState = null,
stageX:Number = 0, stageY:Number = 0,
localX:Number = 0, localY:Number = 0,
scaleX:Number = 1.0, scaleY:Number = 1.0)

View file

@ -0,0 +1,77 @@
package org.gestouch.extensions.starling
{
import starling.core.Starling;
import starling.display.DisplayObject;
import starling.display.DisplayObjectContainer;
import org.gestouch.core.IDisplayListAdapter;
import flash.geom.Point;
import flash.utils.Dictionary;
/**
* @author Pavel fljot
*/
final public class StarlingDisplayListAdapter implements IDisplayListAdapter
{
private var targetWeekStorage:Dictionary;
public function StarlingDisplayListAdapter(target:DisplayObject = null)
{
if (target)
{
targetWeekStorage = new Dictionary(true);
targetWeekStorage[target] = true;
}
}
public function get target():Object
{
for (var key:Object in targetWeekStorage)
{
return key;
}
return null;
}
public function globalToLocal(point:Point):Point
{
point = StarlingUtils.adjustGlobalPoint(Starling.current, point);
return (target as DisplayObject).globalToLocal(point);
}
public function contains(object:Object):Boolean
{
const targetAsDOC:DisplayObjectContainer = this.target as DisplayObjectContainer;
const objectAsDO:DisplayObject = object as DisplayObject;
return (targetAsDOC && objectAsDO && targetAsDOC.contains(objectAsDO));
}
public function getHierarchy(genericTarget:Object):Vector.<Object>
{
var list:Vector.<Object> = new Vector.<Object>();
var i:uint = 0;
var target:DisplayObject = genericTarget as DisplayObject;
while (target)
{
list[i] = target;
target = target.parent;
i++;
}
return list;
}
public function reflect():Class
{
return StarlingDisplayListAdapter;
}
}
}

View file

@ -0,0 +1,36 @@
package org.gestouch.extensions.starling
{
import starling.core.Starling;
import org.gestouch.core.ITouchHitTester;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
* @author Pavel fljot
*/
final public class StarlingTouchHitTester implements ITouchHitTester
{
private var starling:Starling;
public function StarlingTouchHitTester(starling:Starling)
{
if (!starling)
{
throw ArgumentError("Missing starling argument.");
}
this.starling = starling;
}
public function hitTest(point:Point, nativeTarget:InteractiveObject):Object
{
point = StarlingUtils.adjustGlobalPoint(starling, point);
return starling.stage.hitTest(point, true);
}
}
}

View file

@ -0,0 +1,38 @@
package org.gestouch.extensions.starling
{
import starling.display.Stage;
import starling.core.Starling;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* @author Pavel fljot
*/
final public class StarlingUtils
{
/**
* Transforms real global point (in the scope of flash.display::Stage) into
* starling stage "global" coordinates.
*/
public static function adjustGlobalPoint(starling:Starling, point:Point):Point
{
const vp:Rectangle = starling.viewPort;
const stStage:Stage = starling.stage;
if (vp.x != 0 || vp.y != 0 ||
stStage.stageWidth != vp.width || stStage.stageHeight != vp.height)
{
point = point.clone();
// Same transformation they do in Starling
// WTF!? https://github.com/PrimaryFeather/Starling-Framework/issues/72
point.x = stStage.stageWidth * (point.x - vp.x) / vp.width;
point.y = stStage.stageHeight * (point.y - vp.y) / vp.height;
}
return point;
}
}
}

View file

@ -1,28 +1,32 @@
package org.gestouch.gestures
{
import org.gestouch.core.Gestouch;
import org.gestouch.core.GestureState;
import org.gestouch.core.GesturesManager;
import org.gestouch.core.IGestureDelegate;
import org.gestouch.core.IGesturesManager;
import org.gestouch.core.IGestureTargetAdapter;
import org.gestouch.core.Touch;
import org.gestouch.core.gestouch_internal;
import org.gestouch.events.GestureStateEvent;
import flash.display.InteractiveObject;
import flash.errors.IllegalOperationError;
import flash.events.EventDispatcher;
import flash.geom.Point;
import flash.system.Capabilities;
import flash.utils.Dictionary;
/**
* Dispatched when the state of the gesture changes.
*
* @eventType org.gestouch.events.GestureStateEvent
* @see #state
*/
[Event(name="stateChange", type="org.gestouch.events.GestureStateEvent")]
/**
* Base class for all gestures. Gesture is essentially a detector that tracks touch points
* in order detect specific gesture motion and form gesture event on target.
*
* TODO:
* -
*
* @author Pavel fljot
*/
public class Gesture extends EventDispatcher
@ -32,10 +36,10 @@ package org.gestouch.gestures
* (not an accidental offset on touch),
* based on 20 pixels on a 252ppi device.
*/
public static const DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI);
public static var DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI);
protected const _gesturesManager:IGesturesManager = GesturesManager.getInstance();
protected const _gesturesManager:GesturesManager = Gestouch.gesturesManager;
/**
* Map (generic object) of tracking touch points, where keys are touch points IDs.
*/
@ -47,10 +51,12 @@ package org.gestouch.gestures
* @see requireGestureToFail()
*/
protected var _gesturesToFail:Dictionary = new Dictionary(true);
protected var _pendingRecognizedState:uint;
protected var _pendingRecognizedState:GestureState;
use namespace gestouch_internal;
public function Gesture(target:InteractiveObject = null)
public function Gesture(target:Object = null)
{
super();
@ -61,9 +67,22 @@ package org.gestouch.gestures
/** @private */
private var _targetWeekStorage:Dictionary;
protected var _targetAdapter:IGestureTargetAdapter;
/**
*
*/
gestouch_internal function get targetAdapter():IGestureTargetAdapter
{
return _targetAdapter;
}
protected function get targetAdapter():IGestureTargetAdapter
{
return _targetAdapter;
}
/**
* FIXME
* InteractiveObject (DisplayObject) which this gesture is tracking the actual gesture motion on.
*
* <p>Could be some image, component (like map) or the larger view like Stage.</p>
@ -74,29 +93,18 @@ package org.gestouch.gestures
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html
*/
public function get target():InteractiveObject
public function get target():Object
{
for (var key:Object in _targetWeekStorage)
{
return key as InteractiveObject;
}
return null;
return _targetAdapter ? _targetAdapter.target : null;
}
public function set target(value:InteractiveObject):void
public function set target(value:Object):void
{
var target:InteractiveObject = this.target;
var target:Object = this.target;
if (target == value)
return;
uninstallTarget(target);
for (var key:Object in _targetWeekStorage)
{
delete _targetWeekStorage[key];
}
if (value)
{
(_targetWeekStorage ||= new Dictionary(true))[value] = true;
}
_targetAdapter = value ? Gestouch.createGestureTargetAdapter(value) : null;
installTarget(value);
}
@ -117,11 +125,18 @@ package org.gestouch.gestures
return;
_enabled = value;
//TODO
if (!_enabled && state != GestureState.IDLE)
if (!_enabled)
{
setState(GestureState.CANCELLED);
reset();
if (state == GestureState.POSSIBLE)
{
setState(GestureState.FAILED);
}
else
if (state == GestureState.BEGAN || state == GestureState.CHANGED)
{
setState(GestureState.CANCELLED);
}
}
}
@ -148,8 +163,8 @@ package org.gestouch.gestures
}
protected var _state:uint = GestureState.IDLE;
public function get state():uint
protected var _state:GestureState = GestureState.IDLE;
public function get state():GestureState
{
return _state;
}
@ -173,7 +188,6 @@ package org.gestouch.gestures
*/
public function get location():Point
{
//TODO: to clone or not clone? performance & convention or ...
return _location.clone();
}
@ -184,7 +198,7 @@ package org.gestouch.gestures
//
// Public methods
//
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
[Abstract]
/**
@ -213,7 +227,7 @@ package org.gestouch.gestures
*/
public function reset():void
{
var state:uint = this.state;//caching getter
var state:GestureState = this.state;//caching getter
if (state == GestureState.IDLE)
return;// Do nothing as we're in IDLE and nothing to reset
@ -228,14 +242,14 @@ package org.gestouch.gestures
var gestureToFail:Gesture = key as Gesture;
gestureToFail.removeEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler);
}
_pendingRecognizedState = 0;
_pendingRecognizedState = null;
if (state == GestureState.POSSIBLE)
{
// manual reset() call. Set to FAILED to keep our State Machine clean and stable
setState(GestureState.FAILED);
}
else if (state == GestureState.BEGAN || state == GestureState.RECOGNIZED)
else if (state == GestureState.BEGAN || state == GestureState.CHANGED)
{
// manual reset() call. Set to CANCELLED to keep our State Machine clean and stable
setState(GestureState.CANCELLED);
@ -285,6 +299,11 @@ package org.gestouch.gestures
public function requireGestureToFail(gesture:Gesture):void
{
//TODO
if (!gesture)
{
throw new ArgumentError();
}
_gesturesToFail[gesture] = true;
}
@ -310,11 +329,11 @@ package org.gestouch.gestures
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html
*/
protected function installTarget(target:InteractiveObject):void
protected function installTarget(target:Object):void
{
if (target)
{
_gesturesManager.gestouch_internal::addGesture(this);
_gesturesManager.addGesture(this);
}
}
@ -326,11 +345,11 @@ package org.gestouch.gestures
*
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html
*/
protected function uninstallTarget(target:InteractiveObject):void
protected function uninstallTarget(target:Object):void
{
if (target)
{
_gesturesManager.gestouch_internal::removeGesture(this);
_gesturesManager.removeGesture(this);
}
}
@ -375,30 +394,33 @@ package org.gestouch.gestures
}
protected function setState(newState:uint):Boolean
protected function setState(newState:GestureState):Boolean
{
if (_state == newState && _state == GestureState.CHANGED)
{
return true;
}
//TODO: is state sequence validation needed? e.g.:
//POSSIBLE should be followed by BEGAN or RECOGNIZED or FAILED
//BEGAN should be follwed by CHANGED or ENDED or CANCELLED
//CHANGED should be followed by CHANGED or ENDED or CANCELLED
//...
if (!_state.canTransitionTo(newState))
{
throw new IllegalOperationError("You cannot change from state " +
_state + " to state " + newState + ".");
}
if (newState == GestureState.BEGAN || newState == GestureState.RECOGNIZED)
{
var gestureToFail:Gesture;
var key:*;
// first we check if other required-to-fail gestures recognized
// TODO: is this really necessary? using "requireGestureToFail" API assume that
// required-to-fail gesture always recognizes AFTER this one.
for (var key:* in _gesturesToFail)
for (key in _gesturesToFail)
{
gestureToFail = key as Gesture;
if (gestureToFail.state != GestureState.IDLE && gestureToFail.state != GestureState.POSSIBLE
&& gestureToFail.state != GestureState.FAILED)
if (gestureToFail.state != GestureState.IDLE &&
gestureToFail.state != GestureState.POSSIBLE &&
gestureToFail.state != GestureState.FAILED)
{
// Looks like other gesture won't fail,
// which means the required condition will not happen, so we must fail
@ -406,7 +428,7 @@ package org.gestouch.gestures
return false;
}
}
// then we check of other required-to-fail gestures are actually tracked (not IDLE)
// then we check if other required-to-fail gestures are actually tracked (not IDLE)
// and not still not recognized (e.g. POSSIBLE state)
for (key in _gesturesToFail)
{
@ -415,6 +437,13 @@ package org.gestouch.gestures
{
// Other gesture might fail soon, so we postpone state change
_pendingRecognizedState = newState;
for (key in _gesturesToFail)
{
gestureToFail = key as Gesture;
gestureToFail.addEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler, false, 0, true);
}
return false;
}
// else if gesture is in IDLE state it means it doesn't track anything,
@ -430,12 +459,12 @@ package org.gestouch.gestures
}
}
var oldState:uint = _state;
var oldState:GestureState = _state;
_state = newState;
if (((GestureState.CANCELLED | GestureState.RECOGNIZED | GestureState.ENDED | GestureState.FAILED) & _state) > 0)
if (_state.isEndState)
{
_gesturesManager.gestouch_internal::scheduleGestureStateReset(this);
_gesturesManager.scheduleGestureStateReset(this);
}
//TODO: what if RTE happens in event handlers?
@ -447,14 +476,14 @@ package org.gestouch.gestures
if (_state == GestureState.BEGAN || _state == GestureState.RECOGNIZED)
{
_gesturesManager.gestouch_internal::onGestureRecognized(this);
_gesturesManager.onGestureRecognized(this);
}
return true;
}
gestouch_internal function setState_internal(state:uint):void
gestouch_internal function setState_internal(state:GestureState):void
{
setState(state);
}
@ -481,7 +510,7 @@ package org.gestouch.gestures
updateCentralPoint();
_location.x = _centralPoint.x;
_location.y = _centralPoint.y;
_localLocation = target.globalToLocal(_location);
_localLocation = targetAdapter.globalToLocal(_location);
}
@ -513,12 +542,6 @@ package org.gestouch.gestures
if (_touchesCount == 1 && state == GestureState.IDLE)
{
for (var key:* in _gesturesToFail)
{
var gestureToFail:Gesture = key as Gesture;
gestureToFail.addEventListener(GestureStateEvent.STATE_CHANGE, gestureToFail_stateChangeHandler, false, 0, true);
}
setState(GestureState.POSSIBLE);
}
}
@ -542,10 +565,7 @@ package org.gestouch.gestures
protected function gestureToFail_stateChangeHandler(event:GestureStateEvent):void
{
if (state != GestureState.POSSIBLE)
return;//just in case..FIXME?
if (!_pendingRecognizedState)
if (!_pendingRecognizedState || state != GestureState.POSSIBLE)
return;
if (event.newState == GestureState.FAILED)
@ -560,15 +580,20 @@ package org.gestouch.gestures
}
}
// at this point all gestures-to-fail are either in IDLE or in FAILED states
if (setState(_pendingRecognizedState))
{
onDelayedRecognize();
}
}
else if (event.newState != GestureState.POSSIBLE)
else if (event.newState != GestureState.IDLE && event.newState != GestureState.POSSIBLE)
{
// NB: _other_ gesture may switch to IDLE state if it was in FAILED when
// _this_ gesture initially attempted to switch to one of recognized state.
// ...and that's OK (we ignore that)
setState(GestureState.FAILED);
}
}
}
}
}

View file

@ -4,11 +4,15 @@ package org.gestouch.gestures
import org.gestouch.core.Touch;
import org.gestouch.events.LongPressGestureEvent;
import flash.display.InteractiveObject;
import flash.events.TimerEvent;
import flash.utils.Timer;
/**
*
* @eventType org.gestouch.events.LongPressGestureEvent
*/
[Event(name="gestureLongPress", type="org.gestouch.events.LongPressGestureEvent")]
/**
* TODO: -location
* - check on iOS (Obj-C) what happens when numTouchesRequired=2, two finger down, then quickly release one.
@ -30,7 +34,7 @@ package org.gestouch.gestures
protected var _numTouchesRequiredReached:Boolean;
public function LongPressGesture(target:InteractiveObject = null)
public function LongPressGesture(target:Object = null)
{
super(target);
}
@ -124,7 +128,7 @@ package org.gestouch.gestures
//TODO: check proper condition (behavior) on iOS native
if (_numTouchesRequiredReached)
{
if (((GestureState.BEGAN | GestureState.CHANGED) & state) > 0)
if (state == GestureState.BEGAN || state == GestureState.CHANGED)
{
updateLocation();
if (setState(GestureState.ENDED) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS))

View file

@ -4,9 +4,13 @@ package org.gestouch.gestures
import org.gestouch.core.Touch;
import org.gestouch.events.PanGestureEvent;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
*
* @eventType org.gestouch.events.PanGestureEvent
*/
[Event(name="gesturePan", type="org.gestouch.events.PanGestureEvent")]
/**
* TODO:
@ -27,7 +31,7 @@ package org.gestouch.gestures
protected var _gestureBeginOffsetY:Number;
public function PanGesture(target:InteractiveObject = null)
public function PanGesture(target:Object = null)
{
super(target);
}

View file

@ -5,9 +5,13 @@ package org.gestouch.gestures
import org.gestouch.events.RotateGestureEvent;
import org.gestouch.utils.GestureUtils;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
*
* @eventType org.gestouch.events.RotateGestureEvent
*/
[Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")]
/**
* TODO:
@ -24,7 +28,7 @@ package org.gestouch.gestures
protected var _transformVector:Point;
public function RotateGesture(target:InteractiveObject = null)
public function RotateGesture(target:Object = null)
{
super(target);
}

View file

@ -4,9 +4,13 @@ package org.gestouch.gestures
import org.gestouch.core.Touch;
import org.gestouch.events.SwipeGestureEvent;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
*
* @eventType org.gestouch.events.SwipeGestureEvent
*/
[Event(name="gestureSwipe", type="org.gestouch.events.SwipeGestureEvent")]
/**
* TODO:
@ -31,7 +35,7 @@ package org.gestouch.gestures
protected var _decelerationCounter:uint = 0;
public function SwipeGesture(target:InteractiveObject = null)
public function SwipeGesture(target:Object = null)
{
super(target);
}
@ -143,7 +147,7 @@ package org.gestouch.gestures
{
if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
_localLocation = target.globalToLocal(_location);//refresh local location in case target moved
_localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved
dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED,
_location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y));
}
@ -179,7 +183,7 @@ package org.gestouch.gestures
_offset.y = 0;
if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
_localLocation = target.globalToLocal(_location);//refresh local location in case target moved
_localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved
dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED,
_location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y));
}
@ -207,7 +211,7 @@ package org.gestouch.gestures
_offset.x = 0;
if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
_localLocation = target.globalToLocal(_location);//refresh local location in case target moved
_localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved
dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED,
_location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y));
}
@ -234,7 +238,7 @@ package org.gestouch.gestures
{
if (hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
_localLocation = target.globalToLocal(_location);//refresh local location in case target moved
_localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved
dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED,
_location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y));
}

View file

@ -4,11 +4,14 @@ package org.gestouch.gestures
import org.gestouch.core.Touch;
import org.gestouch.events.TapGestureEvent;
import flash.display.InteractiveObject;
import flash.events.TimerEvent;
import flash.utils.Timer;
/**
*
* @eventType org.gestouch.events.TapGestureEvent
*/
[Event(name="gestureTap", type="org.gestouch.events.TapGestureEvent")]
/**
* TODO: check failing conditions (iDevice)
@ -28,7 +31,7 @@ package org.gestouch.gestures
protected var _tapCounter:uint = 0;
public function TapGesture(target:InteractiveObject = null)
public function TapGesture(target:Object = null)
{
super(target);
}

View file

@ -5,10 +5,13 @@ package org.gestouch.gestures
import org.gestouch.events.TransformGestureEvent;
import org.gestouch.utils.GestureUtils;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
*
* @eventType org.gestouch.events.TransformGestureEvent
*/
[Event(name="gestureTransform", type="org.gestouch.events.TransformGestureEvent")]
/**
* @author Pavel fljot
@ -22,7 +25,7 @@ package org.gestouch.gestures
protected var _transformVector:Point;
public function TransformGesture(target:InteractiveObject = null)
public function TransformGesture(target:Object = null)
{
super(target);
}
@ -127,7 +130,7 @@ package org.gestouch.gestures
{
// Note that we dispatch previous location point which gives a way to perform
// accurate UI redraw. See examples project for more info.
prevLocalLocation = target.globalToLocal(prevLocation);
prevLocalLocation = targetAdapter.globalToLocal(prevLocation);
dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.BEGAN,
prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY));
}
@ -138,7 +141,7 @@ package org.gestouch.gestures
{
// Note that we dispatch previous location point which gives a way to perform
// accurate UI redraw. See examples project for more info.
prevLocalLocation = target.globalToLocal(prevLocation);
prevLocalLocation = targetAdapter.globalToLocal(prevLocation);
dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.CHANGED,
prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY));
}

View file

@ -4,9 +4,13 @@ package org.gestouch.gestures
import org.gestouch.core.Touch;
import org.gestouch.events.ZoomGestureEvent;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
*
* @eventType org.gestouch.events.ZoomGestureEvent
*/
[Event(name="gestureZoom", type="org.gestouch.events.ZoomGestureEvent")]
/**
* TODO:
@ -24,7 +28,7 @@ package org.gestouch.gestures
protected var _transformVector:Point;
public function ZoomGesture(target:InteractiveObject = null)
public function ZoomGesture(target:Object = null)
{
super(target);
}

View file

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

View file

@ -1,136 +0,0 @@
package org.gestouch.input
{
import org.gestouch.core.Touch;
import org.gestouch.core.gestouch_internal;
import flash.display.InteractiveObject;
import flash.display.Stage;
import flash.events.EventPhase;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.utils.getTimer;
/**
* @author Pavel fljot
*/
public class MouseInputAdapter extends AbstractInputAdapter
{
private static const PRIMARY_TOUCH_POINT_ID:uint = 0;
protected var _stage:Stage;
public function MouseInputAdapter(stage:Stage)
{
super();
if (!stage)
{
throw new Error("Stage must be not null.");
}
_stage = stage;
}
override public function init():void
{
_stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true);
_stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);// to catch with EventPhase.AT_TARGET
}
override public function dispose():void
{
_stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true);
_stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
uninstallStageListeners();
}
protected function installStageListeners():void
{
// Maximum priority to prevent event hijacking
_stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true, int.MAX_VALUE);
_stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false, int.MAX_VALUE);
_stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, int.MAX_VALUE);
_stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, int.MAX_VALUE);
}
protected function uninstallStageListeners():void
{
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true);
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
_stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true);
_stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
}
protected function mouseDownHandler(event:MouseEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
// Way to prevent MouseEvent/TouchEvent collisions.
// Also helps to ignore possible fake events.
if (_touchesManager.hasTouch(PRIMARY_TOUCH_POINT_ID))
return;
installStageListeners();
var touch:Touch = _touchesManager.createTouch();
touch.target = event.target as InteractiveObject;
touch.id = PRIMARY_TOUCH_POINT_ID;
touch.gestouch_internal::setLocation(new Point(event.stageX, event.stageY));
touch.gestouch_internal::setTime(getTimer());
touch.gestouch_internal::setBeginTime(getTimer());
_touchesManager.addTouch(touch);
_gesturesManager.gestouch_internal::onTouchBegin(touch);
}
protected function mouseMoveHandler(event:MouseEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
// Way to prevent MouseEvent/TouchEvent collisions.
// Also helps to ignore possible fake events.
if (!_touchesManager.hasTouch(PRIMARY_TOUCH_POINT_ID))
return;
var touch:Touch = _touchesManager.getTouch(PRIMARY_TOUCH_POINT_ID);
touch.gestouch_internal::updateLocation(event.stageX, event.stageY);
touch.gestouch_internal::setTime(getTimer());
_gesturesManager.gestouch_internal::onTouchMove(touch);
}
protected function mouseUpHandler(event:MouseEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
// Way to prevent MouseEvent/TouchEvent collisions.
// Also helps to ignore possible fake events.
if (!_touchesManager.hasTouch(PRIMARY_TOUCH_POINT_ID))
return;
var touch:Touch = _touchesManager.getTouch(PRIMARY_TOUCH_POINT_ID);
touch.gestouch_internal::updateLocation(event.stageX, event.stageY);
touch.gestouch_internal::setTime(getTimer());
_gesturesManager.gestouch_internal::onTouchEnd(touch);
_touchesManager.removeTouch(touch);
if (_touchesManager.activeTouchesCount == 0)
{
uninstallStageListeners();
}
}
}
}

View file

@ -0,0 +1,224 @@
package org.gestouch.input
{
import org.gestouch.core.IInputAdapter;
import org.gestouch.core.TouchesManager;
import org.gestouch.core.gestouch_internal;
import flash.display.InteractiveObject;
import flash.display.Stage;
import flash.events.EventPhase;
import flash.events.MouseEvent;
import flash.events.TouchEvent;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
/**
* @author Pavel fljot
*/
public class NativeInputAdapter implements IInputAdapter
{
protected static const MOUSE_TOUCH_POINT_ID:uint = 0;
protected var _stage:Stage;
protected var _explicitlyHandleTouchEvents:Boolean;
protected var _explicitlyHandleMouseEvents:Boolean;
use namespace gestouch_internal;
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
}
public function NativeInputAdapter(stage:Stage,
explicitlyHandleTouchEvents:Boolean = false,
explicitlyHandleMouseEvents:Boolean = false)
{
super();
if (!stage)
{
throw new ArgumentError("Stage must be not null.");
}
_stage = stage;
_explicitlyHandleTouchEvents = explicitlyHandleTouchEvents;
_explicitlyHandleMouseEvents = explicitlyHandleMouseEvents;
}
protected var _touchesManager:TouchesManager;
public function set touchesManager(value:TouchesManager):void
{
_touchesManager = value;
}
//--------------------------------------------------------------------------
//
// Public methods
//
//--------------------------------------------------------------------------
public function init():void
{
if (Multitouch.supportsTouchEvents || _explicitlyHandleTouchEvents)
{
_stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true);
_stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, false);
_stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true);
_stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false);
// Maximum priority to prevent event hijacking and loosing the touch
_stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true, int.MAX_VALUE);
_stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, false, int.MAX_VALUE);
}
if (!Multitouch.supportsTouchEvents || _explicitlyHandleMouseEvents)
{
_stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true);
_stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, false);
}
}
public function onDispose():void
{
_touchesManager = null;
_stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true);
_stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, false);
_stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true);
_stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false);
_stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true);
_stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, false);
_stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true);
_stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, false);
unstallMouseListeners();
}
//--------------------------------------------------------------------------
//
// Private methods
//
//--------------------------------------------------------------------------
protected function installMouseListeners():void
{
_stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true);
_stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false);
// Maximum priority to prevent event hijacking
_stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, int.MAX_VALUE);
_stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, int.MAX_VALUE);
}
protected function unstallMouseListeners():void
{
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true);
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false);
// Maximum priority to prevent event hijacking
_stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true);
_stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false);
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
protected function touchBeginHandler(event:TouchEvent):void
{
// We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET
// (to catch on empty stage) phases only
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;
_touchesManager.onTouchBegin(event.touchPointID, event.stageX, event.stageY, event.target as InteractiveObject);
}
protected function touchMoveHandler(event:TouchEvent):void
{
// We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET
// (to catch on empty stage) phases only
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;
_touchesManager.onTouchMove(event.touchPointID, event.stageX, event.stageY);
}
protected function touchEndHandler(event:TouchEvent):void
{
// We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET
// (to catch on empty stage) phases only
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;
if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"])
{
_touchesManager.onTouchCancel(event.touchPointID, event.stageX, event.stageY);
}
else
{
_touchesManager.onTouchEnd(event.touchPointID, event.stageX, event.stageY);
}
}
protected function mouseDownHandler(event:MouseEvent):void
{
// We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET
// (to catch on empty stage) phases only
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;
const touchAccepted:Boolean = _touchesManager.onTouchBegin(MOUSE_TOUCH_POINT_ID, event.stageX, event.stageY, event.target as InteractiveObject);
if (touchAccepted)
{
installMouseListeners();
}
}
protected function mouseMoveHandler(event:MouseEvent):void
{
// We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET
// (to catch on empty stage) phases only
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;
_touchesManager.onTouchMove(MOUSE_TOUCH_POINT_ID, event.stageX, event.stageY);
}
protected function mouseUpHandler(event:MouseEvent):void
{
// We listen in EventPhase.CAPTURE_PHASE or EventPhase.AT_TARGET
// (to catch on empty stage) phases only
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;
_touchesManager.onTouchEnd(MOUSE_TOUCH_POINT_ID, event.stageX, event.stageY);
if (_touchesManager.activeTouchesCount == 0)
{
unstallMouseListeners();
}
}
}
}

View file

@ -1,17 +1,33 @@
package org.gestouch.input
{
import org.gestouch.input.AbstractInputAdapter;
import org.gestouch.core.IInputAdapter;
import org.gestouch.core.TouchesManager;
/**
* TODO: You can implement your own TUIO Input Adapter (and supply touchesManager with
* touch info), but IMHO it is way easier to use NativeInputAdapter and any TUIO library
* and manually dispatch native TouchEvents using DisplayObjectContainer#getObjectsUnderPoint()
*
* @see NativeInputAdapter
* @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/DisplayObjectContainer.html#getObjectsUnderPoint() DisplayObjectContainer#getObjectsUnderPoint()
*
* @author Pavel fljot
*/
public class TUIOInputAdapter extends AbstractInputAdapter
public class TUIOInputAdapter implements IInputAdapter
{
public function TUIOInputAdapter()
public function init():void
{
}
public function onDispose():void
{
}
public function set touchesManager(value:TouchesManager):void
{
super();
//TODO
}
}
}
}

View file

@ -1,186 +0,0 @@
package org.gestouch.input
{
import org.gestouch.core.Touch;
import org.gestouch.core.gestouch_internal;
import flash.display.InteractiveObject;
import flash.display.Stage;
import flash.events.EventPhase;
import flash.events.TouchEvent;
import flash.geom.Point;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.utils.getTimer;
/**
* @author Pavel fljot
*/
public class TouchInputAdapter extends AbstractInputAdapter
{
protected var _stage:Stage;
/**
* The hash map of touches instantiated via TouchEvent.
* Used to avoid collisions (double processing) with MouseInputAdapter.
*
* TODO: any better way?
*/
protected var _touchesMap:Object = {};
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
}
public function TouchInputAdapter(stage:Stage)
{
super();
if (!stage)
{
throw new Error("Stage must be not null.");
}
_stage = stage;
}
override public function init():void
{
_stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true);
_stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);// to catch with EventPhase.AT_TARGET
}
override public function dispose():void
{
_stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true);
_stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
uninstallStageListeners();
}
protected function installStageListeners():void
{
// Maximum priority to prevent event hijacking
_stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true, int.MAX_VALUE);
_stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false, int.MAX_VALUE);
_stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true, int.MAX_VALUE);
_stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, false, int.MAX_VALUE);
}
protected function uninstallStageListeners():void
{
_stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true);
_stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler);
_stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true);
_stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler);
}
protected function touchBeginHandler(event:TouchEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
// Way to prevent MouseEvent/TouchEvent collisions.
// Also helps to ignore possible fake events.
if (_touchesManager.hasTouch(event.touchPointID))
return;
installStageListeners();
var touch:Touch = _touchesManager.createTouch();
touch.id = event.touchPointID;
touch.target = event.target as InteractiveObject;
touch.gestouch_internal::setLocation(new Point(event.stageX, event.stageY));
touch.sizeX = event.sizeX;
touch.sizeY = event.sizeY;
touch.pressure = event.pressure;
//TODO: conditional compilation?
if (event.hasOwnProperty("timestamp"))
{
touch.gestouch_internal::setTime(event["timestamp"]);
touch.gestouch_internal::setBeginTime(event["timestamp"]);
}
else
{
touch.gestouch_internal::setTime(getTimer());
touch.gestouch_internal::setBeginTime(getTimer());
}
_touchesManager.addTouch(touch);
_touchesMap[touch.id] = true;
_gesturesManager.gestouch_internal::onTouchBegin(touch);
}
protected function touchMoveHandler(event:TouchEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
// Way to prevent MouseEvent/TouchEvent collisions.
// Also helps to ignore possible fake events.
if (!_touchesManager.hasTouch(event.touchPointID) || !_touchesMap.hasOwnProperty(event.touchPointID))
return;
var touch:Touch = _touchesManager.getTouch(event.touchPointID);
touch.gestouch_internal::updateLocation(event.stageX, event.stageY);
touch.sizeX = event.sizeX;
touch.sizeY = event.sizeY;
touch.pressure = event.pressure;
//TODO: conditional compilation?
if (event.hasOwnProperty("timestamp"))
{
touch.gestouch_internal::setTime(event["timestamp"]);
}
else
{
touch.gestouch_internal::setTime(getTimer());
}
_gesturesManager.gestouch_internal::onTouchMove(touch);
}
protected function touchEndHandler(event:TouchEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
// Way to prevent MouseEvent/TouchEvent collisions.
// Also helps to ignore possible fake events.
if (!_touchesManager.hasTouch(event.touchPointID))
return;
var touch:Touch = _touchesManager.getTouch(event.touchPointID);
touch.gestouch_internal::updateLocation(event.stageX, event.stageY);
touch.sizeX = event.sizeX;
touch.sizeY = event.sizeY;
touch.pressure = event.pressure;
//TODO: conditional compilation?
if (event.hasOwnProperty("timestamp"))
{
touch.gestouch_internal::setTime(event["timestamp"]);
}
else
{
touch.gestouch_internal::setTime(getTimer());
}
_gesturesManager.gestouch_internal::onTouchEnd(touch);
_touchesManager.removeTouch(touch);
delete _touchesMap[touch.id];
if (_touchesManager.activeTouchesCount == 0)
{
uninstallStageListeners();
}
// TODO: handle cancelled touch:
// if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"] && ...
}
}
}

View file

@ -1,5 +1,6 @@
package org.gestouch.utils
{
import flash.geom.Point;
import flash.system.Capabilities;
/**
* Set of constants.
@ -24,5 +25,6 @@ package org.gestouch.utils
* Precalculated coefficient Math.PI * 2
*/
public static const PI_DOUBLE:Number = Math.PI * 2;
public static const GLOBAL_ZERO:Point = new Point();
}
}

View file

@ -1 +1 @@
project.version = 0.3.1
project.version = 0.4-alpha