mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Added input overlay
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
android:name="info.cemu.Cemu.CemuApplication"
|
||||
android:allowBackup="true"
|
||||
@@ -20,7 +22,14 @@
|
||||
android:name=".EmulationActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:screenOrientation="userLandscape" />
|
||||
android:screenOrientation="userLandscape"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
<activity
|
||||
android:name=".InputOverlayConfigureActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".SettingsActivity"
|
||||
android:screenOrientation="userLandscape"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
|
||||
@@ -25,6 +25,18 @@ class AndroidEmulatedController {
|
||||
controller = std::unique_ptr<AndroidEmulatedController>(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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<Integer> 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<Integer> gamepadChannelsSelectionAdapter = new SelectionAdapter<>(gamepadChannelsChoices, gamepadChannels);
|
||||
SingleSelectionRecyclerViewItem<Integer> gamepadChannelsModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.gamepad_channels),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
public interface ButtonStateChangeListener {
|
||||
void onButtonStateChange(InputOverlaySurfaceView.OverlayButton button, boolean state);
|
||||
}
|
||||
@@ -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<Drawable> getPressedButtonIcon = () -> Objects.requireNonNull(ResourcesCompat.getDrawable(resources, pressedButtonId, null));
|
||||
Supplier<Drawable> 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);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public class EmulatedControllerTypeAdapter extends SelectionAdapter<Integer> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Integer> vsyncSelectionAdapter = new SelectionAdapter<>(vsyncChoices, vsyncMode);
|
||||
SingleSelectionRecyclerViewItem<Integer> vsyncModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.vsync),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Integer> 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<Integer> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<Rect> 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<OverlaySettings> overlaySettingsConsumer;
|
||||
|
||||
private OverlaySettings(boolean vibrateOnTouchEnabled, boolean overlayEnabled, int controllerIndex, int alpha, Consumer<OverlaySettings> 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<InputOverlaySettings> inputOverlaySettingsConsumer;
|
||||
private final int alpha;
|
||||
|
||||
private InputOverlaySettings(Rect rectangle, int alpha, Consumer<InputOverlaySettings> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -379,4 +379,8 @@ public class NativeLibrary {
|
||||
public static native void setGraphicPackActivePreset(long id, String category, String preset);
|
||||
|
||||
public static native ArrayList<GraphicPackPreset> 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);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user