thing in HUD

This commit is contained in:
legop3
2025-12-21 15:42:01 -05:00
parent 8e01dcaabd
commit 1b013ed91b
9 changed files with 116 additions and 37 deletions
+20
View File
@@ -6,6 +6,7 @@ import KeymapSettings from './KeymapSettings.jsx';
import GamepadMappingSettings from './GamepadMappingSettings.jsx';
import Tabs, { Tab, TabList, TabPanel, TabPanels } from './Tabs.jsx';
import SessionSnapshot from './SessionSnapshot.jsx';
import { useHudMapSetting } from '../hooks/useHudMapSetting.js';
const manualTabs = [
{ key: 'start', label: 'Start OI' },
@@ -21,6 +22,7 @@ export default function SettingsPanel() {
actions: { sendOiCommand, setSensorStream },
} = useControlSystem();
const canControl = Boolean(roverId);
const [hudMapDesktop, setHudMapDesktop] = useHudMapSetting();
const sensorButtons = useMemo(
() => [
@@ -39,6 +41,7 @@ export default function SettingsPanel() {
<TabList>
<Tab id="keybindings">Keybindings</Tab>
<Tab id="controller">Controller</Tab>
<Tab id="page">Page settings</Tab>
<Tab id="admin">Admin</Tab>
</TabList>
<TabPanels>
@@ -52,6 +55,23 @@ export default function SettingsPanel() {
<GamepadMappingSettings />
</div>
</TabPanel>
<TabPanel id="page">
<div className="space-y-0.5">
<section className="panel-section space-y-0.5 text-sm">
<p className="text-slate-400">HUD</p>
<label className="flex items-center gap-0.5 text-slate-200">
<input
type="checkbox"
className="accent-emerald-500"
checked={hudMapDesktop}
onChange={(e) => setHudMapDesktop(e.target.checked)}
/>
<span>Show top-down map in HUD (desktop)</span>
</label>
<p className="text-xs text-slate-500">Mobile HUD keeps the map on by default.</p>
</section>
</div>
</TabPanel>
<TabPanel id="admin">
<div className="space-y-0.5">
<section className="panel-section space-y-0.5 text-sm">
+11 -4
View File
@@ -1,6 +1,6 @@
import React from 'react';
function TopDownMap({ sensors = {}, variant = 'full', size: overrideSize }) {
function TopDownMap({ sensors = {}, variant = 'full', size: overrideSize, overlay = false }) {
const size = overrideSize || (variant === 'mini' ? 190 : 260);
const center = size / 2;
const innerCircle = center * 0.8; // keep proportions consistent as size changes
@@ -50,9 +50,16 @@ function TopDownMap({ sensors = {}, variant = 'full', size: overrideSize }) {
const maxLight = lightMaxSamples.length ? Math.max(...lightMaxSamples, 1200) : 1200;
return (
<div className="surface relative p-1" style={{ height: '100%', width: '100%', aspectRatio: '1 / 1' }}>
<div className="absolute left-1 top-1 text-xs text-slate-400">Top-down</div>
<div className="absolute right-1 top-1 text-[0.65rem] text-slate-500">0{maxLight}</div>
<div
className={`${overlay ? 'relative' : 'surface relative p-1'}`}
style={overlay ? { width: `${size}px`, height: `${size}px` } : { height: '100%', width: '100%', aspectRatio: '1 / 1' }}
>
{!overlay ? (
<>
<div className="absolute left-1 top-1 text-xs text-slate-400">Top-down</div>
<div className="absolute right-1 top-1 text-[0.65rem] text-slate-500">0{maxLight}</div>
</>
) : null}
<svg width="100%" height="100%" viewBox={`0 0 ${size} ${size}`} preserveAspectRatio="xMidYMid meet" className="mx-auto block">
<circle cx={center} cy={center} r={innerCircle} fill="#0f172a" stroke="#334155" strokeWidth="2" />
<WheelVisual
+42 -18
View File
@@ -1,5 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { WhepPlayer } from '../lib/whepPlayer.js';
import TopDownMap from './TopDownMap.jsx';
import { useHudMapSetting } from '../hooks/useHudMapSetting.js';
const RESTART_DELAY_MS = 2000;
const UNMUTE_RETRY_MS = 3000;
@@ -66,6 +68,9 @@ export default function VideoTile({
const sensors = telemetryFrame?.sensors;
const batteryCharge = sensors?.batteryChargeMah ?? null;
const desktopLayout = layoutFormat === 'desktop';
const mobileHud = !desktopLayout;
const [showHudMapDesktop, setShowHudMapDesktop] = useHudMapSetting();
const showHudMap = mobileHud ? true : showHudMapDesktop;
const batteryVisual = buildBatteryVisual(batteryCharge, batteryConfig);
// console.log('[BatteryBarDebug]', {
// frameSensors: sensors,
@@ -268,6 +273,7 @@ export default function VideoTile({
<audio ref={audioRef} autoPlay hidden />
<HudOverlay
frame={telemetryFrame}
sensors={sensors}
label={label}
status={renderedStatus}
audioStatus={renderedAudioStatus}
@@ -276,6 +282,8 @@ export default function VideoTile({
driverLabel={driverLabel}
battery={batteryVisual}
songNote={songNote}
showTopDown={showHudMap}
mobileHud={mobileHud}
/>
<OvercurrentOverlay motors={overcurrentMotors} />
<LowBatteryOverlay charge={batteryCharge} config={batteryConfig} />
@@ -329,6 +337,7 @@ function BatteryBarVertical({ visual }) {
function HudOverlay({
frame,
sensors,
label,
status,
audioStatus,
@@ -337,8 +346,9 @@ function HudOverlay({
driverLabel = null,
battery,
songNote = null,
showTopDown = false,
mobileHud = false,
}) {
const sensors = frame?.sensors;
const bumps = sensors?.bumpsAndWheelDrops || {};
const [now, setNow] = useState(() => Date.now());
@@ -383,12 +393,20 @@ function HudOverlay({
{driverLabel ? <span className="text-slate-300"> {driverLabel}</span> : null}
</div>
<div className="absolute top-0.5 left-1/2 flex -translate-x-1/2 gap-1 bg-black/70 text-[0.6rem] font-medium text-slate-200">
<div className={`${desktopLayout ? 'px-1 py-0.5' : 'px-0.5 py-0 text-nowrap'} ${bumps.bumpLeft ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Left bump</div>
<div className={`${desktopLayout ? 'px-1 py-0.5' : 'px-0.5 py-0 text-nowrap'} ${bumps.wheelDropLeft ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Left wheel drop</div>
<div className={`${desktopLayout ? 'px-1 py-0.5' : 'px-0.5 py-0 text-nowrap'} ${bumps.wheelDropRight ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Right wheel drop</div>
<div className={`${desktopLayout ? 'px-1 py-0.5' : 'px-0.5 py-0 text-nowrap'} ${bumps.bumpRight ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Right bump</div>
{showTopDown ? (
<div
className="pointer-events-none absolute right-1 top-1"
style={{
width: '240px',
height: '240px',
opacity: 0.7,
transform: `scale(${mobileHud ? 0.55 : 0.7})`,
transformOrigin: 'top right',
}}
>
<TopDownMap sensors={sensors} size={240} overlay />
</div>
) : null}
</div>
);
}
@@ -400,26 +418,32 @@ function HudOverlay({
{audioStatus ? <div>Audio: {audioStatus}</div> : null}
</div>
{songNote != null ? (
<div className="absolute right-1 top-1 rounded bg-black/70 px-1 py-0.25 text-[0.65rem] font-semibold text-emerald-200">
<div className="absolute bottom-1 right-1 rounded bg-black/70 px-1 py-0.25 text-[0.65rem] font-semibold text-emerald-200">
Song {formatNoteLabel(songNote)} <span className="text-slate-400">({songNote})</span>
</div>
) : null}
<div className="absolute bottom-0.5 left-1/2 flex -translate-x-1/2 gap-0.5 bg-black/80 px-0.5 py-0.5 text-slate-100">
<span>Rover: "{label || 'Unnamed Rover'}"</span>
{/* <span>{pulse ? 'Sensors active' : 'No recent sensors'}</span> */}
</div>
</div>
{/* bump and wheel drops bar */}
<div className="absolute top-0.5 left-1/2 flex -translate-x-1/2 gap-1 bg-black/70 text-[0.6rem] font-medium text-slate-200">
<div className={`${desktopLayout ? 'px-1 py-0.5':'px-0.5 py-0 text-nowrap'} ${bumps.bumpLeft ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Left bump</div>
<div className={`${desktopLayout ? 'px-1 py-0.5':'px-0.5 py-0 text-nowrap'} ${bumps.wheelDropLeft ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Left wheel drop</div>
<div className={`${desktopLayout ? 'px-1 py-0.5':'px-0.5 py-0 text-nowrap'} ${bumps.wheelDropRight ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Right wheel drop</div>
<div className={`${desktopLayout ? 'px-1 py-0.5':'px-0.5 py-0 text-nowrap'} ${bumps.bumpRight ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Right bump</div>
{showTopDown ? (
<div
className="pointer-events-none absolute right-1 top-1"
style={{
width: '240px',
height: '240px',
opacity: 0.7,
transform: `scale(${mobileHud ? 0.55 : 0.7})`,
transformOrigin: 'top right',
}}
>
<TopDownMap sensors={sensors} size={240} overlay />
</div>
) : null}
</div>
</div>
);
}
);
}
const OVERCURRENT_LABELS = {
leftWheel: 'Left wheel',