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
204 lines
7.2 KiB
QML
204 lines
7.2 KiB
QML
// Compact album row for the LIST arm of the Albums / Folders-flat / Artists
|
|
// surfaces (AlbumCollectionView's list mode, mounted by
|
|
// LocalLibraryView.slint:1255 with show-source / show-source-badge on).
|
|
//
|
|
// 56px: cover, title + artist, year, track count, quality mark, the source
|
|
// column and the trailing ⋯ overflow. Multi-select puts the shared checkbox
|
|
// in front of the cover, as the collection view does.
|
|
//
|
|
// The ⋯ menu is AlbumListRow.slint:381 minus its two Qobuz-only entries: the
|
|
// favourite heart (local albums are not catalog favourites) and "Block this
|
|
// album" (which the Slint itself hides for source local/plex, :435). What is
|
|
// left is the five entries Slint always shows — Open album, Play, Play next,
|
|
// Play later, Add to queue — and all five are wired. Right-clicking the row
|
|
// opens the same menu at the pointer.
|
|
|
|
import QtQuick
|
|
import com.blitzfc.qbz
|
|
import "../../controls"
|
|
import "../../theme"
|
|
|
|
Rectangle {
|
|
id: root
|
|
|
|
property var item: ({})
|
|
property string artSource: ""
|
|
/// The LocalLibraryView root — the shared skeleton pulse, the per-item
|
|
/// artwork gate and the settle bound all live there (never duplicated
|
|
/// per row, so the rule cannot drift between surfaces).
|
|
property var view: null
|
|
property bool showSource: true
|
|
property bool selectMode: false
|
|
property bool checked: false
|
|
signal opened()
|
|
signal playRequested()
|
|
signal enqueueRequested(string mode)
|
|
signal toggleSelect()
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
height: 56
|
|
radius: 6
|
|
color: rowArea.containsMouse ? theme.surfaceHover : "transparent"
|
|
|
|
MouseArea {
|
|
id: rowArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: function (mouse) {
|
|
if (mouse.button === Qt.RightButton) {
|
|
rowMenu.openAtCursor(rowArea, mouse.x, mouse.y)
|
|
return
|
|
}
|
|
if (root.selectMode) root.toggleSelect()
|
|
else root.opened()
|
|
}
|
|
onDoubleClicked: if (!root.selectMode) root.playRequested()
|
|
}
|
|
|
|
CardMenu {
|
|
id: rowMenu
|
|
menuWidth: 196
|
|
entries: [
|
|
{ "label": QbzSession.tr("Open album", QbzSession.trRev), "icon": "library-big", "action": "open" },
|
|
{ "label": QbzSession.tr("Play", QbzSession.trRev), "icon": "play-fill", "action": "play" },
|
|
{ "label": QbzSession.tr("Play next", QbzSession.trRev), "icon": "list-start", "action": "next" },
|
|
{ "label": QbzSession.tr("Play later", QbzSession.trRev), "icon": "list-plus", "action": "later" },
|
|
{ "label": QbzSession.tr("Add to queue", QbzSession.trRev), "icon": "list-end", "action": "queue" },
|
|
]
|
|
onPicked: function (a) {
|
|
if (a === "open") root.opened()
|
|
else if (a === "play") root.playRequested()
|
|
else root.enqueueRequested(a)
|
|
}
|
|
}
|
|
|
|
Row {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 10
|
|
anchors.rightMargin: 12
|
|
spacing: 12
|
|
|
|
Item {
|
|
visible: root.selectMode
|
|
width: visible ? 16 : 0
|
|
height: parent.height
|
|
SelectCheck {
|
|
anchors.centerIn: parent
|
|
on: root.checked
|
|
onToggled: root.toggleSelect()
|
|
}
|
|
}
|
|
Rectangle {
|
|
width: 40
|
|
height: 40
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
radius: 4
|
|
color: theme.surfaceElevated
|
|
clip: true
|
|
RoundedImage {
|
|
id: rowArt
|
|
anchors.fill: parent
|
|
source: root.artSource
|
|
radius: 4
|
|
}
|
|
// Per-item: hands over the instant the art is actually ON the
|
|
// canvas (`rowArt.ready`, not "the path landed"); settles out
|
|
// when the album simply has none (local artwork drops such keys).
|
|
QbzSkeleton {
|
|
variant: "art"
|
|
anchors.fill: parent
|
|
blockRadius: 4
|
|
pending: root.view ? root.view.artWanted(root.item.artKey) : false
|
|
coverReady: rowArt.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 - 40 - 70 - 90 - 92 - 32
|
|
- (root.showSource ? 34 : 0)
|
|
- (root.selectMode ? 28 : 0) - 6 * 12
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 2
|
|
Text {
|
|
width: parent.width
|
|
text: root.item.title || ""
|
|
color: theme.textPrimary
|
|
font.pixelSize: 14
|
|
font.weight: theme.weightMedium
|
|
elide: Text.ElideRight
|
|
}
|
|
Text {
|
|
width: parent.width
|
|
text: root.item.artist || ""
|
|
color: theme.textMuted
|
|
font.pixelSize: 12
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
Text {
|
|
width: 70
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: root.item.year || ""
|
|
color: theme.textMuted
|
|
font.pixelSize: 12
|
|
}
|
|
Text {
|
|
width: 90
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: (root.item.trackCount || 0) + " "
|
|
+ QbzSession.tr("tracks", QbzSession.trRev)
|
|
color: theme.textMuted
|
|
font.pixelSize: 12
|
|
}
|
|
Item {
|
|
width: 92
|
|
height: parent.height
|
|
QualityMini {
|
|
tier: root.item.qualityTier || ""
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
Item {
|
|
visible: root.showSource
|
|
width: visible ? 34 : 0
|
|
height: parent.height
|
|
QbzIcon {
|
|
visible: root.item.source === "local" || root.item.source === "plex"
|
|
|| root.item.source === "offline"
|
|
name: root.item.source === "offline" ? "cloud-download" : "hard-drive"
|
|
width: 14
|
|
height: 14
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
tintName: root.item.source === "plex" ? "accent" : "muted"
|
|
}
|
|
}
|
|
// Trailing ⋯ overflow (AlbumListRow.slint:360).
|
|
Rectangle {
|
|
width: 32
|
|
height: 32
|
|
radius: 6
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
color: moreArea.containsMouse ? theme.surfaceElevated : "transparent"
|
|
QbzIcon {
|
|
name: "ellipsis"
|
|
width: 18
|
|
height: 18
|
|
anchors.centerIn: parent
|
|
tintName: moreArea.containsMouse ? "textPrimary" : "muted"
|
|
}
|
|
MouseArea {
|
|
id: moreArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: function (mouse) { rowMenu.openAtCursor(moreArea, mouse.x, mouse.y) }
|
|
}
|
|
}
|
|
}
|
|
}
|