mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
feat(ui): render the Display panel's Scope and Celestial controls from the store
This commit is contained in:
1 parent
3aa5a3e5e3
commit
1139d364c7
10 files changed
+110
-99
No files matched your search
@@ -94,9 +94,9 @@ 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
|
||||||
the HUD, Sonar, detection, bloom and sharpen from the `gev-display` store, which VisualSettings
|
the HUD, Sonar, Scope, Celestial, detection, bloom and sharpen from the
|
||||||
publishes to; the other groups follow one owner at a time), Context and
|
`gev-display` store, which VisualSettings publishes to; the other groups
|
||||||
Radio, the command dock,
|
follow one owner at a time), Context and 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
|
||||||
directly gain a slot instead.
|
directly gain a slot instead.
|
||||||
|
|
||||||
|
|||||||
@@ -520,8 +520,8 @@ test('every explicit visual UI gesture claims restore authority before it mutate
|
|||||||
['toggleBloom:', 'setBloomIntensity:', 'this._setBloomEnabled(', 'toggleBloom control'],
|
['toggleBloom:', 'setBloomIntensity:', 'this._setBloomEnabled(', 'toggleBloom control'],
|
||||||
['setBloomIntensity:', 'toggleSharpen:', 'this._setBloomIntensity(', 'setBloomIntensity control'],
|
['setBloomIntensity:', 'toggleSharpen:', 'this._setBloomIntensity(', 'setBloomIntensity control'],
|
||||||
['toggleSharpen:', 'setScopeEnabled:', 'this._setSharpenEnabled(', 'toggleSharpen control'],
|
['toggleSharpen:', 'setScopeEnabled:', 'this._setSharpenEnabled(', 'toggleSharpen control'],
|
||||||
['setScopeEnabled:', 'setScopeFeather:', 'setScopeMaskEnabled(', 'setScopeEnabled control'],
|
['setScopeEnabled:', 'setScopeFeather:', 'this._setScope(', 'setScopeEnabled control'],
|
||||||
['setScopeFeather:', 'setSharpenIntensity:', 'setScopeMaskFeather(', 'setScopeFeather control'],
|
['setScopeFeather:', 'setSharpenIntensity:', 'this._setScope(', 'setScopeFeather control'],
|
||||||
['setSharpenIntensity:', 'setHudLayout:', 'this._setSharpenIntensity(', 'setSharpenIntensity control'],
|
['setSharpenIntensity:', 'setHudLayout:', 'this._setSharpenIntensity(', 'setSharpenIntensity control'],
|
||||||
['setHudLayout:', 'toggleCleanView:', 'this._setHudVariant(', 'setHudLayout control'],
|
['setHudLayout:', 'toggleCleanView:', 'this._setHudVariant(', 'setHudLayout control'],
|
||||||
['setDensity:', 'setAllocation:', 'this._setDetectionDensity(', 'setDensity control'],
|
['setDensity:', 'setAllocation:', 'this._setDetectionDensity(', 'setDensity control'],
|
||||||
|
|||||||
@@ -208,12 +208,8 @@ export class StyleManager extends ShellFacade {
|
|||||||
setScopeMaskFeather: services.setScopeMaskFeather,
|
setScopeMaskFeather: services.setScopeMaskFeather,
|
||||||
},
|
},
|
||||||
elements: {
|
elements: {
|
||||||
_celestialBtn: this._celestialBtn,
|
|
||||||
_cockpitDisplayToggleBtn: this._cockpitDisplayToggleBtn,
|
_cockpitDisplayToggleBtn: this._cockpitDisplayToggleBtn,
|
||||||
_ppToggles: this._ppToggles,
|
_ppToggles: this._ppToggles,
|
||||||
_scopeBtn: this._scopeBtn,
|
|
||||||
_scopeFeatherSlider: this._scopeFeatherSlider,
|
|
||||||
_scopeFeatherValue: this._scopeFeatherValue,
|
|
||||||
_sliderContainer: this._sliderContainer,
|
_sliderContainer: this._sliderContainer,
|
||||||
_sliderPanel: this._sliderPanel,
|
_sliderPanel: this._sliderPanel,
|
||||||
_styleIndicator: this._styleIndicator,
|
_styleIndicator: this._styleIndicator,
|
||||||
@@ -491,9 +487,7 @@ export class StyleManager extends ShellFacade {
|
|||||||
viewer,
|
viewer,
|
||||||
services: {
|
services: {
|
||||||
cycleDetectionMode: services.cycleDetectionMode,
|
cycleDetectionMode: services.cycleDetectionMode,
|
||||||
setScopeMaskEnabled: services.setScopeMaskEnabled,
|
|
||||||
isScopeMaskEnabled: services.isScopeMaskEnabled,
|
isScopeMaskEnabled: services.isScopeMaskEnabled,
|
||||||
setScopeMaskFeather: services.setScopeMaskFeather,
|
|
||||||
},
|
},
|
||||||
elements: {
|
elements: {
|
||||||
_locationSearch: this._locationSearch,
|
_locationSearch: this._locationSearch,
|
||||||
@@ -520,7 +514,6 @@ export class StyleManager extends ShellFacade {
|
|||||||
_detectionAllocationBtns: this._detectionAllocationBtns,
|
_detectionAllocationBtns: this._detectionAllocationBtns,
|
||||||
_celestialBtn: this._celestialBtn,
|
_celestialBtn: this._celestialBtn,
|
||||||
_models3dBtn: this._models3dBtn,
|
_models3dBtn: this._models3dBtn,
|
||||||
_scopeFeatherValue: this._scopeFeatherValue,
|
|
||||||
},
|
},
|
||||||
operations: {
|
operations: {
|
||||||
setStyle: (...args) => this.setStyle(...args),
|
setStyle: (...args) => this.setStyle(...args),
|
||||||
@@ -540,6 +533,7 @@ export class StyleManager extends ShellFacade {
|
|||||||
_setDetectionAllocation: (...args) =>
|
_setDetectionAllocation: (...args) =>
|
||||||
this._setDetectionAllocation(...args),
|
this._setDetectionAllocation(...args),
|
||||||
_setDetectionFade: (...args) => this._setDetectionFade(...args),
|
_setDetectionFade: (...args) => this._setDetectionFade(...args),
|
||||||
|
_setScope: (...args) => this._visualSettings._setScope(...args),
|
||||||
setCelestialRingEnabled: (...args) =>
|
setCelestialRingEnabled: (...args) =>
|
||||||
this.setCelestialRingEnabled(...args),
|
this.setCelestialRingEnabled(...args),
|
||||||
_setModels3dEnabled: (...args) => this._setModels3dEnabled(...args),
|
_setModels3dEnabled: (...args) => this._setModels3dEnabled(...args),
|
||||||
|
|||||||
@@ -279,28 +279,23 @@ test('scope choice and expanded feather survive every HUD layout and both restor
|
|||||||
const previous = { enabled: isScopeMaskEnabled(), feather: getScopeMaskFeather() };
|
const previous = { enabled: isScopeMaskEnabled(), feather: getScopeMaskFeather() };
|
||||||
t.after(() => { setScopeMaskEnabled(previous.enabled); setScopeMaskFeather(previous.feather); });
|
t.after(() => { setScopeMaskEnabled(previous.enabled); setScopeMaskFeather(previous.feather); });
|
||||||
Object.assign(owner.services, { setScopeMaskEnabled, setScopeMaskFeather });
|
Object.assign(owner.services, { setScopeMaskEnabled, setScopeMaskFeather });
|
||||||
owner._scopeBtn = { checked: true };
|
let shown = null;
|
||||||
owner._scopeFeatherSlider = { value: '11', disabled: false };
|
owner.store = { publish: (view) => (shown = view.scope) };
|
||||||
owner._scopeFeatherValue = {};
|
|
||||||
await owner.restoreShareState({ scopeEnabled: false, scopeFeatherPct: 200 });
|
await owner.restoreShareState({ scopeEnabled: false, scopeFeatherPct: 200 });
|
||||||
assert.equal(owner._scopeBtn.checked, false);
|
assert.deepEqual(shown, { enabled: false, featherPct: 200 });
|
||||||
assert.equal(owner._scopeFeatherSlider.disabled, true);
|
|
||||||
assert.equal(owner._scopeFeatherSlider.value, '200');
|
|
||||||
for (const layout of ['minimal', 'cyber', 'tactical']) {
|
for (const layout of ['minimal', 'cyber', 'tactical']) {
|
||||||
owner._setHudVariant(layout, { applyVisualDefaults: true });
|
owner._setHudVariant(layout, { applyVisualDefaults: true });
|
||||||
assert.equal(isScopeMaskEnabled(), false, layout);
|
assert.equal(isScopeMaskEnabled(), false, layout);
|
||||||
assert.equal(getScopeMaskFeather(), 2, layout);
|
assert.equal(getScopeMaskFeather(), 2, layout);
|
||||||
assert.equal(owner._scopeBtn.checked, false, layout);
|
assert.equal(shown.enabled, false, layout);
|
||||||
}
|
}
|
||||||
await owner.applyVisualState({ scope: { enabled: true, featherPct: 175 } });
|
await owner.applyVisualState({ scope: { enabled: true, featherPct: 175 } });
|
||||||
assert.equal(isScopeMaskEnabled(), true);
|
assert.equal(isScopeMaskEnabled(), true);
|
||||||
assert.equal(getScopeMaskFeather(), 1.75);
|
assert.equal(getScopeMaskFeather(), 1.75);
|
||||||
assert.equal(owner._scopeBtn.checked, true);
|
assert.deepEqual(shown, { enabled: true, featherPct: 175 });
|
||||||
assert.equal(owner._scopeFeatherSlider.disabled, false);
|
|
||||||
assert.equal(owner._scopeFeatherValue.textContent, '175%');
|
|
||||||
await owner.applyVisualState({ scope: { featherPct: 500 } });
|
await owner.applyVisualState({ scope: { featherPct: 500 } });
|
||||||
assert.equal(getScopeMaskFeather(), 2);
|
assert.equal(getScopeMaskFeather(), 2);
|
||||||
assert.equal(owner._scopeFeatherSlider.value, '200');
|
assert.deepEqual(shown, { enabled: true, featherPct: 200 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a scene shot saved with the retired Snow style plays on Normal', async (t) => {
|
test('a scene shot saved with the retired Snow style plays on Normal', async (t) => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
|
|
||||||
import { createFrameRateMonitor } from './frameRateMonitor.js';
|
import { createFrameRateMonitor } from './frameRateMonitor.js';
|
||||||
import { bindApplicationShortcuts } from './visualInput.js';
|
import { bindApplicationShortcuts } from './visualInput.js';
|
||||||
import { bindDisplayControls, syncScopeControls } from './displayControls.js';
|
import { bindDisplayControls } from './displayControls.js';
|
||||||
|
|
||||||
/** Own keyboard/display event subscriptions; settings remain with their state owners. */
|
/** Own keyboard/display event subscriptions; settings remain with their state owners. */
|
||||||
export class DisplayBindings {
|
export class DisplayBindings {
|
||||||
@@ -45,12 +44,7 @@ export class DisplayBindings {
|
|||||||
return this.readState()._models3dModeBtns;
|
return this.readState()._models3dModeBtns;
|
||||||
}
|
}
|
||||||
_initUI() {
|
_initUI() {
|
||||||
const {
|
const { cycleDetectionMode, isScopeMaskEnabled } = this.services;
|
||||||
cycleDetectionMode,
|
|
||||||
setScopeMaskEnabled,
|
|
||||||
isScopeMaskEnabled,
|
|
||||||
setScopeMaskFeather,
|
|
||||||
} = this.services;
|
|
||||||
this._applicationShortcuts?.destroy();
|
this._applicationShortcuts?.destroy();
|
||||||
this._frameRateMonitor?.destroy();
|
this._frameRateMonitor?.destroy();
|
||||||
this._frameRateMonitor = createFrameRateMonitor({
|
this._frameRateMonitor = createFrameRateMonitor({
|
||||||
@@ -89,11 +83,7 @@ export class DisplayBindings {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
syncScopeControls(
|
this._setScope({ enabled: isScopeMaskEnabled() }, { apply: false });
|
||||||
this._scopeBtn,
|
|
||||||
this._scopeFeatherSlider,
|
|
||||||
isScopeMaskEnabled(),
|
|
||||||
);
|
|
||||||
this._displayControls?.destroy();
|
this._displayControls?.destroy();
|
||||||
this._displayControls = bindDisplayControls({
|
this._displayControls = bindDisplayControls({
|
||||||
elements: {
|
elements: {
|
||||||
@@ -139,16 +129,12 @@ export class DisplayBindings {
|
|||||||
},
|
},
|
||||||
setScopeEnabled: (enabled) => {
|
setScopeEnabled: (enabled) => {
|
||||||
this.shareLinkManager?.claimRestoreLane?.('visual');
|
this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||||
setScopeMaskEnabled(enabled);
|
this._setScope({ enabled });
|
||||||
syncScopeControls(this._scopeBtn, this._scopeFeatherSlider, enabled);
|
|
||||||
this._syncShareState();
|
this._syncShareState();
|
||||||
},
|
},
|
||||||
setScopeFeather: (value) => {
|
setScopeFeather: (value) => {
|
||||||
this.shareLinkManager?.claimRestoreLane?.('visual');
|
this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||||
const pct = clampScopeFeatherPct(value);
|
this._setScope({ featherPct: value });
|
||||||
if (this._scopeFeatherValue)
|
|
||||||
this._scopeFeatherValue.textContent = `${pct}%`;
|
|
||||||
setScopeMaskFeather(pct / 100);
|
|
||||||
this._syncShareState();
|
this._syncShareState();
|
||||||
},
|
},
|
||||||
setSharpenIntensity: (pct) => {
|
setSharpenIntensity: (pct) => {
|
||||||
|
|||||||
@@ -73,9 +73,3 @@ export function bindDisplayControls({ elements, actions }) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Keep the native Scope checkbox and feather availability in step with the mask. */
|
|
||||||
export function syncScopeControls(toggle, featherSlider, enabled) {
|
|
||||||
if (toggle) toggle.checked = enabled;
|
|
||||||
if (featherSlider) featherSlider.disabled = !enabled;
|
|
||||||
}
|
|
||||||
@@ -253,3 +253,47 @@ test('the HUD and Sonar controls render what VisualSettings shows', async (t) =>
|
|||||||
assert.equal($('cyber-sonar-sector').value, '30');
|
assert.equal($('cyber-sonar-sector').value, '30');
|
||||||
assert.equal($('cyber-sonar-sector-value').textContent, '30°');
|
assert.equal($('cyber-sonar-sector-value').textContent, '30°');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Scope and Celestial render what VisualSettings shows', async (t) => {
|
||||||
|
const { store, $ } = fixture(t);
|
||||||
|
const { owner } = settings(store);
|
||||||
|
const mask = { enabled: true, feather: 0.35 };
|
||||||
|
owner.services = {
|
||||||
|
...owner.services,
|
||||||
|
setScopeMaskEnabled: (on) => (mask.enabled = on),
|
||||||
|
setScopeMaskFeather: (value) => (mask.feather = value),
|
||||||
|
};
|
||||||
|
assert.equal($('scope-toggle').checked, true);
|
||||||
|
assert.equal($('scope-feather-value').textContent, '100%');
|
||||||
|
assert.equal($('celestial-toggle').hasAttribute('aria-disabled'), false);
|
||||||
|
|
||||||
|
owner._setScope({ enabled: false }, { apply: false });
|
||||||
|
await nextTick();
|
||||||
|
assert.equal($('scope-toggle').checked, false);
|
||||||
|
assert.equal($('scope-feather-slider').disabled, true);
|
||||||
|
assert.equal(mask.enabled, true, 'only shown');
|
||||||
|
|
||||||
|
owner._setScope({ enabled: true, featherPct: 250 });
|
||||||
|
await nextTick();
|
||||||
|
assert.equal($('scope-toggle').checked, true);
|
||||||
|
assert.equal($('scope-feather-slider').disabled, false);
|
||||||
|
assert.equal($('scope-feather-slider').value, '200', 'clamped');
|
||||||
|
assert.equal($('scope-feather-value').textContent, '200%');
|
||||||
|
assert.deepEqual(mask, { enabled: true, feather: 2 });
|
||||||
|
|
||||||
|
owner._celestialShown = { active: false, supported: false };
|
||||||
|
owner._publishDisplay();
|
||||||
|
await nextTick();
|
||||||
|
const celestial = $('celestial-toggle');
|
||||||
|
assert.equal(celestial.disabled, true);
|
||||||
|
assert.equal(celestial.getAttribute('aria-disabled'), 'true');
|
||||||
|
assert.equal(celestial.title, 'Celestial ring — available in Normal style');
|
||||||
|
|
||||||
|
owner._celestialShown = { active: true, supported: true };
|
||||||
|
owner._publishDisplay();
|
||||||
|
await nextTick();
|
||||||
|
assert.equal(celestial.disabled, false);
|
||||||
|
assert.equal(celestial.getAttribute('aria-pressed'), 'true');
|
||||||
|
assert.equal(celestial.classList.contains('active'), true);
|
||||||
|
assert.equal(celestial.title, 'Celestial ring — reveal the full globe');
|
||||||
|
});
|
||||||
@@ -32,7 +32,6 @@ export function readShellElements(document = globalThis.document) {
|
|||||||
_celestialBtn: document.getElementById('celestial-toggle'),
|
_celestialBtn: document.getElementById('celestial-toggle'),
|
||||||
_scopeBtn: document.getElementById('scope-toggle'),
|
_scopeBtn: document.getElementById('scope-toggle'),
|
||||||
_scopeFeatherSlider: document.getElementById('scope-feather-slider'),
|
_scopeFeatherSlider: document.getElementById('scope-feather-slider'),
|
||||||
_scopeFeatherValue: document.getElementById('scope-feather-value'),
|
|
||||||
_mapStackChips: document.getElementById('map-stack-chips'),
|
_mapStackChips: document.getElementById('map-stack-chips'),
|
||||||
_mapStackStatus: document.getElementById('map-stack-status'),
|
_mapStackStatus: document.getElementById('map-stack-status'),
|
||||||
_cleanViewBtn: document.getElementById('clean-view-toggle'),
|
_cleanViewBtn: document.getElementById('clean-view-toggle'),
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ 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 the HUD, Sonar, detection,
|
// The Display panel. VisualSettings presents the HUD, Sonar, Scope,
|
||||||
// bloom and sharpen and publishes them; until it does, the panel shows its
|
// Celestial, detection, bloom and sharpen and publishes them; until it does,
|
||||||
// initial markup. The other groups keep their owners' element bindings.
|
// the panel shows its 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 hud = computed(() => view.value?.hud ?? null);
|
||||||
@@ -16,6 +16,14 @@ 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');
|
||||||
|
const scopeOn = computed(() => view.value?.scope.enabled ?? true);
|
||||||
|
const scopeFeather = computed(() => view.value?.scope.featherPct ?? 100);
|
||||||
|
const celestial = computed(() => view.value?.celestial ?? null);
|
||||||
|
const celestialTitle = computed(() =>
|
||||||
|
celestial.value && !celestial.value.supported
|
||||||
|
? t('Celestial ring — available in Normal style')
|
||||||
|
: t('Celestial ring — reveal the full globe'),
|
||||||
|
);
|
||||||
const allocationSegment = (value) => ({
|
const allocationSegment = (value) => ({
|
||||||
class: { active: allocation.value === value },
|
class: { active: allocation.value === value },
|
||||||
'aria-checked': String(allocation.value === value),
|
'aria-checked': String(allocation.value === value),
|
||||||
@@ -133,12 +141,12 @@ const allocationSegment = (value) => ({
|
|||||||
<label class="pp-toggle-btn pp-scope-toggle" title="Scope — the circular viewport mask" data-i18n-title>
|
<label class="pp-toggle-btn pp-scope-toggle" title="Scope — the circular viewport mask" data-i18n-title>
|
||||||
<span class="pp-icon material-symbols-outlined" aria-hidden="true">adjust</span>
|
<span class="pp-icon material-symbols-outlined" aria-hidden="true">adjust</span>
|
||||||
<span class="pp-label" data-i18n>Scope</span>
|
<span class="pp-label" data-i18n>Scope</span>
|
||||||
<input type="checkbox" id="scope-toggle" checked />
|
<input type="checkbox" id="scope-toggle" :checked="scopeOn" />
|
||||||
</label>
|
</label>
|
||||||
<div class="pp-slider-row visible" id="scope-slider-row">
|
<div class="pp-slider-row visible" id="scope-slider-row">
|
||||||
<span class="pp-slider-mini-label" data-i18n>Feather</span>
|
<span class="pp-slider-mini-label" data-i18n>Feather</span>
|
||||||
<input type="range" class="gev-quantitative-slider pp-slider" id="scope-feather-slider" min="0" max="200" value="100" aria-label="Scope edge feather" title="Scope edge feather as a percentage of the keyhole radius" data-i18n-aria-label data-i18n-title />
|
<input type="range" class="gev-quantitative-slider pp-slider" id="scope-feather-slider" min="0" max="200" value="100" v-range-value="scopeFeather" :disabled="!scopeOn" aria-label="Scope edge feather" title="Scope edge feather as a percentage of the keyhole radius" data-i18n-aria-label data-i18n-title />
|
||||||
<span class="pp-slider-value" id="scope-feather-value">100%</span>
|
<span class="pp-slider-value" id="scope-feather-value">{{ pct(scopeFeather) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pp-toggle-group">
|
<div class="pp-toggle-group">
|
||||||
@@ -177,7 +185,7 @@ const allocationSegment = (value) => ({
|
|||||||
<span class="draw-hint geo-import-hint" id="geo-import-hint" aria-live="polite"></span>
|
<span class="draw-hint geo-import-hint" id="geo-import-hint" aria-live="polite"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pp-toggle-group">
|
<div class="pp-toggle-group">
|
||||||
<button class="pp-toggle-btn" id="celestial-toggle" title="Celestial ring — reveal the full globe" aria-pressed="false" data-i18n-title>
|
<button class="pp-toggle-btn" :class="{ active: celestial?.active }" id="celestial-toggle" :title="celestialTitle" :aria-pressed="String(celestial?.active ?? false)" :disabled="celestial ? !celestial.supported : undefined" :aria-disabled="celestial ? String(!celestial.supported) : undefined">
|
||||||
<span class="pp-icon material-symbols-outlined" aria-hidden="true">flare</span>
|
<span class="pp-icon material-symbols-outlined" aria-hidden="true">flare</span>
|
||||||
<span class="pp-label" data-i18n>Celestial</span>
|
<span class="pp-label" data-i18n>Celestial</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+34
-43
@@ -1,5 +1,4 @@
|
|||||||
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
|
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
|
||||||
import { syncScopeControls } from './displayControls.js';
|
|
||||||
import { STYLE_STATUS_LABELS } from './visualPresets.js';
|
import { STYLE_STATUS_LABELS } from './visualPresets.js';
|
||||||
import { RETIRED_STYLES } from '../visualStylePolicy.js';
|
import { RETIRED_STYLES } from '../visualStylePolicy.js';
|
||||||
import { useDisplayStore } from './stores/display.js';
|
import { useDisplayStore } from './stores/display.js';
|
||||||
@@ -49,12 +48,7 @@ const DETECTION_ALLOCATION_STORAGE_KEY = 'gev:detection-allocation:v1';
|
|||||||
/** Own visual preferences, detection overrides and display-control state. */
|
/** Own visual preferences, detection overrides and display-control state. */
|
||||||
export class VisualSettings {
|
export class VisualSettings {
|
||||||
async restoreShareState(state) {
|
async restoreShareState(state) {
|
||||||
const {
|
const { setScopeTerminusOverride, clampScopeTerminusPct } = this.services;
|
||||||
setScopeMaskEnabled,
|
|
||||||
setScopeMaskFeather,
|
|
||||||
setScopeTerminusOverride,
|
|
||||||
clampScopeTerminusPct,
|
|
||||||
} = this.services;
|
|
||||||
const {
|
const {
|
||||||
style,
|
style,
|
||||||
bloom,
|
bloom,
|
||||||
@@ -139,17 +133,7 @@ export class VisualSettings {
|
|||||||
focus: false,
|
focus: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (typeof scopeEnabled === 'boolean') {
|
this._setScope({ enabled: scopeEnabled, featherPct: scopeFeatherPct });
|
||||||
setScopeMaskEnabled(scopeEnabled);
|
|
||||||
syncScopeControls(this._scopeBtn, this._scopeFeatherSlider, scopeEnabled);
|
|
||||||
}
|
|
||||||
if (typeof scopeFeatherPct === 'number' && this._scopeFeatherSlider) {
|
|
||||||
const pct = clampScopeFeatherPct(scopeFeatherPct);
|
|
||||||
this._scopeFeatherSlider.value = String(pct);
|
|
||||||
if (this._scopeFeatherValue)
|
|
||||||
this._scopeFeatherValue.textContent = `${pct}%`;
|
|
||||||
setScopeMaskFeather(pct / 100);
|
|
||||||
}
|
|
||||||
// null restores the altitude-adaptive ramp; a number pins the terminus
|
// null restores the altitude-adaptive ramp; a number pins the terminus
|
||||||
// (clamped to the supported 94..100 band, same as the `sce` hash key).
|
// (clamped to the supported 94..100 band, same as the `sce` hash key).
|
||||||
if (scopeTerminusPct === null) setScopeTerminusOverride(null);
|
if (scopeTerminusPct === null) setScopeTerminusOverride(null);
|
||||||
@@ -218,6 +202,10 @@ export class VisualSettings {
|
|||||||
this._hudVisibleShown = null;
|
this._hudVisibleShown = null;
|
||||||
this._hudVariantShown = null;
|
this._hudVariantShown = null;
|
||||||
this._sonarShown = null;
|
this._sonarShown = null;
|
||||||
|
// What the Scope and Celestial controls show; null until first set.
|
||||||
|
this._scopeEnabledShown = null;
|
||||||
|
this._scopeFeatherShown = null;
|
||||||
|
this._celestialShown = null;
|
||||||
this.activeStyle = 'normal';
|
this.activeStyle = 'normal';
|
||||||
this._preCyberStyle = null;
|
this._preCyberStyle = null;
|
||||||
this._cyberStyleUserSelected = false;
|
this._cyberStyleUserSelected = false;
|
||||||
@@ -483,9 +471,31 @@ export class VisualSettings {
|
|||||||
detection: this._presentDetection(),
|
detection: this._presentDetection(),
|
||||||
hud: { visible: this._hudVisibleShown, variant: this._hudVariantShown },
|
hud: { visible: this._hudVisibleShown, variant: this._hudVariantShown },
|
||||||
sonar: this._sonarShown,
|
sonar: this._sonarShown,
|
||||||
|
scope: {
|
||||||
|
enabled: this._scopeEnabledShown,
|
||||||
|
featherPct: this._scopeFeatherShown,
|
||||||
|
},
|
||||||
|
celestial: this._celestialShown,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the Scope mask and show it on its controls; a field that is not
|
||||||
|
* given stays as it is. `apply: false` only shows the mask's state.
|
||||||
|
*/
|
||||||
|
_setScope({ enabled, featherPct } = {}, { apply = true } = {}) {
|
||||||
|
if (typeof enabled === 'boolean') {
|
||||||
|
if (apply) this.services.setScopeMaskEnabled(enabled);
|
||||||
|
this._scopeEnabledShown = enabled;
|
||||||
|
}
|
||||||
|
if (typeof featherPct === 'number') {
|
||||||
|
const pct = clampScopeFeatherPct(featherPct);
|
||||||
|
if (apply) this.services.setScopeMaskFeather(pct / 100);
|
||||||
|
this._scopeFeatherShown = pct;
|
||||||
|
}
|
||||||
|
this._publishDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
/** Detection's button, rows, sliders and allocation segments. */
|
/** Detection's button, rows, sliders and allocation segments. */
|
||||||
_presentDetection() {
|
_presentDetection() {
|
||||||
const mode = this._detectionModeLabel;
|
const mode = this._detectionModeLabel;
|
||||||
@@ -1150,15 +1160,8 @@ export class VisualSettings {
|
|||||||
if (syncShare) this.shareLinkManager?.claimRestoreLane?.('visual');
|
if (syncShare) this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||||
const nextEnabled = styleSupported && enabled;
|
const nextEnabled = styleSupported && enabled;
|
||||||
this.celestialRing?.setEnabled(nextEnabled);
|
this.celestialRing?.setEnabled(nextEnabled);
|
||||||
this._celestialBtn?.classList.toggle('active', nextEnabled);
|
this._celestialShown = { active: nextEnabled, supported: styleSupported };
|
||||||
this._celestialBtn?.setAttribute('aria-pressed', String(nextEnabled));
|
this._publishDisplay();
|
||||||
if (this._celestialBtn) {
|
|
||||||
this._celestialBtn.disabled = !styleSupported;
|
|
||||||
this._celestialBtn.setAttribute('aria-disabled', String(!styleSupported));
|
|
||||||
this._celestialBtn.title = styleSupported
|
|
||||||
? t('Celestial ring — reveal the full globe')
|
|
||||||
: t('Celestial ring — available in Normal style');
|
|
||||||
}
|
|
||||||
let cameraFocused = false;
|
let cameraFocused = false;
|
||||||
if (nextEnabled && focus) {
|
if (nextEnabled && focus) {
|
||||||
cameraFocused = !!this.celestialRing?.focusFullGlobe();
|
cameraFocused = !!this.celestialRing?.focusFullGlobe();
|
||||||
@@ -1220,7 +1223,6 @@ export class VisualSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async applyVisualState(state = {}, { isCurrent = null } = {}) {
|
async applyVisualState(state = {}, { isCurrent = null } = {}) {
|
||||||
const { setScopeMaskEnabled, setScopeMaskFeather } = this.services;
|
|
||||||
const superseded = () => typeof isCurrent === 'function' && !isCurrent();
|
const superseded = () => typeof isCurrent === 'function' && !isCurrent();
|
||||||
if (superseded()) return false;
|
if (superseded()) return false;
|
||||||
|
|
||||||
@@ -1258,21 +1260,10 @@ export class VisualSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const scopeState = state.scope || {};
|
const scopeState = state.scope || {};
|
||||||
if (typeof scopeState.enabled === 'boolean') {
|
this._setScope({
|
||||||
setScopeMaskEnabled(scopeState.enabled);
|
enabled: scopeState.enabled,
|
||||||
syncScopeControls(
|
featherPct: scopeState.featherPct,
|
||||||
this._scopeBtn,
|
});
|
||||||
this._scopeFeatherSlider,
|
|
||||||
scopeState.enabled,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (typeof scopeState.featherPct === 'number' && this._scopeFeatherSlider) {
|
|
||||||
const pct = clampScopeFeatherPct(scopeState.featherPct);
|
|
||||||
this._scopeFeatherSlider.value = String(pct);
|
|
||||||
if (this._scopeFeatherValue)
|
|
||||||
this._scopeFeatherValue.textContent = `${pct}%`;
|
|
||||||
setScopeMaskFeather(pct / 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
const detectionState = state.detection || {};
|
const detectionState = state.detection || {};
|
||||||
if (typeof detectionState.density === 'number')
|
if (typeof detectionState.density === 'number')
|
||||||
|
|||||||
Reference in new issue
Block a user