Feat[input]: Minecraft hotbar as a View

This commit is contained in:
artdeell 2024-04-07 15:25:13 -04:00 committed by Maksim Belov
parent 781341eafd
commit 6bac7ee10b
7 changed files with 160 additions and 134 deletions

View File

@ -75,7 +75,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
/* View holding the surface, either a SurfaceView or a TextureView */ /* View holding the surface, either a SurfaceView or a TextureView */
View mSurface; View mSurface;
private final TouchEventProcessor mIngameProcessor = new InGameEventProcessor(mScaleFactor, mSensitivityFactor); private final TouchEventProcessor mIngameProcessor = new InGameEventProcessor(mSensitivityFactor);
private final TouchEventProcessor mInGUIProcessor = new InGUIEventProcessor(mScaleFactor); private final TouchEventProcessor mInGUIProcessor = new InGUIEventProcessor(mScaleFactor);
private boolean mLastGrabState = false; private boolean mLastGrabState = false;

View File

@ -15,12 +15,11 @@ public class DropGesture implements Runnable{
this.mHandler = mHandler; this.mHandler = mHandler;
} }
public void submit(boolean hasGuiBarHit) { public void submit() {
if(hasGuiBarHit && !mActive) { if(!mActive) {
mActive = true; mActive = true;
mHandler.postDelayed(this, LauncherPreferences.PREF_LONGPRESS_TRIGGER); mHandler.postDelayed(this, LauncherPreferences.PREF_LONGPRESS_TRIGGER);
} }
if(!hasGuiBarHit && mActive) cancel();
} }
public void cancel() { public void cancel() {

View File

@ -1,114 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.mouse;
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
import android.os.Handler;
import android.view.MotionEvent;
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
import org.lwjgl.glfw.CallbackBridge;
public class HotbarTracker implements MCOptionUtils.MCOptionListener {
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 int mLastHudKey, mHudPointerId;
private final DropGesture mDropGesture;
private int mBarWidth, mBarHeight;
private final float mScaleFactor;
/**
* @param mGestureHandler the gesture handler for the integrated drop gesture
* @param mScaleFactor the screen scale factor
*/
public HotbarTracker(Handler mGestureHandler, float mScaleFactor) {
computeBarDimensions();
MCOptionUtils.addMCOptionListener(this);
this.mScaleFactor = mScaleFactor;
mDropGesture = new DropGesture(mGestureHandler);
mHudPointerId = -1;
}
public boolean begin(MotionEvent motionEvent, boolean hasDoubleTapped) {
if(mHudPointerId != -1) return false;
int pointer = motionEvent.getActionIndex();
if(motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) pointer = 0;
int x = (int)motionEvent.getX(pointer);
if(isWithinHotbar(x, (int)motionEvent.getY(pointer))) {
mHudPointerId = motionEvent.getPointerId(pointer);
hotbarClick(x, hasDoubleTapped);
mDropGesture.submit(true);
return true;
}else {
mHudPointerId = -1;
return false;
}
}
public boolean track(MotionEvent motionEvent, int trackedIndex, boolean hasDoubleTapped) {
if(mHudPointerId == -1) return false;
int index = motionEvent.findPointerIndex(mHudPointerId);
if(index == -1) {
cancel();
return false;
}
int x = (int)motionEvent.getX(index);
if(isWithinHotbar(x, (int)motionEvent.getY(index))) {
hotbarClick(x, hasDoubleTapped);
mDropGesture.submit(true);
}else {
mDropGesture.submit(false);
}
return trackedIndex == index;
}
public void cancel() {
mDropGesture.cancel();
mHudPointerId = -1;
}
private boolean isWithinHotbar(int x, int y) {
int barY = CallbackBridge.physicalHeight - mBarHeight;
if(y < barY) return false;
int barX = (CallbackBridge.physicalWidth / 2) - (mBarWidth / 2);
return x >= barX && x < barX + mBarWidth;
}
private void hotbarClick(int x, boolean hasDoubleTapped) {
if(hasDoubleTapped && !LauncherPreferences.PREF_DISABLE_SWAP_HAND) {
CallbackBridge.sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_F);
}
int barX = (CallbackBridge.physicalWidth / 2) - (mBarWidth / 2);
if(x < barX || x >= barX + mBarWidth) return;
int key = HOTBAR_KEYS[(int) net.kdt.pojavlaunch.utils.MathUtils.map(x, barX, barX + mBarWidth, 0, 9)];
if(key != mLastHudKey) {
CallbackBridge.sendKeyPress(key);
// The GUI bar is handled before the gesture will be submitted, so this
// will be resubmitted again soon (with the timer restarted)
mDropGesture.cancel();
mLastHudKey = key;
}
}
private int mcScale(int input, int guiScale) {
return (int)((guiScale * input)/ mScaleFactor);
}
private void computeBarDimensions() {
int guiScale = getMcScale();
mBarHeight = mcScale(20, guiScale);
mBarWidth = mcScale(180, guiScale);
}
@Override
public void onOptionChanged() {
computeBarDimensions();
}
}

View File

@ -0,0 +1,150 @@
package net.kdt.pojavlaunch.customcontrols.mouse;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
import net.kdt.pojavlaunch.TapDetector;
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 HotbarView extends View implements MCOptionUtils.MCOptionListener, View.OnLayoutChangeListener, Runnable {
private final TapDetector mDoubleTapDetector = new TapDetector(2, TapDetector.DETECTION_METHOD_DOWN);
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 final DropGesture mDropGesture = new DropGesture(new Handler(Looper.getMainLooper()));
private final float mScaleFactor = LauncherPreferences.PREF_SCALE_FACTOR/100f;
private int mWidth;
private int mLastIndex;
private int mGuiScale;
public HotbarView(Context context) {
super(context);
initialize();
}
public HotbarView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize();
}
public HotbarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
@SuppressWarnings("unused") // You suggested me this constructor, Android
public HotbarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
private void initialize() {
MCOptionUtils.addMCOptionListener(this);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ViewParent parent = getParent();
if(parent == null) return;
if(parent instanceof View) {
View parentView = (View) parent;
parentView.addOnLayoutChangeListener(this);
}
mGuiScale = MCOptionUtils.getMcScale();
repositionView();
}
private void repositionView() {
ViewGroup.LayoutParams layoutParams = getLayoutParams();
if(!(layoutParams instanceof ViewGroup.MarginLayoutParams))
throw new RuntimeException("Incorrect LayoutParams type, expected ViewGroup.MarginLayoutParams");
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams;
int height;
marginLayoutParams.width = mWidth = mcScale(180);
marginLayoutParams.height = height = mcScale(20);
marginLayoutParams.leftMargin = (CallbackBridge.physicalWidth / 2) - (mWidth / 2);
marginLayoutParams.topMargin = CallbackBridge.physicalHeight - height;
setLayoutParams(marginLayoutParams);
}
@SuppressWarnings("ClickableViewAccessibility") // performClick does not report coordinates.
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!CallbackBridge.isGrabbing()) return false;
// Check if we are double-tapping to swap hands
boolean hasDoubleTapped = mDoubleTapDetector.onTouchEvent(event);
if(hasDoubleTapped && !LauncherPreferences.PREF_DISABLE_SWAP_HAND) CallbackBridge.sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_F);
// Check if we need to cancel the drop event
int actionMasked = event.getActionMasked();
if(isLastEventInGesture(actionMasked)) cancelDropGesture();
else submitDropGesture();
// Determine the hotbar slot
float x = event.getX();
if(x < 0 || x > mWidth) return true;
int hotbarIndex = (int)MathUtils.map(x, 0, mWidth, 0, HOTBAR_KEYS.length);
// Check if the slot changed and we need to make a key press
if(hotbarIndex == mLastIndex) return true;
mLastIndex = hotbarIndex;
int hotbarKey = HOTBAR_KEYS[hotbarIndex];
CallbackBridge.sendKeyPress(hotbarKey);
// Cancel the event since we changed hotbar slots.
cancelDropGesture();
// Only resubmit the gesture only if it isn't the last event we will receive.
if(!isLastEventInGesture(actionMasked)) submitDropGesture();
return true;
}
private void submitDropGesture() {
if(LauncherPreferences.PREF_DISABLE_GESTURES) return;
mDropGesture.submit();
}
private void cancelDropGesture() {
if(LauncherPreferences.PREF_DISABLE_GESTURES) return;
mDropGesture.cancel();
}
private boolean isLastEventInGesture(int actionMasked) {
return actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL;
}
private int mcScale(int input) {
return (int)((mGuiScale * input)/ mScaleFactor);
}
@Override
public void onOptionChanged() {
Tools.runOnUiThread(this);
}
@Override
public void run() {
if(getParent() == null) return;
int scale = MCOptionUtils.getMcScale();
if(scale == mGuiScale) return;
mGuiScale = scale;
repositionView();
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
repositionView();
}
}

View File

@ -4,7 +4,6 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.MotionEvent; import android.view.MotionEvent;
import net.kdt.pojavlaunch.TapDetector;
import net.kdt.pojavlaunch.prefs.LauncherPreferences; import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import org.lwjgl.glfw.CallbackBridge; import org.lwjgl.glfw.CallbackBridge;
@ -13,34 +12,24 @@ public class InGameEventProcessor implements TouchEventProcessor {
private final Handler mGestureHandler = new Handler(Looper.getMainLooper()); private final Handler mGestureHandler = new Handler(Looper.getMainLooper());
private final double mSensitivity; private final double mSensitivity;
private final PointerTracker mTracker = new PointerTracker(); private final PointerTracker mTracker = new PointerTracker();
private final HotbarTracker mGuiBarTracker;
private final LeftClickGesture mLeftClickGesture = new LeftClickGesture(mGestureHandler); private final LeftClickGesture mLeftClickGesture = new LeftClickGesture(mGestureHandler);
private final RightClickGesture mRightClickGesture = new RightClickGesture(mGestureHandler); private final RightClickGesture mRightClickGesture = new RightClickGesture(mGestureHandler);
private final TapDetector mDoubleTapDetector = new TapDetector(2, TapDetector.DETECTION_METHOD_DOWN);
public InGameEventProcessor(float scaleFactor, double sensitivity) { public InGameEventProcessor(double sensitivity) {
mGuiBarTracker = new HotbarTracker(mGestureHandler, scaleFactor);
mSensitivity = sensitivity; mSensitivity = sensitivity;
} }
@Override @Override
public boolean processTouchEvent(MotionEvent motionEvent) { public boolean processTouchEvent(MotionEvent motionEvent) {
boolean hasDoubleTapped = mDoubleTapDetector.onTouchEvent(motionEvent);
switch (motionEvent.getActionMasked()) { switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
if(mGuiBarTracker.begin(motionEvent, hasDoubleTapped)) break;
mTracker.startTracking(motionEvent); mTracker.startTracking(motionEvent);
if(LauncherPreferences.PREF_DISABLE_GESTURES) break; if(LauncherPreferences.PREF_DISABLE_GESTURES) break;
checkGestures(); checkGestures();
break; break;
case MotionEvent.ACTION_POINTER_DOWN:
mGuiBarTracker.begin(motionEvent, hasDoubleTapped);
break;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
int trackedIndex = mTracker.trackEvent(motionEvent); mTracker.trackEvent(motionEvent);
// Don't send mouse positions if there's a finger in the gui bar *and* the camera tracker if(LauncherPreferences.PREF_DISABLE_GESTURES) break;
// tracks the same finger as the gui bar.
if(mGuiBarTracker.track(motionEvent, trackedIndex, hasDoubleTapped)) break;
checkGestures(); checkGestures();
float[] motionVector = mTracker.getMotionVector(); float[] motionVector = mTracker.getMotionVector();
CallbackBridge.mouseX += motionVector[0] * mSensitivity; CallbackBridge.mouseX += motionVector[0] * mSensitivity;
@ -49,7 +38,6 @@ public class InGameEventProcessor implements TouchEventProcessor {
break; break;
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_CANCEL:
mGuiBarTracker.cancel();
mTracker.cancelTracking(); mTracker.cancelTracking();
cancelGestures(false); cancelGestures(false);
} }

View File

@ -24,7 +24,7 @@ public class PointerTracker {
public int trackEvent(MotionEvent motionEvent) { public int trackEvent(MotionEvent motionEvent) {
int trackedPointerIndex = motionEvent.findPointerIndex(mTrackedPointerId); int trackedPointerIndex = motionEvent.findPointerIndex(mTrackedPointerId);
int pointerCount = motionEvent.getPointerCount(); int pointerCount = motionEvent.getPointerCount();
if(trackedPointerIndex == -1 || mPointerCount != pointerCount || mColdStart) { if(trackedPointerIndex == -1 || mPointerCount != pointerCount || mColdStart) {
startTracking(motionEvent); startTracking(motionEvent);
trackedPointerIndex = 0; trackedPointerIndex = 0;
} }

View File

@ -51,6 +51,9 @@
android:layout_height="12dp" android:layout_height="12dp"
android:elevation="10dp" android:elevation="10dp"
android:layout_gravity="center_horizontal"/> android:layout_gravity="center_horizontal"/>
<net.kdt.pojavlaunch.customcontrols.mouse.HotbarView
android:layout_width="0px"
android:layout_height="0px"/>
</net.kdt.pojavlaunch.customcontrols.ControlLayout> </net.kdt.pojavlaunch.customcontrols.ControlLayout>