diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt index d3c5baa1b..7323876c6 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.GraphicsLayerScope import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.flowWithLifecycle @@ -45,6 +46,7 @@ typealias GamePad = RadialGamePad typealias GamePadConfig = RadialGamePadConfig class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = RyujinxNative()) { + private var controllerView: View? = null var leftGamePad: GamePad var rightGamePad: GamePad var controllerId: Int = -1 @@ -65,7 +67,6 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = @Composable fun Compose(lifecycleScope: LifecycleCoroutineScope, lifecycle:Lifecycle) : Unit { - AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> Create(context)}) @@ -81,14 +82,22 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = } } - private fun Create(context: Context) : View + private fun Create(context: Context) : View { var inflator = LayoutInflater.from(context); var view = inflator.inflate(R.layout.game_layout, null) view.findViewById(R.id.leftcontainer)!!.addView(leftGamePad); view.findViewById(R.id.rightcontainer)!!.addView(rightGamePad); - return view as View + controllerView = view + + return controllerView as View + } + + fun setVisible(isVisible: Boolean){ + controllerView?.apply { + this.isVisible = isVisible + } } fun connect(){ diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt index a79306c8f..563bd4f28 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt @@ -132,7 +132,16 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo _nativeRyujinx.inputInitialize(width, height) - controller.connect() + if(!settings.useVirtualController){ + controller.setVisible(false) + } + else{ + controller.connect() + } + + mainViewModel.activity.physicalControllerManager.connect() + + // _nativeRyujinx.graphicsRendererSetSize( surfaceHolder.surfaceFrame.width(), diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt index 422b05fed..8458adf80 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt @@ -1,5 +1,6 @@ package org.ryujinx.android +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo @@ -8,6 +9,8 @@ import android.media.AudioManager import android.os.Build import android.os.Bundle import android.os.Environment +import android.view.KeyEvent +import android.view.MotionEvent import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -29,6 +32,7 @@ import org.ryujinx.android.views.MainView class MainActivity : ComponentActivity() { + var physicalControllerManager: PhysicalControllerManager private var mainViewModel: MainViewModel? = null private var _isInit: Boolean = false var storageHelper: SimpleStorageHelper? = null @@ -41,6 +45,7 @@ class MainActivity : ComponentActivity() { } init { + physicalControllerManager = PhysicalControllerManager(this) storageHelper = SimpleStorageHelper(this) StorageHelper = storageHelper } @@ -79,6 +84,21 @@ class MainActivity : ComponentActivity() { } } + @SuppressLint("RestrictedApi") + override fun dispatchKeyEvent(event: KeyEvent?): Boolean { + event?.apply { + return physicalControllerManager.onKeyEvent(this) + } + return super.dispatchKeyEvent(event) + } + + override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean { + ev?.apply { + physicalControllerManager.onMotionEvent(this) + } + return super.dispatchGenericMotionEvent(ev) + } + private fun initialize() : Unit { if(_isInit) diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt new file mode 100644 index 000000000..fe372bdcd --- /dev/null +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt @@ -0,0 +1,69 @@ +package org.ryujinx.android + +import android.view.KeyEvent +import android.view.MotionEvent + +class PhysicalControllerManager(val activity: MainActivity) { + private var controllerId: Int = -1 + private var ryujinxNative: RyujinxNative = RyujinxNative() + + fun onKeyEvent(event: KeyEvent) : Boolean{ + if(controllerId != -1) { + var id = GetGamePadButtonInputId(event.keyCode) + + if(id != GamePadButtonInputId.None) { + when (event.action) { + KeyEvent.ACTION_UP -> { + ryujinxNative.inputSetButtonReleased(id.ordinal, controllerId) + } + + KeyEvent.ACTION_DOWN -> { + ryujinxNative.inputSetButtonPressed(id.ordinal, controllerId) + } + } + return true; + } + } + + return false + } + + fun onMotionEvent(ev: MotionEvent) { + if(controllerId != -1) { + if(ev.action == MotionEvent.ACTION_MOVE) { + var leftStickX = ev.getAxisValue(MotionEvent.AXIS_X); + var leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y); + var rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z); + var rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ); + ryujinxNative.inputSetStickAxis(1, leftStickX, -leftStickY ,controllerId) + ryujinxNative.inputSetStickAxis(2, rightStickX, -rightStickY ,controllerId) + } + } + } + + fun connect(){ + controllerId = ryujinxNative.inputConnectGamepad(0) + } + + fun GetGamePadButtonInputId(keycode: Int): GamePadButtonInputId { + return when (keycode) { + KeyEvent.KEYCODE_BUTTON_A -> GamePadButtonInputId.B + KeyEvent.KEYCODE_BUTTON_B -> GamePadButtonInputId.A + KeyEvent.KEYCODE_BUTTON_X -> GamePadButtonInputId.X + KeyEvent.KEYCODE_BUTTON_Y -> GamePadButtonInputId.Y + KeyEvent.KEYCODE_BUTTON_L1 -> GamePadButtonInputId.LeftShoulder + KeyEvent.KEYCODE_BUTTON_L2 -> GamePadButtonInputId.LeftTrigger + KeyEvent.KEYCODE_BUTTON_R1 -> GamePadButtonInputId.RightShoulder + KeyEvent.KEYCODE_BUTTON_R2 -> GamePadButtonInputId.RightTrigger + KeyEvent.KEYCODE_BUTTON_THUMBL -> GamePadButtonInputId.LeftStick + KeyEvent.KEYCODE_BUTTON_THUMBR -> GamePadButtonInputId.RightStick + KeyEvent.KEYCODE_DPAD_UP -> GamePadButtonInputId.DpadUp + KeyEvent.KEYCODE_DPAD_DOWN -> GamePadButtonInputId.DpadDown + KeyEvent.KEYCODE_DPAD_LEFT -> GamePadButtonInputId.DpadLeft + KeyEvent.KEYCODE_DPAD_RIGHT -> GamePadButtonInputId.DpadRight + KeyEvent.KEYCODE_BUTTON_START -> GamePadButtonInputId.Plus + KeyEvent.KEYCODE_BUTTON_SELECT -> GamePadButtonInputId.Minus + else -> GamePadButtonInputId.None + } + } +} \ No newline at end of file diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/QuickSettings.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/QuickSettings.kt index 188e9ec6f..de1d6b320 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/QuickSettings.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/QuickSettings.kt @@ -10,6 +10,7 @@ class QuickSettings(val activity: MainActivity) { var enableDocked: Boolean var enableVsync: Boolean var useNce: Boolean + var useVirtualController: Boolean var isHostMapped: Boolean var enableShaderCache: Boolean var enableTextureRecompression: Boolean @@ -27,5 +28,6 @@ class QuickSettings(val activity: MainActivity) { enableShaderCache = sharedPref.getBoolean("enableShaderCache", true) enableTextureRecompression = sharedPref.getBoolean("enableTextureRecompression", false) resScale = sharedPref.getFloat("resScale", 1f) + useVirtualController = sharedPref.getBoolean("useVirtualController", true) } } \ No newline at end of file diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt index 095398921..73ed910ad 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt @@ -26,7 +26,8 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main ignoreMissingServices: MutableState, enableShaderCache: MutableState, enableTextureRecompression: MutableState, - resScale: MutableState + resScale: MutableState, + useVirtualController: MutableState ) { @@ -39,6 +40,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main enableShaderCache.value = sharedPref.getBoolean("enableShaderCache", true) enableTextureRecompression.value = sharedPref.getBoolean("enableTextureRecompression", false) resScale.value = sharedPref.getFloat("resScale", 1f) + useVirtualController.value = sharedPref.getBoolean("useVirtualController", true) } fun save( @@ -50,7 +52,8 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main ignoreMissingServices: MutableState, enableShaderCache: MutableState, enableTextureRecompression: MutableState, - resScale: MutableState + resScale: MutableState, + useVirtualController: MutableState ){ var editor = sharedPref.edit() @@ -63,6 +66,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main editor.putBoolean("enableShaderCache", enableShaderCache?.value ?: true) editor.putBoolean("enableTextureRecompression", enableTextureRecompression?.value ?: false) editor.putFloat("resScale", resScale?.value ?: 1f) + editor.putBoolean("useVirtualController", useVirtualController?.value ?: true) editor.apply() } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt index 3546fe7b5..6ba85e1ad 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt @@ -92,6 +92,9 @@ class SettingViews { var resScale = remember { mutableStateOf(1f) } + var useVirtualController = remember { + mutableStateOf(true) + } if (!loaded.value) { settingsViewModel.initializeState( @@ -100,7 +103,8 @@ class SettingViews { enableVsync, enableDocked, enablePtc, ignoreMissingServices, enableShaderCache, enableTextureRecompression, - resScale + resScale, + useVirtualController ) loaded.value = true } @@ -121,7 +125,8 @@ class SettingViews { ignoreMissingServices, enableShaderCache, enableTextureRecompression, - resScale + resScale, + useVirtualController ) settingsViewModel.navController.popBackStack() }) { @@ -136,7 +141,8 @@ class SettingViews { useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices, enableShaderCache, enableTextureRecompression, - resScale + resScale, + useVirtualController ) } ExpandableView(onCardArrowClick = { }, title = "System") { @@ -431,6 +437,25 @@ class SettingViews { */ } } + ExpandableView(onCardArrowClick = { }, title = "Input") { + Column(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Show virtual controller", + modifier = Modifier.align(Alignment.CenterVertically) + ) + Switch(checked = useVirtualController.value, onCheckedChange = { + useVirtualController.value = !useVirtualController.value + }) + } + } + } } } }