Feat[mouse]: always grab the mouse and use the touchpad as the cursor.

This commit is contained in:
artdeell 2024-04-27 22:50:54 +03:00 committed by Maksim Belov
parent 5884451735
commit aab37d1fef
4 changed files with 165 additions and 60 deletions

View File

@ -10,6 +10,7 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.os.Build;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.InputDevice; import android.view.InputDevice;
@ -28,6 +29,7 @@ import androidx.annotation.RequiresApi;
import net.kdt.pojavlaunch.customcontrols.ControlLayout; import net.kdt.pojavlaunch.customcontrols.ControlLayout;
import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad; import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad;
import net.kdt.pojavlaunch.customcontrols.mouse.AbstractTouchpad; 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.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.customcontrols.mouse.TouchEventProcessor;
@ -76,6 +78,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
private final InGameEventProcessor mIngameProcessor = new InGameEventProcessor(mSensitivityFactor); private final InGameEventProcessor mIngameProcessor = new InGameEventProcessor(mSensitivityFactor);
private final InGUIEventProcessor mInGUIProcessor = new InGUIEventProcessor(mScaleFactor); private final InGUIEventProcessor mInGUIProcessor = new InGUIEventProcessor(mScaleFactor);
private TouchEventProcessor mCurrentTouchProcessor = mInGUIProcessor; private TouchEventProcessor mCurrentTouchProcessor = mInGUIProcessor;
private AndroidPointerCapture mPointerCapture;
private boolean mLastGrabState = false; private boolean mLastGrabState = false;
public MinecraftGLSurface(Context context) { public MinecraftGLSurface(Context context) {
@ -87,6 +90,12 @@ public class MinecraftGLSurface extends View implements GrabListener {
setFocusable(true); 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 /** Initialize the view and all its settings
* @param isAlreadyRunning set to true to tell the view that the game is already running * @param isAlreadyRunning set to true to tell the view that the game is already running
* (only updates the window without calling the start listener) * (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 * when the cursor is not grabbed
*/ */
public void start(boolean isAlreadyRunning, AbstractTouchpad touchpad){ public void start(boolean isAlreadyRunning, AbstractTouchpad touchpad){
if(MainActivity.isAndroid8OrHigher()) setUpPointerCapture(touchpad);
mInGUIProcessor.setAbstractTouchpad(touchpad); mInGUIProcessor.setAbstractTouchpad(touchpad);
if(LauncherPreferences.PREF_USE_ALTERNATE_SURFACE){ if(LauncherPreferences.PREF_USE_ALTERNATE_SURFACE){
SurfaceView surfaceView = new SurfaceView(getContext()); 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 * The touch event for both grabbed an non-grabbed mouse state on the touch screen
* Does not cover the virtual mouse touchpad * 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. // Looking for a mouse to handle, won't have an effect if no mouse exists.
for (int i = 0; i < e.getPointerCount(); i++) { 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 // Mouse found
if(CallbackBridge.isGrabbing()) return false; 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 */ /** The event for keyboard/ gamepad button inputs */
public boolean processKeyEvent(KeyEvent event) { public boolean processKeyEvent(KeyEvent event) {
//Log.i("KeyEvent", event.toString()); //Log.i("KeyEvent", event.toString());
@ -404,29 +394,6 @@ public class MinecraftGLSurface extends View implements GrabListener {
mCurrentTouchProcessor = pickEventProcessor(isGrabbing); mCurrentTouchProcessor = pickEventProcessor(isGrabbing);
mLastGrabState = 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 */ /** A small interface called when the listener is ready for the first time */

View File

@ -1,6 +1,37 @@
package net.kdt.pojavlaunch.customcontrols.mouse; package net.kdt.pojavlaunch.customcontrols.mouse;
public interface AbstractTouchpad { 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(); 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); 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();
} }

View File

@ -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);
}
}

View File

@ -41,13 +41,13 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad {
} }
/** Enable the touchpad */ /** Enable the touchpad */
public void enable(){ private void privateEnable(){
setVisibility(VISIBLE); setVisibility(VISIBLE);
placeMouseAt(currentDisplayMetrics.widthPixels / 2f, currentDisplayMetrics.heightPixels / 2f); placeMouseAt(currentDisplayMetrics.widthPixels / 2f, currentDisplayMetrics.heightPixels / 2f);
} }
/** Disable the touchpad and hides the mouse */ /** Disable the touchpad and hides the mouse */
public void disable(){ private void privateDisable(){
setVisibility(GONE); setVisibility(GONE);
} }
@ -55,8 +55,8 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad {
public boolean switchState(){ public boolean switchState(){
mDisplayState = !mDisplayState; mDisplayState = !mDisplayState;
if(!CallbackBridge.isGrabbing()) { if(!CallbackBridge.isGrabbing()) {
if(mDisplayState) enable(); if(mDisplayState) privateEnable();
else disable(); else privateDisable();
} }
return mDisplayState; return mDisplayState;
} }
@ -112,10 +112,10 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad {
} }
private void updateGrabState(boolean isGrabbing) { private void updateGrabState(boolean isGrabbing) {
if(!isGrabbing) { if(!isGrabbing) {
if(mDisplayState && getVisibility() != VISIBLE) enable(); if(mDisplayState && getVisibility() != VISIBLE) privateEnable();
if(!mDisplayState && getVisibility() == VISIBLE) disable(); if(!mDisplayState && getVisibility() == VISIBLE) privateDisable();
}else{ }else{
if(getVisibility() != View.GONE) disable(); if(getVisibility() != View.GONE) privateDisable();
} }
} }
@ -126,8 +126,30 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad {
@Override @Override
public void applyMotionVector(float[] vector) { public void applyMotionVector(float[] vector) {
mMouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mMouseX + vector[0] * LauncherPreferences.PREF_MOUSESPEED)); applyMotionVector(vector[0], vector[1]);
mMouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mMouseY + vector[1] * LauncherPreferences.PREF_MOUSESPEED)); }
@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(); 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();
}
} }