feat(slint): playlist detail polish, sort (incl. custom) + WIP track drag

Polish + consistency:
  - Compact playlist header (cover 150px; nav->header spacing
    homologated with AlbumView); description truncated to ~2 lines +
    "Read more" modal; custom-artwork buttons only on cover hover.
  - New Typography.section (18px) token for hero/section titles,
    applied across Album / Artist / Mix / Playlist + Home carousels.
  - Shorter track rows app-wide (50px row, 36px artwork, -1 fonts);
    +3px column-header/divider gap; wider track-number column.
  - Reusable ExpandableSearch (the JumpNavBar page-search pattern:
    magnifier -> opens left -> X to close) in the playlist action row.
  - Custom ListScrollbar (thicker, min thumb height, own gutter) since
    Slint's ListView scrollbar can't be styled.

Sort:
  - Default / Title / Artist / Album / Duration with asc/desc per
    field, plus Custom (manual order persisted in library.db with
    up/down reorder). Client-side search + sort combined.

WIP: track drag-and-drop onto sidebar playlists
  - DragState/DragActions globals, drag source on the shared TrackRow,
    follow-cursor DragGhost, sidebar playlist rows self-detect the drop
    via absolute-position, multi-select group drag, add_tracks_to_playlist
    on drop, Flickables freeze scroll during a drag.
  - KNOWN ISSUE: the ghost is offset from the cursor (the row
    absolute-position + pointer mapping is off, esp. inside Flickables).
    Diagnostic logging is left in (TrackRow debug() + a start log) to
    pin down the wrong term before the generic fix.

cc
This commit is contained in:
vicrodh committed 2026-05-22 18:26:55 -06:00
1 parent 85223b03ff
commit de826bf560
21 files changed
+1223 -65

No files matched your search

@@ -17,6 +17,7 @@ import {
ContentView,
NetworkSidebarState,
NetworkSidebarActions,
DragState,
} from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { TrackRow } from "../primitives/TrackRow.slint";
@@ -159,6 +160,9 @@ export component ArtistPageView inherits Rectangle {
// it can stay anchored on the right and fill the available height
// independently of the page scroll.
page-flickable := Flickable {
// Freeze scrolling while a track drag is in flight, so the
// dragged row (and thus the ghost position) doesn't drift.
interactive: !DragState.active;
viewport-width: self.width;
viewport-height: page.preferred-height;
@@ -290,7 +294,7 @@ export component ArtistPageView inherits Rectangle {
Text {
text: ArtistState.name;
color: Theme.text-primary;
font-size: Typography.title;
font-size: Typography.section;
font-weight: Typography.bold;
overflow: elide;
}
@@ -534,7 +538,7 @@ export component ArtistPageView inherits Rectangle {
for track[index] in ArtistState.top-tracks: TrackRow {
visible: root.top-tracks-expanded || index < root.top-tracks-preview;
height: self.visible ? 56px : 0px;
height: self.visible ? 50px : 0px;
track: track;
index: index;
// Artist Popular Tracks: number cell (no cover), with