mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2026-05-19 15:34:39 -04:00
Feat[touch_input]: unified gesture system, rclick/lclick/hotbar drop gestures reimplemented
This commit is contained in:
@@ -28,7 +28,7 @@ import androidx.annotation.RequiresApi;
|
||||
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
|
||||
import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad;
|
||||
import net.kdt.pojavlaunch.customcontrols.mouse.InGUIEventProcessor;
|
||||
import net.kdt.pojavlaunch.customcontrols.mouse.IngameEventProcessor;
|
||||
import net.kdt.pojavlaunch.customcontrols.mouse.InGameEventProcessor;
|
||||
import net.kdt.pojavlaunch.customcontrols.mouse.TouchEventProcessor;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.utils.JREUtils;
|
||||
@@ -75,7 +75,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
|
||||
/* View holding the surface, either a SurfaceView or a TextureView */
|
||||
View mSurface;
|
||||
|
||||
private final TouchEventProcessor mIngameProcessor = new IngameEventProcessor(mScaleFactor, mSensitivityFactor);
|
||||
private final TouchEventProcessor mIngameProcessor = new InGameEventProcessor(mScaleFactor, mSensitivityFactor);
|
||||
private final TouchEventProcessor mInGUIProcessor = new InGUIEventProcessor(mScaleFactor);
|
||||
private boolean mLastGrabState = false;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.kdt.pojavlaunch.customcontrols.mouse;
|
||||
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
|
||||
|
||||
public class DropGesture extends ValidatorGesture {
|
||||
public DropGesture(Handler mHandler) {
|
||||
super(mHandler, 250);
|
||||
}
|
||||
boolean mGuiBarHit;
|
||||
|
||||
public void submit(boolean hasGuiBarHit) {
|
||||
submit();
|
||||
mGuiBarHit = hasGuiBarHit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAndTrigger() {
|
||||
if(mGuiBarHit) sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_Q);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGestureCancelled(boolean isSwitching) {}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class InGUIEventProcessor implements TouchEventProcessor {
|
||||
switch (motionEvent.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mTracker.startTracking(motionEvent);
|
||||
sendTouchCoords(motionEvent.getX(), motionEvent.getY());
|
||||
sendTouchCoordinates(motionEvent.getX(), motionEvent.getY());
|
||||
enableMouse();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
@@ -30,7 +30,7 @@ public class InGUIEventProcessor implements TouchEventProcessor {
|
||||
float mainPointerX = motionEvent.getX(pointerIndex);
|
||||
float mainPointerY = motionEvent.getY(pointerIndex);
|
||||
if(pointerCount == 1 || LauncherPreferences.PREF_DISABLE_GESTURES) {
|
||||
sendTouchCoords(mainPointerX, mainPointerY);
|
||||
sendTouchCoordinates(mainPointerX, mainPointerY);
|
||||
if(!mIsMouseDown) enableMouse();
|
||||
}else {
|
||||
float[] motionVector = mTracker.getMotionVector();
|
||||
@@ -45,7 +45,7 @@ public class InGUIEventProcessor implements TouchEventProcessor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sendTouchCoords(float x, float y) {
|
||||
private void sendTouchCoordinates(float x, float y) {
|
||||
CallbackBridge.mouseX = x * mScaleFactor;
|
||||
CallbackBridge.mouseY = y * mScaleFactor;
|
||||
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
|
||||
|
||||
@@ -2,50 +2,37 @@ package net.kdt.pojavlaunch.customcontrols.mouse;
|
||||
|
||||
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
|
||||
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.TapDetector;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.utils.MCOptionUtils;
|
||||
import net.kdt.pojavlaunch.utils.MathUtils;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
public class IngameEventProcessor implements TouchEventProcessor {
|
||||
public class InGameEventProcessor implements TouchEventProcessor {
|
||||
|
||||
private int mGuiScale;
|
||||
@SuppressWarnings("FieldCanBeLocal") // it can't, otherwise the weak reference will disappear
|
||||
private final MCOptionUtils.MCOptionListener mGuiScaleListener = () -> mGuiScale = getMcScale();
|
||||
public static final int FINGER_STILL_THRESHOLD = (int) Tools.dpToPx(9);
|
||||
public static final int MSG_LEFT_MOUSE_BUTTON_CHECK = 1028;
|
||||
public static final int MSG_DROP_ITEM_BUTTON_CHECK = 1029;
|
||||
private final Handler mGestureHandler = new Handler(Looper.getMainLooper()) {
|
||||
public void handleMessage(@NonNull Message msg) {IngameEventProcessor.this.handleMessage(msg);}
|
||||
};
|
||||
|
||||
private final Handler mGestureHandler = new Handler(Looper.getMainLooper());
|
||||
private static final int[] HOTBAR_KEYS = {
|
||||
LwjglGlfwKeycode.GLFW_KEY_1, LwjglGlfwKeycode.GLFW_KEY_2, LwjglGlfwKeycode.GLFW_KEY_3,
|
||||
LwjglGlfwKeycode.GLFW_KEY_4, LwjglGlfwKeycode.GLFW_KEY_5, LwjglGlfwKeycode.GLFW_KEY_6,
|
||||
LwjglGlfwKeycode.GLFW_KEY_7, LwjglGlfwKeycode.GLFW_KEY_8, LwjglGlfwKeycode.GLFW_KEY_9};
|
||||
private float mGestureStartX, mGestureStartY;
|
||||
private boolean mHasPendingLongpressGesture;
|
||||
private boolean mHasPendingDropGesture;
|
||||
private boolean mGestureTriggered;
|
||||
private int mLastHudKey;
|
||||
private final float mScaleFactor;
|
||||
private final double mSensitivity;
|
||||
private final PointerTracker mTracker = new PointerTracker();
|
||||
private final LeftClickGesture mLeftClickGesture = new LeftClickGesture(mGestureHandler);
|
||||
private final RightClickGesture mRightClickGesture = new RightClickGesture(mGestureHandler);
|
||||
private final DropGesture mDropGesture = new DropGesture(mGestureHandler);
|
||||
private final TapDetector mDoubleTapDetector = new TapDetector(2, TapDetector.DETECTION_METHOD_DOWN);
|
||||
|
||||
public IngameEventProcessor(float scaleFactor, double sensitivity) {
|
||||
public InGameEventProcessor(float scaleFactor, double sensitivity) {
|
||||
MCOptionUtils.addMCOptionListener(mGuiScaleListener);
|
||||
mScaleFactor = scaleFactor;
|
||||
mSensitivity = sensitivity;
|
||||
@@ -53,9 +40,12 @@ public class IngameEventProcessor implements TouchEventProcessor {
|
||||
|
||||
@Override
|
||||
public boolean processTouchEvent(MotionEvent motionEvent) {
|
||||
boolean hasDoubleTapped = mDoubleTapDetector.onTouchEvent(motionEvent);
|
||||
switch (motionEvent.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mTracker.startTracking(motionEvent);
|
||||
if(LauncherPreferences.PREF_DISABLE_GESTURES) break;
|
||||
checkGestures(handleGuiBar(motionEvent), hasDoubleTapped);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mTracker.trackEvent(motionEvent);
|
||||
@@ -65,24 +55,35 @@ public class IngameEventProcessor implements TouchEventProcessor {
|
||||
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
|
||||
boolean hasGuiBarHit = handleGuiBar(motionEvent);
|
||||
if(LauncherPreferences.PREF_DISABLE_GESTURES) break;
|
||||
checkLongpressGesture();
|
||||
checkGuiBarGesture(hasGuiBarHit);
|
||||
checkGestures(hasGuiBarHit, hasDoubleTapped);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
cancelGestures();
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
cancelGestures(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelPendingActions() {
|
||||
cancelGestures();
|
||||
cancelGestures(true);
|
||||
}
|
||||
|
||||
private void cancelGestures() {
|
||||
cancelLongpressGesture();
|
||||
cancelDropGesture();
|
||||
if(mGestureTriggered) sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, false);
|
||||
private void checkGestures(boolean hasGuiBarHit, boolean hasDoubleTapped) {
|
||||
if(!hasGuiBarHit) {
|
||||
mLeftClickGesture.inputEvent();
|
||||
mRightClickGesture.inputEvent();
|
||||
}
|
||||
mDropGesture.submit(hasGuiBarHit);
|
||||
if(hasGuiBarHit && hasDoubleTapped && !LauncherPreferences.PREF_DISABLE_SWAP_HAND) {
|
||||
CallbackBridge.sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_F);
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelGestures(boolean isSwitching) {
|
||||
mLeftClickGesture.cancel(isSwitching);
|
||||
mRightClickGesture.cancel(isSwitching);
|
||||
mDropGesture.cancel(isSwitching);
|
||||
}
|
||||
|
||||
private boolean handleGuiBar(MotionEvent motionEvent) {
|
||||
@@ -101,51 +102,15 @@ public class IngameEventProcessor implements TouchEventProcessor {
|
||||
return hasGuiBarHit;
|
||||
}
|
||||
|
||||
private void checkGuiBarGesture(boolean hasGuiBarHit) {
|
||||
if(hasGuiBarHit && !mHasPendingDropGesture) submitDropGesture();
|
||||
if(!hasGuiBarHit) cancelDropGesture();
|
||||
}
|
||||
|
||||
private void checkLongpressGesture() {
|
||||
if(mHasPendingLongpressGesture &&
|
||||
MathUtils.dist(CallbackBridge.mouseX, CallbackBridge.mouseY, mGestureStartX, mGestureStartY)
|
||||
>= FINGER_STILL_THRESHOLD) {
|
||||
cancelLongpressGesture();
|
||||
}
|
||||
if(!mHasPendingLongpressGesture) submitLongpressGesture();
|
||||
}
|
||||
|
||||
private void cancelLongpressGesture() {
|
||||
mGestureHandler.removeMessages(MSG_LEFT_MOUSE_BUTTON_CHECK);
|
||||
mHasPendingLongpressGesture = false;
|
||||
}
|
||||
|
||||
private void submitLongpressGesture() {
|
||||
mGestureStartX = CallbackBridge.mouseX;
|
||||
mGestureStartY = CallbackBridge.mouseY;
|
||||
mGestureHandler.sendEmptyMessageDelayed(MSG_LEFT_MOUSE_BUTTON_CHECK, LauncherPreferences.PREF_LONGPRESS_TRIGGER);
|
||||
mHasPendingLongpressGesture = true;
|
||||
}
|
||||
|
||||
private void cancelDropGesture() {
|
||||
mGestureHandler.removeMessages(MSG_DROP_ITEM_BUTTON_CHECK);
|
||||
mHasPendingDropGesture = false;
|
||||
}
|
||||
|
||||
private void submitDropGesture() {
|
||||
mGestureHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 350);
|
||||
mHasPendingDropGesture = true;
|
||||
}
|
||||
|
||||
/** @return the hotbar key, given the position. -1 if no key are pressed */
|
||||
public int handleGuiBar(int x, int y) {
|
||||
if (!CallbackBridge.isGrabbing()) return -1;
|
||||
|
||||
int barHeight = mcscale(20);
|
||||
int barHeight = mcScale(20);
|
||||
int barY = CallbackBridge.physicalHeight - barHeight;
|
||||
if(y < barY) return -1;
|
||||
|
||||
int barWidth = mcscale(180);
|
||||
int barWidth = mcScale(180);
|
||||
int barX = (CallbackBridge.physicalWidth / 2) - (barWidth / 2);
|
||||
if(x < barX || x >= barX + barWidth) return -1;
|
||||
|
||||
@@ -153,23 +118,7 @@ public class IngameEventProcessor implements TouchEventProcessor {
|
||||
}
|
||||
|
||||
/** Return the size, given the UI scale size */
|
||||
private int mcscale(int input) {
|
||||
private int mcScale(int input) {
|
||||
return (int)((mGuiScale * input)/ mScaleFactor);
|
||||
}
|
||||
|
||||
private void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case MSG_LEFT_MOUSE_BUTTON_CHECK:
|
||||
float x = CallbackBridge.mouseX;
|
||||
float y = CallbackBridge.mouseY;
|
||||
if (MathUtils.dist(x, y, mGestureStartX, mGestureStartY) < FINGER_STILL_THRESHOLD) {
|
||||
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, true);
|
||||
mGestureTriggered = true;
|
||||
}
|
||||
return;
|
||||
case MSG_DROP_ITEM_BUTTON_CHECK:
|
||||
sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_Q);
|
||||
mGestureHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 600);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package net.kdt.pojavlaunch.customcontrols.mouse;
|
||||
|
||||
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
|
||||
import net.kdt.pojavlaunch.utils.MathUtils;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
public class LeftClickGesture extends ValidatorGesture {
|
||||
public static final int FINGER_STILL_THRESHOLD = (int) Tools.dpToPx(9);
|
||||
private float mGestureStartX, mGestureStartY;
|
||||
|
||||
public LeftClickGesture(Handler handler) {
|
||||
super(handler, LauncherPreferences.PREF_LONGPRESS_TRIGGER);
|
||||
}
|
||||
|
||||
public final void inputEvent() {
|
||||
if(submit()) {
|
||||
mGestureStartX = CallbackBridge.mouseX;
|
||||
mGestureStartY = CallbackBridge.mouseY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAndTrigger() {
|
||||
boolean fingerStill = LeftClickGesture.isFingerStill(mGestureStartX, mGestureStartY);
|
||||
if(fingerStill) sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, true);
|
||||
return fingerStill;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGestureCancelled(boolean isSwitching) {
|
||||
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the finger is still when compared to mouseX/mouseY in CallbackBridge.
|
||||
* @param startX the starting X of the gesture
|
||||
* @param startY the starting Y of the gesture
|
||||
* @return whether the finger's position counts as "still" or not
|
||||
*/
|
||||
public static boolean isFingerStill(float startX, float startY) {
|
||||
return MathUtils.dist(
|
||||
CallbackBridge.mouseX,
|
||||
CallbackBridge.mouseY,
|
||||
startX,
|
||||
startY
|
||||
) <= FINGER_STILL_THRESHOLD;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package net.kdt.pojavlaunch.customcontrols.mouse;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
public class RightClickGesture extends ValidatorGesture{
|
||||
private boolean mGestureEnabled = true;
|
||||
private boolean mGestureValid = true;
|
||||
private float mGestureStartX, mGestureStartY;
|
||||
public RightClickGesture(Handler mHandler) {
|
||||
super(mHandler, 150);
|
||||
}
|
||||
|
||||
public final void inputEvent() {
|
||||
if(!mGestureEnabled) return;
|
||||
if(submit()) {
|
||||
mGestureStartX = CallbackBridge.mouseX;
|
||||
mGestureStartY = CallbackBridge.mouseY;
|
||||
mGestureEnabled = false;
|
||||
mGestureValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAndTrigger() {
|
||||
// If the validate() method was called, it means that the user held on for too long. The cancellation should be ignored.
|
||||
mGestureValid = false;
|
||||
// Never call onGestureCancelled. This way we will be able to reserve that only for when
|
||||
// the gesture is stopped in the code (when the user lets go of the screen or the tap was
|
||||
// cancelled by turning on the grab)
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGestureCancelled(boolean isSwitching) {
|
||||
mGestureEnabled = true;
|
||||
if(!mGestureValid || isSwitching) return;
|
||||
boolean fingerStill = LeftClickGesture.isFingerStill(mGestureStartX, mGestureStartY);
|
||||
if(!fingerStill) return;
|
||||
CallbackBridge.sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, true);
|
||||
CallbackBridge.sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package net.kdt.pojavlaunch.customcontrols.mouse;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
/**
|
||||
* This class implements an abstract "validator gesture", meant as a base for implementation of
|
||||
* more complex gestures with pinger position tracking and such.
|
||||
*/
|
||||
public abstract class ValidatorGesture implements Runnable{
|
||||
private final Handler mHandler;
|
||||
private boolean mGestureActive;
|
||||
private final int mRequiredDuration;
|
||||
|
||||
/**
|
||||
* @param mHandler the Handler that will be used for calling back the checkAndTrigger() method.
|
||||
* This Handler should run on the same thread as the callee of submit()/cancel()
|
||||
* @param mRequiredDuration the duration after which the class will call checkAndTrigger().
|
||||
*/
|
||||
public ValidatorGesture(Handler mHandler, int mRequiredDuration) {
|
||||
this.mHandler = mHandler;
|
||||
this.mRequiredDuration = mRequiredDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the gesture, starting the timer and marking this gesture as "active".
|
||||
* If the gesture was already active, this call will be ignored
|
||||
* @return true if the gesture was submitted, false if the call was ignored
|
||||
*/
|
||||
public final boolean submit() {
|
||||
if(mGestureActive) return false;
|
||||
mHandler.postDelayed(this, mRequiredDuration);
|
||||
mGestureActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the gesture, stopping the timer and marking this gesture as "inactive".
|
||||
* If the gesture was already inactive, this call will be ignored.
|
||||
* @param isSwitching true if this gesture was cancelled due to user interaction (the user let go of the finger)
|
||||
* false if this gesture is cancelled due a request from the programmer or the OS.
|
||||
* Note that returning false from checkAndTrigger() counts as user interaction.
|
||||
*/
|
||||
public final void cancel(boolean isSwitching) {
|
||||
if(!mGestureActive) return;
|
||||
mHandler.removeCallbacks(this);
|
||||
onGestureCancelled(isSwitching);
|
||||
mGestureActive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
if(checkAndTrigger()) return;
|
||||
mGestureActive = false;
|
||||
onGestureCancelled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called after mRequiredDuration milliseconds, if the gesture was not cancelled.
|
||||
* @return false if you want to mark this gesture as "inactive"
|
||||
* true otherwise
|
||||
*/
|
||||
public abstract boolean checkAndTrigger();
|
||||
|
||||
/**
|
||||
* This method will be called if the gesture was cancelled using the cancel() method or by returning false
|
||||
* from checkAndTrigger().
|
||||
* @param isSwitching true if this gesture was cancelled due to user interaction (the user let go of the finger)
|
||||
* false if this gesture is cancelled due a request from the programmer or the OS.
|
||||
*/
|
||||
public abstract void onGestureCancelled(boolean isSwitching);
|
||||
}
|
||||
Reference in New Issue
Block a user