feat(locallibrary): nav scaffolding for the Local Library Slint view
Greenfield Local Library port, slice 1 — the view now mounts and navigates
end to end (empty placeholder body; per-tab data lands next):
- state.slint: `ContentView::local-library` + `LocalLibraryState`
(active-tab / loading / load-error / per-category counts) +
`LocalLibraryActions` (select-tab, open-settings). Re-exported from
app.slint so the globals reach the Rust API.
- ui/locallibrary/LocalLibraryView.slint: header (title + stats + gear) +
4-tab bar (Albums / Artists / Folders / Tracks) + placeholder body.
- shell: AppShell mounts the view on `local-library`; HeaderBar's
Local Library menu items now carry routes (local-albums / -artists /
-folders / -tracks) instead of being inert.
- nav.rs: `NavEntry::LocalLibrary { tab }` — per-tab history entry,
mirroring Favorites / Discover (back/forward moves between tabs).
- src/local_library.rs: `LibTab` controller stub (route/tab-id parsing,
round-trip tested).
- main.rs: apply_entry arm + `navigate_local_library` + header-route
branch + in-view tab-bar (select-tab) and gear (open-settings ->
Settings; management lives under Settings > Local Library) wiring.
qbz-slint cargo check green. Built blind — needs a runtime smoke test
(./scripts/slint-dev.sh): click the Local Library header menu, confirm the
view shows and the 4 tabs switch.
cc
This commit is contained in:
8 files changed
+333
-6
No files matched your search
@@ -0,0 +1,123 @@
|
||||
// Local Library — the user's on-disk + Plex music, browsed inside QBZ.
|
||||
//
|
||||
// Greenfield Slint port. This shell renders the header stats + the four
|
||||
// browse tabs (Albums / Artists / Folders / Tracks). All folder management,
|
||||
// scan, maintenance, and danger-zone actions live under Settings > Local
|
||||
// Library, NOT in this view — the gear button routes there.
|
||||
//
|
||||
// The per-tab bodies are filled slice by slice (Albums first). For now each
|
||||
// tab shows an empty placeholder so the wiring is visible end to end.
|
||||
|
||||
import { Theme } from "../foundation/semantic-colors.slint";
|
||||
import { LocalLibraryState, LocalLibraryActions } from "../state.slint";
|
||||
|
||||
component TabButton inherits TouchArea {
|
||||
in property <string> label;
|
||||
in property <bool> active;
|
||||
height: 36px;
|
||||
horizontal-stretch: 0;
|
||||
Rectangle {
|
||||
border-radius: 18px;
|
||||
background: root.active
|
||||
? Theme.accent
|
||||
: (root.has-hover ? Theme.surface-hover : transparent);
|
||||
HorizontalLayout {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
Text {
|
||||
text: root.label;
|
||||
vertical-alignment: center;
|
||||
color: root.active ? Theme.accent-text : Theme.text-secondary;
|
||||
font-size: 14px;
|
||||
font-weight: root.active ? 700 : 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component LocalLibraryView inherits Rectangle {
|
||||
background: transparent;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 24px;
|
||||
spacing: 20px;
|
||||
|
||||
// ---- Header: title + stats + gear (-> Settings > Local Library) ----
|
||||
HorizontalLayout {
|
||||
spacing: 16px;
|
||||
VerticalLayout {
|
||||
horizontal-stretch: 1;
|
||||
spacing: 2px;
|
||||
Text {
|
||||
text: @tr("Local Library");
|
||||
color: Theme.text-primary;
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
}
|
||||
Text {
|
||||
text: @tr("{} albums · {} artists · {} tracks",
|
||||
LocalLibraryState.album-count,
|
||||
LocalLibraryState.artist-count,
|
||||
LocalLibraryState.track-count);
|
||||
color: Theme.text-muted;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
gear := TouchArea {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
clicked => { LocalLibraryActions.open-settings(); }
|
||||
Rectangle {
|
||||
border-radius: 10px;
|
||||
background: gear.has-hover ? Theme.surface-hover : transparent;
|
||||
Image {
|
||||
source: @image-url("../assets/icons/settings.svg");
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
colorize: Theme.text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Tab bar (Albums / Artists / Folders / Tracks) ----
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
alignment: start;
|
||||
for tab in [
|
||||
{ id: "albums", label: @tr("Albums") },
|
||||
{ id: "artists", label: @tr("Artists") },
|
||||
{ id: "folders", label: @tr("Folders") },
|
||||
{ id: "tracks", label: @tr("Tracks") },
|
||||
] : TabButton {
|
||||
label: tab.label;
|
||||
active: LocalLibraryState.active-tab == tab.id;
|
||||
clicked => { LocalLibraryActions.select-tab(tab.id); }
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Body (placeholder until each tab slice lands) ----
|
||||
Rectangle {
|
||||
vertical-stretch: 1;
|
||||
background: Theme.surface-card;
|
||||
border-radius: 12px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: @tr("Local Library port in progress");
|
||||
horizontal-alignment: center;
|
||||
color: Theme.text-secondary;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
text: @tr("The “{}” tab is coming next.", LocalLibraryState.active-tab);
|
||||
horizontal-alignment: center;
|
||||
color: Theme.text-muted;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user