@@ -39,6 +39,7 @@ import androidx.compose.runtime.MutableState
3939import androidx.compose.runtime.SideEffect
4040import androidx.compose.runtime.getValue
4141import androidx.compose.runtime.key
42+ import androidx.compose.runtime.mutableIntStateOf
4243import androidx.compose.runtime.mutableStateOf
4344import androidx.compose.runtime.remember
4445import androidx.compose.runtime.saveable.rememberSaveable
@@ -194,6 +195,30 @@ private val isExiting = AtomicBoolean(false)
194195private const val EXIT_PROCESS_TIMEOUT_MS = 30_000L
195196private const val EXIT_PROCESS_POLL_INTERVAL_MS = 1_000L
196197private const val EXIT_PROCESS_RESPONSE_TIMEOUT_MS = 2_000L
198+ private const val PREF_CONTROLS_GYRO_MODE = " controls_gyro_mode"
199+ private const val PREF_CONTROLS_GYRO_SENSITIVITY = " controls_gyro_sensitivity"
200+ private const val GYRO_MODE_DISABLED = 0
201+ private const val GYRO_MODE_LEFT_STICK = 1
202+ private const val GYRO_MODE_RIGHT_STICK = 2
203+ private const val GYRO_MODE_MOUSE = 3
204+
205+ private fun parseGyroMode (value : String? ): Int {
206+ return when (value?.lowercase(Locale .getDefault())) {
207+ " left_stick" -> GYRO_MODE_LEFT_STICK
208+ " right_stick" -> GYRO_MODE_RIGHT_STICK
209+ " mouse" -> GYRO_MODE_MOUSE
210+ else -> GYRO_MODE_DISABLED
211+ }
212+ }
213+
214+ private fun gyroModeToPrefValue (mode : Int ): String {
215+ return when (mode) {
216+ GYRO_MODE_LEFT_STICK -> " left_stick"
217+ GYRO_MODE_RIGHT_STICK -> " right_stick"
218+ GYRO_MODE_MOUSE -> " mouse"
219+ else -> " disabled"
220+ }
221+ }
197222
198223private data class XServerViewReleaseBinding (
199224 val xServerView : XServerView ,
@@ -378,6 +403,18 @@ fun XServerScreen(
378403 var hasPhysicalMouse by remember { mutableStateOf(false ) }
379404 var hasInternalTouchpad by remember { mutableStateOf(false ) }
380405 var hasUpdatedScreenGamepad by remember { mutableStateOf(false ) }
406+ var controlsGyroMode by remember {
407+ mutableStateOf(parseGyroMode(PrefManager .getString(PREF_CONTROLS_GYRO_MODE , " disabled" )))
408+ }
409+ var controlsGyroLastTarget by remember {
410+ val mode = parseGyroMode(PrefManager .getString(PREF_CONTROLS_GYRO_MODE , " disabled" ))
411+ mutableIntStateOf(if (mode != GYRO_MODE_DISABLED ) mode else PrefManager .controlsGyroLastTarget.coerceIn(1 , 3 ))
412+ }
413+ var controlsGyroSensitivity by remember {
414+ mutableStateOf(PrefManager .getFloat(PREF_CONTROLS_GYRO_SENSITIVITY , 0.35f ).coerceIn(0.1f , 2.0f ))
415+ }
416+ var controlsGyroInvertX by remember { mutableStateOf(PrefManager .controlsGyroInvertX) }
417+ var controlsGyroInvertY by remember { mutableStateOf(PrefManager .controlsGyroInvertY) }
381418 var isPerformanceHudEnabled by remember { mutableStateOf(PrefManager .showFps) }
382419
383420 fun loadPerformanceHudConfig (): PerformanceHudConfig {
@@ -1123,8 +1160,13 @@ fun XServerScreen(
11231160 } else {
11241161 var handled = false
11251162 if (isGamepad && it.event != null ) {
1163+ // Physical handler and InputControlsView share the same joystick pipeline; do not
1164+ // call both or axes/triggers are applied twice. Match onKeyEvent: physical first,
1165+ // overlay only if nothing consumed (e.g. no PhysicalControllerHandler yet).
11261166 handled = physicalControllerHandler?.onGenericMotionEvent(it.event!! ) == true
1127- if (! handled) handled = PluviaApp .inputControlsView?.onGenericMotionEvent(it.event) == true
1167+ if (! handled) {
1168+ handled = PluviaApp .inputControlsView?.onGenericMotionEvent(it.event) == true
1169+ }
11281170 // Final fallback to WinHandler passthrough
11291171 if (! handled) handled = xServerView!! .getxServer().winHandler.onGenericMotionEvent(it.event)
11301172 }
@@ -1746,6 +1788,10 @@ fun XServerScreen(
17461788
17471789 // Set container-level shooter mode
17481790 setContainerShooterMode(container.isShooterMode)
1791+ setGyroMode(controlsGyroMode)
1792+ setGyroSensitivity(controlsGyroSensitivity)
1793+ setGyroInvertX(controlsGyroInvertX)
1794+ setGyroInvertY(controlsGyroInvertY)
17491795 }
17501796 PluviaApp .inputControlsView = icView
17511797
@@ -2040,6 +2086,53 @@ fun XServerScreen(
20402086 performanceHudConfig = performanceHudConfig,
20412087 onPerformanceHudConfigChanged = ::applyPerformanceHudConfig,
20422088 hasPhysicalController = hasPhysicalController,
2089+ gyroEnabled = controlsGyroMode != GYRO_MODE_DISABLED ,
2090+ onGyroEnabledChanged = { enabled ->
2091+ if (enabled) {
2092+ val target = controlsGyroLastTarget.coerceIn(GYRO_MODE_LEFT_STICK , GYRO_MODE_MOUSE )
2093+ controlsGyroLastTarget = target
2094+ controlsGyroMode = target
2095+ PrefManager .controlsGyroLastTarget = target
2096+ PrefManager .setGyroMode(gyroModeToPrefValue(target))
2097+ PluviaApp .inputControlsView?.setGyroMode(target)
2098+ } else {
2099+ val target = controlsGyroLastTarget.coerceIn(GYRO_MODE_LEFT_STICK , GYRO_MODE_MOUSE )
2100+ controlsGyroLastTarget = target
2101+ PrefManager .controlsGyroLastTarget = target
2102+ controlsGyroMode = GYRO_MODE_DISABLED
2103+ PrefManager .setGyroMode(" disabled" )
2104+ PluviaApp .inputControlsView?.setGyroMode(GYRO_MODE_DISABLED )
2105+ }
2106+ },
2107+ gyroMapping = controlsGyroLastTarget.coerceIn(GYRO_MODE_LEFT_STICK , GYRO_MODE_MOUSE ),
2108+ onGyroMappingChanged = { target ->
2109+ val t = target.coerceIn(GYRO_MODE_LEFT_STICK , GYRO_MODE_MOUSE )
2110+ controlsGyroLastTarget = t
2111+ PrefManager .controlsGyroLastTarget = t
2112+ if (controlsGyroMode != GYRO_MODE_DISABLED ) {
2113+ controlsGyroMode = t
2114+ PrefManager .setGyroMode(gyroModeToPrefValue(t))
2115+ PluviaApp .inputControlsView?.setGyroMode(t)
2116+ }
2117+ },
2118+ gyroSensitivity = controlsGyroSensitivity,
2119+ onGyroSensitivityChanged = { sensitivity ->
2120+ controlsGyroSensitivity = sensitivity
2121+ PrefManager .setFloat(PREF_CONTROLS_GYRO_SENSITIVITY , sensitivity)
2122+ PluviaApp .inputControlsView?.setGyroSensitivity(sensitivity)
2123+ },
2124+ gyroInvertX = controlsGyroInvertX,
2125+ gyroInvertY = controlsGyroInvertY,
2126+ onGyroInvertXChanged = { invert ->
2127+ controlsGyroInvertX = invert
2128+ PrefManager .controlsGyroInvertX = invert
2129+ PluviaApp .inputControlsView?.setGyroInvertX(invert)
2130+ },
2131+ onGyroInvertYChanged = { invert ->
2132+ controlsGyroInvertY = invert
2133+ PrefManager .controlsGyroInvertY = invert
2134+ PluviaApp .inputControlsView?.setGyroInvertY(invert)
2135+ },
20432136 activeToggleIds = buildSet {
20442137 if (areControlsVisible) add(QuickMenuAction .INPUT_CONTROLS )
20452138 },
@@ -2372,7 +2465,6 @@ private fun showInputControls(profile: ControlsProfile, winHandler: WinHandler,
23722465private fun hideInputControls () {
23732466 PluviaApp .inputControlsView?.setShowTouchscreenControls(false )
23742467 PluviaApp .inputControlsView?.setVisibility(View .GONE )
2375- PluviaApp .inputControlsView?.setProfile(null )
23762468
23772469 PluviaApp .touchpadView?.setSensitivity(1.0f )
23782470 PluviaApp .touchpadView?.setPointerButtonLeftEnabled(true )
0 commit comments