Squashed commit of the following:

commit 1eb3dfa9e1
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Aug 7 17:47:43 2012 +0300

    Bumped version to 0.4

commit 3acafd1dfb
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Aug 7 17:46:53 2012 +0300

    Minor fix for Gesture state machine

    to dispatch STATE_CHANGE even when cycling around CHANGED state

commit 2678e12de8
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Aug 7 17:46:12 2012 +0300

    Add protected from a mistyped event listening

commit a3b618e90a
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Aug 7 17:38:10 2012 +0300

    Move native adapters to extensions package

commit 9c817b7472
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Aug 1 19:41:58 2012 +0300

    Implement touch cancelation handling

commit 7bfb1fae36
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Jul 20 14:52:18 2012 +0300

    Minor type fix

commit 7bacbf087b
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Jul 20 14:49:48 2012 +0300

    Minor changes

commit 7278e863d4
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Jul 20 14:48:52 2012 +0300

    Refactor dirty gestures reset

commit eab6cb4a1c
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Jul 20 14:47:52 2012 +0300

    Remove unnecessary casting

commit 4bdefd12bb
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Jul 13 18:27:20 2012 +0300

    Minor cleanup in LongPressGesture

commit c5f91e6de1
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Jul 13 18:08:07 2012 +0300

    Improve internal algorithm for TapGesture

commit ed2efc1954
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Jul 13 16:51:06 2012 +0300

    Improve internal algorithm for SwipeGesture

commit c2125a06e1
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Thu Jul 12 12:46:11 2012 +0300

    Remove unnecessary imports

commit 4a8e6feada
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 11 22:47:55 2012 +0300

    Improve internal algorithm for SwipeGesture

    but still under question

commit 850ed9849f
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 11 13:34:50 2012 +0300

    Change rotation values from degrees to radians

commit 7be7c8c40a
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 11 13:28:43 2012 +0300

    Improve internal algorithm for TransformGesture

commit f8149935db
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Jul 10 17:11:46 2012 +0300

    Improve internal algorithm for ZoomGesture

commit fd55468579
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Jul 10 17:11:30 2012 +0300

    Improve internal algorithm for RotateGesture

commit 0555813e25
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Mon Jul 9 16:47:09 2012 +0300

    Hotfix for gesture state machine validation

commit c2d31b743b
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Thu Jul 5 10:17:53 2012 +0300

    Fix Stage + Starling gestures simultaneous recognition

commit 37f6220eb5
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 4 23:44:16 2012 +0300

    Changed default PanGesture#maxNumTouchesRequired to uint.MAX_VALUE

commit 2e02b13581
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 4 23:43:01 2012 +0300

    Improved early failing strategy implementation

    as in iOS UIGestureRecognizers. Also fixes bugs with new validating state machine.

commit 6273cc33e6
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 4 21:55:34 2012 +0300

    Bumped version to 0.4-beta

commit 00040bb2e2
Merge: 62fa492 963c660
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 4 21:41:26 2012 +0300

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

commit 963c66024e
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 4 21:40:55 2012 +0300

    Update README

commit 2d52729f7c
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 4 21:36:55 2012 +0300

    Update "requireGestureToFail" API implementation

commit 60d9cb6744
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Jul 4 21:34:49 2012 +0300

    Fixed potential bug with registering gesture target

    in case when previous gesture target was GC-ed, but gesture instance was reused to set a new target

commit bbfb3fc34c
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Jul 3 23:23:48 2012 +0300

    Minor performance improvement via "use namespace" access

commit 193332b9d0
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Jul 3 23:12:38 2012 +0300

    Change GestureState to "real" enum

commit 039a7d79f4
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Mon Jul 2 22:33:50 2012 +0300

    Fixed and slightly improved gesture reset

commit 09c35a5d97
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Mon Jul 2 18:29:33 2012 +0300

    Minor tabs and spaces cleanup

commit cdd90d7479
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Mon Jul 2 18:28:30 2012 +0300

    Moved IDisplayListAdapter creating and retrieving logic to Gestouch class

commit 32e9ff979c
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Sun Jul 1 12:57:31 2012 +0300

    Update to always include Stage in hierarchy

    so that gestures registered on Stage will always react on touch

commit d072f6e478
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Sun Jul 1 11:48:10 2012 +0300

    Fix for potential RTE related to "contains" logic

commit 33108a1bc7
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Jun 1 13:51:15 2012 +0300

    Changed global system gesture slop to variable

commit 8d870f4e93
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Jun 1 13:49:29 2012 +0300

    Improved event handling autocompletion (for FD)

commit 54cc5d42bd
Merge: e9132fe 62fa492
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed May 30 20:54:47 2012 +0300

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

commit e9132fec9b
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue May 29 17:03:16 2012 +0300

    Massive refactoring of input layer

commit e0a892654d
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue May 29 17:01:54 2012 +0300

    Untyped globalToLocal call fix

commit 0adfac4c5d
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri May 4 14:54:25 2012 +0300

    Fixed touch/mouse event handling condition in StarlingInputAdapter

commit 62fa492d66
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Mon Apr 2 17:17:48 2012 +0300

    Fixed initial offset calculation for PanGesture

commit 696b6367f2
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Mon Apr 2 17:16:48 2012 +0300

    Fixed location for TapGesture

commit 86439e7627
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Sun Mar 18 12:19:43 2012 +0200

    README update for simplified API

commit d1150b2a35
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Sun Mar 18 12:17:01 2012 +0200

    Simplified API for Gesture target

commit 63a5df8761
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Mar 16 01:04:50 2012 +0200

    Bumped version to 0.4-alpha

commit c983ebe32a
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Fri Mar 16 01:03:40 2012 +0200

    Readme quick update for Starling

commit 13dbd61014
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Tue Mar 13 14:05:50 2012 +0200

    starling initial commit

commit e15c7e7318
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Mar 14 18:36:05 2012 +0200

    Bumped version to 0.3.1

    Releasing bunch of fixes

commit ffb8ce1ec3
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Mar 14 18:00:38 2012 +0200

    Proper state changing on calling Gesture#reset()

commit 13231a4708
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Mar 14 17:49:49 2012 +0200

    Fix for LongPressGesture to work correctly with minPressDuration of zero (and the new IDLE state)

commit a449965e39
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Mar 14 17:34:24 2012 +0200

    Fix to output GestureStateEvent#toString() propely

commit bcb3dfb61f
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Mar 14 16:58:39 2012 +0200

    Removed some redundant code

commit 82742a8465
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Wed Mar 14 15:32:51 2012 +0200

    Made Gesture weak-referencing target

commit e14bbd11bb
Author: Pavel fljot <pavel.fljot@gmail.com>
Date:   Thu Mar 8 13:40:04 2012 +0200

    Input adapters fix to catch events on empty stage

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

    Bumped version to 0.3

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

    Readme updates

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

    Experimental requireGestureToFail API implemented

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

    Reformat condition

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

    Swipe gesture algorithm rewritten for better recognition and failing

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

    Tiny cleanup

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

    New gesture for free transformation

    more precise and performant then combination of 3

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

    Using custom GestureEvent and TransformGestureEvent from now on

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

    Custom GestureEvent and TransformGestureEvent

    because native one have useless phase and stupid constants

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

    TouchesManager should not clone touches

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

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

    Touch properties update

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

    New gesture state

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

    Touch properties updates (and corresponding gestures fixes)

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

    Minor cleanup for Gesture class

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

    Input adapters initialization and disposing

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

    Minor performance fix for SwipeGesture

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

    Added direction for PanGesture

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

    Touch#time fix (affects SwipeGesture)

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

    Moved some IGesturesManager methods under gestouch_internal namespace

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

    Put back automatic input adapter initialization

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

    Moved input logic out to separate classes

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

    Bugfix for mouse/finger release out of stage

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

    Optimized event dispatching

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

    Small optimization for PanGesture

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

    Catch all the Touch/Mouse events in capture phase

    GesturesManager now captures TOUCH_BEGIN or MOUSE_DOWN from the stage in capture phase to be able to prevent their propagation at the target without affecting the gesture regoznition.

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

    Fix central point calculation for more precise transformations

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

    Initial commit for the new architecture

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

    Added Gesture#enabled property

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

    Fix condition for dispatching GestureTrackingEvent.GESTURE_TRACKING_END

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

    Merge branch 'refs/heads/master' into develop
This commit is contained in:
Pavel fljot 2012-08-07 20:48:02 +03:00
parent 07d82c206c
commit b445a5cbe7
44 changed files with 1705 additions and 987 deletions

View file

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

View file

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

View file

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

BIN
libs/starling.swc Normal file

Binary file not shown.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,19 @@
package org.gestouch.extensions.native
{
import org.gestouch.core.ITouchHitTester;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
* @author Pavel fljot
*/
final public class NativeTouchHitTester implements ITouchHitTester
{
public function hitTest(point:Point, nativeTarget:InteractiveObject):Object
{
return nativeTarget;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -4,14 +4,18 @@ package org.gestouch.gestures
import org.gestouch.core.Touch;
import org.gestouch.events.LongPressGestureEvent;
import flash.display.InteractiveObject;
import flash.events.TimerEvent;
import flash.utils.Timer;
/**
* TODO: -location
* - check on iOS (Obj-C) what happens when numTouchesRequired=2, two finger down, then quickly release one.
*
* @eventType org.gestouch.events.LongPressGestureEvent
*/
[Event(name="gestureLongPress", type="org.gestouch.events.LongPressGestureEvent")]
/**
* TODO:
* - add numTapsRequired
*
* @author Pavel fljot
*/
@ -21,16 +25,16 @@ package org.gestouch.gestures
/**
* The minimum time interval in millisecond fingers must press on the target for the gesture to be recognized.
*
* @default 500
*/
public var minPressDuration:uint = 500;
* @default 500
*/
public var minPressDuration:uint = 500;
public var slop:Number = Gesture.DEFAULT_SLOP;
protected var _timer:Timer;
protected var _numTouchesRequiredReached:Boolean;
public function LongPressGesture(target:InteractiveObject = null)
public function LongPressGesture(target:Object = null)
{
super(target);
}
@ -49,7 +53,7 @@ package org.gestouch.gestures
return TapGesture;
}
override public function reset():void
{
super.reset();
@ -67,6 +71,12 @@ package org.gestouch.gestures
//
// --------------------------------------------------------------------------
override protected function eventTypeIsValid(type:String):Boolean
{
return type == LongPressGestureEvent.GESTURE_LONG_PRESS || super.eventTypeIsValid(type);
}
override protected function preinit():void
{
super.preinit();
@ -80,14 +90,7 @@ package org.gestouch.gestures
{
if (touchesCount > numTouchesRequired)
{
if (state == GestureState.BEGAN || state == GestureState.CHANGED)
{
ignoreTouch(touch);
}
else
{
setState(GestureState.FAILED);
}
failOrIgnoreTouch(touch);
return;
}
@ -121,10 +124,9 @@ package org.gestouch.gestures
override protected function onTouchEnd(touch:Touch):void
{
//TODO: check proper condition (behavior) on iOS native
if (_numTouchesRequiredReached)
{
if (((GestureState.BEGAN | GestureState.CHANGED) & state) > 0)
if (state == GestureState.BEGAN || state == GestureState.CHANGED)
{
updateLocation();
if (setState(GestureState.ENDED) && hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS))
@ -144,7 +146,7 @@ package org.gestouch.gestures
}
}
override protected function onDelayedRecognize():void
{
if (hasEventListener(LongPressGestureEvent.GESTURE_LONG_PRESS))

View file

@ -4,9 +4,13 @@ package org.gestouch.gestures
import org.gestouch.core.Touch;
import org.gestouch.events.PanGestureEvent;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
*
* @eventType org.gestouch.events.PanGestureEvent
*/
[Event(name="gesturePan", type="org.gestouch.events.PanGestureEvent")]
/**
* TODO:
@ -27,14 +31,14 @@ package org.gestouch.gestures
protected var _gestureBeginOffsetY:Number;
public function PanGesture(target:InteractiveObject = null)
public function PanGesture(target:Object = null)
{
super(target);
}
/** @private */
private var _maxNumTouchesRequired:uint = 1;
private var _maxNumTouchesRequired:uint = uint.MAX_VALUE;
/**
*
@ -108,19 +112,24 @@ package org.gestouch.gestures
//
// --------------------------------------------------------------------------
override protected function eventTypeIsValid(type:String):Boolean
{
return type == PanGestureEvent.GESTURE_PAN || super.eventTypeIsValid(type);
}
override protected function onTouchBegin(touch:Touch):void
{
if (touchesCount > maxNumTouchesRequired)
{
//TODO
ignoreTouch(touch);
failOrIgnoreTouch(touch);
return;
}
if (touchesCount >= minNumTouchesRequired)
{
updateLocation();
}
}
}
@ -136,6 +145,10 @@ package org.gestouch.gestures
if (state == GestureState.POSSIBLE)
{
prevLocationX = _location.x;
prevLocationY = _location.y;
updateLocation();
// Check if finger moved enough for gesture to be recognized
var locationOffset:Point = touch.locationOffset;
if (direction == PanGestureDirection.VERTICAL)
@ -149,9 +162,6 @@ package org.gestouch.gestures
if (locationOffset.length > slop || slop != slop)//faster isNaN(slop)
{
prevLocationX = _location.x;
prevLocationY = _location.y;
updateLocation();
offsetX = _location.x - prevLocationX;
offsetY = _location.y - prevLocationY;
// acummulate begin offsets for the case when this gesture recognition is delayed by requireGestureToFail

View file

@ -3,11 +3,14 @@ package org.gestouch.gestures
import org.gestouch.core.GestureState;
import org.gestouch.core.Touch;
import org.gestouch.events.RotateGestureEvent;
import org.gestouch.utils.GestureUtils;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
*
* @eventType org.gestouch.events.RotateGestureEvent
*/
[Event(name="gestureRotate", type="org.gestouch.events.RotateGestureEvent")]
/**
* TODO:
@ -17,14 +20,15 @@ package org.gestouch.gestures
*/
public class RotateGesture extends Gesture
{
public var slop:Number = Gesture.DEFAULT_SLOP >> 1;
public var slop:Number = Gesture.DEFAULT_SLOP;
protected var _touch1:Touch;
protected var _touch2:Touch;
protected var _transformVector:Point;
protected var _thresholdAngle:Number;
public function RotateGesture(target:InteractiveObject = null)
public function RotateGesture(target:Object = null)
{
super(target);
}
@ -52,12 +56,17 @@ package org.gestouch.gestures
//
// --------------------------------------------------------------------------
override protected function eventTypeIsValid(type:String):Boolean
{
return type == RotateGestureEvent.GESTURE_ROTATE || super.eventTypeIsValid(type);
}
override protected function onTouchBegin(touch:Touch):void
{
if (touchesCount > 2)
{
//TODO
ignoreTouch(touch);
failOrIgnoreTouch(touch);
return;
}
@ -70,6 +79,9 @@ package org.gestouch.gestures
_touch2 = touch;
_transformVector = _touch2.location.subtract(_touch1.location);
// @see chord length formula
_thresholdAngle = Math.asin(slop / (2 * _transformVector.length)) * 2;
}
}
@ -79,38 +91,41 @@ package org.gestouch.gestures
if (touchesCount < 2)
return;
var recognized:Boolean = true;
var currTransformVector:Point = _touch2.location.subtract(_touch1.location);
var rotation:Number = Math.atan2(currTransformVector.y, currTransformVector.x) - Math.atan2(_transformVector.y, _transformVector.x);
if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop)
if (state == GestureState.POSSIBLE)
{
recognized = false;
const absRotation:Number = rotation >= 0 ? rotation : -rotation;
if (absRotation < _thresholdAngle)
{
// not recognized yet
return;
}
// adjust angle to avoid initial "jump"
rotation = rotation > 0 ? rotation - _thresholdAngle : rotation + _thresholdAngle;
}
if (recognized)
_transformVector.x = currTransformVector.x;
_transformVector.y = currTransformVector.y;
updateLocation();
if (state == GestureState.POSSIBLE)
{
var currTransformVector:Point = _touch2.location.subtract(_touch1.location);
var rotation:Number = Math.atan2(currTransformVector.y, currTransformVector.x) - Math.atan2(_transformVector.y, _transformVector.x);
rotation *= GestureUtils.RADIANS_TO_DEGREES;
_transformVector.x = currTransformVector.x;
_transformVector.y = currTransformVector.y;
updateLocation();
if (state == GestureState.POSSIBLE)
if (setState(GestureState.BEGAN) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE))
{
if (setState(GestureState.BEGAN) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE))
{
dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.BEGAN,
_location.x, _location.y, _localLocation.x, _localLocation.y, rotation));
}
dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.BEGAN,
_location.x, _location.y, _localLocation.x, _localLocation.y, rotation));
}
else
}
else
{
if (setState(GestureState.CHANGED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE))
{
if (setState(GestureState.CHANGED) && hasEventListener(RotateGestureEvent.GESTURE_ROTATE))
{
dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.CHANGED,
_location.x, _location.y, _localLocation.x, _localLocation.y, rotation));
}
dispatchEvent(new RotateGestureEvent(RotateGestureEvent.GESTURE_ROTATE, false, false, GestureState.CHANGED,
_location.x, _location.y, _localLocation.x, _localLocation.y, rotation));
}
}
}

View file

@ -3,35 +3,85 @@ package org.gestouch.gestures
import org.gestouch.core.GestureState;
import org.gestouch.core.Touch;
import org.gestouch.events.SwipeGestureEvent;
import org.gestouch.utils.GestureUtils;
import flash.display.InteractiveObject;
import flash.events.TimerEvent;
import flash.geom.Point;
import flash.system.Capabilities;
import flash.utils.Timer;
/**
*
* @eventType org.gestouch.events.SwipeGestureEvent
*/
[Event(name="gestureSwipe", type="org.gestouch.events.SwipeGestureEvent")]
/**
* TODO:
* -check native behavior on iDevice
* Recognition logic:<br/>
* 1. should be recognized during <code>maxDuration</code> period<br/>
* 2. velocity >= minVelocity <b>OR</b> offset >= minOffset
*
*
* @author Pavel fljot
*/
public class SwipeGesture extends Gesture
{
public var slop:Number = Gesture.DEFAULT_SLOP;
private static const ANGLE:Number = 40 * GestureUtils.DEGREES_TO_RADIANS;
private static const MAX_DURATION:uint = 500;
private static const MIN_OFFSET:Number = Capabilities.screenDPI / 6;
private static const MIN_VELOCITY:Number = 2 * MIN_OFFSET / MAX_DURATION;
/**
* "Dirty" region around touch begin location which does not taken into account
* for gesture failing conditions. It might be useful in case your device is too sensitive
* (so when you just touch the <i>screen</i> you get undesired accidental movement).<br/>
* <b>NB! Unlike in other gestures, default value for slop is 0 here.</b>
*
* @default 0
*/
public var slop:Number = 0;
public var numTouchesRequired:uint = 1;
public var minVelocity:Number = 0.8;
public var minOffset:Number = Gesture.DEFAULT_SLOP;
public var direction:uint = SwipeGestureDirection.ORTHOGONAL;
public var maxDirectionalOffset:Number = Gesture.DEFAULT_SLOP << 1;
/**
* The duration of period (in milliseconds) in which SwipeGesture must be recognized.
* If gesture is not recognized during this period it fails. Default value is 500 (half a
* second) and generally should not be changed. You can change it though for some special
* cases, most likely together with <code>minVelocity</code> and <code>minOffset</code>
* to achieve really custom behavior.
*
* @default 500
*
* @see #minVelocity
* @see #minOffset
*/
public var maxDuration:uint = MAX_DURATION;
/**
* Minimum offset (in pixels) for gesture to be recognized.
* Default value is <code>Capabilities.screenDPI / 6</code> and generally should not
* be changed.
*/
public var minOffset:Number = MIN_OFFSET;
/**
* Minimum velocity (in pixels per millisecond) for gesture to be recognized.
* Default value is <code>2 * minOffset / maxDuration</code> and generally should not
* be changed.
*
* @see #minOffset
* @see #minDuration
*/
public var minVelocity:Number = MIN_VELOCITY;
protected var _offset:Point = new Point();
protected var _startTime:int;
protected var _noDirection:Boolean;
protected var _avrgVel:Point = new Point();
protected var _prevAvrgVel:Point = new Point();
protected var _decelerationCounter:uint = 0;
protected var _timer:Timer;
public function SwipeGesture(target:InteractiveObject = null)
public function SwipeGesture(target:Object = null)
{
super(target);
}
@ -50,14 +100,14 @@ package org.gestouch.gestures
return SwipeGesture;
}
override public function reset():void
{
_startTime = 0;
_offset.x = 0;
_offset.y = 0;
_decelerationCounter = 0;
_timer.reset();
super.reset();
}
@ -70,12 +120,26 @@ package org.gestouch.gestures
//
// --------------------------------------------------------------------------
override protected function eventTypeIsValid(type:String):Boolean
{
return type == SwipeGestureEvent.GESTURE_SWIPE || super.eventTypeIsValid(type);
}
override protected function preinit():void
{
super.preinit();
_timer = new Timer(maxDuration, 1);
_timer.addEventListener(TimerEvent.TIMER_COMPLETE, timer_timerCompleteHandler);
}
override protected function onTouchBegin(touch:Touch):void
{
if (touchesCount > numTouchesRequired)
{
//TODO: or ignore?
setState(GestureState.FAILED);
failOrIgnoreTouch(touch);
return;
}
@ -83,6 +147,10 @@ package org.gestouch.gestures
{
// Because we want to fail as quick as possible
_startTime = touch.time;
_timer.reset();
_timer.delay = maxDuration;
_timer.start();
}
if (touchesCount == numTouchesRequired)
{
@ -100,6 +168,10 @@ package org.gestouch.gestures
if (touchesCount < numTouchesRequired)
return;
var totalTime:int = touch.time - _startTime;
if (totalTime == 0)
return;//It was somehow THAT MUCH performant on one Android tablet
var prevCentralPointX:Number = _centralPoint.x;
var prevCentralPointY:Number = _centralPoint.y;
updateCentralPoint();
@ -107,43 +179,19 @@ package org.gestouch.gestures
_offset.x = _centralPoint.x - _location.x;
_offset.y = _centralPoint.y - _location.y;
var offsetLength:Number = _offset.length;
if (offsetLength < slop)
{
// no need in processing - we're in the very beginning of movement
return;
}
// average velocity (total offset to total duration)
_prevAvrgVel.x = _avrgVel.x;
_prevAvrgVel.y = _avrgVel.y;
var absPrevAvrgVel:Number = _prevAvrgVel.length;
var totalTime:int = touch.time - _startTime;
_avrgVel.x = _offset.x / totalTime;
_avrgVel.y = _offset.y / totalTime;
var avrgVel:Number = _avrgVel.length;
if (avrgVel * 0.95 < absPrevAvrgVel)
{
_decelerationCounter++;
}
if (_decelerationCounter > 5 || avrgVel < 0.1)
{
setState(GestureState.FAILED);
return;
}
if (_noDirection)
{
// We should quickly fail if we have noticable deceleration
// or first movement happend way later after touch
if (avrgVel >= minVelocity && (minOffset != minOffset || offsetLength >= minOffset))
if (avrgVel >= minVelocity || offsetLength >= minOffset)
{
if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
_localLocation = target.globalToLocal(_location);//refresh local location in case target moved
_localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved
dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED,
_location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y));
}
@ -161,25 +209,25 @@ package org.gestouch.gestures
{
var absOffsetX:Number = _offset.x > 0 ? _offset.x : -_offset.x;
if ((SwipeGestureDirection.HORIZONTAL & direction) == 0)
{
// horizontal velocity is greater then vertical, but we're not interested in any horizontal direction
setState(GestureState.FAILED);
}
else if (recentOffsetX < 0 && (direction & SwipeGestureDirection.LEFT) == 0 ||
recentOffsetX > 0 && (direction & SwipeGestureDirection.RIGHT) == 0 ||
Math.abs(_offset.y) > maxDirectionalOffset)
if ((recentOffsetX < 0 && (direction & SwipeGestureDirection.LEFT) == 0) ||
(recentOffsetX > 0 && (direction & SwipeGestureDirection.RIGHT) == 0) ||
Math.abs(Math.atan(_offset.y/_offset.x)) > ANGLE)
{
// movement in opposite direction
// or too much diagonally
setState(GestureState.FAILED);
// Give some tolerance for accidental offset on finger press (slop)
if (offsetLength > slop || slop != slop)//faster isNaN()
{
setState(GestureState.FAILED);
}
}
else if (absVelX >= minVelocity && (minOffset != minOffset || absOffsetX >= minOffset))
else if (absVelX >= minVelocity || absOffsetX >= minOffset)
{
_offset.y = 0;
if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
_localLocation = target.globalToLocal(_location);//refresh local location in case target moved
_localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved
dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED,
_location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y));
}
@ -189,36 +237,37 @@ package org.gestouch.gestures
{
var absOffsetY:Number = _offset.y > 0 ? _offset.y : -_offset.y;
if ((SwipeGestureDirection.VERTICAL & direction) == 0)
{
// horizontal velocity is greater then vertical, but we're not interested in any horizontal direction
setState(GestureState.FAILED);
}
else if (recentOffsetY < 0 && (direction & SwipeGestureDirection.UP) == 0 ||
recentOffsetY > 0 && (direction & SwipeGestureDirection.DOWN) == 0 ||
Math.abs(_offset.x) > maxDirectionalOffset)
if ((recentOffsetY < 0 && (direction & SwipeGestureDirection.UP) == 0) ||
(recentOffsetY > 0 && (direction & SwipeGestureDirection.DOWN) == 0) ||
Math.abs(Math.atan(_offset.x/_offset.y)) > ANGLE)
{
// movement in opposite direction
// or too much diagonally
setState(GestureState.FAILED);
// Give some tolerance for accidental offset on finger press (slop)
if (offsetLength > slop || slop != slop)//faster isNaN()
{
setState(GestureState.FAILED);
}
}
else if (absVelY >= minVelocity && (minOffset != minOffset || absOffsetY >= minOffset))
else if (absVelY >= minVelocity || absOffsetY >= minOffset)
{
_offset.x = 0;
if (setState(GestureState.RECOGNIZED) && hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
_localLocation = target.globalToLocal(_location);//refresh local location in case target moved
_localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved
dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED,
_location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y));
}
}
}
else
// Give some tolerance for accidental offset on finger press (slop)
else if (offsetLength > slop || slop != slop)//faster isNaN()
{
setState(GestureState.FAILED);
}
}
}
}
override protected function onTouchEnd(touch:Touch):void
@ -234,10 +283,27 @@ package org.gestouch.gestures
{
if (hasEventListener(SwipeGestureEvent.GESTURE_SWIPE))
{
_localLocation = target.globalToLocal(_location);//refresh local location in case target moved
_localLocation = targetAdapter.globalToLocal(_location);//refresh local location in case target moved
dispatchEvent(new SwipeGestureEvent(SwipeGestureEvent.GESTURE_SWIPE, false, false, GestureState.RECOGNIZED,
_location.x, _location.y, _localLocation.x, _localLocation.y, _offset.x, _offset.y));
}
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
protected function timer_timerCompleteHandler(event:TimerEvent):void
{
if (state == GestureState.POSSIBLE)
{
setState(GestureState.FAILED);
}
}
}
}

View file

@ -4,14 +4,16 @@ package org.gestouch.gestures
import org.gestouch.core.Touch;
import org.gestouch.events.TapGestureEvent;
import flash.display.InteractiveObject;
import flash.events.TimerEvent;
import flash.utils.Timer;
/**
*
* @eventType org.gestouch.events.TapGestureEvent
*/
[Event(name="gestureTap", type="org.gestouch.events.TapGestureEvent")]
/**
* TODO: check failing conditions (iDevice)
*
* @author Pavel fljot
*/
@ -19,7 +21,7 @@ package org.gestouch.gestures
{
public var numTouchesRequired:uint = 1;
public var numTapsRequired:uint = 1;
public var slop:Number = Gesture.DEFAULT_SLOP;
public var slop:Number = Gesture.DEFAULT_SLOP << 2;//iOS has 45px for 132 dpi screen
public var maxTapDelay:uint = 400;
public var maxTapDuration:uint = 1500;
@ -28,7 +30,7 @@ package org.gestouch.gestures
protected var _tapCounter:uint = 0;
public function TapGesture(target:InteractiveObject = null)
public function TapGesture(target:Object = null)
{
super(target);
}
@ -47,17 +49,17 @@ package org.gestouch.gestures
return TapGesture;
}
override public function reset():void
{
_numTouchesRequiredReached = false;
_tapCounter = 0;
_timer.reset();
super.reset();
}
override public function canPreventGesture(preventedGesture:Gesture):Boolean
{
if (preventedGesture is TapGesture &&
@ -77,6 +79,12 @@ package org.gestouch.gestures
//
// --------------------------------------------------------------------------
override protected function eventTypeIsValid(type:String):Boolean
{
return type == TapGestureEvent.GESTURE_TAP || super.eventTypeIsValid(type);
}
override protected function preinit():void
{
super.preinit();
@ -90,9 +98,7 @@ package org.gestouch.gestures
{
if (touchesCount > numTouchesRequired)
{
// We put more fingers then required at the same time,
// so treat that as failed
setState(GestureState.FAILED);
failOrIgnoreTouch(touch);
return;
}
@ -105,7 +111,8 @@ package org.gestouch.gestures
if (touchesCount == numTouchesRequired)
{
_numTouchesRequiredReached = true;
_numTouchesRequiredReached = true;
updateLocation();
}
}
@ -123,7 +130,6 @@ package org.gestouch.gestures
{
if (!_numTouchesRequiredReached)
{
//TODO: check this condition on iDevice
setState(GestureState.FAILED);
}
else if (touchesCount == 0)
@ -136,7 +142,6 @@ package org.gestouch.gestures
if (_tapCounter == numTapsRequired)
{
updateLocation();
if (setState(GestureState.RECOGNIZED) && hasEventListener(TapGestureEvent.GESTURE_TAP))
{
dispatchEvent(new TapGestureEvent(TapGestureEvent.GESTURE_TAP, false, false, GestureState.RECOGNIZED,

View file

@ -3,12 +3,14 @@ package org.gestouch.gestures
import org.gestouch.core.GestureState;
import org.gestouch.core.Touch;
import org.gestouch.events.TransformGestureEvent;
import org.gestouch.utils.GestureUtils;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
*
* @eventType org.gestouch.events.TransformGestureEvent
*/
[Event(name="gestureTransform", type="org.gestouch.events.TransformGestureEvent")]
/**
* @author Pavel fljot
@ -22,7 +24,7 @@ package org.gestouch.gestures
protected var _transformVector:Point;
public function TransformGesture(target:InteractiveObject = null)
public function TransformGesture(target:Object = null)
{
super(target);
}
@ -59,12 +61,17 @@ package org.gestouch.gestures
//
// --------------------------------------------------------------------------
override protected function eventTypeIsValid(type:String):Boolean
{
return type == TransformGestureEvent.GESTURE_TRANSFORM || super.eventTypeIsValid(type);
}
override protected function onTouchBegin(touch:Touch):void
{
if (touchesCount > 2)
{
//TODO: to ignore or to keep this touch somewhere?
ignoreTouch(touch);
failOrIgnoreTouch(touch);
return;
}
@ -97,51 +104,60 @@ package org.gestouch.gestures
var prevLocation:Point = _location.clone();
updateLocation();
var recognized:Boolean = true;
var currTransformVector:Point;
if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop)
if (state == GestureState.POSSIBLE)
{
recognized = false;
if (slop > 0 && touch.locationOffset.length < slop)
{
// Not recognized yet
if (_touch2)
{
// Recalculate _transformVector to avoid initial "jump" on recognize
_transformVector = _touch2.location.subtract(_touch1.location);
}
return;
}
}
if (recognized)
if (_touch2 && !currTransformVector)
{
var prevLocalLocation:Point;
var offsetX:Number = _location.x - prevLocation.x;
var offsetY:Number = _location.y - prevLocation.y;
var scale:Number = 1;
var rotation:Number = 0;
if (_touch2)
currTransformVector = _touch2.location.subtract(_touch1.location);
}
var prevLocalLocation:Point;
var offsetX:Number = _location.x - prevLocation.x;
var offsetY:Number = _location.y - prevLocation.y;
var scale:Number = 1;
var rotation:Number = 0;
if (_touch2)
{
rotation = Math.atan2(currTransformVector.y, currTransformVector.x) - Math.atan2(_transformVector.y, _transformVector.x);
scale = currTransformVector.length / _transformVector.length;
_transformVector = _touch2.location.subtract(_touch1.location);
}
if (state == GestureState.POSSIBLE)
{
if (setState(GestureState.BEGAN) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM))
{
var currTransformVector:Point = _touch2.location.subtract(_touch1.location);
rotation = Math.atan2(currTransformVector.y, currTransformVector.x) - Math.atan2(_transformVector.y, _transformVector.x);
rotation *= GestureUtils.RADIANS_TO_DEGREES;
scale = currTransformVector.length / _transformVector.length;
_transformVector = _touch2.location.subtract(_touch1.location);
// Note that we dispatch previous location point which gives a way to perform
// accurate UI redraw. See examples project for more info.
prevLocalLocation = targetAdapter.globalToLocal(prevLocation);
dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.BEGAN,
prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY));
}
if (state == GestureState.POSSIBLE)
}
else
{
if (setState(GestureState.CHANGED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM))
{
if (setState(GestureState.BEGAN) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM))
{
// Note that we dispatch previous location point which gives a way to perform
// accurate UI redraw. See examples project for more info.
prevLocalLocation = target.globalToLocal(prevLocation);
dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.BEGAN,
prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY));
}
}
else
{
if (setState(GestureState.CHANGED) && hasEventListener(TransformGestureEvent.GESTURE_TRANSFORM))
{
// Note that we dispatch previous location point which gives a way to perform
// accurate UI redraw. See examples project for more info.
prevLocalLocation = target.globalToLocal(prevLocation);
dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.CHANGED,
prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY));
}
// Note that we dispatch previous location point which gives a way to perform
// accurate UI redraw. See examples project for more info.
prevLocalLocation = targetAdapter.globalToLocal(prevLocation);
dispatchEvent(new TransformGestureEvent(TransformGestureEvent.GESTURE_TRANSFORM, false, false, GestureState.CHANGED,
prevLocation.x, prevLocation.y, prevLocalLocation.x, prevLocalLocation.y, scale, scale, rotation, offsetX, offsetY));
}
}
}

View file

@ -4,27 +4,30 @@ package org.gestouch.gestures
import org.gestouch.core.Touch;
import org.gestouch.events.ZoomGestureEvent;
import flash.display.InteractiveObject;
import flash.geom.Point;
/**
*
* @eventType org.gestouch.events.ZoomGestureEvent
*/
[Event(name="gestureZoom", type="org.gestouch.events.ZoomGestureEvent")]
/**
* TODO:
* -check native behavior on iDevice
*
* @author Pavel fljot
*/
public class ZoomGesture extends Gesture
{
public var slop:Number = Gesture.DEFAULT_SLOP >> 1;
public var slop:Number = Gesture.DEFAULT_SLOP;
public var lockAspectRatio:Boolean = true;
protected var _touch1:Touch;
protected var _touch2:Touch;
protected var _transformVector:Point;
protected var _initialDistance:Number;
public function ZoomGesture(target:InteractiveObject = null)
public function ZoomGesture(target:Object = null)
{
super(target);
}
@ -52,12 +55,17 @@ package org.gestouch.gestures
//
// --------------------------------------------------------------------------
override protected function eventTypeIsValid(type:String):Boolean
{
return type == ZoomGestureEvent.GESTURE_ZOOM || super.eventTypeIsValid(type);
}
override protected function onTouchBegin(touch:Touch):void
{
if (touchesCount > 2)
{
//TODO
ignoreTouch(touch);
failOrIgnoreTouch(touch);
return;
}
@ -70,6 +78,7 @@ package org.gestouch.gestures
_touch2 = touch;
_transformVector = _touch2.location.subtract(_touch1.location);
_initialDistance = _transformVector.length;
}
}
@ -79,48 +88,59 @@ package org.gestouch.gestures
if (touchesCount < 2)
return;
var recognized:Boolean = true;
var currTransformVector:Point = _touch2.location.subtract(_touch1.location);
var scaleX:Number;
var scaleY:Number;
if (state == GestureState.POSSIBLE && slop > 0 && touch.locationOffset.length < slop)
if (state == GestureState.POSSIBLE)
{
recognized = false;
const d:Number = currTransformVector.length - _initialDistance;
const absD:Number = d >= 0 ? d : -d;
if (absD < slop)
{
// Not recognized yet
return;
}
if (slop > 0)
{
// adjust _transformVector to avoid initial "jump"
const slopVector:Point = currTransformVector.clone();
slopVector.normalize(_initialDistance + (d >= 0 ? slop : -slop));
_transformVector = slopVector;
}
}
if (recognized)
if (lockAspectRatio)
{
var currTransformVector:Point = _touch2.location.subtract(_touch1.location);
var scaleX:Number;
var scaleY:Number;
if (lockAspectRatio)
scaleX = scaleY = currTransformVector.length / _transformVector.length;
}
else
{
scaleX = currTransformVector.x / _transformVector.x;
scaleY = currTransformVector.y / _transformVector.y;
}
_transformVector.x = currTransformVector.x;
_transformVector.y = currTransformVector.y;
updateLocation();
if (state == GestureState.POSSIBLE)
{
if (setState(GestureState.BEGAN) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM))
{
scaleX = scaleY = currTransformVector.length / _transformVector.length;
dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.BEGAN,
_location.x, _location.y, _localLocation.x, _localLocation.y, scaleX, scaleY));
}
else
}
else
{
if (setState(GestureState.CHANGED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM))
{
scaleX = currTransformVector.x / _transformVector.x;
scaleY = currTransformVector.y / _transformVector.y;
}
_transformVector.x = currTransformVector.x;
_transformVector.y = currTransformVector.y;
updateLocation();
if (state == GestureState.POSSIBLE)
{
if (setState(GestureState.BEGAN) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM))
{
dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.BEGAN,
_location.x, _location.y, _localLocation.x, _localLocation.y, scaleX, scaleY));
}
}
else
{
if (setState(GestureState.CHANGED) && hasEventListener(ZoomGestureEvent.GESTURE_ZOOM))
{
dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.CHANGED,
_location.x, _location.y, _localLocation.x, _localLocation.y, scaleX, scaleY));
}
dispatchEvent(new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM, false, false, GestureState.CHANGED,
_location.x, _location.y, _localLocation.x, _localLocation.y, scaleX, scaleY));
}
}
}
@ -141,7 +161,7 @@ package org.gestouch.gestures
else if (state == GestureState.POSSIBLE)
{
setState(GestureState.FAILED);
}
}
}
else//== 1
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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