keyboard speed controls

This commit is contained in:
legop3
2025-12-06 01:07:01 -05:00
parent 2b1122dce9
commit b72c58452e
10 changed files with 126 additions and 19 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -11,8 +11,8 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-CB1QIk24.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-4BBPmLYK.css">
<script type="module" crossorigin src="/assets/index-BCyA38CU.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DusXB4R9.css">
</head>
<body>
<div id="root"></div>
+79
View File
@@ -2,6 +2,8 @@ import { useMemo, useState, useEffect, useCallback } from 'react';
import { useControlSystem } from '../controls/index.js';
import { DEFAULT_KEYMAP } from '../controls/constants.js';
import { canonicalizeKeyInput, formatKeyLabel } from '../controls/keymapUtils.js';
import { useSettingsNamespace } from '../settings/index.js';
import { INPUT_SETTINGS_DEFAULTS } from '../settings/namespaces.js';
const KEY_ACTIONS = [
{ id: 'driveForward', label: 'Drive Forward', group: 'Driving' },
@@ -33,6 +35,12 @@ function groupActions(actions) {
}, {});
}
function clampSpeed(value, fallback) {
const num = Number(value);
if (!Number.isFinite(num)) return fallback;
return Math.max(0, Math.min(500, num));
}
function useKeyCapture(onCapture) {
const [active, setActive] = useState(null);
@@ -57,11 +65,44 @@ function useKeyCapture(onCapture) {
return { active, startCapture: setActive, cancel: () => setActive(null) };
}
function SpeedField({ label, description, value, onChange }) {
return (
<label className="surface-muted block p-0.5">
<div className="flex items-center justify-between text-xs text-slate-300">
<span className="font-semibold text-slate-100">{label}</span>
<input
type="number"
min={0}
max={500}
value={value}
onChange={(event) => onChange(event.target.value)}
className="w-16 rounded border border-slate-700 bg-slate-900 px-1 py-[2px] text-right text-[0.75rem] font-mono text-slate-100"
/>
</div>
{description && <p className="text-[0.65rem] text-slate-500">{description}</p>}
<div className="mt-0.5 flex items-center gap-0.5">
<input
type="range"
min={0}
max={500}
step={5}
value={value}
onChange={(event) => onChange(event.target.value)}
className="h-2 flex-1 accent-emerald-400"
/>
<span className="w-12 text-right text-[0.75rem] font-mono text-slate-400">{value}</span>
</div>
</label>
);
}
export default function KeymapSettings() {
const {
state: { keymap },
actions: { updateKeyBinding, resetKeyBindings },
} = useControlSystem();
const { value: inputSettings, save: saveInputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
const keyboardSpeeds = inputSettings.keyboard ?? INPUT_SETTINGS_DEFAULTS.keyboard;
const grouped = useMemo(() => groupActions(KEY_ACTIONS), []);
const { active, startCapture, cancel } = useKeyCapture((actionId, value) => {
updateKeyBinding(actionId, value);
@@ -72,6 +113,21 @@ export default function KeymapSettings() {
[keymap],
);
const updateKeyboardSpeed = useCallback(
(key, nextValue) => {
const defaults = INPUT_SETTINGS_DEFAULTS.keyboard;
const clamped = clampSpeed(nextValue, defaults[key]);
saveInputSettings((prev) => ({
...(prev ?? {}),
keyboard: {
...(prev?.keyboard ?? defaults),
[key]: clamped,
},
}));
},
[saveInputSettings],
);
return (
<section className="panel-section space-y-0.5 text-sm">
<div className="flex items-center justify-between">
@@ -83,6 +139,29 @@ export default function KeymapSettings() {
Reset defaults
</button>
</div>
<div className="space-y-0.5 surface">
<p className="text-[0.7rem] text-slate-500">Keyboard speeds</p>
<div className="space-y-0.5">
<SpeedField
label="Base speed"
description="Normal driving speed"
value={keyboardSpeeds.baseSpeed}
onChange={(value) => updateKeyboardSpeed('baseSpeed', value)}
/>
<SpeedField
label="Turbo speed"
description="Used when holding the boost modifier"
value={keyboardSpeeds.turboSpeed}
onChange={(value) => updateKeyboardSpeed('turboSpeed', value)}
/>
<SpeedField
label="Precision speed"
description="Used when holding the precision modifier"
value={keyboardSpeeds.precisionSpeed}
onChange={(value) => updateKeyboardSpeed('precisionSpeed', value)}
/>
</div>
</div>
<div className="space-y-0.5">
{Object.entries(grouped).map(([group, actions]) => (
<div key={group} className="space-y-0.5 surface">
+1 -1
View File
@@ -122,7 +122,7 @@ export function ControlSystemProvider({ children }) {
const setDriveVector = useCallback(
(vector, meta = {}) => {
const computed = computeDifferentialSpeeds(vector);
const computed = computeDifferentialSpeeds(vector, meta.speedOptions);
dispatch({
type: 'control/update-drive',
payload: { ...computed, source: meta.source ?? null },
@@ -2,12 +2,20 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useControlSystem } from '../ControlContext.jsx';
import { useChat } from '../../context/ChatContext.jsx';
import { normalizeKeymapEntries, tokensForEvent } from '../keymapUtils.js';
import { useSettingsNamespace } from '../../settings/index.js';
import { INPUT_SETTINGS_DEFAULTS } from '../../settings/namespaces.js';
const SOURCE = 'keyboard';
const ZERO_VECTOR = { x: 0, y: 0, boost: false };
const ZERO_AUX = { main: 0, side: 0, vacuum: 0 };
const SERVO_REPEAT_MS = 110;
function clampSpeed(value, fallback) {
const num = Number(value);
if (!Number.isFinite(num)) return fallback;
return Math.max(0, Math.min(500, num));
}
function shouldIgnoreEvent(event) {
const target = event.target;
if (!target) return false;
@@ -90,6 +98,7 @@ export default function KeyboardInputManager() {
},
} = useControlSystem();
const { focusChat, blurChat, isChatFocused } = useChat();
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
const keymap = useMemo(() => normalizeKeymapEntries(state.keymap), [state.keymap]);
const actionTokens = useMemo(() => {
const tokens = new Set();
@@ -99,6 +108,15 @@ export default function KeyboardInputManager() {
});
return tokens;
}, [keymap]);
const keyboardSpeeds = useMemo(() => {
const defaults = INPUT_SETTINGS_DEFAULTS.keyboard;
const current = inputSettings?.keyboard ?? {};
return {
baseSpeed: clampSpeed(current.baseSpeed, defaults.baseSpeed),
turboSpeed: clampSpeed(current.turboSpeed, defaults.turboSpeed),
precisionSpeed: clampSpeed(current.precisionSpeed, defaults.precisionSpeed),
};
}, [inputSettings?.keyboard]);
const servoStep = useMemo(
() => Math.abs(state.camera?.config?.nudgeDegrees || 1),
[state.camera?.config?.nudgeDegrees],
@@ -111,6 +129,11 @@ export default function KeyboardInputManager() {
const driveFromKeys = useCallback(() => {
const tokensSnapshot = new Set(activeTokensRef.current);
const boostActive = bindingActive(keymap.boostModifier, tokensSnapshot);
const slowActive = bindingActive(keymap.slowModifier, tokensSnapshot);
const speedOptions = slowActive
? { baseSpeed: keyboardSpeeds.precisionSpeed, boostSpeed: keyboardSpeeds.precisionSpeed }
: { baseSpeed: keyboardSpeeds.baseSpeed, boostSpeed: keyboardSpeeds.turboSpeed };
const vector = computeDriveVector(tokensSnapshot, keymap);
const aux = computeAuxMotors(tokensSnapshot, keymap);
if (
@@ -119,7 +142,7 @@ export default function KeyboardInputManager() {
vector.boost !== lastVectorRef.current.boost
) {
lastVectorRef.current = vector;
setDriveVector(vector, { source: SOURCE });
setDriveVector(vector, { source: SOURCE, speedOptions });
}
if (
aux.main !== lastAuxRef.current.main ||
@@ -134,7 +157,7 @@ export default function KeyboardInputManager() {
vector,
aux,
});
}, [keymap, registerInputState, setAuxMotors, setDriveVector]);
}, [keymap, keyboardSpeeds, registerInputState, setAuxMotors, setDriveVector]);
const stopServoLoop = useCallback(() => {
if (servoIntervalRef.current) {
+1 -1
View File
@@ -35,7 +35,7 @@ export const HELP_CONTENT = {
{
type: 'keyboard',
title: 'Keyboard controls',
footnote: 'Per-browser; adjust in Settings → Keybindings.',
footnote: 'Per-browser; adjust bindings and speeds in Settings → Keybindings.',
groups: [
{
id: 'movement',
+5
View File
@@ -1,4 +1,9 @@
export const INPUT_SETTINGS_DEFAULTS = {
keyboard: {
baseSpeed: 250,
turboSpeed: 400,
precisionSpeed: 125,
},
gamepad: {
driveDeadzone: 0.2,
cameraDeadzone: 0.25,