feat(ui): render the Display panel's Scope and Celestial controls from the store

This commit is contained in:
Sameh Khamis committed 2026-10-30 23:07:21 -07:00
1 parent 3aa5a3e5e3
commit 1139d364c7
10 files changed
+110 -99

No files matched your search

+3 -3
View File
@@ -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.
+2 -2
View File
@@ -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'],
+1 -7
View File
@@ -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),
+6 -11
View File
@@ -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) => {
+5 -19
View File
@@ -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) => {
-6
View File
@@ -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;
}
+44
View File
@@ -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');
});
-1
View File
@@ -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'),
+15 -7
View File
@@ -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
View File
@@ -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')