fix(slint): flyouts/idle, header nav layout, toasts, settings, search
Flyouts & idle (main effort): - Header dropdowns now glue under the tab: open-menu resets from-sidebar + title (a stale sidebar flyout state was flinging the panel far away). - Idle-close now pauses while the cursor rests on the TRIGGER too (full header tabs gained the trigger-hovered logic the others already had). - Dropped the dropdown caret (saves width on small screens). Header nav layout: - Section nav moved INTO the left-controls cluster, after the three sacred buttons, so it never overlaps them. Full text tabs when nav-in-sidebar off and sidebar not closed; compact icons when closed — mutually exclusive (no more duplicate/cluttered header). - Search field gives up 60px (animated) when the header hosts the nav. Settings made functional: - Toast component mounted in AppShell (it was never rendered) + the in-app toasts toggle drives ToastState.enabled. - "Show album artwork in Local Library track list" now drives the Tracks/ Folders rows. Both rows un-flagged as unsupported. Collapsible search (ExpandableSearch, app-wide): - Smooth open AND close (width animates both ways; magnifier fades). - Reliable auto-focus on open (deferred via a one-shot timer). cc
This commit is contained in:
5 files changed
+128
-95
No files matched your search
@@ -20,6 +20,7 @@ import { FolderEditModal } from "../primitives/FolderEditModal.slint";
|
||||
import { LibFolderEditModal } from "../settings/LibFolderEditModal.slint";
|
||||
import { TagEditorModal } from "../primitives/TagEditorModal.slint";
|
||||
import { DragGhost } from "../primitives/DragGhost.slint";
|
||||
import { Toast } from "../primitives/Toast.slint";
|
||||
import { PlayerBar } from "PlayerBar.slint";
|
||||
import { QueueSidebar } from "QueueSidebar.slint";
|
||||
import { LyricsSidebar } from "LyricsSidebar.slint";
|
||||
@@ -322,4 +323,7 @@ export component AppShell inherits Rectangle {
|
||||
|
||||
// Floating track-drag ghost (follows the cursor, on top of all).
|
||||
DragGhost { }
|
||||
|
||||
// In-app toast notifications — bottom-center, above everything.
|
||||
Toast { }
|
||||
}
|
||||
@@ -24,6 +24,9 @@ component NavTab inherits Rectangle {
|
||||
callback clicked();
|
||||
// Fires when the pointer first enters the tab — drives hover-to-open.
|
||||
callback hovered();
|
||||
// Fires on hover enter/leave so the host can keep the menu open while the
|
||||
// pointer rests on the tab (idle-close must pause then).
|
||||
callback hover-changed(bool);
|
||||
|
||||
height: 30px;
|
||||
width: tab-row.preferred-width;
|
||||
@@ -33,7 +36,7 @@ component NavTab inherits Rectangle {
|
||||
|
||||
tab-row := HorizontalLayout {
|
||||
padding-left: root.show-icon ? 9px : 11px;
|
||||
padding-right: 7px;
|
||||
padding-right: 11px;
|
||||
spacing: 6px;
|
||||
if root.show-icon: VerticalLayout {
|
||||
alignment: center;
|
||||
@@ -44,9 +47,8 @@ component NavTab inherits Rectangle {
|
||||
tint: root.active ? Theme.text-primary : Theme.text-secondary;
|
||||
}
|
||||
}
|
||||
// An explicit row-height box plus vertical-alignment centers the
|
||||
// label optically against the glyph and caret — a bare Text box
|
||||
// rides high because of the font's descender space.
|
||||
// The dropdown caret was dropped (it ate width on small screens; the
|
||||
// dropdown nature is learned after first use).
|
||||
Text {
|
||||
text: root.label;
|
||||
height: root.height;
|
||||
@@ -55,16 +57,6 @@ component NavTab inherits Rectangle {
|
||||
font-size: 11px;
|
||||
font-weight: root.active ? Typography.semibold : Typography.regular;
|
||||
}
|
||||
// Caret marking the tab as a dropdown.
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/chevron-down.svg");
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
tint: root.active ? Theme.text-secondary : Theme.text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ta := TouchArea {
|
||||
@@ -73,6 +65,7 @@ component NavTab inherits Rectangle {
|
||||
root.clicked();
|
||||
}
|
||||
changed has-hover => {
|
||||
root.hover-changed(self.has-hover);
|
||||
if (self.has-hover) {
|
||||
root.hovered();
|
||||
}
|
||||
@@ -154,6 +147,11 @@ component NavTabWithMenu inherits Rectangle {
|
||||
function open-menu() {
|
||||
HeaderMenuState.open-index = root.tab-index;
|
||||
HeaderMenuState.anchor-x = root.absolute-position.x;
|
||||
// Header dropdown — NOT a sidebar flyout. Resetting these is what
|
||||
// glues the panel under the tab: a stale from-sidebar/anchor-y from a
|
||||
// previous sidebar flyout would otherwise fling it far away.
|
||||
HeaderMenuState.from-sidebar = false;
|
||||
HeaderMenuState.title = "";
|
||||
HeaderMenuState.items = root.items;
|
||||
HeaderMenuState.home-item = root.home-item;
|
||||
}
|
||||
@@ -171,6 +169,15 @@ component NavTabWithMenu inherits Rectangle {
|
||||
hovered => {
|
||||
root.open-menu();
|
||||
}
|
||||
// Pause the idle-close while the cursor rests on the tab; only the
|
||||
// active tab clears the flag (avoids a leave/enter race).
|
||||
hover-changed(h) => {
|
||||
if (h) {
|
||||
HeaderMenuState.trigger-hovered = true;
|
||||
} else if (root.active) {
|
||||
HeaderMenuState.trigger-hovered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,82 +308,22 @@ export component HeaderBar inherits Rectangle {
|
||||
|
||||
// Responsive breakpoints.
|
||||
property <bool> show-tab-icons: root.width >= 1140px;
|
||||
// 80% of the prior search width (was 224px / 320px).
|
||||
property <length> search-width: root.width < 960px ? 179px : 256px;
|
||||
// 80% of the prior search width (was 224px / 320px). When the section nav
|
||||
// is in the header (nav-in-sidebar off) it gives up 60px to make room.
|
||||
property <length> search-width:
|
||||
(root.width < 960px ? 179px : 256px) - (ShellState.nav-in-sidebar ? 0px : 60px);
|
||||
|
||||
// --- Tabs (anchored left) ----------------------------------------------
|
||||
tabs := HorizontalLayout {
|
||||
// xs (not md): the sidebar toggle is a bare icon with ~10px of
|
||||
// breathing room to the Discover tab; the window-edge margin is
|
||||
// matched to that so the toggle glyph sits symmetrically.
|
||||
x: Spacing.xs;
|
||||
y: (root.height - self.height) / 2;
|
||||
height: 30px;
|
||||
spacing: 2px;
|
||||
// Section nav lives in the header by default; when the user moves it
|
||||
// to the sidebar (ShellState.nav-in-sidebar) the header tabs hide.
|
||||
// The sidebar-collapse toggle no longer lives here — it moved next to
|
||||
// the back/forward nav buttons (NavButtons), present in every view.
|
||||
if !ShellState.nav-in-sidebar: HorizontalLayout {
|
||||
spacing: 2px;
|
||||
NavTabWithMenu {
|
||||
tab-index: 0;
|
||||
icon: @image-url("../assets/icons/compass.svg");
|
||||
label: @tr("Discover");
|
||||
active: true;
|
||||
show-icon: root.show-tab-icons;
|
||||
// Discover rows are text-only (no glyphs).
|
||||
items: [
|
||||
{ label: @tr("Home"), has-glyph: false, route: "discover-home" },
|
||||
{ label: @tr("Editor's Picks"), has-glyph: false, route: "discover-editorPicks" },
|
||||
{ label: @tr("For You"), has-glyph: false, route: "discover-forYou" },
|
||||
];
|
||||
// Row 0 = Home, the one working destination.
|
||||
home-item: 0;
|
||||
}
|
||||
NavTabWithMenu {
|
||||
tab-index: 1;
|
||||
icon: @image-url("../assets/icons/music-library-2.svg");
|
||||
label: @tr("Library");
|
||||
show-icon: root.show-tab-icons;
|
||||
items: [
|
||||
{ label: @tr("Tracks"), icon: @image-url("../assets/icons/music.svg"), has-glyph: true, route: "favorites-tracks" },
|
||||
{ label: @tr("Albums"), icon: @image-url("../assets/icons/disc.svg"), has-glyph: true, route: "favorites-albums" },
|
||||
{ label: @tr("Artists"), icon: @image-url("../assets/icons/user.svg"), has-glyph: true, route: "favorites-artists" },
|
||||
{ label: @tr("Playlists"), icon: @image-url("../assets/icons/list-music.svg"), has-glyph: true, route: "favorites-playlists" },
|
||||
{ label: @tr("Labels"), icon: @image-url("../assets/icons/disc-3.svg"), has-glyph: true, route: "favorites-labels" },
|
||||
];
|
||||
}
|
||||
NavTabWithMenu {
|
||||
tab-index: 2;
|
||||
icon: @image-url("../assets/icons/hard-drive.svg");
|
||||
label: @tr("Local Library");
|
||||
show-icon: root.show-tab-icons;
|
||||
items: [
|
||||
{ label: @tr("Albums"), icon: @image-url("../assets/icons/disc.svg"), has-glyph: true, route: "local-albums" },
|
||||
{ label: @tr("Artists"), icon: @image-url("../assets/icons/user.svg"), has-glyph: true, route: "local-artists" },
|
||||
{ label: @tr("Folders"), icon: @image-url("../assets/icons/folder.svg"), has-glyph: true, route: "local-folders" },
|
||||
{ label: @tr("Tracks"), icon: @image-url("../assets/icons/music.svg"), has-glyph: true, route: "local-tracks" },
|
||||
];
|
||||
}
|
||||
NavTabWithMenu {
|
||||
tab-index: 3;
|
||||
icon: @image-url("../assets/icons/cassette-tape.svg");
|
||||
label: @tr("My QBZ");
|
||||
show-icon: root.show-tab-icons;
|
||||
items: [
|
||||
{ label: @tr("Collections"), icon: @image-url("../assets/icons/library-big.svg"), has-glyph: true },
|
||||
{ label: @tr("Mixtapes"), icon: @image-url("../assets/icons/cassette-tape.svg"), has-glyph: true },
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
// The section nav now lives inside the left-controls cluster (after the
|
||||
// three sacred nav buttons), so it can never overlap them.
|
||||
|
||||
// --- Search (absolutely centered on the window) ------------------------
|
||||
Rectangle {
|
||||
x: (root.width - self.width) / 2;
|
||||
y: (root.height - self.height) / 2;
|
||||
width: root.search-width;
|
||||
// Smoothly animate the width change when the nav moves in/out of the
|
||||
// header (and the x recenters with it).
|
||||
animate width { duration: 220ms; easing: ease-in-out; }
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
border-width: 1px;
|
||||
@@ -453,6 +400,74 @@ export component HeaderBar inherits Rectangle {
|
||||
clicked => { NavState.request-forward(); }
|
||||
}
|
||||
}
|
||||
// Full section nav (text tabs) — header-nav mode (nav-in-sidebar OFF)
|
||||
// while the sidebar isn't fully closed. Sits AFTER the three sacred
|
||||
// buttons, so it never overlaps them. Mutually exclusive with the
|
||||
// compact icons below (which only show when fully closed).
|
||||
if !ShellState.nav-in-sidebar && ShellState.sidebar-state != 2: HorizontalLayout {
|
||||
spacing: 2px;
|
||||
Rectangle { width: 6px; }
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
NavTabWithMenu {
|
||||
tab-index: 0;
|
||||
icon: @image-url("../assets/icons/compass.svg");
|
||||
label: @tr("Discover");
|
||||
active: true;
|
||||
show-icon: root.show-tab-icons;
|
||||
home-item: 0;
|
||||
items: [
|
||||
{ label: @tr("Home"), has-glyph: false, route: "discover-home" },
|
||||
{ label: @tr("Editor's Picks"), has-glyph: false, route: "discover-editorPicks" },
|
||||
{ label: @tr("For You"), has-glyph: false, route: "discover-forYou" },
|
||||
];
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
NavTabWithMenu {
|
||||
tab-index: 1;
|
||||
icon: @image-url("../assets/icons/music-library-2.svg");
|
||||
label: @tr("Library");
|
||||
show-icon: root.show-tab-icons;
|
||||
items: [
|
||||
{ label: @tr("Tracks"), icon: @image-url("../assets/icons/music.svg"), has-glyph: true, route: "favorites-tracks" },
|
||||
{ label: @tr("Albums"), icon: @image-url("../assets/icons/disc.svg"), has-glyph: true, route: "favorites-albums" },
|
||||
{ label: @tr("Artists"), icon: @image-url("../assets/icons/user.svg"), has-glyph: true, route: "favorites-artists" },
|
||||
{ label: @tr("Playlists"), icon: @image-url("../assets/icons/list-music.svg"), has-glyph: true, route: "favorites-playlists" },
|
||||
{ label: @tr("Labels"), icon: @image-url("../assets/icons/disc-3.svg"), has-glyph: true, route: "favorites-labels" },
|
||||
];
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
NavTabWithMenu {
|
||||
tab-index: 2;
|
||||
icon: @image-url("../assets/icons/hard-drive.svg");
|
||||
label: @tr("Local Library");
|
||||
show-icon: root.show-tab-icons;
|
||||
items: [
|
||||
{ label: @tr("Albums"), icon: @image-url("../assets/icons/disc.svg"), has-glyph: true, route: "local-albums" },
|
||||
{ label: @tr("Artists"), icon: @image-url("../assets/icons/user.svg"), has-glyph: true, route: "local-artists" },
|
||||
{ label: @tr("Folders"), icon: @image-url("../assets/icons/folder.svg"), has-glyph: true, route: "local-folders" },
|
||||
{ label: @tr("Tracks"), icon: @image-url("../assets/icons/music.svg"), has-glyph: true, route: "local-tracks" },
|
||||
];
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
NavTabWithMenu {
|
||||
tab-index: 3;
|
||||
icon: @image-url("../assets/icons/cassette-tape.svg");
|
||||
label: @tr("My QBZ");
|
||||
show-icon: root.show-tab-icons;
|
||||
items: [
|
||||
{ label: @tr("Collections"), icon: @image-url("../assets/icons/library-big.svg"), has-glyph: true },
|
||||
{ label: @tr("Mixtapes"), icon: @image-url("../assets/icons/cassette-tape.svg"), has-glyph: true },
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Compact section nav — only while the sidebar is fully closed
|
||||
// (Cerrado), so Discover / Library / Local Library / My QBZ stay
|
||||
// reachable. Icon-only; each opens a titled downward dropdown.
|
||||
|
||||
Reference in new issue
Block a user