diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 5bb384d26b..657b410505 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1 @@
-open_collective: pojavlauncher
patreon: pojavlauncher
diff --git a/app_pojavlauncher/build.gradle b/app_pojavlauncher/build.gradle
index d7104fca8d..d19a53d646 100644
--- a/app_pojavlauncher/build.gradle
+++ b/app_pojavlauncher/build.gradle
@@ -90,7 +90,7 @@ configurations {
android {
namespace 'net.kdt.pojavlaunch'
- compileSdk = 33
+ compileSdk = 34
lintOptions {
abortOnError false
@@ -114,7 +114,7 @@ android {
defaultConfig {
applicationId "net.kdt.pojavlaunch"
minSdkVersion 21
- targetSdkVersion 33
+ targetSdkVersion 34
versionCode getDateSeconds()
versionName getVersionName()
multiDexEnabled true //important
diff --git a/app_pojavlauncher/src/main/AndroidManifest.xml b/app_pojavlauncher/src/main/AndroidManifest.xml
index 4147e1f3a7..98628756a7 100644
--- a/app_pojavlauncher/src/main/AndroidManifest.xml
+++ b/app_pojavlauncher/src/main/AndroidManifest.xml
@@ -18,6 +18,8 @@
+
+
-
-
+
+
+
+
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/EfficientAndroidLWJGLKeycode.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/EfficientAndroidLWJGLKeycode.java
index c2ff62248f..5fce0dcbc4 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/EfficientAndroidLWJGLKeycode.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/EfficientAndroidLWJGLKeycode.java
@@ -198,7 +198,7 @@ public static void execKeyIndex(int index){
sendKeyPress(getValueByIndex(index));
}
- public static int getValueByIndex(int index) {
+ public static short getValueByIndex(int index) {
return sLwjglKeycodes[index];
}
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..3c4a26f0ee 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;
@@ -202,6 +203,10 @@ public boolean onTouchEvent(MotionEvent e) {
return mCurrentTouchProcessor.processTouchEvent(e);
}
+ private void createGamepad(View contextView, InputDevice inputDevice) {
+ mGamepad = new Gamepad(contextView, inputDevice, DefaultDataProvider.INSTANCE, true);
+ }
+
/**
* The event for mouse/joystick movements
*/
@@ -211,9 +216,7 @@ public boolean dispatchGenericMotionEvent(MotionEvent event) {
int mouseCursorIndex = -1;
if(Gamepad.isGamepadEvent(event)){
- if(mGamepad == null){
- mGamepad = new Gamepad(this, event.getDevice());
- }
+ if(mGamepad == null) createGamepad(this, event.getDevice());
mInputManager.handleMotionEventInput(getContext(), event, mGamepad);
return true;
@@ -285,9 +288,7 @@ public boolean processKeyEvent(KeyEvent event) {
}
if(Gamepad.isGamepadEvent(event)){
- if(mGamepad == null){
- mGamepad = new Gamepad(this, event.getDevice());
- }
+ if(mGamepad == null) createGamepad(this, event.getDevice());
mInputManager.handleKeyEventInput(getContext(), event, mGamepad);
return true;
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..f16c97115e
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/DefaultDataProvider.java
@@ -0,0 +1,33 @@
+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();
+
+ // Cannot instantiate this class publicly
+ private DefaultDataProvider() {}
+
+ @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);
+ }
+}
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..4d3a7b135a 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
@@ -19,7 +19,6 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.Toast;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.math.MathUtils;
@@ -27,7 +26,6 @@
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 +73,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 +88,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, boolean showCursor){
Settings.setDeadzoneScale(PREF_DEADZONE_SCALE);
mScreenChoreographer = Choreographer.getInstance();
@@ -120,16 +119,34 @@ 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);
+
+ if(showCursor) {
+ ((ViewGroup)contextView.getParent()).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();
+ GamepadMapStore.load();
+ 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 +161,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 +170,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 +281,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..37dcbaab5d
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadDataProvider.java
@@ -0,0 +1,10 @@
+package net.kdt.pojavlaunch.customcontrols.gamepad;
+
+import net.kdt.pojavlaunch.GrabListener;
+
+public interface GamepadDataProvider {
+ GamepadMap getMenuMap();
+ GamepadMap getGameMap();
+ boolean isGrabbing();
+ void attachGrabListener(GrabListener grabListener);
+}
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..c18c5dc38e 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,11 @@ 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 final GamepadButton BUTTON_START = new GamepadButton();
- public final GamepadButton BUTTON_SELECT = new GamepadButton();
-
- 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 int[] DIRECTION_FORWARD;
- public int[] DIRECTION_BACKWARD;
- public int[] DIRECTION_RIGHT;
- public int[] DIRECTION_LEFT;
-
- public final GamepadButton THUMBSTICK_RIGHT = new GamepadButton(); //R3
- public final GamepadButton THUMBSTICK_LEFT = new GamepadButton(); //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 BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, BUTTON_START, BUTTON_SELECT,
+ TRIGGER_RIGHT, TRIGGER_LEFT, SHOULDER_RIGHT, SHOULDER_LEFT, THUMBSTICK_RIGHT,
+ THUMBSTICK_LEFT, DPAD_UP, DPAD_DOWN, DPAD_RIGHT, DPAD_LEFT;
+ public GamepadEmulatedButton DIRECTION_FORWARD, DIRECTION_BACKWARD, DIRECTION_RIGHT, DIRECTION_LEFT;
/*
* Sets all buttons to a not pressed state, sending an input if needed
@@ -70,39 +56,70 @@ public void resetPressedState(){
}
+ private static GamepadMap createAndInitializeButtons() {
+ GamepadMap gamepadMap = new GamepadMap();
+ gamepadMap.BUTTON_A = new GamepadButton();
+ gamepadMap.BUTTON_B = new GamepadButton();
+ gamepadMap.BUTTON_X = new GamepadButton();
+ gamepadMap.BUTTON_Y = new GamepadButton();
+
+ gamepadMap.BUTTON_START = new GamepadButton();
+ gamepadMap.BUTTON_SELECT = new GamepadButton();
+
+ gamepadMap.TRIGGER_RIGHT = new GamepadButton();
+ gamepadMap.TRIGGER_LEFT = new GamepadButton();
+
+ gamepadMap.SHOULDER_RIGHT = new GamepadButton();
+ gamepadMap.SHOULDER_LEFT = new GamepadButton();
+
+ gamepadMap.DIRECTION_FORWARD = new GamepadEmulatedButton();
+ gamepadMap.DIRECTION_BACKWARD = new GamepadEmulatedButton();
+ gamepadMap.DIRECTION_RIGHT = new GamepadEmulatedButton();
+ gamepadMap.DIRECTION_LEFT = new GamepadEmulatedButton();
+
+ gamepadMap.THUMBSTICK_RIGHT = new GamepadButton();
+ gamepadMap.THUMBSTICK_LEFT = new GamepadButton();
+
+ gamepadMap.DPAD_UP = new GamepadButton();
+ gamepadMap.DPAD_RIGHT = new GamepadButton();
+ gamepadMap.DPAD_DOWN = new GamepadButton();
+ gamepadMap.DPAD_LEFT = new GamepadButton();
+ return gamepadMap;
+ }
+
/*
* 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 +128,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(){
- 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[]{};
-
+ @SuppressWarnings("unused") public static GamepadMap createEmptyMap(){
+ GamepadMap emptyMap = createAndInitializeButtons();
+ 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..e590bd3a44
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMapperAdapter.java
@@ -0,0 +1,313 @@
+package net.kdt.pojavlaunch.customcontrols.gamepad;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Color;
+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.Tools;
+
+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 ArrayAdapter mKeyAdapter;
+ private final int mSpecialKeycodeCount;
+ private GrabListener mGamepadGrabListener;
+ private boolean mGrabState = false;
+ private boolean mOldState = false;
+
+ public GamepadMapperAdapter(Context context) {
+ GamepadMapStore.load();
+ mKeyAdapter = new ArrayAdapter<>(context, R.layout.item_centered_textview_large);
+ String[] specialKeycodeNames = GamepadMap.getSpecialKeycodeNames();
+ mSpecialKeycodeCount = specialKeycodeNames.length;
+ mKeyAdapter.addAll(specialKeycodeNames);
+ mKeyAdapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName());
+ 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_right, R.string.controller_direction_forward);
+ mSimulatedGamepadMap.DIRECTION_RIGHT = mRebinderButtons[index++] = new RebinderButton(R.drawable.stick_right, R.string.controller_direction_right);
+ mSimulatedGamepadMap.DIRECTION_LEFT = mRebinderButtons[index++] = new RebinderButton(R.drawable.stick_right, R.string.controller_direction_left);
+ mSimulatedGamepadMap.DIRECTION_BACKWARD = mRebinderButtons[index++] = new RebinderButton(R.drawable.stick_right, 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 void updateStickIcons() {
+ // Which stick is used for keyboard emulation depends on grab state, so we need
+ // to update the mapper UI icons accordingly
+ int stickIcon = mGrabState ? R.drawable.stick_left : R.drawable.stick_right;
+ ((RebinderButton)mSimulatedGamepadMap.DIRECTION_FORWARD).iconResourceId = stickIcon;
+ ((RebinderButton)mSimulatedGamepadMap.DIRECTION_BACKWARD).iconResourceId = stickIcon;
+ ((RebinderButton)mSimulatedGamepadMap.DIRECTION_RIGHT).iconResourceId = stickIcon;
+ ((RebinderButton)mSimulatedGamepadMap.DIRECTION_LEFT).iconResourceId = stickIcon;
+ }
+
+ private static class RebinderButton extends GamepadButton {
+ public 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(mButtonHolder == null) return;
+ mButtonHolder.setPressed(isDown);
+ }
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder implements AdapterView.OnItemSelectedListener, View.OnClickListener {
+ private static final int COLOR_ACTIVE_BUTTON = 0x2000FF00;
+ private final Context mContext;
+ private final ImageView mButtonIcon;
+ private final ImageView mExpansionIndicator;
+ private final Spinner[] mKeySpinners;
+ 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);
+ 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) {
+ itemView.setBackgroundColor(pressed ? COLOR_ACTIVE_BUTTON : Color.TRANSPARENT);
+ }
+
+ 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 selectionIndex, long selectionId) {
+ if(mAttachedPosition == -1) return;
+ int editedKeycodeIndex = -1;
+ for(int i = 0; i < mKeySpinners.length && i < mKeycodes.length; i++) {
+ if(!adapterView.equals(mKeySpinners[i])) continue;
+ editedKeycodeIndex = i;
+ break;
+ }
+ if(editedKeycodeIndex == -1) return;
+ int keycode_offset = selectionIndex - mSpecialKeycodeCount;
+ if(selectionIndex <= mSpecialKeycodeCount) mKeycodes[editedKeycodeIndex] = (short) (keycode_offset);
+ else mKeycodes[editedKeycodeIndex] = EfficientAndroidLWJGLKeycode.getValueByIndex(keycode_offset);
+ updateKeycodeLabel();
+ try {
+ GamepadMapStore.save();
+ }catch (Exception e) {
+ Tools.showError(adapterView.getContext(), e);
+ }
+ }
+
+ @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);
+ }
+
+ // 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();
+ updateStickIcons();
+ 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..4f60e46f52
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/GamepadMapperFragment.java
@@ -0,0 +1,121 @@
+package net.kdt.pojavlaunch.fragments;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.InputDevice;
+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.gamepad.Gamepad;
+import net.kdt.pojavlaunch.customcontrols.gamepad.GamepadMapperAdapter;
+
+import fr.spse.gamepad_remapper.RemapperManager;
+import fr.spse.gamepad_remapper.RemapperView;
+
+public class GamepadMapperFragment extends Fragment implements
+ View.OnKeyListener, View.OnGenericMotionListener, 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 final Handler mExitHandler = new Handler(Looper.getMainLooper());
+ private final Runnable mExitRunnable = () -> {
+ Activity activity = getActivity();
+ if(activity == null) return;
+ activity.onBackPressed();
+ };
+ private RemapperManager mInputManager;
+ 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);
+ RecyclerView buttonRecyclerView = view.findViewById(R.id.gamepad_remapper_recycler);
+ mMapperAdapter = new GamepadMapperAdapter(view.getContext());
+ buttonRecyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
+ buttonRecyclerView.setAdapter(mMapperAdapter);
+ buttonRecyclerView.setOnKeyListener(this);
+ buttonRecyclerView.setOnGenericMotionListener(this);
+ buttonRecyclerView.requestFocus();
+ mInputManager = new RemapperManager(view.getContext(), mRemapperViewBuilder);
+ Spinner grabStateSpinner = view.findViewById(R.id.gamepad_remapper_mode_spinner);
+ ArrayAdapter mGrabStateAdapter = new ArrayAdapter<>(view.getContext(), R.layout.support_simple_spinner_dropdown_item);
+ mGrabStateAdapter.addAll(getString(R.string.customctrl_visibility_in_menus), getString(R.string.customctrl_visibility_ingame));
+ grabStateSpinner.setAdapter(mGrabStateAdapter);
+ grabStateSpinner.setSelection(0);
+ grabStateSpinner.setOnItemSelectedListener(this);
+ }
+
+ private void createGamepad(View mainView, InputDevice inputDevice) {
+ mGamepad = new Gamepad(mainView, inputDevice, mMapperAdapter, false) {
+ @Override
+ public void handleGamepadInput(int keycode, float value) {
+ if(keycode == KeyEvent.KEYCODE_BUTTON_SELECT) {
+ handleExitButton(value > 0.5);
+ }
+ super.handleGamepadInput(keycode, value);
+ }
+ };
+ }
+
+ private void handleExitButton(boolean isPressed) {
+ if(isPressed) mExitHandler.postDelayed(mExitRunnable, 400);
+ else mExitHandler.removeCallbacks(mExitRunnable);
+ }
+
+ @Override
+ public boolean onKey(View view, int i, KeyEvent keyEvent) {
+ View mainView = getView();
+ if(!Gamepad.isGamepadEvent(keyEvent) || mainView == null) return false;
+ if(mGamepad == null) createGamepad(mainView, keyEvent.getDevice());
+ mInputManager.handleKeyEventInput(mainView.getContext(), keyEvent, mGamepad);
+ return true;
+ }
+
+ @Override
+ public boolean onGenericMotion(View view, MotionEvent motionEvent) {
+ View mainView = getView();
+ if(!Gamepad.isGamepadEvent(motionEvent) || mainView == null) return false;
+ if(mGamepad == null) createGamepad(mainView, motionEvent.getDevice());
+ mInputManager.handleMotionEventInput(mainView.getContext(), motionEvent, mGamepad);
+ return true;
+ }
+
+ @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/java/net/kdt/pojavlaunch/services/GameService.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/GameService.java
index d259490f09..43ab1c0879 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/GameService.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/GameService.java
@@ -1,8 +1,10 @@
package net.kdt.pojavlaunch.services;
+import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
+import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
@@ -43,7 +45,13 @@ public int onStartCommand(Intent intent, int flags, int startId) {
.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.notification_terminate), pendingKillIntent)
.setSmallIcon(R.drawable.notif_icon)
.setNotificationSilent();
- startForeground(NotificationUtils.NOTIFICATION_ID_GAME_SERVICE, notificationBuilder.build());
+
+ Notification notification = notificationBuilder.build();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ startForeground(NotificationUtils.NOTIFICATION_ID_GAME_SERVICE, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST);
+ } else {
+ startForeground(NotificationUtils.NOTIFICATION_ID_GAME_SERVICE, notification);
+ }
return START_NOT_STICKY; // non-sticky so android wont try restarting the game after the user uses the "Quit" button
}
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/ProgressService.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/ProgressService.java
index fa005f0161..4678c3fb13 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/ProgressService.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/ProgressService.java
@@ -1,10 +1,12 @@
package net.kdt.pojavlaunch.services;
import android.annotation.SuppressLint;
+import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
@@ -64,7 +66,12 @@ public int onStartCommand(Intent intent, int flags, int startId) {
}
Log.d("ProgressService", "Started!");
mNotificationBuilder.setContentText(getString(R.string.progresslayout_tasks_in_progress, ProgressKeeper.getTaskCount()));
- startForeground(NotificationUtils.NOTIFICATION_ID_PROGRESS_SERVICE, mNotificationBuilder.build());
+ Notification notification = mNotificationBuilder.build();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ startForeground(NotificationUtils.NOTIFICATION_ID_PROGRESS_SERVICE, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST);
+ } else {
+ startForeground(NotificationUtils.NOTIFICATION_ID_PROGRESS_SERVICE, notification);
+ }
if(ProgressKeeper.getTaskCount() < 1) stopSelf();
else ProgressKeeper.addTaskCountListener(this, false);
diff --git a/app_pojavlauncher/src/main/res/layout/dialog_control_button_setting.xml b/app_pojavlauncher/src/main/res/layout/dialog_control_button_setting.xml
index a3185552dd..08508c24a4 100644
--- a/app_pojavlauncher/src/main/res/layout/dialog_control_button_setting.xml
+++ b/app_pojavlauncher/src/main/res/layout/dialog_control_button_setting.xml
@@ -483,7 +483,7 @@
android:id="@+id/visibility_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="Visibility"
+ android:text="@string/customctrl_visibility_title"
app:layout_constraintTop_toBottomOf="@+id/editButtonOpacity_seekbar"
tools:layout_editor_absoluteX="6dp" />
@@ -491,14 +491,14 @@
android:id="@+id/visibility_game_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="In game"
+ android:text="@string/customctrl_visibility_ingame"
app:layout_constraintTop_toBottomOf="@+id/visibility_textview" />
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..bf5a97dbf9
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/layout/fragment_controller_remapper.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app_pojavlauncher/src/main/res/layout/item_centered_textview_large.xml b/app_pojavlauncher/src/main/res/layout/item_centered_textview_large.xml
new file mode 100644
index 0000000000..ac329d6e3e
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/layout/item_centered_textview_large.xml
@@ -0,0 +1,10 @@
+
\ 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..af6ff587b3
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/layout/item_controller_mapping.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml
index 993218ff1a..9265f64b62 100644
--- a/app_pojavlauncher/src/main/res/values/strings.xml
+++ b/app_pojavlauncher/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
-
+
SolCraftLauncher (SolDev PojavLancher fork)
SolCraftLauncher
@@ -394,4 +394,33 @@
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
+ Stick Up
+ Stick Down
+ Stick Left
+ Stick Right
+ Left Thumbstick (click)
+ Right Thumbstick (click)
+ D-Pad Up
+ D-Pad Down
+ D-Pad Left
+ D-Pad Right
+ Hold
+ to exit
+ Current mode
+ Visibility
+ In-game
+ In menus
+ Expand to change keycodes
+ Change controller key bindings
+ Allows you to modify the keyboard keys bound to each controller button
diff --git a/app_pojavlauncher/src/main/res/xml/pref_control.xml b/app_pojavlauncher/src/main/res/xml/pref_control.xml
index 550439aeaf..f6300e7c32 100644
--- a/app_pojavlauncher/src/main/res/xml/pref_control.xml
+++ b/app_pojavlauncher/src/main/res/xml/pref_control.xml
@@ -132,6 +132,10 @@
+
+
diff --git a/gradle.properties b/gradle.properties
index 2a84a3a9fe..7be9b6b006 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,4 +3,5 @@ org.gradle.configureondemand=true
android.useAndroidX=true
android.bundle.language.enableSplit=false
-org.gradle.jvmargs=-Xmx8G
+# Increase Gradle daemon RAM allocation
+org.gradle.jvmargs=-Xmx4096M