Skip to content
Open
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
30 changes: 27 additions & 3 deletions app/src/main/java/app/gamenative/ui/component/QuickMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import app.gamenative.R
import app.gamenative.data.GyroSettings
import app.gamenative.powercontrol.PowerManager
import app.gamenative.ui.component.dialog.GyroSettingsDialog
import app.gamenative.ui.component.dialog.OnScreenControllerSettingsDialog
import app.gamenative.ui.component.dialog.ControlProfileLibraryDialog
import app.gamenative.ui.component.quickMenus.PowerControlQuickMenuTab
import app.gamenative.ui.data.PerformanceHudConfig
Expand All @@ -110,6 +111,7 @@ import app.gamenative.ui.theme.PluviaTheme
import app.gamenative.ui.util.adaptivePanelWidth
import app.gamenative.utils.MathUtils.normalizedProgress
import com.winlator.container.Container
import com.winlator.inputcontrols.ControlsProfile
import com.winlator.renderer.GLRenderer
import com.winlator.renderer.VulkanRenderer
import com.winlator.winhandler.ProcessInfo
Expand Down Expand Up @@ -539,6 +541,9 @@ fun QuickMenu(
// broken D8 codegen path).
val inviteMenu = remember(container?.id) { SteamInviteState.createIfAvailable(container) }
var showGyroSettingsDialog by rememberSaveable(container?.id) { mutableStateOf(false) }
var onScreenControllerSettingsProfile by remember(container?.id) {
mutableStateOf<ControlsProfile?>(null)
}
var showControlProfiles by rememberSaveable(container?.id) { mutableStateOf(false) }
var lastControllerFocusRequester by remember(container?.id) { mutableStateOf<FocusRequester?>(null) }
var controlProfileReturnFocusRequester by remember(container?.id) { mutableStateOf<FocusRequester?>(null) }
Expand Down Expand Up @@ -1122,7 +1127,9 @@ fun QuickMenu(
},
focusRequester = if (index == 0) controllerItemFocusRequester else null,
onFocused = { lastControllerFocusRequester = it },
secondaryIcon = if (item.id == QuickMenuAction.EDIT_PHYSICAL_CONTROLLER)
secondaryIcon = if (item.id == QuickMenuAction.INPUT_CONTROLS && item.id in activeToggleIds)
Icons.Default.Settings
else if (item.id == QuickMenuAction.EDIT_PHYSICAL_CONTROLLER)
Icons.Default.Settings
else if (item.id == QuickMenuAction.TOUCHSCREEN_MODE && touchscreenEnabled)
Icons.Default.Settings
Expand All @@ -1131,7 +1138,9 @@ fun QuickMenu(
else if (item.id == QuickMenuAction.GYRO && gyroEnabled)
Icons.Default.Settings
else null,
secondaryContentDescriptionResId = if (item.id == QuickMenuAction.EDIT_PHYSICAL_CONTROLLER)
secondaryContentDescriptionResId = if (item.id == QuickMenuAction.INPUT_CONTROLS && item.id in activeToggleIds)
R.string.on_screen_controller_settings
else if (item.id == QuickMenuAction.EDIT_PHYSICAL_CONTROLLER)
R.string.physical_controller_settings_title
else if (item.id == QuickMenuAction.TOUCHSCREEN_MODE && touchscreenEnabled)
R.string.gesture_settings_title
Expand All @@ -1140,7 +1149,9 @@ fun QuickMenu(
else if (item.id == QuickMenuAction.GYRO && gyroEnabled)
R.string.gyro_settings_title
else null,
onSecondaryClick = if (item.id == QuickMenuAction.EDIT_PHYSICAL_CONTROLLER)
onSecondaryClick = if (item.id == QuickMenuAction.INPUT_CONTROLS && item.id in activeToggleIds)
({ onScreenControllerSettingsProfile = PluviaApp.inputControlsView?.profile })
else if (item.id == QuickMenuAction.EDIT_PHYSICAL_CONTROLLER)
({ onItemSelected(QuickMenuAction.PHYSICAL_CONTROLLER_SETTINGS) })
else if (item.id == QuickMenuAction.TOUCHSCREEN_MODE && touchscreenEnabled)
onTouchGestureSettingsClick
Expand Down Expand Up @@ -1183,6 +1194,19 @@ fun QuickMenu(
)
}

onScreenControllerSettingsProfile?.let { profile ->
OnScreenControllerSettingsDialog(
initialCursorSpeed = profile.cursorSpeed,
onDismiss = { onScreenControllerSettingsProfile = null },
onSave = { speed ->
profile.cursorSpeed = speed
profile.save()
PluviaApp.touchpadView?.setSensitivity(profile.cursorSpeed)
onScreenControllerSettingsProfile = null
},
)
}

if (showControlProfiles && container != null) {
ControlProfileLibraryDialog(
container = container,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package app.gamenative.ui.component.dialog

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import app.gamenative.R
import app.gamenative.ui.theme.PluviaBackground
import com.winlator.inputcontrols.ControlsProfile
import java.util.Locale

internal val DEFAULT_MOUSE_SPEED = ControlsProfile.DEFAULT_CURSOR_SPEED
internal const val MIN_MOUSE_SPEED = 0.1f
internal const val MAX_MOUSE_SPEED = 3.0f

internal fun mouseSpeedOrDefault(value: Float): Float {
return value.takeIf { it.isFinite() && it > 0f } ?: DEFAULT_MOUSE_SPEED
}

internal fun mouseSpeedForSlider(value: Float): Float {
return mouseSpeedOrDefault(value).coerceIn(MIN_MOUSE_SPEED, MAX_MOUSE_SPEED)
}

internal fun mouseSpeedForSave(initialValue: Float, editedValue: Float, wasEdited: Boolean): Float {
return if (wasEdited) mouseSpeedForSlider(editedValue) else mouseSpeedOrDefault(initialValue)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OnScreenControllerSettingsDialog(
initialCursorSpeed: Float,
onDismiss: () -> Unit,
onSave: (Float) -> Unit,
) {
var cursorSpeed by remember(initialCursorSpeed) {
mutableFloatStateOf(mouseSpeedForSlider(initialCursorSpeed))
}
var cursorSpeedWasEdited by remember(initialCursorSpeed) { mutableStateOf(false) }
val locale = Locale.getDefault()

Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(
usePlatformDefaultWidth = false,
dismissOnBackPress = true,
dismissOnClickOutside = false,
),
) {
Scaffold(
modifier = Modifier.fillMaxSize(),
containerColor = PluviaBackground,
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
text = stringResource(R.string.on_screen_controller_settings),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
)
},
navigationIcon = {
IconButton(onClick = onDismiss) {
Icon(Icons.Default.Close, contentDescription = stringResource(R.string.close))
}
},
actions = {
IconButton(
onClick = {
cursorSpeed = DEFAULT_MOUSE_SPEED
cursorSpeedWasEdited = true
},
) {
Icon(
Icons.Default.Refresh,
contentDescription = stringResource(R.string.reset_mouse_speed),
)
}
IconButton(
onClick = {
onSave(mouseSpeedForSave(initialCursorSpeed, cursorSpeed, cursorSpeedWasEdited))
},
) {
Icon(Icons.Default.Check, contentDescription = stringResource(R.string.save))
}
},
)
},
) { padding ->
Column(
modifier = Modifier
.padding(padding)
.fillMaxSize()
.padding(bottom = 16.dp)
.verticalScroll(rememberScrollState()),
) {
SettingsDialogSectionHeader(stringResource(R.string.mouse))

SettingsSliderBlock(
title = stringResource(R.string.mouse_speed),
subtitle = stringResource(R.string.mouse_speed_subtitle),
value = cursorSpeed,
valueRange = MIN_MOUSE_SPEED..MAX_MOUSE_SPEED,
valueText = multiplierText(
mouseSpeedForSave(initialCursorSpeed, cursorSpeed, cursorSpeedWasEdited),
locale,
),
onValueChange = {
cursorSpeed = it
cursorSpeedWasEdited = true
},
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import app.gamenative.ui.theme.settingsTileColors
import app.gamenative.ui.theme.settingsTileColorsAlt
import com.alorma.compose.settings.ui.SettingsSwitch
import com.winlator.inputcontrols.Binding
import java.util.Locale
import kotlin.math.ceil
import kotlin.math.floor

Expand Down Expand Up @@ -252,6 +253,10 @@ fun SettingsSliderBlock(
if (compact) content() else GestureBlock { content() }
}

internal fun multiplierText(value: Float, locale: Locale): String {
return String.format(locale, "%.1fx", value)
}

@Composable
internal fun settingsSliderColors(showTickMarks: Boolean = true) = SliderDefaults.colors(
thumbColor = MaterialTheme.colorScheme.primary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,6 @@ private fun joystickBehaviorLabels(): List<String> = listOf(
stringResource(R.string.joystick_behavior_floating),
)

private fun multiplierText(value: Float, locale: Locale): String {
return String.format(locale, "%.1fx", value)
}

private fun pixelText(value: Float, locale: Locale): String {
return String.format(locale, "%.1f px", value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ class PhysicalControllerHandler(
if (mouseMoveOffset.x == 0f && mouseMoveOffset.y == 0f) return@synchronized

// Look up cursor speed dynamically so it updates when profile changes
val cursorSpeed = profile?.cursorSpeed ?: 1f
val cursorSpeed = profile?.cursorSpeed ?: ControlsProfile.DEFAULT_CURSOR_SPEED
val scaledX = mouseMoveOffset.x * 10 * cursorSpeed + mouseMoveRemainder.x
val scaledY = mouseMoveOffset.y * 10 * cursorSpeed + mouseMoveRemainder.y
val deltaX = scaledX.toInt()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3392,7 +3392,7 @@ private fun showInputControls(profile: ControlsProfile, winHandler: WinHandler,
}
}

PluviaApp.touchpadView?.setSensitivity(profile.getCursorSpeed() * 1.0f)
PluviaApp.touchpadView?.setSensitivity(profile.cursorSpeed)

}

Expand Down Expand Up @@ -3435,7 +3435,7 @@ private fun hideInputControls() {
PluviaApp.inputControlsView?.hideProfileForOverlay()
PluviaApp.xServerView?.getxServer()?.winHandler?.refreshControllerMappingsForHotplug()

PluviaApp.touchpadView?.setSensitivity(1.0f)
PluviaApp.touchpadView?.setSensitivity(ControlsProfile.DEFAULT_CURSOR_SPEED)
PluviaApp.touchpadView?.setPointerButtonLeftEnabled(true)
PluviaApp.touchpadView?.setPointerButtonRightEnabled(true)
PluviaApp.touchpadView?.isEnabled()?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ public static String[] names() {
private PointF currentPosition;
private RangeScroller scroller;
private CubicBezierInterpolator interpolator;
private final MouseDeltaAccumulator trackpadCursorX = new MouseDeltaAccumulator();
private final MouseDeltaAccumulator trackpadCursorY = new MouseDeltaAccumulator();
private Object touchTime;
private String shooterMovementType = "wasd";
private String shooterLookType = "mouse";
Expand All @@ -116,6 +118,27 @@ public ControlElement(InputControlsView inputControlsView) {
this.inputControlsView = inputControlsView;
}

static final class MouseDeltaAccumulator {
private float remainder;

int scale(float value, float multiplier) {
if (value == 0.0f) return 0;
float scaledValue = value * multiplier + remainder;
int wholePixels = (int)scaledValue;
remainder = scaledValue - wholePixels;
return wholePixels;
}

void reset() {
remainder = 0.0f;
}
}

private void resetTrackpadCursorRemainders() {
trackpadCursorX.reset();
trackpadCursorY.reset();
}

private static Object[] createBindingSources(int count) {
Object[] sources = new Object[count];
for (int i = 0; i < count; i++) sources[i] = new Object();
Expand Down Expand Up @@ -1293,6 +1316,7 @@ else if (type == Type.RANGE_BUTTON) {
}
else {
if (type == Type.TRACKPAD) {
resetTrackpadCursorRemainders();
if (currentPosition == null) currentPosition = new PointF();
currentPosition.set(x, y);
}
Expand Down Expand Up @@ -1382,8 +1406,12 @@ public boolean handleTouchMove(int pointerId, float x, float y) {
else if (type == Type.TRACKPAD) {
final boolean[] states = {deltaY <= -TRACKPAD_MIN_SPEED, deltaX >= TRACKPAD_MIN_SPEED, deltaY >= TRACKPAD_MIN_SPEED, deltaX <= -TRACKPAD_MIN_SPEED};
if (handleRadialMenuDirectionalMove(pointerId, states, x, y)) return true;
int cursorDx = 0;
int cursorDy = 0;
ControlsProfile activeProfile = inputControlsView.getProfile();
float cursorSpeed = activeProfile != null
? activeProfile.getCursorSpeed()
: ControlsProfile.DEFAULT_CURSOR_SPEED;
float cursorDeltaX = 0;
float cursorDeltaY = 0;

for (byte i = 0; i < 4; i++) {
float value = (i == 1 || i == 3 ? deltaX : deltaY);
Expand All @@ -1395,10 +1423,10 @@ else if (type == Type.TRACKPAD) {
value *= TouchpadView.CURSOR_ACCELERATION;
}
if (mouseMoveBinding == Binding.MOUSE_MOVE_LEFT || mouseMoveBinding == Binding.MOUSE_MOVE_RIGHT) {
cursorDx = Mathf.roundPoint(value);
cursorDeltaX = value;
}
else {
cursorDy = Mathf.roundPoint(value);
cursorDeltaY = value;
}
boolean nextState = states[i];
if (!bindingCombo.isSingleBinding() && this.states[i] != nextState) {
Expand Down Expand Up @@ -1446,6 +1474,8 @@ else if (this.states[i] != states[i]) {
}
}

int cursorDx = trackpadCursorX.scale(cursorDeltaX, cursorSpeed);
int cursorDy = trackpadCursorY.scale(cursorDeltaY, cursorSpeed);
if (cursorDx != 0 || cursorDy != 0) inputControlsView.getXServer().injectPointerMoveDelta(cursorDx, cursorDy);
}
else {
Expand Down Expand Up @@ -1525,6 +1555,8 @@ else if (!usesToggleSwitch() || selected) {
else if (type == Type.RANGE_BUTTON || type == Type.D_PAD || type == Type.STICK || type == Type.TRACKPAD) {
releaseActiveDirectionalStates();

if (type == Type.TRACKPAD) resetTrackpadCursorRemainders();

if (type == Type.RANGE_BUTTON) {
scroller.handleTouchUp();
}
Expand Down Expand Up @@ -1565,6 +1597,7 @@ else if (type == Type.BUTTON) {
}
else if (type == Type.RANGE_BUTTON || type == Type.D_PAD || type == Type.STICK || type == Type.TRACKPAD) {
releaseActiveDirectionalStates();
if (type == Type.TRACKPAD) resetTrackpadCursorRemainders();
if (type == Type.RANGE_BUTTON) scroller.cancelTouch();
currentPosition = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public static StickDigitalMode fromJsonName(String value) {

public final int id;
private String name;
private float cursorSpeed = DEFAULT_CURSOR_SPEED;
private volatile float cursorSpeed = DEFAULT_CURSOR_SPEED;
private float leftStickDeadzone = DEFAULT_STICK_DEADZONE;
private float rightStickDeadzone = DEFAULT_STICK_DEADZONE;
private float leftStickSensitivity = DEFAULT_STICK_SENSITIVITY;
Expand Down Expand Up @@ -161,7 +161,9 @@ public float getCursorSpeed() {
}

public void setCursorSpeed(float cursorSpeed) {
this.cursorSpeed = cursorSpeed;
this.cursorSpeed = Float.isFinite(cursorSpeed) && cursorSpeed > 0.0f
? cursorSpeed
: DEFAULT_CURSOR_SPEED;
}

private static float clampDeadzone(float value) {
Expand Down
Loading
Loading