feat(slint): library/sidebar UX overhaul + theme switching + fixes

Shell & navigation:
- Three-state sidebar (open/mini/closed); collapse cycles via header button.
- Section nav as right-flyouts (sidebar) + compact header nav (closed) +
  full header tabs (nav-in-sidebar off), via a shared HeaderMenuOverlay.
- Collapsed-folder right-flyout popup (max 4 + scrollbar), Rust-populated.
- Thin auto-hiding sidebar scrollbar pinned to the edge (Flickable, drag-safe).
- Slack-style bezel: rounded inset content panel over surface-card chrome.
- Header restructure: nav buttons, window-centered search, app menu.

Themes & components:
- Live theme switching: Dark / OLED Black / Tokyo Night / System (std-widgets).
- SegmentedTabBar (Discovery-style) with count badges for Library + Local Library.
- MP3 quality-badge tier (format-first classification) across badges.
- QbzSelect (Group/Sort) trimmed 20px.

Behavior fixes:
- Artist + Local Library Folder cover art now reach the rendered (visible/
  grouped) models, not just the source set.
- Favorites Playlists/Labels random buttons.
- Playlist track click plays from the clicked track in visible order.
- Dropdown idle-close pauses while the cursor rests on the menu.

cc
This commit is contained in:
vicrodh committed 2026-05-30 15:40:35 -06:00
1 parent 201082d4a1
commit 682719cab0
27 files changed
+1495 -619

No files matched your search

+3 -2
View File
@@ -1,8 +1,9 @@
// Shell layout metrics.
export global Layout {
out property <length> sidebar-width: 280px;
out property <length> header-height: 48px;
// Max sidebar width (also the default). The drag handle clamps to this.
out property <length> sidebar-width: 260px;
out property <length> header-height: 42px;
out property <length> player-bar-height: 110px;
out property <length> hairline: 1px;
}
@@ -1,28 +1,69 @@
// Semantic color tokens — the contract every component consumes.
//
// Currently bound to the QBZ dark theme. When theme switching lands
// (later in M1), `Theme` becomes the surface that swaps palettes; the
// token names stay stable so components never change.
// `Theme` is the surface that swaps palettes: every token is conditional on
// ThemeState.mode (0 = Dark, 1 = OLED Black, 2 = Tokyo Night, 3 = System).
// The System mode sources colors from the std-widgets Palette, which follows
// the OS light/dark scheme. Token names stay stable so components never
// change. The fixed values mirror src/app.css.
import { Palette } from "palette.slint";
import { Palette as StdPalette } from "std-widgets.slint";
import { ThemeState } from "theme-state.slint";
export global Theme {
// Surfaces
out property <color> surface-main: Palette.black-0;
out property <color> surface-card: Palette.black-1;
out property <color> surface-elevated: Palette.black-2;
// Surfaces (--bg-primary / --bg-secondary / --bg-tertiary)
out property <color> surface-main:
ThemeState.mode == 1 ? #000000
: ThemeState.mode == 2 ? #1a1b26
: ThemeState.mode == 3 ? StdPalette.background
: Palette.black-0;
out property <color> surface-card:
ThemeState.mode == 1 ? #0a0a0a
: ThemeState.mode == 2 ? #16161e
: ThemeState.mode == 3 ? StdPalette.alternate-background
: Palette.black-1;
out property <color> surface-elevated:
ThemeState.mode == 1 ? #1a1a1a
: ThemeState.mode == 2 ? #1c1d29
: ThemeState.mode == 3 ? StdPalette.control-background
: Palette.black-2;
// Text
out property <color> text-primary: Palette.white;
out property <color> text-secondary: Palette.gray-c;
out property <color> text-muted: Palette.gray-8;
out property <color> text-disabled: Palette.gray-5;
// Text (Tokyo Night recolors; OLED keeps the default dark text; System
// derives muted tiers from the OS foreground via alpha)
out property <color> text-primary:
ThemeState.mode == 2 ? #a9b1d6
: ThemeState.mode == 3 ? StdPalette.foreground
: Palette.white;
out property <color> text-secondary:
ThemeState.mode == 2 ? #787c99
: ThemeState.mode == 3 ? StdPalette.foreground.with-alpha(0.78)
: Palette.gray-c;
out property <color> text-muted:
ThemeState.mode == 2 ? #545c7e
: ThemeState.mode == 3 ? StdPalette.foreground.with-alpha(0.55)
: Palette.gray-8;
out property <color> text-disabled:
ThemeState.mode == 2 ? #3d425e
: ThemeState.mode == 3 ? StdPalette.foreground.with-alpha(0.35)
: Palette.gray-5;
// Accent
out property <color> accent: Palette.blue;
out property <color> accent-hover: Palette.blue-hover;
out property <color> accent-pressed: Palette.blue-active;
out property <color> accent-text: Palette.white;
// Accent (--accent-primary / hover / active / btn-primary-text)
out property <color> accent:
ThemeState.mode == 2 ? #7aa2f7
: ThemeState.mode == 3 ? StdPalette.accent-background
: Palette.blue;
out property <color> accent-hover:
ThemeState.mode == 2 ? #7dcfff
: ThemeState.mode == 3 ? StdPalette.accent-background.brighter(0.15)
: Palette.blue-hover;
out property <color> accent-pressed:
ThemeState.mode == 2 ? #bb9af7
: ThemeState.mode == 3 ? StdPalette.accent-background.darker(0.15)
: Palette.blue-active;
out property <color> accent-text:
ThemeState.mode == 2 ? #1a1b26
: ThemeState.mode == 3 ? StdPalette.accent-foreground
: Palette.white;
// Favorite (heart) — the active "loved" state.
out property <color> favorite: Palette.danger;
@@ -0,0 +1,11 @@
// Active theme mode — the single switch that swaps the semantic palette.
// Kept in foundation (not state.slint) so semantic-colors.slint can read it
// without a circular import. Set from Settings > Appearance > Theme.
//
// 0 = Dark (default) 1 = OLED Black 2 = Tokyo Night 3 = System
//
// "System" (3) sources colors from the std-widgets Palette (OS light/dark).
// The Appearance dropdown lists only these ported themes.
export global ThemeState {
in-out property <int> mode: 0;
}