From 80c5ea8ea1eea17706bb4ff3f5bafefdc1538d9b Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Mon, 19 Aug 2024 22:51:01 +0300 Subject: [PATCH] DO NOT USE. Feat[gamepad_remapper]: start work on gamepad remapper --- .../kdt/pojavlaunch/MinecraftGLSurface.java | 5 +- .../gamepad/DefaultDataProvider.java | 39 +++ .../customcontrols/gamepad/Gamepad.java | 83 +++-- .../customcontrols/gamepad/GamepadButton.java | 46 +-- .../gamepad/GamepadDataProvider.java | 11 + .../gamepad/GamepadEmulatedButton.java | 33 ++ .../customcontrols/gamepad/GamepadMap.java | 201 ++++++----- .../gamepad/GamepadMapStore.java | 60 ++++ .../gamepad/GamepadMapperAdapter.java | 314 ++++++++++++++++++ .../fragments/GamepadMapperFragment.java | 112 +++++++ .../fragments/MainMenuFragment.java | 2 +- .../layout/fragment_controller_remapper.xml | 58 ++++ .../res/layout/item_controller_mapping.xml | 146 ++++++++ .../src/main/res/values/strings.xml | 21 ++ 14 files changed, 980 insertions(+), 151 deletions(-) create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/DefaultDataProvider.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadDataProvider.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadEmulatedButton.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMapStore.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMapperAdapter.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/GamepadMapperFragment.java create mode 100644 app_pojavlauncher/src/main/res/layout/fragment_controller_remapper.xml create mode 100644 app_pojavlauncher/src/main/res/layout/item_controller_mapping.xml 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 7ea6225126..cefb2d8bae 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java @@ -27,6 +27,7 @@ import androidx.annotation.RequiresApi; import net.kdt.pojavlaunch.customcontrols.ControlLayout; +import net.kdt.pojavlaunch.customcontrols.gamepad.DefaultDataProvider; import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad; import net.kdt.pojavlaunch.customcontrols.mouse.AbstractTouchpad; import net.kdt.pojavlaunch.customcontrols.mouse.AndroidPointerCapture; @@ -212,7 +213,7 @@ public boolean dispatchGenericMotionEvent(MotionEvent event) { if(Gamepad.isGamepadEvent(event)){ if(mGamepad == null){ - mGamepad = new Gamepad(this, event.getDevice()); + mGamepad = new Gamepad(this, event.getDevice(), DefaultDataProvider.INSTANCE); } mInputManager.handleMotionEventInput(getContext(), event, mGamepad); @@ -286,7 +287,7 @@ public boolean processKeyEvent(KeyEvent event) { if(Gamepad.isGamepadEvent(event)){ if(mGamepad == null){ - mGamepad = new Gamepad(this, event.getDevice()); + mGamepad = new Gamepad(this, event.getDevice(), DefaultDataProvider.INSTANCE); } mInputManager.handleKeyEventInput(getContext(), event, mGamepad); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/DefaultDataProvider.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/DefaultDataProvider.java new file mode 100644 index 0000000000..a56790c695 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/DefaultDataProvider.java @@ -0,0 +1,39 @@ +package net.kdt.pojavlaunch.customcontrols.gamepad; + +import net.kdt.pojavlaunch.GrabListener; + +import org.lwjgl.glfw.CallbackBridge; + +public class DefaultDataProvider implements GamepadDataProvider { + public static final DefaultDataProvider INSTANCE = new DefaultDataProvider(); + + private DefaultDataProvider() { + reloadGamepadMaps(); + } + + @Override + public GamepadMap getGameMap() { + return GamepadMapStore.getGameMap(); + } + + + @Override + public GamepadMap getMenuMap() { + return GamepadMapStore.getMenuMap(); + } + + @Override + public boolean isGrabbing() { + return CallbackBridge.isGrabbing(); + } + + @Override + public void attachGrabListener(GrabListener grabListener) { + CallbackBridge.addGrabListener(grabListener); + } + + @Override + public void reloadGamepadMaps() { + GamepadMapStore.load(); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java index dad6a443fd..90b6d18f18 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java @@ -17,17 +17,17 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.Toast; import androidx.core.content.res.ResourcesCompat; import androidx.core.math.MathUtils; +import androidx.fragment.app.Fragment; import net.kdt.pojavlaunch.GrabListener; import net.kdt.pojavlaunch.LwjglGlfwKeycode; import net.kdt.pojavlaunch.R; -import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.prefs.LauncherPreferences; import net.kdt.pojavlaunch.utils.MCOptionUtils; @@ -75,12 +75,11 @@ public class Gamepad implements GrabListener, GamepadHandler { private double mMouseAngle; private double mMouseSensitivity = 19; - private final GamepadMap mGameMap = GamepadMap.getDefaultGameMap(); - private final GamepadMap mMenuMap = GamepadMap.getDefaultMenuMap(); - private GamepadMap mCurrentMap = mGameMap; + private GamepadMap mGameMap; + private GamepadMap mMenuMap; + private GamepadMap mCurrentMap; - // The negation is to force trigger the onGrabState - private boolean isGrabbing = !CallbackBridge.isGrabbing(); + private boolean isGrabbing; /* Choreographer with time to compute delta on ticking */ @@ -91,7 +90,9 @@ public class Gamepad implements GrabListener, GamepadHandler { @SuppressWarnings("FieldCanBeLocal") //the field is used in a WeakReference private final MCOptionUtils.MCOptionListener mGuiScaleListener = () -> notifyGUISizeChange(getMcScale()); - public Gamepad(View contextView, InputDevice inputDevice){ + private final GamepadDataProvider mMapProvider; + + public Gamepad(View contextView, InputDevice inputDevice, GamepadDataProvider mapProvider){ Settings.setDeadzoneScale(PREF_DEADZONE_SCALE); mScreenChoreographer = Choreographer.getInstance(); @@ -120,16 +121,33 @@ public void doFrame(long frameTimeNanos) { int size = (int) ((22 * getMcScale()) / mScaleFactor); mPointerImageView.setLayoutParams(new FrameLayout.LayoutParams(size, size)); + mMapProvider = mapProvider; + CallbackBridge.sendCursorPos(CallbackBridge.windowWidth/2f, CallbackBridge.windowHeight/2f); - ((ViewGroup)contextView.getParent()).addView(mPointerImageView); + ViewParent parent = contextView.getParent(); + if(parent instanceof FrameLayout) { + // ((FrameLayout)parent).addView(mPointerImageView); + } placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2); - CallbackBridge.addGrabListener(this); + reloadGamepadMaps(); + mMapProvider.attachGrabListener(this); } - + public void reloadGamepadMaps() { + if(mGameMap != null) mGameMap.resetPressedState(); + if(mMenuMap != null) mMenuMap.resetPressedState(); + mMapProvider.reloadGamepadMaps(); + mGameMap = mMapProvider.getGameMap(); + mMenuMap = mMapProvider.getMenuMap(); + mCurrentMap = mGameMap; + // Force state refresh + boolean currentGrab = CallbackBridge.isGrabbing(); + isGrabbing = !currentGrab; + onGrabState(currentGrab); + } public void updateJoysticks(){ updateDirectionalJoystick(); @@ -144,8 +162,8 @@ public void notifyGUISizeChange(int newSize){ } - public static void sendInput(int[] keycodes, boolean isDown){ - for(int keycode : keycodes){ + public static void sendInput(short[] keycodes, boolean isDown){ + for(short keycode : keycodes){ switch (keycode){ case GamepadMap.MOUSE_SCROLL_DOWN: if(isDown) CallbackBridge.sendScroll(0, -1); @@ -153,20 +171,23 @@ public static void sendInput(int[] keycodes, boolean isDown){ case GamepadMap.MOUSE_SCROLL_UP: if(isDown) CallbackBridge.sendScroll(0, 1); break; - - case LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT: + case GamepadMap.MOUSE_LEFT: + sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown); + break; + case GamepadMap.MOUSE_MIDDLE: + sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_MIDDLE, isDown); + break; + case GamepadMap.MOUSE_RIGHT: sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown); break; - case LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT: - sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown); + case GamepadMap.UNSPECIFIED: break; - default: sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown); + CallbackBridge.setModifiers(keycode, isDown); break; } - CallbackBridge.setModifiers(keycode, isDown); } } @@ -261,32 +282,32 @@ private GamepadMap getCurrentMap(){ private static void sendDirectionalKeycode(int direction, boolean isDown, GamepadMap map){ switch (direction){ case DIRECTION_NORTH: - sendInput(map.DIRECTION_FORWARD, isDown); + map.DIRECTION_FORWARD.update(isDown); break; case DIRECTION_NORTH_EAST: - sendInput(map.DIRECTION_FORWARD, isDown); - sendInput(map.DIRECTION_RIGHT, isDown); + map.DIRECTION_FORWARD.update(isDown); + map.DIRECTION_RIGHT.update(isDown); break; case DIRECTION_EAST: - sendInput(map.DIRECTION_RIGHT, isDown); + map.DIRECTION_RIGHT.update(isDown); break; case DIRECTION_SOUTH_EAST: - sendInput(map.DIRECTION_RIGHT, isDown); - sendInput(map.DIRECTION_BACKWARD, isDown); + map.DIRECTION_RIGHT.update(isDown); + map.DIRECTION_BACKWARD.update(isDown); break; case DIRECTION_SOUTH: - sendInput(map.DIRECTION_BACKWARD, isDown); + map.DIRECTION_BACKWARD.update(isDown); break; case DIRECTION_SOUTH_WEST: - sendInput(map.DIRECTION_BACKWARD, isDown); - sendInput(map.DIRECTION_LEFT, isDown); + map.DIRECTION_BACKWARD.update(isDown); + map.DIRECTION_LEFT.update(isDown); break; case DIRECTION_WEST: - sendInput(map.DIRECTION_LEFT, isDown); + map.DIRECTION_LEFT.update(isDown); break; case DIRECTION_NORTH_WEST: - sendInput(map.DIRECTION_FORWARD, isDown); - sendInput(map.DIRECTION_LEFT, isDown); + map.DIRECTION_FORWARD.update(isDown); + map.DIRECTION_LEFT.update(isDown); break; } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadButton.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadButton.java index a6c7cc1e79..4d2d9aaba5 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadButton.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadButton.java @@ -1,46 +1,30 @@ package net.kdt.pojavlaunch.customcontrols.gamepad; -import android.view.KeyEvent; - /** - * Simple button able to store its state and some properties + * This class corresponds to a button that does exist on the gamepad */ -public class GamepadButton { - - public int[] keycodes; +public class GamepadButton extends GamepadEmulatedButton { public boolean isToggleable = false; - private boolean mIsDown = false; private boolean mIsToggled = false; - public void update(KeyEvent event){ - boolean isKeyDown = (event.getAction() == KeyEvent.ACTION_DOWN); - update(isKeyDown); - } - public void update(boolean isKeyDown){ - if(isKeyDown != mIsDown){ - mIsDown = isKeyDown; - if(isToggleable){ - if(isKeyDown){ - mIsToggled = !mIsToggled; - Gamepad.sendInput(keycodes, mIsToggled); - } - return; - } - Gamepad.sendInput(keycodes, mIsDown); + @Override + protected void onDownStateChanged(boolean isDown) { + if(isToggleable && isDown){ + mIsToggled = !mIsToggled; + Gamepad.sendInput(keycodes, mIsToggled); + return; } + super.onDownStateChanged(isDown); } - public void resetButtonState(){ - if(mIsDown || mIsToggled){ + @Override + public void resetButtonState() { + if(!mIsDown && mIsToggled) { Gamepad.sendInput(keycodes, false); + mIsToggled = false; + } else { + super.resetButtonState(); } - mIsDown = false; - mIsToggled = false; } - - public boolean isDown(){ - return isToggleable ? mIsToggled : mIsDown; - } - } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadDataProvider.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadDataProvider.java new file mode 100644 index 0000000000..168cc0d39f --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadDataProvider.java @@ -0,0 +1,11 @@ +package net.kdt.pojavlaunch.customcontrols.gamepad; + +import net.kdt.pojavlaunch.GrabListener; + +public interface GamepadDataProvider { + GamepadMap getMenuMap(); + GamepadMap getGameMap(); + boolean isGrabbing(); + void attachGrabListener(GrabListener grabListener); + void reloadGamepadMaps(); +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadEmulatedButton.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadEmulatedButton.java new file mode 100644 index 0000000000..a05095b80a --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadEmulatedButton.java @@ -0,0 +1,33 @@ +package net.kdt.pojavlaunch.customcontrols.gamepad; + +import android.view.KeyEvent; + +/** + * This class corresponds to a button that does not physically exist on the gamepad, but is + * emulated from other inputs on it (like WASD directional keys) + */ +public class GamepadEmulatedButton { + public short[] keycodes; + protected boolean mIsDown = false; + + public void update(KeyEvent event) { + boolean isKeyDown = (event.getAction() == KeyEvent.ACTION_DOWN); + update(isKeyDown); + } + + public void update(boolean isKeyDown){ + if(isKeyDown != mIsDown){ + mIsDown = isKeyDown; + onDownStateChanged(mIsDown); + } + } + + public void resetButtonState() { + if(mIsDown) Gamepad.sendInput(keycodes, false); + mIsDown = false; + } + + protected void onDownStateChanged(boolean isDown) { + Gamepad.sendInput(keycodes, mIsDown); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMap.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMap.java index 4d7fbb1c15..530e23908c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMap.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMap.java @@ -4,8 +4,16 @@ public class GamepadMap { - public static final int MOUSE_SCROLL_DOWN = -1; - public static final int MOUSE_SCROLL_UP = -2; + + public static final short MOUSE_SCROLL_DOWN = -1; + public static final short MOUSE_SCROLL_UP = -2; + // Made mouse keycodes their own specials because managing special keycodes above 0 + // proved to be complicated + public static final short MOUSE_LEFT = -3; + public static final short MOUSE_MIDDLE = -4; + public static final short MOUSE_RIGHT = -5; + // Workaround, because GLFW_KEY_UNKNOWN and GLFW_MOUSE_BUTTON_LEFT are both 0 + public static final short UNSPECIFIED = -6; /* This class is just here to store the mapping @@ -14,33 +22,55 @@ public class GamepadMap { Be warned, you should define ALL keys if you want to avoid a non defined exception */ - public final GamepadButton BUTTON_A = new GamepadButton(); - public final GamepadButton BUTTON_B = new GamepadButton(); - public final GamepadButton BUTTON_X = new GamepadButton(); - public final GamepadButton BUTTON_Y = new GamepadButton(); + public GamepadButton BUTTON_A; + public GamepadButton BUTTON_B; + public GamepadButton BUTTON_X; + public GamepadButton BUTTON_Y; - public final GamepadButton BUTTON_START = new GamepadButton(); - public final GamepadButton BUTTON_SELECT = new GamepadButton(); + public GamepadButton BUTTON_START; + public GamepadButton BUTTON_SELECT; - public final GamepadButton TRIGGER_RIGHT = new GamepadButton(); //R2 - public final GamepadButton TRIGGER_LEFT = new GamepadButton(); //L2 - - public final GamepadButton SHOULDER_RIGHT = new GamepadButton(); //R1 - public final GamepadButton SHOULDER_LEFT = new GamepadButton(); //L1 + public GamepadButton TRIGGER_RIGHT; //R2 + public GamepadButton TRIGGER_LEFT; //L2 - public int[] DIRECTION_FORWARD; - public int[] DIRECTION_BACKWARD; - public int[] DIRECTION_RIGHT; - public int[] DIRECTION_LEFT; + public GamepadButton SHOULDER_RIGHT; //R1 + public GamepadButton SHOULDER_LEFT; //L1 + + public GamepadEmulatedButton DIRECTION_FORWARD; + public GamepadEmulatedButton DIRECTION_BACKWARD; + public GamepadEmulatedButton DIRECTION_RIGHT; + public GamepadEmulatedButton DIRECTION_LEFT; - public final GamepadButton THUMBSTICK_RIGHT = new GamepadButton(); //R3 - public final GamepadButton THUMBSTICK_LEFT = new GamepadButton(); //L3 + public GamepadButton THUMBSTICK_RIGHT; //R3 + public GamepadButton THUMBSTICK_LEFT; //L3 - public final GamepadButton DPAD_UP = new GamepadButton(); - public final GamepadButton DPAD_RIGHT = new GamepadButton(); - public final GamepadButton DPAD_DOWN = new GamepadButton(); - public final GamepadButton DPAD_LEFT = new GamepadButton(); - + public GamepadButton DPAD_UP; + public GamepadButton DPAD_RIGHT; + public GamepadButton DPAD_DOWN; + public GamepadButton DPAD_LEFT; + + public GamepadMap() { + BUTTON_A = new GamepadButton(); + BUTTON_B = new GamepadButton(); + BUTTON_X = new GamepadButton(); + BUTTON_Y = new GamepadButton(); + BUTTON_START = new GamepadButton(); + BUTTON_SELECT = new GamepadButton(); + TRIGGER_RIGHT = new GamepadButton(); + TRIGGER_LEFT = new GamepadButton(); + SHOULDER_RIGHT = new GamepadButton(); + SHOULDER_LEFT = new GamepadButton(); + DIRECTION_FORWARD = new GamepadEmulatedButton(); + DIRECTION_BACKWARD = new GamepadEmulatedButton(); + DIRECTION_RIGHT = new GamepadEmulatedButton(); + DIRECTION_LEFT = new GamepadEmulatedButton(); + THUMBSTICK_RIGHT = new GamepadButton(); + THUMBSTICK_LEFT = new GamepadButton(); + DPAD_UP = new GamepadButton(); + DPAD_RIGHT = new GamepadButton(); + DPAD_DOWN = new GamepadButton(); + DPAD_LEFT = new GamepadButton(); + } /* * Sets all buttons to a not pressed state, sending an input if needed @@ -74,35 +104,35 @@ public void resetPressedState(){ * Returns a pre-done mapping used when the mouse is grabbed by the game. */ public static GamepadMap getDefaultGameMap(){ - GamepadMap gameMap = new GamepadMap(); + GamepadMap gameMap = GamepadMap.createEmptyMap(); - gameMap.BUTTON_A.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_SPACE}; - gameMap.BUTTON_B.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_Q}; - gameMap.BUTTON_X.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_E}; - gameMap.BUTTON_Y.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_F}; + gameMap.BUTTON_A.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_SPACE; + gameMap.BUTTON_B.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_Q; + gameMap.BUTTON_X.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_E; + gameMap.BUTTON_Y.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_F; - gameMap.DIRECTION_FORWARD = new int[]{LwjglGlfwKeycode.GLFW_KEY_W}; - gameMap.DIRECTION_BACKWARD = new int[]{LwjglGlfwKeycode.GLFW_KEY_S}; - gameMap.DIRECTION_RIGHT = new int[]{LwjglGlfwKeycode.GLFW_KEY_D}; - gameMap.DIRECTION_LEFT = new int[]{LwjglGlfwKeycode.GLFW_KEY_A}; + gameMap.DIRECTION_FORWARD.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_W; + gameMap.DIRECTION_BACKWARD.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_S; + gameMap.DIRECTION_RIGHT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_D; + gameMap.DIRECTION_LEFT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_A; - gameMap.DPAD_UP.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT}; - gameMap.DPAD_DOWN.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_O}; //For mods ? - gameMap.DPAD_RIGHT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_K}; //For mods ? - gameMap.DPAD_LEFT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_J}; //For mods ? + gameMap.DPAD_UP.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT; + gameMap.DPAD_DOWN.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_O; //For mods ? + gameMap.DPAD_RIGHT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_K; //For mods ? + gameMap.DPAD_LEFT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_J; //For mods ? - gameMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP}; - gameMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN}; + gameMap.SHOULDER_LEFT.keycodes[0] = GamepadMap.MOUSE_SCROLL_UP; + gameMap.SHOULDER_RIGHT.keycodes[0] = GamepadMap.MOUSE_SCROLL_DOWN; - gameMap.TRIGGER_LEFT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT}; - gameMap.TRIGGER_RIGHT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT}; + gameMap.TRIGGER_LEFT.keycodes[0] = GamepadMap.MOUSE_RIGHT; + gameMap.TRIGGER_RIGHT.keycodes[0] = GamepadMap.MOUSE_LEFT; - gameMap.THUMBSTICK_LEFT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL}; - gameMap.THUMBSTICK_RIGHT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT}; + gameMap.THUMBSTICK_LEFT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL; + gameMap.THUMBSTICK_RIGHT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT; gameMap.THUMBSTICK_RIGHT.isToggleable = true; - gameMap.BUTTON_START.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_ESCAPE}; - gameMap.BUTTON_SELECT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_TAB}; + gameMap.BUTTON_START.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_ESCAPE; + gameMap.BUTTON_SELECT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_TAB; return gameMap; } @@ -111,64 +141,63 @@ public static GamepadMap getDefaultGameMap(){ * Returns a pre-done mapping used when the mouse is NOT grabbed by the game. */ public static GamepadMap getDefaultMenuMap(){ - GamepadMap menuMap = new GamepadMap(); - - menuMap.BUTTON_A.keycodes = new int[]{LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT}; - menuMap.BUTTON_B.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_ESCAPE}; - menuMap.BUTTON_X.keycodes = new int[]{LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT}; - menuMap.BUTTON_Y.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT, LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT}; //Oops, doesn't work since left shift isn't properly applied. - - menuMap.DIRECTION_FORWARD = new int[]{GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP}; - menuMap.DIRECTION_BACKWARD = new int[]{GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN}; - menuMap.DIRECTION_RIGHT = new int[]{}; - menuMap.DIRECTION_LEFT = new int[]{}; - - menuMap.DPAD_UP.keycodes = new int[]{}; - menuMap.DPAD_DOWN.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_O}; //For mods ? - menuMap.DPAD_RIGHT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_K}; //For mods ? - menuMap.DPAD_LEFT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_J}; //For mods ? - - menuMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP}; - menuMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN}; - - menuMap.TRIGGER_LEFT.keycodes = new int[]{}; - menuMap.TRIGGER_RIGHT.keycodes = new int[]{}; - - menuMap.THUMBSTICK_LEFT.keycodes = new int[]{}; - menuMap.THUMBSTICK_RIGHT.keycodes = new int[]{}; - - menuMap.BUTTON_START.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_ESCAPE}; - menuMap.BUTTON_SELECT.keycodes = new int[]{}; + GamepadMap menuMap = GamepadMap.createEmptyMap(); + + menuMap.BUTTON_A.keycodes[0] = GamepadMap.MOUSE_LEFT; + menuMap.BUTTON_B.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_ESCAPE; + menuMap.BUTTON_X.keycodes[0] = GamepadMap.MOUSE_RIGHT; + { + short[] keycodes = menuMap.BUTTON_Y.keycodes; + keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT; + keycodes[1] = GamepadMap.MOUSE_RIGHT; + } + + { + short[] keycodes = menuMap.DIRECTION_FORWARD.keycodes; + keycodes[0] = keycodes[1] = keycodes[2] = keycodes[3] = GamepadMap.MOUSE_SCROLL_UP; + } + { + short[] keycodes = menuMap.DIRECTION_BACKWARD.keycodes; + keycodes[0] = keycodes[1] = keycodes[2] = keycodes[3] = GamepadMap.MOUSE_SCROLL_DOWN; + } + + menuMap.DPAD_DOWN.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_O; //For mods ? + menuMap.DPAD_RIGHT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_K; //For mods ? + menuMap.DPAD_LEFT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_J; //For mods ? + + menuMap.SHOULDER_LEFT.keycodes[0] = GamepadMap.MOUSE_SCROLL_UP; + menuMap.SHOULDER_RIGHT.keycodes[0] = GamepadMap.MOUSE_SCROLL_DOWN; + + menuMap.BUTTON_SELECT.keycodes[0] = LwjglGlfwKeycode.GLFW_KEY_ESCAPE; return menuMap; } /* - * Returns all GamepadButtons, does not include directional keys + * Returns all GamepadEmulatedButtons of the controller key map. */ - public GamepadButton[] getButtons(){ - return new GamepadButton[]{ BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, + public GamepadEmulatedButton[] getButtons(){ + return new GamepadEmulatedButton[]{ BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, BUTTON_SELECT, BUTTON_START, TRIGGER_LEFT, TRIGGER_RIGHT, SHOULDER_LEFT, SHOULDER_RIGHT, THUMBSTICK_LEFT, THUMBSTICK_RIGHT, - DPAD_UP, DPAD_RIGHT, DPAD_DOWN, DPAD_LEFT}; + DPAD_UP, DPAD_RIGHT, DPAD_DOWN, DPAD_LEFT, + DIRECTION_FORWARD, DIRECTION_BACKWARD, + DIRECTION_LEFT, DIRECTION_RIGHT}; } /* * Returns an pre-initialized GamepadMap with only empty keycodes */ - @SuppressWarnings("unused") public static GamepadMap getEmptyMap(){ + @SuppressWarnings("unused") public static GamepadMap createEmptyMap(){ GamepadMap emptyMap = new GamepadMap(); - for(GamepadButton button : emptyMap.getButtons()) - button.keycodes = new int[]{}; - - emptyMap.DIRECTION_LEFT = new int[]{}; - emptyMap.DIRECTION_FORWARD = new int[]{}; - emptyMap.DIRECTION_RIGHT = new int[]{}; - emptyMap.DIRECTION_BACKWARD = new int[]{}; - + for(GamepadEmulatedButton button : emptyMap.getButtons()) + button.keycodes = new short[] {UNSPECIFIED, UNSPECIFIED, UNSPECIFIED, UNSPECIFIED}; return emptyMap; } + public static String[] getSpecialKeycodeNames() { + return new String[] {"UNSPECIFIED", "MOUSE_RIGHT", "MOUSE_MIDDLE", "MOUSE_LEFT", "SCROLL_UP", "SCROLL_DOWN"}; + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMapStore.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMapStore.java new file mode 100644 index 0000000000..85af2404cc --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMapStore.java @@ -0,0 +1,60 @@ +package net.kdt.pojavlaunch.customcontrols.gamepad; + +import android.util.Log; + +import com.google.gson.JsonParseException; + +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.utils.FileUtils; + +import java.io.File; +import java.io.IOException; + +public class GamepadMapStore { + private static final File STORE_FILE = new File(Tools.DIR_DATA, "gamepad_map.json"); + private static GamepadMapStore sMapStore; + private GamepadMap mInMenuMap; + private GamepadMap mInGameMap; + private static GamepadMapStore createDefault() { + GamepadMapStore mapStore = new GamepadMapStore(); + mapStore.mInGameMap = GamepadMap.getDefaultGameMap(); + mapStore.mInMenuMap = GamepadMap.getDefaultMenuMap(); + return mapStore; + } + + private static void loadIfNecessary() { + if(sMapStore == null) return; + load(); + } + + public static void load() { + GamepadMapStore mapStore = null; + if(STORE_FILE.exists() && STORE_FILE.canRead()) { + try { + String storeFileContent = Tools.read(STORE_FILE); + mapStore = Tools.GLOBAL_GSON.fromJson(storeFileContent, GamepadMapStore.class); + } catch (JsonParseException | IOException e) { + Log.w("GamepadMapStore", "Map store failed to load!", e); + } + } + if(mapStore == null) mapStore = createDefault(); + sMapStore = mapStore; + } + + public static void save() throws IOException { + if(sMapStore == null) throw new RuntimeException("Must load map store first!"); + FileUtils.ensureParentDirectory(STORE_FILE); + String jsonData = Tools.GLOBAL_GSON.toJson(sMapStore); + Tools.write(STORE_FILE.getAbsolutePath(), jsonData); + } + + public static GamepadMap getGameMap() { + loadIfNecessary(); + return sMapStore.mInGameMap; + } + + public static GamepadMap getMenuMap() { + loadIfNecessary(); + return sMapStore.mInMenuMap; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMapperAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMapperAdapter.java new file mode 100644 index 0000000000..50ee1b5378 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMapperAdapter.java @@ -0,0 +1,314 @@ +package net.kdt.pojavlaunch.customcontrols.gamepad; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.Spinner; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import net.kdt.pojavlaunch.EfficientAndroidLWJGLKeycode; +import net.kdt.pojavlaunch.GrabListener; +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.customcontrols.EditorExitable; + +import android.os.Handler; +import android.widget.TextView; + +public class GamepadMapperAdapter extends RecyclerView.Adapter implements GamepadDataProvider { + private static final int BUTTON_COUNT = 20; + + private GamepadMap mSimulatedGamepadMap; + private RebinderButton[] mRebinderButtons; + private GamepadEmulatedButton[] mRealButtons; + private final Handler mExitHandler = new Handler(Looper.getMainLooper()); + private final Runnable mExitRunnable; + private final ArrayAdapter mKeyAdapter; + private final int mSpecialKeycodeCount; + private GrabListener mGamepadGrabListener; + private boolean mGrabState = false; + private boolean mOldState = false; + + public GamepadMapperAdapter(Context context, EditorExitable exitable) { + GamepadMapStore.load(); + mKeyAdapter = new ArrayAdapter<>(context, R.layout.item_centered_textview); + String[] specialKeycodeNames = GamepadMap.getSpecialKeycodeNames(); + mSpecialKeycodeCount = specialKeycodeNames.length; + mKeyAdapter.addAll(specialKeycodeNames); + mKeyAdapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName()); + this.mExitRunnable = exitable::exitEditor; + createRebinderMap(); + updateRealButtons(); + } + + private void createRebinderMap() { + mRebinderButtons = new RebinderButton[BUTTON_COUNT]; + mRealButtons = new GamepadEmulatedButton[BUTTON_COUNT]; + mSimulatedGamepadMap = new GamepadMap(); + int index = 0; + mSimulatedGamepadMap.BUTTON_A = mRebinderButtons[index++] = new RebinderButton(R.drawable.button_a, R.string.controller_button_a); + mSimulatedGamepadMap.BUTTON_B = mRebinderButtons[index++] = new RebinderButton(R.drawable.button_b, R.string.controller_button_b); + mSimulatedGamepadMap.BUTTON_X = mRebinderButtons[index++] = new RebinderButton(R.drawable.button_x, R.string.controller_button_x); + mSimulatedGamepadMap.BUTTON_Y = mRebinderButtons[index++] = new RebinderButton(R.drawable.button_y, R.string.controller_button_y); + mSimulatedGamepadMap.BUTTON_START = mRebinderButtons[index++] = new RebinderButton(R.drawable.button_start, R.string.controller_button_start); + mSimulatedGamepadMap.BUTTON_SELECT = mRebinderButtons[index++] = new RebinderButton(R.drawable.button_select, R.string.controller_button_select); + mSimulatedGamepadMap.TRIGGER_RIGHT = mRebinderButtons[index++] = new RebinderButton(R.drawable.trigger_right, R.string.controller_button_trigger_right); + mSimulatedGamepadMap.TRIGGER_LEFT = mRebinderButtons[index++] = new RebinderButton(R.drawable.trigger_left, R.string.controller_button_trigger_left); + mSimulatedGamepadMap.SHOULDER_RIGHT = mRebinderButtons[index++] = new RebinderButton(R.drawable.shoulder_right, R.string.controller_button_shoulder_right); + mSimulatedGamepadMap.SHOULDER_LEFT = mRebinderButtons[index++] = new RebinderButton(R.drawable.shoulder_left, R.string.controller_button_shoulder_left); + mSimulatedGamepadMap.DIRECTION_FORWARD = mRebinderButtons[index++] = new RebinderButton(R.drawable.stick_left, R.string.controller_direction_forward); + mSimulatedGamepadMap.DIRECTION_RIGHT = mRebinderButtons[index++] = new RebinderButton(R.drawable.stick_left, R.string.controller_direction_right); + mSimulatedGamepadMap.DIRECTION_LEFT = mRebinderButtons[index++] = new RebinderButton(R.drawable.stick_left, R.string.controller_direction_left); + mSimulatedGamepadMap.DIRECTION_BACKWARD = mRebinderButtons[index++] = new RebinderButton(R.drawable.stick_left, R.string.controller_direction_backward); + mSimulatedGamepadMap.THUMBSTICK_RIGHT = mRebinderButtons[index++] = new RebinderButton(R.drawable.stick_right_click, R.string.controller_stick_press_r); + mSimulatedGamepadMap.THUMBSTICK_LEFT = mRebinderButtons[index++] = new RebinderButton(R.drawable.stick_left_click, R.string.controller_stick_press_l); + mSimulatedGamepadMap.DPAD_UP = mRebinderButtons[index++] = new RebinderButton(R.drawable.dpad_up, R.string.controller_dpad_up); + mSimulatedGamepadMap.DPAD_DOWN = mRebinderButtons[index++] = new RebinderButton(R.drawable.dpad_down, R.string.controller_dpad_down); + mSimulatedGamepadMap.DPAD_RIGHT = mRebinderButtons[index++] = new RebinderButton(R.drawable.dpad_right, R.string.controller_dpad_right); + mSimulatedGamepadMap.DPAD_LEFT = mRebinderButtons[index] = new RebinderButton(R.drawable.dpad_left, R.string.controller_dpad_left); + } + + private void updateRealButtons() { + GamepadMap currentRealMap = mGrabState ? GamepadMapStore.getGameMap() : GamepadMapStore.getMenuMap(); + int index = 0; + mRealButtons[index++] = currentRealMap.BUTTON_A; + mRealButtons[index++] = currentRealMap.BUTTON_B; + mRealButtons[index++] = currentRealMap.BUTTON_X; + mRealButtons[index++] = currentRealMap.BUTTON_Y; + mRealButtons[index++] = currentRealMap.BUTTON_START; + mRealButtons[index++] = currentRealMap.BUTTON_SELECT; + mRealButtons[index++] = currentRealMap.TRIGGER_RIGHT; + mRealButtons[index++] = currentRealMap.TRIGGER_LEFT; + mRealButtons[index++] = currentRealMap.SHOULDER_RIGHT; + mRealButtons[index++] = currentRealMap.SHOULDER_LEFT; + mRealButtons[index++] = currentRealMap.DIRECTION_FORWARD; + mRealButtons[index++] = currentRealMap.DIRECTION_RIGHT; + mRealButtons[index++] = currentRealMap.DIRECTION_LEFT; + mRealButtons[index++] = currentRealMap.DIRECTION_BACKWARD; + mRealButtons[index++] = currentRealMap.THUMBSTICK_RIGHT; + mRealButtons[index++] = currentRealMap.THUMBSTICK_LEFT; + mRealButtons[index++] = currentRealMap.DPAD_UP; + mRealButtons[index++] = currentRealMap.DPAD_DOWN; + mRealButtons[index++] = currentRealMap.DPAD_RIGHT; + mRealButtons[index] = currentRealMap.DPAD_LEFT; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view = layoutInflater.inflate(R.layout.item_controller_mapping, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.attach(position); + } + + @Override + public void onViewRecycled(@NonNull ViewHolder holder) { + super.onViewRecycled(holder); + holder.detach(); + } + + @Override + public int getItemCount() { + return mRebinderButtons.length; + } + + + private class RebinderButton extends GamepadButton { + public final int iconResourceId; + public final int localeResourceId; + private GamepadMapperAdapter.ViewHolder mButtonHolder; + + public RebinderButton(int iconResourceId, int localeResourceId) { + this.iconResourceId = iconResourceId; + this.localeResourceId = localeResourceId; + } + + public void changeViewHolder(GamepadMapperAdapter.ViewHolder viewHolder) { + mButtonHolder = viewHolder; + if(mButtonHolder != null) mButtonHolder.setPressed(mIsDown); + } + + @Override + protected void onDownStateChanged(boolean isDown) { + if(iconResourceId == R.drawable.button_select) { + if(isDown) mExitHandler.postDelayed(mExitRunnable, 400); + else mExitHandler.removeCallbacks(mExitRunnable); + } + if(mButtonHolder == null) return; + mButtonHolder.setPressed(isDown); + } + } + + public class ViewHolder extends RecyclerView.ViewHolder implements AdapterView.OnItemSelectedListener, View.OnClickListener { + private final Context mContext; + private final ImageView mButtonIcon; + private final ImageView mExpansionIndicator; + private final Spinner[] mKeySpinners; + private final View mClickIndicator; + private final View mExpandedView; + private final TextView mKeycodeLabel; + private int mAttachedPosition = -1; + private short[] mKeycodes; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + mContext = itemView.getContext(); + mButtonIcon = itemView.findViewById(R.id.controller_mapper_button); + mClickIndicator = itemView.findViewById(R.id.controller_mapper_indicator); + mExpandedView = itemView.findViewById(R.id.controller_mapper_expanded_view); + mExpansionIndicator = itemView.findViewById(R.id.controller_mapper_expand_button); + mKeycodeLabel = itemView.findViewById(R.id.controller_mapper_keycode_label); + View defaultView = itemView.findViewById(R.id.controller_mapper_default_view); + defaultView.setOnClickListener(this); + mKeySpinners = new Spinner[4]; + mKeySpinners[0] = itemView.findViewById(R.id.controller_mapper_key_spinner1); + mKeySpinners[1] = itemView.findViewById(R.id.controller_mapper_key_spinner2); + mKeySpinners[2] = itemView.findViewById(R.id.controller_mapper_key_spinner3); + mKeySpinners[3] = itemView.findViewById(R.id.controller_mapper_key_spinner4); + for(Spinner spinner : mKeySpinners) { + spinner.setAdapter(mKeyAdapter); + spinner.setOnItemSelectedListener(this); + } + } + private void attach(int index) { + RebinderButton rebinderButton = mRebinderButtons[index]; + mExpandedView.setVisibility(View.GONE); + mButtonIcon.setImageResource(rebinderButton.iconResourceId); + String buttonName = mContext.getString(rebinderButton.localeResourceId); + mButtonIcon.setContentDescription(buttonName); + rebinderButton.changeViewHolder(this); + + GamepadEmulatedButton realButton = mRealButtons[index]; + + mKeycodes = realButton.keycodes; + + int spinnerIndex; + + // Populate spinners with known keycodes until we run out of keycodes + for(spinnerIndex = 0; spinnerIndex < mKeycodes.length; spinnerIndex++) { + Spinner keySpinner = mKeySpinners[spinnerIndex]; + keySpinner.setEnabled(true); + short keyCode = mKeycodes[spinnerIndex]; + int selected; + if(keyCode < 0) selected = keyCode + mSpecialKeycodeCount; + else selected = EfficientAndroidLWJGLKeycode.getIndexByValue(keyCode) + mSpecialKeycodeCount; + keySpinner.setSelection(selected); + } + // In case if there is too much spinners, disable the rest of them + for(;spinnerIndex < mKeySpinners.length; spinnerIndex++) { + mKeySpinners[spinnerIndex].setEnabled(false); + } + updateKeycodeLabel(); + + mAttachedPosition = index; + } + private void detach() { + mRebinderButtons[mAttachedPosition].changeViewHolder(null); + mAttachedPosition = -1; + } + private void setPressed(boolean pressed) { + mClickIndicator.setBackgroundColor(pressed ? Color.GREEN : Color.DKGRAY); + } + + private void computeKeycodePosition(short keyCode) { + + } + + private void updateKeycodeLabel() { + StringBuilder labelBuilder = new StringBuilder(); + boolean first = true; + int unspecifiedPosition = GamepadMap.UNSPECIFIED + mSpecialKeycodeCount; + for (Spinner keySpinner : mKeySpinners) { + if (keySpinner.getSelectedItemPosition() == unspecifiedPosition) continue; + if (!first) labelBuilder.append(" + "); + else first = false; + labelBuilder.append(keySpinner.getSelectedItem().toString()); + } + if(labelBuilder.length() == 0) labelBuilder.append(mKeyAdapter.getItem(unspecifiedPosition)); + mKeycodeLabel.setText(labelBuilder.toString()); + } + + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + /*if(mAttachedPosition == -1) return; + int[] keycodes = mRealButtons[mAttachedPosition].keycodes; + if(keycodes.length < 1) return; + int selectedKeycode; + if(i < mSpecialKeycodeCount) { + selectedKeycode = i - mSpecialKeycodeCount; + } else { + selectedKeycode = EfficientAndroidLWJGLKeycode.getValueByIndex(i - mSpecialKeycodeCount); + } + keycodes[0] = selectedKeycode;*/ + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + + @Override + public void onClick(View view) { + int visibility = mExpandedView.getVisibility(); + switch (visibility) { + case View.INVISIBLE: + case View.GONE: + mExpansionIndicator.setRotation(0); + mExpandedView.setVisibility(View.VISIBLE); + break; + case View.VISIBLE: + mExpansionIndicator.setRotation(180); + mExpandedView.setVisibility(View.GONE); + } + } + } + + @Override + public GamepadMap getMenuMap() { + return mSimulatedGamepadMap; + } + + @Override + public GamepadMap getGameMap() { + return mSimulatedGamepadMap; + } + + @Override + public boolean isGrabbing() { + return mGrabState; + } + + @Override + public void attachGrabListener(GrabListener grabListener) { + mGamepadGrabListener = grabListener; + grabListener.onGrabState(mGrabState); + } + + @Override + public void reloadGamepadMaps() { + } + + // Cannot do it another way + @SuppressLint("NotifyDataSetChanged") + public void setGrabState(boolean newState) { + mGrabState = newState; + if(mGamepadGrabListener != null) mGamepadGrabListener.onGrabState(newState); + if(mGrabState == mOldState) return; + updateRealButtons(); + notifyDataSetChanged(); + mOldState = mGrabState; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/GamepadMapperFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/GamepadMapperFragment.java new file mode 100644 index 0000000000..5d0db6015a --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/GamepadMapperFragment.java @@ -0,0 +1,112 @@ +package net.kdt.pojavlaunch.fragments; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Spinner; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.customcontrols.EditorExitable; +import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad; +import net.kdt.pojavlaunch.customcontrols.gamepad.GamepadMapperAdapter; + +import fr.spse.gamepad_remapper.GamepadHandler; +import fr.spse.gamepad_remapper.Remapper; +import fr.spse.gamepad_remapper.RemapperManager; +import fr.spse.gamepad_remapper.RemapperView; + +public class GamepadMapperFragment extends Fragment implements View.OnKeyListener, View.OnGenericMotionListener, EditorExitable, AdapterView + + .OnItemSelectedListener { + public static final String TAG = "GamepadMapperFragment"; + private final RemapperView.Builder mRemapperViewBuilder = new RemapperView.Builder(null) + .remapA(true) + .remapB(true) + .remapX(true) + .remapY(true) + .remapLeftJoystick(true) + .remapRightJoystick(true) + .remapStart(true) + .remapSelect(true) + .remapLeftShoulder(true) + .remapRightShoulder(true) + .remapLeftTrigger(true) + .remapRightTrigger(true); + private RemapperManager mInputManager; + private RecyclerView mButtonRecyclerView; + private Spinner mGrabStateSpinner; + private GamepadMapperAdapter mMapperAdapter; + private Gamepad mGamepad; + public GamepadMapperFragment() { + super(R.layout.fragment_controller_remapper); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mButtonRecyclerView = view.findViewById(R.id.gamepad_remapper_recycler); + mMapperAdapter = new GamepadMapperAdapter(view.getContext(), this); + mButtonRecyclerView.setLayoutManager(new LinearLayoutManager(view.getContext())); + mButtonRecyclerView.setAdapter(mMapperAdapter); + mButtonRecyclerView.setOnKeyListener(this); + mButtonRecyclerView.setOnGenericMotionListener(this); + mButtonRecyclerView.requestFocus(); + mInputManager = new RemapperManager(view.getContext(), mRemapperViewBuilder); + mGrabStateSpinner = view.findViewById(R.id.gamepad_remapper_mode_spinner); + ArrayAdapter mGrabStateAdapter = new ArrayAdapter<>(view.getContext(), R.layout.item_centered_textview); + mGrabStateAdapter.addAll("INMENU", "INGAME"); + mGrabStateSpinner.setAdapter(mGrabStateAdapter); + mGrabStateSpinner.setSelection(0); + mGrabStateSpinner.setOnItemSelectedListener(this); + } + + + @Override + public boolean onKey(View view, int i, KeyEvent keyEvent) { + Log.i("onKey", keyEvent.toString()); + View mainView = getView(); + if(!Gamepad.isGamepadEvent(keyEvent) || mainView == null) return false; + if(mGamepad == null) mGamepad = new Gamepad(mainView, keyEvent.getDevice(), mMapperAdapter); + mInputManager.handleKeyEventInput(mainView.getContext(), keyEvent, mGamepad); + return true; + } + + @Override + public boolean onGenericMotion(View view, MotionEvent motionEvent) { + Log.i("onGenericMotion", motionEvent.toString()); + View mainView = getView(); + if(!Gamepad.isGamepadEvent(motionEvent) || mainView == null) return false; + if(mGamepad == null) mGamepad = new Gamepad(mainView, motionEvent.getDevice(), mMapperAdapter); + mInputManager.handleMotionEventInput(mainView.getContext(), motionEvent, mGamepad); + return true; + } + + @Override + public void exitEditor() { + Activity activity = getActivity(); + if(activity == null) return; + activity.onBackPressed(); + } + + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + boolean grab = i == 1; + mMapperAdapter.setGrabState(grab); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java index beea56e1d8..3c827ec715 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java @@ -56,7 +56,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat mShareLogsButton.setOnClickListener((v) -> shareLog(requireContext())); mNewsButton.setOnLongClickListener((v)->{ - Tools.swapFragment(requireActivity(), SearchModFragment.class, SearchModFragment.TAG, null); + Tools.swapFragment(requireActivity(), GamepadMapperFragment.class, GamepadMapperFragment.TAG, null); return true; }); } diff --git a/app_pojavlauncher/src/main/res/layout/fragment_controller_remapper.xml b/app_pojavlauncher/src/main/res/layout/fragment_controller_remapper.xml new file mode 100644 index 0000000000..66ad2d5f56 --- /dev/null +++ b/app_pojavlauncher/src/main/res/layout/fragment_controller_remapper.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app_pojavlauncher/src/main/res/layout/item_controller_mapping.xml b/app_pojavlauncher/src/main/res/layout/item_controller_mapping.xml new file mode 100644 index 0000000000..53cee4265b --- /dev/null +++ b/app_pojavlauncher/src/main/res/layout/item_controller_mapping.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml index 4a730f2138..2e3be3c4bf 100644 --- a/app_pojavlauncher/src/main/res/values/strings.xml +++ b/app_pojavlauncher/src/main/res/values/strings.xml @@ -384,4 +384,25 @@ Log output: Failed to read the .jar file Execute .jar feature is not compatible with Java %d + A + B + X + Y + Start + Select + Left Trigger + Right Trigger + Left Shoulder + Right Shoulder + Walk Forward + Walk Backward + Walk Left + Walk Right + Left Thumbstick (click) + Right Thumbstick (click) + D-Pad Up + D-Pad Down + D-Pad Left + D-Pad Right + Hold "Select" to exit