From 78ce6c84d70a63a0ad1f4e74ccb5436825149383 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sun, 29 Mar 2026 00:42:42 +0300 Subject: [PATCH] fix: composite layer opacity/brightness widgets + CSS layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix opacity widget empty space (CSS selector .composite-layer-opacity → .composite-layer-opacity-container) - Replace brightness select dropdown with BindableScalarWidget (slider + VS toggle) - Legacy brightness_source_id auto-converted to BindableFloat on load - Add .composite-layer-brightness-container CSS rule --- .../src/wled_controller/api/routes/system.py | 3 +- .../core/processing/metrics_history.py | 3 +- .../wled_controller/static/css/dashboard.css | 70 +++++++++++++++++- .../src/wled_controller/static/css/modal.css | 3 +- .../js/features/color-strips-composite.ts | 44 +++++++----- .../static/js/features/perf-charts.ts | 72 +++++++++++++------ 6 files changed, 151 insertions(+), 44 deletions(-) diff --git a/server/src/wled_controller/api/routes/system.py b/server/src/wled_controller/api/routes/system.py index 738d393..aed4746 100644 --- a/server/src/wled_controller/api/routes/system.py +++ b/server/src/wled_controller/api/routes/system.py @@ -268,7 +268,8 @@ def get_system_performance(_: AuthRequired): # App-level metrics proc_mem = _process.memory_info() - app_cpu = _process.cpu_percent(interval=None) + # Process.cpu_percent() is per-core (0–N*100%); normalize to 0–100% scale + app_cpu = _process.cpu_percent(interval=None) / (psutil.cpu_count(logical=True) or 1) app_ram_mb = round(proc_mem.rss / 1024 / 1024, 1) gpu = None diff --git a/server/src/wled_controller/core/processing/metrics_history.py b/server/src/wled_controller/core/processing/metrics_history.py index 0480c82..4c66b40 100644 --- a/server/src/wled_controller/core/processing/metrics_history.py +++ b/server/src/wled_controller/core/processing/metrics_history.py @@ -38,7 +38,8 @@ def _collect_system_snapshot() -> dict: "ram_pct": mem.percent, "ram_used": round(mem.used / 1024 / 1024, 1), "ram_total": round(mem.total / 1024 / 1024, 1), - "app_cpu": _process.cpu_percent(interval=None), + # Process.cpu_percent() is per-core (0–N*100%); normalize to 0–100% + "app_cpu": _process.cpu_percent(interval=None) / (psutil.cpu_count(logical=True) or 1), "app_ram": round(proc_mem.rss / 1024 / 1024, 1), "gpu_util": None, "gpu_temp": None, diff --git a/server/src/wled_controller/static/css/dashboard.css b/server/src/wled_controller/static/css/dashboard.css index 1bc6a66..4111561 100644 --- a/server/src/wled_controller/static/css/dashboard.css +++ b/server/src/wled_controller/static/css/dashboard.css @@ -368,12 +368,37 @@ } } +/* ── Per-metric accent colors ── */ .perf-chart-card { + --perf-accent: var(--primary-color); + --perf-accent-glow: transparent; background: var(--card-bg); border: 1px solid var(--border-color); + border-top: 3px solid var(--perf-accent); border-radius: 6px; padding: 10px 0 0; min-width: 0; + position: relative; + transition: box-shadow var(--duration-normal) ease; +} + +.perf-chart-card:hover { + box-shadow: 0 0 16px var(--perf-accent-glow); +} + +.perf-chart-card[data-metric="cpu"] { + --perf-accent: #FF6B6B; + --perf-accent-glow: rgba(255, 107, 107, 0.12); +} + +.perf-chart-card[data-metric="ram"] { + --perf-accent: #A855F7; + --perf-accent-glow: rgba(168, 85, 247, 0.12); +} + +.perf-chart-card[data-metric="gpu"] { + --perf-accent: #10B981; + --perf-accent-glow: rgba(16, 185, 129, 0.12); } .perf-chart-wrap { @@ -382,6 +407,16 @@ overflow: hidden; } +/* Subtle gradient wash in chart background */ +.perf-chart-wrap::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(180deg, var(--perf-accent-glow), transparent 60%); + pointer-events: none; + z-index: 0; +} + .perf-chart-header { display: flex; justify-content: space-between; @@ -395,7 +430,21 @@ font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; - color: var(--text-secondary); + color: var(--perf-accent); + display: flex; + align-items: center; + gap: 6px; +} + +/* Accent dot before label */ +.perf-chart-label::before { + content: ''; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--perf-accent); + box-shadow: 0 0 6px var(--perf-accent); + flex-shrink: 0; } .perf-chart-subtitle { @@ -414,10 +463,26 @@ z-index: 1; } +/* ── Value display ── */ .perf-chart-value { font-size: 0.85rem; font-weight: 700; - color: var(--primary-text-color); + color: var(--perf-accent); + font-family: var(--font-mono, monospace); + display: flex; + align-items: baseline; + gap: 6px; +} + +/* App value shown as subdued tag in "both" mode */ +.perf-chart-value .perf-val-app { + font-size: 0.65rem; + font-weight: 500; + color: var(--text-secondary); + background: var(--hover-bg); + padding: 1px 5px; + border-radius: 3px; + letter-spacing: 0.2px; } .perf-chart-label .color-picker-swatch { @@ -433,6 +498,7 @@ font-size: 0.8rem; } +/* ── Mode toggle ── */ .perf-mode-toggle { display: inline-flex; gap: 0; diff --git a/server/src/wled_controller/static/css/modal.css b/server/src/wled_controller/static/css/modal.css index 2c2f2cb..a6032da 100644 --- a/server/src/wled_controller/static/css/modal.css +++ b/server/src/wled_controller/static/css/modal.css @@ -1980,7 +1980,8 @@ flex-shrink: 0; } -.composite-layer-opacity { +.composite-layer-opacity-container, +.composite-layer-brightness-container { flex: 1; min-width: 60px; } diff --git a/server/src/wled_controller/static/js/features/color-strips-composite.ts b/server/src/wled_controller/static/js/features/color-strips-composite.ts index e9b5f7a..257e236 100644 --- a/server/src/wled_controller/static/js/features/color-strips-composite.ts +++ b/server/src/wled_controller/static/js/features/color-strips-composite.ts @@ -23,6 +23,7 @@ let _compositeLayers: any[] = []; let _compositeAvailableSources: any[] = []; // non-composite sources for layer dropdowns let _compositeSourceEntitySelects: any[] = []; let _compositeBrightnessEntitySelects: any[] = []; +let _compositeBrightnessWidgets: BindableScalarWidget[] = []; let _compositeBlendIconSelects: any[] = []; let _compositeCSPTEntitySelects: any[] = []; let _compositeOpacityWidgets: BindableScalarWidget[] = []; @@ -51,6 +52,8 @@ export function compositeDestroyEntitySelects() { _compositeCSPTEntitySelects = []; _compositeOpacityWidgets.forEach(w => w.destroy()); _compositeOpacityWidgets = []; + _compositeBrightnessWidgets.forEach(w => w.destroy()); + _compositeBrightnessWidgets = []; } function _getCompositeBlendItems() { @@ -96,15 +99,10 @@ export function compositeRenderList() { const list = document.getElementById('composite-layers-list') as HTMLElement | null; if (!list) return; compositeDestroyEntitySelects(); - const vsList = _cachedValueSources || []; list.innerHTML = _compositeLayers.map((layer, i) => { const srcOptions = _compositeAvailableSources.map(s => `` ).join(''); - const vsOptions = `` + - vsList.map(v => - `` - ).join(''); const csptList = _cachedCSPTemplates || []; const csptOptions = `` + csptList.map(tmpl => @@ -151,7 +149,7 @@ export function compositeRenderList() { - +