Files
qbz/crates/qbz-qt/qml/views/local/LocalArtistRow.qml
T
vicrodh 81c43a114e 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
2026-07-29 10:30:43 -06:00

114 lines
4.2 KiB
QML

// One row of the Artists master rail (LocalLibraryView.slint:609
// LocalArtistRow). 60px tall, radius 8, 48px ROUND avatar (cached photo or a
// user glyph), display name + "N albums · M tracks". Selected = accent fill
// with accent-text labels (that is the Slint, :615-660 — the accent fill is
// the master-list selection, not a pill).
import QtQuick
import com.blitzfc.qbz
import "../../controls"
import "../../theme"
Rectangle {
id: root
property var item: ({})
property bool selected: false
property string artSource: ""
/// The LocalLibraryView root (shared pulse + per-item artwork gate).
property var view: null
signal picked(string name)
/// Does this artist expect an avatar at all? (Whether one has ARRIVED is
/// the placeholder's business — it hands over on the paint, not the path.)
readonly property bool avatarWanted: root.view
? root.view.artWanted(root.item.artKey) : false
QbzTheme { id: theme }
height: 60
radius: 8
color: selected ? theme.accent
: rowArea.containsMouse ? theme.surfaceHover : "transparent"
Row {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
spacing: 12
Rectangle {
width: 48
height: 48
radius: 24
anchors.verticalCenter: parent.verticalCenter
color: theme.surfaceElevated
clip: true
QbzIcon {
// The designed empty state — shown exactly when there is no
// art AND the placeholder has retired, so the user is never
// left with a blank disc and never sees the glyph flash
// underneath a cover that is about to paint.
visible: root.artSource === "" && avatarSkel.retired
name: "user"
width: 22
height: 22
anchors.centerIn: parent
tintName: "muted"
}
RoundedImage {
id: avatarArt
anchors.fill: parent
source: root.artSource
radius: 24
}
// Round per-item placeholder — the avatar shape, not a square.
QbzSkeleton {
id: avatarSkel
variant: "circle"
anchors.fill: parent
pending: root.avatarWanted
coverReady: avatarArt.ready
phase: root.view ? root.view.skelPhase : false
settleMs: root.view ? root.view.artSettleMs : 0
settleHold: root.view ? root.view.artPulse : false
}
}
Column {
width: parent.width - 60
anchors.verticalCenter: parent.verticalCenter
spacing: 3
Text {
width: parent.width
text: root.item.displayName || root.item.name || ""
// Both labels sit on the selected row's `theme.accent` fill.
// Same story as views/local/FilterChip.qml: NOT a departure
// from locallibrary/LocalLibraryView.slint:653 + :660
// (`Theme.accent-text`), which is what the twin returns on 34
// of the 35 palettes — only a floor for rose-pine-dawn, where
// accent-text measures 2.56:1 on this accent.
color: root.selected ? theme.accentGlyphColor : theme.textPrimary
font.pixelSize: theme.fontBody
font.weight: root.selected ? theme.weightSemibold : theme.weightMedium
elide: Text.ElideRight
}
Text {
width: parent.width
text: (root.item.albumCount || 0) + " "
+ QbzSession.tr("albums", QbzSession.trRev) + " · "
+ (root.item.trackCount || 0) + " "
+ QbzSession.tr("tracks", QbzSession.trRev)
color: root.selected ? theme.accentGlyphColor : theme.textMuted
font.pixelSize: theme.fontLegal
elide: Text.ElideRight
}
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.picked(root.item.name)
}
}