mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
Keep Scope opacity at half or more, and ignore a value that is not a number
A fainter mask reads as Scope off while labels still fade outside the keyhole and the HUD gets no backplates. Co-authored-by: Manjunath Paparaju <[email protected]>
This commit is contained in:
1 parent
a1e7b2a394
commit
2fd8d32a5d
7 files changed
+106
-42
No files matched your search
@@ -253,7 +253,7 @@ test('detection-on-by-default is a default, not an operator override', () => {
|
||||
// A factory default is not that. If applying the baseline set the flag, a
|
||||
// fresh session would silently lose the style auto-enable behaviour — a
|
||||
// separate landed feature, taken out by an unrelated change.
|
||||
const applyDefaults = uiBlock(' _applyGlobalPostDefaults() {', '\n }\n');
|
||||
const applyDefaults = uiBlock(' _applyGlobalPostDefaults({ keepHudVariant = false } = {}) {', '\n }\n');
|
||||
assert.match(applyDefaults, /this\._setDetectionMode\(defaults\.detectionMode\)/,
|
||||
'the baseline still goes through the real detection path');
|
||||
assert.doesNotMatch(applyDefaults, /_detectionUserOverridden/,
|
||||
|
||||
@@ -316,13 +316,13 @@ test('a shared scope opacity is restored and rides in the next share state', asy
|
||||
});
|
||||
let shown = null;
|
||||
owner.store = { publish: (view) => (shown = view.scope) };
|
||||
await owner.restoreShareState({ scopeOpacityPct: 40 });
|
||||
assert.equal(getScopeMaskOpacity(), 0.4);
|
||||
assert.equal(shown.opacityPct, 40);
|
||||
await owner.restoreShareState({ scopeOpacityPct: 60 });
|
||||
assert.equal(getScopeMaskOpacity(), 0.6);
|
||||
assert.equal(shown.opacityPct, 60);
|
||||
// A state without it leaves the opacity alone.
|
||||
await owner.restoreShareState({ scopeFeatherPct: 80 });
|
||||
assert.equal(getScopeMaskOpacity(), 0.4);
|
||||
assert.equal(shown.opacityPct, 40);
|
||||
assert.equal(getScopeMaskOpacity(), 0.6);
|
||||
assert.equal(shown.opacityPct, 60);
|
||||
Object.assign(owner.services, {
|
||||
getDetectionTuning: () => ({ allocationStrategy: 'ELASTIC' }),
|
||||
getDetectionMode: () => 'OFF',
|
||||
@@ -332,7 +332,11 @@ test('a shared scope opacity is restored and rides in the next share state', asy
|
||||
getScopeTerminusOverride: () => null,
|
||||
});
|
||||
owner.readCelestialRing = () => null;
|
||||
assert.equal(owner._readShareState().options.scopeOpacityPct, 40);
|
||||
assert.equal(owner._readShareState().options.scopeOpacityPct, 60);
|
||||
// A link asking for less than the floor opens at the floor, and says so.
|
||||
await owner.restoreShareState({ scopeOpacityPct: 20 });
|
||||
assert.equal(getScopeMaskOpacity(), 0.5);
|
||||
assert.equal(owner._readShareState().options.scopeOpacityPct, 50);
|
||||
});
|
||||
|
||||
test('a scene shot saved with the retired Snow style plays on Normal', async (t) => {
|
||||
|
||||
@@ -179,7 +179,9 @@ export class DisplayBindings {
|
||||
resetVisual: () => {
|
||||
this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||
this.setStyle('normal', { userInitiated: true });
|
||||
this._applyGlobalPostDefaults();
|
||||
// The HUD layout follows the Normal style, as choosing it does: the
|
||||
// Cyber HUD stays Cyber.
|
||||
this._applyGlobalPostDefaults({ keepHudVariant: true });
|
||||
this._setDetectionAllocation('ELASTIC');
|
||||
this._setScope({ enabled: true, featherPct: 100, opacityPct: 100 });
|
||||
this._setModels3dEnabled(true);
|
||||
|
||||
@@ -108,7 +108,11 @@ test('bloom and sharpen render their state, slider and readout', async (t) => {
|
||||
await nextTick();
|
||||
assert.equal($('bloom-toggle').classList.contains('active'), false);
|
||||
assert.equal($('bloom-slider-row').classList.contains('visible'), false);
|
||||
assert.equal($('bloom-intensity-value').textContent, '140%', 'the amount stays');
|
||||
assert.equal(
|
||||
$('bloom-intensity-value').textContent,
|
||||
'140%',
|
||||
'the amount stays',
|
||||
);
|
||||
});
|
||||
|
||||
test('a publish never rewrites a slider the user is dragging', async (t) => {
|
||||
@@ -326,36 +330,50 @@ test('scope opacity renders what VisualSettings shows and sets the mask', async
|
||||
assert.equal($('scope-opacity-slider').value, '100');
|
||||
assert.equal($('scope-opacity-value').textContent, '100%');
|
||||
|
||||
owner._setScope({ opacityPct: 42.4 });
|
||||
owner._setScope({ opacityPct: 62.4 });
|
||||
await nextTick();
|
||||
assert.equal(mask.opacity, 0.42);
|
||||
assert.equal($('scope-opacity-slider').value, '42');
|
||||
assert.equal($('scope-opacity-value').textContent, '42%');
|
||||
assert.equal(store.view.scope.opacityPct, 42);
|
||||
assert.equal(mask.opacity, 0.62);
|
||||
assert.equal($('scope-opacity-slider').value, '62');
|
||||
assert.equal($('scope-opacity-value').textContent, '62%');
|
||||
assert.equal(store.view.scope.opacityPct, 62);
|
||||
|
||||
// No fainter than the floor, where the view would read as Scope off.
|
||||
owner._setScope({ opacityPct: 10 });
|
||||
await nextTick();
|
||||
assert.equal(mask.opacity, 0.5, 'floored');
|
||||
assert.equal($('scope-opacity-value').textContent, '50%');
|
||||
assert.equal($('scope-opacity-slider').min, '50');
|
||||
|
||||
// A value that is not a number changes nothing.
|
||||
owner._setScope({ opacityPct: Number.NaN });
|
||||
await nextTick();
|
||||
assert.equal($('scope-opacity-value').textContent, '50%');
|
||||
|
||||
owner._setScope({ opacityPct: 180 });
|
||||
await nextTick();
|
||||
assert.equal(mask.opacity, 1, 'clamped');
|
||||
assert.equal($('scope-opacity-value').textContent, '100%');
|
||||
|
||||
owner._setScope({ opacityPct: 30 }, { apply: false });
|
||||
owner._setScope({ opacityPct: 70 }, { apply: false });
|
||||
await nextTick();
|
||||
assert.equal(mask.opacity, 1, 'only shown');
|
||||
assert.equal($('scope-opacity-value').textContent, '30%');
|
||||
assert.equal($('scope-opacity-value').textContent, '70%');
|
||||
|
||||
owner._setScope({ enabled: false });
|
||||
await nextTick();
|
||||
assert.equal($('scope-opacity-slider').disabled, true, 'off with the scope');
|
||||
assert.equal(store.view.scope.opacityPct, 30, 'kept while off');
|
||||
assert.equal(store.view.scope.opacityPct, 70, 'kept while off');
|
||||
});
|
||||
|
||||
test('the Visual panel groups its controls and words its on/off pills', (t) => {
|
||||
const { $ } = fixture(t);
|
||||
const panel = $('pp-toggles');
|
||||
assert.deepEqual(
|
||||
[...panel.querySelectorAll('.pp-section-heading, .visual-presets__heading')].map(
|
||||
(el) => el.textContent.trim(),
|
||||
),
|
||||
[
|
||||
...panel.querySelectorAll(
|
||||
'.pp-section-heading, .visual-presets__heading',
|
||||
),
|
||||
].map((el) => el.textContent.trim()),
|
||||
['Look', 'MAP SOURCE 3D', 'Overlay', 'Effects'],
|
||||
);
|
||||
assert.equal(panel.querySelector('.pp-header-label').textContent, 'VISUAL');
|
||||
@@ -384,7 +402,10 @@ test('the 3D aircraft group renders what AircraftDisplay shows', async (t) => {
|
||||
assert.equal(coreClasses(button), 'pp-toggle-btn active');
|
||||
assert.equal(button.getAttribute('aria-pressed'), 'true');
|
||||
assert.equal(row.classList.contains('visible'), true);
|
||||
assert.equal($('models3d-mode-proximity').getAttribute('aria-checked'), 'true');
|
||||
assert.equal(
|
||||
$('models3d-mode-proximity').getAttribute('aria-checked'),
|
||||
'true',
|
||||
);
|
||||
|
||||
// The button leaves the row to its own sync.
|
||||
owner._setModels3dEnabled(false);
|
||||
@@ -400,7 +421,10 @@ test('the 3D aircraft group renders what AircraftDisplay shows', async (t) => {
|
||||
await nextTick();
|
||||
assert.equal(all.classList.contains('active'), true);
|
||||
assert.equal(all.getAttribute('aria-checked'), 'true');
|
||||
assert.equal($('models3d-mode-proximity').classList.contains('active'), false);
|
||||
assert.equal(
|
||||
$('models3d-mode-proximity').classList.contains('active'),
|
||||
false,
|
||||
);
|
||||
assert.deepEqual(params.at(-1), ['military', { models3dMode: 'all' }]);
|
||||
|
||||
owner._syncModels3dFromLayerState({
|
||||
@@ -429,12 +453,18 @@ test('the Draw controls render what the draw tool publishes', async (t) => {
|
||||
assert.equal(segment('area').getAttribute('aria-label'), 'Area');
|
||||
assert.equal(segment('area').title, 'Area');
|
||||
assert.equal(segment('area').textContent, 'pentagon');
|
||||
const swatch = (color) => document.querySelector(`#draw-colors [data-color="${color}"]`);
|
||||
const swatch = (color) =>
|
||||
document.querySelector(`#draw-colors [data-color="${color}"]`);
|
||||
assert.equal(swatch('primary').getAttribute('aria-checked'), 'true');
|
||||
assert.equal(swatch('amber').getAttribute('aria-label'), 'Amber');
|
||||
assert.equal($('draw-hint').textContent, '');
|
||||
|
||||
store.publishDraw({ active: true, shape: 'line', color: 'amber', hint: 'Click to add points.' });
|
||||
store.publishDraw({
|
||||
active: true,
|
||||
shape: 'line',
|
||||
color: 'amber',
|
||||
hint: 'Click to add points.',
|
||||
});
|
||||
await nextTick();
|
||||
assert.equal(swatch('amber').getAttribute('aria-checked'), 'true');
|
||||
assert.equal(swatch('primary').classList.contains('active'), false);
|
||||
@@ -530,7 +560,10 @@ test('the imported files render what the file import publishes and keep focus',
|
||||
assert.equal(document.activeElement === button(first, 'toggle'), true);
|
||||
assert.equal(button(first, 'toggle').getAttribute('aria-pressed'), 'true');
|
||||
assert.equal(button(first, 'fly').title, 'Fly to <b>a</b>.geojson');
|
||||
assert.equal(button(first, 'remove').getAttribute('aria-label'), 'Remove <b>a</b>.geojson');
|
||||
assert.equal(
|
||||
button(first, 'remove').getAttribute('aria-label'),
|
||||
'Remove <b>a</b>.geojson',
|
||||
);
|
||||
|
||||
await press(button(first, 'remove'));
|
||||
assert.equal(rowsNow().length, 1);
|
||||
@@ -646,7 +679,11 @@ test('VisualSettings publishes a style’s parameters and writes a moved slider
|
||||
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');
|
||||
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: [] };
|
||||
@@ -658,10 +695,16 @@ test('VisualSettings publishes a style’s parameters and writes a moved slider
|
||||
test('the four Cockpit groups teleport to the published slots and back to their places', async (t) => {
|
||||
const { store, $ } = fixture(t);
|
||||
const groupOf = (id) => $(id).closest('.pp-toggle-group') ?? $(id);
|
||||
const ids = ['hud-toggle', 'detection-toggle', 'param-slider-panel', 'models3d-toggle'];
|
||||
const ids = [
|
||||
'hud-toggle',
|
||||
'detection-toggle',
|
||||
'param-slider-panel',
|
||||
'models3d-toggle',
|
||||
];
|
||||
const body = $('pp-toggles').querySelector('.pp-panel-body');
|
||||
const order = () =>
|
||||
[...body.children].filter((el) => el.matches('.pp-toggle-group, #param-slider-panel'))
|
||||
[...body.children]
|
||||
.filter((el) => el.matches('.pp-toggle-group, #param-slider-panel'))
|
||||
.map((el) => el.id || el.querySelector('[id]')?.id);
|
||||
const home = order();
|
||||
const popover = document.body.appendChild(document.createElement('div'));
|
||||
|
||||
@@ -246,7 +246,7 @@ test('the Visual panel’s scope opacity, reset and presets call their actions',
|
||||
],
|
||||
});
|
||||
await nextTick();
|
||||
input($('scope-opacity-slider'), 40);
|
||||
input($('scope-opacity-slider'), 60);
|
||||
document.querySelector('#pp-toggles .pp-reset-btn').click();
|
||||
const presets = document.querySelector('#pp-toggles .visual-presets');
|
||||
presets.querySelector('[data-style="noir"]').click();
|
||||
@@ -254,7 +254,7 @@ test('the Visual panel’s scope opacity, reset and presets call their actions',
|
||||
chips[0].click();
|
||||
chips[1].click();
|
||||
assert.deepEqual(calls, [
|
||||
['setScopeOpacity', 40],
|
||||
['setScopeOpacity', 60],
|
||||
['resetVisual'],
|
||||
['setStyle', 'noir'],
|
||||
]);
|
||||
@@ -290,7 +290,8 @@ test('DisplayBindings sets the scope opacity and resets Visual to its defaults',
|
||||
elements: { _locationSearch: document.createElement('input') },
|
||||
operations: {
|
||||
setStyle: (style, options) => calls.push(['style', style, options]),
|
||||
_applyGlobalPostDefaults: () => calls.push(['postDefaults']),
|
||||
_applyGlobalPostDefaults: (options) =>
|
||||
calls.push(['postDefaults', options]),
|
||||
_setDetectionAllocation: (value) => calls.push(['allocation', value]),
|
||||
_setScope: (scope) => calls.push(['scope', scope]),
|
||||
_setModels3dEnabled: (on) => calls.push(['models3d', on]),
|
||||
@@ -310,10 +311,10 @@ test('DisplayBindings sets the scope opacity and resets Visual to its defaults',
|
||||
bindings._initUI();
|
||||
t.after(() => bindings.destroy());
|
||||
calls.length = 0;
|
||||
input($('scope-opacity-slider'), 35);
|
||||
input($('scope-opacity-slider'), 65);
|
||||
assert.deepEqual(calls, [
|
||||
['claim', 'visual'],
|
||||
['scope', { opacityPct: 35 }],
|
||||
['scope', { opacityPct: 65 }],
|
||||
['share'],
|
||||
]);
|
||||
calls.length = 0;
|
||||
@@ -321,7 +322,7 @@ test('DisplayBindings sets the scope opacity and resets Visual to its defaults',
|
||||
assert.deepEqual(calls, [
|
||||
['claim', 'visual'],
|
||||
['style', 'normal', { userInitiated: true }],
|
||||
['postDefaults'],
|
||||
['postDefaults', { keepHudVariant: true }],
|
||||
['allocation', 'ELASTIC'],
|
||||
['scope', { enabled: true, featherPct: 100, opacityPct: 100 }],
|
||||
['models3d', true],
|
||||
|
||||
@@ -71,7 +71,7 @@ const allocationOptions = computed(() => [
|
||||
<p class="pp-section-heading">{{ t('Overlay') }}</p>
|
||||
<Teleport :to="cockpitPortal?.hud ?? 'body'" :disabled="!cockpitPortal?.hud">
|
||||
<div class="pp-toggle-group">
|
||||
<button class="pp-toggle-btn" :class="{ active: hud?.visible }" id="hud-toggle" @click="act('toggleHud')" :title="t('Intelligence HUD (H)')">
|
||||
<button class="pp-toggle-btn" :class="{ active: hud?.visible }" :aria-pressed="String(Boolean(hud?.visible))" id="hud-toggle" @click="act('toggleHud')" :title="t('Intelligence HUD (H)')">
|
||||
<span class="pp-icon">◎</span>
|
||||
<span class="pp-label" translate="no">HUD</span>
|
||||
</button>
|
||||
@@ -182,7 +182,7 @@ const allocationOptions = computed(() => [
|
||||
</div>
|
||||
<div class="pp-slider-row visible" id="scope-opacity-row">
|
||||
<span class="pp-slider-mini-label">{{ t('Opacity') }}</span>
|
||||
<UiSlider class="pp-slider" id="scope-opacity-slider" @update:model-value="act('setScopeOpacity', $event)" min="0" max="100" :model-value="scopeOpacity" :disabled="!scopeOn" :aria-label="t('Scope opacity')" :title="t('How dark the scope shades the view outside the keyhole')" />
|
||||
<UiSlider class="pp-slider" id="scope-opacity-slider" @update:model-value="act('setScopeOpacity', $event)" min="50" max="100" :model-value="scopeOpacity" :disabled="!scopeOn" :aria-label="t('Scope opacity')" :title="t('How dark the scope shades the view outside the keyhole')" />
|
||||
<span class="pp-slider-value" id="scope-opacity-value">{{ pct(scopeOpacity) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -198,7 +198,7 @@ const allocationOptions = computed(() => [
|
||||
</UiButton>
|
||||
</div>
|
||||
<div class="pp-toggle-group">
|
||||
<button class="pp-toggle-btn" :class="{ active: view?.bloom.active }" id="bloom-toggle" @click="act('toggleBloom')" :title="t('Bloom / Glow')">
|
||||
<button class="pp-toggle-btn" :class="{ active: view?.bloom.active }" :aria-pressed="String(Boolean(view?.bloom.active))" id="bloom-toggle" @click="act('toggleBloom')" :title="t('Bloom / Glow')">
|
||||
<span class="pp-icon">✨</span>
|
||||
<span class="pp-label">{{ t('Bloom') }}</span>
|
||||
</button>
|
||||
@@ -208,7 +208,7 @@ const allocationOptions = computed(() => [
|
||||
</div>
|
||||
</div>
|
||||
<div class="pp-toggle-group">
|
||||
<button class="pp-toggle-btn" :class="{ active: view?.sharpen.active }" id="sharpen-toggle" @click="act('toggleSharpen')" :title="t('Sharpening')">
|
||||
<button class="pp-toggle-btn" :class="{ active: view?.sharpen.active }" :aria-pressed="String(Boolean(view?.sharpen.active))" id="sharpen-toggle" @click="act('toggleSharpen')" :title="t('Sharpening')">
|
||||
<span class="pp-icon">🔍</span>
|
||||
<span class="pp-label">{{ t('Sharpen') }}</span>
|
||||
</button>
|
||||
|
||||
@@ -46,6 +46,13 @@ import { cyberVisualDefaultsForHudTransition } from '../hudLayouts.js';
|
||||
const DETECTION_ALLOCATION_STORAGE_KEY = 'gev:detection-allocation:v1';
|
||||
|
||||
/** Own visual preferences, detection overrides and display-control state. */
|
||||
/**
|
||||
* The faintest the Scope mask goes. Below about half, the view reads as if
|
||||
* Scope were off while labels still fade outside the keyhole and the HUD gets
|
||||
* no backplates, so the slider stops here.
|
||||
*/
|
||||
export const SCOPE_OPACITY_MIN_PCT = 50;
|
||||
|
||||
export class VisualSettings {
|
||||
async restoreShareState(state) {
|
||||
const { setScopeTerminusOverride, clampScopeTerminusPct } = this.services;
|
||||
@@ -511,8 +518,11 @@ export class VisualSettings {
|
||||
* given stays as it is. `apply: false` only shows the mask's state.
|
||||
*/
|
||||
_setScope({ enabled, featherPct, opacityPct } = {}, { apply = true } = {}) {
|
||||
if (typeof opacityPct === 'number') {
|
||||
const pct = Math.max(0, Math.min(100, Math.round(opacityPct)));
|
||||
if (Number.isFinite(opacityPct)) {
|
||||
const pct = Math.max(
|
||||
SCOPE_OPACITY_MIN_PCT,
|
||||
Math.min(100, Math.round(opacityPct)),
|
||||
);
|
||||
if (apply) this.services.setScopeMaskOpacity?.(pct / 100);
|
||||
this._scopeOpacityShown = pct;
|
||||
}
|
||||
@@ -846,7 +856,11 @@ export class VisualSettings {
|
||||
if (det.mode) this._setDetectionMode(String(det.mode).toUpperCase());
|
||||
}
|
||||
|
||||
_applyGlobalPostDefaults() {
|
||||
/**
|
||||
* @param {{keepHudVariant?: boolean}} [options] `keepHudVariant` leaves the
|
||||
* HUD layout to the style, as choosing a style does (Visual's reset).
|
||||
*/
|
||||
_applyGlobalPostDefaults({ keepHudVariant = false } = {}) {
|
||||
const defaults = GLOBAL_POST_DEFAULTS;
|
||||
if (typeof defaults.bloom?.intensity === 'number') {
|
||||
this._setBloomIntensity(clampBloomIntensity(defaults.bloom.intensity), {
|
||||
@@ -865,7 +879,7 @@ export class VisualSettings {
|
||||
this._setSharpenEnabled(defaults.sharpen.enabled);
|
||||
}
|
||||
|
||||
if (defaults.hudVariant) {
|
||||
if (defaults.hudVariant && !keepHudVariant) {
|
||||
this._setHudVariant(defaults.hudVariant);
|
||||
}
|
||||
if (typeof defaults.hudVisible === 'boolean') {
|
||||
|
||||
Reference in new issue
Block a user