feat(ui): render the Display panel's HUD and Sonar controls from the store

This commit is contained in:
Sameh Khamis committed 2026-10-30 23:01:09 -07:00
1 parent c99c0e8000
commit 3aa5a3e5e3
11 files changed
+118 -94

No files matched your search

+1 -1
View File
@@ -94,7 +94,7 @@ They contain no application state.
renders `LayerRows` synchronously; keyed chips and list items keep focus renders `LayerRows` synchronously; keyed chips and list items keep focus
through the refresh their own click triggers, and clicks re-read the live through the refresh their own click triggers, and clicks re-read the live
descriptor before acting), Display (in progress: `DisplayPanel` renders descriptor before acting), Display (in progress: `DisplayPanel` renders
bloom, sharpen and detection from the `gev-display` store, which VisualSettings the HUD, Sonar, detection, bloom and sharpen from the `gev-display` store, which VisualSettings
publishes to; the other groups follow one owner at a time), Context and publishes to; the other groups follow one owner at a time), Context and
Radio, the command dock, Radio, the command dock,
the phone shell, then Cockpit. Layers that write into the right rail the phone shell, then Cockpit. Layers that write into the right rail
+1 -17
View File
@@ -116,6 +116,7 @@ export class StyleManager extends ShellFacade {
this._lifetime = new UiLifetime(); this._lifetime = new UiLifetime();
this._recording = new RecordingControls({ this._recording = new RecordingControls({
syncShareState: () => this._syncShareState(), syncShareState: () => this._syncShareState(),
syncHudControls: () => this._visualSettings._syncHudControls(),
}); });
Object.assign(this, readShellElements()); Object.assign(this, readShellElements());
this._panelChrome = new PanelChrome({ this._panelChrome = new PanelChrome({
@@ -209,20 +210,6 @@ export class StyleManager extends ShellFacade {
elements: { elements: {
_celestialBtn: this._celestialBtn, _celestialBtn: this._celestialBtn,
_cockpitDisplayToggleBtn: this._cockpitDisplayToggleBtn, _cockpitDisplayToggleBtn: this._cockpitDisplayToggleBtn,
_hudBtn: this._hudBtn,
_cyberSonarBtn: this._cyberSonarBtn,
_cyberSonarRings: this._cyberSonarRings,
_cyberSonarRingsValue: this._cyberSonarRingsValue,
_cyberSonarRange: this._cyberSonarRange,
_cyberSonarRangeValue: this._cyberSonarRangeValue,
_cyberSonarIntensity: this._cyberSonarIntensity,
_cyberSonarIntensityValue: this._cyberSonarIntensityValue,
_cyberSonarOpacity: this._cyberSonarOpacity,
_cyberSonarOpacityValue: this._cyberSonarOpacityValue,
_cyberSonarSector: this._cyberSonarSector,
_cyberSonarSectorValue: this._cyberSonarSectorValue,
_hudLayoutRow: this._hudLayoutRow,
_hudLayoutSelect: this._hudLayoutSelect,
_ppToggles: this._ppToggles, _ppToggles: this._ppToggles,
_scopeBtn: this._scopeBtn, _scopeBtn: this._scopeBtn,
_scopeFeatherSlider: this._scopeFeatherSlider, _scopeFeatherSlider: this._scopeFeatherSlider,
@@ -1693,9 +1680,6 @@ export class StyleManager extends ShellFacade {
*/ */
_initHUDToggle() { _initHUDToggle() {
if (this._hudLayoutSelect) {
this._hudLayoutSelect.value = 'tactical';
}
this._setHudVariant('tactical'); this._setHudVariant('tactical');
this.hud.setMode('on'); this.hud.setMode('on');
this._updateHudButtonState(); this._updateHudButtonState();
+2
View File
@@ -48,6 +48,8 @@ test('real runner and facade use visual settings, preserve visibility and return
const renders = []; const renders = [];
const visual = Object.create(VisualSettings.prototype); const visual = Object.create(VisualSettings.prototype);
visual.services = { governorRequestRender: (reason) => renders.push(reason) }; visual.services = { governorRequestRender: (reason) => renders.push(reason) };
// No Display panel here: the controls' render is not under test.
visual._publishDisplay = () => {};
const owner = Object.create(StyleManager.prototype); const owner = Object.create(StyleManager.prototype);
owner._visualSettings = visual; owner._visualSettings = visual;
Object.defineProperty(owner, 'hud', { value: f.owner.hud }); Object.defineProperty(owner, 'hud', { value: f.owner.hud });
+1 -2
View File
@@ -65,6 +65,7 @@ function styleOwner(t, initialVariant = 'cyber') {
const owner = Object.create(VisualSettings.prototype); const owner = Object.create(VisualSettings.prototype);
Object.assign(owner, { Object.assign(owner, {
activeStyle: 'normal', activeStyle: 'normal',
store: { publish() {} },
readHud: () => hud, readHud: () => hud,
readShareLinks: () => ({ onStyleChange() {} }), readShareLinks: () => ({ onStyleChange() {} }),
_visualEffects: { _visualEffects: {
@@ -76,7 +77,6 @@ function styleOwner(t, initialVariant = 'cyber') {
), ),
}, },
services: { governorRequestRender() {}, setDetectionStyle() {} }, services: { governorRequestRender() {}, setDetectionStyle() {} },
_hudLayoutSelect: { value: initialVariant },
_styleIndicator: {}, _styleIndicator: {},
_setBloomEnabled: (value) => calls.push(['bloom', value]), _setBloomEnabled: (value) => calls.push(['bloom', value]),
_setSharpenEnabled: (value) => calls.push(['sharpen', value]), _setSharpenEnabled: (value) => calls.push(['sharpen', value]),
@@ -116,7 +116,6 @@ test('visual styles retain Cyber through real preset application without droppin
assert.equal(owner.activeStyle, style); assert.equal(owner.activeStyle, style);
assert.equal(hud.getVariant(), 'cyber'); assert.equal(hud.getVariant(), 'cyber');
assert.equal(root.dataset.uiTheme, 'cyber'); assert.equal(root.dataset.uiTheme, 'cyber');
assert.equal(owner._hudLayoutSelect.value, 'cyber');
assert.deepEqual(calls.at(-1), ['share', 'cyber']); assert.deepEqual(calls.at(-1), ['share', 'cyber']);
} }
assert.equal(owner.stages.thermal.uniforms.sensitivity, 0.85); assert.equal(owner.stages.thermal.uniforms.sensitivity, 0.85);
+48
View File
@@ -205,3 +205,51 @@ test('detection renders its mode, rows, sliders and allocation', async (t) => {
assert.equal(button.className, 'pp-toggle-btn'); assert.equal(button.className, 'pp-toggle-btn');
assert.equal($('detection-slider-row').classList.contains('visible'), false); assert.equal($('detection-slider-row').classList.contains('visible'), false);
}); });
test('the HUD and Sonar controls render what VisualSettings shows', async (t) => {
const { store, $ } = fixture(t);
const { owner } = settings(store);
let visible = true;
let variant = 'minimal';
owner.readHud = () => ({
get visible() {
return visible;
},
getVariant: () => variant,
});
owner._scheduleAdaptivePanelLayout = () => {};
assert.equal($('cyber-sonar-toggle').textContent, 'ON');
assert.equal($('cyber-sonar-toggle').getAttribute('aria-pressed'), 'true');
owner._syncHudControls();
await nextTick();
assert.equal($('hud-toggle').classList.contains('active'), true);
assert.equal($('hud-layout-row').classList.contains('visible'), true);
assert.equal($('hud-layout-select').value, 'minimal');
visible = false;
owner._updateHudButtonState();
await nextTick();
assert.equal($('hud-toggle').classList.contains('active'), false);
assert.equal($('hud-layout-row').classList.contains('visible'), false);
owner._sonarShown = {
enabled: false,
rings: 5,
range: 90,
intensity: 40,
opacity: 60,
sector: 30,
};
owner._publishDisplay();
await nextTick();
const toggle = $('cyber-sonar-toggle');
assert.equal(toggle.textContent, 'OFF');
assert.equal(toggle.getAttribute('aria-pressed'), 'false');
assert.equal(toggle.classList.contains('active'), false);
assert.equal($('cyber-sonar-rings').value, '5');
assert.equal($('cyber-sonar-rings-value').textContent, '5');
assert.equal($('cyber-sonar-range-value').textContent, '90%');
assert.equal($('cyber-sonar-sector').value, '30');
assert.equal($('cyber-sonar-sector-value').textContent, '30°');
});
+2 -1
View File
@@ -1,5 +1,6 @@
/** /**
* `v-range-value`: a range input's live value, written only when it differs. * `v-range-value`: a range input's (or select's) live value, written only
* when it differs.
* A `:value` binding would also rewrite the value attribute on every patch; * A `:value` binding would also rewrite the value attribute on every patch;
* the directive runs after the element's min, max and step are patched, so * the directive runs after the element's min, max and step are patched, so
* the value is never clamped to a stale range. * the value is never clamped to a stale range.
+4 -8
View File
@@ -1,7 +1,9 @@
/** Own recording chrome, safe frames and the pre-recording HUD snapshot. */ /** Own recording chrome, safe frames and the pre-recording HUD snapshot. */
export class RecordingControls { export class RecordingControls {
constructor({ syncShareState }) { constructor({ syncShareState, syncHudControls }) {
this._syncShareState = syncShareState; this._syncShareState = syncShareState;
// Shows the HUD's visibility and layout on the Display controls.
this._syncHudControls = syncHudControls;
this.destroyed = false; this.destroyed = false;
this._recordingMode = false; this._recordingMode = false;
this._recordingConfig = { this._recordingConfig = {
@@ -12,8 +14,6 @@ export class RecordingControls {
this._preRecordingHudState = null; this._preRecordingHudState = null;
this._safeFrameOverlay = document.getElementById('safe-frame-overlay'); this._safeFrameOverlay = document.getElementById('safe-frame-overlay');
this._safeFrameBox = document.getElementById('safe-frame-box'); this._safeFrameBox = document.getElementById('safe-frame-box');
this._hudLayoutSelect = document.getElementById('hud-layout-select');
this._hudBtn = document.getElementById('hud-toggle');
this.hud = null; this.hud = null;
} }
_initRecordingOverlay() { _initRecordingOverlay() {
@@ -63,8 +63,6 @@ export class RecordingControls {
} else if (hudMode === 'full' || hudMode === 'minimal') { } else if (hudMode === 'full' || hudMode === 'minimal') {
this.hud.setMode('on'); this.hud.setMode('on');
this.hud.setVariant(hudMode === 'minimal' ? 'minimal' : 'tactical'); this.hud.setVariant(hudMode === 'minimal' ? 'minimal' : 'tactical');
if (this._hudLayoutSelect)
this._hudLayoutSelect.value = this.hud.getVariant();
} else { } else {
this.hud.setMode('auto'); this.hud.setMode('auto');
} }
@@ -73,8 +71,6 @@ export class RecordingControls {
this._preRecordingHudState = null; this._preRecordingHudState = null;
if (saved) { if (saved) {
this.hud.setVariant(saved.variant); this.hud.setVariant(saved.variant);
if (this._hudLayoutSelect)
this._hudLayoutSelect.value = this.hud.getVariant();
} }
this.hud.setMode(saved ? saved.mode : 'auto'); this.hud.setMode(saved ? saved.mode : 'auto');
if (this._safeFrameOverlay) { if (this._safeFrameOverlay) {
@@ -85,7 +81,7 @@ export class RecordingControls {
); );
} }
} }
this._hudBtn.classList.toggle('active', this.hud.visible); this._syncHudControls();
this._syncShareState(); this._syncShareState();
} }
destroy() { destroy() {
-10
View File
@@ -11,23 +11,13 @@ export function readShellElements(document = globalThis.document) {
_sharpenBtn: document.getElementById('sharpen-toggle'), _sharpenBtn: document.getElementById('sharpen-toggle'),
_sharpenSlider: document.getElementById('sharpen-intensity-slider'), _sharpenSlider: document.getElementById('sharpen-intensity-slider'),
_hudBtn: document.getElementById('hud-toggle'), _hudBtn: document.getElementById('hud-toggle'),
_hudLayoutRow: document.getElementById('hud-layout-row'),
_hudLayoutSelect: document.getElementById('hud-layout-select'), _hudLayoutSelect: document.getElementById('hud-layout-select'),
_cyberSonarBtn: document.getElementById('cyber-sonar-toggle'), _cyberSonarBtn: document.getElementById('cyber-sonar-toggle'),
_cyberSonarRings: document.getElementById('cyber-sonar-rings'), _cyberSonarRings: document.getElementById('cyber-sonar-rings'),
_cyberSonarRingsValue: document.getElementById('cyber-sonar-rings-value'),
_cyberSonarRange: document.getElementById('cyber-sonar-range'), _cyberSonarRange: document.getElementById('cyber-sonar-range'),
_cyberSonarRangeValue: document.getElementById('cyber-sonar-range-value'),
_cyberSonarIntensity: document.getElementById('cyber-sonar-intensity'), _cyberSonarIntensity: document.getElementById('cyber-sonar-intensity'),
_cyberSonarIntensityValue: document.getElementById(
'cyber-sonar-intensity-value',
),
_cyberSonarOpacity: document.getElementById('cyber-sonar-opacity'), _cyberSonarOpacity: document.getElementById('cyber-sonar-opacity'),
_cyberSonarOpacityValue: document.getElementById(
'cyber-sonar-opacity-value',
),
_cyberSonarSector: document.getElementById('cyber-sonar-sector'), _cyberSonarSector: document.getElementById('cyber-sonar-sector'),
_cyberSonarSectorValue: document.getElementById('cyber-sonar-sector-value'),
_detectionDensitySlider: document.getElementById( _detectionDensitySlider: document.getElementById(
'detection-density-slider', 'detection-density-slider',
), ),
+10 -1
View File
@@ -200,7 +200,11 @@ test('recording restores the original HUD after repeated entry and revokes retai
try { try {
let mode = 'auto', let mode = 'auto',
variant = 'tactical'; variant = 'tactical';
const owner = new RecordingControls({ syncShareState() {} }); const hudSyncs = [];
const owner = new RecordingControls({
syncShareState() {},
syncHudControls: () => hudSyncs.push(variant),
});
owner.hud = { owner.hud = {
getMode: () => mode, getMode: () => mode,
getVariant: () => variant, getVariant: () => variant,
@@ -218,6 +222,11 @@ test('recording restores the original HUD after repeated entry and revokes retai
owner.setRecordingMode(true); owner.setRecordingMode(true);
assert.equal(mode, 'auto'); assert.equal(mode, 'auto');
assert.equal(variant, 'tactical'); assert.equal(variant, 'tactical');
assert.deepEqual(
hudSyncs,
['minimal', 'minimal', 'tactical'],
'each change shows the HUD layout it leaves',
);
assert.equal(document.body.classList.contains('recording-mode'), false); assert.equal(document.body.classList.contains('recording-mode'), false);
assert.equal( assert.equal(
f.nodes.get('safe-frame-overlay').classList.contains('active'), f.nodes.get('safe-frame-overlay').classList.contains('active'),
+20 -17
View File
@@ -5,11 +5,14 @@ import { t } from '../../../i18n/index.js';
import { vRangeValue } from '../../rail/rangeValue.js'; import { vRangeValue } from '../../rail/rangeValue.js';
import { useDisplayStore } from '../../stores/display.js'; import { useDisplayStore } from '../../stores/display.js';
// The Display panel. VisualSettings presents bloom, sharpen and detection // The Display panel. VisualSettings presents the HUD, Sonar, detection,
// and publishes them; until it does, the panel shows its initial markup. // bloom and sharpen and publishes them; until it does, the panel shows its
// The other groups keep their owners' element bindings. // initial markup. The other groups keep their owners' element bindings.
const { view } = storeToRefs(useDisplayStore()); const { view } = storeToRefs(useDisplayStore());
const pct = (value) => `${value}%`; const pct = (value) => `${value}%`;
const hud = computed(() => view.value?.hud ?? null);
const sonar = computed(() => view.value?.sonar ?? null);
const sonarOn = computed(() => sonar.value?.enabled ?? true);
const detection = computed(() => view.value?.detection ?? null); const detection = computed(() => view.value?.detection ?? null);
const detectButton = computed(() => detection.value?.button ?? null); const detectButton = computed(() => detection.value?.button ?? null);
const allocation = computed(() => detection.value?.allocation ?? 'ELASTIC'); const allocation = computed(() => detection.value?.allocation ?? 'ELASTIC');
@@ -29,13 +32,13 @@ const allocationSegment = (value) => ({
</div> </div>
<div class="pp-panel-body" data-rail-scroller> <div class="pp-panel-body" data-rail-scroller>
<div class="pp-toggle-group"> <div class="pp-toggle-group">
<button class="pp-toggle-btn" id="hud-toggle" title="Intelligence HUD (H)" data-i18n-title> <button class="pp-toggle-btn" :class="{ active: hud?.visible }" id="hud-toggle" title="Intelligence HUD (H)" data-i18n-title>
<span class="pp-icon">&#x25CE;</span> <span class="pp-icon">&#x25CE;</span>
<span class="pp-label" translate="no">HUD</span> <span class="pp-label" translate="no">HUD</span>
</button> </button>
<div class="pp-slider-row" id="hud-layout-row"> <div class="pp-slider-row" :class="{ visible: hud?.visible }" id="hud-layout-row">
<span class="pp-slider-mini-label" data-i18n>Layout</span> <span class="pp-slider-mini-label" data-i18n>Layout</span>
<select id="hud-layout-select" class="pp-select gev-field gev-field--compact" aria-label="HUD layout" data-i18n-aria-label> <select id="hud-layout-select" class="pp-select gev-field gev-field--compact" v-range-value="hud?.variant ?? 'tactical'" aria-label="HUD layout" data-i18n-aria-label>
<option value="tactical" data-i18n>Tactical</option> <option value="tactical" data-i18n>Tactical</option>
<option value="minimal" data-i18n>Minimal</option> <option value="minimal" data-i18n>Minimal</option>
<option value="cyber" data-i18n>Cyber</option> <option value="cyber" data-i18n>Cyber</option>
@@ -43,33 +46,33 @@ const allocationSegment = (value) => ({
</div> </div>
<div class="pp-slider-row cyber-sonar-control cyber-sonar-toggle-row" id="cyber-sonar-row"> <div class="pp-slider-row cyber-sonar-control cyber-sonar-toggle-row" id="cyber-sonar-row">
<span class="pp-slider-mini-label" data-i18n>Sonar</span> <span class="pp-slider-mini-label" data-i18n>Sonar</span>
<button class="pp-mode-btn active" id="cyber-sonar-toggle" title="Cyber sonar contact scanner" aria-label="Cyber sonar contact scanner" aria-pressed="true" data-i18n-title data-i18n-aria-label data-i18n>ON</button> <button class="pp-mode-btn" :class="{ active: sonarOn }" id="cyber-sonar-toggle" title="Cyber sonar contact scanner" aria-label="Cyber sonar contact scanner" :aria-pressed="String(sonarOn)" data-i18n-title data-i18n-aria-label>{{ sonarOn ? t('ON') : t('OFF') }}</button>
</div> </div>
<output id="cyber-sonar-status" class="cyber-sonar-control" role="status"></output> <output id="cyber-sonar-status" class="cyber-sonar-control" role="status"></output>
<div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-rings-row"> <div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-rings-row">
<label class="pp-slider-mini-label" for="cyber-sonar-rings" data-i18n>Rings</label> <label class="pp-slider-mini-label" for="cyber-sonar-rings" data-i18n>Rings</label>
<input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-rings" min="3" max="12" step="1" value="8" aria-label="Sonar ring count" data-i18n-aria-label /> <input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-rings" min="3" max="12" step="1" value="8" v-range-value="sonar?.rings ?? 8" aria-label="Sonar ring count" data-i18n-aria-label />
<span class="pp-slider-value" id="cyber-sonar-rings-value">8</span> <span class="pp-slider-value" id="cyber-sonar-rings-value">{{ sonar?.rings ?? 8 }}</span>
</div> </div>
<div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-range-row"> <div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-range-row">
<label class="pp-slider-mini-label" for="cyber-sonar-range" data-i18n>Range</label> <label class="pp-slider-mini-label" for="cyber-sonar-range" data-i18n>Range</label>
<input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-range" min="60" max="120" step="1" value="100" aria-label="Sonar ring range" data-i18n-aria-label /> <input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-range" min="60" max="120" step="1" value="100" v-range-value="sonar?.range ?? 100" aria-label="Sonar ring range" data-i18n-aria-label />
<span class="pp-slider-value" id="cyber-sonar-range-value">100%</span> <span class="pp-slider-value" id="cyber-sonar-range-value">{{ pct(sonar?.range ?? 100) }}</span>
</div> </div>
<div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-intensity-row"> <div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-intensity-row">
<label class="pp-slider-mini-label" for="cyber-sonar-intensity" data-i18n>Power</label> <label class="pp-slider-mini-label" for="cyber-sonar-intensity" data-i18n>Power</label>
<input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-intensity" min="0" max="100" step="1" value="70" aria-label="Sonar intensity" data-i18n-aria-label /> <input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-intensity" min="0" max="100" step="1" value="70" v-range-value="sonar?.intensity ?? 70" aria-label="Sonar intensity" data-i18n-aria-label />
<span class="pp-slider-value" id="cyber-sonar-intensity-value">70%</span> <span class="pp-slider-value" id="cyber-sonar-intensity-value">{{ pct(sonar?.intensity ?? 70) }}</span>
</div> </div>
<div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-opacity-row"> <div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-opacity-row">
<label class="pp-slider-mini-label" for="cyber-sonar-opacity" data-i18n>Opacity</label> <label class="pp-slider-mini-label" for="cyber-sonar-opacity" data-i18n>Opacity</label>
<input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-opacity" min="35" max="100" step="1" value="84" aria-label="Cyber contact and label opacity" title="Minimum opacity for map dots, icons, models, brackets, and labels between sonar passes" data-i18n-aria-label data-i18n-title /> <input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-opacity" min="35" max="100" step="1" value="84" v-range-value="sonar?.opacity ?? 84" aria-label="Cyber contact and label opacity" title="Minimum opacity for map dots, icons, models, brackets, and labels between sonar passes" data-i18n-aria-label data-i18n-title />
<span class="pp-slider-value" id="cyber-sonar-opacity-value">84%</span> <span class="pp-slider-value" id="cyber-sonar-opacity-value">{{ pct(sonar?.opacity ?? 84) }}</span>
</div> </div>
<div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-sector-row"> <div class="pp-slider-row cyber-sonar-control cyber-sonar-setting" id="cyber-sonar-sector-row">
<label class="pp-slider-mini-label" for="cyber-sonar-sector" data-i18n>Sector</label> <label class="pp-slider-mini-label" for="cyber-sonar-sector" data-i18n>Sector</label>
<input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-sector" min="8" max="60" step="1" value="24" aria-label="Sonar sector width" data-i18n-aria-label /> <input type="range" class="gev-quantitative-slider pp-slider" id="cyber-sonar-sector" min="8" max="60" step="1" value="24" v-range-value="sonar?.sector ?? 24" aria-label="Sonar sector width" data-i18n-aria-label />
<span class="pp-slider-value" id="cyber-sonar-sector-value">24°</span> <span class="pp-slider-value" id="cyber-sonar-sector-value">{{ `${sonar?.sector ?? 24}°` }}</span>
</div> </div>
</div> </div>
<div class="pp-toggle-group"> <div class="pp-toggle-group">
+29 -37
View File
@@ -213,6 +213,11 @@ export class VisualSettings {
this._detectionOutsidePct = 1; this._detectionOutsidePct = 1;
this._detectionModeLabel = null; this._detectionModeLabel = null;
this._detectionAllocationShown = null; this._detectionAllocationShown = null;
// What the HUD and Sonar controls show; null until first synced, so the
// initial markup stands.
this._hudVisibleShown = null;
this._hudVariantShown = null;
this._sonarShown = null;
this.activeStyle = 'normal'; this.activeStyle = 'normal';
this._preCyberStyle = null; this._preCyberStyle = null;
this._cyberStyleUserSelected = false; this._cyberStyleUserSelected = false;
@@ -476,6 +481,8 @@ export class VisualSettings {
bloom: { active: this.bloomEnabled, intensity: this.bloomIntensityPct }, bloom: { active: this.bloomEnabled, intensity: this.bloomIntensityPct },
sharpen: { active: this.sharpenEnabled, intensity: this._sharpenPct }, sharpen: { active: this.sharpenEnabled, intensity: this._sharpenPct },
detection: this._presentDetection(), detection: this._presentDetection(),
hud: { visible: this._hudVisibleShown, variant: this._hudVariantShown },
sonar: this._sonarShown,
}; };
} }
@@ -666,9 +673,8 @@ export class VisualSettings {
this._preCyberStyle = this.activeStyle; this._preCyberStyle = this.activeStyle;
this._cyberStyleUserSelected = false; this._cyberStyleUserSelected = false;
} }
if (this._hudLayoutSelect && this._hudLayoutSelect.value !== nextVariant) { this._hudVariantShown = nextVariant;
this._hudLayoutSelect.value = nextVariant; this._publishDisplay();
}
const visualDefaults = cyberVisualDefaultsForHudTransition( const visualDefaults = cyberVisualDefaultsForHudTransition(
previousVariant, previousVariant,
nextVariant, nextVariant,
@@ -709,37 +715,16 @@ export class VisualSettings {
} }
_syncCyberSonarControl() { _syncCyberSonarControl() {
if (!this._cyberSonarBtn) return;
const enabled = isCyberSonarEnabled();
const settings = applyCyberSonarSettings(readCyberSonarSettings()); const settings = applyCyberSonarSettings(readCyberSonarSettings());
this._cyberSonarBtn.classList.toggle('active', enabled); this._sonarShown = {
this._cyberSonarBtn.setAttribute('aria-pressed', String(enabled)); enabled: isCyberSonarEnabled(),
this._cyberSonarBtn.textContent = enabled ? t('ON') : t('OFF'); rings: settings.rings,
for (const [input, output, value, suffix] of [ range: settings.range,
[this._cyberSonarRings, this._cyberSonarRingsValue, settings.rings, ''], intensity: settings.intensity,
[this._cyberSonarRange, this._cyberSonarRangeValue, settings.range, '%'], opacity: settings.opacity,
[ sector: settings.sector,
this._cyberSonarIntensity, };
this._cyberSonarIntensityValue, this._publishDisplay();
settings.intensity,
'%',
],
[
this._cyberSonarOpacity,
this._cyberSonarOpacityValue,
settings.opacity,
'%',
],
[
this._cyberSonarSector,
this._cyberSonarSectorValue,
settings.sector,
'°',
],
]) {
if (input) input.value = String(value);
if (output) output.textContent = `${value}${suffix}`;
}
} }
_setCyberSonarEnabled(enabled = !isCyberSonarEnabled()) { _setCyberSonarEnabled(enabled = !isCyberSonarEnabled()) {
@@ -1587,10 +1572,17 @@ export class VisualSettings {
} }
_updateHudButtonState() { _updateHudButtonState() {
this._hudBtn.classList.toggle('active', this.hud.visible); this._syncHudControls();
if (this._hudLayoutRow) { }
this._hudLayoutRow.classList.toggle('visible', this.hud.visible);
} /**
* Show the HUD's current visibility and layout on its controls; the layout
* row's visibility changes the panel's height, so the rails settle again.
*/
_syncHudControls() {
this._hudVisibleShown = Boolean(this.hud.visible);
this._hudVariantShown = this.hud.getVariant();
this._publishDisplay();
this._scheduleAdaptivePanelLayout({ settle: true }); this._scheduleAdaptivePanelLayout({ settle: true });
} }