Massive refactoring of input layer

This commit is contained in:
Pavel fljot 2012-05-29 17:03:16 +03:00
parent e0a892654d
commit e9132fec9b
18 changed files with 653 additions and 740 deletions

View file

@ -0,0 +1,86 @@
package org.gestouch.core
{
import flash.display.DisplayObject;
/**
* @author Pavel fljot
*/
public class Gestouch
{
{
initClass();
}
/** @private */
private static var _inputAdapter:IInputAdapter;
/**
*
*/
public static function get inputAdapter():IInputAdapter
{
return _inputAdapter;
}
public static function set inputAdapter(value:IInputAdapter):void
{
if (_inputAdapter == value)
return;
_inputAdapter = value;
if (inputAdapter)
{
inputAdapter.touchesManager = touchesManager;
inputAdapter.init();
}
}
private static var _touchesManager:TouchesManager;
/**
*
*/
public static function get touchesManager():TouchesManager
{
return _touchesManager ||= new TouchesManager(gesturesManager);
}
private static var _gesturesManager:GesturesManager;
public static function get gesturesManager():GesturesManager
{
return _gesturesManager ||= new GesturesManager();
}
public static function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void
{
gesturesManager.gestouch_internal::addDisplayListAdapter(targetClass, adapter);
}
public static function addTouchHitTester(hitTester:ITouchHitTester, priority:int = 0):void
{
touchesManager.gestouch_internal::addTouchHitTester(hitTester, priority);
}
public static function removeTouchHitTester(hitTester:ITouchHitTester):void
{
touchesManager.gestouch_internal::removeInputAdapter(hitTester);
}
// public static function getTouches(target:Object = null):Array
// {
// return touchesManager.getTouches(target);
// }
private static function initClass():void
{
addDisplayListAdapter(DisplayObject, new DisplayListAdapter());
}
}
}

View file

@ -1,32 +1,24 @@
package org.gestouch.core
{
import flash.utils.getQualifiedClassName;
import org.gestouch.gestures.Gesture;
import org.gestouch.input.MouseInputAdapter;
import org.gestouch.input.TouchInputAdapter;
import org.gestouch.input.NativeInputAdapter;
import flash.display.DisplayObject;
import flash.display.Shape;
import flash.display.Stage;
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.ui.Multitouch;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;
/**
* @author Pavel fljot
*/
public class GesturesManager implements IGesturesManager
public class GesturesManager
{
public static var initDefaultInputAdapter:Boolean = true;
private static var _instance:IGesturesManager;
private static var _allowInstantiation:Boolean;
protected const _touchesManager:ITouchesManager = TouchesManager.getInstance();
protected const _displayListAdaptersMap:Dictionary = new Dictionary();
protected const _frameTickerShape:Shape = new Shape();
protected var _inputAdapters:Vector.<IInputAdapter> = new Vector.<IInputAdapter>();
protected var _displayListAdaptersMap:Dictionary = new Dictionary();
protected var _stage:Stage;
protected var _gesturesMap:Dictionary = new Dictionary(true);
protected var _gesturesForTouchMap:Dictionary = new Dictionary();
protected var _gesturesForTargetMap:Dictionary = new Dictionary(true);
@ -34,120 +26,36 @@ package org.gestouch.core
protected var _dirtyGesturesLength:uint = 0;
protected var _dirtyGesturesMap:Dictionary = new Dictionary(true);
use namespace gestouch_internal;
public function GesturesManager()
{
if (Object(this).constructor == GesturesManager && !_allowInstantiation)
{
throw new Error("Do not instantiate GesturesManager directly.");
}
{
_touchesManager.gesturesManager = this;
_displayListAdaptersMap[DisplayObject] = new DisplayListAdapter();
}
public function get inputAdapters():Vector.<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.init();
}
public function removeInputAdapter(inputAdapter:IInputAdapter, dispose:Boolean = true):void
{
if (!inputAdapter)
{
throw new Error("Input adapter must be non null.");
}
var index:int = _inputAdapters.indexOf(inputAdapter);
if (index == -1)
{
throw new Error("This input manager is not registered.");
}
_inputAdapters.splice(index, 1);
if (dispose)
{
inputAdapter.dispose();
}
}
public function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void
gestouch_internal function addDisplayListAdapter(targetClass:Class, adapter:IDisplayListAdapter):void
{
if (!targetClass || !adapter)
{
throw new Error("Argument error: both arguments required.");
}
_displayListAdaptersMap[targetClass] = adapter;
}
//--------------------------------------------------------------------------
//
// Private methods
//
//--------------------------------------------------------------------------
protected function installStage(stage:Stage):void
protected function installDefaultInputAdapter(stage:Stage):void
{
_stage = stage;
if (Multitouch.supportsTouchEvents)
{
addInputAdapter(new TouchInputAdapter(stage));
}
else
{
addInputAdapter(new MouseInputAdapter(stage));
}
Gestouch.inputAdapter ||= new NativeInputAdapter(stage);
}
@ -201,14 +109,14 @@ package org.gestouch.core
_gesturesMap[gesture] = true;
if (GesturesManager.initDefaultInputAdapter)
if (!Gestouch.inputAdapter)
{
var targetAsDO:DisplayObject = target as DisplayObject;
if (targetAsDO)
{
if (!_stage && targetAsDO.stage)
if (targetAsDO.stage)
{
installStage(targetAsDO.stage);
installDefaultInputAdapter(targetAsDO.stage);
}
else
{
@ -274,8 +182,8 @@ package org.gestouch.core
otherGesture.state == GestureState.POSSIBLE)
{
if (otherTarget == target ||
gesture.gestouch_internal::targetAdapter.contains(otherTarget) ||
otherGesture.gestouch_internal::targetAdapter.contains(target)
gesture.targetAdapter.contains(otherTarget) ||
otherGesture.targetAdapter.contains(target)
)
{
var gestureDelegate:IGestureDelegate = gesture.delegate;
@ -286,7 +194,7 @@ package org.gestouch.core
(!gestureDelegate || !gestureDelegate.gesturesShouldRecognizeSimultaneously(gesture, otherGesture)) &&
(!otherGestureDelegate || !otherGestureDelegate.gesturesShouldRecognizeSimultaneously(otherGesture, gesture)))
{
otherGesture.gestouch_internal::setState_internal(GestureState.FAILED);
otherGesture.setState_internal(GestureState.FAILED);
}
}
}
@ -362,7 +270,7 @@ package org.gestouch.core
// Check for state because previous (i+1) gesture may already abort current (i) one
if (gesture.state != GestureState.FAILED)
{
gesture.gestouch_internal::touchBeginHandler(touch);
gesture.touchBeginHandler(touch);
}
else
{
@ -388,7 +296,7 @@ package org.gestouch.core
if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id))
{
gesture.gestouch_internal::touchMoveHandler(touch);
gesture.touchMoveHandler(touch);
}
else
{
@ -415,7 +323,7 @@ package org.gestouch.core
if (gesture.state != GestureState.FAILED && gesture.isTrackingTouch(touch.id))
{
gesture.gestouch_internal::touchEndHandler(touch);
gesture.touchEndHandler(touch);
}
}
@ -441,9 +349,9 @@ package org.gestouch.core
{
var target:DisplayObject = event.target as DisplayObject;
target.removeEventListener(Event.ADDED_TO_STAGE, gestureTarget_addedToStageHandler);
if (!_stage && GesturesManager.initDefaultInputAdapter)
if (!Gestouch.inputAdapter)
{
installStage(target.stage);
installDefaultInputAdapter(target.stage);
}
}

View file

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

View file

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

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,18 +0,0 @@
package org.gestouch.core
{
/**
* @author Pavel fljot
*/
public interface ITouchesManager
{
function set gesturesManager(value:IGesturesManager):void;
function get activeTouchesCount():uint;
function onTouchBegin(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number, target:Object):void;
function onTouchMove(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void;
function onTouchEnd(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void;
function onInputAdapterDispose(inputAdapter:IInputAdapter):void;
}
}

View file

@ -25,6 +25,8 @@ package org.gestouch.core
public var pressure:Number;
// public var lastMove:Point;
use namespace gestouch_internal;
public function Touch(id:uint = 0)
@ -38,9 +40,9 @@ package org.gestouch.core
{
return _location.clone();
}
gestouch_internal function setLocation(value:Point, time:uint):void
gestouch_internal function setLocation(x:Number, y:Number, time:uint):void
{
_location = value;
_location = new Point(x, y);
_beginLocation = _location.clone();
_previousLocation = _location.clone();
@ -59,7 +61,7 @@ package org.gestouch.core
}
else
{
gestouch_internal::setLocation(new Point(x, y), time);
setLocation(x, y, time);
}
}

View file

@ -1,32 +1,30 @@
package org.gestouch.core
{
import flash.display.InteractiveObject;
import flash.display.Stage;
import flash.geom.Point;
import flash.utils.Dictionary;
import flash.utils.getTimer;
/**
* @author Pavel fljot
*/
public class TouchesManager implements ITouchesManager
public class TouchesManager
{
private static var _instance:ITouchesManager;
private static var _allowInstantiation:Boolean;
protected var _gesturesManager:GesturesManager;
protected var _touchesMap:Object = {};
protected var _hitTesters:Vector.<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.");
}
}
protected var _gesturesManager:IGesturesManager;
public function set gesturesManager(value:IGesturesManager):void
{
_gesturesManager = value;
_gesturesManager = gesturesManager;
addTouchHitTester(new DefaultTouchHitTester());
}
@ -37,132 +35,167 @@ package org.gestouch.core
}
public static function setImplementation(value:ITouchesManager):void
public function getTouches(target:Object = null):Array
{
if (!value)
const touches:Array = [];
if (!target || target is Stage)
{
throw new ArgumentError("value cannot be null.");
}
if (_instance)
{
throw new Error("Instance of TouchesManager is already created. If you want to have own implementation of single TouchesManager instace, you should set it earlier.");
}
_instance = value;
}
public static function getInstance():ITouchesManager
{
if (!_instance)
{
_allowInstantiation = true;
_instance = new TouchesManager();
_allowInstantiation = false;
}
return _instance;
}
public function onTouchBegin(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number, target:Object):void
{
var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary;
if (overlappingTouches)
{
// In case we listen to both TouchEvents and MouseEvents, one of them will come first
// (right now looks like MouseEvent dispatches first, but who know what Adobe will
// do tomorrow). This check is to filter out the one comes second.
for each (var registeredTouch:Touch in overlappingTouches)
// return all touches
var i:uint = 0;
for each (var touch:Touch in _touchesMap)
{
if (registeredTouch.target == target)
return;
touches[i++] = touch;
}
}
else
{
overlappingTouches = _touchesMap[touchID] = new Dictionary();
_activeTouchesCount++;
//TODO
}
var touch:Touch = createTouch();
return touches;
}
gestouch_internal function addTouchHitTester(touchHitTester:ITouchHitTester, priority:int = 0):void
{
if (!touchHitTester)
{
throw new ArgumentError("Argument must be non null.");
}
if (_hitTesters.indexOf(touchHitTester) == -1)
{
_hitTesters.push(touchHitTester);
}
_hitTesterPrioritiesMap[touchHitTester] = priority;
// Sort hit testers using their priorities
_hitTesters.sort(hitTestersSorter);
}
gestouch_internal function removeInputAdapter(touchHitTester:ITouchHitTester):void
{
if (!touchHitTester)
{
throw new ArgumentError("Argument must be non null.");
}
var index:int = _hitTesters.indexOf(touchHitTester);
if (index == -1)
{
throw new Error("This touchHitTester is not registered.");
}
_hitTesters.splice(index, 1);
delete _hitTesterPrioritiesMap[touchHitTester];
}
gestouch_internal function onTouchBegin(touchID:uint, x:Number, y:Number, nativeTarget:InteractiveObject = null):Boolean
{
if (touchID in _touchesMap)
return false;// touch with specified ID is already registered and being tracked
const location:Point = new Point(x, y);
for each (var registeredTouch:Touch in _touchesMap)
{
// Check if touch at the same location exists.
// In case we listen to both TouchEvents and MouseEvents, one of them will come first
// (right now looks like MouseEvent dispatched first, but who know what Adobe will
// do tomorrow). This check helps to filter out the one comes after.
// NB! According to the tests with some IR multitouch frame and Windows computer
// TouchEvent comes first, but the following MouseEvent has slightly offset location
// (1px both axis). That is why Point#distance() used instead of Point#equals()
if (Point.distance(registeredTouch.location, location) < 2)
return false;
}
const touch:Touch = createTouch();
touch.id = touchID;
touch.target = target;
touch.gestouch_internal::setLocation(new Point(x, y), getTimer());
overlappingTouches[inputAdapter] = touch;
_gesturesManager.gestouch_internal::onTouchBegin(touch);
}
public function onTouchMove(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void
{
var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary;
if (!overlappingTouches)
return;//this touch isn't properly registered.. some fake
var touch:Touch = overlappingTouches[inputAdapter] as Touch;
if (!touch)
return;//touch with this ID from this inputAdapter is not registered. see workaround reason above
touch.gestouch_internal::updateLocation(x, y, getTimer());
_gesturesManager.gestouch_internal::onTouchMove(touch);
}
public function onTouchEnd(inputAdapter:IInputAdapter, touchID:uint, x:Number, y:Number):void
{
var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary;
if (!overlappingTouches)
return;//this touch isn't properly registered.. some fake
var touch:Touch = overlappingTouches[inputAdapter] as Touch;
if (!touch)
return;//touch with this ID from this inputAdapter is not registered. see workaround reason above
touch.gestouch_internal::updateLocation(x, y, getTimer());
delete overlappingTouches[inputAdapter];
var empty:Boolean = true;
for (var key:Object in overlappingTouches)
var target:Object;
var altTarget:Object;
for each (var hitTester:ITouchHitTester in _hitTesters)
{
empty = false;
break;
}
if (empty)
{
delete _touchesMap[touchID];
_activeTouchesCount--;
}
_gesturesManager.gestouch_internal::onTouchEnd(touch);
}
/**
* Must be called by IInputAdapter#dispose() to remove all the touches invoked by it.
*/
public function onInputAdapterDispose(inputAdapter:IInputAdapter):void
{
for (var touchID:Object in _touchesMap)
{
var overlappingTouches:Dictionary = _touchesMap[touchID] as Dictionary;
if (overlappingTouches[inputAdapter])
target = hitTester.hitTest(location, nativeTarget);
if (target)
{
delete overlappingTouches[inputAdapter];
var empty:Boolean = true;
for (var key:Object in overlappingTouches)
if ((target is Stage))
{
empty = false;
break;
// NB! Target is flash.display::Stage is a special case. If it is true, we want
// to give a try to a lower-priority (Stage3D) hit-testers.
altTarget = target;
continue;
}
if (empty)
else
{
delete _touchesMap[touchID];
_activeTouchesCount--;
// We found a target.
break;
}
}
}
if (!target && !altTarget)
{
throw new Error("Not touch target found (hit test)." +
"Something is wrong, at least flash.display::Stage should be found." +
"See Gestouch#addTouchHitTester() and Gestouch#inputAdapter.");
}
touch.target = target || altTarget;
touch.setLocation(x, y, getTimer());
_touchesMap[touchID] = touch;
_activeTouchesCount++;
_gesturesManager.onTouchBegin(touch);
return true;
}
gestouch_internal function onTouchMove(touchID:uint, x:Number, y:Number):void
{
const touch:Touch = _touchesMap[touchID] as Touch;
if (!touch)
return;// touch with specified ID isn't registered
touch.updateLocation(x, y, getTimer());
_gesturesManager.onTouchMove(touch);
}
gestouch_internal function onTouchEnd(touchID:uint, x:Number, y:Number):void
{
const touch:Touch = _touchesMap[touchID] as Touch;
if (!touch)
return;// touch with specified ID isn't registered
touch.updateLocation(x, y, getTimer());
delete _touchesMap[touchID];
_activeTouchesCount--;
_gesturesManager.onTouchEnd(touch);
}
gestouch_internal function onTouchCancel(touchID:uint, x:Number, y:Number):void
{
const touch:Touch = _touchesMap[touchID] as Touch;
if (!touch)
return;// touch with specified ID isn't registered
touch.updateLocation(x, y, getTimer());
delete _touchesMap[touchID];
_activeTouchesCount--;
_gesturesManager.onTouchCancel(touch);
}
@ -171,5 +204,38 @@ package org.gestouch.core
//TODO: pool
return new Touch();
}
/**
* Sorts from higher priority to lower. Items with the same priority keep the order
* of addition, e.g.:
* add(a), add(b), add(c, -1), add(d, 1) will be ordered to
* d, a, b, c
*/
protected function hitTestersSorter(x:ITouchHitTester, y:ITouchHitTester):Number
{
const d:int = int(_hitTesterPrioritiesMap[x]) - int(_hitTesterPrioritiesMap[y]);
if (d > 0)
return -1;
else if (d < 0)
return 1;
return _hitTesters.indexOf(x) > _hitTesters.indexOf(y) ? 1 : -1;
}
}
}
}
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,6 @@
package org.gestouch.extensions.starling
{
import starling.core.Starling;
import starling.display.DisplayObject;
import starling.display.DisplayObjectContainer;
@ -14,22 +15,22 @@ package org.gestouch.extensions.starling
*/
final public class StarlingDisplayListAdapter implements IDisplayListAdapter
{
private var _targetWeekStorage:Dictionary;
private var targetWeekStorage:Dictionary;
public function StarlingDisplayListAdapter(target:DisplayObject = null)
{
if (target)
{
_targetWeekStorage = new Dictionary(true);
_targetWeekStorage[target] = true;
targetWeekStorage = new Dictionary(true);
targetWeekStorage[target] = true;
}
}
public function get target():Object
{
for (var key:Object in _targetWeekStorage)
for (var key:Object in targetWeekStorage)
{
return key;
}
@ -39,6 +40,7 @@ package org.gestouch.extensions.starling
public function globalToLocal(point:Point):Point
{
point = StarlingUtils.adjustGlobalPoint(Starling.current, point);
return (target as DisplayObject).globalToLocal(point);
}

View file

@ -1,180 +0,0 @@
package org.gestouch.extensions.starling
{
import starling.core.Starling;
import org.gestouch.input.AbstractInputAdapter;
import flash.events.EventPhase;
import flash.events.MouseEvent;
import flash.events.TouchEvent;
import flash.geom.Point;
import flash.ui.Multitouch;
/**
* @author Pavel fljot
*/
public class StarlingInputAdapter extends AbstractInputAdapter
{
private static const PRIMARY_TOUCH_POINT_ID:uint = 0;
protected var _starling:Starling;
public function StarlingInputAdapter(starling:Starling)
{
super();
if (!starling)
{
throw new Error("Argument error.");
}
_starling = starling;
}
override public function init():void
{
// We want to begin tracking only those touches that happen on Stage3D layer,
// e.g. event.target == nativeStage. That's we don't listen for touch begin
// in capture phase (as we do for native display list).
if (Multitouch.supportsTouchEvents)
{
_starling.nativeStage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
}
else
{
_starling.nativeStage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
}
}
override public function dispose():void
{
_starling.nativeStage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
_starling.nativeStage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
uninstallStageListeners();
_starling = null;
_touchesManager.onInputAdapterDispose(this);
_touchesManager = null;
}
protected function installStageListeners():void
{
// Maximum priority to prevent event hijacking
if (Multitouch.supportsTouchEvents)
{
_starling.nativeStage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true, int.MAX_VALUE);
_starling.nativeStage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false, int.MAX_VALUE);
_starling.nativeStage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true, int.MAX_VALUE);
_starling.nativeStage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, false, int.MAX_VALUE);
}
else
{
_starling.nativeStage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true, int.MAX_VALUE);
_starling.nativeStage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false, int.MAX_VALUE);
_starling.nativeStage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, int.MAX_VALUE);
_starling.nativeStage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, int.MAX_VALUE);
}
}
protected function uninstallStageListeners():void
{
_starling.nativeStage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true);
_starling.nativeStage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false);
_starling.nativeStage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true);
_starling.nativeStage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, false);
_starling.nativeStage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true);
_starling.nativeStage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false);
_starling.nativeStage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true);
_starling.nativeStage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false);
}
protected function mouseDownHandler(event:MouseEvent):void
{
// We ignore event with bubbling phase because it happened on some native InteractiveObject,
// which basically hovers Stage3D layer. So we treat it as if Starling wouldn't recieve any input.
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;
var target:Object = _starling.stage.hitTest(new Point(event.stageX, event.stageY), true);
_touchesManager.onTouchBegin(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY, target);
if (_touchesManager.activeTouchesCount > 0)
{
installStageListeners();
}
}
protected function mouseMoveHandler(event:MouseEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchMove(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY);
}
protected function mouseUpHandler(event:MouseEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchEnd(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY);
if (_touchesManager.activeTouchesCount == 0)
{
uninstallStageListeners();
}
}
protected function touchBeginHandler(event:TouchEvent):void
{
// We ignore event with bubbling phase because it happened on some native InteractiveObject,
// which basically hovers Stage3D layer. So we treat it as if Starling wouldn't recieve any input.
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;
var target:Object = _starling.stage.hitTest(new Point(event.stageX, event.stageY), true);
_touchesManager.onTouchBegin(this, event.touchPointID, event.stageX, event.stageY, target);
if (_touchesManager.activeTouchesCount > 0)
{
installStageListeners();
}
}
protected function touchMoveHandler(event:TouchEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchMove(this, event.touchPointID, event.stageX, event.stageY);
}
protected function touchEndHandler(event:TouchEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchEnd(this, event.touchPointID, event.stageX, event.stageY);
if (_touchesManager.activeTouchesCount == 0)
{
uninstallStageListeners();
}
// TODO: handle cancelled touch:
// if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"] && ...
}
}
}

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,10 +1,10 @@
package org.gestouch.gestures
{
import org.gestouch.core.Gestouch;
import org.gestouch.core.GestureState;
import org.gestouch.core.GesturesManager;
import org.gestouch.core.IGestureDelegate;
import org.gestouch.core.IGestureTargetAdapter;
import org.gestouch.core.IGesturesManager;
import org.gestouch.core.Touch;
import org.gestouch.core.gestouch_internal;
import org.gestouch.events.GestureStateEvent;
@ -20,9 +20,6 @@ package org.gestouch.gestures
* Base class for all gestures. Gesture is essentially a detector that tracks touch points
* in order detect specific gesture motion and form gesture event on target.
*
* TODO:
* -
*
* @author Pavel fljot
*/
public class Gesture extends EventDispatcher
@ -35,7 +32,7 @@ package org.gestouch.gestures
public static const DEFAULT_SLOP:uint = Math.round(20 / 252 * flash.system.Capabilities.screenDPI);
protected const _gesturesManager:IGesturesManager = GesturesManager.getInstance();
protected const _gesturesManager:GesturesManager = Gestouch.gesturesManager;
/**
* Map (generic object) of tracking touch points, where keys are touch points IDs.
*/
@ -175,7 +172,6 @@ package org.gestouch.gestures
*/
public function get location():Point
{
//TODO: to clone or not clone? performance & convention or ...
return _location.clone();
}

View file

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

View file

@ -1,103 +0,0 @@
package org.gestouch.input
{
import flash.display.Stage;
import flash.events.EventPhase;
import flash.events.MouseEvent;
/**
* @author Pavel fljot
*/
public class MouseInputAdapter extends AbstractInputAdapter
{
private static const PRIMARY_TOUCH_POINT_ID:uint = 0;
protected var _stage:Stage;
public function MouseInputAdapter(stage:Stage)
{
super();
if (!stage)
{
throw new Error("Stage must be not null.");
}
_stage = stage;
}
override public function init():void
{
_stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true);
_stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);// to catch with EventPhase.AT_TARGET
}
override public function dispose():void
{
_stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, true);
_stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
uninstallStageListeners();
_touchesManager.onInputAdapterDispose(this);
_touchesManager = null;
}
protected function installStageListeners():void
{
// Maximum priority to prevent event hijacking
_stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true, int.MAX_VALUE);
_stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false, int.MAX_VALUE);
_stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, int.MAX_VALUE);
_stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, int.MAX_VALUE);
}
protected function uninstallStageListeners():void
{
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, true);
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
_stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true);
_stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
}
protected function mouseDownHandler(event:MouseEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchBegin(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY, event.target);
if (_touchesManager.activeTouchesCount > 0)
{
installStageListeners();
}
}
protected function mouseMoveHandler(event:MouseEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchMove(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY);
}
protected function mouseUpHandler(event:MouseEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchEnd(this, PRIMARY_TOUCH_POINT_ID, event.stageX, event.stageY);
if (_touchesManager.activeTouchesCount == 0)
{
uninstallStageListeners();
}
}
}
}

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,117 +0,0 @@
package org.gestouch.input
{
import flash.display.Stage;
import flash.events.EventPhase;
import flash.events.TouchEvent;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
/**
* @author Pavel fljot
*/
public class TouchInputAdapter extends AbstractInputAdapter
{
protected var _stage:Stage;
/**
* The hash map of touches instantiated via TouchEvent.
* Used to avoid collisions (double processing) with MouseInputAdapter.
*
* TODO: any better way?
*/
protected var _touchesMap:Object = {};
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
}
public function TouchInputAdapter(stage:Stage)
{
super();
if (!stage)
{
throw new Error("Stage must be not null.");
}
_stage = stage;
}
override public function init():void
{
_stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true);
_stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);// to catch with EventPhase.AT_TARGET
}
override public function dispose():void
{
_stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler, true);
_stage.removeEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
uninstallStageListeners();
_touchesManager.onInputAdapterDispose(this);
_touchesManager = null;
}
protected function installStageListeners():void
{
// Maximum priority to prevent event hijacking
_stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true, int.MAX_VALUE);
_stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, false, int.MAX_VALUE);
_stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, true, int.MAX_VALUE);
_stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler, false, int.MAX_VALUE);
}
protected function uninstallStageListeners():void
{
_stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler, true);
_stage.removeEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler);
_stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler, true);
_stage.removeEventListener(TouchEvent.TOUCH_END, touchEndHandler);
}
protected function touchBeginHandler(event:TouchEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchBegin(this, event.touchPointID, event.stageX, event.stageY, event.target);
if (_touchesManager.activeTouchesCount > 0)
{
installStageListeners();
}
}
protected function touchMoveHandler(event:TouchEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchMove(this, event.touchPointID, event.stageX, event.stageY);
}
protected function touchEndHandler(event:TouchEvent):void
{
if (event.eventPhase == EventPhase.BUBBLING_PHASE)
return;//we listen in capture or at_target (to catch on empty stage)
_touchesManager.onTouchEnd(this, event.touchPointID, event.stageX, event.stageY);
if (_touchesManager.activeTouchesCount == 0)
{
uninstallStageListeners();
}
// TODO: handle cancelled touch:
// if (event.hasOwnProperty("isTouchPointCanceled") && event["isTouchPointCanceled"] && ...
}
}
}