feat(shell): resizable playlist sidebar and queue/lyrics column (#771)

Both panels were fixed-width (240 / 300); playlist names elided on wide
monitors. Each panel's visible edge (the content pane's edge, 8px past the
panel — the bezel between them shares the panel colour) now carries a 5px
grab strip with a 2x28 notch, split cursor, and a one-shot accent pulse when
a drag lands on a snap point.

Rules live in `panel_resize.rs` (pure, unit-tested), the bridge applies them
live and persists on release (`ui_prefs` `sidebar_width` / `queue_panel_width`,
clamped on read):
- sidebar: 240..480 while open; below 200 it snaps to the mini rail, below 24
  it closes through the same state the header button uses (with the Large-NPB
  fallback); the mini rail reopens at 220 — a 20px hysteresis band so no
  pointer x can flip open<->mini on every move (the flapping seen in the
  2026-09-11 screencast). The stored width never drops below 240, so
  reopening restores the last open width. A state snap during a drag still
  animates: the bridge raises `sidebarSnapping` before the state change and
  Sidebar's width Behavior honours it while the drag otherwise disables it.
- column: 300..600; below 260 it closes queue and lyrics as their X would.
  With the queue in the column and the width parked at 600, the notch shows a
  steady larger glow toward the content; pushing 60px further opens the
  Listen List (queue-view) and the queue leaves the column, lyrics staying
  if open. A lyrics-only column never pushes through.

The Large-NPB dock is untouched: it is mounted with absolute x/width in
AppShell and reads nothing from the sidebar width.

Verified: panel_resize 9/9, qmllint + the six qt-run.sh audits, release
builds v1-v4, four owner smoke rounds.

cc
This commit is contained in:
vicrodh committed 2026-09-11 13:09:47 -06:00
1 parent f9795e3c18
commit dc158bd9b6
8 files changed
+536 -2

No files matched your search

+11 -1
View File
@@ -486,9 +486,14 @@ Rectangle {
}
}
// #771: the open width is the user's (240..480, persisted); the other
// two states keep their fixed rails.
width: QbzShell.sidebarState === 2 ? 0
: QbzShell.sidebarState === 1 ? theme.sidebarMiniWidth
: theme.sidebarOpenWidth
: QbzShell.sidebarWidth
/// Set by AppShell while its resize handle is pressed: the width follows
/// the pointer directly, the state snaps still animate.
property bool resizing: false
// surface-card @ 0.5 while the ambient background is active (phase 14).
color: ambientOn ? theme.surfaceCardA50 : theme.surfaceCard
readonly property bool ambientOn: theme.ambientOn
@@ -497,7 +502,12 @@ Rectangle {
// animates (same as the Slint root).
clip: true
// While the pointer drives the width the Behavior is off (it would lag
// the mouse); a STATE snap during that drag (open <-> mini <-> closed)
// still animates, because the bridge raises `sidebarSnapping` right
// before it changes the state and lowers it on the next pointer-follow.
Behavior on width {
enabled: !root.resizing || QbzShell.sidebarSnapping
NumberAnimation { duration: 160; easing.type: Easing.InOutQuad }
}