diff --git a/crates/qbz-qt/qml/controls/QbzSegToggle.qml b/crates/qbz-qt/qml/controls/QbzSegToggle.qml index 5c2ec7eea..0704d19f5 100644 --- a/crates/qbz-qt/qml/controls/QbzSegToggle.qml +++ b/crates/qbz-qt/qml/controls/QbzSegToggle.qml @@ -18,13 +18,11 @@ // views/local/LocalFolderDetail.qml (grid/list). The old // views/local/LocalSegToggle.qml is GONE — this file is the only copy. // -// SIZING. `width` is a plain assignment, not a binding on the content, so a -// call site with more than two segments MUST widen it: with the DEFAULT metrics -// the segments are a centred Row of 26px cells with 2px gaps, so N segments -// need 28N - 2 px (2 -> 54, fits the 60 default; 3 -> 82, hence the detail -// view's 90). Too narrow does not wrap or elide — `clip: true` silently cuts -// the last segment off, and a segment you cannot see is a segment you cannot -// click. +// SIZING. The well derives its width from the real segment row. With the +// DEFAULT metrics that is 26px cells, 2px gaps and 3px of well on each side: +// 2 segments -> 60px, 3 -> 88px. A caller may still override `width` for a +// roomier reference metric, but adding a segment can no longer leave its +// button visibly outside a stale 60px background. // // ── TWO METRIC SETS, AND WHY THE CELL LOOK IS PARAMETERISED ──────────────── // The DEFAULTS below are the Local Library set, unchanged: 26x24 r4 cells with @@ -92,7 +90,12 @@ Rectangle { QbzTheme { id: theme } - width: 60 + readonly property int contentWidth: root.segments.length > 0 + ? root.segments.length * root.segWidth + + (root.segments.length - 1) * root.segSpacing + : 0 + implicitWidth: Math.max(60, root.contentWidth + 6) + width: implicitWidth height: 30 radius: 6 // The well itself: surface-elevated @ 0.5 under the dynamic background diff --git a/crates/qbz-qt/qml/immersive/AlbumReactivePanel.qml b/crates/qbz-qt/qml/immersive/AlbumReactivePanel.qml index b028814d0..0e437cc41 100644 --- a/crates/qbz-qt/qml/immersive/AlbumReactivePanel.qml +++ b/crates/qbz-qt/qml/immersive/AlbumReactivePanel.qml @@ -206,7 +206,6 @@ Item { // Track info (shared, homologated across the panels, :173-176). ImmersiveTrackMeta { width: root.width - equalizerTint: "#7c3aed" } } } diff --git a/crates/qbz-qt/qml/immersive/CoverflowPanel.qml b/crates/qbz-qt/qml/immersive/CoverflowPanel.qml index 0ceef86c3..d5efd1962 100644 --- a/crates/qbz-qt/qml/immersive/CoverflowPanel.qml +++ b/crates/qbz-qt/qml/immersive/CoverflowPanel.qml @@ -354,7 +354,6 @@ Item { // fan + metadata read as one balanced group (:276-283). ImmersiveTrackMeta { width: root.width - equalizerTint: "#7c3aed" } } } diff --git a/crates/qbz-qt/qml/immersive/EqualizerBars.qml b/crates/qbz-qt/qml/immersive/EqualizerBars.qml index cc3ab7a8c..f9a6895c1 100644 --- a/crates/qbz-qt/qml/immersive/EqualizerBars.qml +++ b/crates/qbz-qt/qml/immersive/EqualizerBars.qml @@ -25,12 +25,15 @@ import QtQuick import com.blitzfc.qbz +import "../theme" Item { id: root - /// Bar color — every immersive mount passes #7c3aed. - property color tint: "#7c3aed" + QbzTheme { id: theme } + + /// Bar color follows the active theme unless a host deliberately overrides it. + property color tint: theme.accent width: 18 height: 14 @@ -43,8 +46,12 @@ Item { // (180 Hz on the owner's panel) and every update is a whole-window // present. The wave's period is 800 ms, so the 30 Hz pulse samples it // ~24 times per loop — indistinguishable. Riding the pulse also means - // ZERO extra presents: the bars move in the same frame as everything - // else the pulse moves. + // ZERO extra clocks: the bars move in the same frame as everything else + // the pulse moves. A pulse receiver still has CPU cost, though, so an + // inactive indicator disconnects structurally instead of waking a JS + // handler just to return. TrackRow also loads this object only for the + // one active, visible play cell; a long expanded box set therefore owns + // one receiver rather than one receiver per track. // // The `active` gate belongs to the HOST, because this component mounts // in always-alive-but-invisible trees (the immersive overlay) where a @@ -58,10 +65,9 @@ Item { property real t: 0 readonly property int _tickMs: 33 Connections { - target: QbzShell + target: root.active && root.visible ? QbzShell : null function onPulseMsChanged() { - if (root.active && root.visible) - root.t = (root.t + root._tickMs / 800.0) % 1.0 + root.t = (root.t + root._tickMs / 800.0) % 1.0 } } diff --git a/crates/qbz-qt/qml/immersive/ImmersiveTrackMeta.qml b/crates/qbz-qt/qml/immersive/ImmersiveTrackMeta.qml index 48c74c55b..929a1b57d 100644 --- a/crates/qbz-qt/qml/immersive/ImmersiveTrackMeta.qml +++ b/crates/qbz-qt/qml/immersive/ImmersiveTrackMeta.qml @@ -20,12 +20,15 @@ import QtQuick import com.blitzfc.qbz import "../controls" +import "../theme" Column { id: root - /// Equalizer tint + "Now Playing" label color (Slint equalizer-tint). - property color equalizerTint: "#7c3aed" + QbzTheme { id: theme } + + /// Equalizer tint + "Now Playing" label follow the active theme. + property color equalizerTint: theme.accent spacing: 6 diff --git a/crates/qbz-qt/qml/immersive/ImmersiveView.qml b/crates/qbz-qt/qml/immersive/ImmersiveView.qml index 66d259c12..cab00bcd0 100644 --- a/crates/qbz-qt/qml/immersive/ImmersiveView.qml +++ b/crates/qbz-qt/qml/immersive/ImmersiveView.qml @@ -541,7 +541,6 @@ Item { ImmersiveTrackMeta { anchors.horizontalCenter: parent.horizontalCenter width: parent.width - 44 - equalizerTint: "#7c3aed" } } } diff --git a/crates/qbz-qt/qml/immersive/StaticPanel.qml b/crates/qbz-qt/qml/immersive/StaticPanel.qml index 9e07cad11..2b2397d46 100644 --- a/crates/qbz-qt/qml/immersive/StaticPanel.qml +++ b/crates/qbz-qt/qml/immersive/StaticPanel.qml @@ -116,7 +116,6 @@ Item { // Track info (shared, homologated across the panels, :106-109). ImmersiveTrackMeta { width: root.width - equalizerTint: "#7c3aed" } } } diff --git a/crates/qbz-qt/qml/shell/SongCard.qml b/crates/qbz-qt/qml/shell/SongCard.qml index cba539933..4aaf05e99 100644 --- a/crates/qbz-qt/qml/shell/SongCard.qml +++ b/crates/qbz-qt/qml/shell/SongCard.qml @@ -245,6 +245,11 @@ Rectangle { font: albumText.font text: albumText.text } + TextMetrics { + id: ellipsisMetrics + font: artistText.font + text: "…" + } // Fixed furniture: the context box (+ its 7px gap when shown) // and the separator dot with a 7px gap on each side. @@ -255,8 +260,15 @@ Rectangle { // a long album elides before the artist does, and both keep a // readable slice instead of one pushing the other off the card. readonly property var budget: { - var a = artistMetrics.width - var b = albumMetrics.width + // `width` is the painted bounding box; `advanceWidth` is + // what the line layout actually consumes. Budget the + // larger one plus a 2px bearing/rounding guard — assigning + // the exact bounding width made "Metallica" render as + // "Metalli…" despite spare room. + var a = Math.ceil(Math.max(artistMetrics.width, + artistMetrics.advanceWidth)) + 2 + var b = Math.ceil(Math.max(albumMetrics.width, + albumMetrics.advanceWidth)) + 2 var av = metaRow.avail if (a + b <= av) return [a, b] @@ -269,6 +281,18 @@ Rectangle { qb = b qa = av - b } + // An ellipsis costs roughly the same runway as the final + // one or two glyphs. If a label is within that distance of + // fitting, finish the word and take the small deficit from + // the other, genuinely long label instead. + var near = Math.ceil(ellipsisMetrics.advanceWidth) + 3 + if (a > qa && a - qa <= near && av >= a) { + qa = a + qb = av - a + } else if (b > qb && b - qb <= near && av >= b) { + qb = b + qa = av - b + } return [Math.floor(qa), Math.floor(qb)] }