mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
feat(display): render the style parameters from the display store
VisualSettings publishes the active style's parameter rows to the display store, and the Display panel renders them with the shared row component; a moved slider goes back through the store's setStyleParameter action. The readout snaps a value to its step as the browser does, half-steps up and an off-grid maximum down. A preset that resets the shown style's uniforms republishes its rows, input for another style's parameters is ignored, and a stopped owner takes its rows with it unless a newer owner has published since. createStyleParameters is deprecated and builds its rows the same way.
This commit is contained in:
1 parent
3a750c9021
commit
c3c69a91cd
14 files changed
+316
-80
No files matched your search
@@ -224,13 +224,14 @@ test('parameterized Display presets keep one stable scroll owner', () => {
|
||||
assert.match(css, /#pp-toggles:not\(\.collapsed\) > \.pp-panel-body > #param-slider-panel\.active\s*\{[\s\S]*?flex:\s*0 0 auto;[\s\S]*?max-height:\s*none;[\s\S]*?overflow-y:\s*visible;/);
|
||||
assert.match(ui, /readDisplayScrollTop: \(\) =>\s*this\._displayPortalScrollRestoreOwner === 'standard'[\s\S]*?this\._standardDisplayScrollTop[\s\S]*?displayPanelScroller\(this\._ppToggles\)\?\.scrollTop \|\| 0/);
|
||||
assert.match(rightRail, /scroller\.scrollTop = Math\.min\(displayScrollTop, maxScrollTop\);/);
|
||||
// The panel's parameters change, and the rail lays out again.
|
||||
assert.match(
|
||||
ui,
|
||||
/this\._sliderPanel\.classList\.remove\('active'\);\s*this\._scheduleRightPanelLayout\(\);/,
|
||||
/this\._publishParameters\(null\);\s*this\._scheduleRightPanelLayout\(\);/,
|
||||
);
|
||||
assert.match(
|
||||
ui,
|
||||
/this\._sliderPanel\.classList\.add\('active'\);\s*this\._scheduleRightPanelLayout\(\);/,
|
||||
/this\._publishParameters\(\{[\s\S]*?\}\);\s*this\._scheduleRightPanelLayout\(\);/,
|
||||
);
|
||||
assert.match(
|
||||
css,
|
||||
|
||||
@@ -641,8 +641,8 @@ test('every explicit visual control facade claims restore authority before mutat
|
||||
|
||||
const style = sourceBlock(' setStyle(styleName, {', ' _startTransition(styleName, fromValue, toValue) {');
|
||||
assertClaimsBefore(style, 'this.activeStyle = styleName', 'setStyle');
|
||||
const sliders = sourceBlock(' _updateSliderPanel(styleName, { reveal = false } = {}) {', ' _revealStyleParameters() {');
|
||||
assertClaimsBefore(sliders, 'this.stages[styleName].uniforms[uName] = val', 'style parameter slider');
|
||||
const sliders = sourceBlock(' setStyleParameter(name, value) {', ' _revealStyleParameters() {');
|
||||
assertClaimsBefore(sliders, 'this.stages[parameters.style].uniforms[name] = value', 'style parameter slider');
|
||||
});
|
||||
|
||||
test('public visual facades reject the complete invalid request before authority or mutation', () => {
|
||||
@@ -794,7 +794,7 @@ test('visual input listeners are revoked before asynchronous UI teardown', () =>
|
||||
assert.match(LocationNavigation.prototype.destroy.toString(), /this\._locationLookup\?\.destroy\(\)/);
|
||||
assert.match(DisplayBindings.prototype.destroy.toString(), /this\._detachDisplayControls\?\.\(\)/);
|
||||
assert.match(DisplayBindings.prototype.destroy.toString(), /this\._applicationShortcuts\?\.destroy\(\)/);
|
||||
assert.match(VisualSettings.prototype.stop.toString(), /this\._styleParameters\?\.destroy\(\)/);
|
||||
assert.match(VisualSettings.prototype.stop.toString(), /this\.store\.detach\(this\._parametersToken\)/);
|
||||
assert.match(VisualSettings.prototype.stop.toString(), /this\._visualEffects\.stop\(\)/);
|
||||
});
|
||||
|
||||
|
||||
@@ -225,7 +225,6 @@ export class StyleManager extends ShellFacade {
|
||||
},
|
||||
elements: {
|
||||
_ppToggles: this._ppToggles,
|
||||
_sliderContainer: this._sliderContainer,
|
||||
_sliderPanel: this._sliderPanel,
|
||||
},
|
||||
operations: {
|
||||
|
||||
@@ -460,3 +460,118 @@ test('the imported files render what the file import publishes and keep focus',
|
||||
assert.equal(list.className, 'geo-import-list');
|
||||
assert.equal(document.activeElement === $('geo-import-button'), true);
|
||||
});
|
||||
|
||||
test('the parameters panel shows the active style’s rows and hands each change to its owner', async (t) => {
|
||||
const { store, $ } = fixture(t);
|
||||
const panel = $('param-slider-panel');
|
||||
assert.equal(panel.classList.contains('active'), false);
|
||||
assert.equal($('param-sliders').children.length, 0);
|
||||
const changes = [];
|
||||
store.connect({
|
||||
setStyleParameter: (name, value) => changes.push([name, value]),
|
||||
});
|
||||
store.publishParameters({
|
||||
style: 'noir',
|
||||
rows: [
|
||||
{
|
||||
name: 'amount',
|
||||
label: 'Amount',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: '0.01',
|
||||
digits: 2,
|
||||
value: 0.25,
|
||||
text: '0.25',
|
||||
},
|
||||
],
|
||||
});
|
||||
await nextTick();
|
||||
assert.equal(panel.classList.contains('active'), true);
|
||||
const row = $('param-sliders').querySelector('.param-slider-row');
|
||||
assert.equal(row.querySelector('.param-label').textContent, 'Amount');
|
||||
assert.equal(row.querySelector('.param-value').textContent, '0.25');
|
||||
const slider = row.querySelector('input.param-slider');
|
||||
assert.equal(slider.getAttribute('aria-label'), 'Amount');
|
||||
assert.equal(slider.step, '0.01');
|
||||
slider.value = '0.5';
|
||||
slider.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
assert.deepEqual(changes, [['amount', 0.5]]);
|
||||
store.publishParameters(null);
|
||||
await nextTick();
|
||||
assert.equal(panel.classList.contains('active'), false);
|
||||
assert.equal($('param-sliders').children.length, 0);
|
||||
});
|
||||
|
||||
test('VisualSettings publishes a style’s parameters and writes a moved slider’s uniform', async (t) => {
|
||||
const { store, $ } = fixture(t);
|
||||
const owner = Object.create(VisualSettings.prototype);
|
||||
const renders = [];
|
||||
let shared = 0;
|
||||
Object.assign(owner, {
|
||||
store,
|
||||
_visualEffects: {
|
||||
stages: {
|
||||
noir: {
|
||||
uniforms: { contrastAmt: 1.2, grainAmt: 0.5, vignetteAmt: 0.5 },
|
||||
},
|
||||
},
|
||||
stop() {},
|
||||
},
|
||||
services: { governorRequestRender: (reason) => renders.push(reason) },
|
||||
_parameters: null,
|
||||
readShareLinks: () => null,
|
||||
_scheduleRightPanelLayout() {},
|
||||
_syncShareState: () => (shared += 1),
|
||||
});
|
||||
owner._parametersToken = store.connect({
|
||||
setStyleParameter: (name, value) => owner.setStyleParameter(name, value),
|
||||
});
|
||||
owner._updateSliderPanel('noir');
|
||||
await nextTick();
|
||||
const labels = () =>
|
||||
[...$('param-sliders').querySelectorAll('.param-label')].map(
|
||||
(label) => label.textContent,
|
||||
);
|
||||
assert.deepEqual(labels(), ['Contrast', 'Grain', 'Vignette']);
|
||||
const grain = $('param-sliders').querySelectorAll('input.param-slider')[1];
|
||||
grain.value = '0.8';
|
||||
grain.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
await nextTick();
|
||||
assert.equal(owner.stages.noir.uniforms.grainAmt, 0.8);
|
||||
assert.equal(
|
||||
$('param-sliders').querySelectorAll('.param-value')[1].textContent,
|
||||
'0.80',
|
||||
);
|
||||
assert.deepEqual(renders, ['style-param-slider']);
|
||||
assert.equal(shared, 1);
|
||||
// A name that isn't one of the shown style's parameters writes nothing.
|
||||
owner.setStyleParameter('pixelation', 4);
|
||||
assert.equal(owner.stages.noir.uniforms.pixelation, undefined);
|
||||
assert.equal(shared, 1);
|
||||
owner._updateSliderPanel('normal');
|
||||
await nextTick();
|
||||
assert.deepEqual(labels(), []);
|
||||
assert.equal($('param-slider-panel').classList.contains('active'), false);
|
||||
// A preset that resets the shown style's uniforms republishes its rows.
|
||||
owner._updateSliderPanel('noir');
|
||||
owner.stages.noir.uniforms.grainAmt = 0.3;
|
||||
owner._refreshParameters();
|
||||
await nextTick();
|
||||
assert.equal(
|
||||
$('param-sliders').querySelectorAll('.param-value')[1].textContent,
|
||||
'0.30',
|
||||
);
|
||||
owner._lifetime = { destroy() {} };
|
||||
owner.stop();
|
||||
await nextTick();
|
||||
assert.equal(store.parameters, null, 'the sliders go with their owner');
|
||||
assert.equal($('param-slider-panel').classList.contains('active'), false);
|
||||
assert.equal(store.act('setStyleParameter', 'grainAmt', 0.2), undefined);
|
||||
assert.equal(owner.stages.noir.uniforms.grainAmt, 0.3, 'a stopped owner no longer answers');
|
||||
// An older owner's stop leaves a newer owner's rows.
|
||||
const newer = { style: 'noir', rows: [] };
|
||||
owner._parameters = { style: 'noir', rows: [] };
|
||||
store.publishParameters(newer);
|
||||
owner.stop();
|
||||
assert.equal(store.parameters, newer);
|
||||
});
|
||||
@@ -3,7 +3,6 @@ export function readShellElements(document = globalThis.document) {
|
||||
return {
|
||||
_rightPanelStack: document.getElementById('right-context-rail'),
|
||||
_sliderPanel: document.getElementById('param-slider-panel'),
|
||||
_sliderContainer: document.getElementById('param-sliders'),
|
||||
_ppToggles: document.getElementById('pp-toggles'),
|
||||
_bloomBtn: document.getElementById('bloom-toggle'),
|
||||
_bloomSlider: document.getElementById('bloom-intensity-slider'),
|
||||
|
||||
@@ -413,14 +413,6 @@ export class ShellFacade {
|
||||
this._visualSettings._detectionAllocationPreference = value;
|
||||
}
|
||||
|
||||
get _styleParameters() {
|
||||
return this._visualSettings._styleParameters;
|
||||
}
|
||||
|
||||
set _styleParameters(value) {
|
||||
this._visualSettings._styleParameters = value;
|
||||
}
|
||||
|
||||
get _irBoostActive() {
|
||||
return this._visualSettings._irBoostActive;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,17 @@ export const useDisplayStore = defineStore('gev-display', () => {
|
||||
// buttons, the draw tool and the file import their own.
|
||||
const { connect, detach, act } = createOwners();
|
||||
|
||||
/**
|
||||
* The active style's parameter rows, `{style, rows}`; null while the style
|
||||
* has none. Each row's change calls act('setStyleParameter', name, value).
|
||||
*/
|
||||
const parameters = shallowRef(null);
|
||||
|
||||
/** @param {?{style: string, rows: object[]}} next */
|
||||
function publishParameters(next) {
|
||||
parameters.value = next && markRaw(next);
|
||||
}
|
||||
|
||||
/** The phone shell's tab bar; null until the shell first presents it. */
|
||||
const phoneShell = shallowRef(null);
|
||||
|
||||
@@ -91,6 +102,8 @@ export const useDisplayStore = defineStore('gev-display', () => {
|
||||
publishGeoImport,
|
||||
style,
|
||||
publishStyle,
|
||||
parameters,
|
||||
publishParameters,
|
||||
phoneShell,
|
||||
publishPhoneShell,
|
||||
mapSource,
|
||||
|
||||
+69
-25
@@ -13,9 +13,9 @@ const formatParam = (value, digits) =>
|
||||
/**
|
||||
* The generated rows: a label, the kit's slider and its value in the UI
|
||||
* language. A render function, so the portable input package needs no
|
||||
* single-file-component compiler.
|
||||
* single-file-component compiler. The Display panel renders it too.
|
||||
*/
|
||||
const StyleParameterRows = {
|
||||
export const StyleParameterRows = {
|
||||
props: {
|
||||
rows: { type: Array, required: true },
|
||||
onInput: { type: Function, required: true },
|
||||
@@ -39,6 +39,68 @@ const StyleParameterRows = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A value as a range input holds it: clamped to its bounds and snapped to
|
||||
* its step from the minimum, as the browser snaps the slider.
|
||||
* @param {number} value
|
||||
* @param {{min: number, max: number, step: string}} row
|
||||
*/
|
||||
export function snapToStep(value, { min, max, step }) {
|
||||
const size = Number(step);
|
||||
const decimals = (String(step).split('.')[1] || '').length;
|
||||
// Rounded to nine places first, so a value halfway between two steps
|
||||
// (1.15 from 1 by 0.1) rounds up as the browser does, not down through
|
||||
// floating point error.
|
||||
const count = (span) => Number((span / size).toFixed(9));
|
||||
// A maximum off the step grid holds the last step below it, as the browser
|
||||
// does.
|
||||
const steps = Math.min(
|
||||
Math.floor(count(max - min)),
|
||||
Math.max(0, Math.round(count(Number(value) - min))),
|
||||
);
|
||||
return Number((min + steps * size).toFixed(decimals));
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameter rows of a style's uniforms: each one's label, bounds, step,
|
||||
* value and the value's text in the UI language.
|
||||
* @param {object} uniforms The style's uniform metadata.
|
||||
* @param {(name: string) => number} readValue A uniform's current value.
|
||||
*/
|
||||
export function styleParameterRows(uniforms, readValue) {
|
||||
return Object.entries(uniforms).map(([name, metadata]) =>
|
||||
withValue(
|
||||
{
|
||||
name,
|
||||
label: t(metadata.label),
|
||||
min: metadata.min,
|
||||
max: metadata.max,
|
||||
step: metadata.max <= 1 ? '0.01' : '0.1',
|
||||
digits: metadata.max <= 1 ? 2 : 1,
|
||||
},
|
||||
readValue(name),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The rows with one parameter's value changed.
|
||||
* @param {object[]} rows From {@link styleParameterRows}.
|
||||
* @param {string} name
|
||||
* @param {number} value
|
||||
*/
|
||||
export function withParameterValue(rows, name, value) {
|
||||
return rows.map((row) => (row.name === name ? withValue(row, value) : row));
|
||||
}
|
||||
|
||||
function withValue(row, value) {
|
||||
return {
|
||||
...row,
|
||||
value,
|
||||
text: formatParam(snapToStep(value, row), row.digits),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Own generated parameter rows and their input handling. Renders
|
||||
* StyleParameterRows into `container` and patches it synchronously.
|
||||
@@ -47,6 +109,9 @@ const StyleParameterRows = {
|
||||
* @param {object} options
|
||||
* @param {HTMLElement} options.container Container owned by this component.
|
||||
* @returns {{render: Function, clear: Function, destroy: Function}}
|
||||
* @deprecated The Display panel renders the rows VisualSettings publishes to
|
||||
* the display store (styleParameterRows); this stays exported from
|
||||
* `./ui/input` for hosts that imported it.
|
||||
*/
|
||||
export function createStyleParameters({ container }) {
|
||||
let rows = [];
|
||||
@@ -69,36 +134,15 @@ export function createStyleParameters({ container }) {
|
||||
if (destroyed) return;
|
||||
clear();
|
||||
const current = generation;
|
||||
rows = Object.entries(uniforms).map(([name, metadata]) => ({
|
||||
name,
|
||||
label: t(metadata.label),
|
||||
min: metadata.min,
|
||||
max: metadata.max,
|
||||
step: metadata.max <= 1 ? '0.01' : '0.1',
|
||||
digits: metadata.max <= 1 ? 2 : 1,
|
||||
value: readValue(name),
|
||||
text: '',
|
||||
}));
|
||||
rows = styleParameterRows(uniforms, readValue);
|
||||
onInput = (name, value) => {
|
||||
if (destroyed || current !== generation) return;
|
||||
writeValue(name, value);
|
||||
rows = rows.map((row) =>
|
||||
row.name === name
|
||||
? { ...row, value, text: formatParam(value, row.digits) }
|
||||
: row,
|
||||
);
|
||||
rows = withParameterValue(rows, name, value);
|
||||
paint();
|
||||
onChange();
|
||||
};
|
||||
paint();
|
||||
// The readout shows the value the slider holds once the browser has
|
||||
// clamped and snapped it to its step.
|
||||
const sliders = container.querySelectorAll('input.param-slider');
|
||||
rows = rows.map((row, index) => ({
|
||||
...row,
|
||||
text: formatParam(parseFloat(sliders[index].value), row.digits),
|
||||
}));
|
||||
paint();
|
||||
},
|
||||
clear,
|
||||
destroy() {
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
import '../testSupport/registerDom.mjs';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createStyleParameters } from './styleParameters.js';
|
||||
import {
|
||||
createStyleParameters,
|
||||
snapToStep,
|
||||
styleParameterRows,
|
||||
withParameterValue,
|
||||
} from './styleParameters.js';
|
||||
|
||||
function parameters(t, values = { amount: 0.25, scale: 2 }) {
|
||||
document.body.replaceChildren();
|
||||
@@ -120,3 +125,44 @@ test('destroy is final and cannot clear a subsequent owner’s DOM', (t) => {
|
||||
assert.equal(f.container.firstElementChild === nextOwner, true);
|
||||
assert.deepEqual(f.order, []);
|
||||
});
|
||||
|
||||
test('a value snaps to its step from the minimum and stays in bounds', () => {
|
||||
const row = { min: 0, max: 1, step: '0.01' };
|
||||
assert.equal(snapToStep(0.256, row), 0.26);
|
||||
assert.equal(snapToStep(1.4, row), 1);
|
||||
assert.equal(snapToStep(-2, row), 0);
|
||||
assert.equal(snapToStep(2.04, { min: 0.5, max: 4, step: '0.1' }), 2);
|
||||
// Halfway between two steps rounds up, as the browser snaps it.
|
||||
assert.equal(snapToStep(1.15, { min: 1, max: 2, step: '0.1' }), 1.2);
|
||||
assert.equal(snapToStep(0.125, { min: 0, max: 1, step: '0.01' }), 0.13);
|
||||
// A maximum off the grid snaps down to the last step within it.
|
||||
assert.equal(snapToStep(1.06, { min: 0, max: 1.06, step: '0.1' }), 1);
|
||||
assert.equal(snapToStep(9, { min: 0, max: 1.06, step: '0.1' }), 1);
|
||||
});
|
||||
|
||||
test('rows carry each uniform, its step and its snapped value text', () => {
|
||||
const rows = styleParameterRows(
|
||||
{
|
||||
amount: { label: 'Amount', min: 0, max: 1 },
|
||||
scale: { label: 'Scale', min: 0.5, max: 4 },
|
||||
},
|
||||
(name) => ({ amount: 0.256, scale: 2.04 })[name],
|
||||
);
|
||||
assert.deepEqual(
|
||||
rows.map(({ name, step, digits, value, text }) => [
|
||||
name,
|
||||
step,
|
||||
digits,
|
||||
value,
|
||||
text,
|
||||
]),
|
||||
[
|
||||
['amount', '0.01', 2, 0.256, '0.26'],
|
||||
['scale', '0.1', 1, 2.04, '2.0'],
|
||||
],
|
||||
);
|
||||
const next = withParameterValue(rows, 'scale', 3.33);
|
||||
assert.equal(next[1].value, 3.33);
|
||||
assert.equal(next[1].text, '3.3');
|
||||
assert.equal(next[0], rows[0], 'the other rows are kept');
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { t } from '../../../i18n/index.js';
|
||||
import { vPanelState } from '../../rail/panelState.js';
|
||||
import { vClassToggle } from '../../rail/classToggle.js';
|
||||
import PanelCollapseButton from '../../rail/PanelCollapseButton.vue';
|
||||
import { vRangeValue } from '../../rail/rangeValue.js';
|
||||
import UiSegmented from '../../kit/UiSegmented.vue';
|
||||
@@ -11,13 +12,14 @@ import GeoImport from './GeoImport.vue';
|
||||
import { useDisplayStore } from '../../stores/display.js';
|
||||
import UiButton from '../../kit/UiButton.vue';
|
||||
import UiSelect from '../../kit/UiSelect.js';
|
||||
import { StyleParameterRows } from '../../styleParameters.js';
|
||||
|
||||
// The Display panel. VisualSettings presents the HUD, Sonar, Scope,
|
||||
// Celestial, detection, bloom and sharpen, AircraftDisplay the 3D group and
|
||||
// the draw tool its controls, and each publishes them; until they do, the
|
||||
// panel shows its initial markup. GeoImport renders the file import.
|
||||
const display = useDisplayStore();
|
||||
const { view, models3d, draw } = storeToRefs(display);
|
||||
const { view, models3d, draw, parameters } = storeToRefs(display);
|
||||
// A control's change goes to its owner (DisplayBindings or the draw tool).
|
||||
const act = (name, ...args) => display.act(name, ...args);
|
||||
const pct = (value) => `${value}%`;
|
||||
@@ -131,14 +133,14 @@ const allocationOptions = [
|
||||
<span class="pp-slider-value" id="detection-opacity-value">{{ pct(detection?.outsideOpacityPct ?? 1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- The one shared preset-parameter surface is inserted here at runtime. -->
|
||||
<div id="param-slider-panel" v-panel-state="'param-slider-panel'">
|
||||
<!-- The one shared preset-parameter surface: the active style's parameters, as VisualSettings publishes them. -->
|
||||
<div id="param-slider-panel" v-panel-state="'param-slider-panel'" v-class-toggle="{ active: !!parameters }">
|
||||
<div class="param-panel-header">
|
||||
<span class="param-panel-title" data-i18n>PARAMETERS</span>
|
||||
<span class="param-panel-divider"></span>
|
||||
<PanelCollapseButton id="param-panel-collapse-btn" target="param-slider-panel" />
|
||||
</div>
|
||||
<div id="param-sliders"></div>
|
||||
<div id="param-sliders"><StyleParameterRows v-if="parameters" :key="parameters.style" :rows="parameters.rows" :on-input="(name, value) => act('setStyleParameter', name, value)" /></div>
|
||||
</div>
|
||||
<div class="pp-toggle-group">
|
||||
<!-- 3D aircraft is DEFAULT-ON in Proximity (owner directive 2026-08-22), so the
|
||||
|
||||
+50
-30
@@ -12,7 +12,7 @@ import {
|
||||
STYLE_PRESET_DEFAULTS,
|
||||
MILITARY_DETECTION_PRESET,
|
||||
} from './effects.js';
|
||||
import { createStyleParameters } from './visualInput.js';
|
||||
import { styleParameterRows, withParameterValue } from './styleParameters.js';
|
||||
import {
|
||||
BLOOM_SCALE_VERSION,
|
||||
clampBloomIntensity,
|
||||
@@ -180,6 +180,11 @@ export class VisualSettings {
|
||||
readDisplayPortalActive,
|
||||
});
|
||||
this._lifetime = new UiLifetime();
|
||||
// The active style's published parameter rows; null while it has none.
|
||||
this._parameters = null;
|
||||
this._parametersToken = store.connect({
|
||||
setStyleParameter: (name, value) => this.setStyleParameter(name, value),
|
||||
});
|
||||
this._visualEffects = new VisualEffects({
|
||||
viewer,
|
||||
requestRender: services.governorRequestRender,
|
||||
@@ -435,11 +440,7 @@ export class VisualSettings {
|
||||
}
|
||||
|
||||
_revealCockpitStyleParameters({ openDisplay = false } = {}) {
|
||||
if (
|
||||
!this.cockpitView?.active ||
|
||||
!this._sliderPanel?.classList.contains('active')
|
||||
)
|
||||
return;
|
||||
if (!this.cockpitView?.active || !this._parameters) return;
|
||||
if (openDisplay && !this._cockpitDisclosureOpen?.('display')) {
|
||||
this._setCockpitDisclosure?.('display', true);
|
||||
return;
|
||||
@@ -763,6 +764,7 @@ export class VisualSettings {
|
||||
governorRequestRender('style-param');
|
||||
}
|
||||
}
|
||||
this._refreshParameters();
|
||||
}
|
||||
|
||||
const bloomInput = preset.bloom || {};
|
||||
@@ -1397,41 +1399,54 @@ export class VisualSettings {
|
||||
this._syncShareState();
|
||||
}
|
||||
|
||||
/** Publish the style's parameter rows, or none for a style without any. */
|
||||
_updateSliderPanel(styleName, { reveal = false } = {}) {
|
||||
const { governorRequestRender } = this.services;
|
||||
this._styleParameters ||= createStyleParameters({
|
||||
container: this._sliderContainer,
|
||||
});
|
||||
this._styleParameters.clear();
|
||||
const shader = STYLES[styleName];
|
||||
|
||||
if (!shader || !shader.uniforms || styleName === 'normal') {
|
||||
this._sliderPanel.classList.remove('active');
|
||||
this._publishParameters(null);
|
||||
this._scheduleRightPanelLayout();
|
||||
return;
|
||||
}
|
||||
|
||||
this._styleParameters.render({
|
||||
uniforms: shader.uniforms,
|
||||
readValue: (uName) => this.stages[styleName].uniforms[uName],
|
||||
writeValue: (uName, val) => {
|
||||
this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||
this.stages[styleName].uniforms[uName] = val;
|
||||
},
|
||||
onChange: () => {
|
||||
// Uniform writes need an explicit render under the idle governor.
|
||||
governorRequestRender('style-param-slider');
|
||||
this._syncShareState();
|
||||
},
|
||||
this._publishParameters({
|
||||
style: styleName,
|
||||
rows: styleParameterRows(
|
||||
shader.uniforms,
|
||||
(name) => this.stages[styleName].uniforms[name],
|
||||
),
|
||||
});
|
||||
|
||||
this._sliderPanel.classList.add('active');
|
||||
this._scheduleRightPanelLayout();
|
||||
if (reveal) this._revealStyleParameters();
|
||||
}
|
||||
|
||||
/** Publish the shown style's rows again from its uniforms as they stand. */
|
||||
_refreshParameters() {
|
||||
const style = this._parameters?.style;
|
||||
if (style) this._updateSliderPanel(style);
|
||||
}
|
||||
|
||||
_publishParameters(next) {
|
||||
this._parameters = next;
|
||||
this.store.publishParameters(next);
|
||||
}
|
||||
|
||||
/** A parameter slider moved: write the active style's uniform. */
|
||||
setStyleParameter(name, value) {
|
||||
const parameters = this._parameters;
|
||||
// Only the shown style's own parameters.
|
||||
if (!parameters?.rows.some((row) => row.name === name)) return;
|
||||
this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||
this.stages[parameters.style].uniforms[name] = value;
|
||||
this._publishParameters({
|
||||
...parameters,
|
||||
rows: withParameterValue(parameters.rows, name, value),
|
||||
});
|
||||
// Uniform writes need an explicit render under the idle governor.
|
||||
this.services.governorRequestRender('style-param-slider');
|
||||
this._syncShareState();
|
||||
}
|
||||
|
||||
_revealStyleParameters() {
|
||||
if (!this._sliderPanel?.classList.contains('active')) return;
|
||||
if (!this._parameters) return;
|
||||
if (this._cockpitDisplayPortalActive) return;
|
||||
this._presentPanel(this._sliderPanel, { collapsed: false });
|
||||
this.setPanelCollapsed('pp-toggles', false, { explicit: true });
|
||||
@@ -1574,7 +1589,12 @@ export class VisualSettings {
|
||||
}
|
||||
stop() {
|
||||
this._lifetime.destroy();
|
||||
this._styleParameters?.destroy();
|
||||
if (this._parametersToken) this.store.detach(this._parametersToken);
|
||||
this._parametersToken = null;
|
||||
// The sliders go with their owner, unless a newer owner has published
|
||||
// its own since.
|
||||
if (this._parameters && this.store.parameters === this._parameters)
|
||||
this._publishParameters(null);
|
||||
this._visualEffects.stop();
|
||||
}
|
||||
releaseIrBoost() {
|
||||
|
||||
Reference in new issue
Block a user