Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions app/src/main/java/app/gamenative/PrefManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,38 @@ object PrefManager {
setPref(PORTRAIT_MODE, value)
}

private val CONTROLS_GYRO_MODE = stringPreferencesKey("controls_gyro_mode")
var controlsGyroMode: String
get() = getPref(CONTROLS_GYRO_MODE, "disabled")
set(value) {
setPref(CONTROLS_GYRO_MODE, value)
}

fun setGyroMode(value: String) {
controlsGyroMode = value
}

private val CONTROLS_GYRO_LAST_TARGET = intPreferencesKey("controls_gyro_last_target")
var controlsGyroLastTarget: Int
get() = getPref(CONTROLS_GYRO_LAST_TARGET, 1).coerceIn(1, 3)
set(value) {
setPref(CONTROLS_GYRO_LAST_TARGET, value.coerceIn(1, 3))
}

private val CONTROLS_GYRO_INVERT_X = booleanPreferencesKey("controls_gyro_invert_x")
var controlsGyroInvertX: Boolean
get() = getPref(CONTROLS_GYRO_INVERT_X, false)
set(value) {
setPref(CONTROLS_GYRO_INVERT_X, value)
}

private val CONTROLS_GYRO_INVERT_Y = booleanPreferencesKey("controls_gyro_invert_y")
var controlsGyroInvertY: Boolean
get() = getPref(CONTROLS_GYRO_INVERT_Y, false)
set(value) {
setPref(CONTROLS_GYRO_INVERT_Y, value)
}

private val BOX_86_VERSION = stringPreferencesKey("box86_version")
var box86Version: String
get() = getPref(BOX_86_VERSION, DefaultVersion.BOX86)
Expand Down
110 changes: 109 additions & 1 deletion app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
Expand Down Expand Up @@ -220,6 +221,16 @@ fun QuickMenu(
performanceHudConfig: PerformanceHudConfig = PerformanceHudConfig(),
onPerformanceHudConfigChanged: (PerformanceHudConfig) -> Unit = {},
hasPhysicalController: Boolean = false,
gyroEnabled: Boolean = false,
onGyroEnabledChanged: (Boolean) -> Unit = {},
gyroMapping: Int = 1,
onGyroMappingChanged: (Int) -> Unit = {},
gyroSensitivity: Float = 0.35f,
onGyroSensitivityChanged: (Float) -> Unit = {},
gyroInvertX: Boolean = false,
gyroInvertY: Boolean = false,
onGyroInvertXChanged: (Boolean) -> Unit = {},
onGyroInvertYChanged: (Boolean) -> Unit = {},
activeToggleIds: Set<Int> = emptySet(),
modifier: Modifier = Modifier,
) {
Expand Down Expand Up @@ -492,7 +503,7 @@ fun QuickMenu(
}
}

else -> {
QuickMenuTab.CONTROLLER -> {
Column(
modifier = Modifier
.fillMaxSize()
Expand All @@ -512,6 +523,18 @@ fun QuickMenu(
focusRequester = if (index == 0) controllerItemFocusRequester else null,
)
}
ControllerGyroSection(
gyroEnabled = gyroEnabled,
onGyroEnabledChanged = onGyroEnabledChanged,
gyroMapping = gyroMapping,
onGyroMappingChanged = onGyroMappingChanged,
gyroSensitivity = gyroSensitivity,
onGyroSensitivityChanged = onGyroSensitivityChanged,
gyroInvertX = gyroInvertX,
onGyroInvertXChanged = onGyroInvertXChanged,
gyroInvertY = gyroInvertY,
onGyroInvertYChanged = onGyroInvertYChanged,
)
}
}
}
Expand All @@ -537,6 +560,91 @@ fun QuickMenu(
}
}

@Composable
private fun ControllerGyroSection(
gyroEnabled: Boolean,
onGyroEnabledChanged: (Boolean) -> Unit,
gyroMapping: Int,
onGyroMappingChanged: (Int) -> Unit,
gyroSensitivity: Float,
onGyroSensitivityChanged: (Float) -> Unit,
gyroInvertX: Boolean,
onGyroInvertXChanged: (Boolean) -> Unit,
gyroInvertY: Boolean,
onGyroInvertYChanged: (Boolean) -> Unit,
) {
val accentColor = PluviaTheme.colors.accentPurple
Spacer(modifier = Modifier.height(8.dp))
QuickMenuToggleRow(
title = stringResource(R.string.quick_menu_tab_gyro),
subtitle = stringResource(R.string.controller_gyro_mode_subtitle),
enabled = gyroEnabled,
onToggle = { onGyroEnabledChanged(!gyroEnabled) },
accentColor = accentColor,
)
Spacer(modifier = Modifier.height(8.dp))
Column(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Text(
text = stringResource(R.string.controller_gyro_mapping),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Medium,
)
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
QuickMenuChoiceChip(
text = stringResource(R.string.left_stick),
selected = gyroMapping == 1,
accentColor = accentColor,
onClick = { onGyroMappingChanged(1) },
)
QuickMenuChoiceChip(
text = stringResource(R.string.right_stick),
selected = gyroMapping == 2,
accentColor = accentColor,
onClick = { onGyroMappingChanged(2) },
)
QuickMenuChoiceChip(
text = stringResource(R.string.mouse),
selected = gyroMapping == 3,
accentColor = accentColor,
onClick = { onGyroMappingChanged(3) },
)
}
}
Spacer(modifier = Modifier.height(8.dp))
QuickMenuAdjustmentRow(
title = stringResource(R.string.controller_gyro_sensitivity),
valueText = stringResource(
R.string.controller_gyro_sensitivity_value,
(gyroSensitivity * 100f).roundToInt(),
),
progress = normalizedProgress(gyroSensitivity, 0.1f, 2.0f),
onDecrease = { onGyroSensitivityChanged((gyroSensitivity - 0.05f).coerceIn(0.1f, 2.0f)) },
onIncrease = { onGyroSensitivityChanged((gyroSensitivity + 0.05f).coerceIn(0.1f, 2.0f)) },
accentColor = accentColor,
)
Spacer(modifier = Modifier.height(8.dp))
QuickMenuToggleRow(
title = stringResource(R.string.controller_gyro_invert_x),
enabled = gyroInvertX,
onToggle = { onGyroInvertXChanged(!gyroInvertX) },
accentColor = accentColor,
)
QuickMenuToggleRow(
title = stringResource(R.string.controller_gyro_invert_y),
enabled = gyroInvertY,
onToggle = { onGyroInvertYChanged(!gyroInvertY) },
accentColor = accentColor,
)
Spacer(modifier = Modifier.height(12.dp))
}

@Composable
private fun PerformanceHudQuickMenuTab(
isPerformanceHudEnabled: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
Expand Down Expand Up @@ -194,6 +195,30 @@ private val isExiting = AtomicBoolean(false)
private const val EXIT_PROCESS_TIMEOUT_MS = 30_000L
private const val EXIT_PROCESS_POLL_INTERVAL_MS = 1_000L
private const val EXIT_PROCESS_RESPONSE_TIMEOUT_MS = 2_000L
private const val PREF_CONTROLS_GYRO_MODE = "controls_gyro_mode"
private const val PREF_CONTROLS_GYRO_SENSITIVITY = "controls_gyro_sensitivity"
private const val GYRO_MODE_DISABLED = 0
private const val GYRO_MODE_LEFT_STICK = 1
private const val GYRO_MODE_RIGHT_STICK = 2
private const val GYRO_MODE_MOUSE = 3

private fun parseGyroMode(value: String?): Int {
return when (value?.lowercase(Locale.getDefault())) {
"left_stick" -> GYRO_MODE_LEFT_STICK
"right_stick" -> GYRO_MODE_RIGHT_STICK
"mouse" -> GYRO_MODE_MOUSE
else -> GYRO_MODE_DISABLED
}
}

private fun gyroModeToPrefValue(mode: Int): String {
return when (mode) {
GYRO_MODE_LEFT_STICK -> "left_stick"
GYRO_MODE_RIGHT_STICK -> "right_stick"
GYRO_MODE_MOUSE -> "mouse"
else -> "disabled"
}
}

private data class XServerViewReleaseBinding(
val xServerView: XServerView,
Expand Down Expand Up @@ -378,6 +403,18 @@ fun XServerScreen(
var hasPhysicalMouse by remember { mutableStateOf(false) }
var hasInternalTouchpad by remember { mutableStateOf(false) }
var hasUpdatedScreenGamepad by remember { mutableStateOf(false) }
var controlsGyroMode by remember {
mutableStateOf(parseGyroMode(PrefManager.getString(PREF_CONTROLS_GYRO_MODE, "disabled")))
}
var controlsGyroLastTarget by remember {
val mode = parseGyroMode(PrefManager.getString(PREF_CONTROLS_GYRO_MODE, "disabled"))
mutableIntStateOf(if (mode != GYRO_MODE_DISABLED) mode else PrefManager.controlsGyroLastTarget.coerceIn(1, 3))
}
var controlsGyroSensitivity by remember {
mutableStateOf(PrefManager.getFloat(PREF_CONTROLS_GYRO_SENSITIVITY, 0.35f).coerceIn(0.1f, 2.0f))
}
var controlsGyroInvertX by remember { mutableStateOf(PrefManager.controlsGyroInvertX) }
var controlsGyroInvertY by remember { mutableStateOf(PrefManager.controlsGyroInvertY) }
var isPerformanceHudEnabled by remember { mutableStateOf(PrefManager.showFps) }

fun loadPerformanceHudConfig(): PerformanceHudConfig {
Expand Down Expand Up @@ -1122,8 +1159,13 @@ fun XServerScreen(
} else {
var handled = false
if (isGamepad && it.event != null) {
// Physical handler and InputControlsView share the same joystick pipeline; do not
// call both or axes/triggers are applied twice. Match onKeyEvent: physical first,
// overlay only if nothing consumed (e.g. no PhysicalControllerHandler yet).
handled = physicalControllerHandler?.onGenericMotionEvent(it.event!!) == true
if (!handled) handled = PluviaApp.inputControlsView?.onGenericMotionEvent(it.event) == true
if (!handled) {
handled = PluviaApp.inputControlsView?.onGenericMotionEvent(it.event) == true
}
// Final fallback to WinHandler passthrough
if (!handled) handled = xServerView!!.getxServer().winHandler.onGenericMotionEvent(it.event)
}
Expand Down Expand Up @@ -1729,6 +1771,10 @@ fun XServerScreen(

// Set container-level shooter mode
setContainerShooterMode(container.isShooterMode)
setGyroMode(controlsGyroMode)
setGyroSensitivity(controlsGyroSensitivity)
setGyroInvertX(controlsGyroInvertX)
setGyroInvertY(controlsGyroInvertY)
}
PluviaApp.inputControlsView = icView

Expand Down Expand Up @@ -2024,6 +2070,53 @@ fun XServerScreen(
performanceHudConfig = performanceHudConfig,
onPerformanceHudConfigChanged = ::applyPerformanceHudConfig,
hasPhysicalController = hasPhysicalController,
gyroEnabled = controlsGyroMode != GYRO_MODE_DISABLED,
onGyroEnabledChanged = { enabled ->
if (enabled) {
val target = controlsGyroLastTarget.coerceIn(GYRO_MODE_LEFT_STICK, GYRO_MODE_MOUSE)
controlsGyroLastTarget = target
controlsGyroMode = target
PrefManager.controlsGyroLastTarget = target
PrefManager.setGyroMode(gyroModeToPrefValue(target))
PluviaApp.inputControlsView?.setGyroMode(target)
} else {
val target = controlsGyroLastTarget.coerceIn(GYRO_MODE_LEFT_STICK, GYRO_MODE_MOUSE)
controlsGyroLastTarget = target
PrefManager.controlsGyroLastTarget = target
controlsGyroMode = GYRO_MODE_DISABLED
PrefManager.setGyroMode("disabled")
PluviaApp.inputControlsView?.setGyroMode(GYRO_MODE_DISABLED)
}
},
gyroMapping = controlsGyroLastTarget.coerceIn(GYRO_MODE_LEFT_STICK, GYRO_MODE_MOUSE),
onGyroMappingChanged = { target ->
val t = target.coerceIn(GYRO_MODE_LEFT_STICK, GYRO_MODE_MOUSE)
controlsGyroLastTarget = t
PrefManager.controlsGyroLastTarget = t
if (controlsGyroMode != GYRO_MODE_DISABLED) {
controlsGyroMode = t
PrefManager.setGyroMode(gyroModeToPrefValue(t))
PluviaApp.inputControlsView?.setGyroMode(t)
}
},
gyroSensitivity = controlsGyroSensitivity,
onGyroSensitivityChanged = { sensitivity ->
controlsGyroSensitivity = sensitivity
PrefManager.setFloat(PREF_CONTROLS_GYRO_SENSITIVITY, sensitivity)
PluviaApp.inputControlsView?.setGyroSensitivity(sensitivity)
},
gyroInvertX = controlsGyroInvertX,
gyroInvertY = controlsGyroInvertY,
onGyroInvertXChanged = { invert ->
controlsGyroInvertX = invert
PrefManager.controlsGyroInvertX = invert
PluviaApp.inputControlsView?.setGyroInvertX(invert)
},
onGyroInvertYChanged = { invert ->
controlsGyroInvertY = invert
PrefManager.controlsGyroInvertY = invert
PluviaApp.inputControlsView?.setGyroInvertY(invert)
},
activeToggleIds = buildSet {
if (areControlsVisible) add(QuickMenuAction.INPUT_CONTROLS)
},
Expand Down Expand Up @@ -2356,7 +2449,6 @@ private fun showInputControls(profile: ControlsProfile, winHandler: WinHandler,
private fun hideInputControls() {
PluviaApp.inputControlsView?.setShowTouchscreenControls(false)
PluviaApp.inputControlsView?.setVisibility(View.GONE)
PluviaApp.inputControlsView?.setProfile(null)

PluviaApp.touchpadView?.setSensitivity(1.0f)
PluviaApp.touchpadView?.setPointerButtonLeftEnabled(true)
Expand Down
Loading
Loading