mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
mobile controls fixes
This commit is contained in:
@@ -33,6 +33,7 @@ export default function HornControl({
|
||||
);
|
||||
const [waveform, setWaveform] = useState(hornSettings?.waveform || HORN_SETTINGS_DEFAULTS.waveform);
|
||||
const settingsRootRef = useRef(null);
|
||||
const activePointerIdRef = useRef(null);
|
||||
const [freqs, setFreqs] = useState(() => {
|
||||
const base = Array.isArray(hornSettings?.freqs) ? hornSettings.freqs : HORN_SETTINGS_DEFAULTS.freqs;
|
||||
return [...base, 0, 0, 0, 0].slice(0, 4).map((f) => clampFreq(f));
|
||||
@@ -129,16 +130,39 @@ export default function HornControl({
|
||||
|
||||
const handlePointerDown = (event) => {
|
||||
if (disabled) return;
|
||||
if (activePointerIdRef.current !== null) return;
|
||||
const tag = event.target?.tagName?.toLowerCase();
|
||||
if (tag === 'input' || tag === 'select' || tag === 'option' || tag === 'label' || tag === 'button') return;
|
||||
|
||||
/*
|
||||
Horn is a hold control, so it should claim exactly the finger that started
|
||||
it. Capturing that pointer prevents tiny mobile finger drift or nearby
|
||||
controls from making the horn feel delayed or randomly interrupted.
|
||||
*/
|
||||
event.preventDefault();
|
||||
activePointerIdRef.current = event.pointerId;
|
||||
event.currentTarget.setPointerCapture?.(event.pointerId);
|
||||
start();
|
||||
};
|
||||
|
||||
const handlePointerUp = (event) => {
|
||||
if (activePointerIdRef.current !== event.pointerId) return;
|
||||
const tag = event.target?.tagName?.toLowerCase();
|
||||
if (tag === 'input' || tag === 'select' || tag === 'option' || tag === 'label' || tag === 'button') return;
|
||||
event.preventDefault();
|
||||
activePointerIdRef.current = null;
|
||||
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
||||
stop();
|
||||
};
|
||||
|
||||
const handlePointerCancel = (event) => {
|
||||
if (activePointerIdRef.current !== event.pointerId) return;
|
||||
activePointerIdRef.current = null;
|
||||
stop();
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
activePointerIdRef.current = null;
|
||||
stop();
|
||||
};
|
||||
|
||||
@@ -164,10 +188,15 @@ export default function HornControl({
|
||||
aria-pressed={pressed}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerUp={handlePointerUp}
|
||||
onPointerLeave={stop}
|
||||
onPointerCancel={stop}
|
||||
onPointerCancel={handlePointerCancel}
|
||||
onLostPointerCapture={(event) => {
|
||||
if (activePointerIdRef.current === event.pointerId) {
|
||||
activePointerIdRef.current = null;
|
||||
stop();
|
||||
}
|
||||
}}
|
||||
onContextMenu={(event) => event.preventDefault()}
|
||||
onBlur={stop}
|
||||
onBlur={handleBlur}
|
||||
className={buttonClasses}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function NightVisionControl({
|
||||
isBoolean(nightVisionOn) ? nightVisionOn : null,
|
||||
);
|
||||
const suppressClickRef = useRef(false);
|
||||
const suppressClickTimerRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isBoolean(nightVisionOn)) {
|
||||
@@ -26,6 +27,15 @@ export default function NightVisionControl({
|
||||
}
|
||||
}, [nightVisionOn]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (suppressClickTimerRef.current) {
|
||||
clearTimeout(suppressClickTimerRef.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const hasState = isBoolean(optimistic);
|
||||
const displayOn = hasState ? optimistic : false;
|
||||
const statusLabel = hasState ? (displayOn ? 'On' : 'Off') : '—';
|
||||
@@ -52,15 +62,29 @@ export default function NightVisionControl({
|
||||
*/
|
||||
event.preventDefault();
|
||||
suppressClickRef.current = true;
|
||||
handleToggle();
|
||||
window.setTimeout(() => {
|
||||
if (suppressClickTimerRef.current) {
|
||||
clearTimeout(suppressClickTimerRef.current);
|
||||
}
|
||||
/*
|
||||
The next click normally clears this flag, but Safari may suppress the click
|
||||
completely after preventDefault(). The timer prevents a stale touch flag
|
||||
from swallowing a later keyboard or desktop click.
|
||||
*/
|
||||
suppressClickTimerRef.current = window.setTimeout(() => {
|
||||
suppressClickRef.current = false;
|
||||
}, 0);
|
||||
suppressClickTimerRef.current = null;
|
||||
}, 800);
|
||||
handleToggle();
|
||||
};
|
||||
|
||||
const handleClick = (event) => {
|
||||
if (suppressClickRef.current) {
|
||||
event.preventDefault();
|
||||
suppressClickRef.current = false;
|
||||
if (suppressClickTimerRef.current) {
|
||||
clearTimeout(suppressClickTimerRef.current);
|
||||
suppressClickTimerRef.current = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
handleToggle();
|
||||
|
||||
@@ -473,7 +473,7 @@ export function ControlSystemProvider({ children }) {
|
||||
(nightVisionOn) => {
|
||||
if (!pipeline.nightVision) return;
|
||||
if (typeof nightVisionOn === 'boolean') {
|
||||
const action = nightVisionOn ? 'off' : 'on';
|
||||
const action = nightVisionOn ? 'on' : 'off';
|
||||
pipeline.sendNightVision(action);
|
||||
} else {
|
||||
pipeline.sendNightVision('toggle');
|
||||
|
||||
Reference in New Issue
Block a user