Feat[touch_input]: Begin reimplementing touch input

This commit is contained in:
artdeell 2024-01-12 23:23:55 +03:00 committed by Maksim Belov
parent 2967d5c639
commit 959d4c4edd
6 changed files with 312 additions and 85 deletions

View File

@ -1,9 +1,7 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.MainActivity.touchCharInput;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_DISABLE_SWAP_HAND;
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
import static org.lwjgl.glfw.CallbackBridge.windowHeight;
import static org.lwjgl.glfw.CallbackBridge.windowWidth;
@ -12,9 +10,6 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputDevice;
@ -32,10 +27,12 @@ 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.TouchEventProcessor;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.JREUtils;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
import net.kdt.pojavlaunch.utils.MathUtils;
import org.lwjgl.glfw.CallbackBridge;
@ -69,69 +66,18 @@ public class MinecraftGLSurface extends View implements GrabListener {
/* Sensitivity, adjusted according to screen size */
private final double mSensitivityFactor = (1.4 * (1080f/ Tools.getDisplayMetrics((Activity) getContext()).heightPixels));
/* Use to detect simple and double taps */
private final TapDetector mSingleTapDetector = new TapDetector(1, TapDetector.DETECTION_METHOD_BOTH);
private final TapDetector mDoubleTapDetector = new TapDetector(2, TapDetector.DETECTION_METHOD_DOWN);
/* MC GUI scale, listened by MCOptionUtils */
private int mGuiScale;
@SuppressWarnings("FieldCanBeLocal") // it can't, otherwise the weak reference will disappear
private final MCOptionUtils.MCOptionListener mGuiScaleListener = () -> mGuiScale = getMcScale();
//private final TapDetector mSingleTapDetector = new TapDetector(1, TapDetector.DETECTION_METHOD_BOTH);
//private final TapDetector mDoubleTapDetector = new TapDetector(2, TapDetector.DETECTION_METHOD_DOWN);
/* Surface ready listener, used by the activity to launch minecraft */
SurfaceReadyListener mSurfaceReadyListener = null;
final Object mSurfaceReadyListenerLock = new Object();
/* View holding the surface, either a SurfaceView or a TextureView */
View mSurface;
/* List of hotbarKeys, used when clicking on the hotbar */
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};
/* Last hotbar button (0-9) registered */
private int mLastHotbarKey = -1;
/* Events can start with only a move instead of an pointerDown due to mouse passthrough */
private boolean mShouldBeDown = false;
/* When fingers are really near to each other, it tends to either swap or remove a pointer ! */
private int mLastPointerCount = 0;
/* Previous MotionEvent position, not scale */
private float mPrevX, mPrevY;
/* PointerID used for the moving camera */
private int mCurrentPointerID = -1000;
/* Initial first pointer positions non-scaled, used to test touch sloppiness */
private float mInitialX, mInitialY;
/* Last first pointer positions non-scaled, used to scroll distance */
private float mScrollLastInitialX, mScrollLastInitialY;
/* How much distance a finger has to go for touch sloppiness to be disabled */
public static final int FINGER_STILL_THRESHOLD = (int) Tools.dpToPx(9);
/* How much distance a finger has to go to scroll */
public static final int FINGER_SCROLL_THRESHOLD = (int) Tools.dpToPx(6);
/* Whether the button was triggered, used by the handler */
private static boolean triggeredLeftMouseButton = false;
/* Handle hotbar throw button and mouse mining button */
public static final int MSG_LEFT_MOUSE_BUTTON_CHECK = 1028;
public static final int MSG_DROP_ITEM_BUTTON_CHECK = 1029;
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message msg) {
if(msg.what == MSG_LEFT_MOUSE_BUTTON_CHECK) {
if (LauncherPreferences.PREF_DISABLE_GESTURES) return;
float x = CallbackBridge.mouseX;
float y = CallbackBridge.mouseY;
if (CallbackBridge.isGrabbing() &&
MathUtils.dist(x, y, mInitialX, mInitialY) < FINGER_STILL_THRESHOLD) {
triggeredLeftMouseButton = true;
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, true);
}
return;
}
if(msg.what == MSG_DROP_ITEM_BUTTON_CHECK) {
if(CallbackBridge.isGrabbing()){
sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_Q);
mHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 600);
}
}
}
};
private final TouchEventProcessor mIngameProcessor = new IngameEventProcessor(mScaleFactor, mSensitivityFactor);
private final TouchEventProcessor mInGUIProcessor = new InGUIEventProcessor(mScaleFactor);
private boolean mLastGrabState = false;
public MinecraftGLSurface(Context context) {
this(context, null);
@ -140,8 +86,6 @@ public class MinecraftGLSurface extends View implements GrabListener {
public MinecraftGLSurface(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
setFocusable(true);
MCOptionUtils.addMCOptionListener(mGuiScaleListener);
}
/** Initialize the view and all its settings
@ -241,7 +185,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
//Getting scaled position from the event
/* Tells if a double tap happened [MOUSE GRAB ONLY]. Doesn't tell where though. */
if(!CallbackBridge.isGrabbing()) {
/*if(!CallbackBridge.isGrabbing()) {
CallbackBridge.mouseX = (e.getX() * mScaleFactor);
CallbackBridge.mouseY = (e.getY() * mScaleFactor);
//One android click = one MC click
@ -393,6 +337,17 @@ public class MinecraftGLSurface extends View implements GrabListener {
mLastPointerCount = e.getPointerCount();
return true;
*/
boolean isGrabbing = CallbackBridge.isGrabbing();
if(mLastGrabState != isGrabbing) {
pickEventProcessor(mLastGrabState).cancelPendingActions();
mLastGrabState = isGrabbing;
}
return pickEventProcessor(isGrabbing).processTouchEvent(e);
}
private TouchEventProcessor pickEventProcessor(boolean isGrabbing) {
return isGrabbing ? mIngameProcessor : mInGUIProcessor;
}
/**
@ -545,25 +500,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
/** @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 barY = CallbackBridge.physicalHeight - barHeight;
if(y < barY) return -1;
int barWidth = mcscale(180);
int barX = (CallbackBridge.physicalWidth / 2) - (barWidth / 2);
if(x < barX || x >= barX + barWidth) return -1;
return HOTBAR_KEYS[(int) net.kdt.pojavlaunch.utils.MathUtils.map(x, barX, barX + barWidth, 0, 9)];
}
/** Return the size, given the UI scale size */
private int mcscale(int input) {
return (int)((mGuiScale * input)/ mScaleFactor);
}
/** Called when the size need to be set at any point during the surface lifecycle **/
public void refreshSize(){

View File

@ -1,6 +1,6 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.MinecraftGLSurface.FINGER_SCROLL_THRESHOLD;
import static net.kdt.pojavlaunch.customcontrols.mouse.InGUIEventProcessor.FINGER_SCROLL_THRESHOLD;
import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF;

View File

@ -0,0 +1,68 @@
package net.kdt.pojavlaunch.customcontrols.mouse;
import android.view.MotionEvent;
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import org.lwjgl.glfw.CallbackBridge;
public class InGUIEventProcessor implements TouchEventProcessor {
private final PointerTracker mTracker = new PointerTracker();
private boolean mIsMouseDown = false;
private final float mScaleFactor;
public static final int FINGER_SCROLL_THRESHOLD = (int) Tools.dpToPx(6);
public InGUIEventProcessor(float scaleFactor) {
mScaleFactor = scaleFactor;
}
@Override
public boolean processTouchEvent(MotionEvent motionEvent) {
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mTracker.startTracking(motionEvent);
sendTouchCoords(motionEvent.getX(), motionEvent.getY());
enableMouse();
break;
case MotionEvent.ACTION_MOVE:
int pointerCount = motionEvent.getPointerCount();
int pointerIndex = mTracker.trackEvent(motionEvent);
float mainPointerX = motionEvent.getX(pointerIndex);
float mainPointerY = motionEvent.getY(pointerIndex);
if(pointerCount == 1 || LauncherPreferences.PREF_DISABLE_GESTURES) {
sendTouchCoords(mainPointerX, mainPointerY);
if(!mIsMouseDown) enableMouse();
}else {
float[] motionVector = mTracker.getMotionVector();
int hScroll = ((int) motionVector[0]) / FINGER_SCROLL_THRESHOLD;
int vScroll = ((int) motionVector[1]) / FINGER_SCROLL_THRESHOLD;
if(hScroll != 0 | vScroll != 0) CallbackBridge.sendScroll(hScroll, vScroll);
}
break;
case MotionEvent.ACTION_UP:
disableMouse();
}
return true;
}
private void sendTouchCoords(float x, float y) {
CallbackBridge.mouseX = x * mScaleFactor;
CallbackBridge.mouseY = y * mScaleFactor;
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
}
private void enableMouse() {
CallbackBridge.sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, true);
mIsMouseDown = true;
}
private void disableMouse() {
CallbackBridge.sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, false);
mIsMouseDown = false;
}
@Override
public void cancelPendingActions() {
disableMouse();
}
}

View File

@ -0,0 +1,175 @@
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.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
import net.kdt.pojavlaunch.utils.MathUtils;
import org.lwjgl.glfw.CallbackBridge;
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 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();
public IngameEventProcessor(float scaleFactor, double sensitivity) {
MCOptionUtils.addMCOptionListener(mGuiScaleListener);
mScaleFactor = scaleFactor;
mSensitivity = sensitivity;
}
@Override
public boolean processTouchEvent(MotionEvent motionEvent) {
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mTracker.startTracking(motionEvent);
break;
case MotionEvent.ACTION_MOVE:
mTracker.trackEvent(motionEvent);
float[] motionVector = mTracker.getMotionVector();
CallbackBridge.mouseX += motionVector[0] * mSensitivity;
CallbackBridge.mouseY += motionVector[1] * mSensitivity;
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
boolean hasGuiBarHit = handleGuiBar(motionEvent);
if(LauncherPreferences.PREF_DISABLE_GESTURES) break;
checkLongpressGesture();
checkGuiBarGesture(hasGuiBarHit);
break;
case MotionEvent.ACTION_UP:
cancelGestures();
}
return true;
}
@Override
public void cancelPendingActions() {
cancelGestures();
}
private void cancelGestures() {
cancelLongpressGesture();
cancelDropGesture();
if(mGestureTriggered) sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, false);
}
private boolean handleGuiBar(MotionEvent motionEvent) {
int hudKeyHandled = -1;
for(int i = 0; i < motionEvent.getPointerCount(); i++) {
hudKeyHandled = handleGuiBar(
(int)motionEvent.getX(i), (int)motionEvent.getY(i)
);
if(hudKeyHandled != -1) break;
}
boolean hasGuiBarHit = hudKeyHandled != -1;
if(hasGuiBarHit && hudKeyHandled != mLastHudKey) {
CallbackBridge.sendKeyPress(hudKeyHandled);
mLastHudKey = hudKeyHandled;
}
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 barY = CallbackBridge.physicalHeight - barHeight;
if(y < barY) return -1;
int barWidth = mcscale(180);
int barX = (CallbackBridge.physicalWidth / 2) - (barWidth / 2);
if(x < barX || x >= barX + barWidth) return -1;
return HOTBAR_KEYS[(int) net.kdt.pojavlaunch.utils.MathUtils.map(x, barX, barX + barWidth, 0, 9)];
}
/** Return the size, given the UI scale size */
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);
}
}
}

View File

@ -0,0 +1,39 @@
package net.kdt.pojavlaunch.customcontrols.mouse;
import android.view.MotionEvent;
public class PointerTracker {
private boolean mColdStart = true;
private int mTrackedPointerId;
private int mPointerCount;
private float mLastX, mLastY;
private final float[] mMotionVector = new float[2];
public void startTracking(MotionEvent motionEvent) {
mColdStart = false;
mTrackedPointerId = motionEvent.getPointerId(0);
mPointerCount = motionEvent.getPointerCount();
mLastX = motionEvent.getX();
mLastY = motionEvent.getY();
}
public int trackEvent(MotionEvent motionEvent) {
int trackedPointerIndex = motionEvent.findPointerIndex(mTrackedPointerId);
int pointerCount = motionEvent.getPointerCount();
if(trackedPointerIndex == -1 || mPointerCount != pointerCount || mColdStart) {
startTracking(motionEvent);
trackedPointerIndex = 0;
}
float trackedX = motionEvent.getX(trackedPointerIndex);
float trackedY = motionEvent.getY(trackedPointerIndex);
mMotionVector[0] = trackedX - mLastX;
mMotionVector[1] = trackedY - mLastY;
mLastX = trackedX;
mLastY = trackedY;
return trackedPointerIndex;
}
public float[] getMotionVector() {
return mMotionVector;
}
}

View File

@ -0,0 +1,8 @@
package net.kdt.pojavlaunch.customcontrols.mouse;
import android.view.MotionEvent;
public interface TouchEventProcessor {
boolean processTouchEvent(MotionEvent motionEvent);
void cancelPendingActions();
}