diff --git a/go.mod b/go.mod index 9c0e5a9..d81bfa9 100644 --- a/go.mod +++ b/go.mod @@ -44,4 +44,4 @@ require ( honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect ) -replace fyne.io/fyne/v2 v2.2.4 => github.com/dweymouth/fyne/v2 v2.2.5-0.20221227225053-ae75dfbeb567 +replace fyne.io/fyne/v2 v2.2.4 => github.com/dweymouth/fyne/v2 v2.2.5-0.20230110171955-98071d7f072f diff --git a/go.sum b/go.sum index f846f2b..11c7445 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7h github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dweymouth/fyne/v2 v2.2.5-0.20221227225053-ae75dfbeb567 h1:JEBJ5YN74ryKQNW1TgH0GjHkVpgmHMb1oYuz8ZShjY8= -github.com/dweymouth/fyne/v2 v2.2.5-0.20221227225053-ae75dfbeb567/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA= +github.com/dweymouth/fyne/v2 v2.2.5-0.20230110171955-98071d7f072f h1:Tgz5Tx14yQLkKl4JH5eGWnUpq9J8+cbLTuj8lfzgqG0= +github.com/dweymouth/fyne/v2 v2.2.5-0.20230110171955-98071d7f072f/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA= github.com/dweymouth/go-subsonic v0.0.0-20221214005741-bd8048fa1863 h1:bOWMpFJ9zY839T1EngQ5nxVCiMlatPtpGkg/yHK6szg= github.com/dweymouth/go-subsonic v0.0.0-20221214005741-bd8048fa1863/go.mod h1:fUez6NFiEJiQTZizZ1BThZr5GJXAbigzGYjEPNm4tdI= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index a68f5fe..6355aa0 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -52,6 +52,15 @@ func (a *AlbumPage) SetPlayAlbumCallback(cb func(string, int)) { a.OnPlayAlbum = cb } +func (a *AlbumPage) Save() SavedPage { + return &savedAlbumPage{ + albumID: a.albumID, + lm: a.lm, + im: a.im, + nav: a.nav, + } +} + func (a *AlbumPage) Route() Route { return AlbumRoute(a.albumID) } @@ -175,3 +184,14 @@ func (a *AlbumPageHeader) Update(album *subsonic.AlbumID3, im *backend.ImageMana func formatMiscLabelStr(a *subsonic.AlbumID3) string { return fmt.Sprintf("%d · %d tracks · %s", a.Year, a.SongCount, util.SecondsToTimeString(float64(a.Duration))) } + +type savedAlbumPage struct { + albumID string + lm *backend.LibraryManager + im *backend.ImageManager + nav func(Route) +} + +func (s *savedAlbumPage) Restore() Page { + return NewAlbumPage(s.albumID, s.lm, s.im, s.nav) +} diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 22d2dd0..b72d963 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -16,6 +16,7 @@ var _ fyne.Widget = (*AlbumsPage)(nil) type AlbumsPage struct { widget.BaseWidget + title string im *backend.ImageManager lm *backend.LibraryManager nav func(Route) @@ -52,9 +53,10 @@ func (s *selectWidget) MinSize() fyne.Size { func NewAlbumsPage(title string, sortOrder string, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *AlbumsPage { a := &AlbumsPage{ - lm: lm, - im: im, - nav: nav, + title: title, + lm: lm, + im: im, + nav: nav, } a.ExtendBaseWidget(a) @@ -65,7 +67,6 @@ func NewAlbumsPage(title string, sortOrder string, lm *backend.LibraryManager, i a.sortOrder = NewSelect(backend.AlbumSortOrders, nil) a.sortOrder.Selected = sortOrder a.sortOrder.OnChanged = a.onSortOrderChanged - sortVbox := container.NewVBox(layout.NewSpacer(), a.sortOrder, layout.NewSpacer()) iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected)) a.grid = widgets.NewAlbumGrid(iter, im, false /*showYear*/) a.grid.OnPlayAlbum = a.onPlayAlbum @@ -73,7 +74,14 @@ func NewAlbumsPage(title string, sortOrder string, lm *backend.LibraryManager, i a.grid.OnShowAlbumPage = a.onShowAlbumPage a.searcher = widgets.NewSearcher() a.searcher.OnSearched = a.OnSearched + a.createContainer() + + return a +} + +func (a *AlbumsPage) createContainer() { searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher.Entry, layout.NewSpacer()) + sortVbox := container.NewVBox(layout.NewSpacer(), a.sortOrder, layout.NewSpacer()) a.container = container.NewBorder( container.NewHBox(widgets.NewHSpace(9), a.titleDisp, sortVbox, layout.NewSpacer(), searchVbox, widgets.NewHSpace(15)), nil, @@ -81,6 +89,29 @@ func NewAlbumsPage(title string, sortOrder string, lm *backend.LibraryManager, i nil, a.grid, ) +} + +func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage { + a := &AlbumsPage{ + title: saved.title, + lm: saved.lm, + im: saved.im, + nav: saved.nav, + } + a.ExtendBaseWidget(a) + + a.titleDisp = widget.NewRichTextWithText(a.title) + a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ + SizeName: theme.SizeNameHeadingText, + } + a.sortOrder = NewSelect(backend.AlbumSortOrders, nil) + a.sortOrder.Selected = saved.sortOrder + a.sortOrder.OnChanged = a.onSortOrderChanged + a.grid = widgets.NewAlbumGridFromState(saved.gridState) + a.searcher = widgets.NewSearcher() + a.searcher.OnSearched = a.OnSearched + a.createContainer() + return a } @@ -114,6 +145,17 @@ func (a *AlbumsPage) Reload() { } } +func (a *AlbumsPage) Save() SavedPage { + return &savedAlbumsPage{ + title: a.title, + lm: a.lm, + im: a.im, + nav: a.nav, + sortOrder: a.sortOrder.Selected, + gridState: a.grid.SaveToState(), + } +} + func (a *AlbumsPage) doSearch(query string) { if a.searchGrid == nil { a.searchGrid = widgets.NewAlbumGrid(a.lm.SearchIter(query), a.im, false /*showYear*/) @@ -153,3 +195,16 @@ func (a *AlbumsPage) CreateRenderer() fyne.WidgetRenderer { a.ExtendBaseWidget(a) return widget.NewSimpleRenderer(a.container) } + +type savedAlbumsPage struct { + title string + lm *backend.LibraryManager + im *backend.ImageManager + nav func(Route) + sortOrder string + gridState widgets.AlbumGridState +} + +func (s *savedAlbumsPage) Restore() Page { + return restoreAlbumsPage(s) +} diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index 16aa4e1..c0b399c 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -57,6 +57,15 @@ func (a *ArtistPage) Reload() { a.loadAsync() } +func (a *ArtistPage) Save() SavedPage { + return &savedArtistPage{ + artistID: a.artistID, + sm: a.sm, + im: a.im, + nav: a.nav, + } +} + func (a *ArtistPage) onPlayAlbum(albumID string) { if a.OnPlayAlbum != nil { a.OnPlayAlbum(albumID, 0) @@ -88,3 +97,14 @@ func (a *ArtistPage) CreateRenderer() fyne.WidgetRenderer { a.ExtendBaseWidget(a) return widget.NewSimpleRenderer(a.container) } + +type savedArtistPage struct { + artistID string + sm *backend.ServerManager + im *backend.ImageManager + nav func(Route) +} + +func (s *savedArtistPage) Restore() Page { + return NewArtistPage(s.artistID, s.sm, s.im, s.nav) +} diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go index fc8625e..f2e1682 100644 --- a/ui/browsing/browsingpane.go +++ b/ui/browsing/browsingpane.go @@ -16,10 +16,15 @@ import ( type Page interface { fyne.CanvasObject + Save() SavedPage Reload() Route() Route } +type SavedPage interface { + Restore() Page +} + type CanPlayAlbum interface { SetPlayAlbumCallback(func(albumID string, startingTrack int)) } @@ -42,7 +47,7 @@ type BrowsingPane struct { forward *widget.Button back *widget.Button reload *widget.Button - history []Page + history []SavedPage historyIdx int pageContainer *fyne.Container @@ -67,8 +72,9 @@ func NewBrowsingPane(app *backend.App) *BrowsingPane { } func (b *BrowsingPane) SetPage(p Page) { - if b.doSetPage(p) { - b.addPageToHistory(p) + oldPage := b.curPage + if b.doSetPage(p) && oldPage != nil { + b.addPageToHistory(oldPage, true) } } @@ -99,9 +105,19 @@ func (b *BrowsingPane) onSongChange(song *subsonic.Child) { } } -func (b *BrowsingPane) addPageToHistory(p Page) { - b.history = b.history[:b.historyIdx] - b.history = append(b.history, p) +func (b *BrowsingPane) addPageToHistory(p Page, truncate bool) { + if truncate { + // allow garbage collection of pages that will be removed from the history + for i := b.historyIdx; i < len(b.history); i++ { + b.history[i] = nil + } + b.history = b.history[:b.historyIdx] + } + if b.historyIdx < len(b.history) { + b.history[b.historyIdx] = p.Save() + } else { + b.history = append(b.history, p.Save()) + } b.historyIdx++ } @@ -112,16 +128,17 @@ func (b *BrowsingPane) goHome() { } func (b *BrowsingPane) GoBack() { - if b.historyIdx > 1 { - b.historyIdx -= 1 - b.doSetPage(b.history[b.historyIdx-1]) + if b.historyIdx > 0 { + b.addPageToHistory(b.curPage, false) + b.historyIdx -= 2 + b.doSetPage(b.history[b.historyIdx].Restore()) } } func (b *BrowsingPane) GoForward() { - if b.historyIdx < len(b.history) { - b.historyIdx++ - b.doSetPage(b.history[b.historyIdx-1]) + if b.historyIdx < len(b.history)-1 { + b.addPageToHistory(b.curPage, false) + b.doSetPage(b.history[b.historyIdx].Restore()) } } diff --git a/ui/browsing/genrepage.go b/ui/browsing/genrepage.go index 6843ab3..fa179af 100644 --- a/ui/browsing/genrepage.go +++ b/ui/browsing/genrepage.go @@ -51,6 +51,12 @@ func NewGenrePage(genre string, lm *backend.LibraryManager, im *backend.ImageMan g.grid.OnShowAlbumPage = g.onShowAlbumPage g.searcher = widgets.NewSearcher() g.searcher.OnSearched = g.OnSearched + g.createContainer() + + return g +} + +func (g *GenrePage) createContainer() { searchVbox := container.NewVBox(layout.NewSpacer(), g.searcher.Entry, layout.NewSpacer()) g.container = container.NewBorder( container.NewHBox(widgets.NewHSpace(9), g.titleDisp, layout.NewSpacer(), searchVbox, widgets.NewHSpace(15)), @@ -59,6 +65,26 @@ func NewGenrePage(genre string, lm *backend.LibraryManager, im *backend.ImageMan nil, g.grid, ) +} + +func restoreGenrePage(saved *savedGenrePage) *GenrePage { + g := &GenrePage{ + genre: saved.genre, + lm: saved.lm, + im: saved.im, + nav: saved.nav, + } + g.ExtendBaseWidget(g) + + g.titleDisp = widget.NewRichTextWithText(g.genre) + g.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ + SizeName: theme.SizeNameHeadingText, + } + g.grid = widgets.NewAlbumGridFromState(saved.gridState) + g.searcher = widgets.NewSearcher() + g.searcher.OnSearched = g.OnSearched + g.createContainer() + return g } @@ -83,6 +109,16 @@ func (g *GenrePage) Reload() { } } +func (g *GenrePage) Save() SavedPage { + return &savedGenrePage{ + genre: g.genre, + lm: g.lm, + im: g.im, + nav: g.nav, + gridState: g.grid.SaveToState(), + } +} + func (a *GenrePage) onPlayAlbum(albumID string) { if a.OnPlayAlbum != nil { a.OnPlayAlbum(albumID, 0) @@ -125,3 +161,15 @@ func (g *GenrePage) doSearch(query string) { g.container.Objects[0] = g.searchGrid g.Refresh() } + +type savedGenrePage struct { + genre string + lm *backend.LibraryManager + im *backend.ImageManager + nav func(Route) + gridState widgets.AlbumGridState +} + +func (s *savedGenrePage) Restore() Page { + return restoreGenrePage(s) +} diff --git a/ui/widgets/albumgrid.go b/ui/widgets/albumgrid.go index 3faf2c7..64ecd0e 100644 --- a/ui/widgets/albumgrid.go +++ b/ui/widgets/albumgrid.go @@ -22,7 +22,12 @@ type AlbumIterator interface { type AlbumGrid struct { widget.BaseWidget - grid *widget.GridWrapList + AlbumGridState + + grid *widget.GridWrapList +} + +type AlbumGridState struct { albums []*subsonic.AlbumID3 iter AlbumIterator fetching bool @@ -33,16 +38,20 @@ type AlbumGrid struct { OnPlayAlbum func(string) OnShowAlbumPage func(string) OnShowArtistPage func(string) + + scrollPos float32 } var _ fyne.Widget = (*AlbumGrid)(nil) func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher, showYear bool) *AlbumGrid { ag := &AlbumGrid{ - albums: albums, - done: true, - imageFetcher: fetch, - showYear: showYear, + AlbumGridState: AlbumGridState{ + albums: albums, + done: true, + imageFetcher: fetch, + showYear: showYear, + }, } ag.ExtendBaseWidget(ag) ag.createGridWrapList() @@ -51,8 +60,10 @@ func NewFixedAlbumGrid(albums []*subsonic.AlbumID3, fetch ImageFetcher, showYear func NewAlbumGrid(iter AlbumIterator, fetch ImageFetcher, showYear bool) *AlbumGrid { ag := &AlbumGrid{ - iter: iter, - imageFetcher: fetch, + AlbumGridState: AlbumGridState{ + iter: iter, + imageFetcher: fetch, + }, } ag.ExtendBaseWidget(ag) @@ -63,6 +74,21 @@ func NewAlbumGrid(iter AlbumIterator, fetch ImageFetcher, showYear bool) *AlbumG return ag } +func (ag *AlbumGrid) SaveToState() AlbumGridState { + s := ag.AlbumGridState + s.scrollPos = ag.grid.GetScrollOffset() + return s +} + +func NewAlbumGridFromState(state AlbumGridState) *AlbumGrid { + ag := &AlbumGrid{AlbumGridState: state} + ag.ExtendBaseWidget(ag) + ag.createGridWrapList() + ag.Refresh() // needed to initialize the widget + ag.grid.ScrollToOffset(state.scrollPos) + return ag +} + func (ag *AlbumGrid) Clear() { ag.albums = nil ag.done = true