diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java index 4c84864dd..fc84abeae 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java @@ -10,6 +10,7 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.graphics.SurfaceTexture; +import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.InputDevice; @@ -28,6 +29,7 @@ import androidx.annotation.RequiresApi; import net.kdt.pojavlaunch.customcontrols.ControlLayout; import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad; import net.kdt.pojavlaunch.customcontrols.mouse.AbstractTouchpad; +import net.kdt.pojavlaunch.customcontrols.mouse.AndroidPointerCapture; import net.kdt.pojavlaunch.customcontrols.mouse.InGUIEventProcessor; import net.kdt.pojavlaunch.customcontrols.mouse.InGameEventProcessor; import net.kdt.pojavlaunch.customcontrols.mouse.TouchEventProcessor; @@ -76,6 +78,7 @@ public class MinecraftGLSurface extends View implements GrabListener { private final InGameEventProcessor mIngameProcessor = new InGameEventProcessor(mSensitivityFactor); private final InGUIEventProcessor mInGUIProcessor = new InGUIEventProcessor(mScaleFactor); private TouchEventProcessor mCurrentTouchProcessor = mInGUIProcessor; + private AndroidPointerCapture mPointerCapture; private boolean mLastGrabState = false; public MinecraftGLSurface(Context context) { @@ -87,6 +90,12 @@ public class MinecraftGLSurface extends View implements GrabListener { setFocusable(true); } + @RequiresApi(api = Build.VERSION_CODES.O) + private void setUpPointerCapture(AbstractTouchpad touchpad) { + if(mPointerCapture != null) mPointerCapture.detach(); + mPointerCapture = new AndroidPointerCapture(touchpad, this, mScaleFactor); + } + /** Initialize the view and all its settings * @param isAlreadyRunning set to true to tell the view that the game is already running * (only updates the window without calling the start listener) @@ -94,6 +103,7 @@ public class MinecraftGLSurface extends View implements GrabListener { * when the cursor is not grabbed */ public void start(boolean isAlreadyRunning, AbstractTouchpad touchpad){ + if(MainActivity.isAndroid8OrHigher()) setUpPointerCapture(touchpad); mInGUIProcessor.setAbstractTouchpad(touchpad); if(LauncherPreferences.PREF_USE_ALTERNATE_SURFACE){ SurfaceView surfaceView = new SurfaceView(getContext()); @@ -162,7 +172,6 @@ public class MinecraftGLSurface extends View implements GrabListener { } - /** * The touch event for both grabbed an non-grabbed mouse state on the touch screen * Does not cover the virtual mouse touchpad @@ -175,7 +184,14 @@ public class MinecraftGLSurface extends View implements GrabListener { // Looking for a mouse to handle, won't have an effect if no mouse exists. for (int i = 0; i < e.getPointerCount(); i++) { - if(e.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE && e.getToolType(i) != MotionEvent.TOOL_TYPE_STYLUS ) continue; + int toolType = e.getToolType(i); + if(toolType == MotionEvent.TOOL_TYPE_MOUSE && + MainActivity.isAndroid8OrHigher() && + mPointerCapture != null) { + mPointerCapture.handleAutomaticCapture(); + return true; + } + if(toolType != MotionEvent.TOOL_TYPE_STYLUS) continue; // Mouse found if(CallbackBridge.isGrabbing()) return false; @@ -232,32 +248,6 @@ public class MinecraftGLSurface extends View implements GrabListener { } } - //TODO MOVE THIS SOMEWHERE ELSE - /** The input event for mouse with a captured pointer */ - @RequiresApi(26) - @Override - public boolean dispatchCapturedPointerEvent(MotionEvent e) { - CallbackBridge.mouseX += (e.getX()* mScaleFactor); - CallbackBridge.mouseY += (e.getY()* mScaleFactor); - - // Position is updated by many events, hence it is send regardless of the event value - CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY); - - switch (e.getActionMasked()) { - case MotionEvent.ACTION_MOVE: - return true; - case MotionEvent.ACTION_BUTTON_PRESS: - return sendMouseButtonUnconverted(e.getActionButton(), true); - case MotionEvent.ACTION_BUTTON_RELEASE: - return sendMouseButtonUnconverted(e.getActionButton(), false); - case MotionEvent.ACTION_SCROLL: - CallbackBridge.sendScroll(e.getAxisValue(MotionEvent.AXIS_HSCROLL), e.getAxisValue(MotionEvent.AXIS_VSCROLL)); - return true; - default: - return false; - } - } - /** The event for keyboard/ gamepad button inputs */ public boolean processKeyEvent(KeyEvent event) { //Log.i("KeyEvent", event.toString()); @@ -404,29 +394,6 @@ public class MinecraftGLSurface extends View implements GrabListener { mCurrentTouchProcessor = pickEventProcessor(isGrabbing); mLastGrabState = isGrabbing; } - if(!MainActivity.isAndroid8OrHigher()) return; - - boolean hasPointerCapture = hasPointerCapture(); - if(isGrabbing){ - if(!hasPointerCapture) { - requestFocus(); - if(hasWindowFocus()) requestPointerCapture(); - // Otherwise, onWindowFocusChanged() would get called. - } - return; - } - - if(hasPointerCapture) { - releasePointerCapture(); - clearFocus(); - } - } - - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - super.onWindowFocusChanged(hasWindowFocus); - if(hasWindowFocus && CallbackBridge.isGrabbing() && - MainActivity.isAndroid8OrHigher()) requestPointerCapture(); } /** A small interface called when the listener is ready for the first time */ diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AbstractTouchpad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AbstractTouchpad.java index cac0a1172..58eb0a976 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AbstractTouchpad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AbstractTouchpad.java @@ -1,6 +1,37 @@ package net.kdt.pojavlaunch.customcontrols.mouse; public interface AbstractTouchpad { + /** + * Get the supposed display state of the mouse (whether it should be shown when the user is in a GUI) + * Note that this does *not* reflect the actual visibility state of the mouse + * @return current supposed enabled state + */ boolean getDisplayState(); + + /** + * Apply a motion vector to the mouse in form of a two-entry float array. This will move the mouse + * on the screen and send the new cursor position to the game. + * @param vector the array that contains the vector + */ void applyMotionVector(float[] vector); + + /** + * Apply a motion vector to the mouse in form of the separate X/Y coordinates. This will move the mouse + * on the screen and send the new cursor position to the game. + * @param x the relative X coordinate of the vector + * @param y the relative Y coordinate for the vector + */ + void applyMotionVector(float x, float y); + + /** + * Sets the state of the touchpad to "enabled" + * @param supposed if set to true, this will set the supposed display state to enabled but may not + * affect the touchpad until internal conditions are met + * if set to false it will turn the touchpad on regardless of internal conditions + */ + void enable(boolean supposed); + /** + * Sets the state of the touchpad to "disabled". + */ + void disable(); } 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 new file mode 100644 index 000000000..0db1e0ea3 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java @@ -0,0 +1,85 @@ +package net.kdt.pojavlaunch.customcontrols.mouse; + +import android.os.Build; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; + +import androidx.annotation.RequiresApi; + +import net.kdt.pojavlaunch.MainActivity; +import net.kdt.pojavlaunch.MinecraftGLSurface; + +import org.lwjgl.glfw.CallbackBridge; + +@RequiresApi(api = Build.VERSION_CODES.O) +public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChangeListener, View.OnCapturedPointerListener { + private final AbstractTouchpad mTouchpad; + private final View mHostView; + private final float mScaleFactor; + + public AndroidPointerCapture(AbstractTouchpad touchpad, View hostView, float scaleFactor) { + this.mScaleFactor = scaleFactor; + this.mTouchpad = touchpad; + this.mHostView = hostView; + hostView.setOnCapturedPointerListener(this); + hostView.getViewTreeObserver().addOnWindowFocusChangeListener(this); + } + + private void enableTouchpadIfNecessary() { + if(!mTouchpad.getDisplayState()) mTouchpad.enable(true); + } + + public void handleAutomaticCapture() { + if(!CallbackBridge.isGrabbing()) return; + if(mHostView.hasPointerCapture()) { + enableTouchpadIfNecessary(); + return; + } + if(!mHostView.hasWindowFocus()) { + mHostView.requestFocus(); + } else { + mHostView.requestPointerCapture(); + } + } + + @Override + public boolean onCapturedPointer(View view, MotionEvent event) { + if(!CallbackBridge.isGrabbing()) { + enableTouchpadIfNecessary(); + mTouchpad.applyMotionVector(event.getX(), event.getY()); + } 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.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY); + } + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_MOVE: + return true; + case MotionEvent.ACTION_BUTTON_PRESS: + return MinecraftGLSurface.sendMouseButtonUnconverted(event.getActionButton(), true); + case MotionEvent.ACTION_BUTTON_RELEASE: + return MinecraftGLSurface.sendMouseButtonUnconverted(event.getActionButton(), false); + case MotionEvent.ACTION_SCROLL: + CallbackBridge.sendScroll( + event.getAxisValue(MotionEvent.AXIS_HSCROLL), + event.getAxisValue(MotionEvent.AXIS_VSCROLL) + ); + return true; + default: + return false; + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + if(hasFocus && MainActivity.isAndroid8OrHigher()) mHostView.requestPointerCapture(); + } + + public void detach() { + mHostView.setOnCapturedPointerListener(null); + mHostView.getViewTreeObserver().removeOnWindowFocusChangeListener(this); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java index 897cfb6f9..795aa2897 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java @@ -41,13 +41,13 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad { } /** Enable the touchpad */ - public void enable(){ + private void privateEnable(){ setVisibility(VISIBLE); placeMouseAt(currentDisplayMetrics.widthPixels / 2f, currentDisplayMetrics.heightPixels / 2f); } /** Disable the touchpad and hides the mouse */ - public void disable(){ + private void privateDisable(){ setVisibility(GONE); } @@ -55,8 +55,8 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad { public boolean switchState(){ mDisplayState = !mDisplayState; if(!CallbackBridge.isGrabbing()) { - if(mDisplayState) enable(); - else disable(); + if(mDisplayState) privateEnable(); + else privateDisable(); } return mDisplayState; } @@ -112,10 +112,10 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad { } private void updateGrabState(boolean isGrabbing) { if(!isGrabbing) { - if(mDisplayState && getVisibility() != VISIBLE) enable(); - if(!mDisplayState && getVisibility() == VISIBLE) disable(); + if(mDisplayState && getVisibility() != VISIBLE) privateEnable(); + if(!mDisplayState && getVisibility() == VISIBLE) privateDisable(); }else{ - if(getVisibility() != View.GONE) disable(); + if(getVisibility() != View.GONE) privateDisable(); } } @@ -126,8 +126,30 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad { @Override public void applyMotionVector(float[] vector) { - mMouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mMouseX + vector[0] * LauncherPreferences.PREF_MOUSESPEED)); - mMouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mMouseY + vector[1] * LauncherPreferences.PREF_MOUSESPEED)); + applyMotionVector(vector[0], vector[1]); + } + + @Override + public void applyMotionVector(float x, float y) { + mMouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mMouseX + x * LauncherPreferences.PREF_MOUSESPEED)); + mMouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mMouseY + y * LauncherPreferences.PREF_MOUSESPEED)); updateMousePosition(); } + + @Override + public void enable(boolean supposed) { + if(mDisplayState) return; + mDisplayState = true; + if(supposed && CallbackBridge.isGrabbing()) return; + privateEnable(); + + } + + @Override + public void disable() { + if(!mDisplayState) return; + mDisplayState = false; + privateDisable(); + + } }