begin reuse of widgets across pages

This commit is contained in:
Drew Weymouth
2023-07-07 12:54:20 -07:00
parent a8d1380b48
commit c6c33ecfed
10 changed files with 186 additions and 67 deletions
+42 -17
View File
@@ -33,29 +33,32 @@ type AlbumPage struct {
}
type albumPageState struct {
albumID string
sort widgets.TracklistSort
cfg *backend.AlbumPageConfig
mp mediaprovider.MediaProvider
pm *backend.PlaybackManager
im *backend.ImageManager
contr *controller.Controller
albumID string
sort widgets.TracklistSort
cfg *backend.AlbumPageConfig
widgetCache *util.WidgetCache
mp mediaprovider.MediaProvider
pm *backend.PlaybackManager
im *backend.ImageManager
contr *controller.Controller
}
func NewAlbumPage(
albumID string,
cfg *backend.AlbumPageConfig,
cache *util.WidgetCache,
pm *backend.PlaybackManager,
mp mediaprovider.MediaProvider,
im *backend.ImageManager,
contr *controller.Controller,
) *AlbumPage {
return newAlbumPage(albumID, cfg, pm, mp, im, contr, widgets.TracklistSort{})
return newAlbumPage(albumID, cfg, cache, pm, mp, im, contr, widgets.TracklistSort{})
}
func newAlbumPage(
albumID string,
cfg *backend.AlbumPageConfig,
cache *util.WidgetCache,
pm *backend.PlaybackManager,
mp mediaprovider.MediaProvider,
im *backend.ImageManager,
@@ -64,16 +67,23 @@ func newAlbumPage(
) *AlbumPage {
a := &AlbumPage{
albumPageState: albumPageState{
albumID: albumID,
cfg: cfg,
pm: pm,
mp: mp,
im: im,
contr: contr,
albumID: albumID,
cfg: cfg,
widgetCache: cache,
pm: pm,
mp: mp,
im: im,
contr: contr,
},
}
a.ExtendBaseWidget(a)
a.header = NewAlbumPageHeader(a)
if h := a.widgetCache.Obtain(util.WidgetTypeAlbumPageHeader); h != nil {
a.header = h.(*AlbumPageHeader)
a.header.Clear()
} else {
a.header = NewAlbumPageHeader(a)
}
a.header.page = a
a.tracklist = widgets.NewTracklist(nil)
a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
a.tracklist.SetSorting(sort)
@@ -97,6 +107,7 @@ func (a *AlbumPage) CreateRenderer() fyne.WidgetRenderer {
func (a *AlbumPage) Save() SavedPage {
s := a.albumPageState
s.sort = a.tracklist.Sorting()
a.widgetCache.Release(util.WidgetTypeAlbumPageHeader, a.header)
return &s
}
@@ -134,7 +145,9 @@ func (a *AlbumPage) load() {
return
}
a.header.Update(album, a.im)
a.tracklist.ShowDiscNumber = album.Tracks[0].DiscNumber != album.Tracks[len(album.Tracks)-1].DiscNumber
a.tracklist.Options = widgets.TracklistOptions{
ShowDiscNumber: album.Tracks[0].DiscNumber != album.Tracks[len(album.Tracks)-1].DiscNumber,
}
a.tracks = album.Tracks
a.tracklist.SetTracks(album.Tracks)
a.tracklist.SetNowPlaying(a.nowPlayingID)
@@ -256,6 +269,18 @@ func (a *AlbumPageHeader) Update(album *mediaprovider.AlbumWithTracks, im *backe
}()
}
func (a *AlbumPageHeader) Clear() {
a.albumID = ""
a.coverID = ""
a.artistID = ""
a.titleLabel.Segments[0].(*widget.TextSegment).Text = ""
a.artistLabel.SetText("")
a.genreLabel.SetText("")
a.miscLabel.SetText("")
a.toggleFavButton.IsFavorited = false
a.cover.Image.Image = nil
}
func (a *AlbumPageHeader) toggleFavorited() {
params := mediaprovider.RatingFavoriteParameters{AlbumIDs: []string{a.albumID}}
a.page.mp.SetFavorite(params, a.toggleFavButton.IsFavorited)
@@ -283,5 +308,5 @@ func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string {
}
func (s *albumPageState) Restore() Page {
return newAlbumPage(s.albumID, s.cfg, s.pm, s.mp, s.im, s.contr, s.sort)
return newAlbumPage(s.albumID, s.cfg, s.widgetCache, s.pm, s.mp, s.im, s.contr, s.sort)
}
+40 -20
View File
@@ -28,11 +28,12 @@ type artistPageState struct {
activeView int
trackSort widgets.TracklistSort
cfg *backend.ArtistPageConfig
pm *backend.PlaybackManager
mp mediaprovider.MediaProvider
im *backend.ImageManager
contr *controller.Controller
widgetCache *util.WidgetCache
cfg *backend.ArtistPageConfig
pm *backend.PlaybackManager
mp mediaprovider.MediaProvider
im *backend.ImageManager
contr *controller.Controller
}
type ArtistPage struct {
@@ -49,27 +50,34 @@ type ArtistPage struct {
container *fyne.Container
}
func NewArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller) *ArtistPage {
func NewArtistPage(artistID string, cfg *backend.ArtistPageConfig, cache *util.WidgetCache, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller) *ArtistPage {
activeView := 0
if cfg.InitialView == "Top Tracks" {
activeView = 1
}
return newArtistPage(artistID, cfg, pm, mp, im, contr, activeView, widgets.TracklistSort{})
return newArtistPage(artistID, cfg, cache, pm, mp, im, contr, activeView, widgets.TracklistSort{})
}
func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller, activeView int, sort widgets.TracklistSort) *ArtistPage {
func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, cache *util.WidgetCache, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller, activeView int, sort widgets.TracklistSort) *ArtistPage {
a := &ArtistPage{artistPageState: artistPageState{
artistID: artistID,
cfg: cfg,
pm: pm,
mp: mp,
im: im,
contr: contr,
activeView: activeView,
trackSort: sort,
artistID: artistID,
cfg: cfg,
widgetCache: cache,
pm: pm,
mp: mp,
im: im,
contr: contr,
activeView: activeView,
trackSort: sort,
}}
a.ExtendBaseWidget(a)
a.header = NewArtistPageHeader(a)
if h := a.widgetCache.Obtain(util.WidgetTypeArtistPageHeader); h != nil {
a.header = h.(*ArtistPageHeader)
} else {
a.header = NewArtistPageHeader(a)
}
a.header.artistPage = a
a.header.Clear()
if img, ok := im.GetCachedArtistImage(artistID); ok {
a.header.artistImage.SetImage(img, true /*tappable*/)
}
@@ -116,6 +124,7 @@ func (a *ArtistPage) Save() SavedPage {
if a.tracklistCtr != nil {
s.trackSort = a.tracklistCtr.Objects[0].(*widgets.Tracklist).Sorting()
}
a.widgetCache.Release(util.WidgetTypeArtistPageHeader, a.header)
return &s
}
@@ -190,7 +199,7 @@ func (a *ArtistPage) showTopTracks() {
return
}
tl := widgets.NewTracklist(ts)
tl.AutoNumber = true
tl.Options = widgets.TracklistOptions{AutoNumber: true}
tl.SetVisibleColumns(a.cfg.TracklistColumns)
tl.SetSorting(a.trackSort)
tl.OnVisibleColumnsChanged = func(cols []string) {
@@ -228,9 +237,11 @@ func (a *ArtistPage) CreateRenderer() fyne.WidgetRenderer {
}
func (s *artistPageState) Restore() Page {
return newArtistPage(s.artistID, s.cfg, s.pm, s.mp, s.im, s.contr, s.activeView, s.trackSort)
return newArtistPage(s.artistID, s.cfg, s.widgetCache, s.pm, s.mp, s.im, s.contr, s.activeView, s.trackSort)
}
const artistBioNotAvailableStr = "Artist biography not available."
type ArtistPageHeader struct {
widget.BaseWidget
@@ -250,7 +261,7 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
a := &ArtistPageHeader{
artistPage: page,
titleDisp: widget.NewRichTextWithText(""),
biographyDisp: widget.NewRichTextWithText("Artist biography not available."),
biographyDisp: widget.NewRichTextWithText(artistBioNotAvailableStr),
similarArtists: container.NewHBox(),
}
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
@@ -273,6 +284,15 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
return a
}
func (a *ArtistPageHeader) Clear() {
a.artistID = ""
a.favoriteBtn.IsFavorited = false
a.titleDisp.Segments[0].(*widget.TextSegment).Text = ""
a.biographyDisp.Segments[0].(*widget.TextSegment).Text = artistBioNotAvailableStr
a.similarArtists.RemoveAll()
a.artistImage.SetImage(nil, false)
}
func (a *ArtistPageHeader) Update(artist *mediaprovider.ArtistWithAlbums) {
if artist == nil {
return
+6 -2
View File
@@ -233,17 +233,21 @@ func (b *BrowsingPane) GoHome() {
func (b *BrowsingPane) GoBack() {
if b.historyIdx > 0 {
// due to widget reuse between pages,
// we must create the new page before calling addPageToHistory
p := b.history[b.historyIdx-1].Restore()
b.addPageToHistory(b.curPage, false)
b.historyIdx -= 2
b.doSetPage(b.history[b.historyIdx].Restore())
b.doSetPage(p)
b.updateHistoryButtons()
}
}
func (b *BrowsingPane) GoForward() {
if b.historyIdx < len(b.history)-1 {
p := b.history[b.historyIdx+1].Restore()
b.addPageToHistory(b.curPage, false)
b.doSetPage(b.history[b.historyIdx].Restore())
b.doSetPage(p)
b.updateHistoryButtons()
}
}
+1 -1
View File
@@ -326,7 +326,7 @@ func (a *FavoritesPage) onShowFavoriteSongs() {
return
}
tracklist := widgets.NewTracklist(fav.Tracks)
tracklist.AutoNumber = true
tracklist.Options = widgets.TracklistOptions{AutoNumber: true}
tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
tracklist.SetSorting(a.trackSort)
tracklist.OnVisibleColumnsChanged = func(cols []string) {
+7 -5
View File
@@ -62,14 +62,16 @@ func NewNowPlayingPage(
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
a.conf.TracklistColumns = cols
}
a.tracklist.AutoNumber = true
a.tracklist.DisablePlaybackMenu = true
a.tracklist.Options = widgets.TracklistOptions{
AutoNumber: true,
DisablePlaybackMenu: true,
AuxiliaryMenuItems: []*fyne.MenuItem{
fyne.NewMenuItem("Remove from queue", a.onRemoveSelectedFromQueue),
},
}
contr.ConnectTracklistActions(a.tracklist)
// override the default OnPlayTrackAt handler b/c we don't need to re-load the tracks into the queue
a.tracklist.OnPlayTrackAt = a.onPlayTrackAt
a.tracklist.AuxiliaryMenuItems = []*fyne.MenuItem{
fyne.NewMenuItem("Remove from queue", a.onRemoveSelectedFromQueue),
}
a.title = widget.NewRichTextWithText("Now Playing")
a.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName
a.statusLabel = widget.NewRichTextWithText("Stopped")
+4 -2
View File
@@ -77,8 +77,10 @@ func newPlaylistPage(
fyne.NewMenuItem("Move down", a.onMoveSelectedDown),
fyne.NewMenuItem("Move to bottom", a.onMoveSelectedToBottom),
}...)
a.tracklist.AuxiliaryMenuItems = []*fyne.MenuItem{reorderMenu,
fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)}
a.tracklist.Options = widgets.TracklistOptions{
AuxiliaryMenuItems: []*fyne.MenuItem{reorderMenu,
fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)},
}
// connect tracklist actions
a.contr.ConnectTracklistActions(a.tracklist)
+11 -8
View File
@@ -3,6 +3,7 @@ package browsing
import (
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/ui/controller"
"github.com/dweymouth/supersonic/ui/util"
)
type NavigationHandler interface {
@@ -11,16 +12,18 @@ type NavigationHandler interface {
}
type Router struct {
App *backend.App
Controller *controller.Controller
Nav NavigationHandler
App *backend.App
Controller *controller.Controller
Nav NavigationHandler
widgetCache util.WidgetCache
}
func NewRouter(app *backend.App, controller *controller.Controller, nav NavigationHandler) Router {
r := Router{
App: app,
Controller: controller,
Nav: nav,
App: app,
Controller: controller,
Nav: nav,
widgetCache: util.NewWidgetCache(),
}
return r
}
@@ -28,11 +31,11 @@ func NewRouter(app *backend.App, controller *controller.Controller, nav Navigati
func (r Router) CreatePage(rte controller.Route) Page {
switch rte.Page {
case controller.Album:
return NewAlbumPage(rte.Arg, &r.App.Config.AlbumPage, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
return NewAlbumPage(rte.Arg, &r.App.Config.AlbumPage, &r.widgetCache, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
case controller.Albums:
return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
case controller.Artist:
return NewArtistPage(rte.Arg, &r.App.Config.ArtistPage, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
return NewArtistPage(rte.Arg, &r.App.Config.ArtistPage, &r.widgetCache, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
case controller.Artists:
return NewArtistsPage(r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
case controller.Favorites:
+5 -3
View File
@@ -44,8 +44,10 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig,
t.ExtendBaseWidget(t)
t.tracklist = widgets.NewTracklist(nil)
t.tracklist.DisableSorting = true
t.tracklist.AutoNumber = true
t.tracklist.Options = widgets.TracklistOptions{
DisableSorting: true,
AutoNumber: true,
}
t.tracklist.SetVisibleColumns(conf.TracklistColumns)
t.tracklist.OnVisibleColumnsChanged = func(cols []string) {
t.conf.TracklistColumns = cols
@@ -119,7 +121,7 @@ func (t *TracksPage) OnSearched(query string) {
func (t *TracksPage) doSearch(query string) {
if t.searchTracklist == nil {
t.searchTracklist = widgets.NewTracklist(nil)
t.searchTracklist.AutoNumber = true
t.searchTracklist.Options = widgets.TracklistOptions{AutoNumber: true}
t.searchTracklist.SetVisibleColumns(t.conf.TracklistColumns)
t.searchTracklist.SetNowPlaying(t.nowPlayingID)
t.searchTracklist.OnVisibleColumnsChanged = func(cols []string) {
+50
View File
@@ -0,0 +1,50 @@
package util
import (
"time"
"fyne.io/fyne/v2"
)
type WidgetType string
const (
WidgetTypeAlbumPageHeader WidgetType = "AlbumPageHeader"
WidgetTypeArtistPageHeader WidgetType = "ArtistPageHeader"
)
type WidgetCache struct {
cache map[WidgetType][]cachedWidget
}
type cachedWidget struct {
widget fyne.CanvasObject
releasedAt int64 // unixMillis
}
func NewWidgetCache() WidgetCache {
return WidgetCache{
cache: make(map[WidgetType][]cachedWidget),
}
}
func (w *WidgetCache) Obtain(typ WidgetType) fyne.CanvasObject {
var widget fyne.CanvasObject
if ws, ok := w.cache[typ]; ok && len(ws) > 0 {
i := len(ws) - 1
widget = ws[i].widget
ws[i].widget = nil
w.cache[typ] = ws[:i]
}
return widget
}
func (w *WidgetCache) Release(typ WidgetType, wid fyne.CanvasObject) {
if _, ok := w.cache[typ]; !ok {
w.cache[typ] = make([]cachedWidget, 0)
}
w.cache[typ] = append(w.cache[typ], cachedWidget{
widget: wid,
releasedAt: time.Now().UnixMilli(),
})
}
+20 -9
View File
@@ -47,9 +47,7 @@ type TracklistSort struct {
ColumnName string
}
type Tracklist struct {
widget.BaseWidget
type TracklistOptions struct {
// AutoNumber sets whether to auto-number the tracks 1..N in display order,
// or to use the number from the track's metadata
AutoNumber bool
@@ -68,6 +66,12 @@ type Tracklist struct {
// Disables sorting the tracklist by clicking individual columns.
DisableSorting bool
}
type Tracklist struct {
widget.BaseWidget
Options TracklistOptions
// user action callbacks
OnPlayTrackAt func(int)
@@ -141,7 +145,7 @@ func NewTracklist(tracks []*mediaprovider.Track) *Tracklist {
tr := item.(*TrackRow)
tr.trackIdx = itemID
i := -1 // signal that we want to display the actual track num.
if t.AutoNumber {
if t.Options.AutoNumber {
i = itemID + 1
}
tr.Update(t.trackModelAt(itemID), i)
@@ -153,6 +157,13 @@ func NewTracklist(tracks []*mediaprovider.Track) *Tracklist {
return t
}
func (t *Tracklist) Reset() {
t.Clear()
t.Options = TracklistOptions{}
t.ctxMenu = nil
t.SetSorting(TracklistSort{})
}
func (t *Tracklist) buildHeader() {
t.hdr = NewListHeader([]ListColumn{
{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false},
@@ -331,7 +342,7 @@ func (t *Tracklist) CreateRenderer() fyne.WidgetRenderer {
}
func (t *Tracklist) Refresh() {
t.hdr.DisableSorting = t.DisableSorting
t.hdr.DisableSorting = t.Options.DisableSorting
t.BaseWidget.Refresh()
}
@@ -487,7 +498,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
t.list.Refresh()
if t.ctxMenu == nil {
t.ctxMenu = fyne.NewMenu("")
if !t.DisablePlaybackMenu {
if !t.Options.DisablePlaybackMenu {
t.ctxMenu.Items = append(t.ctxMenu.Items,
fyne.NewMenuItem("Play", func() {
if t.OnPlaySelection != nil {
@@ -530,9 +541,9 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
t.onSetRatings(t.selectedTracks(), rating, true)
})
t.ctxMenu.Items = append(t.ctxMenu.Items, ratingMenu)
if len(t.AuxiliaryMenuItems) > 0 {
if len(t.Options.AuxiliaryMenuItems) > 0 {
t.ctxMenu.Items = append(t.ctxMenu.Items, fyne.NewMenuItemSeparator())
t.ctxMenu.Items = append(t.ctxMenu.Items, t.AuxiliaryMenuItems...)
t.ctxMenu.Items = append(t.ctxMenu.Items, t.Options.AuxiliaryMenuItems...)
}
}
widget.ShowPopUpMenuAtPosition(t.ctxMenu, fyne.CurrentApp().Driver().CanvasForObject(t), e.AbsolutePosition)
@@ -754,7 +765,7 @@ func (t *TrackRow) Update(tm *trackModel, rowNum int) {
var str string
if rowNum < 0 {
rowNum = tr.TrackNumber
if t.tracklist.ShowDiscNumber {
if t.tracklist.Options.ShowDiscNumber {
discNum = tr.DiscNumber
}
}