feat(ui): render the Display panel's detection controls from the store

This commit is contained in:
Sameh Khamis committed 2026-10-30 22:51:47 -07:00
1 parent 343bdebe98
commit c99c0e8000
12 files changed
+273 -299

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 and sharpen from the `gev-display` store, which VisualSettings bloom, sharpen and detection 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
+2 -2
View File
@@ -524,9 +524,9 @@ test('every explicit visual UI gesture claims restore authority before it mutate
['setScopeFeather:', 'setSharpenIntensity:', 'setScopeMaskFeather(', 'setScopeFeather control'], ['setScopeFeather:', 'setSharpenIntensity:', 'setScopeMaskFeather(', '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._applyDetectionDensityFromUi()', 'setDensity control'], ['setDensity:', 'setAllocation:', 'this._setDetectionDensity(', 'setDensity control'],
['setAllocation:', 'setFade:', 'this._setDetectionAllocation(', 'setAllocation control'], ['setAllocation:', 'setFade:', 'this._setDetectionAllocation(', 'setAllocation control'],
['setFade:', 'toggleCelestial:', 'this._applyDetectionFadeFromUi()', 'setFade control'], ['setFade:', 'toggleCelestial:', 'this._setDetectionFade(', 'setFade control'],
]; ];
for (const [start, end, mutation, label] of gestureRoutes) { for (const [start, end, mutation, label] of gestureRoutes) {
const startIndex = initUi.indexOf(start); const startIndex = initUi.indexOf(start);
+4 -18
View File
@@ -209,17 +209,6 @@ export class StyleManager extends ShellFacade {
elements: { elements: {
_celestialBtn: this._celestialBtn, _celestialBtn: this._celestialBtn,
_cockpitDisplayToggleBtn: this._cockpitDisplayToggleBtn, _cockpitDisplayToggleBtn: this._cockpitDisplayToggleBtn,
_detectionAllocationRow: this._detectionAllocationRow,
_detectionBtn: this._detectionBtn,
_detectionDensitySlider: this._detectionDensitySlider,
_detectionDensityValue: this._detectionDensityValue,
_detectionFadeRow: this._detectionFadeRow,
_detectionFadeSlider: this._detectionFadeSlider,
_detectionFadeValue: this._detectionFadeValue,
_detectionOpacityRow: this._detectionOpacityRow,
_detectionOpacitySlider: this._detectionOpacitySlider,
_detectionOpacityValue: this._detectionOpacityValue,
_detectionSliderRow: this._detectionSliderRow,
_hudBtn: this._hudBtn, _hudBtn: this._hudBtn,
_cyberSonarBtn: this._cyberSonarBtn, _cyberSonarBtn: this._cyberSonarBtn,
_cyberSonarRings: this._cyberSonarRings, _cyberSonarRings: this._cyberSonarRings,
@@ -507,7 +496,7 @@ export class StyleManager extends ShellFacade {
); );
initTrackedReadout(viewer); initTrackedReadout(viewer);
setDetectionStyle(this.activeStyle); setDetectionStyle(this.activeStyle);
this._applyDetectionDensityFromUi(); this._setDetectionDensity();
this._initStages(); this._initStages();
this._initBloomSharpen(); this._initBloomSharpen();
@@ -541,10 +530,10 @@ export class StyleManager extends ShellFacade {
_detectionBtn: this._detectionBtn, _detectionBtn: this._detectionBtn,
_detectionFadeSlider: this._detectionFadeSlider, _detectionFadeSlider: this._detectionFadeSlider,
_detectionOpacitySlider: this._detectionOpacitySlider, _detectionOpacitySlider: this._detectionOpacitySlider,
_detectionAllocationBtns: this._detectionAllocationBtns,
_celestialBtn: this._celestialBtn, _celestialBtn: this._celestialBtn,
_models3dBtn: this._models3dBtn, _models3dBtn: this._models3dBtn,
_scopeFeatherValue: this._scopeFeatherValue, _scopeFeatherValue: this._scopeFeatherValue,
_detectionDensityValue: this._detectionDensityValue,
}, },
operations: { operations: {
setStyle: (...args) => this.setStyle(...args), setStyle: (...args) => this.setStyle(...args),
@@ -560,12 +549,10 @@ export class StyleManager extends ShellFacade {
_setHudVariant: (...args) => this._setHudVariant(...args), _setHudVariant: (...args) => this._setHudVariant(...args),
_setCyberSonarEnabled: (...args) => this._setCyberSonarEnabled(...args), _setCyberSonarEnabled: (...args) => this._setCyberSonarEnabled(...args),
_setCyberSonarSetting: (...args) => this._setCyberSonarSetting(...args), _setCyberSonarSetting: (...args) => this._setCyberSonarSetting(...args),
_applyDetectionDensityFromUi: (...args) => _setDetectionDensity: (...args) => this._setDetectionDensity(...args),
this._applyDetectionDensityFromUi(...args),
_setDetectionAllocation: (...args) => _setDetectionAllocation: (...args) =>
this._setDetectionAllocation(...args), this._setDetectionAllocation(...args),
_applyDetectionFadeFromUi: (...args) => _setDetectionFade: (...args) => this._setDetectionFade(...args),
this._applyDetectionFadeFromUi(...args),
setCelestialRingEnabled: (...args) => setCelestialRingEnabled: (...args) =>
this.setCelestialRingEnabled(...args), this.setCelestialRingEnabled(...args),
_setModels3dEnabled: (...args) => this._setModels3dEnabled(...args), _setModels3dEnabled: (...args) => this._setModels3dEnabled(...args),
@@ -581,7 +568,6 @@ export class StyleManager extends ShellFacade {
celestialRingEnabled: this.celestialRingEnabled, celestialRingEnabled: this.celestialRingEnabled,
_models3dEnabled: this._models3dEnabled, _models3dEnabled: this._models3dEnabled,
_models3dModeBtns: this._models3dModeBtns, _models3dModeBtns: this._models3dModeBtns,
_detectionAllocationBtns: this._detectionAllocationBtns,
}), }),
claimDetection: () => { claimDetection: () => {
this._visualSettings._detectionUserOverridden = true; this._visualSettings._detectionUserOverridden = true;
+1 -1
View File
@@ -93,7 +93,7 @@ function styleOwner(t, initialVariant = 'cyber') {
'_syncCockpitInheritedStyle', '_syncCockpitInheritedStyle',
'_syncCyberSonarControl', '_syncCyberSonarControl',
'_scheduleAdaptivePanelLayout', '_scheduleAdaptivePanelLayout',
'_applyDetectionFadeFromUi', '_setDetectionFade',
// Intensities: the fixture has no post-process stages to drive. // Intensities: the fixture has no post-process stages to drive.
'_setBloomIntensity', '_setBloomIntensity',
'_setSharpenIntensity', '_setSharpenIntensity',
+10 -12
View File
@@ -2,7 +2,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, syncScopeControls } from './displayControls.js';
import { canonicalizeDensity } from '../data/detectionPolicy.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,9 +44,6 @@ export class DisplayBindings {
get _models3dModeBtns() { get _models3dModeBtns() {
return this.readState()._models3dModeBtns; return this.readState()._models3dModeBtns;
} }
get _detectionAllocationBtns() {
return this.readState()._detectionAllocationBtns;
}
_initUI() { _initUI() {
const { const {
cycleDetectionMode, cycleDetectionMode,
@@ -121,7 +117,8 @@ export class DisplayBindings {
densitySlider: this._detectionDensitySlider, densitySlider: this._detectionDensitySlider,
detectionButton: this._detectionBtn, detectionButton: this._detectionBtn,
allocationButtons: this._detectionAllocationBtns, allocationButtons: this._detectionAllocationBtns,
fadeSliders: [this._detectionFadeSlider, this._detectionOpacitySlider], fadeSlider: this._detectionFadeSlider,
outsideOpacitySlider: this._detectionOpacitySlider,
celestialButton: this._celestialBtn, celestialButton: this._celestialBtn,
modelsButton: this._models3dBtn, modelsButton: this._models3dBtn,
modelModeButtons: this._models3dBtn ? this._models3dModeBtns : [], modelModeButtons: this._models3dBtn ? this._models3dModeBtns : [],
@@ -167,11 +164,7 @@ export class DisplayBindings {
setDensity: (value) => { setDensity: (value) => {
this.shareLinkManager?.claimRestoreLane?.('visual'); this.shareLinkManager?.claimRestoreLane?.('visual');
this.claimDetection(); this.claimDetection();
const pct = canonicalizeDensity(value); this._setDetectionDensity(value);
this._detectionDensitySlider.value = String(pct);
if (this._detectionDensityValue)
this._detectionDensityValue.textContent = `${pct}%`;
this._applyDetectionDensityFromUi();
this._syncShareState(); this._syncShareState();
}, },
setAllocation: (value) => { setAllocation: (value) => {
@@ -179,9 +172,14 @@ export class DisplayBindings {
this.claimDetection(); this.claimDetection();
this._setDetectionAllocation(value); this._setDetectionAllocation(value);
}, },
setFade: () => { setFade: (fadePct) => {
this.shareLinkManager?.claimRestoreLane?.('visual'); this.shareLinkManager?.claimRestoreLane?.('visual');
this._applyDetectionFadeFromUi(); this._setDetectionFade({ fadePct });
this._syncShareState();
},
setOutsideOpacity: (outsideOpacityPct) => {
this.shareLinkManager?.claimRestoreLane?.('visual');
this._setDetectionFade({ outsideOpacityPct });
this._syncShareState(); this._syncShareState();
}, },
toggleCelestial: () => { toggleCelestial: () => {
+2 -1
View File
@@ -65,7 +65,8 @@ export function bindDisplayControls({ elements, actions }) {
}); });
removers.push(() => control.destroy()); removers.push(() => control.destroy());
} }
for (const el of elements.fadeSliders || []) listen(el, 'input', 'setFade'); listen(elements.fadeSlider, 'input', 'setFade', integer);
listen(elements.outsideOpacitySlider, 'input', 'setOutsideOpacity', integer);
return { return {
destroy() { destroy() {
for (const remove of removers.splice(0)) remove(); for (const remove of removers.splice(0)) remove();
+3 -3
View File
@@ -145,11 +145,11 @@ test('optional controls are absent safely and subscriptions stay instance-owned'
const a = element(); const a = element();
const b = element(); const b = element();
const one = bindDisplayControls({ const one = bindDisplayControls({
elements: { fadeSliders: [null, a] }, elements: { fadeSlider: null, outsideOpacitySlider: a },
actions: { setFade: () => calls.push('a') }, actions: { setOutsideOpacity: () => calls.push('a') },
}); });
const two = bindDisplayControls({ const two = bindDisplayControls({
elements: { fadeSliders: [b] }, elements: { fadeSlider: b },
actions: { setFade: () => calls.push('b') }, actions: { setFade: () => calls.push('b') },
}); });
one.destroy(); one.destroy();
+84
View File
@@ -121,3 +121,87 @@ test('a publish never rewrites a slider the user is dragging', async (t) => {
assert.equal(writes, 0); assert.equal(writes, 0);
assert.equal(slider.value, '30'); assert.equal(slider.value, '30');
}); });
/** VisualSettings with a stand-in detection engine. */
function detectionSettings(store) {
const { owner } = settings(store);
const engine = {
mode: 'OFF',
tuning: { densityPct: 50, allocationStrategy: 'ELASTIC' },
fade: { fadeRatio: 0.07, outsideOpacity: 0.01 },
};
Object.assign(owner, {
_detectionDensityPct: 50,
_detectionFadePct: 7,
_detectionOutsidePct: 1,
_detectionModeLabel: null,
_detectionAllocationShown: null,
viewer: { scene: { requestRender() {} } },
});
owner.services = {
...owner.services,
getDetectionMode: () => engine.mode,
setDetectionModeByLabel: (label) => {
engine.mode = label;
},
getDetectionTuning: () => ({ ...engine.tuning }),
setDetectionTuning: (next) => Object.assign(engine.tuning, next),
getKeyholeFadeTuning: () => ({ ...engine.fade }),
setKeyholeFadeTuning: (next) => Object.assign(engine.fade, next),
};
return { owner, engine };
}
test('detection renders its mode, rows, sliders and allocation', async (t) => {
const { store, $ } = fixture(t);
const { owner, engine } = detectionSettings(store);
const button = $('detection-toggle');
assert.equal(button.querySelector('.pp-label').textContent, 'DETECT');
assert.equal(button.getAttribute('aria-label'), 'Detection overlay');
owner._setDetectionMode('DENSE');
owner._setDetectionDensity(80);
await nextTick();
assert.equal(button.querySelector('.pp-label').textContent, 'DENSE');
assert.equal(button.getAttribute('aria-pressed'), 'true');
assert.equal(button.getAttribute('aria-label'), 'Detection overlay: dense');
assert.equal(button.className, 'pp-toggle-btn active panoptic');
for (const id of [
'detection-slider-row',
'detection-allocation-row',
'detection-fade-row',
'detection-opacity-row',
])
assert.equal($(id).classList.contains('visible'), true, id);
assert.equal($('detection-density-slider').value, '75', 'nearest stop');
assert.equal($('detection-density-value').textContent, '75%');
assert.equal(engine.tuning.densityPct, 75);
owner._setDetectionFade({ fadePct: 55 });
owner._setDetectionAllocation('weighted', { persist: false });
await nextTick();
assert.equal($('detection-fade-slider').value, '40', 'clamped to 40');
assert.equal($('detection-fade-value').textContent, '40%');
assert.equal($('detection-opacity-value').textContent, '1%', 'kept');
assert.equal(engine.fade.fadeRatio, 0.4);
const weighted = $('detection-allocation-weighted');
const elastic = $('detection-allocation-elastic');
assert.equal(weighted.classList.contains('active'), true);
assert.equal(weighted.getAttribute('aria-checked'), 'true');
assert.equal(elastic.classList.contains('active'), false);
assert.equal(elastic.getAttribute('aria-checked'), 'false');
assert.deepEqual(owner.getDetectionState(), {
detectionMode: 'DENSE',
densityPct: 75,
allocationStrategy: 'WEIGHTED',
fadePct: 40,
outsideOpacityPct: 1,
});
owner._setDetectionMode('OFF');
await nextTick();
assert.equal(button.querySelector('.pp-label').textContent, 'DETECT');
assert.equal(button.getAttribute('aria-label'), 'Detection overlay: off');
assert.equal(button.className, 'pp-toggle-btn');
assert.equal($('detection-slider-row').classList.contains('visible'), false);
});
+4 -9
View File
@@ -28,22 +28,17 @@ export function readShellElements(document = globalThis.document) {
), ),
_cyberSonarSector: document.getElementById('cyber-sonar-sector'), _cyberSonarSector: document.getElementById('cyber-sonar-sector'),
_cyberSonarSectorValue: document.getElementById('cyber-sonar-sector-value'), _cyberSonarSectorValue: document.getElementById('cyber-sonar-sector-value'),
_detectionSliderRow: document.getElementById('detection-slider-row'),
_detectionDensitySlider: document.getElementById( _detectionDensitySlider: document.getElementById(
'detection-density-slider', 'detection-density-slider',
), ),
_detectionDensityValue: document.getElementById('detection-density-value'), _detectionAllocationBtns: [
_detectionAllocationRow: document.getElementById( document.getElementById('detection-allocation-elastic'),
'detection-allocation-row', document.getElementById('detection-allocation-weighted'),
), ].filter(Boolean),
_detectionFadeRow: document.getElementById('detection-fade-row'),
_detectionFadeSlider: document.getElementById('detection-fade-slider'), _detectionFadeSlider: document.getElementById('detection-fade-slider'),
_detectionFadeValue: document.getElementById('detection-fade-value'),
_detectionOpacityRow: document.getElementById('detection-opacity-row'),
_detectionOpacitySlider: document.getElementById( _detectionOpacitySlider: document.getElementById(
'detection-opacity-slider', 'detection-opacity-slider',
), ),
_detectionOpacityValue: document.getElementById('detection-opacity-value'),
_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'),
+14 -15
View File
@@ -429,14 +429,6 @@ export class ShellFacade {
this._visualSettings._contactsDetectionRestore = value; this._visualSettings._contactsDetectionRestore = value;
} }
get _detectionAllocationBtns() {
return this._visualSettings._detectionAllocationBtns;
}
set _detectionAllocationBtns(value) {
this._visualSettings._detectionAllocationBtns = value;
}
get _detectionAllocationPreference() { get _detectionAllocationPreference() {
return this._visualSettings._detectionAllocationPreference; return this._visualSettings._detectionAllocationPreference;
} }
@@ -716,17 +708,24 @@ export class ShellFacade {
} }
/** /**
* Reads and canonicalizes the five-stop density control. The engine derives * Sets the five-stop density control, canonicalized; the engine derives
* Sparse/Balanced/Dense from the same stop. * Sparse/Balanced/Dense from the same stop. Without a value it reapplies
* the current stop.
* @param {number} [pct]
* @returns {void} * @returns {void}
*/ */
_applyDetectionDensityFromUi() { _setDetectionDensity(pct) {
return this._visualSettings._applyDetectionDensityFromUi(...arguments); return this._visualSettings._setDetectionDensity(...arguments);
} }
/** Apply responsive keyhole fade controls from normalized UI percentages. */ /**
_applyDetectionFadeFromUi() { * Sets the keyhole fade distance and outside opacity, in percent; either
return this._visualSettings._applyDetectionFadeFromUi(...arguments); * one left out keeps its current value.
* @param {{fadePct?: number, outsideOpacityPct?: number}} [tuning]
* @returns {void}
*/
_setDetectionFade(tuning) {
return this._visualSettings._setDetectionFade(...arguments);
} }
_syncDetectionUiFromEngine() { _syncDetectionUiFromEngine() {
+29 -19
View File
@@ -1,12 +1,22 @@
<script setup> <script setup>
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { computed } from 'vue';
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 and sharpen and // The Display panel. VisualSettings presents bloom, sharpen and detection
// publishes them; until it does, the panel shows its initial markup. The // and publishes them; until it does, the panel shows its initial markup.
// other groups keep their owners' element bindings. // The other groups keep their owners' element bindings.
const { view } = storeToRefs(useDisplayStore()); const { view } = storeToRefs(useDisplayStore());
const pct = (value) => `${value}%`;
const detection = computed(() => view.value?.detection ?? null);
const detectButton = computed(() => detection.value?.button ?? null);
const allocation = computed(() => detection.value?.allocation ?? 'ELASTIC');
const allocationSegment = (value) => ({
class: { active: allocation.value === value },
'aria-checked': String(allocation.value === value),
});
</script> </script>
<template> <template>
@@ -63,31 +73,31 @@ const { view } = storeToRefs(useDisplayStore());
</div> </div>
</div> </div>
<div class="pp-toggle-group"> <div class="pp-toggle-group">
<button class="pp-toggle-btn" id="detection-toggle" title="Detection Overlay (D)" aria-label="Detection overlay" aria-pressed="false" data-i18n-title data-i18n-aria-label> <button class="pp-toggle-btn" :class="{ active: detectButton?.active, panoptic: detectButton?.panoptic }" id="detection-toggle" title="Detection Overlay (D)" :aria-label="detectButton?.ariaLabel ?? t('Detection overlay')" :aria-pressed="String(detectButton?.pressed ?? false)" data-i18n-title>
<span class="pp-icon">&#x25A3;</span> <span class="pp-icon">&#x25A3;</span>
<span class="pp-label" data-i18n>DETECT</span> <span class="pp-label">{{ detectButton?.label ?? t('DETECT') }}</span>
</button> </button>
<div class="pp-slider-row detection-slider-row" id="detection-slider-row"> <div class="pp-slider-row detection-slider-row" :class="{ visible: detection?.rowsVisible }" id="detection-slider-row">
<label class="pp-slider-mini-label" for="detection-density-slider" data-i18n>Density</label> <label class="pp-slider-mini-label" for="detection-density-slider" data-i18n>Density</label>
<input type="range" class="gev-quantitative-slider pp-slider" id="detection-density-slider" min="0" max="100" step="25" value="50" aria-label="Detection label density" data-i18n-aria-label /> <input type="range" class="gev-quantitative-slider pp-slider" id="detection-density-slider" min="0" max="100" step="25" value="50" v-range-value="detection?.densityPct ?? 50" aria-label="Detection label density" data-i18n-aria-label />
<span class="pp-slider-value" id="detection-density-value">50%</span> <span class="pp-slider-value" id="detection-density-value">{{ pct(detection?.densityPct ?? 50) }}</span>
</div> </div>
<div class="pp-slider-row detection-allocation-row" id="detection-allocation-row"> <div class="pp-slider-row detection-allocation-row" :class="{ visible: detection?.rowsVisible }" id="detection-allocation-row">
<span class="pp-slider-mini-label" data-i18n>Allocation</span> <span class="pp-slider-mini-label" data-i18n>Allocation</span>
<div class="gev-segmented pp-mode-seg detection-allocation-seg" role="radiogroup" aria-label="Detection label allocation" data-i18n-aria-label> <div class="gev-segmented pp-mode-seg detection-allocation-seg" role="radiogroup" aria-label="Detection label allocation" data-i18n-aria-label>
<button class="gev-segment pp-mode-btn active" id="detection-allocation-elastic" data-allocation="ELASTIC" role="radio" aria-checked="true" data-i18n>Elastic</button> <button class="gev-segment pp-mode-btn" v-bind="allocationSegment('ELASTIC')" id="detection-allocation-elastic" data-allocation="ELASTIC" role="radio" data-i18n>Elastic</button>
<button class="gev-segment pp-mode-btn" id="detection-allocation-weighted" data-allocation="WEIGHTED" role="radio" aria-checked="false" data-i18n>Weighted</button> <button class="gev-segment pp-mode-btn" v-bind="allocationSegment('WEIGHTED')" id="detection-allocation-weighted" data-allocation="WEIGHTED" role="radio" data-i18n>Weighted</button>
</div> </div>
</div> </div>
<div class="pp-slider-row detection-fade-row" id="detection-fade-row"> <div class="pp-slider-row detection-fade-row" :class="{ visible: detection?.rowsVisible }" id="detection-fade-row">
<label class="pp-slider-mini-label" for="detection-fade-slider" data-i18n>Fade</label> <label class="pp-slider-mini-label" for="detection-fade-slider" data-i18n>Fade</label>
<input type="range" class="gev-quantitative-slider pp-slider" id="detection-fade-slider" min="0" max="40" step="1" value="7" aria-label="Detection fade distance" title="World-overlay fade distance outside the keyhole as a percentage of its radius" data-i18n-aria-label data-i18n-title /> <input type="range" class="gev-quantitative-slider pp-slider" id="detection-fade-slider" min="0" max="40" step="1" value="7" v-range-value="detection?.fadePct ?? 7" aria-label="Detection fade distance" title="World-overlay fade distance outside the keyhole as a percentage of its radius" data-i18n-aria-label data-i18n-title />
<span class="pp-slider-value" id="detection-fade-value">7%</span> <span class="pp-slider-value" id="detection-fade-value">{{ pct(detection?.fadePct ?? 7) }}</span>
</div> </div>
<div class="pp-slider-row detection-opacity-row" id="detection-opacity-row"> <div class="pp-slider-row detection-opacity-row" :class="{ visible: detection?.rowsVisible }" id="detection-opacity-row">
<label class="pp-slider-mini-label" for="detection-opacity-slider" data-i18n>Outside</label> <label class="pp-slider-mini-label" for="detection-opacity-slider" data-i18n>Outside</label>
<input type="range" class="gev-quantitative-slider pp-slider" id="detection-opacity-slider" min="0" max="100" step="1" value="1" aria-label="Detection opacity outside the keyhole" title="World-overlay label and card opacity beyond the fade distance" data-i18n-aria-label data-i18n-title /> <input type="range" class="gev-quantitative-slider pp-slider" id="detection-opacity-slider" min="0" max="100" step="1" value="1" v-range-value="detection?.outsideOpacityPct ?? 1" aria-label="Detection opacity outside the keyhole" title="World-overlay label and card opacity beyond the fade distance" data-i18n-aria-label data-i18n-title />
<span class="pp-slider-value" id="detection-opacity-value">1%</span> <span class="pp-slider-value" id="detection-opacity-value">{{ pct(detection?.outsideOpacityPct ?? 1) }}</span>
</div> </div>
</div> </div>
<!-- The one shared preset-parameter surface is inserted here at runtime. --> <!-- The one shared preset-parameter surface is inserted here at runtime. -->
@@ -180,7 +190,7 @@ const { view } = storeToRefs(useDisplayStore());
</button> </button>
<div class="pp-slider-row" :class="{ visible: view?.bloom.active }" id="bloom-slider-row"> <div class="pp-slider-row" :class="{ visible: view?.bloom.active }" id="bloom-slider-row">
<input type="range" class="gev-quantitative-slider pp-slider" id="bloom-intensity-slider" min="0" max="200" value="0" v-range-value="view?.bloom.intensity ?? 0" aria-label="Bloom intensity" data-i18n-aria-label /> <input type="range" class="gev-quantitative-slider pp-slider" id="bloom-intensity-slider" min="0" max="200" value="0" v-range-value="view?.bloom.intensity ?? 0" aria-label="Bloom intensity" data-i18n-aria-label />
<span class="pp-slider-value" id="bloom-intensity-value">{{ view?.bloom.text ?? '0%' }}</span> <span class="pp-slider-value" id="bloom-intensity-value">{{ pct(view?.bloom.intensity ?? 0) }}</span>
</div> </div>
</div> </div>
<div class="pp-toggle-group"> <div class="pp-toggle-group">
@@ -190,7 +200,7 @@ const { view } = storeToRefs(useDisplayStore());
</button> </button>
<div class="pp-slider-row" :class="{ visible: view?.sharpen.active }" id="sharpen-slider-row"> <div class="pp-slider-row" :class="{ visible: view?.sharpen.active }" id="sharpen-slider-row">
<input type="range" class="gev-quantitative-slider pp-slider" id="sharpen-intensity-slider" min="0" max="100" value="49" v-range-value="view?.sharpen.intensity ?? 49" aria-label="Sharpen intensity" data-i18n-aria-label /> <input type="range" class="gev-quantitative-slider pp-slider" id="sharpen-intensity-slider" min="0" max="100" value="49" v-range-value="view?.sharpen.intensity ?? 49" aria-label="Sharpen intensity" data-i18n-aria-label />
<span class="pp-slider-value" id="sharpen-intensity-value">{{ view?.sharpen.text ?? '49%' }}</span> <span class="pp-slider-value" id="sharpen-intensity-value">{{ pct(view?.sharpen.intensity ?? 49) }}</span>
</div> </div>
</div> </div>
</div> </div>
+119 -218
View File
@@ -2,7 +2,6 @@ import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
import { syncScopeControls } from './displayControls.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 { syncSegmentedControl } from './segmentedControl.js';
import { useDisplayStore } from './stores/display.js'; import { useDisplayStore } from './stores/display.js';
import { applicationPinia } from './vueRuntime.js'; import { applicationPinia } from './vueRuntime.js';
import { t } from '../i18n/index.js'; import { t } from '../i18n/index.js';
@@ -117,28 +116,22 @@ export class VisualSettings {
this.hud.setMode(hudVisible ? 'on' : 'off'); this.hud.setMode(hudVisible ? 'on' : 'off');
this._updateHudButtonState(); this._updateHudButtonState();
} }
if (typeof detectionDensity === 'number' && this._detectionDensitySlider) { if (typeof detectionDensity === 'number')
const pct = canonicalizeDensity(detectionDensity); this._setDetectionDensity(detectionDensity);
this._detectionDensitySlider.value = String(pct);
this._detectionDensityValue.textContent = `${pct}%`;
this._applyDetectionDensityFromUi();
}
if (detectionAllocation) { if (detectionAllocation) {
this._setDetectionAllocation(detectionAllocation, { this._setDetectionAllocation(detectionAllocation, {
syncShare: false, syncShare: false,
persist: false, persist: false,
}); });
} }
if (typeof detectionFadePct === 'number' && this._detectionFadeSlider) { this._setDetectionFade({
this._detectionFadeSlider.value = String(detectionFadePct); fadePct:
} typeof detectionFadePct === 'number' ? detectionFadePct : undefined,
if ( outsideOpacityPct:
typeof detectionOutsideOpacityPct === 'number' && typeof detectionOutsideOpacityPct === 'number'
this._detectionOpacitySlider ? detectionOutsideOpacityPct
) { : undefined,
this._detectionOpacitySlider.value = String(detectionOutsideOpacityPct); });
}
this._applyDetectionFadeFromUi();
if (detectionMode) this._setDetectionMode(detectionMode); if (detectionMode) this._setDetectionMode(detectionMode);
if (typeof celestialRing === 'boolean') { if (typeof celestialRing === 'boolean') {
this.setCelestialRingEnabled(celestialRing, { this.setCelestialRingEnabled(celestialRing, {
@@ -212,6 +205,14 @@ export class VisualSettings {
// The sharpen amount as a whole percentage. VisualEffects keeps the // The sharpen amount as a whole percentage. VisualEffects keeps the
// fraction it applies, and drops writes once it is stopped. // fraction it applies, and drops writes once it is stopped.
this._sharpenPct = 49; this._sharpenPct = 49;
// Detection's density stop, keyhole fade and outside opacity, in whole
// percentages; the mode label the button shows (null until first
// synced) and the allocation its segments show (null until set).
this._detectionDensityPct = 50;
this._detectionFadePct = 7;
this._detectionOutsidePct = 1;
this._detectionModeLabel = null;
this._detectionAllocationShown = null;
this.activeStyle = 'normal'; this.activeStyle = 'normal';
this._preCyberStyle = null; this._preCyberStyle = null;
this._cyberStyleUserSelected = false; this._cyberStyleUserSelected = false;
@@ -220,10 +221,6 @@ export class VisualSettings {
this._cockpitVisionMode = 'optical'; this._cockpitVisionMode = 'optical';
this._cockpitVisionRestore = null; this._cockpitVisionRestore = null;
this._contactsDetectionRestore = null; this._contactsDetectionRestore = null;
this._detectionAllocationBtns = [
document.getElementById('detection-allocation-elastic'),
document.getElementById('detection-allocation-weighted'),
].filter(Boolean);
let storedDetectionAllocation = 'ELASTIC'; let storedDetectionAllocation = 'ELASTIC';
try { try {
storedDetectionAllocation = storedDetectionAllocation =
@@ -475,14 +472,43 @@ export class VisualSettings {
/** Present the Display panel's post-processing groups. */ /** Present the Display panel's post-processing groups. */
_presentDisplay() { _presentDisplay() {
const bloom = this.bloomIntensityPct;
return { return {
bloom: { active: this.bloomEnabled, intensity: bloom, text: `${bloom}%` }, bloom: { active: this.bloomEnabled, intensity: this.bloomIntensityPct },
sharpen: { sharpen: { active: this.sharpenEnabled, intensity: this._sharpenPct },
active: this.sharpenEnabled, detection: this._presentDetection(),
intensity: this._sharpenPct, };
text: `${this._sharpenPct}%`, }
},
/** Detection's button, rows, sliders and allocation segments. */
_presentDetection() {
const mode = this._detectionModeLabel;
const enabled = mode != null && mode !== 'OFF';
const labels = {
SPARSE: t('SPARSE'),
BALANCED: t('BALANCED'),
DENSE: t('DENSE'),
};
return {
// Null until the first sync, so the initial markup stands.
button:
mode == null
? null
: {
pressed: enabled,
ariaLabel: enabled
? t('Detection overlay: {mode}', {
mode: String(mode).toLowerCase(),
})
: t('Detection overlay: off'),
label: labels[mode] ?? t('DETECT'),
active: Object.hasOwn(labels, mode),
panoptic: mode === 'DENSE',
},
rowsVisible: enabled,
densityPct: this._detectionDensityPct,
fadePct: this._detectionFadePct,
outsideOpacityPct: this._detectionOutsidePct,
allocation: this._detectionAllocationShown,
}; };
} }
@@ -547,44 +573,31 @@ export class VisualSettings {
this._layoutRightPanels(); this._layoutRightPanels();
} }
_applyDetectionDensityFromUi() { /** Set detection's density to its nearest stop and apply it. */
_setDetectionDensity(pct = this._detectionDensityPct) {
const { getDetectionMode, setDetectionTuning } = this.services; const { getDetectionMode, setDetectionTuning } = this.services;
if (!this._detectionDensitySlider) return; this._detectionDensityPct = canonicalizeDensity(pct);
const pct = canonicalizeDensity(this._detectionDensitySlider.value); setDetectionTuning({ densityPct: this._detectionDensityPct });
this._detectionDensitySlider.value = String(pct);
if (this._detectionDensityValue)
this._detectionDensityValue.textContent = `${pct}%`;
setDetectionTuning({ densityPct: pct });
this._updateDetectionButton(getDetectionMode()); this._updateDetectionButton(getDetectionMode());
} }
_applyDetectionFadeFromUi() { /**
* Set the keyhole fade distance (0-40%) and the opacity outside it
* (0-100%), keeping either one that is not given, and apply them.
*/
_setDetectionFade({
fadePct = this._detectionFadePct,
outsideOpacityPct = this._detectionOutsidePct,
} = {}) {
const { setKeyholeFadeTuning } = this.services; const { setKeyholeFadeTuning } = this.services;
const fadePct = Math.max( const whole = (value, max) =>
0, Math.max(0, Math.min(max, Math.round(Number(value) || 0)));
Math.min(40, Math.round(Number(this._detectionFadeSlider?.value) || 0)), this._detectionFadePct = whole(fadePct, 40);
); this._detectionOutsidePct = whole(outsideOpacityPct, 100);
const outsideOpacityValue = this._detectionOpacitySlider?.value; this._publishDisplay();
const outsideOpacityPct = Math.max(
0,
Math.min(
100,
Math.round(
outsideOpacityValue == null ? 3 : Number(outsideOpacityValue) || 0,
),
),
);
if (this._detectionFadeSlider)
this._detectionFadeSlider.value = String(fadePct);
if (this._detectionFadeValue)
this._detectionFadeValue.textContent = `${fadePct}%`;
if (this._detectionOpacitySlider)
this._detectionOpacitySlider.value = String(outsideOpacityPct);
if (this._detectionOpacityValue)
this._detectionOpacityValue.textContent = `${outsideOpacityPct}%`;
setKeyholeFadeTuning({ setKeyholeFadeTuning({
fadeRatio: fadePct / 100, fadeRatio: this._detectionFadePct / 100,
outsideOpacity: outsideOpacityPct / 100, outsideOpacity: this._detectionOutsidePct / 100,
}); });
this.viewer.scene.requestRender?.(); this.viewer.scene.requestRender?.();
} }
@@ -598,9 +611,8 @@ export class VisualSettings {
const normalized = normalizeAllocationStrategy(raw); const normalized = normalizeAllocationStrategy(raw);
this._detectionAllocationPreference = normalized; this._detectionAllocationPreference = normalized;
setDetectionTuning({ allocationStrategy: normalized }); setDetectionTuning({ allocationStrategy: normalized });
syncSegmentedControl(this._detectionAllocationBtns, normalized, { this._detectionAllocationShown = normalized;
valueOf: (button) => button.dataset.allocation, this._publishDisplay();
});
if (persist) { if (persist) {
try { try {
localStorage.setItem(DETECTION_ALLOCATION_STORAGE_KEY, normalized); localStorage.setItem(DETECTION_ALLOCATION_STORAGE_KEY, normalized);
@@ -616,25 +628,16 @@ export class VisualSettings {
const { getKeyholeFadeTuning, getDetectionTuning, getDetectionMode } = const { getKeyholeFadeTuning, getDetectionTuning, getDetectionMode } =
this.services; this.services;
const tuning = getDetectionTuning(); const tuning = getDetectionTuning();
if (this._detectionDensitySlider) this._detectionDensityPct = canonicalizeDensity(tuning.densityPct);
this._detectionDensitySlider.value = String(tuning.densityPct);
if (this._detectionDensityValue)
this._detectionDensityValue.textContent = `${tuning.densityPct}%`;
this._setDetectionAllocation(tuning.allocationStrategy, { this._setDetectionAllocation(tuning.allocationStrategy, {
syncShare: false, syncShare: false,
persist: false, persist: false,
}); });
const fadeTuning = getKeyholeFadeTuning(); const fadeTuning = getKeyholeFadeTuning();
if (this._detectionFadeSlider) this._setDetectionFade({
this._detectionFadeSlider.value = String( fadePct: fadeTuning.fadeRatio * 100,
Math.round(fadeTuning.fadeRatio * 100), outsideOpacityPct: fadeTuning.outsideOpacity * 100,
); });
if (this._detectionOpacitySlider) {
this._detectionOpacitySlider.value = String(
Math.round(fadeTuning.outsideOpacity * 100),
);
}
this._applyDetectionFadeFromUi();
this._updateDetectionButton(getDetectionMode()); this._updateDetectionButton(getDetectionMode());
} }
@@ -810,13 +813,8 @@ export class VisualSettings {
_applyDetectionPreset(det) { _applyDetectionPreset(det) {
if (!det) return; if (!det) return;
if (typeof det.densityPct === 'number' && this._detectionDensitySlider) { if (typeof det.densityPct === 'number')
const pct = canonicalizeDensity(det.densityPct); this._setDetectionDensity(det.densityPct);
this._detectionDensitySlider.value = String(pct);
if (this._detectionDensityValue)
this._detectionDensityValue.textContent = `${pct}%`;
this._applyDetectionDensityFromUi();
}
if (det.mode) this._setDetectionMode(String(det.mode).toUpperCase()); if (det.mode) this._setDetectionMode(String(det.mode).toUpperCase());
} }
@@ -850,30 +848,18 @@ export class VisualSettings {
if (defaults.detectionMode) { if (defaults.detectionMode) {
this._setDetectionMode(defaults.detectionMode); this._setDetectionMode(defaults.detectionMode);
} }
if ( if (typeof defaults.detectionDensity === 'number')
typeof defaults.detectionDensity === 'number' && this._setDetectionDensity(defaults.detectionDensity);
this._detectionDensitySlider
) {
const density = canonicalizeDensity(defaults.detectionDensity);
this._detectionDensitySlider.value = String(density);
this._detectionDensityValue.textContent = `${density}%`;
this._applyDetectionDensityFromUi();
}
this._setDetectionAllocation( this._setDetectionAllocation(
this._detectionAllocationPreference || this._detectionAllocationPreference ||
defaults.detectionAllocation || defaults.detectionAllocation ||
'ELASTIC', 'ELASTIC',
{ syncShare: false, persist: false }, { syncShare: false, persist: false },
); );
if (this._detectionFadeSlider) { this._setDetectionFade({
this._detectionFadeSlider.value = String(defaults.detectionFadePct ?? 7); fadePct: defaults.detectionFadePct ?? 7,
} outsideOpacityPct: defaults.detectionOutsideOpacityPct ?? 1,
if (this._detectionOpacitySlider) { });
this._detectionOpacitySlider.value = String(
defaults.detectionOutsideOpacityPct ?? 1,
);
}
this._applyDetectionFadeFromUi();
if (typeof defaults.celestialRing === 'boolean') { if (typeof defaults.celestialRing === 'boolean') {
this.setCelestialRingEnabled(defaults.celestialRing, { this.setCelestialRingEnabled(defaults.celestialRing, {
syncShare: false, syncShare: false,
@@ -887,7 +873,7 @@ export class VisualSettings {
return shareableDetectionState({ return shareableDetectionState({
owned: this._contactsDetectionRestore, owned: this._contactsDetectionRestore,
liveMode: getDetectionMode(), liveMode: getDetectionMode(),
liveDensityPct: parseInt(this._detectionDensitySlider?.value || '50', 10), liveDensityPct: this._detectionDensityPct,
}); });
} }
@@ -911,11 +897,8 @@ export class VisualSettings {
detectionMode: detection.mode, detectionMode: detection.mode,
detectionDensity: detection.densityPct, detectionDensity: detection.densityPct,
detectionAllocation: getDetectionTuning().allocationStrategy, detectionAllocation: getDetectionTuning().allocationStrategy,
detectionFadePct: parseInt(this._detectionFadeSlider?.value || '7', 10), detectionFadePct: this._detectionFadePct,
detectionOutsideOpacityPct: parseInt( detectionOutsideOpacityPct: this._detectionOutsidePct,
this._detectionOpacitySlider?.value || '1',
10,
),
celestialRingEnabled: this.celestialRingEnabled, celestialRingEnabled: this.celestialRingEnabled,
scopeEnabled: isScopeMaskEnabled(), scopeEnabled: isScopeMaskEnabled(),
scopeFeatherPct: Math.round(getScopeMaskFeather() * 100), scopeFeatherPct: Math.round(getScopeMaskFeather() * 100),
@@ -931,18 +914,12 @@ export class VisualSettings {
getDetectionState() { getDetectionState() {
const { getDetectionTuning, getDetectionMode } = this.services; const { getDetectionTuning, getDetectionMode } = this.services;
const pct = this._detectionDensitySlider
? parseInt(this._detectionDensitySlider.value, 10)
: null;
return { return {
detectionMode: getDetectionMode(), detectionMode: getDetectionMode(),
densityPct: pct, densityPct: this._detectionDensityPct,
allocationStrategy: getDetectionTuning().allocationStrategy, allocationStrategy: getDetectionTuning().allocationStrategy,
fadePct: parseInt(this._detectionFadeSlider?.value || '7', 10), fadePct: this._detectionFadePct,
outsideOpacityPct: parseInt( outsideOpacityPct: this._detectionOutsidePct,
this._detectionOpacitySlider?.value || '0',
10,
),
}; };
} }
@@ -1047,18 +1024,11 @@ export class VisualSettings {
if (requestedAllocation) { if (requestedAllocation) {
this._setDetectionAllocation(requestedAllocation, { syncShare: false }); this._setDetectionAllocation(requestedAllocation, { syncShare: false });
} }
if (fadePct != null && this._detectionFadeSlider) {
this._detectionFadeSlider.value = String(
Math.max(0, Math.min(40, Math.round(Number(fadePct)))),
);
}
if (outsideOpacityPct != null && this._detectionOpacitySlider) {
this._detectionOpacitySlider.value = String(
Math.max(0, Math.min(100, Math.round(Number(outsideOpacityPct)))),
);
}
if (fadePct != null || outsideOpacityPct != null) if (fadePct != null || outsideOpacityPct != null)
this._applyDetectionFadeFromUi(); this._setDetectionFade({
fadePct: fadePct ?? undefined,
outsideOpacityPct: outsideOpacityPct ?? undefined,
});
if ( if (
requestedProfile && requestedProfile &&
@@ -1067,10 +1037,7 @@ export class VisualSettings {
) { ) {
requestedDensity = defaultDensityForProfile(requestedProfile); requestedDensity = defaultDensityForProfile(requestedProfile);
} }
if (requestedDensity != null && this._detectionDensitySlider) { if (requestedDensity != null) this._setDetectionDensity(requestedDensity);
this._detectionDensitySlider.value = String(requestedDensity);
this._applyDetectionDensityFromUi();
}
if (enabled === false || requestedProfile === 'OFF') { if (enabled === false || requestedProfile === 'OFF') {
setDetectionModeByLabel('OFF'); setDetectionModeByLabel('OFF');
@@ -1078,9 +1045,7 @@ export class VisualSettings {
setDetectionModeByLabel(requestedProfile); setDetectionModeByLabel(requestedProfile);
} else if (enabled === true && getDetectionMode() === 'OFF') { } else if (enabled === true && getDetectionMode() === 'OFF') {
setDetectionModeByLabel( setDetectionModeByLabel(
profileForDensity( profileForDensity(requestedDensity ?? this._detectionDensityPct),
requestedDensity ?? this._detectionDensitySlider?.value ?? 50,
),
); );
} }
this._syncDetectionUiFromEngine(); this._syncDetectionUiFromEngine();
@@ -1255,13 +1220,10 @@ export class VisualSettings {
}, },
detection: { detection: {
mode: getDetectionMode(), mode: getDetectionMode(),
density: parseInt(this._detectionDensitySlider?.value || '50', 10), density: this._detectionDensityPct,
allocation: getDetectionTuning().allocationStrategy, allocation: getDetectionTuning().allocationStrategy,
fadePct: parseInt(this._detectionFadeSlider?.value || '7', 10), fadePct: this._detectionFadePct,
outsideOpacityPct: parseInt( outsideOpacityPct: this._detectionOutsidePct,
this._detectionOpacitySlider?.value || '0',
10,
),
}, },
scope: { scope: {
enabled: isScopeMaskEnabled(), enabled: isScopeMaskEnabled(),
@@ -1328,36 +1290,23 @@ export class VisualSettings {
} }
const detectionState = state.detection || {}; const detectionState = state.detection || {};
if ( if (typeof detectionState.density === 'number')
typeof detectionState.density === 'number' && this._setDetectionDensity(detectionState.density);
this._detectionDensitySlider
) {
const pct = canonicalizeDensity(detectionState.density);
this._detectionDensitySlider.value = String(pct);
if (this._detectionDensityValue)
this._detectionDensityValue.textContent = `${pct}%`;
this._applyDetectionDensityFromUi();
}
if (detectionState.allocation) { if (detectionState.allocation) {
this._setDetectionAllocation(detectionState.allocation, { this._setDetectionAllocation(detectionState.allocation, {
syncShare: false, syncShare: false,
}); });
} }
if ( this._setDetectionFade({
typeof detectionState.fadePct === 'number' && fadePct:
this._detectionFadeSlider typeof detectionState.fadePct === 'number'
) { ? detectionState.fadePct
this._detectionFadeSlider.value = String(detectionState.fadePct); : undefined,
} outsideOpacityPct:
if ( typeof detectionState.outsideOpacityPct === 'number'
typeof detectionState.outsideOpacityPct === 'number' && ? detectionState.outsideOpacityPct
this._detectionOpacitySlider : undefined,
) { });
this._detectionOpacitySlider.value = String(
detectionState.outsideOpacityPct,
);
}
this._applyDetectionFadeFromUi();
if (detectionState.mode) { if (detectionState.mode) {
this._setDetectionMode(detectionState.mode); this._setDetectionMode(detectionState.mode);
} }
@@ -1452,15 +1401,8 @@ export class VisualSettings {
if (preset.detectionMode) { if (preset.detectionMode) {
this._setDetectionMode(preset.detectionMode); this._setDetectionMode(preset.detectionMode);
} }
if ( if (typeof preset.detectionDensity === 'number')
typeof preset.detectionDensity === 'number' && this._setDetectionDensity(preset.detectionDensity);
this._detectionDensitySlider
) {
const density = canonicalizeDensity(preset.detectionDensity);
this._detectionDensitySlider.value = String(density);
this._detectionDensityValue.textContent = `${density}%`;
this._applyDetectionDensityFromUi();
}
if (preset.detectionAllocation) { if (preset.detectionAllocation) {
this._setDetectionAllocation(preset.detectionAllocation, { this._setDetectionAllocation(preset.detectionAllocation, {
syncShare: false, syncShare: false,
@@ -1653,49 +1595,8 @@ export class VisualSettings {
} }
_updateDetectionButton(modeLabel) { _updateDetectionButton(modeLabel) {
const btn = this._detectionBtn; this._detectionModeLabel = modeLabel;
const enabled = modeLabel !== 'OFF'; this._publishDisplay();
btn.setAttribute('aria-pressed', String(enabled));
btn.setAttribute(
'aria-label',
enabled
? t('Detection overlay: {mode}', {
mode: String(modeLabel).toLowerCase(),
})
: t('Detection overlay: off'),
);
btn.classList.remove('active', 'god', 'panoptic');
if (modeLabel === 'SPARSE') {
btn.querySelector('.pp-label').textContent = t('SPARSE');
btn.classList.add('active');
} else if (modeLabel === 'BALANCED') {
btn.querySelector('.pp-label').textContent = t('BALANCED');
btn.classList.add('active');
} else if (modeLabel === 'DENSE') {
btn.querySelector('.pp-label').textContent = t('DENSE');
btn.classList.add('active', 'panoptic');
} else {
btn.querySelector('.pp-label').textContent = t('DETECT');
}
if (this._detectionSliderRow) {
this._detectionSliderRow.classList.toggle('visible', modeLabel !== 'OFF');
}
if (this._detectionAllocationRow) {
this._detectionAllocationRow.classList.toggle(
'visible',
modeLabel !== 'OFF',
);
}
if (this._detectionFadeRow) {
this._detectionFadeRow.classList.toggle('visible', modeLabel !== 'OFF');
}
if (this._detectionOpacityRow) {
this._detectionOpacityRow.classList.toggle(
'visible',
modeLabel !== 'OFF',
);
}
this._layoutRightPanels(); this._layoutRightPanels();
} }
stop() { stop() {