From 28ab984185b8752f50f5a5aef3280bd7ce8cfc86 Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 4 Jun 2026 03:13:12 +0200 Subject: [PATCH] =?UTF-8?q?Wire=20MENU=20page-menu=20+=20HRZN=20HDG=20?= =?UTF-8?q?=E2=80=94=20last=20dead=20display=20keys=20now=20functional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MENU (bezel hard key) now opens a G1000-style PAGE MENU (Invert / Store / Delete flight plan) instead of only mirroring the sim command. HRZN HDG draws heading reference marks (N/E/S/W + ticks) along the attitude horizon, toggled from the PFD submenu. With TRAFFIC/NEXRAD/PROFILE/PATHWAY/APTSIGNS already wired, every softkey now does something; only ENT remains a pure sim-command mirror. Co-Authored-By: Claude Opus 4.8 --- web/src/App.jsx | 26 ++++++++++++++++++++++---- web/src/components/Bezel.jsx | 6 ++++-- web/src/components/PFD.jsx | 19 +++++++++++++++++-- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/web/src/App.jsx b/web/src/App.jsx index 454d81d..e935760 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -67,7 +67,7 @@ export default function App() { const [win, setWin] = useState(null); const toggleWin = (id) => setWin((w) => (w === id ? null : id)); const nrst = win === 'nrst', tmr = win === 'tmr', dme = win === 'dme', alerts = win === 'alerts'; - const fpl = win === 'fpl', dto = win === 'dto', proc = win === 'proc'; + const fpl = win === 'fpl', dto = win === 'dto', proc = win === 'proc', menu = win === 'menu'; // MFD map mode (base layer + overlays), switched via the Map-Opt softkeys. const [mapMode, setMapMode] = useState({ base: 'topo' }); // VNAV profile control (FPL VNAV keys + Direct-To descent): enabled gates the @@ -76,7 +76,7 @@ export default function App() { const [vnavCfg, setVnavCfg] = useState({ enabled: true, fpa: 3, offsetNm: 0 }); // Synthetic-vision display options (PFD submenu): PATHWAY draws the flight-plan // route on the 3D terrain; APTSIGNS shows runway/airport labels. - const [svtOpts, setSvtOpts] = useState({ pathway: true, aptSigns: true }); + const [svtOpts, setSvtOpts] = useState({ pathway: true, aptSigns: true, hrznHdg: false }); // Altimeter barometric units (false = inHg, true = hectopascal) — PFD ALT UNIT softkey. const [baroHpa, setBaroHpa] = useState(false); // Barometric minimums (set in TMR/REF) — shown on the PFD altimeter as BARO MIN. @@ -102,6 +102,24 @@ export default function App() { <> {dto && setWin(null)} vnav={vnavCfg} onVnav={setVnavCfg} />} {proc && setWin(null)} />} + {menu && (() => { + const wps = xp.flightPlan?.waypoints || []; + const act = (fn) => { fn(); setWin(null); }; + const item = (label, on, dis) => ; + return ( +
setWin(null)}> +
e.stopPropagation()}> +
PAGE MENU
+
+ {item('INVERT FLIGHT PLAN', () => xp.fp.set({ name: 'ACTIVE', waypoints: wps.slice().reverse(), activeLeg: 1 }), wps.length < 2)} + {item('STORE FLIGHT PLAN', () => xp.fp.export('WEBFPL'), wps.length < 2)} + {item('DELETE FLIGHT PLAN', () => xp.fp.clear(), wps.length < 1)} + {item('CANCEL', () => {})} +
+
+
+ ); + })()} {fpl && (
setWin(null)}>
e.stopPropagation()}> setWin(null)} vnav={vnavCfg} onVnav={setVnavCfg} />
@@ -146,7 +164,7 @@ export default function App() { inset={inset} onSetInset={setInset} insetMode={insetMode} onInsetMode={setInsetMode} nrst={nrst} onToggleNrst={() => toggleWin('nrst')} onDirect={() => toggleWin('dto')} tmr={tmr} onToggleTmr={() => toggleWin('tmr')} dme={dme} onToggleDme={() => toggleWin('dme')} - alerts={alerts} onToggleAlerts={() => toggleWin('alerts')} onProc={() => toggleWin('proc')} onFpl={() => toggleWin('fpl')} onClr={() => setWin(null)} + alerts={alerts} onToggleAlerts={() => toggleWin('alerts')} onProc={() => toggleWin('proc')} onFpl={() => toggleWin('fpl')} onMenu={() => toggleWin('menu')} onClr={() => setWin(null)} altHpa={baroHpa} onAltUnit={setBaroHpa} obs={obs} onObs={() => setObs((v) => !v)}> setWin(null)} tmr={tmr} onCloseTmr={() => setWin(null)} dme={dme} onCloseDme={() => setWin(null)} @@ -156,7 +174,7 @@ export default function App() { )} {tab === 'mfd' && ( - toggleWin('dto')} onProc={() => toggleWin('proc')} onFms={cycleMfd} onFpl={() => setMfdPage('fpl')} onClr={() => setWin(null)}> + toggleWin('dto')} onProc={() => toggleWin('proc')} onFms={cycleMfd} onFpl={() => setMfdPage('fpl')} onMenu={() => toggleWin('menu')} onClr={() => setWin(null)}> {dialogs} diff --git a/web/src/components/Bezel.jsx b/web/src/components/Bezel.jsx index 0c451a5..7a28297 100644 --- a/web/src/components/Bezel.jsx +++ b/web/src/components/Bezel.jsx @@ -34,7 +34,7 @@ const MFD_MENU = { }; const KG_PER_GAL = 2.72; // fuel totalizer steps in US gallons (matches the EIS readout) -export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, svtOpts, onSvtOpt, inset, onSetInset, insetMode, onInsetMode, nrst, onToggleNrst, tmr, onToggleTmr, dme, onToggleDme, alerts, onToggleAlerts, onDirect, onProc, onFpl, onClr, onFms, mapMode, onMapMode, altHpa, onAltUnit, obs, onObs, knobMode = 'arrows', children }) { +export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, svtOpts, onSvtOpt, inset, onSetInset, insetMode, onInsetMode, nrst, onToggleNrst, tmr, onToggleTmr, dme, onToggleDme, alerts, onToggleAlerts, onDirect, onProc, onFpl, onClr, onMenu, onFms, mapMode, onMapMode, altHpa, onAltUnit, obs, onObs, knobMode = 'arrows', children }) { const u = variant === 'mfd' ? 'mfd' : 'pfd'; // command prefix const fire = (suffix) => xp && xp.command(`${u}_${suffix}`); const [page, setPage] = useState('root'); // softkey menu page @@ -85,6 +85,7 @@ export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, svtOpts else if (label === 'SYN TERR') onToggleSvt && onToggleSvt(); else if (label === 'PATHWAY') onSvtOpt && onSvtOpt((o) => ({ ...o, pathway: !o.pathway })); // route on the 3D terrain else if (label === 'APTSIGNS') onSvtOpt && onSvtOpt((o) => ({ ...o, aptSigns: !o.aptSigns })); // runway/airport labels in SVT + else if (label === 'HRZN HDG') onSvtOpt && onSvtOpt((o) => ({ ...o, hrznHdg: !o.hrznHdg })); // heading marks on the horizon else if (label === 'ALT UNIT') setPage('altunit'); else if (label === 'IN') { onAltUnit && onAltUnit(false); setPage('pfd'); } else if (label === 'HPA') { onAltUnit && onAltUnit(true); setPage('pfd'); } @@ -124,6 +125,7 @@ export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, svtOpts || (label === 'AIRSPACE' && mapMode?.airspace) || (label === 'TRAFFIC' && mapMode?.traffic) || (label === 'NEXRAD' && mapMode?.nexrad) || (label === 'PROFILE' && mapMode?.profile); return (label === 'SYN TERR' && svt3d) || (label === 'PATHWAY' && svtOpts?.pathway) || (label === 'APTSIGNS' && svtOpts?.aptSigns) + || (label === 'HRZN HDG' && svtOpts?.hrznHdg) || (label === 'INSET' && inset) || (label === 'NRST' && nrst) || (label === 'TMR/REF' && tmr) || (label === 'DME' && dme) || (label === 'OBS' && obs) || (label === 'CAUTION' && (alerts || hasAlerts)) || (label === 'STBY' && xpdrMode === 1) || (label === 'ON' && xpdrMode === 2) || (label === 'ALT' && xpdrMode === 3) @@ -182,7 +184,7 @@ export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, svtOpts
- D→MENU + D→MENU FPLPROC CLRENT
diff --git a/web/src/components/PFD.jsx b/web/src/components/PFD.jsx index 0424121..e8cc77a 100644 --- a/web/src/components/PFD.jsx +++ b/web/src/components/PFD.jsx @@ -186,7 +186,7 @@ export default function PFD({ xp, values: V, command, connected = true, svt = tr {nav && } {vnav && } - + @@ -385,7 +385,21 @@ function AFCS({ V }) { } /* ---------------- attitude + flight director ---------------- */ -function Attitude({ V, svt }) { +// Heading reference marks along the horizon line (HRZN HDG softkey). +function hdgMarks(cx, cy, hdg) { + const PX = 3.4, out = []; // px per degree across the horizon + for (let k = -40; k <= 40; k += 10) { + const d = ((Math.round(hdg / 10) * 10 + k) % 360 + 360) % 360; + const off = (((d - hdg + 540) % 360) - 180) * PX; + const x = cx + off; + const big = d % 30 === 0; + out.push(); + if (big) { const lbl = d === 0 ? 'N' : d === 90 ? 'E' : d === 180 ? 'S' : d === 270 ? 'W' : d / 10; out.push({lbl}); } + } + return out; +} + +function Attitude({ V, svt, hrznHdg }) { const pitch = num(V.pitch), roll = num(V.roll), slip = num(V.slip); const fdP = num(V.fdPitch), fdR = num(V.fdRoll); const cx = W / 2, cy = 270; @@ -434,6 +448,7 @@ function Attitude({ V, svt }) { {!svt && } {!svt && } {pitchLadder(cx, cy)} + {hrznHdg && hdgMarks(cx, cy, ((num(V.heading) % 360) + 360) % 360)} {/* flight director command bars — magenta filled chevron (single cue) */}