scaffold.libs_as.org.gestouch.gestures.SwipeGesture.as Maven / Gradle / Ivy
package org.gestouch.gestures
{
import org.gestouch.core.GestureState;
import org.gestouch.core.Touch;
import org.gestouch.utils.GestureUtils;
import flash.events.TimerEvent;
import flash.geom.Point;
import flash.system.Capabilities;
import flash.utils.Timer;
/**
* Recognition logic:
* 1. should be recognized during maxDuration
period
* 2. velocity >= minVelocity OR offset >= minOffset
*
*
* @author Pavel fljot
*/
public class SwipeGesture extends AbstractDiscreteGesture
{
protected var _offset : Point = new Point();
protected var _startTime : int;
protected var _noDirection : Boolean;
protected var _avrgVel : Point = new Point();
protected var _timer : Timer;
/**
* "Dirty" region around touch begin location which is not taken into account for
* recognition/failing algorithms.
*
* @default Gesture.DEFAULT_SLOP
*/
public var slop : Number = Gesture.DEFAULT_SLOP;
public var numTouchesRequired : uint = 1;
public var direction : uint = SwipeGestureDirection.ORTHOGONAL;
/**
* 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 minVelocity
and minOffset
* 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 Capabilities.screenDPI / 6
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 2 * minOffset / maxDuration
and generally should not
* be changed.
*
* @see #minOffset
* @see #minDuration
*/
public var minVelocity : Number = MIN_VELOCITY;
public function get offsetX() : Number
{
return _offset.x;
}
public function get offsetY() : Number
{
return _offset.y;
}
public function SwipeGesture( target : Object = null )
{
super( target );
}
override public function reflect() : Class
{
return SwipeGesture;
}
override public function reset() : void
{
_startTime = 0;
_offset.x = 0;
_offset.y = 0;
_timer.reset();
super.reset();
}
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 )
{
failOrIgnoreTouch( touch );
return;
}
if( touchesCount == 1 )
{
// Because we want to fail as quick as possible
_startTime = touch.time;
_timer.reset();
_timer.delay = maxDuration;
_timer.start();
}
if( touchesCount == numTouchesRequired )
{
updateLocation();
_avrgVel.x = _avrgVel.y = 0;
// cache direction condition for performance
_noDirection = (SwipeGestureDirection.ORTHOGONAL & direction) == 0;
}
}
// --------------------------------------------------------------------------
//
// Public methods
//
// --------------------------------------------------------------------------
override protected function onTouchMove( touch : Touch ) : void
{
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();
_offset.x = _centralPoint.x - _location.x;
_offset.y = _centralPoint.y - _location.y;
var offsetLength : Number = _offset.length;
// average velocity (total offset to total duration)
_avrgVel.x = _offset.x / totalTime;
_avrgVel.y = _offset.y / totalTime;
var avrgVel : Number = _avrgVel.length;
if( _noDirection )
{
if( (offsetLength > slop || slop != slop) && (avrgVel >= minVelocity || offsetLength >= minOffset) )
{
setState( GestureState.RECOGNIZED );
}
}
else
{
var recentOffsetX : Number = _centralPoint.x - prevCentralPointX;
var recentOffsetY : Number = _centralPoint.y - prevCentralPointY;
// faster Math.abs()
var absVelX : Number = _avrgVel.x > 0 ? _avrgVel.x : -_avrgVel.x;
var absVelY : Number = _avrgVel.y > 0 ? _avrgVel.y : -_avrgVel.y;
if( absVelX > absVelY )
{
var absOffsetX : Number = _offset.x > 0 ? _offset.x : -_offset.x;
if( absOffsetX > slop || slop != slop )// faster isNaN()
{
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 );
}
else if( absVelX >= minVelocity || absOffsetX >= minOffset )
{
_offset.y = 0;
setState( GestureState.RECOGNIZED );
}
}
}
else if( absVelY > absVelX )
{
var absOffsetY : Number = _offset.y > 0 ? _offset.y : -_offset.y;
if( absOffsetY > slop || slop != slop )// faster isNaN()
{
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 );
}
else if( absVelY >= minVelocity || absOffsetY >= minOffset )
{
_offset.x = 0;
setState( GestureState.RECOGNIZED );
}
}
}
// 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
{
if( touchesCount < numTouchesRequired )
{
setState( GestureState.FAILED );
}
}
// --------------------------------------------------------------------------
//
// Protected methods
//
// --------------------------------------------------------------------------
override protected function resetNotificationProperties() : void
{
super.resetNotificationProperties();
_offset.x = _offset.y = 0;
}
protected function timer_timerCompleteHandler( event : TimerEvent ) : void
{
if( state == GestureState.POSSIBLE )
{
setState( GestureState.FAILED );
}
}
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;
// --------------------------------------------------------------------------
//
// Event handlers
//
// --------------------------------------------------------------------------
private static const MIN_VELOCITY : Number = 2 * MIN_OFFSET / MAX_DURATION;
}
}