
tripleplay.gesture.Swipe Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tripleplay Show documentation
Show all versions of tripleplay Show documentation
Utilities for use in PlayN-based games.
The newest version!
//
// Triple Play - utilities for use in PlayN-based games
// Copyright (c) 2011-2018, Triple Play Authors - All rights reserved.
// http://github.com/threerings/tripleplay/blob/master/LICENSE
package tripleplay.gesture;
import java.util.HashMap;
import java.util.Map;
import pythagoras.f.Point;
/**
* A simple swipe gesture in a given cardinal direction. Supports 1 to 4 fingers. If greedy, will
* indicate a continuous swipe. Completes if: one of the fingers stops moving or is removed from the
* display. Cancels if an additional finger is added to the display or one of the fingers switches
* directions.
*/
public class Swipe extends GestureBase
{
public Swipe (Direction direction) {
this(1, direction);
}
public Swipe (int touches, Direction direction) {
if (touches < 1 || touches > 4) {
Log.log.warning("How many fingers do you think people have?", "tapTouches", 4);
touches = Math.max(1, Math.min(4, touches));
}
if (direction == null) {
Log.log.warning("Swipe cannot operate with a null direction, assuming RIGHT");
direction = Direction.RIGHT;
}
_touches = touches;
setDirection(direction);
}
/**
* If true (the default) a pause counts the same as a finger lifted from the display.
*/
public Swipe cancelOnPause (boolean value) {
_cancelOnPause = value;
return this;
}
/**
* A swipe is not qualified if the user moves outside of a region defined by lines that are
* parallel to the line that goes through the start touch point along the direction axis.
* offAxisTolerance is the distance away from that defining line that the parallel boundaries
* sit. The default is 10 pixels. If you want the user to have more freedom in how finely
* defined their swipes are, make the tolerance large.
*/
public Swipe offAxisTolerance (int pixels) {
_offAxisTolerance = pixels;
return this;
}
/**
* The distance on the Swipe's Direction axis that a touch must move to qualify the Swipe for
* completion.
*/
public Swipe onAxisThreshold (int pixels) {
_onAxisThreshold = pixels;
return this;
}
/**
* An axis Swipe will consider any movement along the axis of its configured direction to be
* valid, rather than only movement in the direction itself.
*/
public Swipe axisSwipe (boolean value) {
_axisSwipe = value;
return this;
}
@Override protected void clearMemory () {
_movedEnough = false;
_startNodes.clear();
_lastNodes.clear();
}
@Override protected void updateState (GestureNode node) {
switch (node.type) {
case START:
_startNodes.put(node.touch.id, node);
break;
case MOVE:
// always grounds for immediate dismissal
if (_startNodes.size() != _touches) setState(State.UNQUALIFIED);
evaluateMove(node);
break;
case PAUSE:
if (_cancelOnPause) setState(getEndState());
break;
case END:
setState(getEndState());
break;
case CANCEL:
setState(State.UNQUALIFIED);
break;
}
}
protected State getEndState () {
return _movedEnough && _startNodes.size() == _touches ? State.COMPLETE : State.UNQUALIFIED;
}
// TODO: any gesture that cares about swiping in a cardinal direction could make use of this
protected void evaluateMove (GestureNode node) {
GestureNode start = _startNodes.get(node.touch.id);
if (start == null) {
Log.log.warning("No start point for a path check, invalid state",
"touchId", node.touch.id);
return;
}
GestureNode lastNode = _lastNodes.get(node.touch.id);
Point current = node.location;
_lastNodes.put(node.touch.id, node);
// we haven't moved far enough yet, no further evaluation needed.
Point startLoc = start.location;
if (current.distance(startLoc) < _onAxisThreshold) return;
float offAxisDistance; // distance from our start position in the perpendicular axis
float lastAxisDistance = axisDistance(
lastNode == null ? null : lastNode.location, current);
if (_direction == Direction.UP || _direction == Direction.DOWN)
offAxisDistance = Math.abs(current.x() - startLoc.x());
else
offAxisDistance = Math.abs(current.y() - startLoc.y());
// if we've strayed outside of the safe zone, or we've backtracked from our last position,
// disqualify
if (offAxisDistance > _offAxisTolerance) setState(State.UNQUALIFIED);
else if (lastAxisDistance < 0) backtracked(node, -lastAxisDistance);
// Figure out if we've moved enough to meet minimum requirements with all touches
if (!_movedEnough) {
boolean allMovedEnough = true;
for (Map.Entry touchStart : _startNodes.entrySet()) {
GestureNode touchLast = _lastNodes.get(touchStart.getKey());
Point lastLoc = touchLast == null ? null : touchLast.location;
if (axisDistance(touchStart.getValue().location, lastLoc) <= _onAxisThreshold) {
allMovedEnough = false;
break;
}
}
if (allMovedEnough) {
_movedEnough = true;
if (_greedy) setState(State.GREEDY);
}
}
}
protected float axisDistance (Point start, Point end) {
if (start == null || end == null) return 0;
float value;
if (_direction == Direction.UP || _direction == Direction.DOWN)
value = end.y() - start.y();
else
value = end.x() - start.x();
return _axisSwipe ? Math.abs(value) : value * _directionModifier;
}
protected void backtracked (GestureNode node, float distance) {
if (!_axisSwipe) {
setState(State.UNQUALIFIED);
}
}
protected void setDirection (Direction direction) {
_direction = direction;
_directionModifier = _direction == Direction.UP || _direction == Direction.LEFT ? -1 : 1;
}
protected int _touches;
protected Direction _direction;
protected int _directionModifier;
protected boolean _movedEnough = false;
protected Map _startNodes = new HashMap();
protected Map _lastNodes = new HashMap();
protected boolean _cancelOnPause = true;
protected int _offAxisTolerance = 10;
protected int _onAxisThreshold = 10;
protected boolean _axisSwipe = false;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy