mirror of
https://git.ryujinx.app/kenji-nx/ryujinx.git
synced 2025-12-15 19:37:05 +00:00
Merge branch 'libryujinx_bionic_controller' into 'libryujinx_bionic'
Added 6 different Virtual Controller Presets See merge request kenji-nx/ryujinx!15
This commit is contained in:
commit
258a907b20
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_LEFT_STICK_PRESS_ID = 10001
|
||||||
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
|
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
|
||||||
|
|
||||||
class GameController(var activity: Activity) {
|
class GameController(var activity: Activity) : IGameController {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun init(context: Context, controller: GameController): View {
|
private fun init(context: Context, controller: GameController): View {
|
||||||
|
|
@ -80,12 +80,13 @@ class GameController(var activity: Activity) {
|
||||||
var leftGamePad: GamePad
|
var leftGamePad: GamePad
|
||||||
var rightGamePad: GamePad
|
var rightGamePad: GamePad
|
||||||
var controllerId: Int = -1
|
var controllerId: Int = -1
|
||||||
val isVisible: Boolean
|
override val isVisible: Boolean
|
||||||
get() = controllerView?.isVisible ?: false
|
get() = controllerView?.isVisible ?: false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
leftGamePad = GamePad(generateConfig(true), 16f, activity)
|
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
|
||||||
rightGamePad = GamePad(generateConfig(false), 16f, activity)
|
leftGamePad = GamePad(generateConfig(true, useSwitchLayout), 16f, activity)
|
||||||
|
rightGamePad = GamePad(generateConfig(false, useSwitchLayout), 16f, activity)
|
||||||
|
|
||||||
leftGamePad.primaryDialMaxSizeDp = 200f
|
leftGamePad.primaryDialMaxSizeDp = 200f
|
||||||
rightGamePad.primaryDialMaxSizeDp = 200f
|
rightGamePad.primaryDialMaxSizeDp = 200f
|
||||||
|
|
@ -96,14 +97,14 @@ class GameController(var activity: Activity) {
|
||||||
rightGamePad.gravityY = 1f
|
rightGamePad.gravityY = 1f
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setVisible(isVisible: Boolean) {
|
override fun setVisible(isVisible: Boolean) {
|
||||||
controllerView?.apply {
|
controllerView?.apply {
|
||||||
this.isVisible = isVisible
|
this.isVisible = isVisible
|
||||||
if (isVisible) connect()
|
if (isVisible) connect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connect() {
|
override fun connect() {
|
||||||
if (controllerId == -1)
|
if (controllerId == -1)
|
||||||
controllerId = KenjinxNative.inputConnectGamepad(0)
|
controllerId = KenjinxNative.inputConnectGamepad(0)
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +178,7 @@ suspend fun <T> Flow<T>.safeCollect(block: suspend (T) -> Unit) {
|
||||||
.collect { block(it) }
|
.collect { block(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateConfig(isLeft: Boolean): GamePadConfig {
|
private fun generateConfig(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
|
||||||
val distance = 0.3f
|
val distance = 0.3f
|
||||||
val buttonScale = 1f
|
val buttonScale = 1f
|
||||||
|
|
||||||
|
|
@ -293,20 +294,23 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
|
||||||
/* ringSegments = */ 12,
|
/* ringSegments = */ 12,
|
||||||
/* Primary (ABXY) */
|
/* Primary (ABXY) */
|
||||||
PrimaryDialConfig.PrimaryButtons(
|
PrimaryDialConfig.PrimaryButtons(
|
||||||
listOf(
|
if (useSwitchLayout) {
|
||||||
ButtonConfig(
|
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
|
||||||
GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null
|
listOf(
|
||||||
),
|
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
|
||||||
ButtonConfig(
|
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
|
||||||
GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null
|
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
|
||||||
),
|
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
|
||||||
ButtonConfig(
|
|
||||||
GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null
|
|
||||||
),
|
|
||||||
ButtonConfig(
|
|
||||||
GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null
|
|
||||||
)
|
)
|
||||||
),
|
} 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,
|
null,
|
||||||
0f,
|
0f,
|
||||||
true,
|
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 com.anggrayudi.storage.extension.launchOnUiThread
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import org.kenjinx.android.GameController
|
import org.kenjinx.android.IGameController
|
||||||
import org.kenjinx.android.GameHost
|
import org.kenjinx.android.GameHost
|
||||||
import org.kenjinx.android.Logging
|
import org.kenjinx.android.Logging
|
||||||
import org.kenjinx.android.MainActivity
|
import org.kenjinx.android.MainActivity
|
||||||
|
|
@ -30,7 +30,7 @@ class MainViewModel(val activity: MainActivity) {
|
||||||
var physicalControllerManager: PhysicalControllerManager? = null
|
var physicalControllerManager: PhysicalControllerManager? = null
|
||||||
var motionSensorManager: MotionSensorManager? = null
|
var motionSensorManager: MotionSensorManager? = null
|
||||||
var gameModel: GameModel? = null
|
var gameModel: GameModel? = null
|
||||||
var controller: GameController? = null
|
var controller: IGameController? = null
|
||||||
var performanceManager: PerformanceManager? = null
|
var performanceManager: PerformanceManager? = null
|
||||||
var selected: GameModel? = null
|
var selected: GameModel? = null
|
||||||
val loadGameModel: MutableState<GameModel?> = mutableStateOf(null)
|
val loadGameModel: MutableState<GameModel?> = mutableStateOf(null)
|
||||||
|
|
@ -422,7 +422,7 @@ class MainViewModel(val activity: MainActivity) {
|
||||||
frequenciesState?.let { PerformanceMonitor.getFrequencies(it) }
|
frequenciesState?.let { PerformanceMonitor.getFrequencies(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setGameController(controller: GameController) {
|
fun setGameController(controller: IGameController) {
|
||||||
this.controller = controller
|
this.controller = controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,13 @@ class QuickSettings(val activity: Activity) {
|
||||||
BottomMiddle, BottomLeft, BottomRight, TopMiddle, TopLeft, TopRight
|
BottomMiddle, BottomLeft, BottomRight, TopMiddle, TopLeft, TopRight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Virtual Controller Preset
|
||||||
|
enum class VirtualControllerPreset {
|
||||||
|
Default, Layout2, Layout3, Layout4, Layout5, Layout6
|
||||||
|
}
|
||||||
|
|
||||||
|
var virtualControllerPreset: VirtualControllerPreset
|
||||||
|
|
||||||
var orientationPreference: OrientationPreference
|
var orientationPreference: OrientationPreference
|
||||||
|
|
||||||
// --- Overlay Settings
|
// --- Overlay Settings
|
||||||
|
|
@ -94,6 +101,9 @@ class QuickSettings(val activity: Activity) {
|
||||||
resScale = sharedPref.getFloat("resScale", 1f)
|
resScale = sharedPref.getFloat("resScale", 1f)
|
||||||
maxAnisotropy = sharedPref.getFloat("maxAnisotropy", 0f)
|
maxAnisotropy = sharedPref.getFloat("maxAnisotropy", 0f)
|
||||||
useVirtualController = sharedPref.getBoolean("useVirtualController", true)
|
useVirtualController = sharedPref.getBoolean("useVirtualController", true)
|
||||||
|
virtualControllerPreset = VirtualControllerPreset.entries[
|
||||||
|
sharedPref.getInt("virtualControllerPreset", VirtualControllerPreset.Default.ordinal)
|
||||||
|
]
|
||||||
isGrid = sharedPref.getBoolean("isGrid", true)
|
isGrid = sharedPref.getBoolean("isGrid", true)
|
||||||
useSwitchLayout = sharedPref.getBoolean("useSwitchLayout", true)
|
useSwitchLayout = sharedPref.getBoolean("useSwitchLayout", true)
|
||||||
enableMotion = sharedPref.getBoolean("enableMotion", true)
|
enableMotion = sharedPref.getBoolean("enableMotion", true)
|
||||||
|
|
@ -151,6 +161,7 @@ class QuickSettings(val activity: Activity) {
|
||||||
putBoolean("enableTraceLogs", enableTraceLogs)
|
putBoolean("enableTraceLogs", enableTraceLogs)
|
||||||
putBoolean("enableDebugLogs", enableDebugLogs)
|
putBoolean("enableDebugLogs", enableDebugLogs)
|
||||||
putBoolean("enableGraphicsLogs", enableGraphicsLogs)
|
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
|
||||||
import compose.icons.cssggicons.ToolbarBottom
|
import compose.icons.cssggicons.ToolbarBottom
|
||||||
import org.kenjinx.android.GameController
|
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.GameHost
|
||||||
import org.kenjinx.android.Icons
|
import org.kenjinx.android.Icons
|
||||||
import org.kenjinx.android.MainActivity
|
import org.kenjinx.android.MainActivity
|
||||||
|
|
@ -48,6 +53,10 @@ import org.kenjinx.android.viewmodels.VSyncMode
|
||||||
import org.kenjinx.android.widgets.SimpleAlertDialog
|
import org.kenjinx.android.widgets.SimpleAlertDialog
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import android.net.Uri
|
||||||
|
import android.widget.Toast
|
||||||
|
import org.kenjinx.android.viewmodels.QuickSettings.VirtualControllerPreset
|
||||||
|
|
||||||
|
|
||||||
class GameViews {
|
class GameViews {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -155,7 +164,17 @@ class GameViews {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!showLoading.value) {
|
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
|
// --- Button at any corner/edge + transparency
|
||||||
Row(
|
Row(
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ import org.kenjinx.android.viewmodels.QuickSettings.OverlayMenuPosition // ← N
|
||||||
import org.kenjinx.android.SystemLanguage
|
import org.kenjinx.android.SystemLanguage
|
||||||
import org.kenjinx.android.RegionCode
|
import org.kenjinx.android.RegionCode
|
||||||
|
|
||||||
|
import org.kenjinx.android.viewmodels.QuickSettings.VirtualControllerPreset
|
||||||
|
|
||||||
class SettingViews {
|
class SettingViews {
|
||||||
companion object {
|
companion object {
|
||||||
const val EXPANSTION_TRANSITION_DURATION = 450
|
const val EXPANSTION_TRANSITION_DURATION = 450
|
||||||
|
|
@ -129,6 +131,9 @@ class SettingViews {
|
||||||
val isGrid = remember { mutableStateOf(true) }
|
val isGrid = remember { mutableStateOf(true) }
|
||||||
val useSwitchLayout = remember { mutableStateOf(true) }
|
val useSwitchLayout = remember { mutableStateOf(true) }
|
||||||
val enableMotion = remember { mutableStateOf(true) }
|
val enableMotion = remember { mutableStateOf(true) }
|
||||||
|
val vcPreset = remember {
|
||||||
|
mutableStateOf(QuickSettings(mainViewModel.activity).virtualControllerPreset)
|
||||||
|
}
|
||||||
val enablePerformanceMode = remember { mutableStateOf(true) }
|
val enablePerformanceMode = remember { mutableStateOf(true) }
|
||||||
val controllerStickSensitivity = remember { mutableFloatStateOf(1.0f) }
|
val controllerStickSensitivity = remember { mutableFloatStateOf(1.0f) }
|
||||||
val enableStubLogs = remember { mutableStateOf(true) }
|
val enableStubLogs = remember { mutableStateOf(true) }
|
||||||
|
|
@ -1224,6 +1229,17 @@ class SettingViews {
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
useVirtualController.SwitchSelector(label = "Use Virtual Controller")
|
useVirtualController.SwitchSelector(label = "Use Virtual Controller")
|
||||||
useSwitchLayout.SwitchSelector(label = "Use Switch Controller Layout")
|
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() }
|
val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
|
||||||
|
|
@ -1503,8 +1519,6 @@ class SettingViews {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Existing dropdowns ----
|
|
||||||
|
|
||||||
// ---- Dropdown for orientation ----
|
// ---- Dropdown for orientation ----
|
||||||
@Composable
|
@Composable
|
||||||
fun OrientationDropdown(
|
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
|
@Composable
|
||||||
fun MemoryModeDropdown(
|
fun MemoryModeDropdown(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue