feat(slint): extend scroll-position restore to all views

Wire the per-history-entry scroll memory (added for Local Library) into
every top-level view: Home/Discover, Album, Local Album, Artist, Musician,
Label, Label Releases, Location, Favorites, Mix, Playlist, Playlist Manager,
Offline Manager, Search, Settings.

Each view's primary scroll container reports its viewport-y and restores it
on back/forward via NavState.restore-scope (scope_for in main.rs maps each
entry to a stable id; tab views carry the tab, e.g. "fav:albums"). The
container restores once per arm, only after content lays out, then clears
the scope. A `changed sr-armed` trigger covers views that persist across
sub-tab navigation (Home's Discover tabs, Favorites' shared list, Playlist
A->B->A) where no fresh mount occurs.

ScrollView-based views (Offline Manager, Settings) use visible-height for
the overflow check; ListView/Flickable views use height. Favorites' shared
albums/artists/playlists/labels Flickable matches the active tab's scope
dynamically.

cc
This commit is contained in:
vicrodh committed 2026-06-08 15:53:40 -06:00
1 parent 641aa22c17
commit 46673b2077
17 files changed
+243 -21

No files matched your search

@@ -18,6 +18,7 @@ import {
PmFolderItem,
PmPlaylistItem,
PmTreeRow,
NavState,
} from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
@@ -1233,6 +1234,18 @@ export component PlaylistManagerView inherits Rectangle {
vertical-stretch: 1;
viewport-width: self.width;
viewport-height: page.preferred-height;
// Scroll-position restore on back/forward (see nav::scope_for).
property <string> sr-armed: NavState.restore-scope;
function sr-restore() {
if (NavState.restore-scope == "playlist-manager" && self.viewport-height > self.height) {
self.viewport-y = NavState.scroll-restore;
NavState.restore-scope = "";
}
}
init => { self.sr-restore(); }
changed sr-armed => { self.sr-restore(); }
changed viewport-height => { self.sr-restore(); }
changed viewport-y => { NavState.report-scroll(self.viewport-y); }
page := VerticalLayout {
width: parent.width;
@@ -6,7 +6,7 @@
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { PlaylistState, PlaylistActions, DragState } from "../state.slint";
import { PlaylistState, PlaylistActions, DragState, NavState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { TrackRow } from "../primitives/TrackRow.slint";
import { ExpandableSearch } from "../primitives/ExpandableSearch.slint";
@@ -625,6 +625,18 @@ export component PlaylistView inherits Rectangle {
track-list := ListView {
vertical-stretch: 1;
vertical-scrollbar-policy: ScrollBarPolicy.always-off;
// Scroll-position restore on back/forward (see nav::scope_for).
property <string> sr-armed: NavState.restore-scope;
function sr-restore() {
if (NavState.restore-scope == "playlist" && self.viewport-height > self.height) {
self.viewport-y = NavState.scroll-restore;
NavState.restore-scope = "";
}
}
init => { self.sr-restore(); }
changed sr-armed => { self.sr-restore(); }
changed viewport-height => { self.sr-restore(); }
changed viewport-y => { NavState.report-scroll(self.viewport-y); }
for track[i] in PlaylistState.tracks: TrackRow {
track: track;
index: i;