Skip to content

Commit 3e73314

Browse files
Added gamepad gyro aiming support
1 parent 277cc9c commit 3e73314

7 files changed

Lines changed: 616 additions & 8 deletions

File tree

app/src/main/java/app/gamenative/PrefManager.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,31 @@ object PrefManager {
670670
setPref(PORTRAIT_MODE, value)
671671
}
672672

673+
private val CONTROLS_GYRO_MODE = stringPreferencesKey("controls_gyro_mode")
674+
var controlsGyroMode: String
675+
get() = getPref(CONTROLS_GYRO_MODE, "disabled")
676+
set(value) {
677+
setPref(CONTROLS_GYRO_MODE, value)
678+
}
679+
680+
fun setGyroMode(value: String) {
681+
controlsGyroMode = value
682+
}
683+
684+
private val CONTROLS_GYRO_INVERT_X = booleanPreferencesKey("controls_gyro_invert_x")
685+
var controlsGyroInvertX: Boolean
686+
get() = getPref(CONTROLS_GYRO_INVERT_X, false)
687+
set(value) {
688+
setPref(CONTROLS_GYRO_INVERT_X, value)
689+
}
690+
691+
private val CONTROLS_GYRO_INVERT_Y = booleanPreferencesKey("controls_gyro_invert_y")
692+
var controlsGyroInvertY: Boolean
693+
get() = getPref(CONTROLS_GYRO_INVERT_Y, false)
694+
set(value) {
695+
setPref(CONTROLS_GYRO_INVERT_Y, value)
696+
}
697+
673698
private val BOX_86_VERSION = stringPreferencesKey("box86_version")
674699
var box86Version: String
675700
get() = getPref(BOX_86_VERSION, DefaultVersion.BOX86)

app/src/main/java/app/gamenative/ui/component/QuickMenu.kt

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,14 @@ fun QuickMenu(
220220
performanceHudConfig: PerformanceHudConfig = PerformanceHudConfig(),
221221
onPerformanceHudConfigChanged: (PerformanceHudConfig) -> Unit = {},
222222
hasPhysicalController: Boolean = false,
223+
gyroMode: Int = 0,
224+
onGyroModeChanged: (Int) -> Unit = {},
225+
gyroSensitivity: Float = 0.35f,
226+
onGyroSensitivityChanged: (Float) -> Unit = {},
227+
gyroInvertX: Boolean = false,
228+
gyroInvertY: Boolean = false,
229+
onGyroInvertXChanged: (Boolean) -> Unit = {},
230+
onGyroInvertYChanged: (Boolean) -> Unit = {},
223231
activeToggleIds: Set<Int> = emptySet(),
224232
modifier: Modifier = Modifier,
225233
) {
@@ -492,7 +500,7 @@ fun QuickMenu(
492500
}
493501
}
494502

495-
else -> {
503+
QuickMenuTab.CONTROLLER -> {
496504
Column(
497505
modifier = Modifier
498506
.fillMaxSize()
@@ -512,6 +520,16 @@ fun QuickMenu(
512520
focusRequester = if (index == 0) controllerItemFocusRequester else null,
513521
)
514522
}
523+
ControllerGyroSection(
524+
gyroMode = gyroMode,
525+
onGyroModeChanged = onGyroModeChanged,
526+
gyroSensitivity = gyroSensitivity,
527+
onGyroSensitivityChanged = onGyroSensitivityChanged,
528+
gyroInvertX = gyroInvertX,
529+
onGyroInvertXChanged = onGyroInvertXChanged,
530+
gyroInvertY = gyroInvertY,
531+
onGyroInvertYChanged = onGyroInvertYChanged,
532+
)
515533
}
516534
}
517535
}
@@ -537,6 +555,84 @@ fun QuickMenu(
537555
}
538556
}
539557

558+
@Composable
559+
private fun ControllerGyroSection(
560+
gyroMode: Int,
561+
onGyroModeChanged: (Int) -> Unit,
562+
gyroSensitivity: Float,
563+
onGyroSensitivityChanged: (Float) -> Unit,
564+
gyroInvertX: Boolean,
565+
onGyroInvertXChanged: (Boolean) -> Unit,
566+
gyroInvertY: Boolean,
567+
onGyroInvertYChanged: (Boolean) -> Unit,
568+
) {
569+
val accentColor = PluviaTheme.colors.accentPurple
570+
Spacer(modifier = Modifier.height(8.dp))
571+
QuickMenuSectionHeader(
572+
title = stringResource(R.string.quick_menu_tab_gyro),
573+
subtitle = stringResource(R.string.controller_gyro_mode_subtitle),
574+
)
575+
Column(
576+
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
577+
verticalArrangement = Arrangement.spacedBy(6.dp),
578+
) {
579+
Text(
580+
text = stringResource(R.string.controller_gyro_stick),
581+
style = MaterialTheme.typography.labelLarge,
582+
color = MaterialTheme.colorScheme.onSurface,
583+
fontWeight = FontWeight.Medium,
584+
)
585+
Row(
586+
horizontalArrangement = Arrangement.spacedBy(8.dp),
587+
) {
588+
QuickMenuChoiceChip(
589+
text = stringResource(R.string.disabled),
590+
selected = gyroMode == 0,
591+
accentColor = accentColor,
592+
onClick = { onGyroModeChanged(0) },
593+
)
594+
QuickMenuChoiceChip(
595+
text = stringResource(R.string.left_stick),
596+
selected = gyroMode == 1,
597+
accentColor = accentColor,
598+
onClick = { onGyroModeChanged(1) },
599+
)
600+
QuickMenuChoiceChip(
601+
text = stringResource(R.string.right_stick),
602+
selected = gyroMode == 2,
603+
accentColor = accentColor,
604+
onClick = { onGyroModeChanged(2) },
605+
)
606+
}
607+
}
608+
Spacer(modifier = Modifier.height(8.dp))
609+
QuickMenuAdjustmentRow(
610+
title = stringResource(R.string.controller_gyro_sensitivity),
611+
valueText = stringResource(
612+
R.string.controller_gyro_sensitivity_value,
613+
(gyroSensitivity * 100f).roundToInt(),
614+
),
615+
progress = normalizedProgress(gyroSensitivity, 0.1f, 2.0f),
616+
onDecrease = { onGyroSensitivityChanged((gyroSensitivity - 0.05f).coerceIn(0.1f, 2.0f)) },
617+
onIncrease = { onGyroSensitivityChanged((gyroSensitivity + 0.05f).coerceIn(0.1f, 2.0f)) },
618+
accentColor = accentColor,
619+
)
620+
Spacer(modifier = Modifier.height(8.dp))
621+
QuickMenuToggleRow(
622+
title = stringResource(R.string.controller_gyro_invert_x),
623+
enabled = gyroInvertX,
624+
onToggle = { onGyroInvertXChanged(!gyroInvertX) },
625+
accentColor = accentColor,
626+
)
627+
QuickMenuToggleRow(
628+
title = stringResource(R.string.controller_gyro_invert_y),
629+
enabled = gyroInvertY,
630+
onToggle = { onGyroInvertYChanged(!gyroInvertY) },
631+
accentColor = accentColor,
632+
)
633+
Spacer(modifier = Modifier.height(12.dp))
634+
}
635+
540636
@Composable
541637
private fun PerformanceHudQuickMenuTab(
542638
isPerformanceHudEnabled: Boolean,

app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,27 @@ private val isExiting = AtomicBoolean(false)
194194
private const val EXIT_PROCESS_TIMEOUT_MS = 30_000L
195195
private const val EXIT_PROCESS_POLL_INTERVAL_MS = 1_000L
196196
private const val EXIT_PROCESS_RESPONSE_TIMEOUT_MS = 2_000L
197+
private const val PREF_CONTROLS_GYRO_MODE = "controls_gyro_mode"
198+
private const val PREF_CONTROLS_GYRO_SENSITIVITY = "controls_gyro_sensitivity"
199+
private const val GYRO_MODE_DISABLED = 0
200+
private const val GYRO_MODE_LEFT_STICK = 1
201+
private const val GYRO_MODE_RIGHT_STICK = 2
202+
203+
private fun parseGyroMode(value: String?): Int {
204+
return when (value?.lowercase(Locale.getDefault())) {
205+
"left_stick" -> GYRO_MODE_LEFT_STICK
206+
"right_stick" -> GYRO_MODE_RIGHT_STICK
207+
else -> GYRO_MODE_DISABLED
208+
}
209+
}
210+
211+
private fun gyroModeToPrefValue(mode: Int): String {
212+
return when (mode) {
213+
GYRO_MODE_LEFT_STICK -> "left_stick"
214+
GYRO_MODE_RIGHT_STICK -> "right_stick"
215+
else -> "disabled"
216+
}
217+
}
197218

198219
private data class XServerViewReleaseBinding(
199220
val xServerView: XServerView,
@@ -378,6 +399,14 @@ fun XServerScreen(
378399
var hasPhysicalMouse by remember { mutableStateOf(false) }
379400
var hasInternalTouchpad by remember { mutableStateOf(false) }
380401
var hasUpdatedScreenGamepad by remember { mutableStateOf(false) }
402+
var controlsGyroMode by remember {
403+
mutableStateOf(parseGyroMode(PrefManager.getString(PREF_CONTROLS_GYRO_MODE, "disabled")))
404+
}
405+
var controlsGyroSensitivity by remember {
406+
mutableStateOf(PrefManager.getFloat(PREF_CONTROLS_GYRO_SENSITIVITY, 0.35f).coerceIn(0.1f, 2.0f))
407+
}
408+
var controlsGyroInvertX by remember { mutableStateOf(PrefManager.controlsGyroInvertX) }
409+
var controlsGyroInvertY by remember { mutableStateOf(PrefManager.controlsGyroInvertY) }
381410
var isPerformanceHudEnabled by remember { mutableStateOf(PrefManager.showFps) }
382411

383412
fun loadPerformanceHudConfig(): PerformanceHudConfig {
@@ -1123,8 +1152,13 @@ fun XServerScreen(
11231152
} else {
11241153
var handled = false
11251154
if (isGamepad && it.event != null) {
1155+
// Physical handler and InputControlsView share the same joystick pipeline; do not
1156+
// call both or axes/triggers are applied twice. Match onKeyEvent: physical first,
1157+
// overlay only if nothing consumed (e.g. no PhysicalControllerHandler yet).
11261158
handled = physicalControllerHandler?.onGenericMotionEvent(it.event!!) == true
1127-
if (!handled) handled = PluviaApp.inputControlsView?.onGenericMotionEvent(it.event) == true
1159+
if (!handled) {
1160+
handled = PluviaApp.inputControlsView?.onGenericMotionEvent(it.event) == true
1161+
}
11281162
// Final fallback to WinHandler passthrough
11291163
if (!handled) handled = xServerView!!.getxServer().winHandler.onGenericMotionEvent(it.event)
11301164
}
@@ -1746,6 +1780,10 @@ fun XServerScreen(
17461780

17471781
// Set container-level shooter mode
17481782
setContainerShooterMode(container.isShooterMode)
1783+
setGyroMode(controlsGyroMode)
1784+
setGyroSensitivity(controlsGyroSensitivity)
1785+
setGyroInvertX(controlsGyroInvertX)
1786+
setGyroInvertY(controlsGyroInvertY)
17491787
}
17501788
PluviaApp.inputControlsView = icView
17511789

@@ -2040,6 +2078,30 @@ fun XServerScreen(
20402078
performanceHudConfig = performanceHudConfig,
20412079
onPerformanceHudConfigChanged = ::applyPerformanceHudConfig,
20422080
hasPhysicalController = hasPhysicalController,
2081+
gyroMode = controlsGyroMode,
2082+
onGyroModeChanged = { mode ->
2083+
controlsGyroMode = mode
2084+
PrefManager.setGyroMode(gyroModeToPrefValue(mode))
2085+
PluviaApp.inputControlsView?.setGyroMode(mode)
2086+
},
2087+
gyroSensitivity = controlsGyroSensitivity,
2088+
onGyroSensitivityChanged = { sensitivity ->
2089+
controlsGyroSensitivity = sensitivity
2090+
PrefManager.setFloat(PREF_CONTROLS_GYRO_SENSITIVITY, sensitivity)
2091+
PluviaApp.inputControlsView?.setGyroSensitivity(sensitivity)
2092+
},
2093+
gyroInvertX = controlsGyroInvertX,
2094+
gyroInvertY = controlsGyroInvertY,
2095+
onGyroInvertXChanged = { invert ->
2096+
controlsGyroInvertX = invert
2097+
PrefManager.controlsGyroInvertX = invert
2098+
PluviaApp.inputControlsView?.setGyroInvertX(invert)
2099+
},
2100+
onGyroInvertYChanged = { invert ->
2101+
controlsGyroInvertY = invert
2102+
PrefManager.controlsGyroInvertY = invert
2103+
PluviaApp.inputControlsView?.setGyroInvertY(invert)
2104+
},
20432105
activeToggleIds = buildSet {
20442106
if (areControlsVisible) add(QuickMenuAction.INPUT_CONTROLS)
20452107
},
@@ -2372,7 +2434,6 @@ private fun showInputControls(profile: ControlsProfile, winHandler: WinHandler,
23722434
private fun hideInputControls() {
23732435
PluviaApp.inputControlsView?.setShowTouchscreenControls(false)
23742436
PluviaApp.inputControlsView?.setVisibility(View.GONE)
2375-
PluviaApp.inputControlsView?.setProfile(null)
23762437

23772438
PluviaApp.touchpadView?.setSensitivity(1.0f)
23782439
PluviaApp.touchpadView?.setPointerButtonLeftEnabled(true)

0 commit comments

Comments
 (0)