fix(ui): finish pending Qt interaction polish
Keep segmented controls within their visual well, use the active theme for playback indicators, disconnect inactive pulse receivers and improve near-fit song metadata. cx
This commit is contained in:
8 files changed
+55
-23
No files matched your search
@@ -18,13 +18,11 @@
|
|||||||
// views/local/LocalFolderDetail.qml (grid/list). The old
|
// views/local/LocalFolderDetail.qml (grid/list). The old
|
||||||
// views/local/LocalSegToggle.qml is GONE — this file is the only copy.
|
// 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
|
// SIZING. The well derives its width from the real segment row. With the
|
||||||
// call site with more than two segments MUST widen it: with the DEFAULT metrics
|
// DEFAULT metrics that is 26px cells, 2px gaps and 3px of well on each side:
|
||||||
// the segments are a centred Row of 26px cells with 2px gaps, so N segments
|
// 2 segments -> 60px, 3 -> 88px. A caller may still override `width` for a
|
||||||
// need 28N - 2 px (2 -> 54, fits the 60 default; 3 -> 82, hence the detail
|
// roomier reference metric, but adding a segment can no longer leave its
|
||||||
// view's 90). Too narrow does not wrap or elide — `clip: true` silently cuts
|
// button visibly outside a stale 60px background.
|
||||||
// the last segment off, and a segment you cannot see is a segment you cannot
|
|
||||||
// click.
|
|
||||||
//
|
//
|
||||||
// ── TWO METRIC SETS, AND WHY THE CELL LOOK IS PARAMETERISED ────────────────
|
// ── TWO METRIC SETS, AND WHY THE CELL LOOK IS PARAMETERISED ────────────────
|
||||||
// The DEFAULTS below are the Local Library set, unchanged: 26x24 r4 cells with
|
// The DEFAULTS below are the Local Library set, unchanged: 26x24 r4 cells with
|
||||||
@@ -92,7 +90,12 @@ Rectangle {
|
|||||||
|
|
||||||
QbzTheme { id: theme }
|
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
|
height: 30
|
||||||
radius: 6
|
radius: 6
|
||||||
// The well itself: surface-elevated @ 0.5 under the dynamic background
|
// The well itself: surface-elevated @ 0.5 under the dynamic background
|
||||||
|
|||||||
@@ -206,7 +206,6 @@ Item {
|
|||||||
// Track info (shared, homologated across the panels, :173-176).
|
// Track info (shared, homologated across the panels, :173-176).
|
||||||
ImmersiveTrackMeta {
|
ImmersiveTrackMeta {
|
||||||
width: root.width
|
width: root.width
|
||||||
equalizerTint: "#7c3aed"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -354,7 +354,6 @@ Item {
|
|||||||
// fan + metadata read as one balanced group (:276-283).
|
// fan + metadata read as one balanced group (:276-283).
|
||||||
ImmersiveTrackMeta {
|
ImmersiveTrackMeta {
|
||||||
width: root.width
|
width: root.width
|
||||||
equalizerTint: "#7c3aed"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,15 @@
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import com.blitzfc.qbz
|
import com.blitzfc.qbz
|
||||||
|
import "../theme"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/// Bar color — every immersive mount passes #7c3aed.
|
QbzTheme { id: theme }
|
||||||
property color tint: "#7c3aed"
|
|
||||||
|
/// Bar color follows the active theme unless a host deliberately overrides it.
|
||||||
|
property color tint: theme.accent
|
||||||
|
|
||||||
width: 18
|
width: 18
|
||||||
height: 14
|
height: 14
|
||||||
@@ -43,8 +46,12 @@ Item {
|
|||||||
// (180 Hz on the owner's panel) and every update is a whole-window
|
// (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
|
// 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
|
// ~24 times per loop — indistinguishable. Riding the pulse also means
|
||||||
// ZERO extra presents: the bars move in the same frame as everything
|
// ZERO extra clocks: the bars move in the same frame as everything else
|
||||||
// else the pulse moves.
|
// 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
|
// The `active` gate belongs to the HOST, because this component mounts
|
||||||
// in always-alive-but-invisible trees (the immersive overlay) where a
|
// in always-alive-but-invisible trees (the immersive overlay) where a
|
||||||
@@ -58,10 +65,9 @@ Item {
|
|||||||
property real t: 0
|
property real t: 0
|
||||||
readonly property int _tickMs: 33
|
readonly property int _tickMs: 33
|
||||||
Connections {
|
Connections {
|
||||||
target: QbzShell
|
target: root.active && root.visible ? QbzShell : null
|
||||||
function onPulseMsChanged() {
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,15 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import com.blitzfc.qbz
|
import com.blitzfc.qbz
|
||||||
import "../controls"
|
import "../controls"
|
||||||
|
import "../theme"
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/// Equalizer tint + "Now Playing" label color (Slint equalizer-tint).
|
QbzTheme { id: theme }
|
||||||
property color equalizerTint: "#7c3aed"
|
|
||||||
|
/// Equalizer tint + "Now Playing" label follow the active theme.
|
||||||
|
property color equalizerTint: theme.accent
|
||||||
|
|
||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
|
|||||||
@@ -541,7 +541,6 @@ Item {
|
|||||||
ImmersiveTrackMeta {
|
ImmersiveTrackMeta {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
width: parent.width - 44
|
width: parent.width - 44
|
||||||
equalizerTint: "#7c3aed"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ Item {
|
|||||||
// Track info (shared, homologated across the panels, :106-109).
|
// Track info (shared, homologated across the panels, :106-109).
|
||||||
ImmersiveTrackMeta {
|
ImmersiveTrackMeta {
|
||||||
width: root.width
|
width: root.width
|
||||||
equalizerTint: "#7c3aed"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,6 +245,11 @@ Rectangle {
|
|||||||
font: albumText.font
|
font: albumText.font
|
||||||
text: albumText.text
|
text: albumText.text
|
||||||
}
|
}
|
||||||
|
TextMetrics {
|
||||||
|
id: ellipsisMetrics
|
||||||
|
font: artistText.font
|
||||||
|
text: "…"
|
||||||
|
}
|
||||||
|
|
||||||
// Fixed furniture: the context box (+ its 7px gap when shown)
|
// Fixed furniture: the context box (+ its 7px gap when shown)
|
||||||
// and the separator dot with a 7px gap on each side.
|
// 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
|
// a long album elides before the artist does, and both keep a
|
||||||
// readable slice instead of one pushing the other off the card.
|
// readable slice instead of one pushing the other off the card.
|
||||||
readonly property var budget: {
|
readonly property var budget: {
|
||||||
var a = artistMetrics.width
|
// `width` is the painted bounding box; `advanceWidth` is
|
||||||
var b = albumMetrics.width
|
// 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
|
var av = metaRow.avail
|
||||||
if (a + b <= av)
|
if (a + b <= av)
|
||||||
return [a, b]
|
return [a, b]
|
||||||
@@ -269,6 +281,18 @@ Rectangle {
|
|||||||
qb = b
|
qb = b
|
||||||
qa = av - 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)]
|
return [Math.floor(qa), Math.floor(qb)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user