Improve gesture handling to not make press effect happen immediately and make release effect apply when starting to pan or pinch

This commit is contained in:
drdev
2014-03-06 15:32:58 +00:00
parent 24c6ff43ae
commit 61aa3e5603
8 changed files with 400 additions and 94 deletions

1
.gitattributes vendored
View File

@@ -16038,6 +16038,7 @@ forge-m-base/src/forge/screens/settings/SettingsScreen.java -text
forge-m-base/src/forge/toolbox/FButton.java -text
forge-m-base/src/forge/toolbox/FContainer.java -text
forge-m-base/src/forge/toolbox/FDisplayObject.java -text
forge-m-base/src/forge/toolbox/FGestureAdapter.java -text
forge-m-base/src/forge/toolbox/FLabel.java -text
forge-m-base/src/forge/toolbox/FList.java -text
forge-m-base/src/forge/toolbox/FOverlay.java -text

View File

@@ -15,7 +15,6 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
@@ -30,6 +29,7 @@ import forge.screens.SplashScreen;
import forge.screens.home.HomeScreen;
import forge.toolbox.FContainer;
import forge.toolbox.FDisplayObject;
import forge.toolbox.FGestureAdapter;
public class Forge implements ApplicationListener {
private static Forge game;
@@ -78,7 +78,7 @@ public class Forge implements ApplicationListener {
FSkin.loadFull(splashScreen);
Gdx.input.setInputProcessor(new FGestureDetector());
Gdx.input.setInputProcessor(new MainInputProcessor());
openScreen(new HomeScreen());
splashScreen = null;
}
@@ -152,29 +152,22 @@ public class Forge implements ApplicationListener {
shapeRenderer.dispose();
}
private static class FGestureDetector extends GestureDetector {
private static class MainInputProcessor extends FGestureAdapter {
private static final ArrayList<FDisplayObject> potentialListeners = new ArrayList<FDisplayObject>();
@Override
public boolean touchUp(float x, float y, int pointer, int button) {
for (FDisplayObject listener : potentialListeners) {
if (listener.touchUp(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
break;
}
}
return super.touchUp(x, y, pointer, button);
}
private FGestureDetector() {
super(new GestureListener() {
@Override
public boolean touchDown(float x, float y, int pointer, int button) {
public boolean touchDown(int x, int y, int pointer, int button) {
potentialListeners.clear();
if (currentScreen != null) { //base potential listeners on object containing touch down point
currentScreen.buildTouchListeners(x, y, potentialListeners);
}
return super.touchDown(x, y, pointer, button);
}
@Override
public boolean press(float x, float y) {
for (FDisplayObject listener : potentialListeners) {
if (listener.touchDown(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
if (listener.press(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
return true;
}
}
@@ -182,9 +175,9 @@ public class Forge implements ApplicationListener {
}
@Override
public boolean tap(float x, float y, int count, int button) {
public boolean release(float x, float y) {
for (FDisplayObject listener : potentialListeners) {
if (listener.tap(listener.screenToLocalX(x), listener.screenToLocalY(y), count)) {
if (listener.release(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
return true;
}
}
@@ -202,7 +195,17 @@ public class Forge implements ApplicationListener {
}
@Override
public boolean fling(float velocityX, float velocityY, int button) {
public boolean tap(float x, float y, int count) {
for (FDisplayObject listener : potentialListeners) {
if (listener.tap(listener.screenToLocalX(x), listener.screenToLocalY(y), count)) {
return true;
}
}
return false;
}
@Override
public boolean fling(float velocityX, float velocityY) {
for (FDisplayObject listener : potentialListeners) {
if (listener.fling(velocityX, velocityY)) {
return true;
@@ -222,7 +225,7 @@ public class Forge implements ApplicationListener {
}
@Override
public boolean panStop(float x, float y, int pointer, int button) {
public boolean panStop(float x, float y) {
for (FDisplayObject listener : potentialListeners) {
if (listener.panStop(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
return true;
@@ -250,8 +253,6 @@ public class Forge implements ApplicationListener {
}
return false;
}
});
}
}
public static class Graphics {

View File

@@ -53,13 +53,13 @@ public abstract class LaunchScreen extends FScreen {
}
@Override
public final boolean touchDown(float x, float y) {
public final boolean press(float x, float y) {
pressed = true;
return true;
}
@Override
public final boolean touchUp(float x, float y) {
public final boolean release(float x, float y) {
pressed = false;
return true;
}

View File

@@ -89,7 +89,7 @@ public class FButton extends FDisplayObject {
}
@Override
public final boolean touchDown(float x, float y) {
public final boolean press(float x, float y) {
if (isToggled()) { return true; }
imgL = FSkinImage.BTN_DOWN_LEFT;
imgM = FSkinImage.BTN_DOWN_CENTER;
@@ -98,7 +98,7 @@ public class FButton extends FDisplayObject {
}
@Override
public final boolean touchUp(float x, float y) {
public final boolean release(float x, float y) {
if (isToggled()) { return true; }
resetImg();
return true;

View File

@@ -98,11 +98,15 @@ public abstract class FDisplayObject {
}
}
public boolean touchDown(float x, float y) {
public boolean press(float x, float y) {
return false;
}
public boolean touchUp(float x, float y) {
public boolean longPress(float x, float y) {
return false;
}
public boolean release(float x, float y) {
return false;
}
@@ -110,10 +114,6 @@ public abstract class FDisplayObject {
return false;
}
public boolean longPress(float x, float y) {
return tap(x, y, 1); //treat longPress the same as a tap by default
}
public boolean fling(float velocityX, float velocityY) {
return false;
}

View File

@@ -0,0 +1,304 @@
package forge.toolbox;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.TimeUtils;
import com.badlogic.gdx.utils.Timer;
import com.badlogic.gdx.utils.Timer.Task;
public abstract class FGestureAdapter extends InputAdapter {
public abstract boolean press(float x, float y);
public abstract boolean longPress(float x, float y);
public abstract boolean release(float x, float y);
public abstract boolean tap(float x, float y, int count);
public abstract boolean fling(float velocityX, float velocityY);
public abstract boolean pan(float x, float y, float deltaX, float deltaY);
public abstract boolean panStop(float x, float y);
public abstract boolean zoom(float initialDistance, float distance);
public abstract boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2);
private float tapSquareSize, pressDelay, longPressDelay, lastTapX, lastTapY, tapSquareCenterX, tapSquareCenterY;
private long tapCountInterval, flingDelay, lastTapTime, gestureStartTime;
private int tapCount, lastTapButton, lastTapPointer;
private boolean inTapSquare, pressed, longPressed, pinching, panning;
private final VelocityTracker tracker = new VelocityTracker();
Vector2 pointer1 = new Vector2();
private final Vector2 pointer2 = new Vector2();
private final Vector2 initialPointer1 = new Vector2();
private final Vector2 initialPointer2 = new Vector2();
private final Task pressTask = new Task() {
@Override
public void run () {
if (!pressed) {
press(pointer1.x, pointer1.y);
pressed = true;
}
}
};
private final Task longPressTask = new Task() {
@Override
public void run () {
if (!longPressed) {
longPress(pointer1.x, pointer1.y);
longPressed = true;
}
}
};
public FGestureAdapter() {
this(20f, 0.4f, 0.1f, 1.1f, 0.15f);
}
/** @param tapSquareSize0 half width in pixels of the square around an initial touch event
* @param tapCountInterval0 time in seconds that must pass for two touch down/up sequences to be detected as consecutive taps.
* @param pressDelay0 time in seconds that must pass for a press event to be fired.
* @param longPressDelay0 time in seconds that must pass for a long press event to be fired.
* @param flingDelay0 time in seconds the finger must have been dragged for a fling event to be fired. */
public FGestureAdapter(float tapSquareSize0, float tapCountInterval0, float pressDelay0, float longPressDelay0, float flingDelay0) {
tapSquareSize = tapSquareSize0;
tapCountInterval = (long)(tapCountInterval0 * 1000000000l);
pressDelay = pressDelay0;
longPressDelay = longPressDelay0;
flingDelay = (long)(flingDelay0 * 1000000000l);
}
@Override
public boolean touchDown(int x, int y, int pointer, int button) {
return touchDown((float)x, (float)y, pointer, button);
}
private boolean touchDown(float x, float y, int pointer, int button) {
if (pointer > 1) { return false; }
if (pointer == 0) {
pointer1.set(x, y);
gestureStartTime = Gdx.input.getCurrentEventTime();
tracker.start(x, y, gestureStartTime);
if (Gdx.input.isTouched(1)) {
// Start pinch.
inTapSquare = false;
pinching = true;
initialPointer1.set(pointer1);
initialPointer2.set(pointer2);
releasePress(x, y);
}
else {
// Normal touch down.
inTapSquare = true;
pinching = false;
pressed = false;
tapSquareCenterX = x;
tapSquareCenterY = y;
if (!pressTask.isScheduled()) {
Timer.schedule(pressTask, pressDelay);
}
if (!longPressTask.isScheduled()) {
Timer.schedule(longPressTask, longPressDelay);
}
}
}
else {
// Start pinch.
pointer2.set(x, y);
inTapSquare = false;
pinching = true;
initialPointer1.set(pointer1);
initialPointer2.set(pointer2);
releasePress(pointer1.x, pointer1.y);
}
return true;
}
@Override
public boolean touchDragged(int x, int y, int pointer) {
return touchDragged((float)x, (float)y, pointer);
}
private boolean touchDragged(float x, float y, int pointer) {
if (pointer > 1) { return false; }
if (pointer == 0) {
pointer1.set(x, y);
}
else {
pointer2.set(x, y);
}
// handle pinch zoom
if (pinching) {
boolean result = pinch(initialPointer1, initialPointer2, pointer1, pointer2);
return zoom(initialPointer1.dst(initialPointer2), pointer1.dst(pointer2)) || result;
}
// update tracker
tracker.update(x, y, Gdx.input.getCurrentEventTime());
// check if we are still tapping.
if (inTapSquare && !isWithinTapSquare(x, y, tapSquareCenterX, tapSquareCenterY)) {
releasePress(x, y);
inTapSquare = false;
}
// if we have left the tap square, we are panning
if (!inTapSquare) {
panning = true;
return pan(x, y, tracker.deltaX, tracker.deltaY);
}
return false;
}
@Override
public boolean touchUp(int x, int y, int pointer, int button) {
return touchUp((float)x, (float)y, pointer, button);
}
private boolean touchUp(float x, float y, int pointer, int button) {
if (pointer > 1) { return false; }
// check if we are still tapping.
if (inTapSquare && !isWithinTapSquare(x, y, tapSquareCenterX, tapSquareCenterY)) {
inTapSquare = false;
}
boolean wasPanning = panning;
panning = false;
releasePress(x, y);
if (inTapSquare) {
// handle taps
if (lastTapButton != button || lastTapPointer != pointer
|| TimeUtils.nanoTime() - lastTapTime > tapCountInterval
|| !isWithinTapSquare(x, y, lastTapX, lastTapY)) {
tapCount = 0;
}
tapCount++;
lastTapTime = TimeUtils.nanoTime();
lastTapX = x;
lastTapY = y;
lastTapButton = button;
lastTapPointer = pointer;
gestureStartTime = 0;
return tap(x, y, tapCount);
}
if (pinching) {
// handle pinch end
pinching = false;
panning = true;
// we are in pan mode again, reset velocity tracker
if (pointer == 0) {
// first pointer has lifted off, set up panning to use the second pointer...
tracker.start(pointer2.x, pointer2.y, Gdx.input.getCurrentEventTime());
}
else {
// second pointer has lifted off, set up panning to use the first pointer...
tracker.start(pointer1.x, pointer1.y, Gdx.input.getCurrentEventTime());
}
return false;
}
// handle no longer panning
boolean handled = false;
if (wasPanning && !panning) {
handled = panStop(x, y);
}
// handle fling
gestureStartTime = 0;
long time = Gdx.input.getCurrentEventTime();
if (time - tracker.lastTime < flingDelay) {
tracker.update(x, y, time);
handled = fling(tracker.getVelocityX(), tracker.getVelocityY()) || handled;
}
return handled;
}
private void releasePress(float x, float y) {
pressTask.cancel();
longPressTask.cancel();
longPressed = false;
if (pressed) {
pressed = false;
release(x, y);
}
}
private boolean isWithinTapSquare(float x, float y, float centerX, float centerY) {
return Math.abs(x - centerX) < tapSquareSize && Math.abs(y - centerY) < tapSquareSize;
}
private static class VelocityTracker {
int sampleSize = 10;
float lastX, lastY;
float deltaX, deltaY;
long lastTime;
int numSamples;
float[] meanX = new float[sampleSize];
float[] meanY = new float[sampleSize];
long[] meanTime = new long[sampleSize];
public void start(float x, float y, long timeStamp) {
lastX = x;
lastY = y;
deltaX = 0;
deltaY = 0;
numSamples = 0;
for (int i = 0; i < sampleSize; i++) {
meanX[i] = 0;
meanY[i] = 0;
meanTime[i] = 0;
}
lastTime = timeStamp;
}
public void update(float x, float y, long timeStamp) {
long currTime = timeStamp;
deltaX = x - lastX;
deltaY = y - lastY;
lastX = x;
lastY = y;
long deltaTime = currTime - lastTime;
lastTime = currTime;
int index = numSamples % sampleSize;
meanX[index] = deltaX;
meanY[index] = deltaY;
meanTime[index] = deltaTime;
numSamples++;
}
public float getVelocityX() {
float meanX = getAverage(this.meanX, numSamples);
float meanTime = getAverage(this.meanTime, numSamples) / 1000000000.0f;
if (meanTime == 0) return 0;
return meanX / meanTime;
}
public float getVelocityY() {
float meanY = getAverage(this.meanY, numSamples);
float meanTime = getAverage(this.meanTime, numSamples) / 1000000000.0f;
if (meanTime == 0) return 0;
return meanY / meanTime;
}
private float getAverage(float[] values, int numSamples) {
numSamples = Math.min(sampleSize, numSamples);
float sum = 0;
for (int i = 0; i < numSamples; i++) {
sum += values[i];
}
return sum / numSamples;
}
private long getAverage(long[] values, int numSamples) {
numSamples = Math.min(sampleSize, numSamples);
long sum = 0;
for (int i = 0; i < numSamples; i++) {
sum += values[i];
}
if (numSamples == 0) return 0;
return sum / numSamples;
}
}
}

View File

@@ -116,7 +116,7 @@ public class FLabel extends FDisplayObject {
}
@Override
public final boolean touchDown(float x, float y) {
public final boolean press(float x, float y) {
if (opaque || selectable) {
pressed = true;
return true;
@@ -125,7 +125,7 @@ public class FLabel extends FDisplayObject {
}
@Override
public final boolean touchUp(float x, float y) {
public final boolean release(float x, float y) {
if (pressed) {
pressed = false;
return true;

View File

@@ -160,12 +160,12 @@ public class FList<E> extends FScrollPane {
value = value0;
}
public boolean touchDown(float x, float y) {
public boolean press(float x, float y) {
pressed = true;
return true;
}
public boolean touchUp(float x, float y) {
public boolean release(float x, float y) {
pressed = false;
return true;
}