feat(qt): polarity-aware ambient veil and weak-ramp hardening for light themes

The ambient legibility scrim was a fixed BLACK layer at ambientDim — right
for dark themes, but over a LIGHT theme it pushed the field to mid
luminance, the worst ground for dark text, and the grey secondary/muted
ramp drowned. Light themes now veil with their own near-white surfaceMain
a notch stronger, with the in-shader (mode 1) and in-atmosphere (mode 2)
darkening turned off there; dark themes keep the exact previous look.
QbzTheme additionally steps textSecondary/textMuted toward textPrimary
while ambient is on under a light theme, so the weak ramp keeps a
contrast floor everywhere without call-site changes.

cc
This commit is contained in:
vicrodh committed 2026-08-31 15:47:44 -06:00
1 parent c51df2a061
commit 2e3c765616
3 files changed
+59 -12

No files matched your search

+8 -2
View File
@@ -61,6 +61,11 @@ Item {
property color accent: QbzShell.ambientAccent
// Set false by the host to freeze the clock (inactive / hidden).
property bool running: true
// The in-shader legibility dim (multiply toward black). The host lowers
// it to 0 on LIGHT themes, where darkening is the wrong direction — the
// polarity-aware veil rectangle above the field takes over there
// (AppShell, 2026-08-31).
property real dim: QbzShell.ambientDim
// True unless the window is minimized or hidden — see the GATING RULE at
// the top. Focus is deliberately NOT part of this.
@@ -183,8 +188,9 @@ Item {
property real resX: root.resW
property real resY: root.resH
// The dark legibility scrim, applied inside the program rather than as
// a second full-window Rectangle above it — see the shader.
property real dim: QbzShell.ambientDim
// a second full-window Rectangle above it — see the shader. Host-fed:
// 0 on light themes (the veil rectangle handles legibility there).
property real dim: root.dim
onStatusChanged: {
if (status === ShaderEffect.Error)
+29 -8
View File
@@ -69,20 +69,32 @@ Rectangle {
readonly property bool ambientModeOn: root.ambientOn && QbzShell.ambientMode === 1
readonly property bool blurredModeOn: root.ambientOn && QbzShell.ambientMode === 2
// Mode 1 — the album-triad metaball field, plus the tunable dark scrim
// that keeps chrome text legible over a bright album palette
// (QBZ_BG_DIM, default 0.35). The scrim is mode 1 ONLY: mode 2's
// atmosphere applies the same dim internally (AppShell.slint:236).
// --- The polarity-aware legibility veil (owner, 2026-08-31) ------------
// The old scrim was a fixed BLACK layer at ambientDim — tuned for dark
// themes, where dark ground + light text is exactly right. Over a LIGHT
// theme the same dark scrim pushed the field to MID luminance, the worst
// possible ground for dark text (the weak grey ramp died completely).
// Light themes therefore veil with their own near-white surfaceMain, a
// notch stronger, and the in-shader/in-atmosphere darkening is turned
// OFF there (darkening and then whitening would just grey the field).
// Dark themes keep the exact previous look.
readonly property real veilStrength: theme.isDark
? QbzShell.ambientDim
: Math.min(0.72, QbzShell.ambientDim + 0.2)
// Mode 1 — the album-triad metaball field, plus the veil that keeps text
// legible over a bright album palette (QBZ_BG_DIM, default 0.35).
AmbientField {
anchors.fill: parent
visible: root.ambientModeOn
running: root.ambientModeOn
dim: theme.isDark ? QbzShell.ambientDim : 0.0
}
Rectangle {
anchors.fill: parent
visible: root.ambientModeOn
color: "#000000"
opacity: QbzShell.ambientDim
color: theme.isDark ? "#000000" : theme.surfaceMain
opacity: root.veilStrength
}
// Mode 2 — Blurred art: the SAME ImmersiveAtmosphere the immersive view
@@ -90,14 +102,23 @@ Rectangle {
// reuses the identical component). `animated` follows the transport, so a
// paused player holds the static pose instead of drifting forever, and the
// fallback is the plain cover for a track whose atmosphere bitmap has not
// been generated yet.
// been generated yet. On light themes its internal dark dim is disabled
// and the veil below provides the legibility layer instead (the
// atmosphere's baked gradient scrim stays — it reads as depth under the
// light veil, not as darkness).
ImmersiveAtmosphere {
anchors.fill: parent
visible: root.blurredModeOn
source: root.blurredModeOn ? QbzImmersive.atmosphereUrl : ""
fallbackSource: root.blurredModeOn ? QbzPlayer.npArtworkPath : ""
animated: root.blurredModeOn && QbzPlayer.npPlaying
dim: QbzShell.ambientDim
dim: theme.isDark ? QbzShell.ambientDim : 0.0
}
Rectangle {
anchors.fill: parent
visible: root.blurredModeOn && !theme.isDark
color: theme.surfaceMain
opacity: root.veilStrength
}
// The host ApplicationWindow (custom chrome: drag / maximize / resize).