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

+24 -13
View File
@@ -664,20 +664,31 @@ fn navigate_search(
/// Apply a history entry — set the view and re-load entity pages. /// Apply a history entry — set the view and re-load entity pages.
/// Stable scroll-restore id for an entry's primary list container, matching /// Stable scroll-restore id for an entry's primary list container, matching
/// the `scope` strings the Slint scroll containers compare against. Returns /// the `restore-scope` strings the Slint scroll containers compare against.
/// `""` for views without a wired scroll memory (no container will match, so /// Returns `""` for views without a wired scroll memory (no container will
/// nothing restores). Keep in sync with the `restore-scope` checks in the /// match, so nothing restores). Tab/sub-page views carry the tab in the id so
/// `.slint` views. /// each tab keeps its own position. Keep in sync with the `.slint` views.
fn scope_for(entry: &nav::NavEntry) -> &'static str { fn scope_for(entry: &nav::NavEntry) -> String {
match entry { match entry {
nav::NavEntry::LocalLibrary { tab } => match tab.as_str() { // HomeView is one persistent Flickable shared by the Discover tabs;
"albums" => "ll:albums", // a single scope is enough (each tab entry stores its own scroll).
"tracks" => "ll:tracks", nav::NavEntry::Home | nav::NavEntry::Discover { .. } => "home".into(),
"folders" => "ll:folders", nav::NavEntry::Favorites { tab } => format!("fav:{tab}"),
"artists" => "ll:artists", nav::NavEntry::LocalLibrary { tab } => format!("ll:{tab}"),
_ => "", nav::NavEntry::DiscoverBrowse { .. } => "discover-browse".into(),
}, nav::NavEntry::Mix { .. } => "mix".into(),
_ => "", nav::NavEntry::Playlist(_) => "playlist".into(),
nav::NavEntry::PlaylistManager => "playlist-manager".into(),
nav::NavEntry::OfflineManager => "offline-manager".into(),
nav::NavEntry::Album(_) => "album".into(),
nav::NavEntry::LocalAlbum(_) => "local-album".into(),
nav::NavEntry::Artist(_) => "artist".into(),
nav::NavEntry::Settings => "settings".into(),
nav::NavEntry::Search(_) => "search".into(),
nav::NavEntry::Musician { .. } => "musician".into(),
nav::NavEntry::Label { .. } => "label".into(),
nav::NavEntry::LabelReleases { .. } => "label-releases".into(),
nav::NavEntry::Location { .. } => "location".into(),
} }
} }
@@ -136,6 +136,18 @@ export component AlbumPageView inherits Rectangle {
interactive: !DragState.active; interactive: !DragState.active;
viewport-width: self.width; viewport-width: self.width;
viewport-height: page.preferred-height; 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 == "album" && 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); }
// Artwork-tinted gradient behind the header; scrolls with content. // Artwork-tinted gradient behind the header; scrolls with content.
Rectangle { Rectangle {
+13 -1
View File
@@ -7,7 +7,7 @@
import { Theme } from "../foundation/semantic-colors.slint"; import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint"; import { Typography } from "../foundation/typography.slint";
import { LocalAlbumState, LocalAlbumActions, LocalAlbumVersion, NowPlayingState } from "../state.slint"; import { LocalAlbumState, LocalAlbumActions, LocalAlbumVersion, NowPlayingState, NavState } from "../state.slint";
import { NavButtons } from "../shell/NavButtons.slint"; import { NavButtons } from "../shell/NavButtons.slint";
import { CircleAction } from "../primitives/CircleAction.slint"; import { CircleAction } from "../primitives/CircleAction.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint";
@@ -320,6 +320,18 @@ export component LocalAlbumView inherits Rectangle {
padding-top: 12px; padding-top: 12px;
tracks-list := ListView { tracks-list := ListView {
vertical-scrollbar-policy: ScrollBarPolicy.always-off; 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 == "local-album" && 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 LocalAlbumState.tracks: TrackRow { for track[i] in LocalAlbumState.tracks: TrackRow {
track: track; track: track;
index: i; index: i;
@@ -130,6 +130,18 @@ export component ArtistPageView inherits Rectangle {
interactive: !DragState.active; interactive: !DragState.active;
viewport-width: self.width; viewport-width: self.width;
viewport-height: page.preferred-height; 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 == "artist" && 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); }
// Artwork-tinted gradient behind the header; scrolls with content. // Artwork-tinted gradient behind the header; scrolls with content.
Rectangle { Rectangle {
@@ -10,7 +10,7 @@
import { Theme } from "../foundation/semantic-colors.slint"; import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint"; import { Typography } from "../foundation/typography.slint";
import { DiscoverBrowseState, DiscoverBrowseActions, GenreFilterState, GenreFilterActions } from "../state.slint"; import { DiscoverBrowseState, DiscoverBrowseActions, GenreFilterState, GenreFilterActions, NavState } from "../state.slint";
import { AlbumCollectionView } from "AlbumCollectionView.slint"; import { AlbumCollectionView } from "AlbumCollectionView.slint";
import { GenreFilterPopup } from "GenreFilterPopup.slint"; import { GenreFilterPopup } from "GenreFilterPopup.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint"; import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
@@ -248,12 +248,24 @@ export component DiscoverBrowseView inherits Rectangle {
vertical-stretch: 1; vertical-stretch: 1;
viewport-width: self.width; viewport-width: self.width;
viewport-height: page.preferred-height; 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 == "discover-browse" && 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(); }
// Infinite scroll — when the viewport nears the bottom of the // Infinite scroll — when the viewport nears the bottom of the
// content (within 600px), pull the next page. Guarded by // content (within 600px), pull the next page. Guarded by
// has-more + the loading flags AND an empty search query, so a // has-more + the loading flags AND an empty search query, so a
// client-side search filter does not keep paging in the catalog. // client-side search filter does not keep paging in the catalog.
changed viewport-y => { changed viewport-y => {
NavState.report-scroll(self.viewport-y);
if (-self.viewport-y + self.height >= self.viewport-height - 600px if (-self.viewport-y + self.height >= self.viewport-height - 600px
&& DiscoverBrowseState.has-more && DiscoverBrowseState.has-more
&& !DiscoverBrowseState.loading-more && !DiscoverBrowseState.loading-more
@@ -254,6 +254,18 @@ export component HomeView inherits Rectangle {
height: 100%; height: 100%;
viewport-width: self.width; viewport-width: self.width;
viewport-height: home-content.preferred-height; viewport-height: home-content.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 == "home" && 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); }
home-content := VerticalLayout { home-content := VerticalLayout {
padding: 32px; padding: 32px;
@@ -20,6 +20,7 @@ import {
GenreFilterState, GenreFilterState,
GenreFilterActions, GenreFilterActions,
AlphaJump, AlphaJump,
NavState,
} from "../state.slint"; } from "../state.slint";
import { AlbumCollectionView } from "../discover/AlbumCollectionView.slint"; import { AlbumCollectionView } from "../discover/AlbumCollectionView.slint";
import { PlaylistCard } from "../discover/PlaylistCard.slint"; import { PlaylistCard } from "../discover/PlaylistCard.slint";
@@ -963,6 +964,18 @@ export component FavoritesView inherits Rectangle {
track-list := ListView { track-list := ListView {
vertical-stretch: 1; vertical-stretch: 1;
vertical-scrollbar-policy: ScrollBarPolicy.always-off; 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 == "fav:tracks" && 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 FavoritesState.tracks-visible: VerticalLayout { for track[i] in FavoritesState.tracks-visible: VerticalLayout {
spacing: 0px; spacing: 0px;
// Group header rows when grouping by album / artist. // Group header rows when grouping by album / artist.
@@ -1050,6 +1063,21 @@ export component FavoritesView inherits Rectangle {
interactive: !DragState.active; interactive: !DragState.active;
viewport-width: self.width; viewport-width: self.width;
viewport-height: other-page.preferred-height; viewport-height: other-page.preferred-height;
// Scroll-position restore on back/forward. One Flickable serves the
// albums/artists/playlists/labels tabs, so match the active tab's
// scope dynamically (see nav::scope_for → "fav:<tab>").
property <string> sr-armed: NavState.restore-scope;
function sr-restore() {
if (NavState.restore-scope == "fav:" + FavoritesState.active-tab
&& 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); }
other-page := VerticalLayout { other-page := VerticalLayout {
width: parent.width; width: parent.width;
+13 -1
View File
@@ -12,7 +12,7 @@
import { Theme } from "../foundation/semantic-colors.slint"; import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint"; import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint"; import { Radius } from "../foundation/radius.slint";
import { LabelState, LabelActions, TrackItem, DragState } from "../state.slint"; import { LabelState, LabelActions, TrackItem, DragState, NavState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint";
import { TrackRow } from "../primitives/TrackRow.slint"; import { TrackRow } from "../primitives/TrackRow.slint";
import { CircleAction } from "../primitives/CircleAction.slint"; import { CircleAction } from "../primitives/CircleAction.slint";
@@ -52,6 +52,18 @@ export component LabelPageView inherits Rectangle {
interactive: !DragState.active; interactive: !DragState.active;
viewport-width: self.width; viewport-width: self.width;
viewport-height: page.preferred-height; 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 == "label" && 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 { page := VerticalLayout {
width: parent.width; width: parent.width;
@@ -10,7 +10,7 @@
import { Theme } from "../foundation/semantic-colors.slint"; import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint"; import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint"; import { Radius } from "../foundation/radius.slint";
import { LabelState, LabelActions } from "../state.slint"; import { LabelState, LabelActions, NavState } from "../state.slint";
import { AlbumCollectionView } from "../discover/AlbumCollectionView.slint"; import { AlbumCollectionView } from "../discover/AlbumCollectionView.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint";
import { QbzSelect } from "../primitives/QbzSelect.slint"; import { QbzSelect } from "../primitives/QbzSelect.slint";
@@ -32,6 +32,18 @@ export component LabelReleasesView inherits Rectangle {
flick := Flickable { flick := Flickable {
viewport-width: self.width; viewport-width: self.width;
viewport-height: page.preferred-height; 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 == "label-releases" && 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 { page := VerticalLayout {
width: parent.width; width: parent.width;
@@ -7,7 +7,7 @@
import { Theme } from "../foundation/semantic-colors.slint"; import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint"; import { Typography } from "../foundation/typography.slint";
import { LocationViewState, LocationViewActions, LocationArtistItem } from "../state.slint"; import { LocationViewState, LocationViewActions, LocationArtistItem, NavState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint"; import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
import { ListScrollbar } from "../primitives/ListScrollbar.slint"; import { ListScrollbar } from "../primitives/ListScrollbar.slint";
@@ -87,6 +87,18 @@ export component ArtistsByLocationView inherits Rectangle {
flick := Flickable { flick := Flickable {
viewport-width: self.width; viewport-width: self.width;
viewport-height: page.preferred-height; 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 == "location" && 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 { page := VerticalLayout {
width: parent.width; width: parent.width;
+13 -1
View File
@@ -8,7 +8,7 @@
import { Theme } from "../foundation/semantic-colors.slint"; import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint"; import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint"; import { Radius } from "../foundation/radius.slint";
import { MixState, DragState } from "../state.slint"; import { MixState, DragState, NavState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint";
import { TrackRow } from "../primitives/TrackRow.slint"; import { TrackRow } from "../primitives/TrackRow.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint"; import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
@@ -71,6 +71,18 @@ export component MixView inherits Rectangle {
interactive: !DragState.active; interactive: !DragState.active;
viewport-width: self.width; viewport-width: self.width;
viewport-height: page.preferred-height; 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 == "mix" && 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 { page := VerticalLayout {
width: parent.width; width: parent.width;
@@ -12,6 +12,7 @@ import {
MusicianState, MusicianState,
MusicianActions, MusicianActions,
MusicianAppearanceItem, MusicianAppearanceItem,
NavState,
} from "../state.slint"; } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint"; import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
@@ -93,6 +94,18 @@ export component MusicianPageView inherits Rectangle {
flick := Flickable { flick := Flickable {
viewport-width: self.width; viewport-width: self.width;
viewport-height: page.preferred-height; 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 == "musician" && 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 { page := VerticalLayout {
width: parent.width; width: parent.width;
@@ -13,6 +13,7 @@ import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint"; import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint"; import { Radius } from "../foundation/radius.slint";
import { OfflineManagerState, OfflineManagerActions, OfflineRow, OfflineArtist } from "../state.slint"; import { OfflineManagerState, OfflineManagerActions, OfflineRow, OfflineArtist } from "../state.slint";
import { NavState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint";
import { QbzSelect } from "../primitives/QbzSelect.slint"; import { QbzSelect } from "../primitives/QbzSelect.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint"; import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
@@ -394,6 +395,18 @@ export component OfflineManagerView inherits Rectangle {
} }
} }
if !OfflineManagerState.loading && OfflineManagerState.rows.length > 0: ScrollView { if !OfflineManagerState.loading && OfflineManagerState.rows.length > 0: ScrollView {
// Scroll-position restore on back/forward (see nav::scope_for).
property <string> sr-armed: NavState.restore-scope;
function sr-restore() {
if (NavState.restore-scope == "offline-manager" && self.viewport-height > self.visible-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); }
VerticalLayout { VerticalLayout {
spacing: 2px; spacing: 2px;
for row in OfflineManagerState.rows: Rectangle { for row in OfflineManagerState.rows: Rectangle {
@@ -18,6 +18,7 @@ import {
PmFolderItem, PmFolderItem,
PmPlaylistItem, PmPlaylistItem,
PmTreeRow, PmTreeRow,
NavState,
} from "../state.slint"; } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint"; import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
@@ -1233,6 +1234,18 @@ export component PlaylistManagerView inherits Rectangle {
vertical-stretch: 1; vertical-stretch: 1;
viewport-width: self.width; viewport-width: self.width;
viewport-height: page.preferred-height; 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 { page := VerticalLayout {
width: parent.width; width: parent.width;
@@ -6,7 +6,7 @@
import { Theme } from "../foundation/semantic-colors.slint"; import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint"; import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.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 { QbzIcon } from "../primitives/QbzIcon.slint";
import { TrackRow } from "../primitives/TrackRow.slint"; import { TrackRow } from "../primitives/TrackRow.slint";
import { ExpandableSearch } from "../primitives/ExpandableSearch.slint"; import { ExpandableSearch } from "../primitives/ExpandableSearch.slint";
@@ -625,6 +625,18 @@ export component PlaylistView inherits Rectangle {
track-list := ListView { track-list := ListView {
vertical-stretch: 1; vertical-stretch: 1;
vertical-scrollbar-policy: ScrollBarPolicy.always-off; 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 { for track[i] in PlaylistState.tracks: TrackRow {
track: track; track: track;
index: i; index: i;
@@ -8,7 +8,7 @@
// per-type tabs show the full loaded set as a card grid or row list with // per-type tabs show the full loaded set as a card grid or row list with
// a load-more button. // a load-more button.
import { SearchState, SearchActions, TrackItem, SearchPlaylistItem, AlbumCardItem, SlimItem, DragState } from "../state.slint"; import { SearchState, SearchActions, TrackItem, SearchPlaylistItem, AlbumCardItem, SlimItem, DragState, NavState } from "../state.slint";
import { Theme } from "../foundation/semantic-colors.slint"; import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint"; import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint"; import { Radius } from "../foundation/radius.slint";
@@ -436,6 +436,18 @@ export component SearchResultsView inherits Rectangle {
vertical-stretch: 1; vertical-stretch: 1;
viewport-width: self.width; viewport-width: self.width;
viewport-height: body.preferred-height; viewport-height: body.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 == "search" && 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); }
body := VerticalLayout { body := VerticalLayout {
padding-left: 32px; padding-left: 32px;
@@ -165,6 +165,18 @@ export component SettingsView inherits Rectangle {
ScrollView { ScrollView {
horizontal-stretch: 1; horizontal-stretch: 1;
horizontal-scrollbar-policy: ScrollBarPolicy.always-off; horizontal-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 == "settings" && self.viewport-height > self.visible-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); }
VerticalLayout { VerticalLayout {
padding-left: 20px; padding-left: 20px;
padding-right: 40px; padding-right: 40px;