feat: dirty-Markierung + Standard-Hinweis + Bloom 0.01-Schritte im Panel
- Werte abweichend vom Standard werden amber markiert - klickbarer Hinweis '↺ <Standard>' zeigt Originalwert (Bugfix: _defaultEl wurde nie zugewiesen) - Bloom-Schwelle jetzt in 0.01-Schritten einstellbar Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
6e8e207afa
commit
951cb26f3f
2 files changed
+98
-19
No files matched your search
+84
-18
@@ -14,7 +14,7 @@
|
||||
// ============================================================
|
||||
import * as THREE from 'three';
|
||||
import * as CFG from './config.js';
|
||||
import { loadSettings, saveSettings, clearSettings } from './config.js';
|
||||
import { loadSettings, saveSettings, clearSettings, DEFAULTS } from './config.js';
|
||||
|
||||
// ---------- Apply-Registry (von main.js gefüllt) ----------
|
||||
const applyFns = Object.create(null);
|
||||
@@ -141,7 +141,7 @@ const SCHEMA = [
|
||||
{ path: 'BLOOM_ENABLED', label: 'Bloom an', type: 'checkbox', live: true },
|
||||
{ path: 'BLOOM.strength', label: 'Bloom-Stärke', type: 'range', min: 0, max: 2, step: 0.05, live: true },
|
||||
{ path: 'BLOOM.radius', label: 'Bloom-Radius', type: 'range', min: 0, max: 2, step: 0.05, live: true },
|
||||
{ path: 'BLOOM.threshold', label: 'Bloom-Schwelle', type: 'range', min: 0, max: 2, step: 0.05, live: true },
|
||||
{ path: 'BLOOM.threshold', label: 'Bloom-Schwelle', type: 'range', min: 0, max: 2, step: 0.01, live: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -202,6 +202,7 @@ function onControlChange(item, value, skipDisplay) {
|
||||
markReloadNeeded();
|
||||
}
|
||||
if (!skipDisplay && item._valueEl) item._valueEl.textContent = fmt(item, value);
|
||||
updateDirty(item);
|
||||
}
|
||||
|
||||
function markReloadNeeded() {
|
||||
@@ -225,9 +226,57 @@ function fmt(item, value) {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
// ---------- Wert eines Controls aus config lesen ----------
|
||||
function currentValue(item) {
|
||||
return getPath(CFG, item.path);
|
||||
// ---------- Effektiver Wert (Override aus settings, sonst config = Default) ----------
|
||||
// Für Reload-Werte, die erst beim Neustart greifen, steht die letzte
|
||||
// Änderung nur in `settings` (localStorage), nicht im config-Export.
|
||||
// Daher für Anzeige + Dirty-Check immer `settings` zuerst fragen.
|
||||
function effectiveValue(item) {
|
||||
const v = getPath(settings, item.path);
|
||||
return v !== undefined ? v : getPath(CFG, item.path);
|
||||
}
|
||||
// Code-Default (unabhängig von Overrides), aus dem DEFAULTS-Snapshot.
|
||||
function defaultFor(item) {
|
||||
return getPath(DEFAULTS, item.path);
|
||||
}
|
||||
// Gleichheit mit numerischer Toleranz (Slider-Rundungsfehler abfangen,
|
||||
// sonst bliebe ein per Slider auf Default zurückbewegter Wert "dirty").
|
||||
function valueEquals(a, b) {
|
||||
if (typeof a === 'number' && typeof b === 'number') {
|
||||
if (a === b) return true;
|
||||
return Math.abs(a - b) < 1e-6 * Math.max(1, Math.abs(a), Math.abs(b));
|
||||
}
|
||||
return a === b;
|
||||
}
|
||||
// ---------- Dirty-Markierung pro Control ----------
|
||||
// Zeigt an, dass ein Wert vom Standard abweicht: Label + Wert werden
|
||||
// amber, und der klickbare "↺ Standard: X"-Hinweis erscheint.
|
||||
function updateDirty(item) {
|
||||
if (!item._row) return;
|
||||
const dirty = !valueEquals(effectiveValue(item), defaultFor(item));
|
||||
item._row.classList.toggle('dirty', dirty);
|
||||
if (item._defaultEl) {
|
||||
item._defaultEl.hidden = !dirty;
|
||||
item._defaultEl.textContent = '↺ ' + fmt(item, defaultFor(item));
|
||||
}
|
||||
}
|
||||
// Auf Default zurücksetzen (Klick auf den Standard-Hinweis).
|
||||
function resetItem(item) {
|
||||
const d = defaultFor(item);
|
||||
const inp = item._input;
|
||||
if (item.type === 'checkbox') {
|
||||
if (inp) inp.checked = !!d;
|
||||
onControlChange(item, !!d);
|
||||
} else if (item.type === 'select') {
|
||||
if (inp) inp.value = String(d);
|
||||
onControlChange(item, Number(d));
|
||||
} else if (item.type === 'color') {
|
||||
if (inp) inp.value = toHex(d);
|
||||
onControlChange(item, d);
|
||||
} else {
|
||||
const v = Number(d);
|
||||
if (inp) inp.value = v;
|
||||
onControlChange(item, v);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- DOM-Aufbau ----------
|
||||
@@ -240,28 +289,41 @@ function el(tag, cls, html) {
|
||||
|
||||
function buildControl(item) {
|
||||
const row = el('div', 'ctrl-row');
|
||||
item._row = row;
|
||||
|
||||
const head = el('div', 'ctrl-head');
|
||||
const label = el('span', 'ctrl-label', item.label);
|
||||
const badge = el('span', 'badge ' + (item.live === false || item.groupReload ? 'reload' : 'live'),
|
||||
item.live === false || item.groupReload ? '⟳' : '●');
|
||||
badge.title = item.live === false || item.groupReload ? 'Braucht Neustart' : 'Greift sofort';
|
||||
head.append(label, badge);
|
||||
// Klickbarer Standard-Hinweis (nur sichtbar, wenn der Wert abweicht).
|
||||
const defSpan = el('span', 'ctrl-default');
|
||||
defSpan.title = 'Auf Standard zurücksetzen';
|
||||
defSpan.addEventListener('click', (e) => { e.stopPropagation(); resetItem(item); });
|
||||
item._defaultEl = defSpan;
|
||||
const isReload = item.live === false || item.groupReload;
|
||||
const badge = el('span', 'badge ' + (isReload ? 'reload' : 'live'), isReload ? '⟳' : '●');
|
||||
badge.title = isReload ? 'Braucht Neustart' : 'Greift sofort';
|
||||
const right = el('span', 'ctrl-head-right');
|
||||
right.append(defSpan, badge);
|
||||
head.append(label, right);
|
||||
row.append(head);
|
||||
|
||||
const val = el('span', 'ctrl-value');
|
||||
item._valueEl = val;
|
||||
const numSpan = el('span', 'cv-num');
|
||||
val.append(numSpan);
|
||||
item._valueEl = numSpan;
|
||||
|
||||
let input;
|
||||
if (item.type === 'checkbox') {
|
||||
input = el('label', 'switch');
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.checked = !!currentValue(item);
|
||||
cb.checked = !!effectiveValue(item);
|
||||
cb.addEventListener('change', () => onControlChange(item, cb.checked));
|
||||
const slider = el('span', 'switch-track');
|
||||
input.append(cb, slider);
|
||||
val.textContent = cb.checked ? 'an' : 'aus';
|
||||
item._input = cb;
|
||||
numSpan.textContent = cb.checked ? 'an' : 'aus';
|
||||
row.append(input, val);
|
||||
updateDirty(item);
|
||||
return row;
|
||||
}
|
||||
|
||||
@@ -273,34 +335,38 @@ function buildControl(item) {
|
||||
opt.textContent = o.label;
|
||||
input.append(opt);
|
||||
});
|
||||
input.value = String(currentValue(item));
|
||||
input.value = String(effectiveValue(item));
|
||||
input.addEventListener('change', () => onControlChange(item, Number(input.value)));
|
||||
val.textContent = fmt(item, Number(input.value));
|
||||
item._input = input;
|
||||
numSpan.textContent = fmt(item, Number(input.value));
|
||||
row.append(input, val);
|
||||
updateDirty(item);
|
||||
return row;
|
||||
}
|
||||
|
||||
input = document.createElement('input');
|
||||
input.type = item.type;
|
||||
const cv = currentValue(item);
|
||||
const cv = effectiveValue(item);
|
||||
if (item.type === 'color') {
|
||||
input.value = toHex(cv);
|
||||
input.addEventListener('input', () => onControlChange(item, fromHex(input.value)));
|
||||
val.textContent = toHex(cv);
|
||||
numSpan.textContent = toHex(cv);
|
||||
} else if (item.type === 'number') {
|
||||
if (item.min != null) input.min = item.min;
|
||||
if (item.max != null) input.max = item.max;
|
||||
if (item.step != null) input.step = item.step;
|
||||
input.value = cv;
|
||||
input.addEventListener('change', () => onControlChange(item, Number(input.value)));
|
||||
val.textContent = fmt(item, Number(cv));
|
||||
numSpan.textContent = fmt(item, Number(cv));
|
||||
} else { // range
|
||||
input.min = item.min; input.max = item.max; input.step = item.step;
|
||||
input.value = cv;
|
||||
input.addEventListener('input', () => onControlChange(item, Number(input.value)));
|
||||
val.textContent = fmt(item, Number(cv));
|
||||
numSpan.textContent = fmt(item, Number(cv));
|
||||
}
|
||||
item._input = input;
|
||||
row.append(input, val);
|
||||
updateDirty(item);
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,8 @@ canvas { display: block; }
|
||||
#overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at 50% 40%, rgba(40,70,110,0.85), rgba(10,20,35,0.92));
|
||||
background: radial-gradient(circle at 50% 50%, #3339 0%, #111c 120%);
|
||||
backdrop-filter: blur(1em);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -202,6 +203,18 @@ body.panel-open #crosshair { opacity: 0; pointer-events: none; transition: opaci
|
||||
.badge { font-size: 10px; padding: 1px 5px; border-radius: 8px; margin-left: 6px; vertical-align: middle; }
|
||||
.badge.live { background: rgba(80,200,120,0.18); color: #7ee2a0; }
|
||||
.badge.reload { background: rgba(255,180,60,0.18); color: #ffc870; }
|
||||
.ctrl-head-right { display: inline-flex; align-items: center; gap: 7px; }
|
||||
/* Standard-Wert-Hinweis (nur sichtbar wenn der Wert abweicht = dirty) */
|
||||
.ctrl-default {
|
||||
font-size: 11px; color: #8a92a0; cursor: pointer; white-space: nowrap;
|
||||
padding: 1px 6px; border-radius: 8px; border: 1px solid transparent;
|
||||
transition: color 0.15s, background 0.15s, border-color 0.15s; user-select: none;
|
||||
}
|
||||
.ctrl-default[hidden] { display: none; }
|
||||
.ctrl-default:hover { color: #ffc870; background: rgba(255,180,60,0.12); border-color: rgba(255,180,60,0.35); }
|
||||
/* Dirty-Markierung: Wert weicht vom Standard ab → Label + Wert in amber */
|
||||
.ctrl-row.dirty .ctrl-label { color: #ffc870; }
|
||||
.ctrl-row.dirty .ctrl-value { color: #ffc870; }
|
||||
|
||||
/* Range-Slider */
|
||||
.ctrl-row input[type=range] {
|
||||
|
||||
Reference in new issue
Block a user