feat(qbz-qt): the quality stamp, ported from Slint per bar mode
The port had one hand-made badge taken from the archived Tauri tree. Slint has three distinct components and each bar mounts a specific one, so the wrong tree had cost the stamp its LEDs entirely — they exist only in Slint, derived from the output backend and mode, and no amount of polishing the Svelte port would have produced them. QualityBadge becomes Slint's icon-only mark with its hover tooltip, for cards and rows. QualityBadgeFull is the icon plus stacked tier and detail. AudioStamp is the inline two-row form that carries the LEDs: backend blue when engaged, mode green, purple for a cast session, amber with the renderer name for a peer Qobuz Connect. New and Classic mount the SongCard stamp column, Large and Small the inline stamp, matching the .slint mount sites. The downgrade case follows Slint too: row one reports what is actually being delivered with an amber arrow, and the catalog maximum moves into the tooltip next to the reason it was limited. PlayerBar's LEDs stop reading the settings document, which never knew whether a backend was engaged, and read the now-playing output state instead — where Slint reads them. cc
This commit is contained in:
9 files changed
+876
-445
No files matched your search
@@ -0,0 +1,136 @@
|
||||
// QualityBadgeFull — 1:1 port of
|
||||
// crates/qbz-ui/ui/primitives/QualityBadgeFull.slint.
|
||||
//
|
||||
// The contained pill: format icon + the tier label stacked over the exact
|
||||
// bit-depth / sample-rate line. This is what the New and Classic now-playing
|
||||
// bars mount (inside the song card's audio stamp) and what album/track rows
|
||||
// use to advertise stored quality. The Small and Large bars mount
|
||||
// shell/AudioStamp.qml instead — a different component entirely.
|
||||
//
|
||||
// Numbers, read off the .slint (sf = scaleFactor):
|
||||
// height (compact 29 | 36) * sf
|
||||
// min width (compact 108 | 136) * sf, or content width when `bare`
|
||||
// radius 4, surface-hover fill, 1px border-subtle (all dropped when `bare`)
|
||||
// padding (compact 8 | 10) * sf spacing (compact 5 | 6) * sf
|
||||
// icon hires (compact 19 | 24) * sf cd/mp3/lossless (compact 13 | 16) * sf
|
||||
// tier text (compact 7 | 8) * sf, weight 100, letter-spacing 0.5, secondary
|
||||
// detail (compact 8 | 9) * sf, weight 100, muted
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
QbzTheme { id: theme }
|
||||
|
||||
/// "hires" | "cd" | "mp3" | "lossless" | "".
|
||||
property string tier: ""
|
||||
/// Exact quality, e.g. "24-bit / 96 kHz".
|
||||
property string detail: ""
|
||||
/// Show the format mark. The track-row variant turns it off.
|
||||
property bool showIcon: true
|
||||
/// No chip background or border, text centred — blends into a track row.
|
||||
property bool bare: false
|
||||
/// ~20%-smaller variant for the now-playing stamp.
|
||||
property bool compact: false
|
||||
/// Extra proportional scale on top of `compact` (1.0 = none).
|
||||
property real scaleFactor: 1.0
|
||||
|
||||
readonly property string tierLabel: tier === "hires" ? "HI-RES"
|
||||
: (tier === "mp3" ? "MP3"
|
||||
: (tier === "lossless" ? "LOSSLESS" : "CD"))
|
||||
|
||||
readonly property bool isHiRes: tier === "hires"
|
||||
readonly property real sf: scaleFactor
|
||||
readonly property real padH: bare ? 0 : (compact ? 8 : 10) * sf
|
||||
readonly property real gap: bare ? 0 : (compact ? 5 : 6) * sf
|
||||
readonly property real iconSide: isHiRes ? (compact ? 19 : 24) * sf
|
||||
: (compact ? 13 : 16) * sf
|
||||
// The row's own width (no paddings) — the icon slot plus the text column.
|
||||
readonly property real innerWidth: (showIcon ? iconSide + gap : 0) + textCol.width
|
||||
|
||||
visible: tier !== ""
|
||||
implicitHeight: (compact ? 29 : 36) * sf
|
||||
implicitWidth: bare ? innerWidth
|
||||
: Math.max(innerWidth + 2 * padH, (compact ? 108 : 136) * sf)
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 4
|
||||
color: root.bare ? "transparent" : theme.surfaceHover
|
||||
border.width: root.bare ? 0 : 1
|
||||
border.color: theme.borderSubtle
|
||||
}
|
||||
|
||||
Row {
|
||||
id: content
|
||||
// `alignment: start` past the left padding; `alignment: center` when
|
||||
// bare (no padding to start from).
|
||||
x: root.bare ? Math.round((root.width - root.innerWidth) / 2) : root.padH
|
||||
height: parent.height
|
||||
spacing: root.showIcon ? root.gap : 0
|
||||
|
||||
// Format mark. The Hi-Res brand mark keeps its own colours; the CD /
|
||||
// MP3 / LOSSLESS marks render in the themed muted tier.
|
||||
Item {
|
||||
visible: root.showIcon
|
||||
width: root.showIcon ? root.iconSide : 0
|
||||
height: parent.height
|
||||
|
||||
Image {
|
||||
visible: root.isHiRes
|
||||
anchors.centerIn: parent
|
||||
source: "qrc:/qt/qml/com/blitzfc/qbz/qml/assets/hi-res.svg"
|
||||
width: root.iconSide
|
||||
height: root.iconSide
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize: Qt.size(Math.round(root.iconSide * 2), Math.round(root.iconSide * 2))
|
||||
}
|
||||
// Un-hydrated lossless reuses the neutral disc mark; the detail
|
||||
// line carries "FLAC".
|
||||
QbzIcon {
|
||||
visible: !root.isHiRes
|
||||
anchors.centerIn: parent
|
||||
name: root.tier === "mp3" ? "mp3" : "cd"
|
||||
tintName: "muted"
|
||||
width: root.iconSide
|
||||
height: root.iconSide
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: textCol
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 1
|
||||
// Explicit (not implicit) so the children may bind to it: a Text
|
||||
// with `width: parent.width` inside an implicitly-sized Column is
|
||||
// a binding loop. Both Texts are unwrapped, so `implicitWidth` is
|
||||
// the natural content width and does not depend on `width`.
|
||||
width: Math.max(tierText.implicitWidth, detailText.implicitWidth)
|
||||
|
||||
Text {
|
||||
id: tierText
|
||||
width: parent.width
|
||||
text: root.tierLabel
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: Math.max(1, Math.round((root.compact ? 7 : 8) * root.sf))
|
||||
font.weight: Font.Thin
|
||||
font.letterSpacing: 0.5
|
||||
horizontalAlignment: root.bare ? Text.AlignHCenter : Text.AlignLeft
|
||||
}
|
||||
Text {
|
||||
id: detailText
|
||||
width: parent.width
|
||||
text: root.detail
|
||||
color: theme.textMuted
|
||||
font.pixelSize: Math.max(1, Math.round((root.compact ? 8 : 9) * root.sf))
|
||||
font.weight: Font.Thin
|
||||
horizontalAlignment: root.bare ? Text.AlignHCenter : Text.AlignLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user