commit fca1ae4bc94cc862ed325197c8560b8c289e26f8 Author: Pavel fljot Date: Fri Apr 29 18:27:52 2011 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9955068 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +_resources/ +bin/ +bin-debug/ +bin-release/ +wiki/ + +.svn/ + +Thumbs.db +.DS_Store +build.properties \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..d376ddf --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Gestouch + + + + + + com.powerflasher.fdt.core.FlashBuilder + + + + + + com.powerflasher.fdt.core.FlashNature + + diff --git a/.settings/com.powerflasher.fdt.classpath b/.settings/com.powerflasher.fdt.classpath new file mode 100644 index 0000000..75e92cc --- /dev/null +++ b/.settings/com.powerflasher.fdt.classpath @@ -0,0 +1,13 @@ + + + src + frameworks/libs/air/airglobal.swc + frameworks/libs/mobile/mobilecomponents.swc + frameworks/libs/textLayout.swc + frameworks/libs/framework.swc + frameworks/libs/spark.swc + frameworks/libs/sparkskins.swc + frameworks/libs/air/servicemonitor.swc + frameworks/themes/Mobile/mobile.swc + frameworks/libs/mx/mx.swc + diff --git a/.settings/com.powerflasher.fdt.core.prefs b/.settings/com.powerflasher.fdt.core.prefs new file mode 100644 index 0000000..f2cc2b0 --- /dev/null +++ b/.settings/com.powerflasher.fdt.core.prefs @@ -0,0 +1,15 @@ +#Mon Apr 25 18:38:51 EEST 2011 +com.powerflasher.fdt.core.CompatiblePlayers=AIR_Debug_Launcher +com.powerflasher.fdt.core.CompilerArguments=-load-config\="{flexSDK}/frameworks/airmobile-config.xml"\n-target-player\={playerVersion}\n-library-path+\="{flexSDK}/frameworks/locale/en_US" +com.powerflasher.fdt.core.DefaultOutputFolder=bin +com.powerflasher.fdt.core.DefaultPlayer=AIR_Debug_Launcher +com.powerflasher.fdt.core.Language=AS3 +com.powerflasher.fdt.core.PassClasspath=true +com.powerflasher.fdt.core.PassMainclass=true +com.powerflasher.fdt.core.PassRsls=false +com.powerflasher.fdt.core.PassSwcs=true +com.powerflasher.fdt.core.PlayerVersion=10.2 +com.powerflasher.fdt.core.ProjectTypeHint=Flex 4.5 Mobile +com.powerflasher.fdt.core.Runtime=AIR +com.powerflasher.fdt.core.SdkName=Flex 4.5 +eclipse.preferences.version=1 diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..2140b16 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Sun Apr 17 10:41:00 EEST 2011 +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..17ee0ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2011 the original author or authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.textile b/README.textile new file mode 100644 index 0000000..4f6d6b4 --- /dev/null +++ b/README.textile @@ -0,0 +1,61 @@ +h2. Gestouch: NUI gestures detection framework for mouse, touch and multitouch AS3 development. + +Gestouch is a very basic framework that helps you to detect gestures when you develop NUI (Natural User Interface). +Last versions of Flash Player and AIR have built-in touch and multitouch support, but the gestures support is quite poor: only small set of gestures are supported, they depend on OS, they are not customizable, only one can be processed at the same time and, finally, you are forced to use either raw TouchEvents, or gestures (@see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/ui/Multitouch.html#inputMode). + +This framework is aimed to simplify the process of detecting raw TouchEvents and processing them into a specific gesture(s). There are several built-in common gestures, but you are welcome write your own. + +Features: + +* Doesn't require any additional software (uses runtimes build-in touch support); +* Doesn't break your DisplayList architecture (doesn't require any wrappers, so could be easily used for Flex development); +* Basically allows you to write multi-user interfaces; +* Extendable. You can write your own application-specific gestures; +* Open-source and easy to use. + + +h3. Getting Started + +* "Introduction video":http://www.youtube.com/watch?v=NjkmB8rfQjY +* "Disclaimer and Architecture Overview":http://github.com/fljot/Gestouch/wiki/Overview +* "Usage":http://github.com/fljot/Gestouch/wiki/Usage + + +h3. Code + +* "Gestouch Framework":http://github.com/fljot/Gestouch +* "Gestouch Examples":http://github.com/fljot/GestouchExamples + + +h3. Roadmap, TODOs + +* Simulator (for testing multitouch gestures without special devices) +* Chained gestures concept / behaviors. +* 3-fingers (3D) gestures (two fingers still, one moving) + + +h3. Contribution, Donations + +Contribute, share. Found it useful, nothing to add? Hire me for some project. + + +h3. News: + +* "Follow me on Twitter":http://twitter.com/fljot for latest updates + + +h3. Other Resources + +* "GestureWorks":http://www.gestureworks.com +* "TUIO":http://www.tuio.org + +* "Google: Flash + Multitouch":http://www.google.com/search?q=flash+multitouch + + +h2. License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/build.template.properties b/build.template.properties new file mode 100644 index 0000000..3eedc0a --- /dev/null +++ b/build.template.properties @@ -0,0 +1,12 @@ +# IMPORTANT Change to your local system paths before using ANT + +# Flex SDK related properties +FLEX_HOME = path-to-flex-4.5-SDK +flexSDK.dir = ${FLEX_HOME} + +project.name = ${ant.project.name} + +# Project-path relative properties +src.dir = ${basedir}/src +binrelease.dir = ${basedir}/bin +asdoc.dir = ${binrelease.dir}/docs \ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..11a9ac2 --- /dev/null +++ b/build.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + [compile] Compiling release SWC + + + + + + + + + + + + + + + + [compile] Release SWC gestouch-${project.version}.swc created successfully + + + + [asdoc] Generating ASDOC documentation + + + + + + + + + + + + + + + + + + + + + [asdoc] ASDOC documentation generated successfully + + + \ No newline at end of file diff --git a/src/com/inreflected/utils/pooling/ObjectPool.as b/src/com/inreflected/utils/pooling/ObjectPool.as new file mode 100644 index 0000000..89f066f --- /dev/null +++ b/src/com/inreflected/utils/pooling/ObjectPool.as @@ -0,0 +1,131 @@ +package com.inreflected.utils.pooling +{ + import flash.utils.getQualifiedClassName; + + /** + * @author Pavel fljot + * + * "inspired" by Jonnie Hallman + * @link http://destroytoday.com + * @link https://github.com/destroytoday + * + * Added some optimization and changes. + */ + public class ObjectPool + { + // -------------------------------------------------------------------------- + // + // Properties + // + // -------------------------------------------------------------------------- + + protected var _type:Class; + protected var objectList:Array = []; + + + // -------------------------------------------------------------------------- + // + // Constructor + // + // -------------------------------------------------------------------------- + + public function ObjectPool(type:Class, size:uint = 0) + { + _type = type; + + if (size > 0) + { + allocate(size); + } + } + + + + + // -------------------------------------------------------------------------- + // + // Getters / Setters + // + // -------------------------------------------------------------------------- + + public function get type():Class + { + return _type; + } + + + public function get numObjects():uint + { + return objectList.length; + } + + + + // -------------------------------------------------------------------------- + // + // Public Methods + // + // -------------------------------------------------------------------------- + + public function hasObject(object:Object):Boolean + { + return objectList.indexOf(object) > -1; + } + + + public function getObject():* + { + return numObjects > 0 ? objectList.pop() : createObject(); + } + + + public function disposeObject(object:Object):void + { + if (!(object is type)) + { + throw new TypeError("Disposed object type mismatch. Expected " + type + ", got " + getQualifiedClassName(object)); + } + + addObject(object); + } + + + public function empty():void + { + objectList.length = 0; + } + + + + //-------------------------------------------------------------------------- + // + // Protected methods + // + //-------------------------------------------------------------------------- + + protected function addObject(object:Object):* + { + if (!hasObject(object)) + objectList[objectList.length] = object; + + return object; + } + + + protected function createObject():* + { + return new type(); + } + + + protected function allocate(value:uint):void + { + var n:int = value - numObjects; + + while (n-- > 0) + { + addObject(createObject()); + } + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/Direction.as b/src/org/gestouch/Direction.as new file mode 100644 index 0000000..56e8852 --- /dev/null +++ b/src/org/gestouch/Direction.as @@ -0,0 +1,17 @@ +package org.gestouch +{ + public class Direction + { + public static const NONE:String = "none"; + public static const LEFT:String = "left"; + public static const RIGHT:String = "right"; + public static const UP:String = "up"; + public static const DOWN:String = "down"; + public static const HORIZONTAL:String = "horizontal"; + public static const VERTICAL:String = "vertical"; + public static const STRAIGHT_AXES:String = "straightsAxes"; + public static const DIAGONAL_AXES:String = "diagonalAxes"; + public static const OCTO:String = "octo"; + public static const ALL:String = "all"; + } +} \ No newline at end of file diff --git a/src/org/gestouch/GestureUtils.as b/src/org/gestouch/GestureUtils.as new file mode 100644 index 0000000..abbc265 --- /dev/null +++ b/src/org/gestouch/GestureUtils.as @@ -0,0 +1,28 @@ +package org.gestouch +{ + import flash.system.Capabilities; + /** + * Set of constants. + * + * @author Pavel fljot + */ + public class GestureUtils + { + /** + * Precalculated coefficient used to convert 'inches per second' value to 'pixels per millisecond' value. + */ + public static const IPS_TO_PPMS:Number = Capabilities.screenDPI * 0.001; + /** + * Precalculated coefficient used to convert radians to degress. + */ + public static const RADIANS_TO_DEGREES:Number = 180 / Math.PI; + /** + * Precalculated coefficient used to convert degress to radians. + */ + public static const DEGREES_TO_RADIANS:Number = Math.PI / 180; + /** + * Precalculated coefficient Math.PI * 2 + */ + public static const PI_DOUBLE:Number = Math.PI * 2; + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/GesturesManager.as b/src/org/gestouch/core/GesturesManager.as new file mode 100644 index 0000000..e199480 --- /dev/null +++ b/src/org/gestouch/core/GesturesManager.as @@ -0,0 +1,371 @@ +package org.gestouch.core +{ + import com.inreflected.utils.pooling.ObjectPool; + + import org.gestouch.events.MouseTouchEvent; + + import flash.display.InteractiveObject; + import flash.display.Stage; + import flash.errors.IllegalOperationError; + import flash.events.Event; + import flash.events.MouseEvent; + import flash.events.TouchEvent; + import flash.ui.Multitouch; + import flash.ui.MultitouchInputMode; + import flash.utils.Dictionary; + import flash.utils.getTimer; + + + /** + * @author Pavel fljot + */ + public class GesturesManager implements IGesturesManager + { + public static var implementation:IGesturesManager; + + protected static var _impl:IGesturesManager; + protected static var _initialized:Boolean = false; + + protected var _stage:Stage; + protected var _gestures:Vector. = new Vector.(); + protected var _currGestures:Vector. = new Vector.(); + /** + * Maps (Dictionary[target] = gesture) by gesture type. + */ + protected var _gestureMapsByType:Dictionary = new Dictionary(); + protected var _touchPoints:Vector. = new Vector.(Multitouch.maxTouchPoints); + protected var _touchPointsPool:ObjectPool = new ObjectPool(TouchPoint); + + + gestouch_internal static function addGesture(gesture:IGesture):IGesture + { + if (!_impl) + { + _impl = implementation || new GesturesManager(); + } + return _impl.addGesture(gesture); + } + + + gestouch_internal static function removeGesture(gesture:IGesture):IGesture + { + return _impl.removeGesture(gesture); + } + + + gestouch_internal static function removeGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture + { + return _impl.removeGestureByTarget(gestureType, target); + } + + + gestouch_internal static function cancelGesture(gesture:IGesture):void + { + _impl.cancelGesture(gesture); + } + + + gestouch_internal static function addCurrentGesture(gesture:IGesture):void + { + _impl.addCurrentGesture(gesture); + } + + + gestouch_internal static function updateGestureTarget(gesture:IGesture, oldTarget:InteractiveObject, newTarget:InteractiveObject):void + { + _impl.updateGestureTarget(gesture, oldTarget, newTarget); + } + + + public function init(stage:Stage):void + { + Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT; + + _stage = stage; + _stage.addEventListener(MouseEvent.MOUSE_DOWN, stage_mouseDownHandler); + _stage.addEventListener(TouchEvent.TOUCH_BEGIN, stage_touchBeginHandler); + _stage.addEventListener(TouchEvent.TOUCH_MOVE, stage_touchMoveHandler); + _stage.addEventListener(TouchEvent.TOUCH_END, stage_touchEndHandler); + } + + + public static function getTouchPoint(touchPointID:int):TouchPoint + { + return _impl.getTouchPoint(touchPointID); + } + + + public function addGesture(gesture:IGesture):IGesture + { + if (_gestures.indexOf(gesture) > -1) + { + throw new IllegalOperationError("Gesture instace '" + gesture + "' is already registered."); + } + + _gestures.push(gesture); + + return gesture; + } + + + public function removeGesture(gesture:IGesture):IGesture + { + var index:int = _gestures.indexOf(gesture); + if (index == -1) + { + throw new IllegalOperationError("Gesture instace '" + gesture + "' is not registered."); + } + + _gestures.splice(index, 1); + + index = _currGestures.indexOf(gesture); + if (index > -1) + { + _currGestures.splice(index, 1); + } + + gesture.dispose(); + + return gesture; + } + + + public function removeGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture + { + var gesture:IGesture = getGestureByTarget(gestureType, target); + return removeGesture(gesture); + } + + + public function getGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture + { + var gesturesOfTypeByTarget:Dictionary = _gestureMapsByType[gestureType] as Dictionary; + var gesture:IGesture = gesturesOfTypeByTarget ? gesturesOfTypeByTarget[target] as IGesture : null; + return gesture; + } + + + public function cancelGesture(gesture:IGesture):void + { + var index:int = _currGestures.indexOf(gesture); + if (index == -1) + { + return;// don't see point in throwing error + } + + _currGestures.splice(index, 1); + gesture.onCancel(); + } + + + public function addCurrentGesture(gesture:IGesture):void + { + if (_currGestures.indexOf(gesture) == -1) + { + _currGestures.push(gesture); + } + } + + + public function updateGestureTarget(gesture:IGesture, oldTarget:InteractiveObject, newTarget:InteractiveObject):void + { + if (!_initialized) + { + var stage:Stage = newTarget.stage; + if (stage) + { + _impl.init(stage); + _initialized = true; + } + else + { + newTarget.addEventListener(Event.ADDED_TO_STAGE, target_addedToStageHandler, false, 0, true); + } + } + + var gesturesOfTypeByTarget:Dictionary = _gestureMapsByType[gesture.reflect()] as Dictionary; + if (!gesturesOfTypeByTarget) + { + gesturesOfTypeByTarget = _gestureMapsByType[gesture.reflect()] = new Dictionary(); + } + if (gesturesOfTypeByTarget[newTarget]) + { + throw new IllegalOperationError("You cannot add two gestures of the same type to one target (it makes no sence)."); + } + if (oldTarget) + { + delete gesturesOfTypeByTarget[oldTarget]; + } + if (newTarget) + { + gesturesOfTypeByTarget[newTarget] = gesture; + } + } + + + public function getTouchPoint(touchPointID:int):TouchPoint + { + var p:TouchPoint = _touchPoints[touchPointID]; + if (!p) + { + throw new ArgumentError("No touch point with ID " + touchPointID + " found."); + } + return p.clone() as TouchPoint; + } + + + private static function target_addedToStageHandler(event:Event):void + { + var target:InteractiveObject = event.currentTarget as InteractiveObject; + target.removeEventListener(Event.ADDED_TO_STAGE, target_addedToStageHandler); + + if (!_initialized) + { + _impl.init(target.stage); + _initialized = true; + } + } + + + protected function stage_mouseDownHandler(event:MouseEvent):void + { + if (Multitouch.supportsTouchEvents) + { + return; + } + + _stage.addEventListener(MouseEvent.MOUSE_MOVE, stage_mouseMoveHandler); + _stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler); + + stage_touchBeginHandler(new MouseTouchEvent(TouchEvent.TOUCH_BEGIN, event)); + } + + + protected function stage_mouseMoveHandler(event:MouseEvent):void + { + stage_touchMoveHandler(new MouseTouchEvent(TouchEvent.TOUCH_MOVE, event)); + } + + + protected function stage_mouseUpHandler(event:MouseEvent):void + { + _stage.removeEventListener(MouseEvent.MOUSE_MOVE, stage_mouseMoveHandler); + _stage.removeEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler); + + stage_touchEndHandler(new MouseTouchEvent(TouchEvent.TOUCH_END, event)); + } + + + protected function stage_touchBeginHandler(event:TouchEvent):void + { + var outOfRange:Boolean = (_touchPoints.length <= event.touchPointID); + var tp:TouchPoint = outOfRange ? null : _touchPoints[event.touchPointID]; + if (!tp) + { + tp = _touchPointsPool.getObject() as TouchPoint; + tp.id = event.touchPointID; + if (outOfRange) + { + _touchPoints.length = tp.id + 1; + } + _touchPoints[tp.id] = tp; + } + tp.reset(); + tp.x = event.stageX; + tp.y = event.stageY; + tp.sizeX = event.sizeX; + tp.sizeY = event.sizeY; + tp.pressure = event.pressure; + tp.touchBeginPos.x = tp.x; + tp.touchBeginPos.y = tp.y; + tp.touchBeginTime = tp.lastTime = getTimer(); + tp.moveOffset.x = tp.moveOffset.y = 0; + tp.lastMove.x = tp.lastMove.y = 0; + tp.velocity.x = tp.velocity.y = 0; + + for each (var gesture:IGesture in _gestures) + { + if (gesture.target && gesture.shouldTrackPoint(event, tp)) + { + gesture.onTouchBegin(tp); + } + } + + // add gestures that are being tracked to the current gestures list + var n:uint = _gestures.length; + while (n-- > 0) + { + gesture = _gestures[n]; + //TODO: which condition first (performance-wise)? + if (_currGestures.indexOf(gesture) == -1 && gesture.isTracking(tp.id)) + { + _currGestures.push(gesture); + } + } + } + + + protected function stage_touchMoveHandler(event:TouchEvent):void + { + var tp:TouchPoint = _touchPoints[event.touchPointID]; + var oldX:Number = tp.x; + var oldY:Number = tp.y; + tp.x = event.stageX; + tp.y = event.stageY; + tp.sizeX = event.sizeX; + tp.sizeY = event.sizeY; + tp.pressure = event.pressure; +// tp.moveOffset = tp.subtract(tp.touchBeginPos); + tp.moveOffset.x = tp.x - tp.touchBeginPos.x; + tp.moveOffset.y = tp.y - tp.touchBeginPos.y; + tp.lastMove.x = tp.x - oldX; + tp.lastMove.y = tp.y - oldY; + var now:uint = getTimer(); + var dt:uint = now - tp.lastTime; + tp.lastTime = now; + tp.velocity.x = tp.lastMove.x / dt; + tp.velocity.y = tp.lastMove.y / dt; + + for each (var gesture:IGesture in _currGestures) + { + if (gesture.isTracking(tp.id)) + { + gesture.onTouchMove(tp); + } + } + } + + + protected function stage_touchEndHandler(event:TouchEvent):void + { + var tp:TouchPoint = _touchPoints[event.touchPointID]; + tp.x = event.stageX; + tp.y = event.stageY; + tp.sizeX = event.sizeX; + tp.sizeY = event.sizeY; + tp.pressure = event.pressure; + tp.moveOffset = tp.subtract(tp.touchBeginPos); + + for each (var gesture:IGesture in _currGestures) + { + if (gesture.isTracking(tp.id)) + { + gesture.onTouchEnd(tp); + } + } + + var i:uint = 0; + for each (gesture in _currGestures.concat()) + { + if (gesture.trackingPointsCount == 0) + { + _currGestures.splice(i, 1); + } + else + { + i++; + } + } + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/IGesture.as b/src/org/gestouch/core/IGesture.as new file mode 100644 index 0000000..6ba69a4 --- /dev/null +++ b/src/org/gestouch/core/IGesture.as @@ -0,0 +1,28 @@ +package org.gestouch.core +{ + import flash.display.InteractiveObject; + import flash.events.TouchEvent; + + /** + * @author Pavel fljot + */ + public interface IGesture + { + function get target():InteractiveObject; + function get trackingPoints():Vector.; + function get trackingPointsCount():uint; + + function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean; + function isTracking(touchPointID:uint):Boolean; + + function cancel():void; + function pickAndContinue(gesture:IGesture):void; + function reflect():Class; + function dispose():void; + + function onTouchBegin(touchPoint:TouchPoint):void; + function onTouchMove(touchPoint:TouchPoint):void; + function onTouchEnd(touchPoint:TouchPoint):void; + function onCancel():void; + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/IGesturesManager.as b/src/org/gestouch/core/IGesturesManager.as new file mode 100644 index 0000000..6841c8b --- /dev/null +++ b/src/org/gestouch/core/IGesturesManager.as @@ -0,0 +1,24 @@ +package org.gestouch.core +{ + import flash.display.InteractiveObject; + import flash.display.Stage; + + /** + * @author Pavel fljot + */ + public interface IGesturesManager + { + function init(stage:Stage):void; + + function addGesture(gesture:IGesture):IGesture; + function removeGesture(gesture:IGesture):IGesture; + function removeGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture; + function getGestureByTarget(gestureType:Class, target:InteractiveObject):IGesture; + function cancelGesture(gesture:IGesture):void; + function addCurrentGesture(gesture:IGesture):void; + + function updateGestureTarget(gesture:IGesture, oldTarget:InteractiveObject, newTarget:InteractiveObject):void; + + function getTouchPoint(touchPointID:int):TouchPoint; + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/TouchPoint.as b/src/org/gestouch/core/TouchPoint.as new file mode 100644 index 0000000..c72cd91 --- /dev/null +++ b/src/org/gestouch/core/TouchPoint.as @@ -0,0 +1,68 @@ +package org.gestouch.core +{ + import flash.geom.Point; + + + /** + * @author Pavel fljot + */ + public class TouchPoint extends Point + { + public var id:uint; + public var localX:Number; + public var localY:Number; + public var sizeX:Number; + public var sizeY:Number; + public var pressure:Number; + public var touchBeginPos:Point; + public var touchBeginTime:uint; + public var moveOffset:Point; + public var lastMove:Point; + public var lastTime:uint; + public var velocity:Point; + + + public function TouchPoint(id:uint = 0, x:Number = 0, y:Number = 0, + sizeX:Number = NaN, sizeY:Number = NaN, + pressure:Number = NaN, + touchBeginPos:Point = null, touchBeginTime:uint = 0, + moveOffset:Point = null, + lastMove:Point = null, lastTime:uint = 0, velocity:Point = null) + { + super(x, y); + + this.id = id; + this.sizeX = sizeX; + this.sizeY = sizeY; + this.pressure = pressure; + this.touchBeginPos = touchBeginPos || new Point(); + this.touchBeginTime = touchBeginTime; + this.moveOffset = moveOffset || new Point(); + this.lastMove = lastMove || new Point(); + this.lastTime = lastTime; + this.velocity = velocity || new Point(); + } + + + override public function clone():Point + { + var p:TouchPoint = new TouchPoint(id, x, y, sizeX, sizeY, pressure, + touchBeginPos.clone(), touchBeginTime, + moveOffset.clone(), + lastMove.clone(), lastTime, velocity.clone()); + return p; + } + + + public function reset():void + { + + } + + + override public function toString():String + { + return "Touch point [id: " + id + ", x: " + x + ", y: " + y + ", ...]"; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/core/gestouch_internal.as b/src/org/gestouch/core/gestouch_internal.as new file mode 100644 index 0000000..b4cd43b --- /dev/null +++ b/src/org/gestouch/core/gestouch_internal.as @@ -0,0 +1,7 @@ +package org.gestouch.core +{ + /** + * @author Pavel fljot + */ + public namespace gestouch_internal = "org.gestouch.core::gestouch_internal"; +} \ No newline at end of file diff --git a/src/org/gestouch/events/DoubleTapGestureEvent.as b/src/org/gestouch/events/DoubleTapGestureEvent.as new file mode 100644 index 0000000..552ed36 --- /dev/null +++ b/src/org/gestouch/events/DoubleTapGestureEvent.as @@ -0,0 +1,19 @@ +package org.gestouch.events +{ + import flash.events.GestureEvent; + + + /** + * @author Pavel fljot + */ + public class DoubleTapGestureEvent extends GestureEvent + { + public static const GESTURE_DOUBLE_TAP:String = "gestureDoubleTap"; + + + public function DoubleTapGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false) + { + super(type, bubbles, cancelable, phase, localX, localY, ctrlKey, altKey, shiftKey); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/DragGestureEvent.as b/src/org/gestouch/events/DragGestureEvent.as new file mode 100644 index 0000000..e74a9e9 --- /dev/null +++ b/src/org/gestouch/events/DragGestureEvent.as @@ -0,0 +1,19 @@ +package org.gestouch.events +{ + import flash.events.TransformGestureEvent; + + + /** + * @author Pavel fljot + */ + public class DragGestureEvent extends TransformGestureEvent + { + public static const GESTURE_DRAG:String = "gestureDrag"; + + + public function DragGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0, rotation:Number = 0, offsetX:Number = 0, offsetY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, commandKey:Boolean = false, controlKey:Boolean = false) + { + super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/LongPressGestureEvent.as b/src/org/gestouch/events/LongPressGestureEvent.as new file mode 100644 index 0000000..0f2cba0 --- /dev/null +++ b/src/org/gestouch/events/LongPressGestureEvent.as @@ -0,0 +1,19 @@ +package org.gestouch.events +{ + import flash.events.GestureEvent; + + + /** + * @author Pavel fljot + */ + public class LongPressGestureEvent extends GestureEvent + { + public static const GESTURE_LONG_PRESS:String = "gestureLongPress"; + + //TODO: default + public function LongPressGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = "begin", localX:Number = 0, localY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false) + { + super(type, bubbles, cancelable, phase, localX, localY, ctrlKey, altKey, shiftKey); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/MouseTouchEvent.as b/src/org/gestouch/events/MouseTouchEvent.as new file mode 100644 index 0000000..6104da6 --- /dev/null +++ b/src/org/gestouch/events/MouseTouchEvent.as @@ -0,0 +1,55 @@ +package org.gestouch.events +{ + import flash.events.Event; + import flash.events.MouseEvent; + import flash.events.TouchEvent; + + + /** + * @author Pavel fljot + */ + public class MouseTouchEvent extends TouchEvent + { + public function MouseTouchEvent(type:String, event:MouseEvent) + { + super(type, event.bubbles, event.cancelable, 0, true, event.localX, event.localY, NaN, NaN, NaN, event.relatedObject, event.ctrlKey, event.altKey, event.shiftKey); + + _target = event.target; + _stageX = event.stageX; + _stageY = event.stageY; + } + + + protected var _target:Object; + override public function get target():Object + { + return _target; + } + + + protected var _stageX:Number; + override public function get stageX():Number + { + return _stageX; + } + + + protected var _stageY:Number; + override public function get stageY():Number + { + return _stageY; + } + + + override public function clone():Event + { + return super.clone(); + } + + + override public function toString():String + { + return super.toString() + " *faked"; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/RotateGestureEvent.as b/src/org/gestouch/events/RotateGestureEvent.as new file mode 100644 index 0000000..063efea --- /dev/null +++ b/src/org/gestouch/events/RotateGestureEvent.as @@ -0,0 +1,19 @@ +package org.gestouch.events +{ + import flash.events.TransformGestureEvent; + + + /** + * @author Pavel fljot + */ + public class RotateGestureEvent extends TransformGestureEvent + { + public static const GESTURE_ROTATE:String = "gestureRotate"; + + + public function RotateGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0, rotation:Number = 0, offsetX:Number = 0, offsetY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, commandKey:Boolean = false, controlKey:Boolean = false) + { + super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/SwipeGestureEvent.as b/src/org/gestouch/events/SwipeGestureEvent.as new file mode 100644 index 0000000..469669a --- /dev/null +++ b/src/org/gestouch/events/SwipeGestureEvent.as @@ -0,0 +1,19 @@ +package org.gestouch.events +{ + import flash.events.TransformGestureEvent; + + + /** + * @author Pavel fljot + */ + public class SwipeGestureEvent extends TransformGestureEvent + { + public static const GESTURE_SWIPE:String = "gestureSwipe"; + + + public function SwipeGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0, rotation:Number = 0, offsetX:Number = 0, offsetY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, commandKey:Boolean = false, controlKey:Boolean = false) + { + super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/events/ZoomGestureEvent.as b/src/org/gestouch/events/ZoomGestureEvent.as new file mode 100644 index 0000000..93b3ede --- /dev/null +++ b/src/org/gestouch/events/ZoomGestureEvent.as @@ -0,0 +1,19 @@ +package org.gestouch.events +{ + import flash.events.TransformGestureEvent; + + + /** + * @author Pavel fljot + */ + public class ZoomGestureEvent extends TransformGestureEvent + { + public static const GESTURE_ZOOM:String = "gestureZoom"; + + + public function ZoomGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, scaleX:Number = 1.0, scaleY:Number = 1.0, rotation:Number = 0, offsetX:Number = 0, offsetY:Number = 0, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, commandKey:Boolean = false, controlKey:Boolean = false) + { + super(type, bubbles, cancelable, phase, localX, localY, scaleX, scaleY, rotation, offsetX, offsetY, ctrlKey, altKey, shiftKey); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/gestures/DoubleTapGesture.as b/src/org/gestouch/gestures/DoubleTapGesture.as new file mode 100644 index 0000000..4c4a2c3 --- /dev/null +++ b/src/org/gestouch/gestures/DoubleTapGesture.as @@ -0,0 +1,254 @@ +package org.gestouch.gestures +{ + import flash.display.DisplayObjectContainer; + import flash.display.InteractiveObject; + import flash.events.GesturePhase; + import flash.events.TimerEvent; + import flash.events.TouchEvent; + import flash.geom.Point; + import flash.utils.Timer; + import org.gestouch.core.GesturesManager; + import org.gestouch.core.TouchPoint; + import org.gestouch.core.gestouch_internal; + import org.gestouch.events.DoubleTapGestureEvent; + + + + + /** + * DoubleTapGesture tracks quick double-tap (double-click). + * + *

Gesture-specific configuratin properties:

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

+ * moveThreshold — maximum allowed distance between two taps.

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

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

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

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

+ * + * @see org.gestouch.events.DragGestureEvent + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/GestureEvent.html#phase + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/GesturePhase.html + * + * @author Pavel fljot + */ + public class DragGesture extends MovingGestureBase + { + public function DragGesture(target:InteractiveObject, settings:Object = null) + { + super(target, settings); + } + + + + + //-------------------------------------------------------------------------- + // + // Static methods + // + //-------------------------------------------------------------------------- + + public static function add(target:InteractiveObject, settings:Object = null):DragGesture + { + return new DragGesture(target, settings); + } + + + public static function remove(target:InteractiveObject):DragGesture + { + return GesturesManager.gestouch_internal::removeGestureByTarget(DragGesture, target) as DragGesture; + } + + + + + //-------------------------------------------------------------------------- + // + // Public methods + // + //-------------------------------------------------------------------------- + + override public function reflect():Class + { + return DragGesture; + } + + + override public function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return false; + } + // this particular gesture is interested only in those touchpoints on top of target + var touchTarget:InteractiveObject = event.target as InteractiveObject; + if (touchTarget != target && !(target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(touchTarget))) + { + return false; + } + + return true; + } + + + override public function onTouchBegin(touchPoint:TouchPoint):void + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return; + } + + _trackPoint(touchPoint); + + if (_trackingPointsCount > 1) + { + _adjustCentralPoint(); + _centralPoint.lastMove.x = _centralPoint.lastMove.y = 0; + } + } + + + override public function onTouchMove(touchPoint:TouchPoint):void + { + // do calculations only when we track enough points + if (_trackingPointsCount < minTouchPointsCount) + { + return; + } + + _adjustCentralPoint(); + + if (!_slopPassed) + { + _slopPassed = _checkSlop(_centralPoint.moveOffset); + + if (_slopPassed) + { + _centralPoint.lastMove.x = _centralPoint.lastMove.y = 0; + _dispatch(new DragGestureEvent(DragGestureEvent.GESTURE_DRAG, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + } + } + + if (_slopPassed) + { + _dispatch(new DragGestureEvent(DragGestureEvent.GESTURE_DRAG, true, false, GesturePhase.UPDATE, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, 1, 1, 0, _centralPoint.lastMove.x, _centralPoint.lastMove.y)); + } + } + + + override public function onTouchEnd(touchPoint:TouchPoint):void + { + var ending:Boolean = (_trackingPointsCount == minTouchPointsCount); + _forgetPoint(touchPoint); + + _adjustCentralPoint(); + _centralPoint.lastMove.x = _centralPoint.lastMove.y = 0; + + if (ending) + { + _reset(); + _dispatch(new DragGestureEvent(DragGestureEvent.GESTURE_DRAG, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, 1, 1, 0, 0, 0)); + } + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/gestures/Gesture.as b/src/org/gestouch/gestures/Gesture.as new file mode 100644 index 0000000..c0dd985 --- /dev/null +++ b/src/org/gestouch/gestures/Gesture.as @@ -0,0 +1,522 @@ +package org.gestouch.gestures +{ + import flash.errors.IllegalOperationError; + import flash.display.InteractiveObject; + import flash.events.GestureEvent; + import flash.events.TouchEvent; + import flash.geom.Point; + import flash.system.Capabilities; + + import org.gestouch.core.GesturesManager; + import org.gestouch.core.IGesture; + import org.gestouch.core.TouchPoint; + import org.gestouch.core.gestouch_internal; + + + /** + * 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. + * + * @author Pavel fljot + */ + public class Gesture implements IGesture + { + /** + * Threshold for screen distance they must move to count as valid input + * (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); + + /** + * Array of configuration properties (Strings). + */ + protected var _propertyNames:Array = ["minTouchPointsCount", "maxTouchPointsCount"]; + /** + * Map (generic object) of tracking touch points, where keys are touch points IDs. + */ + protected var _trackingPointsMap:Object = {}; + protected var _trackingPointsCount:int = 0; + protected var _firstTouchPoint:TouchPoint; + protected var _lastLocalCentralPoint:Point; + + + public function Gesture(target:InteractiveObject, settings:Object = null) + { + // Check if gesture reflects it's class properly + reflect(); + + _preinit(); + + GesturesManager.gestouch_internal::addGesture(this); + + this.target = target; + + if (settings != null) + { + _parseSettings(settings); + } + } + + + /** @private */ + private var _minTouchPointsCount:uint = 1; + /** + * Minimum amount of touch points required for gesture. + * + * @default 1 + */ + public function get minTouchPointsCount():uint + { + return _minTouchPointsCount; + } + public function set minTouchPointsCount(value:uint):void + { + if (_minTouchPointsCount == value) return; + + _minTouchPointsCount = value; + if (maxTouchPointsCount < minTouchPointsCount) + { + maxTouchPointsCount = minTouchPointsCount; + } + } + + + /** @private */ + private var _maxTouchPointsCount:uint = 1; + + /** + * Maximum amount of touch points required for gesture. + * + * @default 1 + */ + public function get maxTouchPointsCount():uint + { + return _maxTouchPointsCount; + } + public function set maxTouchPointsCount(value:uint):void + { + if (value < minTouchPointsCount) + { + throw new IllegalOperationError("maxTouchPointsCount can not be less then minTouchPointsCount"); + } + if (_maxTouchPointsCount == value) return; + + _maxTouchPointsCount = value; + } + + + /** @private */ + protected var _target:InteractiveObject; + + /** + * InteractiveObject (DisplayObject) which this gesture is tracking the actual gesture motion on. + * + *

Could be some image, component (like map) or the larger view like Stage.

+ * + *

You can change the target in the runtime, e.g. you have a gallery + * where only one item is visible at the moment, so use one gesture instance + * and change the target to the currently visible item.

+ * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html + */ + public function get target():InteractiveObject + { + return _target; + } + public function set target(value:InteractiveObject):void + { + if (_target == value) return; + + GesturesManager.gestouch_internal::updateGestureTarget(this, target, value); + + // if GesturesManager hasn't thrown any error we can safely continue + + if (target) + { + _uninstallTarget(target); + } + + _target = value; + + if (target) + { + _installTarget(target); + } + } + + + /** + * Storage for the trackingPoints property. + */ + protected var _trackingPoints:Vector. = new Vector.(); + /** + * Vector of tracking touch points — touch points this gesture is interested in. + * + *

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

+ * + * @see #isTracking() + * @see #shouldTrackPoint() + */ + public function get trackingPoints():Vector. + { + return _trackingPoints.concat(); + } + + + /** + * Amount of currently tracked touch points. Cached value of trackingPoints.length + * + * @see #trackingPoints + */ + public function get trackingPointsCount():uint + { + return _trackingPointsCount; + } + + + /** + * Storage for centralPoint property. + */ + protected var _centralPoint:TouchPoint; + /** + * Virtual central touch point among all tracking touch points (geometrical center). + * + *

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

+ * + * @see #_adjustCentralPoint() + */ + public function get centralPoint():TouchPoint + { + return _centralPoint; + } + + + + //-------------------------------------------------------------------------- + // + // Public methods + // + //-------------------------------------------------------------------------- + + [Abstract] + /** + * Reflects gesture class (for better perfomance). + * + *

NB! This is abstract method and must be overridden.

+ * + * @see performance optimization tips + */ + public function reflect():Class + { + throw Error("reflect() is abstract method and must be overridden."); + } + + + [Abstract] + /** + * Used by GesturesManager to check wether this gesture is interested in + * tracking this touch point upon this event (of type TouchEvent.TOUCH_BEGIN). + * + *

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

+ * + *

No need to use it directly.

+ * + *

NB! This is abstract method and must be overridden.

+ * + * @see org.gestouch.core.GesturesManager + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/TouchEvent.html + */ + public function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean + { + throw Error("shouldTrackPoint() is abstract method and must be overridden."); + } + + + /** + * Used by GesturesManager to check wether this gesture is tracking this touch point. + * (Not to invoke onTouchBegin, onTouchMove and onTouchEnd methods with no need) + * + * @see org.gestouch.core.GesturesManager + */ + public function isTracking(touchPointID:uint):Boolean + { + return (_trackingPointsMap[touchPointID] === true); + } + + + /** + * Cancels current tracking (interaction) cycle. + * + *

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

+ */ + public function cancel():void + { + GesturesManager.gestouch_internal::cancelGesture(this); + } + + + /** + * TODO: write description, decide wethere this API is good. + */ + public function pickAndContinue(gesture:IGesture):void + { + GesturesManager.gestouch_internal::addCurrentGesture(this); + + for each (var tp:TouchPoint in gesture.trackingPoints) + { + onTouchBegin(tp); + } + } + + + /** + * Remove gesture and prepare it for GC. + * + *

The gesture is not able to use after calling this method.

+ */ + public function dispose():void + { + _reset(); + target = null; + try + { + GesturesManager.gestouch_internal::removeGesture(this); + } + catch (err:Error) + { + // do nothing + // GesturesManager may throw Error if this gesture is already removed: + // in case dispose() is called by GesturesManager upon GestureClass.remove(target) + + // this part smells a bit, eh? + } + } + + + [Abstract] + /** + * Internal method, used by GesturesManager. + * + *

NB! This is abstract method and must be overridden.

+ */ + public function onTouchBegin(touchPoint:TouchPoint):void + { + + } + + + [Abstract] + /** + * Internal method, used by GesturesManager. + * + *

NB! This is abstract method and must be overridden.

+ */ + public function onTouchMove(touchPoint:TouchPoint):void + { + } + + + [Abstract] + /** + * Internal method, used by GesturesManager. + * + *

NB! This is abstract method and must be overridden.

+ */ + public function onTouchEnd(touchPoint:TouchPoint):void + { + + } + + + /** + * Internal method, used by GesturesManager. Called upon gesture is cancelled. + * + * @see #cancel() + */ + public function onCancel():void + { + _reset(); + } + + + + + // -------------------------------------------------------------------------- + // + // Protected methods + // + // -------------------------------------------------------------------------- + + /** + * First method, called in constructor. + * + *

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

+ * +minTouchPointsCount = 2; +_propertyNames.push("timeThreshold", "moveThreshold"); + * + */ + protected function _preinit():void + { + } + + + /** + * Called internally when changing the target. + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html + */ + protected function _installTarget(target:InteractiveObject):void + { + + } + + + /** + * Called internally when changing the target. + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/InteractiveObject.html + */ + protected function _uninstallTarget(target:InteractiveObject):void + { + + } + + + /** + * Dispatches gesture event from the name of target. + * + * @param event GestureEvent to be dispatched from target + * + * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/GestureEvent.html + */ + protected function _dispatch(event:GestureEvent):void + { + target.dispatchEvent(event); + } + + + /** + * Parses settings and configures the gesture. + * + * @param settings Generic object with configuration properties + */ + protected function _parseSettings(settings:Object):void + { + for each (var propertyName:String in _propertyNames) + { + if (settings.hasOwnProperty(propertyName)) + { + this[propertyName] = settings[propertyName]; + } + } + } + + + /** + * Saves touchPoint for tracking for the current gesture cycle. + * + *

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

+ * + * @see #_firstTouchPoint + * @see #centralPoint + * @see #trackingPointsCount + */ + protected function _trackPoint(touchPoint:TouchPoint):void + { + _trackingPointsMap[touchPoint.id] = true; + var index:uint = _trackingPoints.push(touchPoint); + _trackingPointsCount++; + if (index == 1) + { + _firstTouchPoint = touchPoint; + _centralPoint = touchPoint.clone() as TouchPoint; + } + } + + + /** + * Removes touchPoint from the list of tracking points. + * + *

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

+ * + * @see #trackingPoints + * @see #_trackingPointsMap + * @see #trackingPointsCount + */ + protected function _forgetPoint(touchPoint:TouchPoint):void + { + delete _trackingPointsMap[touchPoint.id]; + _trackingPoints.splice(_trackingPoints.indexOf(touchPoint), 1); + _trackingPointsCount--; + } + + + /** + * Adjusts (recalculates) _centralPoint and all it's properties + * (such as positions, offsets, velocity, etc...). + * Also updates _lastLocalCentralPoint (used for dispatching events). + * + * @see #centralPoint + * @see #_lastLocalCentralPoint + * @see #trackingPoints + */ + protected function _adjustCentralPoint():void + { + var x:Number = 0; + var y:Number = 0; + var velX:Number = 0; + var velY:Number = 0; + for each (var tp:TouchPoint in _trackingPoints) + { + x += tp.x; + y += tp.y; + velX += tp.velocity.x; + velY += tp.velocity.y; + } + x /= _trackingPointsCount; + y /= _trackingPointsCount; + var lastMoveX:Number = x - _centralPoint.x; + var lastMoveY:Number = y - _centralPoint.y; + velX /= _trackingPointsCount; + velY /= _trackingPointsCount; + + _centralPoint.x = x; + _centralPoint.y = y; + _centralPoint.lastMove.x = lastMoveX; + _centralPoint.lastMove.y = lastMoveY; + _centralPoint.velocity.x = velX; + _centralPoint.velocity.y = velY; + // tp.moveOffset = tp.subtract(tp.touchBeginPos); + _centralPoint.moveOffset.x = x - _centralPoint.touchBeginPos.x; + _centralPoint.moveOffset.y = y - _centralPoint.touchBeginPos.y; + + _lastLocalCentralPoint = target.globalToLocal(_centralPoint); + } + + + /** + * Reset data for the current tracking (interaction) cycle. + * + *

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

+ * + *

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

+ * + * @see #trackingPoints + * @see #trackingPointsCount + * @see #onCancel() + */ + protected function _reset():void + { + // forget all touch points + _trackingPointsMap = {}; + _trackingPoints.length = 0; + _trackingPointsCount = 0; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/gestures/LongPressGesture.as b/src/org/gestouch/gestures/LongPressGesture.as new file mode 100644 index 0000000..4aabd48 --- /dev/null +++ b/src/org/gestouch/gestures/LongPressGesture.as @@ -0,0 +1,184 @@ +package org.gestouch.gestures +{ + import flash.display.DisplayObjectContainer; + import flash.display.InteractiveObject; + import flash.events.GesturePhase; + import flash.events.TimerEvent; + import flash.events.TouchEvent; + import flash.utils.Timer; + import org.gestouch.core.GesturesManager; + import org.gestouch.core.TouchPoint; + import org.gestouch.core.gestouch_internal; + import org.gestouch.events.LongPressGestureEvent; + + + + /** + * + * + * @author Pavel fljot + */ + public class LongPressGesture extends Gesture + { + /** + * Default value 1000ms + */ + public var timeThreshold:uint = 500; + /** + * Deafult value is Gesture.DEFAULT_SLOP + * @see org.gestouchers.core.Gesture#DEFAULT_SLOP + */ + public var slop:Number = Gesture.DEFAULT_SLOP; + + protected var _thresholdTimer:Timer; + + + public function LongPressGesture(target:InteractiveObject, settings:Object = null) + { + super(target, settings); + } + + + + + //-------------------------------------------------------------------------- + // + // Static methods + // + //-------------------------------------------------------------------------- + + public static function add(target:InteractiveObject, settings:Object = null):LongPressGesture + { + return new LongPressGesture(target, settings); + } + + + public static function remove(target:InteractiveObject):LongPressGesture + { + return GesturesManager.gestouch_internal::removeGestureByTarget(LongPressGesture, target) as LongPressGesture; + } + + + + + //-------------------------------------------------------------------------- + // + // Public methods + // + //-------------------------------------------------------------------------- + + override public function reflect():Class + { + return LongPressGesture; + } + + + + override public function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return false; + } + // this particular gesture is interested only in those touchpoints on top of target + var touchTarget:InteractiveObject = event.target as InteractiveObject; + if (touchTarget != target && !(target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(touchTarget))) + { + return false; + } + + return true; + } + + + override public function onTouchBegin(touchPoint:TouchPoint):void + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return; + } + + _trackPoint(touchPoint); + + if (_trackingPointsCount == minTouchPointsCount) + { + _thresholdTimer.reset(); + _thresholdTimer.delay = timeThreshold; + _thresholdTimer.start(); + } + } + + + override public function onTouchMove(touchPoint:TouchPoint):void + { + // faster isNaN + if (_thresholdTimer.currentCount == 0 && slop == slop) + { + if (touchPoint.moveOffset.length > slop) + { + cancel(); + } + } + } + + + override public function onTouchEnd(touchPoint:TouchPoint):void + { + _forgetPoint(touchPoint); + + var held:Boolean = (_thresholdTimer.currentCount > 0); + _thresholdTimer.reset(); + + if (held) + { + _adjustCentralPoint(); + _reset(); + _dispatch(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + } + } + + + + + //-------------------------------------------------------------------------- + // + // Protected methods + // + //-------------------------------------------------------------------------- + + override protected function _preinit():void + { + super._preinit(); + + _thresholdTimer = new Timer(timeThreshold, 1); + _thresholdTimer.addEventListener(TimerEvent.TIMER_COMPLETE, _onThresholdTimerComplete); + + _propertyNames.push("timeThreshold", "slop"); + } + + + override protected function _reset():void + { + super._reset(); + + _thresholdTimer.reset(); + } + + + + + //-------------------------------------------------------------------------- + // + // Event handlers + // + //-------------------------------------------------------------------------- + + protected function _onThresholdTimerComplete(event:TimerEvent):void + { + _adjustCentralPoint(); + _dispatch(new LongPressGestureEvent(LongPressGestureEvent.GESTURE_LONG_PRESS, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/gestures/MovingGestureBase.as b/src/org/gestouch/gestures/MovingGestureBase.as new file mode 100644 index 0000000..789e200 --- /dev/null +++ b/src/org/gestouch/gestures/MovingGestureBase.as @@ -0,0 +1,173 @@ +package org.gestouch.gestures +{ + import flash.display.InteractiveObject; + import flash.geom.Point; + import org.gestouch.Direction; + + + + /** + * Base class for those gestures where you have to move finger/mouse, + * i.e. DragGesture, SwipeGesture + * + * @author Pavel fljot + */ + public class MovingGestureBase extends Gesture + { + /** + * Threshold for screen distance they must move to count as valid input + * (not an accidental offset on touch). Once this distance is passed, + * gesture starts more intensive and specific processing in onTouchMove() method. + * + * @default Gesture.DEFAULT_SLOP + * + * @see org.gestouch.gestures.Gesture#DEFAULT_SLOP + */ + public var slop:Number = Gesture.DEFAULT_SLOP; + + protected var _slopPassed:Boolean = false; + protected var _canMoveHorizontally:Boolean = true; + protected var _canMoveVertically:Boolean = true; + + + public function MovingGestureBase(target:InteractiveObject, settings:Object = null) + { + super(target, settings); + + if (reflect() == MovingGestureBase) + { + dispose(); + throw new Error("This is abstract class and cannot be instantiated."); + } + } + + + /** + * @private + * Storage for direction property. + */ + protected var _direction:String = Direction.ALL; + + + /** + * Allowed direction for this gesture. Used to determine slop overcome + * and could be used for specific calculations (as in SwipeGesture for example). + * + * @default Direction.ALL + * + * @see org.gestouch.Direction + * @see org.gestouch.gestures.SwipeGesture + */ + public function get direction():String + { + return _direction; + } + public function set direction(value:String):void + { + if (_direction == value) return; + + _validateDirection(value); + + _direction = value; + + _canMoveHorizontally = (_direction != Direction.VERTICAL); + _canMoveVertically = (_direction != Direction.HORIZONTAL); + } + + + + + //-------------------------------------------------------------------------- + // + // Protected methods + // + //-------------------------------------------------------------------------- + + override protected function _preinit():void + { + super._preinit(); + + _propertyNames.push("slop", "direction"); + } + + + override protected function _reset():void + { + super._reset(); + + _slopPassed = false; + } + + + /** + * Validates direction property (in setter) to help + * developer prevent accidental mistake (Strings suck). + * + * @see org.gestouch.Direction + */ + protected function _validateDirection(value:String):void + { + if (value != Direction.HORIZONTAL && + value != Direction.VERTICAL && + value != Direction.STRAIGHT_AXES && + value != Direction.DIAGONAL_AXES && + value != Direction.OCTO && + value != Direction.ALL) + { + throw new ArgumentError("Invalid direction value \"" + value + "\"."); + } + } + + + /** + * Checks wether slop has been overcome. + * Typically used in onTouchMove() method. + * + * @param moveDelta offset of touch point / central point + * starting from beginning of interaction cycle. + * + * @see #onTouchMove() + */ + protected function _checkSlop(moveDelta:Point):Boolean + { + var slopPassed:Boolean = false; + + if (_canMoveHorizontally && _canMoveVertically) + { + slopPassed = moveDelta.length > slop; + } + else if (_canMoveHorizontally) + { + slopPassed = Math.abs(moveDelta.x) > slop; + } + else if (_canMoveVertically) + { + slopPassed = Math.abs(moveDelta.y) > slop; + } + + if (slopPassed) + { + var slopVector:Point; + if (_canMoveHorizontally && _canMoveVertically) + { + slopVector = moveDelta.clone(); + slopVector.normalize(slop); + slopVector.x = Math.round(slopVector.x); + slopVector.y = Math.round(slopVector.y); + } + else if (_canMoveHorizontally) + { + slopVector = new Point(moveDelta.x >= slop ? slop : -slop, 0); + } + else if (_canMoveVertically) + { + slopVector = new Point(0, moveDelta.y >= slop ? slop : -slop); + } +// _gestureAnchorPoint = _touchPoint.add(slopVector); +// startGestureTrack(); + } + + return slopPassed; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/gestures/RotateGesture.as b/src/org/gestouch/gestures/RotateGesture.as new file mode 100644 index 0000000..6b12eda --- /dev/null +++ b/src/org/gestouch/gestures/RotateGesture.as @@ -0,0 +1,163 @@ +package org.gestouch.gestures +{ + import flash.display.DisplayObjectContainer; + import flash.display.InteractiveObject; + import flash.events.GesturePhase; + import flash.events.TouchEvent; + import flash.geom.Point; + import org.gestouch.GestureUtils; + import org.gestouch.core.GesturesManager; + import org.gestouch.core.TouchPoint; + import org.gestouch.core.gestouch_internal; + import org.gestouch.events.RotateGestureEvent; + + + + /** + * @author Pavel fljot + */ + public class RotateGesture extends Gesture + { + + protected var _currVector:Point = new Point(); + protected var _lastVector:Point = new Point(); + + + public function RotateGesture(target:InteractiveObject, settings:Object = null) + { + super(target, settings); + } + + + + + //-------------------------------------------------------------------------- + // + // Static methods + // + //-------------------------------------------------------------------------- + + public static function add(target:InteractiveObject, settings:Object = null):RotateGesture + { + return new RotateGesture(target, settings); + } + + + public static function remove(target:InteractiveObject):RotateGesture + { + return GesturesManager.gestouch_internal::removeGestureByTarget(RotateGesture, target) as RotateGesture; + } + + + + + //-------------------------------------------------------------------------- + // + // Public methods + // + //-------------------------------------------------------------------------- + + override public function onCancel():void + { + super.onCancel(); + + + } + + + override public function reflect():Class + { + return RotateGesture; + } + + + override public function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return false; + } + // this particular gesture is interested only in those touchpoints on top of target + //FIXME? + var touchTarget:InteractiveObject = event.target as InteractiveObject; + if (touchTarget != target && !(target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(touchTarget))) + { + return false; + } + + return true; + } + + + override public function onTouchBegin(touchPoint:TouchPoint):void + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return; + } + + _trackPoint(touchPoint); + + if (_trackingPointsCount == minTouchPointsCount) + { + _lastVector.x = _trackingPoints[1].x - _trackingPoints[0].x; + _lastVector.y = _trackingPoints[1].y - _trackingPoints[0].y; + + _adjustCentralPoint(); + + _dispatch(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + } + } + + + override public function onTouchMove(touchPoint:TouchPoint):void + { + // do calculations only when we track enough points + if (_trackingPointsCount < minTouchPointsCount) + { + return; + } + + _adjustCentralPoint(); + + _currVector.x = _trackingPoints[1].x - _trackingPoints[0].x; + _currVector.y = _trackingPoints[1].y - _trackingPoints[0].y; + + var a1:Number = Math.atan2(_lastVector.y, _lastVector.x); + var a2:Number = Math.atan2(_currVector.y, _currVector.x); + var angle:Number = a2 - a1; + if (angle < 0) + { + angle += GestureUtils.PI_DOUBLE; + } + angle *= GestureUtils.RADIANS_TO_DEGREES; + + _dispatch(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, true, false, GesturePhase.UPDATE, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, 1, 1, angle)); + + _lastVector.x = _currVector.x; + _lastVector.y = _currVector.y; + } + + + override public function onTouchEnd(touchPoint:TouchPoint):void + { + var ending:Boolean = (_trackingPointsCount == minTouchPointsCount); + _forgetPoint(touchPoint); + + if (ending) + { + _dispatch(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + } + } + + + override protected function _preinit():void + { + super._preinit(); + + minTouchPointsCount = 2; + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/gestures/SwipeGesture.as b/src/org/gestouch/gestures/SwipeGesture.as new file mode 100644 index 0000000..ca873f8 --- /dev/null +++ b/src/org/gestouch/gestures/SwipeGesture.as @@ -0,0 +1,199 @@ +package org.gestouch.gestures +{ + import flash.display.DisplayObjectContainer; + import flash.display.InteractiveObject; + import flash.events.GesturePhase; + import flash.events.TouchEvent; + import flash.geom.Point; + import flash.utils.getTimer; + + import org.gestouch.Direction; + import org.gestouch.GestureUtils; + import org.gestouch.core.GesturesManager; + import org.gestouch.core.TouchPoint; + import org.gestouch.core.gestouch_internal; + import org.gestouch.events.SwipeGestureEvent; + + + /** + * SwipeGesture detects swipe motion (also known as flick or flig). + * + *

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

+ * + * @author Pavel fljot + */ + public class SwipeGesture extends MovingGestureBase + { + public var moveThreshold:Number = Gesture.DEFAULT_SLOP; + public var minTimeThreshold:uint = 50; + public var velocityThreshold:Number = 7 * GestureUtils.IPS_TO_PPMS; + public var sideVelocityThreshold:Number = 2 * GestureUtils.IPS_TO_PPMS; + + protected var _startTime:uint; + + + public function SwipeGesture(target:InteractiveObject, settings:Object = null) + { + super(target, settings); + } + + + + + //-------------------------------------------------------------------------- + // + // Static methods + // + //-------------------------------------------------------------------------- + + public static function add(target:InteractiveObject, settings:Object = null):SwipeGesture + { + return new SwipeGesture(target, settings); + } + + + public static function remove(target:InteractiveObject):SwipeGesture + { + return GesturesManager.gestouch_internal::removeGestureByTarget(SwipeGesture, target) as SwipeGesture; + } + + + + + //-------------------------------------------------------------------------- + // + // Public methods + // + //-------------------------------------------------------------------------- + + override public function reflect():Class + { + return SwipeGesture; + } + + + override public function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return false; + } + // this particular gesture is interested only in those touchpoints on top of target + var touchTarget:InteractiveObject = event.target as InteractiveObject; + if (touchTarget != target && !(target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(touchTarget))) + { + return false; + } + + return true; + } + + + override public function onTouchBegin(touchPoint:TouchPoint):void + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return; + } + + _trackPoint(touchPoint); + } + + + override public function onTouchMove(touchPoint:TouchPoint):void + { + // do calculations only when we track enought points + if (_trackingPointsCount < minTouchPointsCount) + { + return; + } + + _adjustCentralPoint(); + + if (!_slopPassed) + { + _slopPassed = _checkSlop(_centralPoint.moveOffset); + } + + if (_slopPassed) + { + var velocity:Point = _centralPoint.velocity; + + var foo:Number = _centralPoint.moveOffset.length;//FIXME! + var swipeDetected:Boolean = false; + + if (getTimer() - _startTime > minTimeThreshold && foo > 10) + { + var lastMoveX:Number = 0; + var lastMoveY:Number = 0; + + if (_canMoveHorizontally && _canMoveVertically) + { + lastMoveX = _centralPoint.lastMove.x; + lastMoveY = _centralPoint.lastMove.y; + + if (direction == Direction.STRAIGHT_AXES) + { + // go to logic below: if (!swipeDetected && _canMove*).. + } + else if (direction == Direction.OCTO) + { + swipeDetected = velocity.length >= velocityThreshold; + + if (Math.abs(velocity.y) < sideVelocityThreshold) + { + // horizontal swipe + lastMoveY = 0; + } + else if (Math.abs(velocity.x) < sideVelocityThreshold) + { + // vertical swipe + lastMoveX = 0; + } + } + else + { + // free direction swipe + swipeDetected = velocity.length >= velocityThreshold; + } + } + + if (!swipeDetected && _canMoveHorizontally) + { + swipeDetected = Math.abs(velocity.x) >= velocityThreshold && + Math.abs(velocity.y) < sideVelocityThreshold; + + lastMoveX = _centralPoint.lastMove.x; + lastMoveY = 0; + } + if (!swipeDetected && _canMoveVertically) + { + swipeDetected = Math.abs(velocity.y) >= velocityThreshold && + Math.abs(velocity.x) < sideVelocityThreshold; + + lastMoveX = 0; + lastMoveY = _centralPoint.lastMove.y; + } + + if (swipeDetected) + { + _reset(); +// trace("swipe detected:", lastMoveX, lastMoveY); + _dispatch(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, true, false, GesturePhase.ALL, target.mouseX, target.mouseY, 1, 1, 0, lastMoveX, lastMoveY)); + } + } + } + } + + + override public function onTouchEnd(touchPoint:TouchPoint):void + { + _forgetPoint(touchPoint); + } + } +} \ No newline at end of file diff --git a/src/org/gestouch/gestures/ZoomGesture.as b/src/org/gestouch/gestures/ZoomGesture.as new file mode 100644 index 0000000..c0ae80d --- /dev/null +++ b/src/org/gestouch/gestures/ZoomGesture.as @@ -0,0 +1,160 @@ +package org.gestouch.gestures +{ + import flash.display.DisplayObjectContainer; + import flash.display.InteractiveObject; + import flash.events.GesturePhase; + import flash.events.TouchEvent; + import flash.geom.Point; + import org.gestouch.core.GesturesManager; + import org.gestouch.core.TouchPoint; + import org.gestouch.core.gestouch_internal; + import org.gestouch.events.ZoomGestureEvent; + + + + /** + * @author Pavel fljot + */ + public class ZoomGesture extends Gesture + { + public var lockAspectRatio:Boolean = true; + + protected var _currVector:Point = new Point(); + protected var _lastVector:Point = new Point(); + + + public function ZoomGesture(target:InteractiveObject, settings:Object = null) + { + super(target, settings); + } + + + + + //-------------------------------------------------------------------------- + // + // Static methods + // + //-------------------------------------------------------------------------- + + public static function add(target:InteractiveObject, settings:Object = null):ZoomGesture + { + return new ZoomGesture(target, settings); + } + + + public static function remove(target:InteractiveObject):ZoomGesture + { + return GesturesManager.gestouch_internal::removeGestureByTarget(ZoomGesture, target) as ZoomGesture; + } + + + + + //-------------------------------------------------------------------------- + // + // Public methods + // + //-------------------------------------------------------------------------- + + override public function reflect():Class + { + return ZoomGesture; + } + + + override public function shouldTrackPoint(event:TouchEvent, tp:TouchPoint):Boolean + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return false; + } + // this particular gesture is interested only in those touchpoints on top of target + //FIXME? + var touchTarget:InteractiveObject = event.target as InteractiveObject; + if (touchTarget != target && !(target is DisplayObjectContainer && (target as DisplayObjectContainer).contains(touchTarget))) + { + return false; + } + + return true; + } + + + override public function onTouchBegin(touchPoint:TouchPoint):void + { + // No need to track more points than we need + if (_trackingPointsCount == maxTouchPointsCount) + { + return; + } + + _trackPoint(touchPoint); + + if (_trackingPointsCount == minTouchPointsCount) + { + _lastVector.x = _trackingPoints[1].x - _trackingPoints[0].x; + _lastVector.y = _trackingPoints[1].y - _trackingPoints[0].y; + + _adjustCentralPoint(); + + _dispatch(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, true, false, GesturePhase.BEGIN, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + } + } + + + override public function onTouchMove(touchPoint:TouchPoint):void + { + // do calculations only when we track enought points + if (_trackingPointsCount < minTouchPointsCount) + { + return; + } + + _adjustCentralPoint(); + + _currVector.x = _trackingPoints[1].x - _trackingPoints[0].x; + _currVector.y = _trackingPoints[1].y - _trackingPoints[0].y; + + var scaleX:Number = _currVector.x / _lastVector.x; + var scaleY:Number = _currVector.y / _lastVector.y; + if (lockAspectRatio) + { + scaleX = scaleY = _currVector.length / _lastVector.length; + } + else + { + scaleX = _currVector.x / _lastVector.x; + scaleY = _currVector.y / _lastVector.y; + } + + _dispatch(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, true, false, GesturePhase.UPDATE, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y, scaleX, scaleY)); + + _lastVector.x = _currVector.x; + _lastVector.y = _currVector.y; + } + + + override public function onTouchEnd(touchPoint:TouchPoint):void + { + var ending:Boolean = (_trackingPointsCount == minTouchPointsCount); + _forgetPoint(touchPoint); + + if (ending) + { + _dispatch(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, true, false, GesturePhase.END, _lastLocalCentralPoint.x, _lastLocalCentralPoint.y)); + } + } + + + override protected function _preinit():void + { + super._preinit(); + + minTouchPointsCount = 2; + + _propertyNames.push("lockAspectRatio"); + } + } +} \ No newline at end of file diff --git a/version.properties b/version.properties new file mode 100644 index 0000000..c94bbd6 --- /dev/null +++ b/version.properties @@ -0,0 +1 @@ +project.version = 0.1 \ No newline at end of file