mirror of
https://git.ryujinx.app/kenji-nx/ryujinx.git
synced 2025-12-13 13:37:08 +00:00
Added 6 different Virtual Controller Presets
Also make the "Use Switch Controller" toggle have an effect on virtual Controllers, too.
This commit is contained in:
parent
78db4c365f
commit
fba336f3af
11 changed files with 2094 additions and 27 deletions
|
|
@ -37,7 +37,7 @@ typealias GamePadConfig = RadialGamePadConfig
|
|||
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
|
||||
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
|
||||
|
||||
class GameController(var activity: Activity) {
|
||||
class GameController(var activity: Activity) : IGameController {
|
||||
|
||||
companion object {
|
||||
private fun init(context: Context, controller: GameController): View {
|
||||
|
|
@ -80,12 +80,13 @@ class GameController(var activity: Activity) {
|
|||
var leftGamePad: GamePad
|
||||
var rightGamePad: GamePad
|
||||
var controllerId: Int = -1
|
||||
val isVisible: Boolean
|
||||
override val isVisible: Boolean
|
||||
get() = controllerView?.isVisible ?: false
|
||||
|
||||
init {
|
||||
leftGamePad = GamePad(generateConfig(true), 16f, activity)
|
||||
rightGamePad = GamePad(generateConfig(false), 16f, activity)
|
||||
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
|
||||
leftGamePad = GamePad(generateConfig(true, useSwitchLayout), 16f, activity)
|
||||
rightGamePad = GamePad(generateConfig(false, useSwitchLayout), 16f, activity)
|
||||
|
||||
leftGamePad.primaryDialMaxSizeDp = 200f
|
||||
rightGamePad.primaryDialMaxSizeDp = 200f
|
||||
|
|
@ -96,14 +97,14 @@ class GameController(var activity: Activity) {
|
|||
rightGamePad.gravityY = 1f
|
||||
}
|
||||
|
||||
fun setVisible(isVisible: Boolean) {
|
||||
override fun setVisible(isVisible: Boolean) {
|
||||
controllerView?.apply {
|
||||
this.isVisible = isVisible
|
||||
if (isVisible) connect()
|
||||
}
|
||||
}
|
||||
|
||||
fun connect() {
|
||||
override fun connect() {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
}
|
||||
|
|
@ -177,7 +178,7 @@ suspend fun <T> Flow<T>.safeCollect(block: suspend (T) -> Unit) {
|
|||
.collect { block(it) }
|
||||
}
|
||||
|
||||
private fun generateConfig(isLeft: Boolean): GamePadConfig {
|
||||
private fun generateConfig(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
|
|
@ -293,20 +294,23 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
|
|||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
listOf(
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null
|
||||
),
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null
|
||||
),
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null
|
||||
),
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null
|
||||
if (useSwitchLayout) {
|
||||
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
|
||||
)
|
||||
),
|
||||
} else {
|
||||
// Xbox-Stil: B=Right, Y=Top, X=Left, A=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
|
||||
)
|
||||
},
|
||||
null,
|
||||
0f,
|
||||
true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,403 @@
|
|||
package org.kenjinx.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.math.MathUtils
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.swordfish.radialgamepad.library.RadialGamePad
|
||||
import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kenjinx.android.viewmodels.MainViewModel
|
||||
import org.kenjinx.android.viewmodels.QuickSettings
|
||||
|
||||
// --- Dummy IDs to disconnect legacy L3/R3 (stick double tap/tap) ---
|
||||
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
|
||||
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
|
||||
|
||||
/**
|
||||
* GameController2
|
||||
* Layout 2 – aktuell identisch zum Default-Layout (GameController),
|
||||
* als eigenständige Klasse für Preset-Umschaltung.
|
||||
*/
|
||||
class GameController2(var activity: Activity) : IGameController {
|
||||
|
||||
companion object {
|
||||
private fun init(context: Context, controller: GameController2): View {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val parent = FrameLayout(context)
|
||||
val view = inflater.inflate(R.layout.game_layout, parent, false)
|
||||
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
|
||||
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(controller.rightGamePad)
|
||||
return view
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Compose(viewModel: MainViewModel) {
|
||||
AndroidView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
factory = { context ->
|
||||
val controller = GameController2(viewModel.activity)
|
||||
val c = init(context, controller)
|
||||
|
||||
viewModel.activity.lifecycleScope.launch {
|
||||
val events = merge(
|
||||
controller.leftGamePad.events(),
|
||||
controller.rightGamePad.events()
|
||||
)
|
||||
// safeCollect kommt aus GameController.kt (bitte dort belassen)
|
||||
events.safeCollect { controller.handleEvent(it) }
|
||||
}
|
||||
|
||||
controller.controllerView = c
|
||||
viewModel.setGameController(controller)
|
||||
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
|
||||
c
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var controllerView: View? = null
|
||||
var leftGamePad: RadialGamePad
|
||||
var rightGamePad: RadialGamePad
|
||||
var controllerId: Int = -1
|
||||
override val isVisible: Boolean
|
||||
get() = controllerView?.isVisible ?: false
|
||||
|
||||
init {
|
||||
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
|
||||
leftGamePad = RadialGamePad(generateConfig2(true, useSwitchLayout), 16f, activity)
|
||||
rightGamePad = RadialGamePad(generateConfig2(false, useSwitchLayout), 16f, activity)
|
||||
|
||||
leftGamePad.primaryDialMaxSizeDp = 200f
|
||||
rightGamePad.primaryDialMaxSizeDp = 200f
|
||||
|
||||
leftGamePad.gravityX = -1f
|
||||
leftGamePad.gravityY = 1f
|
||||
rightGamePad.gravityX = 1f
|
||||
rightGamePad.gravityY = 1f
|
||||
}
|
||||
|
||||
override fun setVisible(isVisible: Boolean) {
|
||||
controllerView?.apply {
|
||||
this.isVisible = isVisible
|
||||
if (isVisible) connect()
|
||||
}
|
||||
}
|
||||
|
||||
override fun connect() {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
}
|
||||
|
||||
private fun handleEvent(ev: Event) {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
|
||||
controllerId.apply {
|
||||
when (ev) {
|
||||
is Event.Button -> {
|
||||
// Ignore legacy L3/R3 via stick press (double tap)
|
||||
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) {
|
||||
return
|
||||
}
|
||||
when (ev.action) {
|
||||
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
|
||||
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
|
||||
}
|
||||
}
|
||||
|
||||
is Event.Direction -> {
|
||||
when (ev.id) {
|
||||
GamePadButtonInputId.DpadUp.ordinal -> {
|
||||
// Horizontal
|
||||
if (ev.xAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
} else if (ev.xAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
}
|
||||
// Vertical
|
||||
if (ev.yAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
} else if (ev.yAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
}
|
||||
}
|
||||
|
||||
GamePadButtonInputId.LeftStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(1, x, -y, this)
|
||||
}
|
||||
|
||||
GamePadButtonInputId.RightStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(2, x, -y, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Lokale Kopie der Config-Erzeugung (Name = generateConfig2, um Konflikte zu vermeiden) ---
|
||||
private fun generateConfig2(isLeft: Boolean, useSwitchLayout: Boolean): RadialGamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
if (isLeft) {
|
||||
return RadialGamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (Stick) */
|
||||
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
|
||||
PrimaryDialConfig.Stick(
|
||||
GamePadButtonInputId.LeftStick.ordinal,
|
||||
DUMMY_LEFT_STICK_PRESS_ID,
|
||||
setOf(),
|
||||
"LeftStick",
|
||||
null
|
||||
),
|
||||
listOf(
|
||||
// D-Pad
|
||||
SecondaryDialConfig.Cross(
|
||||
/* sector */ 10,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.1f,
|
||||
distance,
|
||||
CrossConfig(
|
||||
GamePadButtonInputId.DpadUp.ordinal,
|
||||
CrossConfig.Shape.STANDARD,
|
||||
null,
|
||||
setOf(),
|
||||
CrossContentDescription(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Minus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
0.3f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Minus.ordinal,
|
||||
"-",
|
||||
true,
|
||||
null,
|
||||
"Minus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// L-Bumper
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 2,
|
||||
0.2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftShoulder.ordinal,
|
||||
"L",
|
||||
true,
|
||||
null,
|
||||
"LeftBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZL-Trigger
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 2,
|
||||
1f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftTrigger.ordinal,
|
||||
"ZL",
|
||||
true,
|
||||
null,
|
||||
"LeftTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// L3 separat
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 1,
|
||||
buttonScale,
|
||||
1.0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftStickButton.ordinal,
|
||||
"L3",
|
||||
true,
|
||||
null,
|
||||
"LeftStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return RadialGamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
if (useSwitchLayout) {
|
||||
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
|
||||
)
|
||||
} else {
|
||||
// Xbox-Stil: B=Right, Y=Top, X=Left, A=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
|
||||
)
|
||||
},
|
||||
null,
|
||||
0f,
|
||||
true,
|
||||
null
|
||||
),
|
||||
listOf(
|
||||
// Right stick
|
||||
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
|
||||
SecondaryDialConfig.Stick(
|
||||
/* sector */ 6,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.7f,
|
||||
distance,
|
||||
GamePadButtonInputId.RightStick.ordinal,
|
||||
DUMMY_RIGHT_STICK_PRESS_ID,
|
||||
null,
|
||||
setOf(),
|
||||
"RightStick",
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Plus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
0.3f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Plus.ordinal,
|
||||
"+",
|
||||
true,
|
||||
null,
|
||||
"Plus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// R-Bumper
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 3,
|
||||
0.2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightShoulder.ordinal,
|
||||
"R",
|
||||
true,
|
||||
null,
|
||||
"RightBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZR-Trigger
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 3,
|
||||
1f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightTrigger.ordinal,
|
||||
"ZR",
|
||||
true,
|
||||
null,
|
||||
"RightTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// R3 separat
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 5,
|
||||
buttonScale,
|
||||
1.0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightStickButton.ordinal,
|
||||
"R3",
|
||||
true,
|
||||
null,
|
||||
"RightStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,396 @@
|
|||
// src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameController3.kt
|
||||
package org.kenjinx.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.math.MathUtils
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.swordfish.radialgamepad.library.RadialGamePad
|
||||
import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kenjinx.android.viewmodels.MainViewModel
|
||||
import org.kenjinx.android.viewmodels.QuickSettings
|
||||
|
||||
// --- Dummy IDs to disconnect legacy L3/R3 (stick double tap/tap) ---
|
||||
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
|
||||
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
|
||||
|
||||
/**
|
||||
* GameController3
|
||||
* Layout 3 – aktuell identisch zum Default-Layout, als eigenständige Klasse.
|
||||
*/
|
||||
class GameController3(var activity: Activity) : IGameController {
|
||||
|
||||
companion object {
|
||||
private fun init(context: Context, controller: GameController3): View {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val parent = FrameLayout(context)
|
||||
val view = inflater.inflate(R.layout.game_layout, parent, false)
|
||||
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
|
||||
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(controller.rightGamePad)
|
||||
return view
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Compose(viewModel: MainViewModel) {
|
||||
AndroidView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
factory = { context ->
|
||||
val controller = GameController3(viewModel.activity)
|
||||
val c = init(context, controller)
|
||||
|
||||
viewModel.activity.lifecycleScope.launch {
|
||||
val events = merge(
|
||||
controller.leftGamePad.events(),
|
||||
controller.rightGamePad.events()
|
||||
)
|
||||
events.safeCollect { controller.handleEvent(it) }
|
||||
}
|
||||
|
||||
controller.controllerView = c
|
||||
viewModel.setGameController(controller)
|
||||
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
|
||||
c
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var controllerView: View? = null
|
||||
var leftGamePad: RadialGamePad
|
||||
var rightGamePad: RadialGamePad
|
||||
var controllerId: Int = -1
|
||||
override val isVisible: Boolean
|
||||
get() = controllerView?.isVisible ?: false
|
||||
|
||||
init {
|
||||
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
|
||||
leftGamePad = RadialGamePad(generateConfig3(true, useSwitchLayout), 16f, activity)
|
||||
rightGamePad = RadialGamePad(generateConfig3(false, useSwitchLayout), 16f, activity)
|
||||
|
||||
leftGamePad.primaryDialMaxSizeDp = 200f
|
||||
rightGamePad.primaryDialMaxSizeDp = 200f
|
||||
|
||||
leftGamePad.gravityX = -1f
|
||||
leftGamePad.gravityY = 1f
|
||||
rightGamePad.gravityX = 1f
|
||||
rightGamePad.gravityY = 1f
|
||||
}
|
||||
|
||||
override fun setVisible(isVisible: Boolean) {
|
||||
controllerView?.apply {
|
||||
this.isVisible = isVisible
|
||||
if (isVisible) connect()
|
||||
}
|
||||
}
|
||||
|
||||
override fun connect() {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
}
|
||||
|
||||
private fun handleEvent(ev: Event) {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
|
||||
controllerId.apply {
|
||||
when (ev) {
|
||||
is Event.Button -> {
|
||||
// Ignore legacy L3/R3 via stick press (double tap)
|
||||
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) return
|
||||
when (ev.action) {
|
||||
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
|
||||
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
|
||||
}
|
||||
}
|
||||
is Event.Direction -> {
|
||||
when (ev.id) {
|
||||
GamePadButtonInputId.DpadUp.ordinal -> {
|
||||
// Horizontal
|
||||
if (ev.xAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
} else if (ev.xAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
}
|
||||
// Vertical
|
||||
if (ev.yAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
} else if (ev.yAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
}
|
||||
}
|
||||
GamePadButtonInputId.LeftStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(1, x, -y, this)
|
||||
}
|
||||
GamePadButtonInputId.RightStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(2, x, -y, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateConfig3(isLeft: Boolean, useSwitchLayout: Boolean): RadialGamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
if (isLeft) {
|
||||
return RadialGamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (D-Pad) */
|
||||
PrimaryDialConfig.Cross(
|
||||
CrossConfig(
|
||||
GamePadButtonInputId.DpadUp.ordinal,
|
||||
CrossConfig.Shape.STANDARD,
|
||||
null,
|
||||
setOf(),
|
||||
CrossContentDescription(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
),
|
||||
listOf(
|
||||
// Left stick
|
||||
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
|
||||
SecondaryDialConfig.Stick(
|
||||
/* sector */ 10,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.7f,
|
||||
distance,
|
||||
GamePadButtonInputId.LeftStick.ordinal,
|
||||
DUMMY_LEFT_STICK_PRESS_ID,
|
||||
null,
|
||||
setOf(),
|
||||
"LeftStick",
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Minus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
0.3f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Minus.ordinal,
|
||||
"-",
|
||||
true,
|
||||
null,
|
||||
"Minus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// L-Bumper
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 2,
|
||||
0.2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftShoulder.ordinal,
|
||||
"L",
|
||||
true,
|
||||
null,
|
||||
"LeftBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZL-Trigger
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 2,
|
||||
1f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftTrigger.ordinal,
|
||||
"ZL",
|
||||
true,
|
||||
null,
|
||||
"LeftTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// L3 separat
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 1,
|
||||
buttonScale,
|
||||
1.0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftStickButton.ordinal,
|
||||
"L3",
|
||||
true,
|
||||
null,
|
||||
"LeftStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return RadialGamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
if (useSwitchLayout) {
|
||||
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
|
||||
)
|
||||
} else {
|
||||
// Xbox-Stil: B=Right, Y=Top, X=Left, A=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
|
||||
)
|
||||
},
|
||||
null,
|
||||
0f,
|
||||
true,
|
||||
null
|
||||
),
|
||||
listOf(
|
||||
// Right stick
|
||||
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
|
||||
SecondaryDialConfig.Stick(
|
||||
/* sector */ 6,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.7f,
|
||||
distance,
|
||||
GamePadButtonInputId.RightStick.ordinal,
|
||||
DUMMY_RIGHT_STICK_PRESS_ID,
|
||||
null,
|
||||
setOf(),
|
||||
"RightStick",
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Plus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
0.3f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Plus.ordinal,
|
||||
"+",
|
||||
true,
|
||||
null,
|
||||
"Plus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// R-Bumper
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 3,
|
||||
0.2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightShoulder.ordinal,
|
||||
"R",
|
||||
true,
|
||||
null,
|
||||
"RightBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZR-Trigger
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 3,
|
||||
1f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightTrigger.ordinal,
|
||||
"ZR",
|
||||
true,
|
||||
null,
|
||||
"RightTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// R3 separat
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 5,
|
||||
buttonScale,
|
||||
1.0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightStickButton.ordinal,
|
||||
"R3",
|
||||
true,
|
||||
null,
|
||||
"RightStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
// src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameController4.kt
|
||||
package org.kenjinx.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.math.MathUtils
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.swordfish.radialgamepad.library.RadialGamePad
|
||||
import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kenjinx.android.viewmodels.MainViewModel
|
||||
import org.kenjinx.android.viewmodels.QuickSettings
|
||||
|
||||
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
|
||||
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
|
||||
|
||||
/**
|
||||
* GameController4
|
||||
* Layout 4 – aktuell identisch zum Default-Layout, als eigenständige Klasse.
|
||||
*/
|
||||
class GameController4(var activity: Activity) : IGameController {
|
||||
|
||||
companion object {
|
||||
private fun init(context: Context, controller: GameController4): View {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val parent = FrameLayout(context)
|
||||
val view = inflater.inflate(R.layout.game_layout, parent, false)
|
||||
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
|
||||
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(controller.rightGamePad)
|
||||
return view
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Compose(viewModel: MainViewModel) {
|
||||
AndroidView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
factory = { context ->
|
||||
val controller = GameController4(viewModel.activity)
|
||||
val c = init(context, controller)
|
||||
|
||||
viewModel.activity.lifecycleScope.launch {
|
||||
val events = merge(
|
||||
controller.leftGamePad.events(),
|
||||
controller.rightGamePad.events()
|
||||
)
|
||||
events.safeCollect { controller.handleEvent(it) }
|
||||
}
|
||||
|
||||
controller.controllerView = c
|
||||
viewModel.setGameController(controller)
|
||||
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
|
||||
c
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var controllerView: View? = null
|
||||
var leftGamePad: RadialGamePad
|
||||
var rightGamePad: RadialGamePad
|
||||
var controllerId: Int = -1
|
||||
override val isVisible: Boolean
|
||||
get() = controllerView?.isVisible ?: false
|
||||
|
||||
init {
|
||||
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
|
||||
leftGamePad = RadialGamePad(generateConfig4(true, useSwitchLayout), 16f, activity)
|
||||
rightGamePad = RadialGamePad(generateConfig4(false, useSwitchLayout), 16f, activity)
|
||||
|
||||
leftGamePad.primaryDialMaxSizeDp = 200f
|
||||
rightGamePad.primaryDialMaxSizeDp = 200f
|
||||
|
||||
leftGamePad.gravityX = -1f
|
||||
leftGamePad.gravityY = 1f
|
||||
rightGamePad.gravityX = 1f
|
||||
rightGamePad.gravityY = 1f
|
||||
}
|
||||
|
||||
override fun setVisible(isVisible: Boolean) {
|
||||
controllerView?.apply {
|
||||
this.isVisible = isVisible
|
||||
if (isVisible) connect()
|
||||
}
|
||||
}
|
||||
|
||||
override fun connect() {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
}
|
||||
|
||||
private fun handleEvent(ev: Event) {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
|
||||
controllerId.apply {
|
||||
when (ev) {
|
||||
is Event.Button -> {
|
||||
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) return
|
||||
when (ev.action) {
|
||||
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
|
||||
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
|
||||
}
|
||||
}
|
||||
is Event.Direction -> {
|
||||
when (ev.id) {
|
||||
GamePadButtonInputId.DpadUp.ordinal -> {
|
||||
if (ev.xAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
} else if (ev.xAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
}
|
||||
if (ev.yAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
} else if (ev.yAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
}
|
||||
}
|
||||
GamePadButtonInputId.LeftStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(1, x, -y, this)
|
||||
}
|
||||
GamePadButtonInputId.RightStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(2, x, -y, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateConfig4(isLeft: Boolean, useSwitchLayout: Boolean): RadialGamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
if (isLeft) {
|
||||
return RadialGamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (Stick) */
|
||||
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
|
||||
PrimaryDialConfig.Stick(
|
||||
GamePadButtonInputId.LeftStick.ordinal,
|
||||
DUMMY_LEFT_STICK_PRESS_ID,
|
||||
setOf(),
|
||||
"LeftStick",
|
||||
null
|
||||
),
|
||||
listOf(
|
||||
// D-Pad
|
||||
SecondaryDialConfig.Cross(
|
||||
/* sector */ 10,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.1f,
|
||||
distance,
|
||||
CrossConfig(
|
||||
GamePadButtonInputId.DpadUp.ordinal,
|
||||
CrossConfig.Shape.STANDARD,
|
||||
null,
|
||||
setOf(),
|
||||
CrossContentDescription(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Minus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
0.3f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Minus.ordinal,
|
||||
"-",
|
||||
true,
|
||||
null,
|
||||
"Minus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// L-Bumper
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 2,
|
||||
1.2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftShoulder.ordinal,
|
||||
"L",
|
||||
true,
|
||||
null,
|
||||
"LeftBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZL-Trigger
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 2,
|
||||
2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftTrigger.ordinal,
|
||||
"ZL",
|
||||
true,
|
||||
null,
|
||||
"LeftTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return RadialGamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
if (useSwitchLayout) {
|
||||
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
|
||||
)
|
||||
} else {
|
||||
// Xbox-Stil: B=Right, Y=Top, X=Left, A=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
|
||||
)
|
||||
},
|
||||
null,
|
||||
0f,
|
||||
true,
|
||||
null
|
||||
),
|
||||
listOf(
|
||||
// Right stick
|
||||
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
|
||||
SecondaryDialConfig.Stick(
|
||||
/* sector */ 6,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.7f,
|
||||
distance,
|
||||
GamePadButtonInputId.RightStick.ordinal,
|
||||
DUMMY_RIGHT_STICK_PRESS_ID,
|
||||
null,
|
||||
setOf(),
|
||||
"RightStick",
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Plus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
0.3f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Plus.ordinal,
|
||||
"+",
|
||||
true,
|
||||
null,
|
||||
"Plus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// R-Bumper
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 3,
|
||||
1.2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightShoulder.ordinal,
|
||||
"R",
|
||||
true,
|
||||
null,
|
||||
"RightBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZR-Trigger
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 3,
|
||||
2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightTrigger.ordinal,
|
||||
"ZR",
|
||||
true,
|
||||
null,
|
||||
"RightTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// R3 separat
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 3,
|
||||
buttonScale,
|
||||
0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightStickButton.ordinal,
|
||||
"R3",
|
||||
true,
|
||||
null,
|
||||
"RightStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// L3 separat
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 4,
|
||||
buttonScale,
|
||||
0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftStickButton.ordinal,
|
||||
"L3",
|
||||
true,
|
||||
null,
|
||||
"LeftStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,396 @@
|
|||
package org.kenjinx.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.math.MathUtils
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.swordfish.radialgamepad.library.RadialGamePad
|
||||
import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kenjinx.android.viewmodels.MainViewModel
|
||||
import org.kenjinx.android.viewmodels.QuickSettings
|
||||
|
||||
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
|
||||
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
|
||||
|
||||
/**
|
||||
* GameController5
|
||||
* Layout 5 – aktuell identisch zum Default-Layout, als eigenständige Klasse.
|
||||
*/
|
||||
class GameController5(var activity: Activity) : IGameController {
|
||||
|
||||
companion object {
|
||||
private fun init(context: Context, controller: GameController5): View {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val parent = FrameLayout(context)
|
||||
val view = inflater.inflate(R.layout.game_layout, parent, false)
|
||||
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
|
||||
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(controller.rightGamePad)
|
||||
return view
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Compose(viewModel: MainViewModel) {
|
||||
AndroidView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
factory = { context ->
|
||||
val controller = GameController5(viewModel.activity)
|
||||
val c = init(context, controller)
|
||||
|
||||
viewModel.activity.lifecycleScope.launch {
|
||||
val events = merge(
|
||||
controller.leftGamePad.events(),
|
||||
controller.rightGamePad.events()
|
||||
)
|
||||
events.safeCollect { controller.handleEvent(it) }
|
||||
}
|
||||
|
||||
controller.controllerView = c
|
||||
viewModel.setGameController(controller)
|
||||
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
|
||||
c
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var controllerView: View? = null
|
||||
var leftGamePad: RadialGamePad
|
||||
var rightGamePad: RadialGamePad
|
||||
var controllerId: Int = -1
|
||||
override val isVisible: Boolean
|
||||
get() = controllerView?.isVisible ?: false
|
||||
|
||||
init {
|
||||
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
|
||||
leftGamePad = RadialGamePad(generateConfig5(true, useSwitchLayout), 16f, activity)
|
||||
rightGamePad = RadialGamePad(generateConfig5(false, useSwitchLayout), 16f, activity)
|
||||
|
||||
leftGamePad.primaryDialMaxSizeDp = 200f
|
||||
rightGamePad.primaryDialMaxSizeDp = 200f
|
||||
|
||||
leftGamePad.gravityX = -1f
|
||||
leftGamePad.gravityY = 1f
|
||||
rightGamePad.gravityX = 1f
|
||||
rightGamePad.gravityY = 1f
|
||||
}
|
||||
|
||||
override fun setVisible(isVisible: Boolean) {
|
||||
controllerView?.apply {
|
||||
this.isVisible = isVisible
|
||||
if (isVisible) connect()
|
||||
}
|
||||
}
|
||||
|
||||
override fun connect() {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
}
|
||||
|
||||
private fun handleEvent(ev: Event) {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
|
||||
controllerId.apply {
|
||||
when (ev) {
|
||||
is Event.Button -> {
|
||||
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) return
|
||||
when (ev.action) {
|
||||
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
|
||||
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
|
||||
}
|
||||
}
|
||||
is Event.Direction -> {
|
||||
when (ev.id) {
|
||||
GamePadButtonInputId.DpadUp.ordinal -> {
|
||||
if (ev.xAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
} else if (ev.xAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
}
|
||||
if (ev.yAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
} else if (ev.yAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
}
|
||||
}
|
||||
GamePadButtonInputId.LeftStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(1, x, -y, this)
|
||||
}
|
||||
GamePadButtonInputId.RightStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(2, x, -y, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateConfig5(isLeft: Boolean, useSwitchLayout: Boolean): RadialGamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
if (isLeft) {
|
||||
return RadialGamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (Stick) */
|
||||
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
|
||||
PrimaryDialConfig.Stick(
|
||||
GamePadButtonInputId.LeftStick.ordinal,
|
||||
DUMMY_LEFT_STICK_PRESS_ID,
|
||||
setOf(),
|
||||
"LeftStick",
|
||||
null
|
||||
),
|
||||
listOf(
|
||||
// D-Pad
|
||||
SecondaryDialConfig.Cross(
|
||||
/* sector */ 10,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.1f,
|
||||
distance,
|
||||
CrossConfig(
|
||||
GamePadButtonInputId.DpadUp.ordinal,
|
||||
CrossConfig.Shape.STANDARD,
|
||||
null,
|
||||
setOf(),
|
||||
CrossContentDescription(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Minus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
0.3f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Minus.ordinal,
|
||||
"-",
|
||||
true,
|
||||
null,
|
||||
"Minus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// L-Bumper
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 3,
|
||||
1.5f,
|
||||
1.0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftShoulder.ordinal,
|
||||
"L",
|
||||
true,
|
||||
null,
|
||||
"LeftBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZL-Trigger
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 2,
|
||||
1.5f,
|
||||
1.1f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftTrigger.ordinal,
|
||||
"ZL",
|
||||
true,
|
||||
null,
|
||||
"LeftTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return RadialGamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
if (useSwitchLayout) {
|
||||
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
|
||||
)
|
||||
} else {
|
||||
// Xbox-Stil: B=Right, Y=Top, X=Left, A=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
|
||||
)
|
||||
},
|
||||
null,
|
||||
0f,
|
||||
true,
|
||||
null
|
||||
),
|
||||
listOf(
|
||||
// Right stick
|
||||
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
|
||||
SecondaryDialConfig.Stick(
|
||||
/* sector */ 6,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.7f,
|
||||
distance,
|
||||
GamePadButtonInputId.RightStick.ordinal,
|
||||
DUMMY_RIGHT_STICK_PRESS_ID,
|
||||
null,
|
||||
setOf(),
|
||||
"RightStick",
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Plus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
0.3f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Plus.ordinal,
|
||||
"+",
|
||||
true,
|
||||
null,
|
||||
"Plus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// R-Bumper
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 3,
|
||||
1.5f,
|
||||
1.0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightShoulder.ordinal,
|
||||
"R",
|
||||
true,
|
||||
null,
|
||||
"RightBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZR-Trigger
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 4,
|
||||
1.5f,
|
||||
1.1f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightTrigger.ordinal,
|
||||
"ZR",
|
||||
true,
|
||||
null,
|
||||
"RightTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// R3 separat
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 3,
|
||||
1.1f,
|
||||
0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightStickButton.ordinal,
|
||||
"R3",
|
||||
true,
|
||||
null,
|
||||
"RightStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// L3 separat
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 4,
|
||||
1.1f,
|
||||
0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftStickButton.ordinal,
|
||||
"L3",
|
||||
true,
|
||||
null,
|
||||
"LeftStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
package org.kenjinx.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.math.MathUtils
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.swordfish.radialgamepad.library.RadialGamePad
|
||||
import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
||||
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
|
||||
import com.swordfish.radialgamepad.library.event.Event
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kenjinx.android.viewmodels.MainViewModel
|
||||
import org.kenjinx.android.viewmodels.QuickSettings
|
||||
|
||||
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
|
||||
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
|
||||
|
||||
/**
|
||||
* GameController6
|
||||
* Layout 6 – aktuell identisch zum Default-Layout, als eigenständige Klasse.
|
||||
*/
|
||||
class GameController6(var activity: Activity) : IGameController {
|
||||
|
||||
companion object {
|
||||
private fun init(context: Context, controller: GameController6): View {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val parent = FrameLayout(context)
|
||||
val view = inflater.inflate(R.layout.game_layout, parent, false)
|
||||
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
|
||||
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(controller.rightGamePad)
|
||||
return view
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Compose(viewModel: MainViewModel) {
|
||||
AndroidView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
factory = { context ->
|
||||
val controller = GameController6(viewModel.activity)
|
||||
val c = init(context, controller)
|
||||
|
||||
viewModel.activity.lifecycleScope.launch {
|
||||
val events = merge(
|
||||
controller.leftGamePad.events(),
|
||||
controller.rightGamePad.events()
|
||||
)
|
||||
events.safeCollect { controller.handleEvent(it) }
|
||||
}
|
||||
|
||||
controller.controllerView = c
|
||||
viewModel.setGameController(controller)
|
||||
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
|
||||
c
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var controllerView: View? = null
|
||||
var leftGamePad: RadialGamePad
|
||||
var rightGamePad: RadialGamePad
|
||||
var controllerId: Int = -1
|
||||
override val isVisible: Boolean
|
||||
get() = controllerView?.isVisible ?: false
|
||||
|
||||
init {
|
||||
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
|
||||
leftGamePad = RadialGamePad(generateConfig6(true, useSwitchLayout), 16f, activity)
|
||||
rightGamePad = RadialGamePad(generateConfig6(false, useSwitchLayout), 16f, activity)
|
||||
|
||||
leftGamePad.primaryDialMaxSizeDp = 200f
|
||||
rightGamePad.primaryDialMaxSizeDp = 200f
|
||||
|
||||
leftGamePad.gravityX = -1f
|
||||
leftGamePad.gravityY = 1f
|
||||
rightGamePad.gravityX = 1f
|
||||
rightGamePad.gravityY = 1f
|
||||
}
|
||||
|
||||
override fun setVisible(isVisible: Boolean) {
|
||||
controllerView?.apply {
|
||||
this.isVisible = isVisible
|
||||
if (isVisible) connect()
|
||||
}
|
||||
}
|
||||
|
||||
override fun connect() {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
}
|
||||
|
||||
private fun handleEvent(ev: Event) {
|
||||
if (controllerId == -1)
|
||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||
|
||||
controllerId.apply {
|
||||
when (ev) {
|
||||
is Event.Button -> {
|
||||
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) return
|
||||
when (ev.action) {
|
||||
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
|
||||
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
|
||||
}
|
||||
}
|
||||
is Event.Direction -> {
|
||||
when (ev.id) {
|
||||
GamePadButtonInputId.DpadUp.ordinal -> {
|
||||
if (ev.xAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
} else if (ev.xAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||
}
|
||||
if (ev.yAxis < 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
} else if (ev.yAxis > 0) {
|
||||
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
} else {
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||
}
|
||||
}
|
||||
GamePadButtonInputId.LeftStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(1, x, -y, this)
|
||||
}
|
||||
GamePadButtonInputId.RightStick.ordinal -> {
|
||||
val setting = QuickSettings(activity)
|
||||
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
|
||||
KenjinxNative.inputSetStickAxis(2, x, -y, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateConfig6(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
|
||||
val distance = 0.3f
|
||||
val buttonScale = 1f
|
||||
|
||||
if (isLeft) {
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (Stick) */
|
||||
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
|
||||
PrimaryDialConfig.Stick(
|
||||
GamePadButtonInputId.LeftStick.ordinal,
|
||||
DUMMY_LEFT_STICK_PRESS_ID,
|
||||
setOf(),
|
||||
"LeftStick",
|
||||
null
|
||||
),
|
||||
listOf(
|
||||
// D-Pad
|
||||
SecondaryDialConfig.Cross(
|
||||
/* sector */ 10,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.5f,
|
||||
distance,
|
||||
CrossConfig(
|
||||
GamePadButtonInputId.DpadUp.ordinal,
|
||||
CrossConfig.Shape.STANDARD,
|
||||
null,
|
||||
setOf(),
|
||||
CrossContentDescription(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Minus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 1,
|
||||
buttonScale,
|
||||
2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Minus.ordinal,
|
||||
"-",
|
||||
true,
|
||||
null,
|
||||
"Minus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// L-Bumper
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 2,
|
||||
distance,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftShoulder.ordinal,
|
||||
"L",
|
||||
true,
|
||||
null,
|
||||
"LeftBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZL-Trigger
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
distance,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftTrigger.ordinal,
|
||||
"ZL",
|
||||
true,
|
||||
null,
|
||||
"LeftTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return GamePadConfig(
|
||||
/* ringSegments = */ 12,
|
||||
/* Primary (ABXY) */
|
||||
PrimaryDialConfig.PrimaryButtons(
|
||||
if (useSwitchLayout) {
|
||||
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
|
||||
)
|
||||
} else {
|
||||
// Xbox-Stil: B=Right, Y=Top, X=Left, A=Bottom
|
||||
listOf(
|
||||
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
|
||||
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
|
||||
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
|
||||
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
|
||||
)
|
||||
},
|
||||
null,
|
||||
0f,
|
||||
true,
|
||||
null
|
||||
),
|
||||
listOf(
|
||||
// Right stick (unchanged)
|
||||
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
|
||||
SecondaryDialConfig.Stick(
|
||||
/* sector */ 6,
|
||||
/* size */ 3,
|
||||
/* gap */ 2.5f,
|
||||
0.7f,
|
||||
GamePadButtonInputId.RightStick.ordinal,
|
||||
DUMMY_RIGHT_STICK_PRESS_ID,
|
||||
null,
|
||||
setOf(),
|
||||
"RightStick",
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// Plus
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 5,
|
||||
buttonScale,
|
||||
2f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.Plus.ordinal,
|
||||
"+",
|
||||
true,
|
||||
null,
|
||||
"Plus",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// R-Bumper
|
||||
SecondaryDialConfig.DoubleButton(
|
||||
/* sector */ 3,
|
||||
distance,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightShoulder.ordinal,
|
||||
"R",
|
||||
true,
|
||||
null,
|
||||
"RightBumper",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// ZR-Trigger
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 9,
|
||||
buttonScale,
|
||||
distance,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightTrigger.ordinal,
|
||||
"ZR",
|
||||
true,
|
||||
null,
|
||||
"RightTrigger",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
|
||||
// NEW (remains): R3 as a separate button
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 5,
|
||||
buttonScale,
|
||||
0.0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.RightStickButton.ordinal,
|
||||
"R3",
|
||||
true,
|
||||
null,
|
||||
"RightStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
// NEW (remains): L3 as a separate button
|
||||
SecondaryDialConfig.SingleButton(
|
||||
/* sector */ 6,
|
||||
buttonScale,
|
||||
0.0f,
|
||||
ButtonConfig(
|
||||
GamePadButtonInputId.LeftStickButton.ordinal,
|
||||
"L3",
|
||||
true,
|
||||
null,
|
||||
"LeftStickButton",
|
||||
setOf(),
|
||||
true,
|
||||
null
|
||||
),
|
||||
null,
|
||||
SecondaryDialConfig.RotationProcessor()
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.kenjinx.android
|
||||
|
||||
interface IGameController {
|
||||
val isVisible: Boolean
|
||||
fun setVisible(isVisible: Boolean)
|
||||
fun connect()
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import androidx.preference.PreferenceManager
|
|||
import com.anggrayudi.storage.extension.launchOnUiThread
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import org.kenjinx.android.GameController
|
||||
import org.kenjinx.android.IGameController
|
||||
import org.kenjinx.android.GameHost
|
||||
import org.kenjinx.android.Logging
|
||||
import org.kenjinx.android.MainActivity
|
||||
|
|
@ -30,7 +30,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||
var physicalControllerManager: PhysicalControllerManager? = null
|
||||
var motionSensorManager: MotionSensorManager? = null
|
||||
var gameModel: GameModel? = null
|
||||
var controller: GameController? = null
|
||||
var controller: IGameController? = null
|
||||
var performanceManager: PerformanceManager? = null
|
||||
var selected: GameModel? = null
|
||||
val loadGameModel: MutableState<GameModel?> = mutableStateOf(null)
|
||||
|
|
@ -422,7 +422,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||
frequenciesState?.let { PerformanceMonitor.getFrequencies(it) }
|
||||
}
|
||||
|
||||
fun setGameController(controller: GameController) {
|
||||
fun setGameController(controller: IGameController) {
|
||||
this.controller = controller
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ class QuickSettings(val activity: Activity) {
|
|||
BottomMiddle, BottomLeft, BottomRight, TopMiddle, TopLeft, TopRight
|
||||
}
|
||||
|
||||
// --- Virtual Controller Preset
|
||||
enum class VirtualControllerPreset {
|
||||
Default, Layout2, Layout3, Layout4, Layout5, Layout6
|
||||
}
|
||||
|
||||
var virtualControllerPreset: VirtualControllerPreset
|
||||
|
||||
var orientationPreference: OrientationPreference
|
||||
|
||||
// --- Overlay Settings
|
||||
|
|
@ -94,6 +101,9 @@ class QuickSettings(val activity: Activity) {
|
|||
resScale = sharedPref.getFloat("resScale", 1f)
|
||||
maxAnisotropy = sharedPref.getFloat("maxAnisotropy", 0f)
|
||||
useVirtualController = sharedPref.getBoolean("useVirtualController", true)
|
||||
virtualControllerPreset = VirtualControllerPreset.entries[
|
||||
sharedPref.getInt("virtualControllerPreset", VirtualControllerPreset.Default.ordinal)
|
||||
]
|
||||
isGrid = sharedPref.getBoolean("isGrid", true)
|
||||
useSwitchLayout = sharedPref.getBoolean("useSwitchLayout", true)
|
||||
enableMotion = sharedPref.getBoolean("enableMotion", true)
|
||||
|
|
@ -151,6 +161,7 @@ class QuickSettings(val activity: Activity) {
|
|||
putBoolean("enableTraceLogs", enableTraceLogs)
|
||||
putBoolean("enableDebugLogs", enableDebugLogs)
|
||||
putBoolean("enableGraphicsLogs", enableGraphicsLogs)
|
||||
putInt("virtualControllerPreset", virtualControllerPreset.ordinal)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ import androidx.compose.ui.draw.alpha
|
|||
import compose.icons.CssGgIcons
|
||||
import compose.icons.cssggicons.ToolbarBottom
|
||||
import org.kenjinx.android.GameController
|
||||
import org.kenjinx.android.GameController2
|
||||
import org.kenjinx.android.GameController3
|
||||
import org.kenjinx.android.GameController4
|
||||
import org.kenjinx.android.GameController5
|
||||
import org.kenjinx.android.GameController6
|
||||
import org.kenjinx.android.GameHost
|
||||
import org.kenjinx.android.Icons
|
||||
import org.kenjinx.android.MainActivity
|
||||
|
|
@ -48,6 +53,10 @@ import org.kenjinx.android.viewmodels.VSyncMode
|
|||
import org.kenjinx.android.widgets.SimpleAlertDialog
|
||||
import java.util.Locale
|
||||
import kotlin.math.roundToInt
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import org.kenjinx.android.viewmodels.QuickSettings.VirtualControllerPreset
|
||||
|
||||
|
||||
class GameViews {
|
||||
companion object {
|
||||
|
|
@ -155,7 +164,17 @@ class GameViews {
|
|||
}
|
||||
|
||||
if (!showLoading.value) {
|
||||
GameController.Compose(mainViewModel)
|
||||
// Aktuelles Preset aus QuickSettings holen (bei jeder Recomposition neu – so greift auch ein Wechsel nach dem Speichern)
|
||||
val preset = QuickSettings(mainViewModel.activity).virtualControllerPreset
|
||||
|
||||
when (preset) {
|
||||
VirtualControllerPreset.Default -> GameController.Compose(mainViewModel)
|
||||
VirtualControllerPreset.Layout2 -> GameController2.Compose(mainViewModel)
|
||||
VirtualControllerPreset.Layout3 -> GameController3.Compose(mainViewModel)
|
||||
VirtualControllerPreset.Layout4 -> GameController4.Compose(mainViewModel)
|
||||
VirtualControllerPreset.Layout5 -> GameController5.Compose(mainViewModel)
|
||||
VirtualControllerPreset.Layout6 -> GameController6.Compose(mainViewModel)
|
||||
}
|
||||
|
||||
// --- Button at any corner/edge + transparency
|
||||
Row(
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ import org.kenjinx.android.viewmodels.QuickSettings.OverlayMenuPosition // ← N
|
|||
import org.kenjinx.android.SystemLanguage
|
||||
import org.kenjinx.android.RegionCode
|
||||
|
||||
import org.kenjinx.android.viewmodels.QuickSettings.VirtualControllerPreset
|
||||
|
||||
class SettingViews {
|
||||
companion object {
|
||||
const val EXPANSTION_TRANSITION_DURATION = 450
|
||||
|
|
@ -129,6 +131,9 @@ class SettingViews {
|
|||
val isGrid = remember { mutableStateOf(true) }
|
||||
val useSwitchLayout = remember { mutableStateOf(true) }
|
||||
val enableMotion = remember { mutableStateOf(true) }
|
||||
val vcPreset = remember {
|
||||
mutableStateOf(QuickSettings(mainViewModel.activity).virtualControllerPreset)
|
||||
}
|
||||
val enablePerformanceMode = remember { mutableStateOf(true) }
|
||||
val controllerStickSensitivity = remember { mutableFloatStateOf(1.0f) }
|
||||
val enableStubLogs = remember { mutableStateOf(true) }
|
||||
|
|
@ -1224,6 +1229,17 @@ class SettingViews {
|
|||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
useVirtualController.SwitchSelector(label = "Use Virtual Controller")
|
||||
useSwitchLayout.SwitchSelector(label = "Use Switch Controller Layout")
|
||||
if (useVirtualController.value) {
|
||||
VirtualControllerPresetDropdown(
|
||||
selectedPreset = vcPreset.value,
|
||||
onPresetSelected = { preset ->
|
||||
vcPreset.value = preset
|
||||
val qs = QuickSettings(mainViewModel.activity)
|
||||
qs.virtualControllerPreset = preset
|
||||
qs.save() // sofort persistieren, wie bei Overlay-Settings
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
|
||||
|
||||
|
|
@ -1503,8 +1519,6 @@ class SettingViews {
|
|||
)
|
||||
}
|
||||
|
||||
// ---- Existing dropdowns ----
|
||||
|
||||
// ---- Dropdown for orientation ----
|
||||
@Composable
|
||||
fun OrientationDropdown(
|
||||
|
|
@ -1532,7 +1546,38 @@ class SettingViews {
|
|||
)
|
||||
}
|
||||
|
||||
// ---- Existing dropdowns ----
|
||||
@Composable
|
||||
fun VirtualControllerPresetDropdown(
|
||||
selectedPreset: VirtualControllerPreset,
|
||||
onPresetSelected: (VirtualControllerPreset) -> Unit
|
||||
) {
|
||||
val options = listOf(
|
||||
VirtualControllerPreset.Default,
|
||||
VirtualControllerPreset.Layout2,
|
||||
VirtualControllerPreset.Layout3,
|
||||
VirtualControllerPreset.Layout4,
|
||||
VirtualControllerPreset.Layout5,
|
||||
VirtualControllerPreset.Layout6
|
||||
)
|
||||
|
||||
DropdownSelector(
|
||||
label = "Controller Layout",
|
||||
selectedValue = selectedPreset,
|
||||
options = options,
|
||||
getDisplayText = { opt ->
|
||||
when (opt) {
|
||||
VirtualControllerPreset.Default -> "Default"
|
||||
VirtualControllerPreset.Layout2 -> "Layout 2"
|
||||
VirtualControllerPreset.Layout3 -> "Layout 3"
|
||||
VirtualControllerPreset.Layout4 -> "Layout 4"
|
||||
VirtualControllerPreset.Layout5 -> "Layout 5"
|
||||
VirtualControllerPreset.Layout6 -> "Layout 6"
|
||||
}
|
||||
},
|
||||
onOptionSelected = onPresetSelected
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun MemoryModeDropdown(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue