165 lines
5.6 KiB
QML
165 lines
5.6 KiB
QML
// ImmersiveSongCard — the minimal bottom-right metadata card (§6.7 of the
|
|
// 2026-08-02 immersive-port contract), port of
|
|
// crates/qbz-ui/ui/immersive/ImmersiveSongCard.slint:15-114 (divergence D6:
|
|
// built fresh — shell/SongCard.qml is the bar's INTERACTIVE meta-link card
|
|
// with AudioStamp; this twin is NON-interactive with the E chip).
|
|
//
|
|
// Mounted by ImmersiveView as layer 4: visible ONLY
|
|
// `viewMode==0 && mode==6 && npHasTrack` (trap 19), bottom-right 24px
|
|
// insets, and it does NOT fade with the auto-hide chrome
|
|
// (ImmersiveView.slint:1602-1605). Content-sized: the card hugs its content
|
|
// and the mount pins it bottom-right, so a wider card grows LEFTWARD.
|
|
//
|
|
// meta column (right-aligned): title 15px semibold capped 420px + elide
|
|
// (:33-45) + "E" explicit chip 16px (:47-60); subline album italic ·
|
|
// artist 12px, NEVER truncated (:63-84); QualityBadgeFull showIcon:true
|
|
// (:89-93).
|
|
// cover thumb (RIGHT): 72x72 radius 6, shadow 16/#00000080/4 (:97-105).
|
|
//
|
|
// EXPLICIT SOURCE: Slint reads NowPlayingState.explicit; QbzPlayer publishes
|
|
// NO np_explicit (player_bridge.rs:31-114). The queue document's `current`
|
|
// row carries the SAME datum (QueueTrack::parental_warning ->
|
|
// queue_qt.rs:47,194), so the chip reads QbzQueue.queueJson with the
|
|
// guarded try/catch + track-id guard of PlayerBar.qml's npFavorite/npSource
|
|
// (the id guard keeps a stale queue document from lending the PREVIOUS
|
|
// track's chip to this one).
|
|
|
|
import QtQuick
|
|
import QtQuick.Effects
|
|
import QtQuick.Window
|
|
import com.blitzfc.qbz
|
|
import "../controls"
|
|
import "../theme"
|
|
|
|
Item {
|
|
id: root
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
// No-shader renderer detection, verbatim from SpectrumBand.qml (effects
|
|
// draw NOTHING on the software/VNC path — the layer is dropped there and
|
|
// the plain shadow rect remains).
|
|
readonly property bool _noShaders: GraphicsInfo.api === GraphicsInfo.Software
|
|
|| GraphicsInfo.api === GraphicsInfo.Null
|
|
|
|
readonly property bool npExplicit: {
|
|
try {
|
|
var d = JSON.parse(QbzQueue.queueJson)
|
|
return !!(d && d.current && d.current.id === QbzPlayer.npTrackId
|
|
&& d.current.explicit)
|
|
} catch (e) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Content-sized (:18-19): meta column width + 12px spacing + 72px cover.
|
|
readonly property real metaW: Math.max(titleRow.implicitWidth,
|
|
subRow.implicitWidth,
|
|
badge.implicitWidth)
|
|
readonly property real metaH: titleRow.height + 3 + subRow.height
|
|
+ 3 + badge.implicitHeight
|
|
implicitWidth: metaW + 12 + 72
|
|
implicitHeight: Math.max(metaH, 72)
|
|
width: implicitWidth
|
|
height: implicitHeight
|
|
|
|
// --- Meta column (LEFT), every row right-aligned ----------------------
|
|
// Title + inline E chip (:30-61). Slint HorizontalLayout alignment: end.
|
|
Row {
|
|
id: titleRow
|
|
x: root.metaW - width
|
|
spacing: 6
|
|
height: Math.max(titleText.implicitHeight, 16)
|
|
Text {
|
|
id: titleText
|
|
// Capped + elided (:34); implicitWidth is the natural content
|
|
// width, so this is NOT a binding loop.
|
|
width: Math.min(implicitWidth, 420)
|
|
text: QbzPlayer.npTitle
|
|
color: theme.textPrimary
|
|
font.pixelSize: 15
|
|
font.weight: Font.DemiBold
|
|
elide: Text.ElideRight
|
|
horizontalAlignment: Text.AlignRight
|
|
}
|
|
// Explicit "E" chip — 1:1 with TrackRow's badge (:47-60).
|
|
Rectangle {
|
|
visible: root.npExplicit
|
|
width: visible ? 16 : 0
|
|
height: 16
|
|
radius: 3
|
|
color: theme.surfaceElevated
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "E"
|
|
color: theme.textMuted
|
|
font.pixelSize: 9
|
|
font.weight: Font.DemiBold
|
|
}
|
|
}
|
|
}
|
|
|
|
// Subline: album (italic) · artist — FULL, never truncated (:63-84).
|
|
Row {
|
|
id: subRow
|
|
x: root.metaW - width
|
|
y: titleRow.height + 3
|
|
spacing: 4
|
|
height: 14
|
|
Text {
|
|
visible: QbzPlayer.npAlbum !== ""
|
|
text: QbzPlayer.npAlbum
|
|
color: "#80ffffff"
|
|
font.pixelSize: 12
|
|
font.italic: true
|
|
}
|
|
Text {
|
|
visible: QbzPlayer.npAlbum !== "" && QbzPlayer.npArtist !== ""
|
|
text: "·"
|
|
color: "#66ffffff"
|
|
font.pixelSize: 12
|
|
}
|
|
Text {
|
|
visible: QbzPlayer.npArtist !== ""
|
|
text: QbzPlayer.npArtist
|
|
color: "#99ffffff"
|
|
font.pixelSize: 12
|
|
}
|
|
}
|
|
|
|
// Quality badge (full mode, icon shown), right-aligned (:86-94).
|
|
QualityBadgeFull {
|
|
id: badge
|
|
x: root.metaW - width
|
|
y: titleRow.height + 3 + subRow.height + 3
|
|
tier: QbzPlayer.npQualityTier
|
|
detail: QbzPlayer.npQualityDetail
|
|
showIcon: true
|
|
}
|
|
|
|
// --- Cover thumb (RIGHT) — 72x72 radius 6, shadow 16/#00000080/4 ------
|
|
// The drop shadow is a dark rounded rect blurred by a MultiEffect (the
|
|
// Qt twin of Slint's drop-shadow-*; no shaders -> plain offset rect).
|
|
Rectangle {
|
|
x: root.width - 72
|
|
y: 4
|
|
width: 72
|
|
height: 72
|
|
radius: 6
|
|
color: "#80000000"
|
|
layer.enabled: !root._noShaders
|
|
layer.effect: MultiEffect {
|
|
blurEnabled: true
|
|
blurMax: 16
|
|
blur: 1.0
|
|
}
|
|
}
|
|
RoundedImage {
|
|
x: root.width - 72
|
|
width: 72
|
|
height: 72
|
|
radius: 6
|
|
source: QbzPlayer.npArtworkPath
|
|
}
|
|
}
|