From f1dec91ae1b787dd52d0c7d2ee131a55c8724a96 Mon Sep 17 00:00:00 2001
From: SSimco <37044560+SSimco@users.noreply.github.com>
Date: Sun, 26 May 2024 17:51:32 +0300
Subject: [PATCH] Added input overlay
---
src/android/app/src/main/AndroidManifest.xml | 11 +-
.../src/main/cpp/AndroidEmulatedController.h | 12 +
src/android/app/src/main/cpp/EmulationState.h | 5 +
src/android/app/src/main/cpp/native-lib.cpp | 12 +
.../info/cemu/Cemu/AudioSettingsFragment.java | 4 +-
.../src/main/java/info/cemu/Cemu/Button.java | 75 +++
.../cemu/Cemu/ButtonStateChangeListener.java | 5 +
.../main/java/info/cemu/Cemu/DPadInput.java | 295 +++++++++
.../Cemu/EmulatedControllerTypeAdapter.java | 2 +-
.../info/cemu/Cemu/EmulationFragment.java | 38 +-
.../cemu/Cemu/GraphicsSettingsFragment.java | 2 +-
.../src/main/java/info/cemu/Cemu/Input.java | 90 +++
.../Cemu/InputOverlayConfigureActivity.java | 53 ++
.../Cemu/InputOverlaySettingsFragment.java | 101 ++++
.../Cemu/InputOverlaySettingsProvider.java | 277 +++++++++
.../cemu/Cemu/InputOverlaySurfaceView.java | 569 ++++++++++++++++++
.../info/cemu/Cemu/InputSettingsFragment.java | 2 +
.../main/java/info/cemu/Cemu/Joystick.java | 130 ++++
.../java/info/cemu/Cemu/MainActivity.java | 2 +-
.../java/info/cemu/Cemu/NativeLibrary.java | 4 +
.../info/cemu/Cemu/ProgressResponseBody.java | 66 ++
.../java/info/cemu/Cemu/RectangleButton.java | 34 ++
.../main/java/info/cemu/Cemu/RoundButton.java | 38 ++
.../java/info/cemu/Cemu/SelectionAdapter.java | 13 +-
.../Cemu/SingleSelectionRecyclerViewItem.java | 24 +-
.../app/src/main/res/drawable/button_a.xml | 16 +
.../main/res/drawable/button_a_pressed.xml | 16 +
.../app/src/main/res/drawable/button_b.xml | 16 +
.../main/res/drawable/button_b_pressed.xml | 16 +
.../app/src/main/res/drawable/button_c.xml | 16 +
.../main/res/drawable/button_c_pressed.xml | 16 +
.../app/src/main/res/drawable/button_home.xml | 16 +
.../main/res/drawable/button_home_pressed.xml | 16 +
.../app/src/main/res/drawable/button_l.xml | 15 +
.../main/res/drawable/button_l_pressed.xml | 15 +
.../src/main/res/drawable/button_minus.xml | 16 +
.../res/drawable/button_minus_pressed.xml | 16 +
.../app/src/main/res/drawable/button_one.xml | 16 +
.../main/res/drawable/button_one_pressed.xml | 16 +
.../app/src/main/res/drawable/button_plus.xml | 16 +
.../main/res/drawable/button_plus_pressed.xml | 16 +
.../app/src/main/res/drawable/button_r.xml | 15 +
.../main/res/drawable/button_r_pressed.xml | 15 +
.../src/main/res/drawable/button_stick.xml | 22 +
.../res/drawable/button_stick_pressed.xml | 22 +
.../app/src/main/res/drawable/button_two.xml | 16 +
.../main/res/drawable/button_two_pressed.xml | 16 +
.../app/src/main/res/drawable/button_x.xml | 16 +
.../main/res/drawable/button_x_pressed.xml | 16 +
.../app/src/main/res/drawable/button_y.xml | 16 +
.../main/res/drawable/button_y_pressed.xml | 16 +
.../app/src/main/res/drawable/button_z.xml | 16 +
.../main/res/drawable/button_z_pressed.xml | 16 +
.../app/src/main/res/drawable/button_zl.xml | 15 +
.../main/res/drawable/button_zl_pressed.xml | 15 +
.../app/src/main/res/drawable/button_zr.xml | 15 +
.../main/res/drawable/button_zr_pressed.xml | 15 +
.../src/main/res/drawable/dpad_background.xml | 12 +
.../app/src/main/res/drawable/dpad_button.xml | 11 +
.../main/res/drawable/dpad_button_pressed.xml | 11 +
.../app/src/main/res/drawable/ic_restart.xml | 10 +
.../main/res/drawable/stick_background.xml | 16 +
.../app/src/main/res/drawable/stick_inner.xml | 12 +
.../main/res/drawable/stick_inner_pressed.xml | 12 +
.../res/layout/activity_overlay_configure.xml | 38 ++
.../main/res/layout/fragment_emulation.xml | 13 +
.../fragment_input_overlay_settings.xml | 16 +
.../layout/layout_single_selection_item.xml | 20 +-
.../res/navigation/nav_settings_graph.xml | 8 +
.../app/src/main/res/values/colors.xml | 2 +
.../app/src/main/res/values/strings.xml | 18 +
src/input/emulated/EmulatedController.cpp | 24 +
src/input/emulated/EmulatedController.h | 7 +-
73 files changed, 2581 insertions(+), 29 deletions(-)
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/Button.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/ButtonStateChangeListener.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/DPadInput.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/Input.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/InputOverlayConfigureActivity.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySettingsFragment.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySettingsProvider.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySurfaceView.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/Joystick.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/ProgressResponseBody.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/RectangleButton.java
create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/RoundButton.java
create mode 100644 src/android/app/src/main/res/drawable/button_a.xml
create mode 100644 src/android/app/src/main/res/drawable/button_a_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_b.xml
create mode 100644 src/android/app/src/main/res/drawable/button_b_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_c.xml
create mode 100644 src/android/app/src/main/res/drawable/button_c_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_home.xml
create mode 100644 src/android/app/src/main/res/drawable/button_home_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_l.xml
create mode 100644 src/android/app/src/main/res/drawable/button_l_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_minus.xml
create mode 100644 src/android/app/src/main/res/drawable/button_minus_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_one.xml
create mode 100644 src/android/app/src/main/res/drawable/button_one_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_plus.xml
create mode 100644 src/android/app/src/main/res/drawable/button_plus_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_r.xml
create mode 100644 src/android/app/src/main/res/drawable/button_r_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_stick.xml
create mode 100644 src/android/app/src/main/res/drawable/button_stick_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_two.xml
create mode 100644 src/android/app/src/main/res/drawable/button_two_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_x.xml
create mode 100644 src/android/app/src/main/res/drawable/button_x_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_y.xml
create mode 100644 src/android/app/src/main/res/drawable/button_y_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_z.xml
create mode 100644 src/android/app/src/main/res/drawable/button_z_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_zl.xml
create mode 100644 src/android/app/src/main/res/drawable/button_zl_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/button_zr.xml
create mode 100644 src/android/app/src/main/res/drawable/button_zr_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/dpad_background.xml
create mode 100644 src/android/app/src/main/res/drawable/dpad_button.xml
create mode 100644 src/android/app/src/main/res/drawable/dpad_button_pressed.xml
create mode 100644 src/android/app/src/main/res/drawable/ic_restart.xml
create mode 100644 src/android/app/src/main/res/drawable/stick_background.xml
create mode 100644 src/android/app/src/main/res/drawable/stick_inner.xml
create mode 100644 src/android/app/src/main/res/drawable/stick_inner_pressed.xml
create mode 100644 src/android/app/src/main/res/layout/activity_overlay_configure.xml
create mode 100644 src/android/app/src/main/res/layout/fragment_input_overlay_settings.xml
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 11fee32e..85c49e40 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -4,6 +4,8 @@
+
+
+ android:screenOrientation="userLandscape"
+ tools:ignore="DiscouragedApi" />
+
(new AndroidEmulatedController(index));
return *controller;
}
+ void setButtonValue(uint64 mappingId, bool value)
+ {
+ if (!m_emulatedController)
+ return;
+ m_emulatedController->setButtonValue(mappingId, value);
+ }
+ void setAxisValue(uint64 mappingId, float value)
+ {
+ if (!m_emulatedController)
+ return;
+ m_emulatedController->setAxisValue(mappingId, value);
+ }
void setType(EmulatedController::Type type)
{
if (m_emulatedController && m_emulatedController->type() == type)
diff --git a/src/android/app/src/main/cpp/EmulationState.h b/src/android/app/src/main/cpp/EmulationState.h
index 18335b79..9fd6f0d0 100644
--- a/src/android/app/src/main/cpp/EmulationState.h
+++ b/src/android/app/src/main/cpp/EmulationState.h
@@ -110,6 +110,11 @@ class EmulationState {
return AndroidEmulatedController::getAndroidEmulatedController(index).getMapping(mappingId);
}
+ AndroidEmulatedController& getEmulatedController(size_t index)
+ {
+ return AndroidEmulatedController::getAndroidEmulatedController(index);
+ }
+
int getVPADControllersCount()
{
int vpadCount = 0;
diff --git a/src/android/app/src/main/cpp/native-lib.cpp b/src/android/app/src/main/cpp/native-lib.cpp
index 5728f574..293d610b 100644
--- a/src/android/app/src/main/cpp/native-lib.cpp
+++ b/src/android/app/src/main/cpp/native-lib.cpp
@@ -401,3 +401,15 @@ Java_info_cemu_Cemu_NativeLibrary_getGraphicPackPresets(JNIEnv* env, [[maybe_unu
{
return getGraphicPresets(env, s_emulationState.getGraphicPack(id), id);
}
+
+extern "C" JNIEXPORT void JNICALL
+Java_info_cemu_Cemu_NativeLibrary_onOverlayButton([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint controllerIndex, jint mappingId, jboolean state)
+{
+ s_emulationState.getEmulatedController(controllerIndex).setButtonValue(mappingId, state);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_info_cemu_Cemu_NativeLibrary_onOverlayAxis([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint controllerIndex, jint mappingId, jfloat value)
+{
+ s_emulationState.getEmulatedController(controllerIndex).setAxisValue(mappingId, value);
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/AudioSettingsFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/AudioSettingsFragment.java
index c4e5bd03..d5da6c13 100644
--- a/src/android/app/src/main/java/info/cemu/Cemu/AudioSettingsFragment.java
+++ b/src/android/app/src/main/java/info/cemu/Cemu/AudioSettingsFragment.java
@@ -28,7 +28,7 @@ public class AudioSettingsFragment extends Fragment {
genericRecyclerViewAdapter.addRecyclerViewItem(tvDeviceCheckbox);
var tvChannelsChoices = Stream.of(NativeLibrary.AUDIO_CHANNELS_MONO, NativeLibrary.AUDIO_CHANNELS_STEREO, NativeLibrary.AUDIO_CHANNELS_SURROUND)
- .map(channels -> new SelectionAdapter.ChoiceItem<>(NativeLibrary.channelsToResourceNameId(channels), channels))
+ .map(channels -> new SelectionAdapter.ChoiceItem<>(t -> t.setText(NativeLibrary.channelsToResourceNameId(channels)), channels))
.collect(Collectors.toList());
int tvChannels = NativeLibrary.getAudioDeviceChannels(true);
SelectionAdapter tvChannelsSelectionAdapter = new SelectionAdapter<>(tvChannelsChoices, tvChannels);
@@ -53,7 +53,7 @@ public class AudioSettingsFragment extends Fragment {
checked -> NativeLibrary.setAudioDeviceEnabled(checked, true));
genericRecyclerViewAdapter.addRecyclerViewItem(padDeviceCheckbox);
- var gamepadChannelsChoices = List.of(new SelectionAdapter.ChoiceItem<>(NativeLibrary.channelsToResourceNameId(NativeLibrary.AUDIO_CHANNELS_STEREO), NativeLibrary.AUDIO_CHANNELS_STEREO));
+ var gamepadChannelsChoices = List.of(new SelectionAdapter.ChoiceItem<>(t -> t.setText(NativeLibrary.channelsToResourceNameId(NativeLibrary.AUDIO_CHANNELS_STEREO)), NativeLibrary.AUDIO_CHANNELS_STEREO));
int gamepadChannels = NativeLibrary.getAudioDeviceChannels(false);
SelectionAdapter gamepadChannelsSelectionAdapter = new SelectionAdapter<>(gamepadChannelsChoices, gamepadChannels);
SingleSelectionRecyclerViewItem gamepadChannelsModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.gamepad_channels),
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/Button.java b/src/android/app/src/main/java/info/cemu/Cemu/Button.java
new file mode 100644
index 00000000..cee9858b
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/Button.java
@@ -0,0 +1,75 @@
+package info.cemu.Cemu;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.view.MotionEvent;
+
+import androidx.annotation.DrawableRes;
+import androidx.core.content.res.ResourcesCompat;
+
+import java.util.Objects;
+
+public abstract class Button extends Input {
+ protected Drawable iconPressed;
+ protected Drawable iconNotPressed;
+ protected Drawable icon;
+ protected int currentPointerId = -1;
+ private final ButtonStateChangeListener buttonStateChangeListener;
+ private final InputOverlaySurfaceView.OverlayButton button;
+
+ public Button(Resources resources, @DrawableRes int pressedButtonId, @DrawableRes int notPressedButtonId, ButtonStateChangeListener buttonStateChangeListener, InputOverlaySurfaceView.OverlayButton button, InputOverlaySettingsProvider.InputOverlaySettings settings) {
+ super(settings);
+ iconPressed = Objects.requireNonNull(ResourcesCompat.getDrawable(resources, pressedButtonId, null));
+ iconPressed.setAlpha(settings.getAlpha());
+ iconNotPressed = Objects.requireNonNull(ResourcesCompat.getDrawable(resources, notPressedButtonId, null));
+ iconNotPressed.setAlpha(settings.getAlpha());
+ icon = iconNotPressed;
+ this.buttonStateChangeListener = buttonStateChangeListener;
+ this.button = button;
+ }
+
+ @Override
+ public boolean onTouch(MotionEvent event) {
+ boolean stateUpdated = false;
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
+ int pointerIndex = event.getActionIndex();
+ int x = (int) event.getX(pointerIndex);
+ int y = (int) event.getY(pointerIndex);
+ if (isInside(x, y)) {
+ currentPointerId = event.getPointerId(pointerIndex);
+ updateState(true);
+ stateUpdated = true;
+ }
+ }
+ case MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
+ if (event.getPointerId(event.getActionIndex()) == currentPointerId) {
+ currentPointerId = -1;
+ updateState(false);
+ stateUpdated = true;
+ }
+ }
+ }
+ return stateUpdated;
+ }
+
+ @Override
+ protected void drawInput(Canvas canvas) {
+ icon.draw(canvas);
+ }
+
+ @Override
+ protected void resetInput() {
+ updateState(false);
+ }
+
+ protected void updateState(boolean state) {
+ if (state) {
+ icon = iconPressed;
+ } else {
+ icon = iconNotPressed;
+ }
+ buttonStateChangeListener.onButtonStateChange(button, state);
+ }
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/ButtonStateChangeListener.java b/src/android/app/src/main/java/info/cemu/Cemu/ButtonStateChangeListener.java
new file mode 100644
index 00000000..436a0eec
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/ButtonStateChangeListener.java
@@ -0,0 +1,5 @@
+package info.cemu.Cemu;
+
+public interface ButtonStateChangeListener {
+ void onButtonStateChange(InputOverlaySurfaceView.OverlayButton button, boolean state);
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/DPadInput.java b/src/android/app/src/main/java/info/cemu/Cemu/DPadInput.java
new file mode 100644
index 00000000..87b0f7c8
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/DPadInput.java
@@ -0,0 +1,295 @@
+package info.cemu.Cemu;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.annotation.DrawableRes;
+import androidx.core.content.res.ResourcesCompat;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+public class DPadInput extends Input {
+ Drawable iconDpadUp;
+ Drawable iconDpadUpPressed;
+ Drawable iconDpadUpNotPressed;
+
+ Drawable iconDpadDown;
+ Drawable iconDpadDownPressed;
+ Drawable iconDpadDownNotPressed;
+
+ Drawable iconDpadLeft;
+ Drawable iconDpadLeftPressed;
+ Drawable iconDpadLeftNotPressed;
+
+ Drawable iconDpadRight;
+ Drawable iconDpadRightPressed;
+ Drawable iconDpadRightNotPressed;
+ Drawable background;
+
+ enum DpadState {
+ NONE, UP, DOWN, LEFT, RIGHT, UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT,
+ }
+
+ DpadState dpadState = DpadState.NONE;
+
+ int centreX;
+ int centreY;
+ int radius;
+ int currentPointerId = -1;
+
+ @FunctionalInterface
+ private interface ConfigureButtonInterface {
+ void configure(int circleXPos, int circleYPos, Drawable pressedButton, Drawable notPressedButton);
+ }
+
+ private final ButtonStateChangeListener buttonStateChangeListener;
+
+ @Override
+ protected void configure() {
+ var rect = settings.getRect();
+ this.centreX = rect.centerX();
+ this.centreY = rect.centerY();
+ this.radius = Math.min(rect.width(), rect.height()) / 2;
+
+ background.setAlpha(settings.getAlpha());
+ background.setBounds(
+ centreX - radius,
+ centreY - radius,
+ centreX + radius,
+ centreY + radius);
+
+ int buttonSize = (int) (radius * 0.5f);
+ ConfigureButtonInterface configureButton = (circleXPos, circleYPos, pressedButton, notPressedButton) -> {
+ pressedButton.setAlpha(settings.getAlpha());
+ notPressedButton.setAlpha(settings.getAlpha());
+ int left = circleXPos - buttonSize / 2;
+ int top = circleYPos - buttonSize / 2;
+ pressedButton.setBounds(left, top, left + buttonSize, top + buttonSize);
+ notPressedButton.setBounds(left, top, left + buttonSize, top + buttonSize);
+ };
+ configureButton.configure(
+ centreX,
+ centreY - 2 * radius / 3,
+ iconDpadUpPressed,
+ iconDpadUp
+ );
+ configureButton.configure(
+ centreX,
+ centreY + 2 * radius / 3,
+ iconDpadDownPressed,
+ iconDpadDownNotPressed
+ );
+ configureButton.configure(
+ centreX - 2 * radius / 3,
+ centreY,
+ iconDpadLeftPressed,
+ iconDpadLeftNotPressed
+ );
+ configureButton.configure(
+ centreX + 2 * radius / 3,
+ centreY,
+ iconDpadRightPressed,
+ iconDpadRightNotPressed
+ );
+ Log.i("INPUT", "Dpad reconfigure: " + rect);
+ }
+
+ public DPadInput(Resources resources, @DrawableRes int backgroundId, @DrawableRes int pressedButtonId, @DrawableRes int notPressedButtonId, ButtonStateChangeListener buttonStateChangeListener, InputOverlaySettingsProvider.InputOverlaySettings settings) {
+ super(settings);
+ this.buttonStateChangeListener = buttonStateChangeListener;
+
+ background = Objects.requireNonNull(ResourcesCompat.getDrawable(resources, backgroundId, null));
+
+ Supplier getPressedButtonIcon = () -> Objects.requireNonNull(ResourcesCompat.getDrawable(resources, pressedButtonId, null));
+ Supplier getNotPressedButtonIcon = () -> Objects.requireNonNull(ResourcesCompat.getDrawable(resources, notPressedButtonId, null));
+
+ iconDpadUpPressed = getPressedButtonIcon.get();
+ iconDpadUpNotPressed = getNotPressedButtonIcon.get();
+ iconDpadUp = iconDpadUpNotPressed;
+
+ iconDpadDownPressed = getPressedButtonIcon.get();
+ iconDpadDownNotPressed = getNotPressedButtonIcon.get();
+ iconDpadDown = iconDpadDownNotPressed;
+
+ iconDpadLeftPressed = getPressedButtonIcon.get();
+ iconDpadLeftNotPressed = getNotPressedButtonIcon.get();
+ iconDpadLeft = iconDpadLeftNotPressed;
+
+ iconDpadRightPressed = getPressedButtonIcon.get();
+ iconDpadRightNotPressed = getNotPressedButtonIcon.get();
+ iconDpadRight = iconDpadRightNotPressed;
+
+ configure();
+ }
+
+ private void updateState(DpadState nextDpadState) {
+ switch (dpadState) {
+ case UP -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_UP, false);
+ iconDpadUp = iconDpadUpNotPressed;
+ }
+ case DOWN -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_DOWN, false);
+ iconDpadDown = iconDpadDownNotPressed;
+ }
+ case LEFT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_LEFT, false);
+ iconDpadLeft = iconDpadLeftNotPressed;
+ }
+ case RIGHT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT, false);
+ iconDpadRight = iconDpadRightNotPressed;
+ }
+ case UP_LEFT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_UP, false);
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_LEFT, false);
+ iconDpadUp = iconDpadUpNotPressed;
+ iconDpadLeft = iconDpadLeftNotPressed;
+ }
+ case UP_RIGHT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_UP, false);
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT, false);
+ iconDpadUp = iconDpadUpNotPressed;
+ iconDpadRight = iconDpadRightNotPressed;
+ }
+ case DOWN_LEFT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_DOWN, false);
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_LEFT, false);
+ iconDpadDown = iconDpadDownNotPressed;
+ iconDpadLeft = iconDpadLeftNotPressed;
+ }
+ case DOWN_RIGHT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_DOWN, false);
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT, false);
+ iconDpadDown = iconDpadDownNotPressed;
+ iconDpadRight = iconDpadRightNotPressed;
+ }
+ }
+ dpadState = nextDpadState;
+ switch (nextDpadState) {
+ case UP -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_UP, true);
+ iconDpadUp = iconDpadUpPressed;
+ }
+ case DOWN -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_DOWN, true);
+ iconDpadDown = iconDpadDownPressed;
+ }
+ case LEFT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_LEFT, true);
+ iconDpadLeft = iconDpadLeftPressed;
+ }
+ case RIGHT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT, true);
+ iconDpadRight = iconDpadRightPressed;
+ }
+ case UP_LEFT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_UP, true);
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_LEFT, true);
+ iconDpadUp = iconDpadUpPressed;
+ iconDpadLeft = iconDpadLeftPressed;
+ }
+ case UP_RIGHT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_UP, true);
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT, true);
+ iconDpadUp = iconDpadUpPressed;
+ iconDpadRight = iconDpadRightPressed;
+ }
+ case DOWN_LEFT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_DOWN, true);
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_LEFT, true);
+ iconDpadDown = iconDpadDownPressed;
+ iconDpadLeft = iconDpadLeftPressed;
+ }
+ case DOWN_RIGHT -> {
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_DOWN, true);
+ buttonStateChangeListener.onButtonStateChange(InputOverlaySurfaceView.OverlayButton.DPAD_RIGHT, true);
+ iconDpadDown = iconDpadDownPressed;
+ iconDpadRight = iconDpadRightPressed;
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouch(MotionEvent event) {
+ DpadState nextState = dpadState;
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
+ int pointerIndex = event.getActionIndex();
+ int x = (int) event.getX(pointerIndex);
+ int y = (int) event.getY(pointerIndex);
+ int pointerId = event.getPointerId(pointerIndex);
+ if (isInside(x, y)) {
+ currentPointerId = pointerId;
+ nextState = DpadState.NONE;
+ }
+ }
+ case MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
+ int pointerId = event.getPointerId(event.getActionIndex());
+ if (pointerId == currentPointerId) {
+ currentPointerId = -1;
+ nextState = DpadState.NONE;
+ }
+ }
+ case MotionEvent.ACTION_MOVE -> {
+ if (currentPointerId == -1) {
+ break;
+ }
+ for (int i = 0; i < event.getPointerCount(); i++) {
+ if (currentPointerId != event.getPointerId(i)) {
+ continue;
+ }
+ int x = (int) event.getX(i);
+ int y = (int) event.getY(i);
+ int norm2 = ((x - centreX) * (x - centreX) + (y - centreY) * (y - centreY));
+ if (norm2 <= radius * radius * 0.1f) {
+ nextState = DpadState.NONE;
+ break;
+ }
+ double angle = Math.atan2(y - centreY, x - centreX);
+ nextState = getStateByAngle(angle);
+ break;
+ }
+ }
+ }
+ if (nextState != dpadState) {
+ updateState(nextState);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void resetInput() {
+ updateState(DpadState.NONE);
+ }
+
+ private DpadState getStateByAngle(double angle) {
+ if (-0.125 * Math.PI <= angle && angle < 0.125 * Math.PI) return DpadState.RIGHT;
+ if (0.125 * Math.PI <= angle && angle < 0.375 * Math.PI) return DpadState.DOWN_RIGHT;
+ if (0.375 * Math.PI <= angle && angle < 0.625 * Math.PI) return DpadState.DOWN;
+ if (0.625 * Math.PI <= angle && angle < 0.875 * Math.PI) return DpadState.DOWN_LEFT;
+ if (-0.875 * Math.PI <= angle && angle < -0.625 * Math.PI) return DpadState.UP_LEFT;
+ if (-0.625 * Math.PI <= angle && angle < -0.375 * Math.PI) return DpadState.UP;
+ if (-0.375 * Math.PI <= angle && angle < -0.125 * Math.PI) return DpadState.UP_RIGHT;
+ return DpadState.LEFT;
+ }
+
+ @Override
+ public boolean isInside(int x, int y) {
+ return ((x - centreX) * (x - centreX) + (y - centreY) * (y - centreY)) <= radius * radius;
+ }
+
+ @Override
+ protected void drawInput(Canvas canvas) {
+ background.draw(canvas);
+ iconDpadUp.draw(canvas);
+ iconDpadDown.draw(canvas);
+ iconDpadLeft.draw(canvas);
+ iconDpadRight.draw(canvas);
+ }
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/EmulatedControllerTypeAdapter.java b/src/android/app/src/main/java/info/cemu/Cemu/EmulatedControllerTypeAdapter.java
index 3ede0c71..e86a4a25 100644
--- a/src/android/app/src/main/java/info/cemu/Cemu/EmulatedControllerTypeAdapter.java
+++ b/src/android/app/src/main/java/info/cemu/Cemu/EmulatedControllerTypeAdapter.java
@@ -14,7 +14,7 @@ public class EmulatedControllerTypeAdapter extends SelectionAdapter {
NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO,
NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC,
NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE)
- .map(type -> new ChoiceItem<>(NativeLibrary.controllerTypeToResourceNameId(type), type))
+ .map(type -> new ChoiceItem<>(t->t.setText(NativeLibrary.controllerTypeToResourceNameId(type)), type))
.collect(Collectors.toList());
setSelectedValue(NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED);
}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/EmulationFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/EmulationFragment.java
index ed81ef3e..6906a12c 100644
--- a/src/android/app/src/main/java/info/cemu/Cemu/EmulationFragment.java
+++ b/src/android/app/src/main/java/info/cemu/Cemu/EmulationFragment.java
@@ -1,12 +1,13 @@
package info.cemu.Cemu;
+import android.content.Context;
import android.os.Bundle;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
@@ -17,6 +18,7 @@ public class EmulationFragment extends Fragment implements SurfaceHolder.Callbac
private final long gameTitleId;
private boolean isGameRunning;
private SurfaceHolder mainCanvasSurfaceHolder;
+ private Toast toast;
public EmulationFragment(long gameTitleId) {
this.gameTitleId = gameTitleId;
@@ -25,6 +27,40 @@ public class EmulationFragment extends Fragment implements SurfaceHolder.Callbac
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentEmulationBinding binding = FragmentEmulationBinding.inflate(inflater, container, false);
+ var context = requireContext();
+ var sharedPref = context.getSharedPreferences(
+ context.getString(R.string.inputoverlaysettings_shared_preferences_name), Context.MODE_PRIVATE);
+ var inputOverlaySettingsProvider = new InputOverlaySettingsProvider(sharedPref);
+ var overlaySettings = inputOverlaySettingsProvider.getOverlaySettings();
+ InputOverlaySurfaceView surfaceView = binding.inputOverlay;
+ binding.inputModeButton.setOnClickListener(v -> {
+ InputOverlaySurfaceView.InputMode nextMode = InputOverlaySurfaceView.InputMode.DEFAULT;
+ int toastTextResId = R.string.input_mode_default;
+ switch (surfaceView.getInputMode()) {
+ case EDIT_POSITION -> {
+ nextMode = InputOverlaySurfaceView.InputMode.EDIT_SIZE;
+ toastTextResId = R.string.input_mode_edit_size;
+ }
+ case EDIT_SIZE -> {
+ nextMode = InputOverlaySurfaceView.InputMode.DEFAULT;
+ toastTextResId = R.string.input_mode_default;
+ }
+ case DEFAULT -> {
+ nextMode = InputOverlaySurfaceView.InputMode.EDIT_POSITION;
+ toastTextResId = R.string.input_mode_edit_position;
+ }
+ }
+ if (toast != null) {
+ toast.cancel();
+ }
+ toast = Toast.makeText(requireContext(), toastTextResId, Toast.LENGTH_SHORT);
+ toast.show();
+ surfaceView.setInputMode(nextMode);
+ });
+ if (!overlaySettings.isOverlayEnabled()) {
+ binding.inputModeButton.setVisibility(View.GONE);
+ surfaceView.setVisibility(View.GONE);
+ }
SurfaceView mainCanvas = binding.mainCanvas;
mainCanvas.getHolder().addCallback(this);
return binding.getRoot();
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/GraphicsSettingsFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/GraphicsSettingsFragment.java
index 5ce7b96e..8582997d 100644
--- a/src/android/app/src/main/java/info/cemu/Cemu/GraphicsSettingsFragment.java
+++ b/src/android/app/src/main/java/info/cemu/Cemu/GraphicsSettingsFragment.java
@@ -26,7 +26,7 @@ public class GraphicsSettingsFragment extends Fragment {
int vsyncMode = NativeLibrary.getVSyncMode();
var vsyncChoices = Stream.of(NativeLibrary.VSYNC_MODE_OFF, NativeLibrary.VSYNC_MODE_DOUBLE_BUFFERING, NativeLibrary.VSYNC_MODE_TRIPLE_BUFFERING)
- .map(vsync -> new SelectionAdapter.ChoiceItem<>(NativeLibrary.vsyncModeToResourceNameId(vsync), vsync))
+ .map(vsync -> new SelectionAdapter.ChoiceItem<>(t -> t.setText(NativeLibrary.vsyncModeToResourceNameId(vsync)), vsync))
.collect(Collectors.toList());
SelectionAdapter vsyncSelectionAdapter = new SelectionAdapter<>(vsyncChoices, vsyncMode);
SingleSelectionRecyclerViewItem vsyncModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.vsync),
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/Input.java b/src/android/app/src/main/java/info/cemu/Cemu/Input.java
new file mode 100644
index 00000000..cfbe9b4e
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/Input.java
@@ -0,0 +1,90 @@
+package info.cemu.Cemu;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+
+import androidx.annotation.ColorInt;
+
+public abstract class Input {
+ protected final InputOverlaySettingsProvider.InputOverlaySettings settings;
+ private boolean drawBoundingRectangle;
+ private Rect boundingRectangle;
+ private final Paint boundingRectanglePaint = new Paint();
+
+ protected Input(InputOverlaySettingsProvider.InputOverlaySettings settings) {
+ this.settings = settings;
+ boundingRectangle = settings.getRect();
+ }
+
+ public abstract boolean onTouch(MotionEvent event);
+
+ public void moveInput(int x, int y, int maxWidth, int maxHeight) {
+ var rect = settings.getRect();
+ int width = rect.width();
+ int height = rect.height();
+ int left = Math.min(Math.max(x - width / 2, 0), maxWidth - width);
+ int top = Math.min(Math.max(y - height / 2, 0), maxHeight - height);
+ boundingRectangle = new Rect(
+ left,
+ top,
+ left + width,
+ top + height
+ );
+ settings.setRect(boundingRectangle);
+ configure();
+ }
+
+ private static final float MIN_WIDTH_HEIGHT = 100;
+
+ public void resize(int diffX, int diffY, int maxWidth, int maxHeight) {
+ var rect = settings.getRect();
+ int newRight = rect.right + diffX;
+ int newBottom = rect.bottom + diffY;
+ if (newRight - rect.left < MIN_WIDTH_HEIGHT || newBottom - rect.top < MIN_WIDTH_HEIGHT
+ || newRight > maxWidth || newBottom > maxHeight) {
+ return;
+ }
+ boundingRectangle = new Rect(
+ rect.left,
+ rect.top,
+ newRight,
+ newBottom
+ );
+ settings.setRect(boundingRectangle);
+ configure();
+ }
+
+ protected abstract void resetInput();
+
+ public void reset() {
+ drawBoundingRectangle = false;
+ }
+
+ public void saveConfiguration() {
+ settings.saveSettings();
+ }
+
+ protected abstract void configure();
+
+ protected abstract void drawInput(Canvas canvas);
+
+ public void draw(Canvas canvas) {
+ if (drawBoundingRectangle) {
+ canvas.drawRect(boundingRectangle, boundingRectanglePaint);
+ }
+ drawInput(canvas);
+ }
+
+ public void enableDrawingBoundingRect(@ColorInt int color) {
+ drawBoundingRectangle = true;
+ boundingRectanglePaint.setColor(color);
+ }
+
+ public void disableDrawingBoundingRect() {
+ drawBoundingRectangle = false;
+ }
+
+ public abstract boolean isInside(int x, int y);
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/InputOverlayConfigureActivity.java b/src/android/app/src/main/java/info/cemu/Cemu/InputOverlayConfigureActivity.java
new file mode 100644
index 00000000..b72aa380
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/InputOverlayConfigureActivity.java
@@ -0,0 +1,53 @@
+package info.cemu.Cemu;
+
+import static info.cemu.Cemu.InputOverlaySurfaceView.InputMode.EDIT_POSITION;
+import static info.cemu.Cemu.InputOverlaySurfaceView.InputMode.EDIT_SIZE;
+
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.core.view.WindowInsetsControllerCompat;
+
+import info.cemu.Cemu.databinding.ActivityOverlayConfigureBinding;
+
+public class InputOverlayConfigureActivity extends AppCompatActivity {
+ private Toast toast;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setFullscreen();
+ ActivityOverlayConfigureBinding binding = ActivityOverlayConfigureBinding.inflate(getLayoutInflater());
+ InputOverlaySurfaceView surfaceView = binding.inputConfigureOverlay;
+ surfaceView.setInputMode(EDIT_POSITION);
+ binding.configureModeButton.setOnClickListener(v -> {
+ InputOverlaySurfaceView.InputMode nextMode;
+ int toastTextResId;
+ if (surfaceView.getInputMode() == EDIT_POSITION) {
+ toastTextResId = R.string.input_mode_edit_size;
+ nextMode = EDIT_SIZE;
+ } else {
+ toastTextResId = R.string.input_mode_edit_position;
+ nextMode = EDIT_POSITION;
+ }
+ if (toast != null) {
+ toast.cancel();
+ }
+ toast = Toast.makeText(InputOverlayConfigureActivity.this, toastTextResId, Toast.LENGTH_SHORT);
+ toast.show();
+ surfaceView.setInputMode(nextMode);
+ });
+ binding.resetOverlayButton.setOnClickListener(v -> surfaceView.resetInputs());
+ setContentView(binding.getRoot());
+ }
+
+ private void setFullscreen() {
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
+ WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
+ controller.hide(WindowInsetsCompat.Type.systemBars());
+ controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+ }
+}
\ No newline at end of file
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySettingsFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySettingsFragment.java
new file mode 100644
index 00000000..33cbe25f
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySettingsFragment.java
@@ -0,0 +1,101 @@
+package info.cemu.Cemu;
+
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import info.cemu.Cemu.databinding.FragmentInputOverlaySettingsBinding;
+
+
+public class InputOverlaySettingsFragment extends Fragment {
+ private InputOverlaySettingsProvider.OverlaySettings overlaySettings;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (overlaySettings == null) {
+ var context = requireContext();
+ var sharedPref = context.getSharedPreferences(
+ context.getString(R.string.inputoverlaysettings_shared_preferences_name), Context.MODE_PRIVATE);
+ InputOverlaySettingsProvider inputOverlaySettingsProvider = new InputOverlaySettingsProvider(sharedPref);
+ overlaySettings = inputOverlaySettingsProvider.getOverlaySettings();
+ }
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ var binding = FragmentInputOverlaySettingsBinding.inflate(inflater, container, false);
+ GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter();
+
+ CheckboxRecyclerViewItem inputOverlayCheckbox = new CheckboxRecyclerViewItem(
+ getString(R.string.input_overlay),
+ getString(R.string.enable_input_overlay),
+ overlaySettings.isOverlayEnabled(),
+ checked -> {
+ overlaySettings.setOverlayEnabled(checked);
+ overlaySettings.saveSettings();
+ });
+ genericRecyclerViewAdapter.addRecyclerViewItem(inputOverlayCheckbox);
+
+ CheckboxRecyclerViewItem vibrateOnTouchCheckbox = new CheckboxRecyclerViewItem(
+ getString(R.string.vibrate),
+ getString(R.string.enable_vibrate_on_touch),
+ overlaySettings.isVibrateOnTouchEnabled(),
+ checked -> {
+ overlaySettings.setVibrateOnTouchEnabled(checked);
+ overlaySettings.saveSettings();
+ });
+ genericRecyclerViewAdapter.addRecyclerViewItem(vibrateOnTouchCheckbox);
+
+ SliderRecyclerViewItem alphaSlider = new SliderRecyclerViewItem(
+ getString(R.string.alpha_slider),
+ 0,
+ 255,
+ overlaySettings.getAlpha(),
+ value -> {
+ overlaySettings.setAlpha((int) value);
+ overlaySettings.saveSettings();
+ });
+ genericRecyclerViewAdapter.addRecyclerViewItem(alphaSlider);
+
+ SelectionAdapter controllerAdapter = new SelectionAdapter<>(
+ IntStream.range(0, NativeLibrary.MAX_CONTROLLERS)
+ .mapToObj(i -> new SelectionAdapter.ChoiceItem<>(t -> t.setText(getString(R.string.controller_numbered, i + 1)), i))
+ .collect(Collectors.toList()),
+ overlaySettings.getControllerIndex()
+ );
+ SingleSelectionRecyclerViewItem controllerSelection = new SingleSelectionRecyclerViewItem<>(
+ getString(R.string.overlay_controller),
+ getString(R.string.controller_numbered, overlaySettings.getControllerIndex() + 1),
+ controllerAdapter,
+ (controllerIndex, selectionRecyclerViewItem) -> {
+ overlaySettings.setControllerIndex(controllerIndex);
+ selectionRecyclerViewItem.setDescription(getString(R.string.controller_numbered, controllerIndex + 1));
+ overlaySettings.saveSettings();
+ });
+ genericRecyclerViewAdapter.addRecyclerViewItem(controllerSelection);
+
+ genericRecyclerViewAdapter.addRecyclerViewItem(
+ new ButtonRecyclerViewItem(
+ getString(R.string.configure_inputs_label),
+ getString(R.string.configure_inputs_description),
+ () -> startActivity(new Intent(requireActivity(), InputOverlayConfigureActivity.class))
+ )
+ );
+
+ binding.inputOverlaySettingsRecyclerView.setAdapter(genericRecyclerViewAdapter);
+
+ return binding.getRoot();
+ }
+}
\ No newline at end of file
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySettingsProvider.java b/src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySettingsProvider.java
new file mode 100644
index 00000000..9c717388
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySettingsProvider.java
@@ -0,0 +1,277 @@
+package info.cemu.Cemu;
+
+
+import android.content.SharedPreferences;
+import android.graphics.Rect;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+public class InputOverlaySettingsProvider {
+ public enum Input {
+ A,
+ B,
+ ONE,
+ TWO,
+ C,
+ Z,
+ HOME,
+ DPAD,
+ L,
+ L_STICK,
+ MINUS,
+ PLUS,
+ R,
+ R_STICK,
+ X,
+ Y,
+ ZL,
+ ZR,
+ NUN_CHUCK_AXIS,
+ LEFT_AXIS,
+ RIGHT_AXIS,
+ }
+
+ private final SharedPreferences sharedPreferences;
+
+ private static final int defaultAlpha = 64;
+
+ public InputOverlaySettingsProvider(SharedPreferences sharedPreferences) {
+ this.sharedPreferences = sharedPreferences;
+ }
+
+ public OverlaySettings getOverlaySettings() {
+ return new OverlaySettings(
+ isVibrateOnTouchEnabled(),
+ isOverlayEnabled(),
+ getControllerIndex(),
+ getAlpha(),
+ settings -> {
+ var editor = sharedPreferences.edit();
+ editor.putInt("controllerIndex", settings.getControllerIndex());
+ editor.putInt("alpha", settings.getAlpha());
+ editor.putBoolean("overlayEnabled", settings.isOverlayEnabled());
+ editor.putBoolean("vibrateOnTouchEnabled", settings.isVibrateOnTouchEnabled());
+ editor.apply();
+ }
+ );
+ }
+
+ public InputOverlaySettings getOverlaySettingsForInput(Input input, int width, int height) {
+ return new InputOverlaySettings(
+ getRectangle(input).orElseGet(() -> getDefaultRectangle(input, width, height)),
+ getAlpha(),
+ settings -> {
+ var editor = sharedPreferences.edit();
+ var rect = settings.getRect();
+ editor.putInt(input + "left", rect.left);
+ editor.putInt(input + "top", rect.top);
+ editor.putInt(input + "right", rect.right);
+ editor.putInt(input + "bottom", rect.bottom);
+ editor.putInt(input + "alpha", settings.getAlpha());
+ editor.apply();
+ }
+ );
+ }
+
+ private boolean isOverlayEnabled() {
+ return sharedPreferences.getBoolean("overlayEnabled", true);
+ }
+
+ private boolean isVibrateOnTouchEnabled() {
+ return sharedPreferences.getBoolean("vibrateOnTouchEnabled", true);
+ }
+
+ private int getControllerIndex() {
+ return sharedPreferences.getInt("controllerIndex", 0);
+ }
+
+ private int getAlpha() {
+ return sharedPreferences.getInt("alpha", defaultAlpha);
+ }
+
+ private Optional getRectangle(Input input) {
+ int left = sharedPreferences.getInt(input + "left", -1);
+ int top = sharedPreferences.getInt(input + "top", -1);
+ int right = sharedPreferences.getInt(input + "right", -1);
+ int bottom = sharedPreferences.getInt(input + "bottom", -1);
+ if (left == -1 || top == -1 || right == -1 || bottom == -1) {
+ return Optional.empty();
+ }
+ return Optional.of(new Rect(left, top, right, bottom));
+ }
+
+ public Rect getDefaultRectangle(Input input, int width, int height) {
+ int pmButtonRadius = (int) (height * 0.065f);
+ int pmButtonsCentreX = (int) (width * 0.5f);
+ int pmButtonsCentreY = (int) (height * 0.875f);
+
+ int roundButtonRadius = (int) (height * 0.065f);
+ int abxyButtonsCentreX = width - roundButtonRadius * 4;
+ int abxyButtonsCentreY = height - roundButtonRadius * 4;
+
+ int rectangleButtonsHeight = (int) (height * 0.10f);
+ int rectangleButtonsWidth = rectangleButtonsHeight * 2;
+
+ int triggersLLeft = (int) (width * 0.05f);
+ int triggersRLeft = (int) (width * 0.95f - rectangleButtonsWidth);
+ int triggersTop = (int) (height * 0.05f);
+
+ int dpadRadius = (int) (height * 0.20f);
+ int dpadCentreX = dpadRadius;
+ int dpadCentreY = (int) (height * 0.75f);
+
+ int joystickRadius = (int) (height * 0.10f);
+ int rightJoystickCentreX = (int) (width * 0.75f);
+ int leftJoystickCentreX = (int) (width * 0.25f);
+ int joystickCentreY = (int) (height * 0.55f);
+ int joystickClickRadius = (int) (joystickRadius * 0.7f);
+
+ return switch (input) {
+ case A -> new Rect(abxyButtonsCentreX + roundButtonRadius,
+ abxyButtonsCentreY - roundButtonRadius,
+ abxyButtonsCentreX + roundButtonRadius * 3,
+ abxyButtonsCentreY + roundButtonRadius);
+ case B -> new Rect(abxyButtonsCentreX - roundButtonRadius,
+ abxyButtonsCentreY + roundButtonRadius,
+ abxyButtonsCentreX + roundButtonRadius,
+ abxyButtonsCentreY + roundButtonRadius * 3);
+ case X, ONE -> new Rect(abxyButtonsCentreX - roundButtonRadius,
+ abxyButtonsCentreY - roundButtonRadius * 3,
+ abxyButtonsCentreX + roundButtonRadius,
+ abxyButtonsCentreY - roundButtonRadius);
+ case Y, TWO -> new Rect(abxyButtonsCentreX - roundButtonRadius * 3,
+ abxyButtonsCentreY - roundButtonRadius,
+ abxyButtonsCentreX - roundButtonRadius,
+ abxyButtonsCentreY + roundButtonRadius);
+ case MINUS -> new Rect(pmButtonsCentreX - pmButtonRadius * 3,
+ pmButtonsCentreY - pmButtonRadius,
+ pmButtonsCentreX - pmButtonRadius,
+ pmButtonsCentreY + pmButtonRadius);
+ case PLUS -> new Rect(pmButtonsCentreX + pmButtonRadius,
+ pmButtonsCentreY - pmButtonRadius,
+ pmButtonsCentreX + pmButtonRadius * 3,
+ pmButtonsCentreY + pmButtonRadius);
+ case L -> new Rect(triggersLLeft,
+ triggersTop,
+ triggersLLeft + rectangleButtonsWidth,
+ triggersTop + rectangleButtonsHeight);
+ case ZL -> new Rect(triggersLLeft,
+ (int) (triggersTop + rectangleButtonsHeight * 1.5f),
+ triggersLLeft + rectangleButtonsWidth,
+ (int) (triggersTop + rectangleButtonsHeight * 2.5f));
+ case R -> new Rect(triggersRLeft,
+ triggersTop,
+ triggersRLeft + rectangleButtonsWidth,
+ triggersTop + rectangleButtonsHeight);
+ case ZR -> new Rect(triggersRLeft,
+ (int) (triggersTop + rectangleButtonsHeight * 1.5f),
+ triggersRLeft + rectangleButtonsWidth,
+ (int) (triggersTop + rectangleButtonsHeight * 2.5f));
+ // TODO: Wiimote settings
+ case C, HOME, Z, NUN_CHUCK_AXIS -> new Rect();
+ case DPAD -> new Rect(dpadCentreX - dpadRadius,
+ dpadCentreY - dpadRadius,
+ dpadCentreX + dpadRadius,
+ dpadCentreY + dpadRadius);
+ case LEFT_AXIS -> new Rect(leftJoystickCentreX - joystickRadius,
+ joystickCentreY - joystickRadius,
+ leftJoystickCentreX + joystickRadius,
+ joystickCentreY + joystickRadius);
+ case RIGHT_AXIS -> new Rect(rightJoystickCentreX - joystickRadius,
+ joystickCentreY - joystickRadius,
+ rightJoystickCentreX + joystickRadius,
+ joystickCentreY + joystickRadius);
+ case L_STICK ->
+ new Rect((int) (leftJoystickCentreX + joystickRadius * 1.5f - joystickClickRadius),
+ (int) (joystickCentreY + joystickRadius * 1.5f - joystickClickRadius),
+ (int) (leftJoystickCentreX + joystickRadius * 1.5f + joystickClickRadius),
+ (int) (joystickCentreY + joystickRadius * 1.5f + joystickClickRadius));
+ case R_STICK ->
+ new Rect((int) (rightJoystickCentreX - joystickRadius * 1.5f - joystickClickRadius),
+ (int) (joystickCentreY + joystickRadius * 1.5f - joystickClickRadius),
+ (int) (rightJoystickCentreX - joystickRadius * 1.5f + joystickClickRadius),
+ (int) (joystickCentreY + joystickRadius * 1.5f + joystickClickRadius));
+ };
+ }
+
+ public static class OverlaySettings {
+ private int controllerIndex;
+ private boolean overlayEnabled;
+ private boolean vibrateOnTouchEnabled;
+ private int alpha;
+ private final Consumer overlaySettingsConsumer;
+
+ private OverlaySettings(boolean vibrateOnTouchEnabled, boolean overlayEnabled, int controllerIndex, int alpha, Consumer overlaySettingsConsumer) {
+ setControllerIndex(controllerIndex);
+ setAlpha(alpha);
+ setVibrateOnTouchEnabled(vibrateOnTouchEnabled);
+ setOverlayEnabled(overlayEnabled);
+ this.overlaySettingsConsumer = overlaySettingsConsumer;
+ }
+
+ public void setAlpha(int alpha) {
+ this.alpha = Math.max(0, Math.min(255, alpha));
+ }
+
+ public int getAlpha() {
+ return this.alpha;
+ }
+
+ public void setControllerIndex(int controllerIndex) {
+ this.controllerIndex = Math.min(Math.max(controllerIndex, 0), NativeLibrary.MAX_CONTROLLERS - 1);
+ }
+
+ public int getControllerIndex() {
+ return this.controllerIndex;
+ }
+
+ public void saveSettings() {
+ overlaySettingsConsumer.accept(this);
+ }
+
+ public boolean isOverlayEnabled() {
+ return overlayEnabled;
+ }
+
+ public void setOverlayEnabled(boolean overlayEnabled) {
+ this.overlayEnabled = overlayEnabled;
+ }
+
+ public boolean isVibrateOnTouchEnabled() {
+ return vibrateOnTouchEnabled;
+ }
+
+ public void setVibrateOnTouchEnabled(boolean vibrateOnTouchEnabled) {
+ this.vibrateOnTouchEnabled = vibrateOnTouchEnabled;
+ }
+ }
+
+ public static class InputOverlaySettings {
+ private Rect rectangle;
+ private final Consumer inputOverlaySettingsConsumer;
+ private final int alpha;
+
+ private InputOverlaySettings(Rect rectangle, int alpha, Consumer inputOverlaySettingsConsumer) {
+ setRect(rectangle);
+ this.alpha = alpha;
+ this.inputOverlaySettingsConsumer = inputOverlaySettingsConsumer;
+ }
+
+ public int getAlpha() {
+ return alpha;
+ }
+
+ public void setRect(Rect rectangle) {
+ this.rectangle = rectangle;
+ }
+
+ public Rect getRect() {
+ return this.rectangle;
+ }
+
+ public void saveSettings() {
+ inputOverlaySettingsConsumer.accept(this);
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySurfaceView.java b/src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySurfaceView.java
new file mode 100644
index 00000000..51c2f988
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/InputOverlaySurfaceView.java
@@ -0,0 +1,569 @@
+package info.cemu.Cemu;
+
+import static info.cemu.Cemu.InputOverlaySurfaceView.InputMode.DEFAULT;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InputOverlaySurfaceView extends SurfaceView implements View.OnTouchListener {
+ public enum InputMode {
+ DEFAULT,
+ EDIT_POSITION,
+ EDIT_SIZE,
+ }
+
+ private InputMode inputMode = DEFAULT;
+
+ public void resetInputs() {
+ int width = getWidth();
+ int height = getHeight();
+ for (var input : InputOverlaySettingsProvider.Input.values()) {
+ var inputSettings = settingsProvider.getOverlaySettingsForInput(input, width, height);
+ inputSettings.setRect(settingsProvider.getDefaultRectangle(input, width, height));
+ inputSettings.saveSettings();
+ }
+ inputs.clear();
+ inputs = null;
+ setInputs();
+ invalidate();
+ }
+
+ public void setInputMode(InputMode inputMode) {
+ if (inputs != null) {
+ if (this.inputMode != DEFAULT) {
+ for (var input : inputs) {
+ input.saveConfiguration();
+ }
+ } else {
+ for (var input : inputs) {
+ input.reset();
+ }
+ }
+ }
+ this.inputMode = inputMode;
+ }
+
+ public InputMode getInputMode() {
+ return inputMode;
+ }
+
+ public enum OverlayButton {
+ A,
+ B,
+ ONE,
+ TWO,
+ C,
+ Z,
+ HOME,
+ DPAD_DOWN,
+ DPAD_LEFT,
+ DPAD_RIGHT,
+ DPAD_UP,
+ L,
+ L_STICK,
+ MINUS,
+ PLUS,
+ R,
+ R_STICK,
+ X,
+ Y,
+ ZL,
+ ZR,
+ }
+
+
+ public enum OverlayJoystick {
+ LEFT,
+ RIGHT,
+ }
+
+ private int nativeControllerType = -1;
+ int controllerIndex;
+
+ private List inputs;
+ private final InputOverlaySettingsProvider settingsProvider;
+ private final Vibrator vibrator;
+ private final VibrationEffect buttonTouchVibrationEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+ private final boolean vibrateOnTouch;
+
+ public InputOverlaySurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ context.getSystemService(Context.VIBRATOR_SERVICE);
+ vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ setOnTouchListener(this);
+ var sharedPref = context.getSharedPreferences(
+ getContext().getString(R.string.inputoverlaysettings_shared_preferences_name), Context.MODE_PRIVATE);
+ settingsProvider = new InputOverlaySettingsProvider(sharedPref);
+ var overlaySettings = settingsProvider.getOverlaySettings();
+ controllerIndex = overlaySettings.getControllerIndex();
+ vibrateOnTouch = vibrator != null && vibrator.hasVibrator() && overlaySettings.isVibrateOnTouchEnabled();
+ }
+
+ int overlayButtonToVPADButton(OverlayButton button) {
+ return switch (button) {
+ case A -> NativeLibrary.VPAD_BUTTON_A;
+ case B -> NativeLibrary.VPAD_BUTTON_B;
+ case X -> NativeLibrary.VPAD_BUTTON_X;
+ case Y -> NativeLibrary.VPAD_BUTTON_Y;
+ case PLUS -> NativeLibrary.VPAD_BUTTON_PLUS;
+ case MINUS -> NativeLibrary.VPAD_BUTTON_MINUS;
+ case DPAD_UP -> NativeLibrary.VPAD_BUTTON_UP;
+ case DPAD_DOWN -> NativeLibrary.VPAD_BUTTON_DOWN;
+ case DPAD_LEFT -> NativeLibrary.VPAD_BUTTON_LEFT;
+ case DPAD_RIGHT -> NativeLibrary.VPAD_BUTTON_RIGHT;
+ case L_STICK -> NativeLibrary.VPAD_BUTTON_STICKL;
+ case R_STICK -> NativeLibrary.VPAD_BUTTON_STICKR;
+ case L -> NativeLibrary.VPAD_BUTTON_L;
+ case R -> NativeLibrary.VPAD_BUTTON_R;
+ case ZR -> NativeLibrary.VPAD_BUTTON_ZR;
+ case ZL -> NativeLibrary.VPAD_BUTTON_ZL;
+ default -> -1;
+ };
+ }
+
+ int overlayButtonToClassicButton(OverlayButton button) {
+ return switch (button) {
+ case A -> NativeLibrary.CLASSIC_BUTTON_A;
+ case B -> NativeLibrary.CLASSIC_BUTTON_B;
+ case X -> NativeLibrary.CLASSIC_BUTTON_X;
+ case Y -> NativeLibrary.CLASSIC_BUTTON_Y;
+ case PLUS -> NativeLibrary.CLASSIC_BUTTON_PLUS;
+ case MINUS -> NativeLibrary.CLASSIC_BUTTON_MINUS;
+ case DPAD_UP -> NativeLibrary.CLASSIC_BUTTON_UP;
+ case DPAD_DOWN -> NativeLibrary.CLASSIC_BUTTON_DOWN;
+ case DPAD_LEFT -> NativeLibrary.CLASSIC_BUTTON_LEFT;
+ case DPAD_RIGHT -> NativeLibrary.CLASSIC_BUTTON_RIGHT;
+ case L -> NativeLibrary.CLASSIC_BUTTON_L;
+ case R -> NativeLibrary.CLASSIC_BUTTON_R;
+ case ZR -> NativeLibrary.CLASSIC_BUTTON_ZR;
+ case ZL -> NativeLibrary.CLASSIC_BUTTON_ZL;
+ default -> -1;
+ };
+ }
+
+ int overlayButtonToProButton(OverlayButton button) {
+ return switch (button) {
+ case A -> NativeLibrary.PRO_BUTTON_A;
+ case B -> NativeLibrary.PRO_BUTTON_B;
+ case X -> NativeLibrary.PRO_BUTTON_X;
+ case Y -> NativeLibrary.PRO_BUTTON_Y;
+ case PLUS -> NativeLibrary.PRO_BUTTON_PLUS;
+ case MINUS -> NativeLibrary.PRO_BUTTON_MINUS;
+ case DPAD_UP -> NativeLibrary.PRO_BUTTON_UP;
+ case DPAD_DOWN -> NativeLibrary.PRO_BUTTON_DOWN;
+ case DPAD_LEFT -> NativeLibrary.PRO_BUTTON_LEFT;
+ case DPAD_RIGHT -> NativeLibrary.PRO_BUTTON_RIGHT;
+ case L_STICK -> NativeLibrary.PRO_BUTTON_STICKL;
+ case R_STICK -> NativeLibrary.PRO_BUTTON_STICKR;
+ case L -> NativeLibrary.PRO_BUTTON_L;
+ case R -> NativeLibrary.PRO_BUTTON_R;
+ case ZR -> NativeLibrary.PRO_BUTTON_ZR;
+ case ZL -> NativeLibrary.PRO_BUTTON_ZL;
+ default -> -1;
+ };
+ }
+
+ int overlayButtonToWiimoteButton(OverlayButton button) {
+ return switch (button) {
+ case A -> NativeLibrary.WIIMOTE_BUTTON_A;
+ case B -> NativeLibrary.WIIMOTE_BUTTON_B;
+ case ONE -> NativeLibrary.WIIMOTE_BUTTON_1;
+ case TWO -> NativeLibrary.WIIMOTE_BUTTON_2;
+ case PLUS -> NativeLibrary.WIIMOTE_BUTTON_PLUS;
+ case MINUS -> NativeLibrary.WIIMOTE_BUTTON_MINUS;
+ case HOME -> NativeLibrary.WIIMOTE_BUTTON_HOME;
+ case DPAD_UP -> NativeLibrary.WIIMOTE_BUTTON_UP;
+ case DPAD_DOWN -> NativeLibrary.WIIMOTE_BUTTON_DOWN;
+ case DPAD_LEFT -> NativeLibrary.WIIMOTE_BUTTON_LEFT;
+ case DPAD_RIGHT -> NativeLibrary.WIIMOTE_BUTTON_RIGHT;
+ case C -> NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_C;
+ case Z -> NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_Z;
+ default -> -1;
+ };
+ }
+
+ void onButtonStateChange(OverlayButton button, boolean state) {
+ int nativeButtonId = switch (nativeControllerType) {
+ case NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD -> overlayButtonToVPADButton(button);
+ case NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC ->
+ overlayButtonToClassicButton(button);
+ case NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO -> overlayButtonToProButton(button);
+ case NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE ->
+ overlayButtonToWiimoteButton(button);
+ default -> -1;
+ };
+ if (nativeButtonId == -1) {
+ return;
+ }
+ if (vibrateOnTouch) {
+ vibrator.vibrate(buttonTouchVibrationEffect);
+ }
+ NativeLibrary.onOverlayButton(controllerIndex, nativeButtonId, state);
+ }
+
+ void onOverlayAxis(int axis, float value) {
+ NativeLibrary.onOverlayAxis(controllerIndex, axis, value);
+ }
+
+ void onVPADJoystickStateChange(OverlayJoystick joystick, float up, float down, float left, float right) {
+ if (joystick == OverlayJoystick.LEFT) {
+ onOverlayAxis(NativeLibrary.VPAD_BUTTON_STICKL_UP, up);
+ onOverlayAxis(NativeLibrary.VPAD_BUTTON_STICKL_DOWN, down);
+ onOverlayAxis(NativeLibrary.VPAD_BUTTON_STICKL_LEFT, left);
+ onOverlayAxis(NativeLibrary.VPAD_BUTTON_STICKL_RIGHT, right);
+ } else {
+ onOverlayAxis(NativeLibrary.VPAD_BUTTON_STICKR_UP, up);
+ onOverlayAxis(NativeLibrary.VPAD_BUTTON_STICKR_DOWN, down);
+ onOverlayAxis(NativeLibrary.VPAD_BUTTON_STICKR_LEFT, left);
+ onOverlayAxis(NativeLibrary.VPAD_BUTTON_STICKR_RIGHT, right);
+ }
+ }
+
+ void onProJoystickStateChange(OverlayJoystick joystick, float up, float down, float left, float right) {
+ if (joystick == OverlayJoystick.LEFT) {
+ onOverlayAxis(NativeLibrary.PRO_BUTTON_STICKL_UP, up);
+ onOverlayAxis(NativeLibrary.PRO_BUTTON_STICKL_DOWN, down);
+ onOverlayAxis(NativeLibrary.PRO_BUTTON_STICKL_LEFT, left);
+ onOverlayAxis(NativeLibrary.PRO_BUTTON_STICKL_RIGHT, right);
+ } else {
+ onOverlayAxis(NativeLibrary.PRO_BUTTON_STICKR_UP, up);
+ onOverlayAxis(NativeLibrary.PRO_BUTTON_STICKR_DOWN, down);
+ onOverlayAxis(NativeLibrary.PRO_BUTTON_STICKR_LEFT, left);
+ onOverlayAxis(NativeLibrary.PRO_BUTTON_STICKR_RIGHT, right);
+ }
+ }
+
+ void onClassicJoystickStateChange(OverlayJoystick joystick, float up, float down, float left, float right) {
+ if (joystick == OverlayJoystick.LEFT) {
+ onOverlayAxis(NativeLibrary.CLASSIC_BUTTON_STICKL_UP, up);
+ onOverlayAxis(NativeLibrary.CLASSIC_BUTTON_STICKL_DOWN, down);
+ onOverlayAxis(NativeLibrary.CLASSIC_BUTTON_STICKL_LEFT, left);
+ onOverlayAxis(NativeLibrary.CLASSIC_BUTTON_STICKL_RIGHT, right);
+ } else {
+ onOverlayAxis(NativeLibrary.CLASSIC_BUTTON_STICKR_UP, up);
+ onOverlayAxis(NativeLibrary.CLASSIC_BUTTON_STICKR_DOWN, down);
+ onOverlayAxis(NativeLibrary.CLASSIC_BUTTON_STICKR_LEFT, left);
+ onOverlayAxis(NativeLibrary.CLASSIC_BUTTON_STICKR_RIGHT, right);
+ }
+ }
+
+ void onWiimoteJoystickStateChange(OverlayJoystick joystick, float up, float down, float left, float right) {
+ if (joystick == OverlayJoystick.LEFT) {
+ onOverlayAxis(NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_UP, up);
+ onOverlayAxis(NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_DOWN, down);
+ onOverlayAxis(NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_LEFT, left);
+ onOverlayAxis(NativeLibrary.WIIMOTE_BUTTON_NUNCHUCK_RIGHT, right);
+ }
+ }
+
+ void onJoystickStateChange(OverlayJoystick joystick, float x, float y) {
+ float up = y < 0 ? -y : 0;
+ float down = y > 0 ? y : 0;
+ float left = x < 0 ? -x : 0;
+ float right = x > 0 ? x : 0;
+ switch (nativeControllerType) {
+ case NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD ->
+ onVPADJoystickStateChange(joystick, up, down, left, right);
+ case NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO ->
+ onProJoystickStateChange(joystick, up, down, left, right);
+ case NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC ->
+ onClassicJoystickStateChange(joystick, up, down, left, right);
+ case NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE ->
+ onWiimoteJoystickStateChange(joystick, up, down, left, right);
+ }
+ }
+
+ private InputOverlaySettingsProvider.InputOverlaySettings getOverlaySettingsForInput(InputOverlaySettingsProvider.Input input) {
+ return settingsProvider.getOverlaySettingsForInput(input, getWidth(), getHeight());
+ }
+
+ void setInputs() {
+ if (inputs != null) {
+ return;
+ }
+ inputs = new ArrayList<>();
+ if (NativeLibrary.isControllerDisabled(controllerIndex)) {
+ return;
+ }
+ var resources = getResources();
+ nativeControllerType = NativeLibrary.getControllerType(controllerIndex);
+
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_a_pressed,
+ R.drawable.button_a,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.A,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.A)
+ ));
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_b_pressed,
+ R.drawable.button_b,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.B,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.B)
+ ));
+
+ if (nativeControllerType != NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE) {
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_x_pressed,
+ R.drawable.button_x,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.X,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.X)
+ ));
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_y_pressed,
+ R.drawable.button_y,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.Y,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.Y)
+ ));
+ inputs.add(new RectangleButton(
+ resources,
+ R.drawable.button_zl_pressed,
+ R.drawable.button_zl,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.ZL,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.ZL)
+ ));
+ inputs.add(new RectangleButton(
+ resources,
+ R.drawable.button_l_pressed,
+ R.drawable.button_l,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.L,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.L)
+ ));
+ inputs.add(new RectangleButton(
+ resources,
+ R.drawable.button_zr_pressed,
+ R.drawable.button_zr,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.ZR,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.ZR)
+ ));
+ inputs.add(new RectangleButton(
+ resources,
+ R.drawable.button_r_pressed,
+ R.drawable.button_r,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.R,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.R)
+ ));
+ inputs.add(new Joystick(
+ resources,
+ R.drawable.stick_background,
+ R.drawable.stick_inner_pressed,
+ R.drawable.stick_inner,
+ InputOverlaySurfaceView.this::onJoystickStateChange,
+ OverlayJoystick.RIGHT,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.RIGHT_AXIS)
+ ));
+ }
+
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_minus_pressed,
+ R.drawable.button_minus,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.MINUS,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.MINUS)
+ ));
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_plus_pressed,
+ R.drawable.button_plus,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.PLUS,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.PLUS)
+ ));
+
+ inputs.add(new DPadInput(
+ resources,
+ R.drawable.dpad_background,
+ R.drawable.dpad_button_pressed,
+ R.drawable.dpad_button,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.DPAD)
+ ));
+ inputs.add(new Joystick(
+ resources,
+ R.drawable.stick_background,
+ R.drawable.stick_inner_pressed,
+ R.drawable.stick_inner,
+ InputOverlaySurfaceView.this::onJoystickStateChange,
+ OverlayJoystick.LEFT,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.LEFT_AXIS)
+ ));
+
+ if (nativeControllerType == NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE) {
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_c_pressed,
+ R.drawable.button_c,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.C,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.C)
+ ));
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_z_pressed,
+ R.drawable.button_z,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.Z,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.Z)
+ ));
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_home_pressed,
+ R.drawable.button_home,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.HOME,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.HOME)
+ ));
+ }
+ if (nativeControllerType != NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC
+ && nativeControllerType != NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE) {
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_stick_pressed,
+ R.drawable.button_stick,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.L_STICK,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.L_STICK)
+ ));
+ inputs.add(new RoundButton(
+ resources,
+ R.drawable.button_stick_pressed,
+ R.drawable.button_stick,
+ InputOverlaySurfaceView.this::onButtonStateChange,
+ OverlayButton.R_STICK,
+ getOverlaySettingsForInput(InputOverlaySettingsProvider.Input.R_STICK)
+ ));
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ setWillNotDraw(false);
+ setInputs();
+ requestFocus();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ for (var input : inputs) {
+ input.draw(canvas);
+ }
+ }
+
+ Input currentConfiguredInput = null;
+
+ private boolean onEditPosition(MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN -> {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ for (var input : inputs) {
+ if (input.isInside(x, y)) {
+ currentConfiguredInput = input;
+ input.enableDrawingBoundingRect(getResources().getColor(R.color.purple, getContext().getTheme()));
+ return true;
+ }
+ }
+ }
+ case MotionEvent.ACTION_UP -> {
+ if (currentConfiguredInput != null) {
+ currentConfiguredInput.disableDrawingBoundingRect();
+ currentConfiguredInput = null;
+ return true;
+ }
+ }
+ case MotionEvent.ACTION_MOVE -> {
+ if (currentConfiguredInput != null) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ currentConfiguredInput.moveInput(x, y, getWidth(), getHeight());
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean onEditSize(MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN -> {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ for (var input : inputs) {
+ if (input.isInside(x, y)) {
+ currentConfiguredInput = input;
+ input.enableDrawingBoundingRect(getResources().getColor(R.color.red, getContext().getTheme()));
+ return true;
+ }
+ }
+ }
+ case MotionEvent.ACTION_UP -> {
+ if (currentConfiguredInput != null) {
+ currentConfiguredInput.disableDrawingBoundingRect();
+ currentConfiguredInput = null;
+ return true;
+ }
+ }
+ case MotionEvent.ACTION_MOVE -> {
+ if (currentConfiguredInput != null) {
+ var histSize = event.getHistorySize();
+ if (event.getHistorySize() >= 2) {
+ var x1 = event.getHistoricalX(0);
+ var y1 = event.getHistoricalY(0);
+ var x2 = event.getHistoricalX(histSize - 1);
+ var y2 = event.getHistoricalY(histSize - 1);
+ currentConfiguredInput.resize(
+ (int) (x2 - x1),
+ (int) (y2 - y1),
+ getWidth(),
+ getHeight());
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ boolean touchEventProcessed = false;
+ if (inputMode == DEFAULT) {
+ for (var input : inputs) {
+ if (input.onTouch(event)) touchEventProcessed = true;
+ }
+ } else if (inputMode == InputMode.EDIT_POSITION) {
+ touchEventProcessed = onEditPosition(event);
+ } else if (inputMode == InputMode.EDIT_SIZE) {
+ touchEventProcessed = onEditSize(event);
+ }
+
+ if (touchEventProcessed) {
+ invalidate();
+ }
+
+ return true;
+ }
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/InputSettingsFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/InputSettingsFragment.java
index 885bcb4d..d540e0e2 100644
--- a/src/android/app/src/main/java/info/cemu/Cemu/InputSettingsFragment.java
+++ b/src/android/app/src/main/java/info/cemu/Cemu/InputSettingsFragment.java
@@ -21,6 +21,8 @@ public class InputSettingsFragment extends Fragment {
FragmentInputSettingsBinding binding = FragmentInputSettingsBinding.inflate(inflater, container, false);
GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter();
+ genericRecyclerViewAdapter.addRecyclerViewItem(new SimpleButtonRecyclerViewItem(getString(R.string.input_overlay_settings), () -> NavHostFragment.findNavController(InputSettingsFragment.this).navigate(R.id.action_inputSettingsFragment_to_inputOverlaySettingsFragment)));
+
for (int index = 0; index < NativeLibrary.MAX_CONTROLLERS; index++) {
int controllerIndex = index;
int controllerType = NativeLibrary.isControllerDisabled(controllerIndex) ? NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED : NativeLibrary.getControllerType(controllerIndex);
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/Joystick.java b/src/android/app/src/main/java/info/cemu/Cemu/Joystick.java
new file mode 100644
index 00000000..190de4f7
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/Joystick.java
@@ -0,0 +1,130 @@
+package info.cemu.Cemu;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.view.MotionEvent;
+
+import androidx.annotation.DrawableRes;
+import androidx.core.content.res.ResourcesCompat;
+
+import java.util.Objects;
+
+public class Joystick extends Input {
+ private final Drawable iconPressed;
+ private final Drawable iconNotPressed;
+ private final Drawable joystickBackground;
+ private Drawable icon;
+ private int currentPointerId = -1;
+ private int centreX = 0;
+ private int centreY = 0;
+ private int radius = 0;
+ private int innerRadius = 0;
+
+ public interface StickStateChangeListener {
+ void onStickStateChange(InputOverlaySurfaceView.OverlayJoystick joystick, float x, float y);
+ }
+
+ private final StickStateChangeListener stickStateChangeListener;
+ private final InputOverlaySurfaceView.OverlayJoystick joystick;
+
+ public Joystick(Resources resources, @DrawableRes int joystickBackgroundId, @DrawableRes int pressedStickId, @DrawableRes int notPressedStickId, StickStateChangeListener stickStateChangeListener, InputOverlaySurfaceView.OverlayJoystick joystick, InputOverlaySettingsProvider.InputOverlaySettings settings) {
+ super(settings);
+ joystickBackground = Objects.requireNonNull(ResourcesCompat.getDrawable(resources, joystickBackgroundId, null));
+ iconPressed = Objects.requireNonNull(ResourcesCompat.getDrawable(resources, pressedStickId, null));
+ iconNotPressed = Objects.requireNonNull(ResourcesCompat.getDrawable(resources, notPressedStickId, null));
+ icon = iconNotPressed;
+ this.stickStateChangeListener = stickStateChangeListener;
+ this.joystick = joystick;
+ configure();
+ }
+
+ private void updateState(boolean pressed, float x, float y) {
+ if (pressed) {
+ icon = iconPressed;
+ } else {
+ icon = iconNotPressed;
+ }
+ stickStateChangeListener.onStickStateChange(joystick, x, y);
+ int newCentreX = (int) (centreX + radius * x);
+ int newCentreY = (int) (centreY + radius * y);
+ iconPressed.setBounds(newCentreX - innerRadius, newCentreY - innerRadius, newCentreX + innerRadius, newCentreY + innerRadius);
+ }
+
+ @Override
+ protected void configure() {
+ var rect = settings.getRect();
+ this.centreX = rect.centerX();
+ this.centreY = rect.centerY();
+ this.radius = Math.min(rect.width(), rect.height()) / 2;
+ this.innerRadius = (int) (radius * 0.65f);
+ joystickBackground.setBounds(centreX - radius, centreY - radius, centreX + radius, centreY + radius);
+ joystickBackground.setAlpha(settings.getAlpha());
+ iconPressed.setAlpha(settings.getAlpha());
+ iconPressed.setBounds(centreX - innerRadius, centreY - innerRadius, centreX + innerRadius, centreY + innerRadius);
+ iconNotPressed.setAlpha(settings.getAlpha());
+ iconNotPressed.setBounds(centreX - innerRadius, centreY - innerRadius, centreX + innerRadius, centreY + innerRadius);
+ }
+
+ @Override
+ public boolean onTouch(MotionEvent event) {
+ boolean stateUpdated = false;
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
+ int pointerIndex = event.getActionIndex();
+ int x = (int) event.getX(pointerIndex);
+ int y = (int) event.getY(pointerIndex);
+ if (isInside(x, y)) {
+ currentPointerId = event.getPointerId(pointerIndex);
+ updateState(true, 0.0f, 0.0f);
+ stateUpdated = true;
+ }
+ }
+ case MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
+ if (currentPointerId == event.getPointerId(event.getActionIndex())) {
+ currentPointerId = -1;
+ updateState(false, 0.0f, 0.0f);
+ stateUpdated = true;
+ }
+ }
+ case MotionEvent.ACTION_MOVE -> {
+ if (currentPointerId == -1) {
+ break;
+ }
+ for (int i = 0; i < event.getPointerCount(); i++) {
+ if (currentPointerId != event.getPointerId(i)) {
+ continue;
+ }
+ float x = (event.getX(i) - centreX) / radius;
+ float y = (event.getY(i) - centreY) / radius;
+ float norm = (float) Math.sqrt(x * x + y * y);
+ if (norm > 1.0f) {
+ x /= norm;
+ y /= norm;
+ }
+ updateState(true, x, y);
+ stateUpdated = true;
+ break;
+ }
+ }
+ }
+ return stateUpdated;
+ }
+
+ @Override
+ protected void resetInput() {
+ updateState(false, 0, 0);
+ }
+
+ @Override
+ protected void drawInput(Canvas canvas) {
+ joystickBackground.draw(canvas);
+ icon.draw(canvas);
+ }
+
+ @Override
+ public boolean isInside(int x, int y) {
+ return ((x - centreX) * (x - centreX) + (y - centreY) * (y - centreY)) <= radius * radius;
+ }
+
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/MainActivity.java b/src/android/app/src/main/java/info/cemu/Cemu/MainActivity.java
index 8b5ae856..9d0e9915 100644
--- a/src/android/app/src/main/java/info/cemu/Cemu/MainActivity.java
+++ b/src/android/app/src/main/java/info/cemu/Cemu/MainActivity.java
@@ -16,7 +16,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(binding.getRoot());
binding.fab.setOnClickListener(
view -> {
- Intent intent = new Intent(this, SettingsActivity.class);
+ Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
startActivity(intent);
}
);
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java b/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java
index 5475474f..0aa1e735 100644
--- a/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java
+++ b/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java
@@ -379,4 +379,8 @@ public class NativeLibrary {
public static native void setGraphicPackActivePreset(long id, String category, String preset);
public static native ArrayList getGraphicPackPresets(long id);
+
+ public static native void onOverlayButton(int controllerIndex, int mappingId, boolean value);
+
+ public static native void onOverlayAxis(int controllerIndex, int mappingId, float value);
}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/ProgressResponseBody.java b/src/android/app/src/main/java/info/cemu/Cemu/ProgressResponseBody.java
new file mode 100644
index 00000000..d3bf43f3
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/ProgressResponseBody.java
@@ -0,0 +1,66 @@
+package info.cemu.Cemu;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+
+import okhttp3.MediaType;
+import okhttp3.ResponseBody;
+import okio.Buffer;
+import okio.BufferedSource;
+import okio.ForwardingSource;
+import okio.Okio;
+import okio.Source;
+
+class ProgressResponseBody extends ResponseBody {
+
+ private final ResponseBody responseBody;
+ private final ProgressListener progressListener;
+ private BufferedSource bufferedSource;
+
+ ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
+ this.responseBody = responseBody;
+ this.progressListener = progressListener;
+ }
+
+ @Override
+ public MediaType contentType() {
+ return responseBody.contentType();
+ }
+
+ @Override
+ public long contentLength() {
+ return responseBody.contentLength();
+ }
+
+ @NonNull
+ @Override
+ public BufferedSource source() {
+ if (bufferedSource == null) {
+ bufferedSource = Okio.buffer(source(responseBody.source()));
+ }
+ return bufferedSource;
+ }
+
+ private Source source(Source source) {
+ return new ForwardingSource(source) {
+ long totalBytesRead = 0L;
+
+ @Override
+ public long read(@NonNull Buffer sink, long byteCount) throws IOException {
+ long bytesRead = super.read(sink, byteCount);
+ sink.flush();
+ if (bytesRead == -1)
+ return 100;
+ totalBytesRead += bytesRead;
+ progressListener.update((int) (100f * totalBytesRead / responseBody.contentLength()));
+ return bytesRead;
+ }
+ };
+ }
+
+ public interface ProgressListener {
+ void update(int progress);
+ }
+}
+
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/RectangleButton.java b/src/android/app/src/main/java/info/cemu/Cemu/RectangleButton.java
new file mode 100644
index 00000000..58a0d8a9
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/RectangleButton.java
@@ -0,0 +1,34 @@
+package info.cemu.Cemu;
+
+import android.content.res.Resources;
+
+import androidx.annotation.DrawableRes;
+
+public class RectangleButton extends Button {
+ int left;
+ int top;
+ int right;
+ int bottom;
+
+ public RectangleButton(Resources resources, @DrawableRes int pressedButtonId, @DrawableRes int notPressedButtonId, ButtonStateChangeListener buttonStateChangeListener, InputOverlaySurfaceView.OverlayButton overlayButton, InputOverlaySettingsProvider.InputOverlaySettings settings) {
+ super(resources, pressedButtonId, notPressedButtonId, buttonStateChangeListener, overlayButton, settings);
+ configure();
+ }
+
+ @Override
+ protected void configure() {
+ var rect = settings.getRect();
+ this.left = rect.left;
+ this.top = rect.top;
+ this.right = rect.right;
+ this.bottom = rect.bottom;
+ iconPressed.setBounds(left, top, right, bottom);
+ iconNotPressed.setBounds(left, top, right, bottom);
+ }
+
+ @Override
+ public boolean isInside(int x, int y) {
+ return x >= left && x < right && y >= top && y < bottom;
+ }
+
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/RoundButton.java b/src/android/app/src/main/java/info/cemu/Cemu/RoundButton.java
new file mode 100644
index 00000000..fb98bc4c
--- /dev/null
+++ b/src/android/app/src/main/java/info/cemu/Cemu/RoundButton.java
@@ -0,0 +1,38 @@
+package info.cemu.Cemu;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+
+import androidx.annotation.DrawableRes;
+
+public class RoundButton extends Button {
+ int centreX;
+ int centreY;
+ int radius;
+
+ RoundButton(Resources resources, @DrawableRes int pressedButtonId, @DrawableRes int notPressedButtonId, ButtonStateChangeListener buttonStateChangeListener, InputOverlaySurfaceView.OverlayButton overlayButton, InputOverlaySettingsProvider.InputOverlaySettings settings) {
+ super(resources, pressedButtonId, notPressedButtonId, buttonStateChangeListener, overlayButton, settings);
+ configure();
+ }
+
+ @Override
+ protected void configure() {
+ var rect = settings.getRect();
+ centreX = rect.centerX();
+ centreY = rect.centerY();
+ radius = Math.min(rect.width(), rect.height()) / 2;
+ var iconRect = new Rect(
+ centreX - radius,
+ centreY - radius,
+ centreX + radius,
+ centreY + radius);
+ iconPressed.setBounds(iconRect);
+ iconNotPressed.setBounds(iconRect);
+ }
+
+ @Override
+ public boolean isInside(int x, int y) {
+ return ((x - centreX) * (x - centreX) + (y - centreY) * (y - centreY)) <= radius * radius;
+ }
+
+}
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/SelectionAdapter.java b/src/android/app/src/main/java/info/cemu/Cemu/SelectionAdapter.java
index f98f4eaf..12b320a7 100644
--- a/src/android/app/src/main/java/info/cemu/Cemu/SelectionAdapter.java
+++ b/src/android/app/src/main/java/info/cemu/Cemu/SelectionAdapter.java
@@ -4,21 +4,26 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import androidx.annotation.StringRes;
import com.google.android.material.radiobutton.MaterialRadioButton;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalInt;
+import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.IntStream;
public class SelectionAdapter extends BaseSelectionAdapter {
public static class ChoiceItem {
- int choiceTextResourceId;
+ Consumer setTextForChoice;
T choiceValue;
- public ChoiceItem(int choiceTextResourceId, T choiceValue) {
- this.choiceTextResourceId = choiceTextResourceId;
+ public ChoiceItem(Consumer setTextForChoice, T choiceValue) {
+ this.setTextForChoice = setTextForChoice;
this.choiceValue = choiceValue;
}
}
@@ -50,7 +55,7 @@ public class SelectionAdapter extends BaseSelectionAdapter {
@Override
protected void setRadioButtonText(MaterialRadioButton radioButton, int position) {
- radioButton.setText(choiceItems.get(position).choiceTextResourceId);
+ choiceItems.get(position).setTextForChoice.accept(radioButton);
}
@Override
diff --git a/src/android/app/src/main/java/info/cemu/Cemu/SingleSelectionRecyclerViewItem.java b/src/android/app/src/main/java/info/cemu/Cemu/SingleSelectionRecyclerViewItem.java
index b4aa8fa6..bb8e4b65 100644
--- a/src/android/app/src/main/java/info/cemu/Cemu/SingleSelectionRecyclerViewItem.java
+++ b/src/android/app/src/main/java/info/cemu/Cemu/SingleSelectionRecyclerViewItem.java
@@ -5,6 +5,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@@ -31,6 +32,7 @@ public class SingleSelectionRecyclerViewItem implements RecyclerViewItem {
private final OnItemSelectedListener onItemSelectedListener;
private SingleSelectionViewHolder singleSelectionViewHolder;
private RecyclerView.Adapter recyclerViewAdapter;
+ private AlertDialog selectAlertDialog;
public SingleSelectionRecyclerViewItem(String label, String description, BaseSelectionAdapter selectionAdapter, OnItemSelectedListener onItemSelectedListener) {
this.label = label;
@@ -56,16 +58,20 @@ public class SingleSelectionRecyclerViewItem implements RecyclerViewItem {
singleSelectionViewHolder.label.setText(label);
singleSelectionViewHolder.description.setText(description);
singleSelectionViewHolder.itemView.setOnClickListener(view -> {
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(viewHolder.itemView.getContext());
- builder.setTitle(label).setAdapter(selectionAdapter, (dialogInterface, position) -> {
- if (!selectionAdapter.isEnabled(position))
- return;
- T selectedValue = selectionAdapter.getItem(position);
- selectionAdapter.setSelectedValue(selectedValue);
- if (onItemSelectedListener != null)
- onItemSelectedListener.onItemSelected(selectedValue, SingleSelectionRecyclerViewItem.this);
+ if (selectAlertDialog == null) {
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(viewHolder.itemView.getContext());
+ selectAlertDialog = builder.setTitle(label).setAdapter(selectionAdapter, (dialogInterface, position) -> {
+ if (!selectionAdapter.isEnabled(position))
+ return;
+ T selectedValue = selectionAdapter.getItem(position);
+ selectionAdapter.setSelectedValue(selectedValue);
+ if (onItemSelectedListener != null)
+ onItemSelectedListener.onItemSelected(selectedValue, SingleSelectionRecyclerViewItem.this);
- }).setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.dismiss()).show();
+ }).setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.dismiss()).show();
+ return;
+ }
+ selectAlertDialog.show();
});
}
}
diff --git a/src/android/app/src/main/res/drawable/button_a.xml b/src/android/app/src/main/res/drawable/button_a.xml
new file mode 100644
index 00000000..d81250c5
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_a.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_a_pressed.xml b/src/android/app/src/main/res/drawable/button_a_pressed.xml
new file mode 100644
index 00000000..828399af
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_a_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_b.xml b/src/android/app/src/main/res/drawable/button_b.xml
new file mode 100644
index 00000000..f16c1506
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_b.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_b_pressed.xml b/src/android/app/src/main/res/drawable/button_b_pressed.xml
new file mode 100644
index 00000000..4f70709b
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_b_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_c.xml b/src/android/app/src/main/res/drawable/button_c.xml
new file mode 100644
index 00000000..f80f90be
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_c.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_c_pressed.xml b/src/android/app/src/main/res/drawable/button_c_pressed.xml
new file mode 100644
index 00000000..6a46d539
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_c_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_home.xml b/src/android/app/src/main/res/drawable/button_home.xml
new file mode 100644
index 00000000..f519e6d7
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_home.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_home_pressed.xml b/src/android/app/src/main/res/drawable/button_home_pressed.xml
new file mode 100644
index 00000000..3d9f52a6
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_home_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_l.xml b/src/android/app/src/main/res/drawable/button_l.xml
new file mode 100644
index 00000000..a6b54c61
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_l.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_l_pressed.xml b/src/android/app/src/main/res/drawable/button_l_pressed.xml
new file mode 100644
index 00000000..a1e99d4b
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_l_pressed.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_minus.xml b/src/android/app/src/main/res/drawable/button_minus.xml
new file mode 100644
index 00000000..de1b123a
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_minus.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_minus_pressed.xml b/src/android/app/src/main/res/drawable/button_minus_pressed.xml
new file mode 100644
index 00000000..6b0f46b7
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_minus_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_one.xml b/src/android/app/src/main/res/drawable/button_one.xml
new file mode 100644
index 00000000..c7bdc4a0
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_one.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_one_pressed.xml b/src/android/app/src/main/res/drawable/button_one_pressed.xml
new file mode 100644
index 00000000..372c9688
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_one_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_plus.xml b/src/android/app/src/main/res/drawable/button_plus.xml
new file mode 100644
index 00000000..00a892ee
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_plus.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_plus_pressed.xml b/src/android/app/src/main/res/drawable/button_plus_pressed.xml
new file mode 100644
index 00000000..e0d72814
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_plus_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_r.xml b/src/android/app/src/main/res/drawable/button_r.xml
new file mode 100644
index 00000000..4ac99b4e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_r.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_r_pressed.xml b/src/android/app/src/main/res/drawable/button_r_pressed.xml
new file mode 100644
index 00000000..08c958f6
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_r_pressed.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_stick.xml b/src/android/app/src/main/res/drawable/button_stick.xml
new file mode 100644
index 00000000..684e43a2
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_stick.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_stick_pressed.xml b/src/android/app/src/main/res/drawable/button_stick_pressed.xml
new file mode 100644
index 00000000..432203b8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_stick_pressed.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_two.xml b/src/android/app/src/main/res/drawable/button_two.xml
new file mode 100644
index 00000000..280ecb41
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_two.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_two_pressed.xml b/src/android/app/src/main/res/drawable/button_two_pressed.xml
new file mode 100644
index 00000000..099f1d47
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_two_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_x.xml b/src/android/app/src/main/res/drawable/button_x.xml
new file mode 100644
index 00000000..9410fd0a
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_x.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_x_pressed.xml b/src/android/app/src/main/res/drawable/button_x_pressed.xml
new file mode 100644
index 00000000..9b97d645
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_x_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_y.xml b/src/android/app/src/main/res/drawable/button_y.xml
new file mode 100644
index 00000000..ccfabb62
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_y.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_y_pressed.xml b/src/android/app/src/main/res/drawable/button_y_pressed.xml
new file mode 100644
index 00000000..3ea23f18
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_y_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_z.xml b/src/android/app/src/main/res/drawable/button_z.xml
new file mode 100644
index 00000000..f509ecff
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_z.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_z_pressed.xml b/src/android/app/src/main/res/drawable/button_z_pressed.xml
new file mode 100644
index 00000000..7c682cd9
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_z_pressed.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_zl.xml b/src/android/app/src/main/res/drawable/button_zl.xml
new file mode 100644
index 00000000..754fc47e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_zl.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_zl_pressed.xml b/src/android/app/src/main/res/drawable/button_zl_pressed.xml
new file mode 100644
index 00000000..3f57b428
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_zl_pressed.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_zr.xml b/src/android/app/src/main/res/drawable/button_zr.xml
new file mode 100644
index 00000000..03ef5b9d
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_zr.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/button_zr_pressed.xml b/src/android/app/src/main/res/drawable/button_zr_pressed.xml
new file mode 100644
index 00000000..44df81d6
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_zr_pressed.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/dpad_background.xml b/src/android/app/src/main/res/drawable/dpad_background.xml
new file mode 100644
index 00000000..bbe39bb9
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/dpad_background.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/src/android/app/src/main/res/drawable/dpad_button.xml b/src/android/app/src/main/res/drawable/dpad_button.xml
new file mode 100644
index 00000000..e4e49338
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/dpad_button.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/src/android/app/src/main/res/drawable/dpad_button_pressed.xml b/src/android/app/src/main/res/drawable/dpad_button_pressed.xml
new file mode 100644
index 00000000..186a63f6
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/dpad_button_pressed.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/src/android/app/src/main/res/drawable/ic_restart.xml b/src/android/app/src/main/res/drawable/ic_restart.xml
new file mode 100644
index 00000000..dbd38d71
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_restart.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/android/app/src/main/res/drawable/stick_background.xml b/src/android/app/src/main/res/drawable/stick_background.xml
new file mode 100644
index 00000000..347c1b3d
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/stick_background.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/src/android/app/src/main/res/drawable/stick_inner.xml b/src/android/app/src/main/res/drawable/stick_inner.xml
new file mode 100644
index 00000000..39e76644
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/stick_inner.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/src/android/app/src/main/res/drawable/stick_inner_pressed.xml b/src/android/app/src/main/res/drawable/stick_inner_pressed.xml
new file mode 100644
index 00000000..0c5f7c96
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/stick_inner_pressed.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/src/android/app/src/main/res/layout/activity_overlay_configure.xml b/src/android/app/src/main/res/layout/activity_overlay_configure.xml
new file mode 100644
index 00000000..aba9b093
--- /dev/null
+++ b/src/android/app/src/main/res/layout/activity_overlay_configure.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml
index ca69352a..0fd8d7f3 100644
--- a/src/android/app/src/main/res/layout/fragment_emulation.xml
+++ b/src/android/app/src/main/res/layout/fragment_emulation.xml
@@ -1,5 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/src/android/app/src/main/res/layout/fragment_input_overlay_settings.xml b/src/android/app/src/main/res/layout/fragment_input_overlay_settings.xml
new file mode 100644
index 00000000..140d500a
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_input_overlay_settings.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/android/app/src/main/res/layout/layout_single_selection_item.xml b/src/android/app/src/main/res/layout/layout_single_selection_item.xml
index d117a543..d1441a9c 100644
--- a/src/android/app/src/main/res/layout/layout_single_selection_item.xml
+++ b/src/android/app/src/main/res/layout/layout_single_selection_item.xml
@@ -1,10 +1,16 @@
-
\ No newline at end of file
+ android:layout_height="wrap_content">
+
+
+
diff --git a/src/android/app/src/main/res/navigation/nav_settings_graph.xml b/src/android/app/src/main/res/navigation/nav_settings_graph.xml
index 6aa3bd79..02c437c2 100644
--- a/src/android/app/src/main/res/navigation/nav_settings_graph.xml
+++ b/src/android/app/src/main/res/navigation/nav_settings_graph.xml
@@ -12,6 +12,9 @@
+
+
\ No newline at end of file
diff --git a/src/android/app/src/main/res/values/colors.xml b/src/android/app/src/main/res/values/colors.xml
index c8524cd9..1013fe28 100644
--- a/src/android/app/src/main/res/values/colors.xml
+++ b/src/android/app/src/main/res/values/colors.xml
@@ -2,4 +2,6 @@
#FF000000
#FFFFFFFF
+ #FFFF0000
+ #FFA020F0
\ No newline at end of file
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index b01aa423..b14d0aa0 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -112,4 +112,22 @@
This graphic pack has no description
Graphic pack icon
Search graphic packs
+ Edit input size
+ Edit input positions
+ Exited input edit mode
+ Change input mode to move/resize or default
+
+ Hello blank fragment
+ info.cemu.Cemu.InputOverlaySettings
+ Overlay controller
+ Input overlay
+ Input overlay settings
+ Enable input overlay
+ Inputs opacity
+ Change the input mode to move position or resize
+ Configure the positions/sizes of the inputs
+ Configure inputs
+ Reset the positions and sizes of the inputs
+ Vibrate
+ Enable vibrate on touch
\ No newline at end of file
diff --git a/src/input/emulated/EmulatedController.cpp b/src/input/emulated/EmulatedController.cpp
index e254db34..2a57468e 100644
--- a/src/input/emulated/EmulatedController.cpp
+++ b/src/input/emulated/EmulatedController.cpp
@@ -252,6 +252,12 @@ void EmulatedController::clear_controllers()
float EmulatedController::get_axis_value(uint64 mapping) const
{
+ const auto overridenAxisMappingIt = m_overridenAxisMappings.find(mapping);
+ if (overridenAxisMappingIt != m_overridenAxisMappings.cend() && overridenAxisMappingIt->second != 0.0f)
+ {
+ return overridenAxisMappingIt->second;
+ }
+
const auto it = m_mappings.find(mapping);
if (it != m_mappings.cend())
{
@@ -263,8 +269,26 @@ float EmulatedController::get_axis_value(uint64 mapping) const
return 0;
}
+void EmulatedController::setButtonValue(uint64 mapping, bool value)
+{
+ std::shared_lock lock(m_mutex);
+ m_overridenButtonMappings[mapping] = value;
+
+}
+void EmulatedController::setAxisValue(uint64 mapping, float value)
+{
+ std::shared_lock lock(m_mutex);
+ m_overridenAxisMappings[mapping] = value;
+}
+
bool EmulatedController::is_mapping_down(uint64 mapping) const
{
+ const auto overridenButtonMappingIt = m_overridenButtonMappings.find(mapping);
+ if (overridenButtonMappingIt != m_overridenButtonMappings.cend() && overridenButtonMappingIt->second)
+ {
+ return true;
+ }
+
const auto it = m_mappings.find(mapping);
if (it != m_mappings.cend())
{
diff --git a/src/input/emulated/EmulatedController.h b/src/input/emulated/EmulatedController.h
index b7bd8c6d..6ac988c2 100644
--- a/src/input/emulated/EmulatedController.h
+++ b/src/input/emulated/EmulatedController.h
@@ -100,7 +100,8 @@ public:
bool was_home_button_down() { return std::exchange(m_homebutton_down, false); }
virtual bool set_default_mapping(const std::shared_ptr& controller) { return false; }
-
+ void setButtonValue(uint64 mapping, bool value);
+ void setAxisValue(uint64 mapping, float value);
protected:
size_t m_player_index;
std::string m_profile_name = "default";
@@ -117,7 +118,9 @@ protected:
uint64 button;
};
std::unordered_map m_mappings;
-
+ std::unordered_map m_overridenButtonMappings;
+ std::unordered_map m_overridenAxisMappings;
+
bool m_homebutton_down = false;
};