From 7614989b09f27994ca7ffdf9b757c024a3655368 Mon Sep 17 00:00:00 2001 From: artdeell Date: Wed, 1 May 2024 13:37:48 +0300 Subject: [PATCH] Fix[input]: implement scrolling gestures for touchpads --- .../mouse/AndroidPointerCapture.java | 23 ++++++++-- .../mouse/InGUIEventProcessor.java | 24 +++-------- .../customcontrols/mouse/Scroller.java | 43 +++++++++++++++++++ 3 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Scroller.java diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java index 773b26187..100b1b009 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java @@ -15,10 +15,12 @@ import org.lwjgl.glfw.CallbackBridge; @RequiresApi(api = Build.VERSION_CODES.O) public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChangeListener, View.OnCapturedPointerListener { + private static final float TOUCHPAD_SCROLL_THRESHOLD = 1; private final AbstractTouchpad mTouchpad; private final View mHostView; private final float mScaleFactor; - private final float mPointerPrescale = Tools.dpToPx(1); + private final float mMousePrescale = Tools.dpToPx(1); + private final Scroller mScroller = new Scroller(TOUCHPAD_SCROLL_THRESHOLD); public AndroidPointerCapture(AbstractTouchpad touchpad, View hostView, float scaleFactor) { this.mScaleFactor = scaleFactor; @@ -47,13 +49,26 @@ public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChan @Override public boolean onCapturedPointer(View view, MotionEvent event) { + // Yes, we actually not only receive relative mouse events here, but also absolute touchpad ones! + // Read from relative axis directly to work around. + float relX = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X); + float relY = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y); if(!CallbackBridge.isGrabbing()) { enableTouchpadIfNecessary(); - mTouchpad.applyMotionVector(event.getX() * mPointerPrescale, event.getY() * mPointerPrescale); + // Yes, if the user's touchpad is multi-touch we will also receive events for that. + // So, handle the scrolling gesture ourselves. + relX *= mMousePrescale; + relY *= mMousePrescale; + if(event.getPointerCount() < 2) { + mTouchpad.applyMotionVector(relX, relY); + mScroller.resetScrollOvershoot(); + } else { + mScroller.performScroll(relX, relY); + } } else { // Position is updated by many events, hence it is send regardless of the event value - CallbackBridge.mouseX += (event.getX() * mScaleFactor); - CallbackBridge.mouseY += (event.getY() * mScaleFactor); + CallbackBridge.mouseX += (relX * mScaleFactor); + CallbackBridge.mouseY += (relY * mScaleFactor); CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java index 0bf746915..e9ef9a41e 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java @@ -11,13 +11,13 @@ import net.kdt.pojavlaunch.prefs.LauncherPreferences; import org.lwjgl.glfw.CallbackBridge; public class InGUIEventProcessor implements TouchEventProcessor { + public static final float FINGER_SCROLL_THRESHOLD = Tools.dpToPx(6); private final PointerTracker mTracker = new PointerTracker(); private final GestureDetector mSingleTapDetector; private AbstractTouchpad mTouchpad; private boolean mIsMouseDown = false; private final float mScaleFactor; - private float mScrollOvershootH, mScrollOvershootV; - public static final float FINGER_SCROLL_THRESHOLD = Tools.dpToPx(6); + private final Scroller mScroller = new Scroller(FINGER_SCROLL_THRESHOLD); public InGUIEventProcessor(float scaleFactor) { mSingleTapDetector = new GestureDetector(null, new SingleTapConfirm()); mScaleFactor = scaleFactor; @@ -44,11 +44,11 @@ public class InGUIEventProcessor implements TouchEventProcessor { sendTouchCoordinates(mainPointerX, mainPointerY); if(!mIsMouseDown) enableMouse(); } - } else performScroll(); + } else mScroller.performScroll(mTracker.getMotionVector()); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: - resetScrollOvershoot(); + mScroller.resetScrollOvershoot(); mTracker.cancelTracking(); if(mIsMouseDown) disableMouse(); } @@ -64,20 +64,6 @@ public class InGUIEventProcessor implements TouchEventProcessor { mTouchpad = touchpad; } - private void performScroll() { - float[] motionVector = mTracker.getMotionVector(); - float hScroll = (motionVector[0] / FINGER_SCROLL_THRESHOLD) + mScrollOvershootH; - float vScroll = (motionVector[1] / FINGER_SCROLL_THRESHOLD) + mScrollOvershootV; - int hScrollRound = (int) hScroll, vScrollRound = (int) vScroll; - if(hScrollRound != 0 || vScrollRound != 0) CallbackBridge.sendScroll(hScroll, vScroll); - mScrollOvershootH = hScroll - hScrollRound; - mScrollOvershootV = vScroll - vScrollRound; - } - - private void resetScrollOvershoot() { - mScrollOvershootH = mScrollOvershootV = 0f; - } - private void sendTouchCoordinates(float x, float y) { CallbackBridge.sendCursorPos( x * mScaleFactor, y * mScaleFactor); } @@ -99,7 +85,7 @@ public class InGUIEventProcessor implements TouchEventProcessor { @Override public void cancelPendingActions() { - resetScrollOvershoot(); + mScroller.resetScrollOvershoot(); disableMouse(); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Scroller.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Scroller.java new file mode 100644 index 000000000..8792fcc78 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Scroller.java @@ -0,0 +1,43 @@ +package net.kdt.pojavlaunch.customcontrols.mouse; + +import org.lwjgl.glfw.CallbackBridge; + +public class Scroller { + + private float mScrollOvershootH, mScrollOvershootV; + private final float mScrollThreshold; + + public Scroller(float mScrollThreshold) { + this.mScrollThreshold = mScrollThreshold; + } + + /** + * Perform a scrolling gesture. + * @param dx the X coordinate of the primary pointer's vector + * @param dy the Y coordinate of the primary pointer's vector + */ + public void performScroll(float dx, float dy) { + float hScroll = (dx / mScrollThreshold) + mScrollOvershootH; + float vScroll = (dy / mScrollThreshold) + mScrollOvershootV; + int hScrollRound = (int) hScroll, vScrollRound = (int) vScroll; + if(hScrollRound != 0 || vScrollRound != 0) CallbackBridge.sendScroll(hScroll, vScroll); + mScrollOvershootH = hScroll - hScrollRound; + mScrollOvershootV = vScroll - vScrollRound; + } + + /** + * Perform a scrolling gesture. + * @param vector a 2-component vector that stores the relative position of the primary pointer. + */ + public void performScroll(float[] vector) { + performScroll(vector[0], vector[1]); + } + + /** + * Reset scroll overshoot values. Scroll overshoot makes the scrolling feel less + * choppy, but will cause anomailes if not reset on the end of a scrolling gesture. + */ + public void resetScrollOvershoot() { + mScrollOvershootH = mScrollOvershootV = 0f; + } +}