mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +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
|
||||
through the refresh their own click triggers, and clicks re-read the live
|
||||
descriptor before acting), Display (in progress: `DisplayPanel` renders
|
||||
the HUD, Sonar, detection, bloom and sharpen from the `gev-display` store, which VisualSettings
|
||||
publishes to; the other groups follow one owner at a time), Context and
|
||||
Radio, the command dock,
|
||||
the HUD, Sonar, Scope, Celestial, detection, bloom and sharpen from the
|
||||
`gev-display` store, which VisualSettings publishes to; the other groups
|
||||
follow one owner at a time), Context and Radio, the command dock,
|
||||
the phone shell, then Cockpit. Layers that write into the right rail
|
||||
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'],
|
||||
['setBloomIntensity:', 'toggleSharpen:', 'this._setBloomIntensity(', 'setBloomIntensity control'],
|
||||
['toggleSharpen:', 'setScopeEnabled:', 'this._setSharpenEnabled(', 'toggleSharpen control'],
|
||||
['setScopeEnabled:', 'setScopeFeather:', 'setScopeMaskEnabled(', 'setScopeEnabled control'],
|
||||
['setScopeFeather:', 'setSharpenIntensity:', 'setScopeMaskFeather(', 'setScopeFeather control'],
|
||||
['setScopeEnabled:', 'setScopeFeather:', 'this._setScope(', 'setScopeEnabled control'],
|
||||
['setScopeFeather:', 'setSharpenIntensity:', 'this._setScope(', 'setScopeFeather control'],
|
||||
['setSharpenIntensity:', 'setHudLayout:', 'this._setSharpenIntensity(', 'setSharpenIntensity control'],
|
||||
['setHudLayout:', 'toggleCleanView:', 'this._setHudVariant(', 'setHudLayout control'],
|
||||
['setDensity:', 'setAllocation:', 'this._setDetectionDensity(', 'setDensity control'],
|
||||
|
||||
@@ -208,12 +208,8 @@ export class StyleManager extends ShellFacade {
|
||||
setScopeMaskFeather: services.setScopeMaskFeather,
|
||||
},
|
||||
elements: {
|
||||
_celestialBtn: this._celestialBtn,
|
||||
_cockpitDisplayToggleBtn: this._cockpitDisplayToggleBtn,
|
||||
_ppToggles: this._ppToggles,
|
||||
_scopeBtn: this._scopeBtn,
|
||||
_scopeFeatherSlider: this._scopeFeatherSlider,
|
||||
_scopeFeatherValue: this._scopeFeatherValue,
|
||||
_sliderContainer: this._sliderContainer,
|
||||
_sliderPanel: this._sliderPanel,
|
||||
_styleIndicator: this._styleIndicator,
|
||||
@@ -491,9 +487,7 @@ export class StyleManager extends ShellFacade {
|
||||
viewer,
|
||||
services: {
|
||||
cycleDetectionMode: services.cycleDetectionMode,
|
||||
setScopeMaskEnabled: services.setScopeMaskEnabled,
|
||||
isScopeMaskEnabled: services.isScopeMaskEnabled,
|
||||
setScopeMaskFeather: services.setScopeMaskFeather,
|
||||
},
|
||||
elements: {
|
||||
_locationSearch: this._locationSearch,
|
||||
@@ -520,7 +514,6 @@ export class StyleManager extends ShellFacade {
|
||||
_detectionAllocationBtns: this._detectionAllocationBtns,
|
||||
_celestialBtn: this._celestialBtn,
|
||||
_models3dBtn: this._models3dBtn,
|
||||
_scopeFeatherValue: this._scopeFeatherValue,
|
||||
},
|
||||
operations: {
|
||||
setStyle: (...args) => this.setStyle(...args),
|
||||
@@ -540,6 +533,7 @@ export class StyleManager extends ShellFacade {
|
||||
_setDetectionAllocation: (...args) =>
|
||||
this._setDetectionAllocation(...args),
|
||||
_setDetectionFade: (...args) => this._setDetectionFade(...args),
|
||||
_setScope: (...args) => this._visualSettings._setScope(...args),
|
||||
setCelestialRingEnabled: (...args) =>
|
||||
this.setCelestialRingEnabled(...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() };
|
||||
t.after(() => { setScopeMaskEnabled(previous.enabled); setScopeMaskFeather(previous.feather); });
|
||||
Object.assign(owner.services, { setScopeMaskEnabled, setScopeMaskFeather });
|
||||
owner._scopeBtn = { checked: true };
|
||||
owner._scopeFeatherSlider = { value: '11', disabled: false };
|
||||
owner._scopeFeatherValue = {};
|
||||
let shown = null;
|
||||
owner.store = { publish: (view) => (shown = view.scope) };
|
||||
await owner.restoreShareState({ scopeEnabled: false, scopeFeatherPct: 200 });
|
||||
assert.equal(owner._scopeBtn.checked, false);
|
||||
assert.equal(owner._scopeFeatherSlider.disabled, true);
|
||||
assert.equal(owner._scopeFeatherSlider.value, '200');
|
||||
assert.deepEqual(shown, { enabled: false, featherPct: 200 });
|
||||
for (const layout of ['minimal', 'cyber', 'tactical']) {
|
||||
owner._setHudVariant(layout, { applyVisualDefaults: true });
|
||||
assert.equal(isScopeMaskEnabled(), false, 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 } });
|
||||
assert.equal(isScopeMaskEnabled(), true);
|
||||
assert.equal(getScopeMaskFeather(), 1.75);
|
||||
assert.equal(owner._scopeBtn.checked, true);
|
||||
assert.equal(owner._scopeFeatherSlider.disabled, false);
|
||||
assert.equal(owner._scopeFeatherValue.textContent, '175%');
|
||||
assert.deepEqual(shown, { enabled: true, featherPct: 175 });
|
||||
await owner.applyVisualState({ scope: { featherPct: 500 } });
|
||||
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) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
|
||||
import { createFrameRateMonitor } from './frameRateMonitor.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. */
|
||||
export class DisplayBindings {
|
||||
@@ -45,12 +44,7 @@ export class DisplayBindings {
|
||||
return this.readState()._models3dModeBtns;
|
||||
}
|
||||
_initUI() {
|
||||
const {
|
||||
cycleDetectionMode,
|
||||
setScopeMaskEnabled,
|
||||
isScopeMaskEnabled,
|
||||
setScopeMaskFeather,
|
||||
} = this.services;
|
||||
const { cycleDetectionMode, isScopeMaskEnabled } = this.services;
|
||||
this._applicationShortcuts?.destroy();
|
||||
this._frameRateMonitor?.destroy();
|
||||
this._frameRateMonitor = createFrameRateMonitor({
|
||||
@@ -89,11 +83,7 @@ export class DisplayBindings {
|
||||
},
|
||||
});
|
||||
|
||||
syncScopeControls(
|
||||
this._scopeBtn,
|
||||
this._scopeFeatherSlider,
|
||||
isScopeMaskEnabled(),
|
||||
);
|
||||
this._setScope({ enabled: isScopeMaskEnabled() }, { apply: false });
|
||||
this._displayControls?.destroy();
|
||||
this._displayControls = bindDisplayControls({
|
||||
elements: {
|
||||
@@ -139,16 +129,12 @@ export class DisplayBindings {
|
||||
},
|
||||
setScopeEnabled: (enabled) => {
|
||||
this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||
setScopeMaskEnabled(enabled);
|
||||
syncScopeControls(this._scopeBtn, this._scopeFeatherSlider, enabled);
|
||||
this._setScope({ enabled });
|
||||
this._syncShareState();
|
||||
},
|
||||
setScopeFeather: (value) => {
|
||||
this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||
const pct = clampScopeFeatherPct(value);
|
||||
if (this._scopeFeatherValue)
|
||||
this._scopeFeatherValue.textContent = `${pct}%`;
|
||||
setScopeMaskFeather(pct / 100);
|
||||
this._setScope({ featherPct: value });
|
||||
this._syncShareState();
|
||||
},
|
||||
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').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'),
|
||||
_scopeBtn: document.getElementById('scope-toggle'),
|
||||
_scopeFeatherSlider: document.getElementById('scope-feather-slider'),
|
||||
_scopeFeatherValue: document.getElementById('scope-feather-value'),
|
||||
_mapStackChips: document.getElementById('map-stack-chips'),
|
||||
_mapStackStatus: document.getElementById('map-stack-status'),
|
||||
_cleanViewBtn: document.getElementById('clean-view-toggle'),
|
||||
|
||||
@@ -5,9 +5,9 @@ import { t } from '../../../i18n/index.js';
|
||||
import { vRangeValue } from '../../rail/rangeValue.js';
|
||||
import { useDisplayStore } from '../../stores/display.js';
|
||||
|
||||
// The Display panel. VisualSettings presents the HUD, Sonar, detection,
|
||||
// bloom and sharpen and publishes them; until it does, the panel shows its
|
||||
// initial markup. The other groups keep their owners' element bindings.
|
||||
// The Display panel. VisualSettings presents the HUD, Sonar, Scope,
|
||||
// Celestial, detection, bloom and sharpen and publishes them; until it does,
|
||||
// the panel shows its initial markup. The other groups keep their owners' element bindings.
|
||||
const { view } = storeToRefs(useDisplayStore());
|
||||
const pct = (value) => `${value}%`;
|
||||
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 detectButton = computed(() => detection.value?.button ?? null);
|
||||
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) => ({
|
||||
class: { active: 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>
|
||||
<span class="pp-icon material-symbols-outlined" aria-hidden="true">adjust</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>
|
||||
<div class="pp-slider-row visible" id="scope-slider-row">
|
||||
<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 />
|
||||
<span class="pp-slider-value" id="scope-feather-value">100%</span>
|
||||
<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">{{ pct(scopeFeather) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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-label" data-i18n>Celestial</span>
|
||||
</button>
|
||||
|
||||
+34
-43
@@ -1,5 +1,4 @@
|
||||
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
|
||||
import { syncScopeControls } from './displayControls.js';
|
||||
import { STYLE_STATUS_LABELS } from './visualPresets.js';
|
||||
import { RETIRED_STYLES } from '../visualStylePolicy.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. */
|
||||
export class VisualSettings {
|
||||
async restoreShareState(state) {
|
||||
const {
|
||||
setScopeMaskEnabled,
|
||||
setScopeMaskFeather,
|
||||
setScopeTerminusOverride,
|
||||
clampScopeTerminusPct,
|
||||
} = this.services;
|
||||
const { setScopeTerminusOverride, clampScopeTerminusPct } = this.services;
|
||||
const {
|
||||
style,
|
||||
bloom,
|
||||
@@ -139,17 +133,7 @@ export class VisualSettings {
|
||||
focus: false,
|
||||
});
|
||||
}
|
||||
if (typeof scopeEnabled === 'boolean') {
|
||||
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);
|
||||
}
|
||||
this._setScope({ enabled: scopeEnabled, featherPct: scopeFeatherPct });
|
||||
// null restores the altitude-adaptive ramp; a number pins the terminus
|
||||
// (clamped to the supported 94..100 band, same as the `sce` hash key).
|
||||
if (scopeTerminusPct === null) setScopeTerminusOverride(null);
|
||||
@@ -218,6 +202,10 @@ export class VisualSettings {
|
||||
this._hudVisibleShown = null;
|
||||
this._hudVariantShown = 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._preCyberStyle = null;
|
||||
this._cyberStyleUserSelected = false;
|
||||
@@ -483,9 +471,31 @@ export class VisualSettings {
|
||||
detection: this._presentDetection(),
|
||||
hud: { visible: this._hudVisibleShown, variant: this._hudVariantShown },
|
||||
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. */
|
||||
_presentDetection() {
|
||||
const mode = this._detectionModeLabel;
|
||||
@@ -1150,15 +1160,8 @@ export class VisualSettings {
|
||||
if (syncShare) this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||
const nextEnabled = styleSupported && enabled;
|
||||
this.celestialRing?.setEnabled(nextEnabled);
|
||||
this._celestialBtn?.classList.toggle('active', nextEnabled);
|
||||
this._celestialBtn?.setAttribute('aria-pressed', String(nextEnabled));
|
||||
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');
|
||||
}
|
||||
this._celestialShown = { active: nextEnabled, supported: styleSupported };
|
||||
this._publishDisplay();
|
||||
let cameraFocused = false;
|
||||
if (nextEnabled && focus) {
|
||||
cameraFocused = !!this.celestialRing?.focusFullGlobe();
|
||||
@@ -1220,7 +1223,6 @@ export class VisualSettings {
|
||||
}
|
||||
|
||||
async applyVisualState(state = {}, { isCurrent = null } = {}) {
|
||||
const { setScopeMaskEnabled, setScopeMaskFeather } = this.services;
|
||||
const superseded = () => typeof isCurrent === 'function' && !isCurrent();
|
||||
if (superseded()) return false;
|
||||
|
||||
@@ -1258,21 +1260,10 @@ export class VisualSettings {
|
||||
}
|
||||
|
||||
const scopeState = state.scope || {};
|
||||
if (typeof scopeState.enabled === 'boolean') {
|
||||
setScopeMaskEnabled(scopeState.enabled);
|
||||
syncScopeControls(
|
||||
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);
|
||||
}
|
||||
this._setScope({
|
||||
enabled: scopeState.enabled,
|
||||
featherPct: scopeState.featherPct,
|
||||
});
|
||||
|
||||
const detectionState = state.detection || {};
|
||||
if (typeof detectionState.density === 'number')
|
||||
|
||||
Reference in new issue
Block a user