mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
refactor android input code
This commit is contained in:
@@ -34,6 +34,7 @@ import info.cemu.cemu.R
|
||||
@Composable
|
||||
fun ScreenContent(
|
||||
appBarText: String,
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
navigateBack: () -> Unit,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
contentModifier: Modifier = Modifier.padding(8.dp),
|
||||
@@ -43,6 +44,7 @@ fun ScreenContent(
|
||||
) {
|
||||
ScreenContentGeneric(
|
||||
appBarTitle = { DefaultAppBarTitle(appBarText) },
|
||||
snackbarHost = snackbarHost,
|
||||
navigateBack = navigateBack,
|
||||
actions = actions,
|
||||
) {
|
||||
|
||||
@@ -4,9 +4,14 @@ import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import info.cemu.cemu.nativeinterface.NativeInput
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.VPadButtons
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.ProControllerButtons
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.ClassicControllerButtons
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.WiimoteButtons
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.onNativeAxis
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.onNativeKey
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.setControllerMapping
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
class InputManager {
|
||||
@@ -40,13 +45,14 @@ class InputManager {
|
||||
}
|
||||
|
||||
private fun isMotionEventFromJoystickOrGamepad(event: MotionEvent): Boolean {
|
||||
return (event.source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (event.source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
|
||||
return (event.source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
|
||||
|| (event.source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
|
||||
}
|
||||
|
||||
fun mapMotionEventToMappingId(
|
||||
fun tryMapMotionEventToMappingId(
|
||||
controllerIndex: Int,
|
||||
mappingId: Int,
|
||||
event: MotionEvent
|
||||
event: MotionEvent,
|
||||
): Boolean {
|
||||
if (!isMotionEventFromJoystickOrGamepad(event)) {
|
||||
return false
|
||||
@@ -127,7 +133,7 @@ class InputManager {
|
||||
return true
|
||||
}
|
||||
|
||||
fun mapKeyEventToMappingId(controllerIndex: Int, mappingId: Int, event: KeyEvent): Boolean {
|
||||
fun mapKeyEventToMappingId(controllerIndex: Int, mappingId: Int, event: KeyEvent) {
|
||||
val device = event.device
|
||||
setControllerMapping(
|
||||
device.descriptor,
|
||||
@@ -136,7 +142,263 @@ class InputManager {
|
||||
mappingId,
|
||||
event.keyCode
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun mapAxisCodeToMappingId(
|
||||
controllerIndex: Int,
|
||||
mappingId: Int,
|
||||
deviceDescriptor: String,
|
||||
deviceName: String,
|
||||
axisCode: Int,
|
||||
isPositive: Boolean,
|
||||
) {
|
||||
setControllerMapping(
|
||||
deviceDescriptor,
|
||||
deviceName,
|
||||
controllerIndex,
|
||||
mappingId,
|
||||
getNativeAxisKey(axisCode, isPositive)
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapKeyCodeToMappingId(
|
||||
controllerIndex: Int,
|
||||
mappingId: Int,
|
||||
deviceDescriptor: String,
|
||||
deviceName: String,
|
||||
keyCode: Int,
|
||||
) {
|
||||
setControllerMapping(
|
||||
deviceDescriptor,
|
||||
deviceName,
|
||||
controllerIndex,
|
||||
mappingId,
|
||||
keyCode
|
||||
)
|
||||
}
|
||||
|
||||
private fun InputDevice.isGameController(): Boolean {
|
||||
return !isVirtual && (
|
||||
sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
|
||||
|| sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
|
||||
|| sources and InputDevice.SOURCE_DPAD == InputDevice.SOURCE_DPAD
|
||||
)
|
||||
}
|
||||
|
||||
fun getGameControllers(): List<Pair<String, Int>> {
|
||||
val gameControllers = mutableListOf<Pair<String, Int>>()
|
||||
|
||||
InputDevice.getDeviceIds().forEach { deviceId ->
|
||||
val device = InputDevice.getDevice(deviceId) ?: return@forEach
|
||||
|
||||
if (gameControllers.any { (_, id) -> id == deviceId }) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
if (device.isGameController()) {
|
||||
gameControllers.add(Pair(device.name, deviceId))
|
||||
}
|
||||
}
|
||||
|
||||
return gameControllers
|
||||
}
|
||||
|
||||
fun mapAllInputs(deviceId: Int, controllerIndex: Int) {
|
||||
if (NativeInput.isControllerDisabled(controllerIndex)) {
|
||||
return
|
||||
}
|
||||
val controllerType = NativeInput.getControllerType(controllerIndex)
|
||||
val device = InputDevice.getDevice(deviceId) ?: return
|
||||
|
||||
val inputs = mutableMapOf<InputMapping, Boolean>().apply {
|
||||
val buttonKeyCodes =
|
||||
ButtonInputMapping.entries.map { it.keyCode }.toIntArray()
|
||||
ButtonInputMapping.entries
|
||||
.zip(device.hasKeys(*buttonKeyCodes).toTypedArray())
|
||||
.forEach { (button, hasKey) -> put(button, hasKey) }
|
||||
|
||||
device.motionRanges.forEach { motionRange ->
|
||||
AxisInputMapping.entries.filter {
|
||||
val isPositive = motionRange.min < 0 && !it.isPositive
|
||||
val isNegative = motionRange.max > 0 && it.isPositive
|
||||
(isPositive || isNegative) && it.axisCode == motionRange.axis
|
||||
}.forEach { put(it, true) }
|
||||
}
|
||||
}
|
||||
|
||||
val buttons = NativeInput.getNativeButtonsForControllerType(controllerType)
|
||||
|
||||
for (button in buttons) {
|
||||
val mapping = getButtonMappings(button).firstOrNull { inputs[it] == true }
|
||||
?: FALLBACK_BUTTONS.firstOrNull { inputs[it] == true }
|
||||
if (mapping == null) {
|
||||
continue
|
||||
}
|
||||
inputs[mapping] = false
|
||||
|
||||
val buttonId = button.nativeKeyCode
|
||||
|
||||
if (mapping is ButtonInputMapping) {
|
||||
mapKeyCodeToMappingId(
|
||||
controllerIndex,
|
||||
buttonId,
|
||||
device.descriptor,
|
||||
device.name,
|
||||
mapping.keyCode
|
||||
)
|
||||
}
|
||||
|
||||
if (mapping is AxisInputMapping) {
|
||||
mapAxisCodeToMappingId(
|
||||
controllerIndex,
|
||||
buttonId,
|
||||
device.descriptor,
|
||||
device.name,
|
||||
mapping.axisCode,
|
||||
mapping.isPositive,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getButtonMappings(button: NativeInput.NativeInputButton): Array<InputMapping> {
|
||||
return when (button) {
|
||||
VPadButtons.A,
|
||||
ProControllerButtons.A,
|
||||
ClassicControllerButtons.A,
|
||||
WiimoteButtons.A,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_A)
|
||||
|
||||
VPadButtons.B,
|
||||
ProControllerButtons.B,
|
||||
ClassicControllerButtons.B,
|
||||
WiimoteButtons.B,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_B)
|
||||
|
||||
VPadButtons.X,
|
||||
ProControllerButtons.X,
|
||||
ClassicControllerButtons.X,
|
||||
WiimoteButtons.ONE,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_X)
|
||||
|
||||
VPadButtons.Y,
|
||||
ProControllerButtons.Y,
|
||||
ClassicControllerButtons.Y,
|
||||
WiimoteButtons.TWO,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_Y)
|
||||
|
||||
VPadButtons.L,
|
||||
ProControllerButtons.L,
|
||||
ClassicControllerButtons.L,
|
||||
WiimoteButtons.NUNCHUCK_C,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_L1)
|
||||
|
||||
VPadButtons.R,
|
||||
ProControllerButtons.R,
|
||||
ClassicControllerButtons.R,
|
||||
WiimoteButtons.NUNCHUCK_Z,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_R1)
|
||||
|
||||
VPadButtons.ZL,
|
||||
ProControllerButtons.ZL,
|
||||
ClassicControllerButtons.ZL,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_L2, AxisInputMapping.LTRIGGER)
|
||||
|
||||
VPadButtons.ZR,
|
||||
ProControllerButtons.ZR,
|
||||
ClassicControllerButtons.ZR,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_R2, AxisInputMapping.RTRIGGER)
|
||||
|
||||
VPadButtons.PLUS,
|
||||
ProControllerButtons.PLUS,
|
||||
ClassicControllerButtons.PLUS,
|
||||
WiimoteButtons.PLUS,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_START)
|
||||
|
||||
VPadButtons.MINUS,
|
||||
ProControllerButtons.MINUS,
|
||||
ClassicControllerButtons.MINUS,
|
||||
WiimoteButtons.MINUS,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_SELECT)
|
||||
|
||||
VPadButtons.STICKL_UP,
|
||||
ProControllerButtons.STICKL_UP,
|
||||
ClassicControllerButtons.STICKL_UP,
|
||||
WiimoteButtons.NUNCHUCK_UP,
|
||||
-> arrayOf(AxisInputMapping.Y_NEG)
|
||||
|
||||
VPadButtons.STICKL_DOWN,
|
||||
ProControllerButtons.STICKL_DOWN,
|
||||
ClassicControllerButtons.STICKL_DOWN,
|
||||
WiimoteButtons.NUNCHUCK_DOWN,
|
||||
-> arrayOf(AxisInputMapping.Y_POS)
|
||||
|
||||
VPadButtons.STICKL_LEFT,
|
||||
ProControllerButtons.STICKL_LEFT,
|
||||
ClassicControllerButtons.STICKL_LEFT,
|
||||
WiimoteButtons.NUNCHUCK_LEFT,
|
||||
-> arrayOf(AxisInputMapping.X_NEG)
|
||||
|
||||
VPadButtons.STICKL_RIGHT,
|
||||
ProControllerButtons.STICKL_RIGHT,
|
||||
ClassicControllerButtons.STICKL_RIGHT,
|
||||
WiimoteButtons.NUNCHUCK_RIGHT,
|
||||
-> arrayOf(AxisInputMapping.X_POS)
|
||||
|
||||
VPadButtons.STICKR_UP,
|
||||
ProControllerButtons.STICKR_UP,
|
||||
ClassicControllerButtons.STICKR_UP,
|
||||
-> arrayOf(AxisInputMapping.RY_NEG, AxisInputMapping.RZ_NEG)
|
||||
|
||||
VPadButtons.STICKR_DOWN,
|
||||
ProControllerButtons.STICKR_DOWN,
|
||||
ClassicControllerButtons.STICKR_DOWN,
|
||||
-> arrayOf(AxisInputMapping.RY_POS, AxisInputMapping.RZ_POS)
|
||||
|
||||
VPadButtons.STICKR_LEFT,
|
||||
ProControllerButtons.STICKR_LEFT,
|
||||
ClassicControllerButtons.STICKR_LEFT,
|
||||
-> arrayOf(AxisInputMapping.RX_NEG, AxisInputMapping.Z_NEG)
|
||||
|
||||
VPadButtons.STICKR_RIGHT,
|
||||
ProControllerButtons.STICKR_RIGHT,
|
||||
ClassicControllerButtons.STICKR_RIGHT,
|
||||
-> arrayOf(AxisInputMapping.RX_POS, AxisInputMapping.Z_POS)
|
||||
|
||||
VPadButtons.UP,
|
||||
ProControllerButtons.UP,
|
||||
ClassicControllerButtons.UP,
|
||||
WiimoteButtons.UP,
|
||||
-> arrayOf(AxisInputMapping.HAT_Y_NEG, ButtonInputMapping.DPAD_UP)
|
||||
|
||||
VPadButtons.DOWN,
|
||||
ProControllerButtons.DOWN,
|
||||
ClassicControllerButtons.DOWN,
|
||||
WiimoteButtons.DOWN,
|
||||
-> arrayOf(AxisInputMapping.HAT_Y_POS, ButtonInputMapping.DPAD_DOWN)
|
||||
|
||||
VPadButtons.LEFT,
|
||||
ProControllerButtons.LEFT,
|
||||
ClassicControllerButtons.LEFT,
|
||||
WiimoteButtons.LEFT,
|
||||
-> arrayOf(AxisInputMapping.HAT_X_NEG, ButtonInputMapping.DPAD_LEFT)
|
||||
|
||||
VPadButtons.RIGHT,
|
||||
ProControllerButtons.RIGHT,
|
||||
ClassicControllerButtons.RIGHT,
|
||||
WiimoteButtons.RIGHT,
|
||||
-> arrayOf(AxisInputMapping.HAT_X_POS, ButtonInputMapping.DPAD_RIGHT)
|
||||
|
||||
VPadButtons.STICKL,
|
||||
ProControllerButtons.STICKL,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_THUMBL)
|
||||
|
||||
VPadButtons.STICKR,
|
||||
ProControllerButtons.STICKR,
|
||||
-> arrayOf(ButtonInputMapping.BUTTON_THUMBR)
|
||||
|
||||
else -> arrayOf()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package info.cemu.cemu.input
|
||||
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
|
||||
sealed interface InputMapping
|
||||
|
||||
enum class ButtonInputMapping(val keyCode: Int) : InputMapping {
|
||||
BUTTON_1(KeyEvent.KEYCODE_BUTTON_1),
|
||||
BUTTON_2(KeyEvent.KEYCODE_BUTTON_2),
|
||||
BUTTON_3(KeyEvent.KEYCODE_BUTTON_3),
|
||||
BUTTON_4(KeyEvent.KEYCODE_BUTTON_4),
|
||||
BUTTON_5(KeyEvent.KEYCODE_BUTTON_5),
|
||||
BUTTON_6(KeyEvent.KEYCODE_BUTTON_6),
|
||||
BUTTON_7(KeyEvent.KEYCODE_BUTTON_7),
|
||||
BUTTON_8(KeyEvent.KEYCODE_BUTTON_8),
|
||||
BUTTON_9(KeyEvent.KEYCODE_BUTTON_9),
|
||||
BUTTON_10(KeyEvent.KEYCODE_BUTTON_10),
|
||||
BUTTON_11(KeyEvent.KEYCODE_BUTTON_11),
|
||||
BUTTON_12(KeyEvent.KEYCODE_BUTTON_12),
|
||||
BUTTON_13(KeyEvent.KEYCODE_BUTTON_13),
|
||||
BUTTON_14(KeyEvent.KEYCODE_BUTTON_14),
|
||||
BUTTON_15(KeyEvent.KEYCODE_BUTTON_15),
|
||||
BUTTON_16(KeyEvent.KEYCODE_BUTTON_16),
|
||||
BUTTON_A(KeyEvent.KEYCODE_BUTTON_A),
|
||||
BUTTON_B(KeyEvent.KEYCODE_BUTTON_B),
|
||||
BUTTON_C(KeyEvent.KEYCODE_BUTTON_C),
|
||||
BUTTON_L1(KeyEvent.KEYCODE_BUTTON_L1),
|
||||
BUTTON_L2(KeyEvent.KEYCODE_BUTTON_L2),
|
||||
BUTTON_MODE(KeyEvent.KEYCODE_BUTTON_MODE),
|
||||
BUTTON_R1(KeyEvent.KEYCODE_BUTTON_R1),
|
||||
BUTTON_R2(KeyEvent.KEYCODE_BUTTON_R2),
|
||||
BUTTON_SELECT(KeyEvent.KEYCODE_BUTTON_SELECT),
|
||||
BUTTON_START(KeyEvent.KEYCODE_BUTTON_START),
|
||||
BUTTON_THUMBL(KeyEvent.KEYCODE_BUTTON_THUMBL),
|
||||
BUTTON_THUMBR(KeyEvent.KEYCODE_BUTTON_THUMBR),
|
||||
BUTTON_X(KeyEvent.KEYCODE_BUTTON_X),
|
||||
BUTTON_Y(KeyEvent.KEYCODE_BUTTON_Y),
|
||||
BUTTON_Z(KeyEvent.KEYCODE_BUTTON_Z),
|
||||
DPAD_DOWN(KeyEvent.KEYCODE_DPAD_DOWN),
|
||||
DPAD_LEFT(KeyEvent.KEYCODE_DPAD_LEFT),
|
||||
DPAD_RIGHT(KeyEvent.KEYCODE_DPAD_RIGHT),
|
||||
DPAD_UP(KeyEvent.KEYCODE_DPAD_UP),
|
||||
}
|
||||
|
||||
val FALLBACK_BUTTONS = arrayOf(
|
||||
ButtonInputMapping.BUTTON_1,
|
||||
ButtonInputMapping.BUTTON_2,
|
||||
ButtonInputMapping.BUTTON_3,
|
||||
ButtonInputMapping.BUTTON_4,
|
||||
ButtonInputMapping.BUTTON_5,
|
||||
ButtonInputMapping.BUTTON_6,
|
||||
ButtonInputMapping.BUTTON_7,
|
||||
ButtonInputMapping.BUTTON_8,
|
||||
ButtonInputMapping.BUTTON_9,
|
||||
ButtonInputMapping.BUTTON_10,
|
||||
ButtonInputMapping.BUTTON_11,
|
||||
ButtonInputMapping.BUTTON_12,
|
||||
ButtonInputMapping.BUTTON_13,
|
||||
ButtonInputMapping.BUTTON_14,
|
||||
ButtonInputMapping.BUTTON_15,
|
||||
ButtonInputMapping.BUTTON_16,
|
||||
)
|
||||
|
||||
enum class AxisInputMapping(val axisCode: Int, val isPositive: Boolean) : InputMapping {
|
||||
HAT_X_POS(MotionEvent.AXIS_HAT_X, true),
|
||||
HAT_X_NEG(MotionEvent.AXIS_HAT_X, false),
|
||||
HAT_Y_POS(MotionEvent.AXIS_HAT_Y, true),
|
||||
HAT_Y_NEG(MotionEvent.AXIS_HAT_Y, false),
|
||||
RX_POS(MotionEvent.AXIS_RX, true),
|
||||
RX_NEG(MotionEvent.AXIS_RX, false),
|
||||
RY_POS(MotionEvent.AXIS_RY, true),
|
||||
RY_NEG(MotionEvent.AXIS_RY, false),
|
||||
RZ_POS(MotionEvent.AXIS_RZ, true),
|
||||
RZ_NEG(MotionEvent.AXIS_RZ, false),
|
||||
LTRIGGER(MotionEvent.AXIS_LTRIGGER, true),
|
||||
RTRIGGER(MotionEvent.AXIS_RTRIGGER, true),
|
||||
X_POS(MotionEvent.AXIS_X, true),
|
||||
X_NEG(MotionEvent.AXIS_X, false),
|
||||
Y_POS(MotionEvent.AXIS_Y, true),
|
||||
Y_NEG(MotionEvent.AXIS_Y, false),
|
||||
Z_POS(MotionEvent.AXIS_Z, true),
|
||||
Z_NEG(MotionEvent.AXIS_Z, false),
|
||||
}
|
||||
@@ -42,7 +42,7 @@ fun String.fromNativePath(): Uri {
|
||||
@Keep
|
||||
fun openContentUri(uri: String): Int {
|
||||
try {
|
||||
Log.i("NATIVE_FILES", "openContentUri $uri")
|
||||
// Log.i("NATIVE_FILES", "openContentUri $uri")
|
||||
val parcelFileDescriptor =
|
||||
ContentResolver.openFileDescriptor(
|
||||
uri.fromNativePath(), MODE
|
||||
@@ -60,7 +60,7 @@ fun openContentUri(uri: String): Int {
|
||||
|
||||
@Keep
|
||||
fun listFiles(uri: String): Array<String?> {
|
||||
Log.i("NATIVE_FILES", "listFiles $uri")
|
||||
//// Log.i("NATIVE_FILES", "listFiles $uri")
|
||||
|
||||
val files = ArrayList<String>()
|
||||
val directoryUri = uri.fromNativePath()
|
||||
@@ -93,7 +93,7 @@ fun listFiles(uri: String): Array<String?> {
|
||||
|
||||
@Keep
|
||||
fun isDirectory(uri: String): Boolean {
|
||||
Log.i("NATIVE_FILES", "isDirectory $uri")
|
||||
// Log.i("NATIVE_FILES", "isDirectory $uri")
|
||||
|
||||
val mimeType = ContentResolver.getType(
|
||||
uri.fromNativePath()
|
||||
@@ -108,7 +108,7 @@ fun isFile(uri: String): Boolean {
|
||||
|
||||
@Keep
|
||||
fun exists(uri: String): Boolean {
|
||||
Log.i("NATIVE_FILES", "exists $uri")
|
||||
// Log.i("NATIVE_FILES", "exists $uri")
|
||||
|
||||
try {
|
||||
ContentResolver.query(uri.fromNativePath(), null, null, null, null).use { cursor ->
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package info.cemu.cemu.nativeinterface
|
||||
|
||||
object NativeInput {
|
||||
sealed interface NativeInputButton {
|
||||
val nativeKeyCode: Int
|
||||
}
|
||||
|
||||
const val VPAD_BUTTON_NONE: Int = 0
|
||||
const val VPAD_BUTTON_A: Int = 1
|
||||
const val VPAD_BUTTON_B: Int = 2
|
||||
@@ -31,6 +35,36 @@ object NativeInput {
|
||||
const val VPAD_BUTTON_HOME: Int = 27
|
||||
const val VPAD_BUTTON_MAX: Int = 28
|
||||
|
||||
enum class VPadButtons(override val nativeKeyCode: Int) : NativeInputButton {
|
||||
A(VPAD_BUTTON_A),
|
||||
B(VPAD_BUTTON_B),
|
||||
X(VPAD_BUTTON_X),
|
||||
Y(VPAD_BUTTON_Y),
|
||||
L(VPAD_BUTTON_L),
|
||||
R(VPAD_BUTTON_R),
|
||||
ZL(VPAD_BUTTON_ZL),
|
||||
ZR(VPAD_BUTTON_ZR),
|
||||
PLUS(VPAD_BUTTON_PLUS),
|
||||
MINUS(VPAD_BUTTON_MINUS),
|
||||
UP(VPAD_BUTTON_UP),
|
||||
DOWN(VPAD_BUTTON_DOWN),
|
||||
LEFT(VPAD_BUTTON_LEFT),
|
||||
RIGHT(VPAD_BUTTON_RIGHT),
|
||||
STICKL(VPAD_BUTTON_STICKL),
|
||||
STICKR(VPAD_BUTTON_STICKR),
|
||||
STICKL_UP(VPAD_BUTTON_STICKL_UP),
|
||||
STICKL_DOWN(VPAD_BUTTON_STICKL_DOWN),
|
||||
STICKL_LEFT(VPAD_BUTTON_STICKL_LEFT),
|
||||
STICKL_RIGHT(VPAD_BUTTON_STICKL_RIGHT),
|
||||
STICKR_UP(VPAD_BUTTON_STICKR_UP),
|
||||
STICKR_DOWN(VPAD_BUTTON_STICKR_DOWN),
|
||||
STICKR_LEFT(VPAD_BUTTON_STICKR_LEFT),
|
||||
STICKR_RIGHT(VPAD_BUTTON_STICKR_RIGHT),
|
||||
MIC(VPAD_BUTTON_MIC),
|
||||
SCREEN(VPAD_BUTTON_SCREEN),
|
||||
HOME(VPAD_BUTTON_HOME),
|
||||
}
|
||||
|
||||
const val PRO_BUTTON_NONE: Int = 0
|
||||
const val PRO_BUTTON_A: Int = 1
|
||||
const val PRO_BUTTON_B: Int = 2
|
||||
@@ -59,6 +93,34 @@ object NativeInput {
|
||||
const val PRO_BUTTON_STICKR_RIGHT: Int = 25
|
||||
const val PRO_BUTTON_MAX: Int = 26
|
||||
|
||||
enum class ProControllerButtons(override val nativeKeyCode: Int) : NativeInputButton {
|
||||
A(PRO_BUTTON_A),
|
||||
B(PRO_BUTTON_B),
|
||||
X(PRO_BUTTON_X),
|
||||
Y(PRO_BUTTON_Y),
|
||||
L(PRO_BUTTON_L),
|
||||
R(PRO_BUTTON_R),
|
||||
ZL(PRO_BUTTON_ZL),
|
||||
ZR(PRO_BUTTON_ZR),
|
||||
PLUS(PRO_BUTTON_PLUS),
|
||||
MINUS(PRO_BUTTON_MINUS),
|
||||
HOME(PRO_BUTTON_HOME),
|
||||
UP(PRO_BUTTON_UP),
|
||||
DOWN(PRO_BUTTON_DOWN),
|
||||
LEFT(PRO_BUTTON_LEFT),
|
||||
RIGHT(PRO_BUTTON_RIGHT),
|
||||
STICKL(PRO_BUTTON_STICKL),
|
||||
STICKR(PRO_BUTTON_STICKR),
|
||||
STICKL_UP(PRO_BUTTON_STICKL_UP),
|
||||
STICKL_DOWN(PRO_BUTTON_STICKL_DOWN),
|
||||
STICKL_LEFT(PRO_BUTTON_STICKL_LEFT),
|
||||
STICKL_RIGHT(PRO_BUTTON_STICKL_RIGHT),
|
||||
STICKR_UP(PRO_BUTTON_STICKR_UP),
|
||||
STICKR_DOWN(PRO_BUTTON_STICKR_DOWN),
|
||||
STICKR_LEFT(PRO_BUTTON_STICKR_LEFT),
|
||||
STICKR_RIGHT(PRO_BUTTON_STICKR_RIGHT),
|
||||
}
|
||||
|
||||
const val CLASSIC_BUTTON_NONE: Int = 0
|
||||
const val CLASSIC_BUTTON_A: Int = 1
|
||||
const val CLASSIC_BUTTON_B: Int = 2
|
||||
@@ -85,6 +147,32 @@ object NativeInput {
|
||||
const val CLASSIC_BUTTON_STICKR_RIGHT: Int = 23
|
||||
const val CLASSIC_BUTTON_MAX: Int = 24
|
||||
|
||||
enum class ClassicControllerButtons(override val nativeKeyCode: Int) : NativeInputButton {
|
||||
A(CLASSIC_BUTTON_A),
|
||||
B(CLASSIC_BUTTON_B),
|
||||
X(CLASSIC_BUTTON_X),
|
||||
Y(CLASSIC_BUTTON_Y),
|
||||
L(CLASSIC_BUTTON_L),
|
||||
R(CLASSIC_BUTTON_R),
|
||||
ZL(CLASSIC_BUTTON_ZL),
|
||||
ZR(CLASSIC_BUTTON_ZR),
|
||||
PLUS(CLASSIC_BUTTON_PLUS),
|
||||
MINUS(CLASSIC_BUTTON_MINUS),
|
||||
HOME(CLASSIC_BUTTON_HOME),
|
||||
UP(CLASSIC_BUTTON_UP),
|
||||
DOWN(CLASSIC_BUTTON_DOWN),
|
||||
LEFT(CLASSIC_BUTTON_LEFT),
|
||||
RIGHT(CLASSIC_BUTTON_RIGHT),
|
||||
STICKL_UP(CLASSIC_BUTTON_STICKL_UP),
|
||||
STICKL_DOWN(CLASSIC_BUTTON_STICKL_DOWN),
|
||||
STICKL_LEFT(CLASSIC_BUTTON_STICKL_LEFT),
|
||||
STICKL_RIGHT(CLASSIC_BUTTON_STICKL_RIGHT),
|
||||
STICKR_UP(CLASSIC_BUTTON_STICKR_UP),
|
||||
STICKR_DOWN(CLASSIC_BUTTON_STICKR_DOWN),
|
||||
STICKR_LEFT(CLASSIC_BUTTON_STICKR_LEFT),
|
||||
STICKR_RIGHT(CLASSIC_BUTTON_STICKR_RIGHT),
|
||||
}
|
||||
|
||||
const val WIIMOTE_BUTTON_NONE: Int = 0
|
||||
const val WIIMOTE_BUTTON_A: Int = 1
|
||||
const val WIIMOTE_BUTTON_B: Int = 2
|
||||
@@ -105,6 +193,36 @@ object NativeInput {
|
||||
const val WIIMOTE_BUTTON_HOME: Int = 17
|
||||
const val WIIMOTE_BUTTON_MAX: Int = 18
|
||||
|
||||
enum class WiimoteButtons(override val nativeKeyCode: Int) : NativeInputButton {
|
||||
A(WIIMOTE_BUTTON_A),
|
||||
B(WIIMOTE_BUTTON_B),
|
||||
ONE(WIIMOTE_BUTTON_1),
|
||||
TWO(WIIMOTE_BUTTON_2),
|
||||
NUNCHUCK_Z(WIIMOTE_BUTTON_NUNCHUCK_Z),
|
||||
NUNCHUCK_C(WIIMOTE_BUTTON_NUNCHUCK_C),
|
||||
PLUS(WIIMOTE_BUTTON_PLUS),
|
||||
MINUS(WIIMOTE_BUTTON_MINUS),
|
||||
UP(WIIMOTE_BUTTON_UP),
|
||||
DOWN(WIIMOTE_BUTTON_DOWN),
|
||||
LEFT(WIIMOTE_BUTTON_LEFT),
|
||||
RIGHT(WIIMOTE_BUTTON_RIGHT),
|
||||
NUNCHUCK_UP(WIIMOTE_BUTTON_NUNCHUCK_UP),
|
||||
NUNCHUCK_DOWN(WIIMOTE_BUTTON_NUNCHUCK_DOWN),
|
||||
NUNCHUCK_LEFT(WIIMOTE_BUTTON_NUNCHUCK_LEFT),
|
||||
NUNCHUCK_RIGHT(WIIMOTE_BUTTON_NUNCHUCK_RIGHT),
|
||||
HOME(WIIMOTE_BUTTON_HOME),
|
||||
}
|
||||
|
||||
fun getNativeButtonsForControllerType(controllerType: Int): Array<NativeInputButton> {
|
||||
return when (controllerType) {
|
||||
EMULATED_CONTROLLER_TYPE_VPAD -> VPadButtons.entries.toTypedArray()
|
||||
EMULATED_CONTROLLER_TYPE_PRO -> ProControllerButtons.entries.toTypedArray()
|
||||
EMULATED_CONTROLLER_TYPE_CLASSIC -> ClassicControllerButtons.entries.toTypedArray()
|
||||
EMULATED_CONTROLLER_TYPE_WIIMOTE -> WiimoteButtons.entries.toTypedArray()
|
||||
else -> arrayOf()
|
||||
}
|
||||
}
|
||||
|
||||
const val EMULATED_CONTROLLER_TYPE_VPAD: Int = 0
|
||||
const val EMULATED_CONTROLLER_TYPE_PRO: Int = 1
|
||||
const val EMULATED_CONTROLLER_TYPE_CLASSIC: Int = 2
|
||||
@@ -135,7 +253,7 @@ object NativeInput {
|
||||
deviceDescriptor: String?,
|
||||
deviceName: String?,
|
||||
key: Int,
|
||||
isPressed: Boolean
|
||||
isPressed: Boolean,
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@@ -143,7 +261,7 @@ object NativeInput {
|
||||
deviceDescriptor: String?,
|
||||
deviceName: String?,
|
||||
axis: Int,
|
||||
value: Float
|
||||
value: Float,
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@@ -175,15 +293,21 @@ object NativeInput {
|
||||
deviceName: String?,
|
||||
index: Int,
|
||||
mappingId: Int,
|
||||
buttonId: Int
|
||||
buttonId: Int,
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
external fun clearControllerMapping(index: Int, mappingId: Int)
|
||||
|
||||
fun clearControllerMapping(index: Int, button: NativeInputButton) =
|
||||
clearControllerMapping(index, button.nativeKeyCode)
|
||||
|
||||
@JvmStatic
|
||||
external fun getControllerMapping(index: Int, mappingId: Int): String
|
||||
|
||||
fun getControllerMapping(index: Int, button: NativeInputButton): String =
|
||||
getControllerMapping(index, button.nativeKeyCode)
|
||||
|
||||
@JvmStatic
|
||||
external fun getControllerMappings(index: Int): Map<Int, String>
|
||||
|
||||
@@ -204,7 +328,7 @@ object NativeInput {
|
||||
gyroZ: Float,
|
||||
accelX: Float,
|
||||
accelY: Float,
|
||||
accelZ: Float
|
||||
accelZ: Float,
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
|
||||
+119
-5
@@ -4,11 +4,34 @@ import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.lifecycle.viewmodel.MutableCreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@@ -17,6 +40,9 @@ import info.cemu.cemu.guicore.components.ScreenContent
|
||||
import info.cemu.cemu.guicore.components.SingleSelection
|
||||
import info.cemu.cemu.guicore.nativeenummapper.controllerTypeToStringId
|
||||
import info.cemu.cemu.nativeinterface.NativeInput
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.material3.Button as MaterialButton
|
||||
|
||||
|
||||
@Composable
|
||||
fun ControllerInputSettingsScreen(
|
||||
@@ -32,21 +58,26 @@ fun ControllerInputSettingsScreen(
|
||||
val context = LocalContext.current
|
||||
val controllerType by controllersViewModel.controllerType.collectAsState()
|
||||
val controls by controllersViewModel.controls.collectAsState()
|
||||
val controllers by controllersViewModel.controllers.collectAsState()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
fun onInputClick(buttonName: String, buttonId: Int) {
|
||||
openInputDialog(
|
||||
context = context,
|
||||
buttonName = buttonName,
|
||||
onClear = { controllersViewModel.clearButtonMapping(buttonId) },
|
||||
tryMapKeyEvent = { controllersViewModel.tryMapKeyEvent(it, buttonId) },
|
||||
mapKeyEvent = { controllersViewModel.mapKeyEvent(it, buttonId) },
|
||||
tryMapMotionEvent = { controllersViewModel.tryMapMotionEvent(it, buttonId) },
|
||||
)
|
||||
}
|
||||
|
||||
ScreenContent(
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
appBarText = stringResource(R.string.controller_numbered, controllerIndex + 1),
|
||||
navigateBack = navigateBack,
|
||||
) {
|
||||
|
||||
SingleSelection(
|
||||
isChoiceEnabled = controllersViewModel::isControllerTypeAllowed,
|
||||
label = stringResource(R.string.emulated_controller),
|
||||
@@ -61,6 +92,34 @@ fun ControllerInputSettingsScreen(
|
||||
choiceToString = { stringResource(controllerTypeToStringId(it)) },
|
||||
onChoiceChanged = controllersViewModel::setControllerType
|
||||
)
|
||||
|
||||
if (controllerType != NativeInput.EMULATED_CONTROLLER_TYPE_DISABLED) {
|
||||
MaterialButton(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
onClick = {
|
||||
controllersViewModel.refreshAvailableControllers {
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.currentSnackbarData?.dismiss()
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.no_controllers_available))
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.controller_setup_all_inputs))
|
||||
}
|
||||
}
|
||||
|
||||
controllers?.let {
|
||||
ControllerSelectDialog(
|
||||
controllers = it,
|
||||
onDismissRequest = controllersViewModel::clearGameControllers,
|
||||
onSelect = { deviceId ->
|
||||
controllersViewModel.mapAllInputs(deviceId)
|
||||
controllersViewModel.clearGameControllers()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
when (controllerType) {
|
||||
NativeInput.EMULATED_CONTROLLER_TYPE_VPAD -> VPADInputs(
|
||||
controllerIndex = controllerIndex,
|
||||
@@ -86,11 +145,67 @@ fun ControllerInputSettingsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ControllerSelectDialog(
|
||||
controllers: List<Pair<String, Int>>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onSelect: (Int) -> Unit,
|
||||
) {
|
||||
Dialog(onDismissRequest = onDismissRequest) {
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
|
||||
modifier = Modifier
|
||||
.sizeIn(maxWidth = 560.dp, maxHeight = 560.dp)
|
||||
.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
top = 16.dp,
|
||||
bottom = 8.dp
|
||||
),
|
||||
text = stringResource(R.string.controller_select_dialog_title),
|
||||
fontSize = 24.sp,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
.weight(weight = 1.0f, fill = false)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
controllers.forEach { (controllerName, deviceId) ->
|
||||
Text(
|
||||
text = controllerName,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onSelect(deviceId) }
|
||||
.padding(vertical = 16.dp, horizontal = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
TextButton(
|
||||
onClick = onDismissRequest,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.align(Alignment.End),
|
||||
) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openInputDialog(
|
||||
context: Context,
|
||||
buttonName: String,
|
||||
onClear: () -> Unit,
|
||||
tryMapKeyEvent: (KeyEvent) -> Boolean,
|
||||
mapKeyEvent: (KeyEvent) -> Unit,
|
||||
tryMapMotionEvent: (MotionEvent) -> Boolean,
|
||||
) {
|
||||
MaterialAlertDialogBuilder(context).setTitle(R.string.inputBindingDialogTitle)
|
||||
@@ -103,9 +218,8 @@ fun openInputDialog(
|
||||
isFocusableInTouchMode = true
|
||||
requestFocus()
|
||||
setOnKeyListener { _, _, keyEvent: KeyEvent ->
|
||||
if (tryMapKeyEvent(keyEvent)) {
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
mapKeyEvent(keyEvent)
|
||||
alertDialog.dismiss()
|
||||
true
|
||||
}
|
||||
setOnGenericMotionListener { _, motionEvent: MotionEvent? ->
|
||||
|
||||
+31
-7
@@ -23,13 +23,15 @@ class ControllersViewModel(val controllerIndex: Int) : ViewModel() {
|
||||
private val _controls = MutableStateFlow<Map<Int, String>>(emptyMap())
|
||||
val controls = _controls.asStateFlow()
|
||||
|
||||
private val _controllers = MutableStateFlow<List<Pair<String, Int>>?>(null)
|
||||
val controllers = _controllers.asStateFlow()
|
||||
|
||||
private var vpadCount = 0
|
||||
private var wpadCount = 0
|
||||
|
||||
private fun getControllerMapping(buttonId: Int) =
|
||||
buttonId to NativeInput.getControllerMapping(controllerIndex, buttonId)
|
||||
|
||||
|
||||
fun setControllerType(controllerType: Int) {
|
||||
if (!isControllerTypeAllowed(controllerType) || _controllerType.value == controllerType)
|
||||
return
|
||||
@@ -38,16 +40,38 @@ class ControllersViewModel(val controllerIndex: Int) : ViewModel() {
|
||||
refreshControllerData()
|
||||
}
|
||||
|
||||
fun tryMapKeyEvent(keyEvent: KeyEvent, buttonId: Int): Boolean {
|
||||
if (inputManager.mapKeyEventToMappingId(controllerIndex, buttonId, keyEvent)) {
|
||||
_controls.value += getControllerMapping(buttonId)
|
||||
return true
|
||||
fun mapKeyEvent(keyEvent: KeyEvent, buttonId: Int) {
|
||||
inputManager.mapKeyEventToMappingId(controllerIndex, buttonId, keyEvent)
|
||||
_controls.value += getControllerMapping(buttonId)
|
||||
}
|
||||
|
||||
fun refreshAvailableControllers(onNoControllersAvailable: () -> Unit) {
|
||||
val newControllers = inputManager.getGameControllers()
|
||||
if (newControllers.isEmpty()) {
|
||||
_controllers.value = null
|
||||
onNoControllersAvailable()
|
||||
return
|
||||
}
|
||||
return false
|
||||
_controllers.value = newControllers
|
||||
}
|
||||
|
||||
fun clearGameControllers() {
|
||||
_controllers.value = null
|
||||
}
|
||||
|
||||
fun mapAllInputs(deviceId: Int) {
|
||||
val oldControls = _controls.value
|
||||
_controls.value = emptyMap()
|
||||
oldControls.keys.forEach { NativeInput.clearControllerMapping(controllerIndex, it) }
|
||||
|
||||
inputManager.mapAllInputs(deviceId, controllerIndex)
|
||||
|
||||
val buttons = NativeInput.getNativeButtonsForControllerType (controllerType.value)
|
||||
buttons.forEach { _controls.value += getControllerMapping(it.nativeKeyCode) }
|
||||
}
|
||||
|
||||
fun tryMapMotionEvent(motionEvent: MotionEvent, buttonId: Int): Boolean {
|
||||
if (inputManager.mapMotionEventToMappingId(controllerIndex, buttonId, motionEvent)) {
|
||||
if (inputManager.tryMapMotionEventToMappingId(controllerIndex, buttonId, motionEvent)) {
|
||||
_controls.value += getControllerMapping(buttonId)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -332,4 +332,7 @@
|
||||
<string name="title_manager_filter_titles_description">Filter titles</string>
|
||||
<string name="title_manager_install_title_description">Install title</string>
|
||||
<string name="title_dropdown_menu_description">Show dropdown menu for title</string>
|
||||
<string name="controller_setup_all_inputs">Setup all inputs</string>
|
||||
<string name="no_controllers_available">No controllers available</string>
|
||||
<string name="controller_select_dialog_title">Select a controller</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user