fix(qt): glyph colours follow the theme, on light palettes and on accent fills
Two defects with one cause: a tint name that means a frozen hex. "primary" is the legacy alias of literal #ffffff, not Theme.text-primary, so every control passing it drew a white glyph. On the eleven light themes that is white-on-light -- the header back/forward chevrons, the sidebar toggle and the carousel arrows the owner marked. They now pass the theme-following names, which icon_tint_qt bakes at runtime. The stale `hslLightness > 0.5 ? "primary" : "black"` workarounds that predate the runtime tinter collapse into those names, and the qrc floor underneath them becomes polarity-aware: dir_for() returns "" whenever the cache dir is unavailable, a bake fails or the theme document has not been published, and that path used to resolve textPrimary straight back to the white bake -- reproducing the bug it was fixing, app-wide, in the degraded state. On accent fills the fix is a deviation from Slint, approved by the owner after measurement. A fixed white glyph on Theme.accent falls under 2.6:1 on 16 of the 35 shipped palettes, including high-contrast at 1.70:1 and wcag-dark at 1.82:1 -- the two themes whose reason to exist is contrast. Slint hardcodes #ffffff there (CircleAction.slint:72 and three others), so the bug is upstream. accentText alone is not the answer either: it is picked for the theme's text ramp and gives 2.56:1 on Rose Pine Dawn, worse than the black it replaces. So QbzTheme picks the best-contrast tint by real sRGB luminance against the live accent, and the labels beside those glyphs use its colour twin -- BrowseGenreButton and LibraryView were drawing a white glyph next to an accentText label inside the same pill. Rounds out the two parity items the same files owe: QbzNavButton gains the ambient arm from Carousel.slint (wired at the six carousel sites, not left dead), and QbzCircleAction rounds its glyph to whole pixels the way CircleAction.slint does. cc
This commit is contained in:
52 files changed
+841
-153
No files matched your search
@@ -532,7 +532,15 @@ Popup {
|
||||
spacing: 6
|
||||
Text {
|
||||
text: tabRoot.label
|
||||
color: tabRoot.selected ? theme.accentText : theme.textPrimary
|
||||
// Selected -> this text sits ON `theme.accent`, so it takes
|
||||
// the on-accent selector like every other accent-filled
|
||||
// control (theme/QbzTheme.qml, "ON AN ACCENT FILL"). This was
|
||||
// the LAST accent fill in the tree still reading raw
|
||||
// accent-text, i.e. the one place the app contradicted its
|
||||
// own rule. shell/CastPicker.slint:86 is a hardcoded #ffffff
|
||||
// — 1.70:1 on high-contrast — so this diverges from upstream
|
||||
// by measurement, same as the rest.
|
||||
color: tabRoot.selected ? theme.accentGlyphColor : theme.textPrimary
|
||||
font.pixelSize: theme.fontBody
|
||||
font.weight: tabRoot.selected ? theme.weightSemibold : Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -540,7 +548,24 @@ Popup {
|
||||
Text {
|
||||
visible: tabRoot.count > 0
|
||||
text: tabRoot.count
|
||||
color: tabRoot.selected ? theme.accentText : theme.textMuted
|
||||
// Same selector as the label beside it — the two halves of a
|
||||
// tab cannot disagree. This was the worst on-accent number
|
||||
// left anywhere: raw accent-text at 80% composites to
|
||||
// #715c7a on rose-pine-dawn's #d7827e, i.e. 2.10:1; through
|
||||
// the selector it composites to #2b1a19 and 5.84:1.
|
||||
color: tabRoot.selected ? theme.accentGlyphColor : theme.textMuted
|
||||
// The 80% is NOT ours to drop: CastPicker.slint:93 spells the
|
||||
// count `#ffffffcc`, and that de-emphasis against the
|
||||
// full-strength label is the tab's whole visual hierarchy.
|
||||
// It does cost contrast, and the residue is stated rather
|
||||
// than hidden: on breeze-light the selector returns #ffffff
|
||||
// (its own accent-text), which at 80% over accent #1d99f3
|
||||
// composites to #d2ebfd = 2.47:1, under the 3:1 floor. That
|
||||
// is the ALPHA's doing, not the colour's — the same label at
|
||||
// full strength is 3.04:1 and clears. Fixing it means either
|
||||
// dropping upstream's alpha or giving de-emphasised
|
||||
// on-accent text its own selector; both are owner calls, and
|
||||
// neither is this round's approved deviation.
|
||||
opacity: tabRoot.selected ? 0.8 : 1.0
|
||||
font.pixelSize: theme.fontLegal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -18,11 +18,12 @@ Rectangle {
|
||||
|
||||
QbzTheme { id: theme }
|
||||
|
||||
// Bakes matching the LIVE tokens (TransportControls.slint's fav-ta:
|
||||
// hover -> Theme.text-primary, idle -> Theme.text-secondary). Hardcoded
|
||||
// bake names are dark-theme-only colours — see QbzIconButton's header.
|
||||
readonly property string tintStrong: theme.textPrimary.hslLightness > 0.5 ? "primary" : "black"
|
||||
readonly property string tintWeak: theme.textSecondary.hslLightness > 0.5 ? "secondary" : "muted"
|
||||
// TransportControls.slint's fav-ta: hover -> Theme.text-primary, idle ->
|
||||
// Theme.text-secondary. Both are runtime-tinted (see QbzIconButton's
|
||||
// header), so they ARE those tokens; the fill under them is
|
||||
// `surface-hover`/transparent, a theme surface.
|
||||
readonly property string tintStrong: "textPrimary"
|
||||
readonly property string tintWeak: "secondary"
|
||||
|
||||
width: 32
|
||||
height: 32
|
||||
|
||||
@@ -115,7 +115,7 @@ Rectangle {
|
||||
name: "sliders-horizontal"
|
||||
width: 16
|
||||
height: 16
|
||||
tintName: dsArea.containsMouse ? "primary" : "secondary"
|
||||
tintName: dsArea.containsMouse ? "textPrimary" : "secondary"
|
||||
}
|
||||
MouseArea {
|
||||
id: dsArea
|
||||
@@ -139,7 +139,7 @@ Rectangle {
|
||||
name: "x"
|
||||
width: 15
|
||||
height: 15
|
||||
tintName: closeArea.containsMouse ? "primary" : "secondary"
|
||||
tintName: closeArea.containsMouse ? "textPrimary" : "secondary"
|
||||
}
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
@@ -285,7 +285,7 @@ Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
tintName: QbzLyrics.showTranslation
|
||||
? "accent" : (trArea.containsMouse ? "primary" : "muted")
|
||||
? "accent" : (trArea.containsMouse ? "textPrimary" : "muted")
|
||||
}
|
||||
MouseArea {
|
||||
id: trArea
|
||||
|
||||
@@ -294,7 +294,7 @@ Rectangle {
|
||||
name: "ellipsis"
|
||||
width: 16
|
||||
height: 16
|
||||
tintName: menuArea.containsMouse ? "primary" : "muted"
|
||||
tintName: menuArea.containsMouse ? "textPrimary" : "muted"
|
||||
}
|
||||
MouseArea {
|
||||
id: menuArea
|
||||
@@ -516,7 +516,7 @@ Rectangle {
|
||||
width: 15
|
||||
height: 15
|
||||
anchors.centerIn: parent
|
||||
tintName: closeArea.containsMouse ? "primary" : "secondary"
|
||||
tintName: closeArea.containsMouse ? "textPrimary" : "secondary"
|
||||
}
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
@@ -631,7 +631,7 @@ Rectangle {
|
||||
name: root.currentRow && root.currentRow.isFavorite ? "heart-filled" : "heart"
|
||||
width: 17
|
||||
height: 17
|
||||
tintName: root.currentRow && root.currentRow.isFavorite ? "favorite" : (npFavArea.containsMouse ? "primary" : "muted")
|
||||
tintName: root.currentRow && root.currentRow.isFavorite ? "favorite" : (npFavArea.containsMouse ? "textPrimary" : "muted")
|
||||
}
|
||||
MouseArea {
|
||||
id: npFavArea
|
||||
|
||||
@@ -413,7 +413,7 @@ Rectangle {
|
||||
width: 16
|
||||
height: 16
|
||||
anchors.centerIn: parent
|
||||
tintName: navRow.lit ? "primary" : "secondary"
|
||||
tintName: navRow.lit ? "textPrimary" : "secondary"
|
||||
}
|
||||
}
|
||||
Text {
|
||||
@@ -552,7 +552,7 @@ Rectangle {
|
||||
width: 15
|
||||
height: 15
|
||||
anchors.centerIn: parent
|
||||
tintName: (searchBtnArea.containsMouse || root.searchOpen) ? "primary" : "muted"
|
||||
tintName: (searchBtnArea.containsMouse || root.searchOpen) ? "textPrimary" : "muted"
|
||||
}
|
||||
MouseArea {
|
||||
id: searchBtnArea
|
||||
@@ -583,7 +583,7 @@ Rectangle {
|
||||
width: 16
|
||||
height: 16
|
||||
anchors.centerIn: parent
|
||||
tintName: plusArea.containsMouse ? "primary" : "muted"
|
||||
tintName: plusArea.containsMouse ? "textPrimary" : "muted"
|
||||
}
|
||||
MouseArea {
|
||||
id: plusArea
|
||||
@@ -604,7 +604,7 @@ Rectangle {
|
||||
width: 15
|
||||
height: 15
|
||||
anchors.centerIn: parent
|
||||
tintName: sortBtnArea.containsMouse ? "primary" : "muted"
|
||||
tintName: sortBtnArea.containsMouse ? "textPrimary" : "muted"
|
||||
}
|
||||
MouseArea {
|
||||
id: sortBtnArea
|
||||
@@ -746,7 +746,7 @@ Rectangle {
|
||||
width: 15
|
||||
height: 15
|
||||
anchors.centerIn: parent
|
||||
tintName: collapseArea.containsMouse ? "primary" : "muted"
|
||||
tintName: collapseArea.containsMouse ? "textPrimary" : "muted"
|
||||
}
|
||||
MouseArea {
|
||||
id: collapseArea
|
||||
@@ -910,7 +910,7 @@ Rectangle {
|
||||
height: 15
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: Math.round((parent.height - height) / 2)
|
||||
tintName: (rowArea.containsMouse || isActive) ? "primary" : "muted"
|
||||
tintName: (rowArea.containsMouse || isActive) ? "textPrimary" : "muted"
|
||||
}
|
||||
}
|
||||
// Name (hidden in mini). A Row is a positioner
|
||||
@@ -962,7 +962,7 @@ Rectangle {
|
||||
width: 14
|
||||
height: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
tintName: rowArea.containsMouse ? "primary" : "muted"
|
||||
tintName: rowArea.containsMouse ? "textPrimary" : "muted"
|
||||
}
|
||||
}
|
||||
// Active-row redundant shape cue: 3px accent bar.
|
||||
|
||||
@@ -146,7 +146,9 @@ Item {
|
||||
name: root.bandOn ? "eye" : "eye-off"
|
||||
width: 15
|
||||
height: 15
|
||||
tintName: "primary"
|
||||
// On the #80000000 / #2effffff chip over the artwork —
|
||||
// theme-independent, so a fixed white glyph is correct.
|
||||
tintName: "white"
|
||||
}
|
||||
MouseArea {
|
||||
id: eyeHover
|
||||
|
||||
@@ -136,9 +136,9 @@ Column {
|
||||
}
|
||||
|
||||
// Close X — the Slint is a bare 32x32 touch area with an 18px
|
||||
// glyph, muted -> primary, with NO hover fill; QbzIconButton
|
||||
// always paints one and idles at `secondary`, so this one stays
|
||||
// hand-rolled to keep the port 1:1.
|
||||
// glyph, text-muted -> text-primary, with NO hover fill;
|
||||
// QbzIconButton always paints one and idles at `secondary`, so
|
||||
// this one stays hand-rolled to keep the port 1:1.
|
||||
Item {
|
||||
width: 32
|
||||
height: 32
|
||||
@@ -149,7 +149,7 @@ Column {
|
||||
width: 18
|
||||
height: 18
|
||||
anchors.centerIn: parent
|
||||
tintName: closeArea.containsMouse ? "primary" : "muted"
|
||||
tintName: closeArea.containsMouse ? "textPrimary" : "muted"
|
||||
}
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
|
||||
@@ -32,14 +32,22 @@ Row {
|
||||
|
||||
QbzTheme { id: theme }
|
||||
|
||||
// Bakes matching the LIVE tokens (see QbzIconButton's header: the icon
|
||||
// tints are frozen SVG colours, so a hardcoded name is a dark-theme-only
|
||||
// colour). `tintOnAccent` is the glyph sitting ON the filled play disc:
|
||||
// the disc is Theme.accent, so its glyph is Theme.accent-text — which is
|
||||
// near-white on some themes and near-black on others (qbz-theme picks it
|
||||
// for worst-case contrast against the accent), never a fixed "black".
|
||||
readonly property string tintStrong: theme.textPrimary.hslLightness > 0.5 ? "primary" : "black"
|
||||
readonly property string tintOnAccent: theme.accentText.hslLightness > 0.5 ? "primary" : "black"
|
||||
// Both are runtime-tinted tokens now (see QbzIconButton's header), so they
|
||||
// are the live colours rather than a two-value approximation of them.
|
||||
//
|
||||
// `tintOnAccent` is the glyph sitting ON the filled play disc, whose fill
|
||||
// is theme.accent (theme.accentHover under the cursor). It used to name
|
||||
// `accentText` directly; it goes through the shared on-accent selector
|
||||
// now. That is NOT a colour change on 34 of the 35 palettes — the
|
||||
// selector's first arm IS accent-text — the point is that PLAY, the
|
||||
// checkboxes, the genre pills and the circle actions all take one
|
||||
// decision. They did not before: CircleAction.slint:72 hardcodes #ffffff
|
||||
// for the same job while QbzCheckbox.slint:24 uses accent-text, and the
|
||||
// port inherited both spellings. See theme/QbzTheme.qml, "ON AN ACCENT
|
||||
// FILL", for why accent-text alone is not enough (rose-pine-dawn: 2.56:1)
|
||||
// and why the hover fill does not get a property of its own.
|
||||
readonly property string tintStrong: "textPrimary"
|
||||
readonly property string tintOnAccent: theme.accentGlyphTint
|
||||
|
||||
spacing: 2
|
||||
height: 44
|
||||
|
||||
Reference in new issue
Block a user