Merge pull request #217 from dweymouth/perf/reuse-widgets
Reuse complex widgets across pages to reduce memory spikes when browsing
This commit is contained in:
@@ -25,6 +25,7 @@ type AlbumPage struct {
|
|||||||
|
|
||||||
albumPageState
|
albumPageState
|
||||||
|
|
||||||
|
disposed bool
|
||||||
header *AlbumPageHeader
|
header *AlbumPageHeader
|
||||||
tracks []*mediaprovider.Track
|
tracks []*mediaprovider.Track
|
||||||
tracklist *widgets.Tracklist
|
tracklist *widgets.Tracklist
|
||||||
@@ -36,6 +37,7 @@ type albumPageState struct {
|
|||||||
albumID string
|
albumID string
|
||||||
sort widgets.TracklistSort
|
sort widgets.TracklistSort
|
||||||
cfg *backend.AlbumPageConfig
|
cfg *backend.AlbumPageConfig
|
||||||
|
pool *util.WidgetPool
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
@@ -45,17 +47,19 @@ type albumPageState struct {
|
|||||||
func NewAlbumPage(
|
func NewAlbumPage(
|
||||||
albumID string,
|
albumID string,
|
||||||
cfg *backend.AlbumPageConfig,
|
cfg *backend.AlbumPageConfig,
|
||||||
|
pool *util.WidgetPool,
|
||||||
pm *backend.PlaybackManager,
|
pm *backend.PlaybackManager,
|
||||||
mp mediaprovider.MediaProvider,
|
mp mediaprovider.MediaProvider,
|
||||||
im *backend.ImageManager,
|
im *backend.ImageManager,
|
||||||
contr *controller.Controller,
|
contr *controller.Controller,
|
||||||
) *AlbumPage {
|
) *AlbumPage {
|
||||||
return newAlbumPage(albumID, cfg, pm, mp, im, contr, widgets.TracklistSort{})
|
return newAlbumPage(albumID, cfg, pool, pm, mp, im, contr, widgets.TracklistSort{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAlbumPage(
|
func newAlbumPage(
|
||||||
albumID string,
|
albumID string,
|
||||||
cfg *backend.AlbumPageConfig,
|
cfg *backend.AlbumPageConfig,
|
||||||
|
pool *util.WidgetPool,
|
||||||
pm *backend.PlaybackManager,
|
pm *backend.PlaybackManager,
|
||||||
mp mediaprovider.MediaProvider,
|
mp mediaprovider.MediaProvider,
|
||||||
im *backend.ImageManager,
|
im *backend.ImageManager,
|
||||||
@@ -66,6 +70,7 @@ func newAlbumPage(
|
|||||||
albumPageState: albumPageState{
|
albumPageState: albumPageState{
|
||||||
albumID: albumID,
|
albumID: albumID,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
pool: pool,
|
||||||
pm: pm,
|
pm: pm,
|
||||||
mp: mp,
|
mp: mp,
|
||||||
im: im,
|
im: im,
|
||||||
@@ -73,8 +78,20 @@ func newAlbumPage(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
a.header = NewAlbumPageHeader(a)
|
if h := pool.Obtain(util.WidgetTypeAlbumPageHeader); h != nil {
|
||||||
a.tracklist = widgets.NewTracklist(nil)
|
a.header = h.(*AlbumPageHeader)
|
||||||
|
a.header.page = a
|
||||||
|
a.header.Clear()
|
||||||
|
} else {
|
||||||
|
a.header = NewAlbumPageHeader(a)
|
||||||
|
}
|
||||||
|
a.header.page = a
|
||||||
|
if t := a.pool.Obtain(util.WidgetTypeTracklist); t != nil {
|
||||||
|
a.tracklist = t.(*widgets.Tracklist)
|
||||||
|
a.tracklist.Reset()
|
||||||
|
} else {
|
||||||
|
a.tracklist = widgets.NewTracklist(nil)
|
||||||
|
}
|
||||||
a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
|
a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
|
||||||
a.tracklist.SetSorting(sort)
|
a.tracklist.SetSorting(sort)
|
||||||
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
@@ -95,8 +112,13 @@ func (a *AlbumPage) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumPage) Save() SavedPage {
|
func (a *AlbumPage) Save() SavedPage {
|
||||||
|
a.disposed = true
|
||||||
s := a.albumPageState
|
s := a.albumPageState
|
||||||
s.sort = a.tracklist.Sorting()
|
s.sort = a.tracklist.Sorting()
|
||||||
|
a.header.page = nil
|
||||||
|
a.pool.Release(util.WidgetTypeAlbumPageHeader, a.header)
|
||||||
|
a.tracklist.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeTracklist, a.tracklist)
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,8 +155,13 @@ func (a *AlbumPage) load() {
|
|||||||
log.Printf("Failed to get album: %s", err.Error())
|
log.Printf("Failed to get album: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if a.disposed {
|
||||||
|
return
|
||||||
|
}
|
||||||
a.header.Update(album, a.im)
|
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.tracks = album.Tracks
|
||||||
a.tracklist.SetTracks(album.Tracks)
|
a.tracklist.SetTracks(album.Tracks)
|
||||||
a.tracklist.SetNowPlaying(a.nowPlayingID)
|
a.tracklist.SetNowPlaying(a.nowPlayingID)
|
||||||
@@ -256,6 +283,19 @@ 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
|
||||||
|
a.cover.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AlbumPageHeader) toggleFavorited() {
|
func (a *AlbumPageHeader) toggleFavorited() {
|
||||||
params := mediaprovider.RatingFavoriteParameters{AlbumIDs: []string{a.albumID}}
|
params := mediaprovider.RatingFavoriteParameters{AlbumIDs: []string{a.albumID}}
|
||||||
a.page.mp.SetFavorite(params, a.toggleFavButton.IsFavorited)
|
a.page.mp.SetFavorite(params, a.toggleFavButton.IsFavorited)
|
||||||
@@ -267,7 +307,9 @@ func (a *AlbumPageHeader) showPopUpCover() {
|
|||||||
log.Printf("error getting full size album cover: %s", err.Error())
|
log.Printf("error getting full size album cover: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.page.contr.ShowPopUpImage(cover)
|
if a.page != nil {
|
||||||
|
a.page.contr.ShowPopUpImage(cover)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string {
|
func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string {
|
||||||
@@ -283,5 +325,5 @@ func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *albumPageState) Restore() Page {
|
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.pool, s.pm, s.mp, s.im, s.contr, s.sort)
|
||||||
}
|
}
|
||||||
|
|||||||
+79
-67
@@ -21,20 +21,22 @@ var _ fyne.Widget = (*AlbumsPage)(nil)
|
|||||||
type AlbumsPage struct {
|
type AlbumsPage struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
cfg *backend.AlbumsPageConfig
|
cfg *backend.AlbumsPageConfig
|
||||||
contr *controller.Controller
|
pool *util.WidgetPool
|
||||||
pm *backend.PlaybackManager
|
contr *controller.Controller
|
||||||
im *backend.ImageManager
|
pm *backend.PlaybackManager
|
||||||
mp mediaprovider.MediaProvider
|
im *backend.ImageManager
|
||||||
grid *widgets.GridView
|
mp mediaprovider.MediaProvider
|
||||||
searchGrid *widgets.GridView
|
grid *widgets.GridView
|
||||||
searcher *widgets.SearchEntry
|
gridState *widgets.GridViewState
|
||||||
filterBtn *widgets.AlbumFilterButton
|
searchGridState *widgets.GridViewState
|
||||||
searchText string
|
searcher *widgets.SearchEntry
|
||||||
filter mediaprovider.AlbumFilter
|
filterBtn *widgets.AlbumFilterButton
|
||||||
titleDisp *widget.RichText
|
searchText string
|
||||||
sortOrder *selectWidget
|
filter mediaprovider.AlbumFilter
|
||||||
container *fyne.Container
|
titleDisp *widget.RichText
|
||||||
|
sortOrder *selectWidget
|
||||||
|
container *fyne.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectWidget struct {
|
type selectWidget struct {
|
||||||
@@ -56,8 +58,9 @@ func (s *selectWidget) MinSize() fyne.Size {
|
|||||||
return fyne.NewSize(170, s.Select.MinSize().Height)
|
return fyne.NewSize(170, s.Select.MinSize().Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *AlbumsPage {
|
func NewAlbumsPage(cfg *backend.AlbumsPageConfig, pool *util.WidgetPool, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *AlbumsPage {
|
||||||
a := &AlbumsPage{
|
a := &AlbumsPage{
|
||||||
|
pool: pool,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
contr: contr,
|
contr: contr,
|
||||||
pm: pm,
|
pm: pm,
|
||||||
@@ -76,10 +79,16 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller,
|
|||||||
}
|
}
|
||||||
a.sortOrder.Selected = cfg.SortOrder
|
a.sortOrder.Selected = cfg.SortOrder
|
||||||
iter := mp.IterateAlbums(a.sortOrder.Selected, a.filter)
|
iter := mp.IterateAlbums(a.sortOrder.Selected, a.filter)
|
||||||
a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im, myTheme.AlbumIcon)
|
if g := pool.Obtain(util.WidgetTypeGridView); g != nil {
|
||||||
|
a.grid = g.(*widgets.GridView)
|
||||||
|
a.grid.Placeholder = myTheme.AlbumIcon
|
||||||
|
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
||||||
|
} else {
|
||||||
|
a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im, myTheme.AlbumIcon)
|
||||||
|
}
|
||||||
contr.ConnectAlbumGridActions(a.grid)
|
contr.ConnectAlbumGridActions(a.grid)
|
||||||
a.createSearchAndFilter()
|
a.createSearchAndFilter()
|
||||||
a.createContainer(false)
|
a.createContainer()
|
||||||
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
@@ -92,31 +101,30 @@ func (a *AlbumsPage) createSearchAndFilter() {
|
|||||||
a.filterBtn.OnChanged = a.Reload
|
a.filterBtn.OnChanged = a.Reload
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumsPage) createContainer(searchgrid bool) {
|
func (a *AlbumsPage) createContainer() {
|
||||||
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
|
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
|
||||||
sortVbox := container.NewVBox(layout.NewSpacer(), a.sortOrder, layout.NewSpacer())
|
sortVbox := container.NewVBox(layout.NewSpacer(), a.sortOrder, layout.NewSpacer())
|
||||||
g := a.grid
|
|
||||||
if searchgrid {
|
|
||||||
g = a.searchGrid
|
|
||||||
}
|
|
||||||
a.container = container.NewBorder(
|
a.container = container.NewBorder(
|
||||||
container.NewHBox(util.NewHSpace(6), a.titleDisp, sortVbox, layout.NewSpacer(), container.NewCenter(a.filterBtn), searchVbox, util.NewHSpace(12)),
|
container.NewHBox(util.NewHSpace(6), a.titleDisp, sortVbox, layout.NewSpacer(), container.NewCenter(a.filterBtn), searchVbox, util.NewHSpace(12)),
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
g,
|
a.grid,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage {
|
func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage {
|
||||||
a := &AlbumsPage{
|
a := &AlbumsPage{
|
||||||
cfg: saved.cfg,
|
cfg: saved.cfg,
|
||||||
contr: saved.contr,
|
pool: saved.pool,
|
||||||
pm: saved.pm,
|
contr: saved.contr,
|
||||||
mp: saved.mp,
|
pm: saved.pm,
|
||||||
im: saved.im,
|
mp: saved.mp,
|
||||||
searchText: saved.searchText,
|
im: saved.im,
|
||||||
filter: saved.filter,
|
gridState: saved.gridState,
|
||||||
|
searchGridState: saved.searchGridState,
|
||||||
|
searchText: saved.searchText,
|
||||||
|
filter: saved.filter,
|
||||||
}
|
}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
|
|
||||||
@@ -127,27 +135,33 @@ func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage {
|
|||||||
a.sortOrder = NewSelect(a.mp.AlbumSortOrders(), nil)
|
a.sortOrder = NewSelect(a.mp.AlbumSortOrders(), nil)
|
||||||
a.sortOrder.Selected = saved.sortOrder
|
a.sortOrder.Selected = saved.sortOrder
|
||||||
a.sortOrder.OnChanged = a.onSortOrderChanged
|
a.sortOrder.OnChanged = a.onSortOrderChanged
|
||||||
a.grid = widgets.NewGridViewFromState(saved.gridState)
|
state := saved.gridState
|
||||||
if a.searchText != "" {
|
if a.searchText != "" {
|
||||||
a.searchGrid = widgets.NewGridViewFromState(saved.searchGridState)
|
a.sortOrder.Disable()
|
||||||
|
state = saved.searchGridState
|
||||||
|
}
|
||||||
|
if g := a.pool.Obtain(util.WidgetTypeGridView); g != nil {
|
||||||
|
a.grid = g.(*widgets.GridView)
|
||||||
|
a.grid.ResetFromState(state)
|
||||||
|
} else {
|
||||||
|
a.grid = widgets.NewGridViewFromState(state)
|
||||||
}
|
}
|
||||||
a.createSearchAndFilter()
|
a.createSearchAndFilter()
|
||||||
a.createContainer(saved.searchText != "")
|
a.createContainer()
|
||||||
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumsPage) OnSearched(query string) {
|
func (a *AlbumsPage) OnSearched(query string) {
|
||||||
a.searchText = query
|
|
||||||
if query == "" {
|
if query == "" {
|
||||||
a.container.Objects[0] = a.grid
|
a.sortOrder.Enable()
|
||||||
if a.searchGrid != nil {
|
a.grid.ResetFromState(a.gridState)
|
||||||
a.searchGrid.Clear()
|
a.searchGridState = nil
|
||||||
}
|
} else {
|
||||||
a.Refresh()
|
a.sortOrder.Disable()
|
||||||
return
|
a.doSearch(query)
|
||||||
}
|
}
|
||||||
a.doSearch(query)
|
a.searchText = query
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumsPage) Route() controller.Route {
|
func (a *AlbumsPage) Route() controller.Route {
|
||||||
@@ -166,48 +180,45 @@ func (a *AlbumsPage) Reload() {
|
|||||||
} else {
|
} else {
|
||||||
iter := a.mp.IterateAlbums(a.sortOrder.Selected, a.filter)
|
iter := a.mp.IterateAlbums(a.sortOrder.Selected, a.filter)
|
||||||
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
||||||
a.grid.Refresh()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumsPage) Save() SavedPage {
|
func (a *AlbumsPage) Save() SavedPage {
|
||||||
sa := &savedAlbumsPage{
|
sa := &savedAlbumsPage{
|
||||||
cfg: a.cfg,
|
cfg: a.cfg,
|
||||||
contr: a.contr,
|
pool: a.pool,
|
||||||
pm: a.pm,
|
contr: a.contr,
|
||||||
mp: a.mp,
|
pm: a.pm,
|
||||||
im: a.im,
|
mp: a.mp,
|
||||||
searchText: a.searchText,
|
im: a.im,
|
||||||
filter: a.filter,
|
searchText: a.searchText,
|
||||||
sortOrder: a.sortOrder.Selected,
|
filter: a.filter,
|
||||||
gridState: a.grid.SaveToState(),
|
sortOrder: a.sortOrder.Selected,
|
||||||
|
gridState: a.gridState,
|
||||||
|
searchGridState: a.searchGridState,
|
||||||
}
|
}
|
||||||
if a.searchGrid != nil {
|
if a.searchText == "" {
|
||||||
sa.searchGridState = a.searchGrid.SaveToState()
|
sa.gridState = a.grid.SaveToState()
|
||||||
|
} else {
|
||||||
|
sa.searchGridState = a.grid.SaveToState()
|
||||||
}
|
}
|
||||||
|
a.grid.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeGridView, a.grid)
|
||||||
return sa
|
return sa
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumsPage) doSearch(query string) {
|
func (a *AlbumsPage) doSearch(query string) {
|
||||||
iter := widgets.NewGridViewAlbumIterator(a.mp.SearchAlbums(query, a.filter))
|
if a.searchText == "" {
|
||||||
if a.searchGrid == nil {
|
a.gridState = a.grid.SaveToState()
|
||||||
a.searchGrid = widgets.NewGridView(iter, a.im, myTheme.AlbumIcon)
|
|
||||||
a.contr.ConnectAlbumGridActions(a.searchGrid)
|
|
||||||
} else {
|
|
||||||
a.searchGrid.Reset(iter)
|
|
||||||
}
|
}
|
||||||
a.container.Objects[0] = a.searchGrid
|
iter := widgets.NewGridViewAlbumIterator(a.mp.SearchAlbums(query, a.filter))
|
||||||
a.Refresh()
|
a.grid.Reset(iter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumsPage) onSortOrderChanged(order string) {
|
func (a *AlbumsPage) onSortOrderChanged(order string) {
|
||||||
a.cfg.SortOrder = a.sortOrder.Selected
|
a.cfg.SortOrder = a.sortOrder.Selected
|
||||||
iter := a.mp.IterateAlbums(order, a.filter)
|
iter := a.mp.IterateAlbums(order, a.filter)
|
||||||
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
||||||
if a.searchText == "" {
|
|
||||||
a.container.Objects[0] = a.grid
|
|
||||||
a.Refresh()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlbumsPage) CreateRenderer() fyne.WidgetRenderer {
|
func (a *AlbumsPage) CreateRenderer() fyne.WidgetRenderer {
|
||||||
@@ -219,13 +230,14 @@ type savedAlbumsPage struct {
|
|||||||
searchText string
|
searchText string
|
||||||
filter mediaprovider.AlbumFilter
|
filter mediaprovider.AlbumFilter
|
||||||
cfg *backend.AlbumsPageConfig
|
cfg *backend.AlbumsPageConfig
|
||||||
|
pool *util.WidgetPool
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
sortOrder string
|
sortOrder string
|
||||||
gridState widgets.GridViewState
|
gridState *widgets.GridViewState
|
||||||
searchGridState widgets.GridViewState
|
searchGridState *widgets.GridViewState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *savedAlbumsPage) Restore() Page {
|
func (s *savedAlbumsPage) Restore() Page {
|
||||||
|
|||||||
+73
-14
@@ -28,6 +28,7 @@ type artistPageState struct {
|
|||||||
activeView int
|
activeView int
|
||||||
trackSort widgets.TracklistSort
|
trackSort widgets.TracklistSort
|
||||||
|
|
||||||
|
pool *util.WidgetPool
|
||||||
cfg *backend.ArtistPageConfig
|
cfg *backend.ArtistPageConfig
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
@@ -39,6 +40,7 @@ type ArtistPage struct {
|
|||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
artistPageState
|
artistPageState
|
||||||
|
disposed bool
|
||||||
|
|
||||||
artistInfo *mediaprovider.ArtistWithAlbums
|
artistInfo *mediaprovider.ArtistWithAlbums
|
||||||
|
|
||||||
@@ -49,18 +51,19 @@ type ArtistPage struct {
|
|||||||
container *fyne.Container
|
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, pool *util.WidgetPool, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller) *ArtistPage {
|
||||||
activeView := 0
|
activeView := 0
|
||||||
if cfg.InitialView == "Top Tracks" {
|
if cfg.InitialView == "Top Tracks" {
|
||||||
activeView = 1
|
activeView = 1
|
||||||
}
|
}
|
||||||
return newArtistPage(artistID, cfg, pm, mp, im, contr, activeView, widgets.TracklistSort{})
|
return newArtistPage(artistID, cfg, pool, 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, pool *util.WidgetPool, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller, activeView int, sort widgets.TracklistSort) *ArtistPage {
|
||||||
a := &ArtistPage{artistPageState: artistPageState{
|
a := &ArtistPage{artistPageState: artistPageState{
|
||||||
artistID: artistID,
|
artistID: artistID,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
pool: pool,
|
||||||
pm: pm,
|
pm: pm,
|
||||||
mp: mp,
|
mp: mp,
|
||||||
im: im,
|
im: im,
|
||||||
@@ -69,7 +72,14 @@ func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.P
|
|||||||
trackSort: sort,
|
trackSort: sort,
|
||||||
}}
|
}}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
a.header = NewArtistPageHeader(a)
|
if h := a.pool.Obtain(util.WidgetTypeArtistPageHeader); h != nil {
|
||||||
|
a.header = h.(*ArtistPageHeader)
|
||||||
|
a.header.artistPage = a
|
||||||
|
a.header.Clear()
|
||||||
|
} else {
|
||||||
|
a.header = NewArtistPageHeader(a)
|
||||||
|
}
|
||||||
|
a.header.artistPage = a
|
||||||
if img, ok := im.GetCachedArtistImage(artistID); ok {
|
if img, ok := im.GetCachedArtistImage(artistID); ok {
|
||||||
a.header.artistImage.SetImage(img, true /*tappable*/)
|
a.header.artistImage.SetImage(img, true /*tappable*/)
|
||||||
}
|
}
|
||||||
@@ -112,9 +122,18 @@ func (a *ArtistPage) Reload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArtistPage) Save() SavedPage {
|
func (a *ArtistPage) Save() SavedPage {
|
||||||
|
a.disposed = true
|
||||||
s := a.artistPageState
|
s := a.artistPageState
|
||||||
if a.tracklistCtr != nil {
|
if a.tracklistCtr != nil {
|
||||||
s.trackSort = a.tracklistCtr.Objects[0].(*widgets.Tracklist).Sorting()
|
tl := a.tracklistCtr.Objects[0].(*widgets.Tracklist)
|
||||||
|
s.trackSort = tl.Sorting()
|
||||||
|
tl.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeTracklist, tl)
|
||||||
|
}
|
||||||
|
a.pool.Release(util.WidgetTypeArtistPageHeader, a.header)
|
||||||
|
if a.albumGrid != nil {
|
||||||
|
a.albumGrid.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeGridView, a.albumGrid)
|
||||||
}
|
}
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
@@ -141,6 +160,9 @@ func (a *ArtistPage) load() {
|
|||||||
log.Printf("Failed to get artist: %s", err.Error())
|
log.Printf("Failed to get artist: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if a.disposed {
|
||||||
|
return
|
||||||
|
}
|
||||||
a.artistInfo = artist
|
a.artistInfo = artist
|
||||||
a.header.Update(artist)
|
a.header.Update(artist)
|
||||||
if a.activeView == 0 {
|
if a.activeView == 0 {
|
||||||
@@ -170,7 +192,13 @@ func (a *ArtistPage) showAlbumGrid() {
|
|||||||
Secondary: strconv.Itoa(al.Year),
|
Secondary: strconv.Itoa(al.Year),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
a.albumGrid = widgets.NewFixedGridView(model, a.im, myTheme.AlbumIcon)
|
if g := a.pool.Obtain(util.WidgetTypeGridView); g != nil {
|
||||||
|
a.albumGrid = g.(*widgets.GridView)
|
||||||
|
a.albumGrid.Placeholder = myTheme.AlbumIcon
|
||||||
|
a.albumGrid.ResetFixed(model)
|
||||||
|
} else {
|
||||||
|
a.albumGrid = widgets.NewFixedGridView(model, a.im, myTheme.AlbumIcon)
|
||||||
|
}
|
||||||
a.contr.ConnectAlbumGridActions(a.albumGrid)
|
a.contr.ConnectAlbumGridActions(a.albumGrid)
|
||||||
}
|
}
|
||||||
a.container.Objects[0].(*fyne.Container).Objects[0] = a.albumGrid
|
a.container.Objects[0].(*fyne.Container).Objects[0] = a.albumGrid
|
||||||
@@ -189,8 +217,18 @@ func (a *ArtistPage) showTopTracks() {
|
|||||||
log.Printf("error getting top songs: %s", err.Error())
|
log.Printf("error getting top songs: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tl := widgets.NewTracklist(ts)
|
if a.disposed {
|
||||||
tl.AutoNumber = true
|
return
|
||||||
|
}
|
||||||
|
var tl *widgets.Tracklist
|
||||||
|
if t := a.pool.Obtain(util.WidgetTypeTracklist); t != nil {
|
||||||
|
tl = t.(*widgets.Tracklist)
|
||||||
|
tl.Reset()
|
||||||
|
tl.SetTracks(ts)
|
||||||
|
} else {
|
||||||
|
tl = widgets.NewTracklist(ts)
|
||||||
|
}
|
||||||
|
tl.Options = widgets.TracklistOptions{AutoNumber: true}
|
||||||
tl.SetVisibleColumns(a.cfg.TracklistColumns)
|
tl.SetVisibleColumns(a.cfg.TracklistColumns)
|
||||||
tl.SetSorting(a.trackSort)
|
tl.SetSorting(a.trackSort)
|
||||||
tl.OnVisibleColumnsChanged = func(cols []string) {
|
tl.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
@@ -228,9 +266,11 @@ func (a *ArtistPage) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *artistPageState) Restore() Page {
|
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.pool, s.pm, s.mp, s.im, s.contr, s.activeView, s.trackSort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const artistBioNotAvailableStr = "Artist biography not available."
|
||||||
|
|
||||||
type ArtistPageHeader struct {
|
type ArtistPageHeader struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
@@ -250,7 +290,7 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
|
|||||||
a := &ArtistPageHeader{
|
a := &ArtistPageHeader{
|
||||||
artistPage: page,
|
artistPage: page,
|
||||||
titleDisp: widget.NewRichTextWithText(""),
|
titleDisp: widget.NewRichTextWithText(""),
|
||||||
biographyDisp: widget.NewRichTextWithText("Artist biography not available."),
|
biographyDisp: widget.NewRichTextWithText(artistBioNotAvailableStr),
|
||||||
similarArtists: container.NewHBox(),
|
similarArtists: container.NewHBox(),
|
||||||
}
|
}
|
||||||
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
|
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
|
||||||
@@ -273,6 +313,17 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
|
|||||||
return a
|
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
|
||||||
|
for _, obj := range a.similarArtists.Objects {
|
||||||
|
obj.Hide()
|
||||||
|
}
|
||||||
|
a.artistImage.SetImage(nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ArtistPageHeader) Update(artist *mediaprovider.ArtistWithAlbums) {
|
func (a *ArtistPageHeader) Update(artist *mediaprovider.ArtistWithAlbums) {
|
||||||
if artist == nil {
|
if artist == nil {
|
||||||
return
|
return
|
||||||
@@ -299,21 +350,29 @@ func (a *ArtistPageHeader) UpdateInfo(info *mediaprovider.ArtistInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.similarArtists.RemoveAll()
|
if len(a.similarArtists.Objects) == 0 {
|
||||||
|
a.similarArtists.Add(widget.NewLabel("Similar Artists:"))
|
||||||
|
}
|
||||||
|
for _, obj := range a.similarArtists.Objects {
|
||||||
|
obj.Hide()
|
||||||
|
}
|
||||||
for i, art := range info.SimilarArtists {
|
for i, art := range info.SimilarArtists {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
a.similarArtists.Add(widget.NewLabel("Similar Artists:"))
|
a.similarArtists.Objects[0].Show() // "Similar Artists:" label
|
||||||
}
|
}
|
||||||
if i == 4 {
|
if i == 4 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
h := widgets.NewCustomHyperlink()
|
if len(a.similarArtists.Objects) <= i+1 {
|
||||||
|
a.similarArtists.Add(widgets.NewCustomHyperlink())
|
||||||
|
}
|
||||||
|
h := a.similarArtists.Objects[i+1].(*widgets.CustomHyperlink)
|
||||||
h.NoTruncate = true
|
h.NoTruncate = true
|
||||||
h.SetText(art.Name)
|
h.SetText(art.Name)
|
||||||
h.OnTapped = func(id string) func() {
|
h.OnTapped = func(id string) func() {
|
||||||
return func() { a.artistPage.contr.NavigateTo(controller.ArtistRoute(id)) }
|
return func() { a.artistPage.contr.NavigateTo(controller.ArtistRoute(id)) }
|
||||||
}(art.ID)
|
}(art.ID)
|
||||||
a.similarArtists.Add(h)
|
h.Show()
|
||||||
}
|
}
|
||||||
a.similarArtists.Refresh()
|
a.similarArtists.Refresh()
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type ArtistsPage struct {
|
|||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
|
pool *util.WidgetPool
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
@@ -40,15 +41,17 @@ type ArtistsPage struct {
|
|||||||
|
|
||||||
func NewArtistsPage(
|
func NewArtistsPage(
|
||||||
contr *controller.Controller,
|
contr *controller.Controller,
|
||||||
|
pool *util.WidgetPool,
|
||||||
pm *backend.PlaybackManager,
|
pm *backend.PlaybackManager,
|
||||||
mp mediaprovider.MediaProvider,
|
mp mediaprovider.MediaProvider,
|
||||||
im *backend.ImageManager,
|
im *backend.ImageManager,
|
||||||
) *ArtistsPage {
|
) *ArtistsPage {
|
||||||
return newArtistsPage(contr, pm, mp, im, "", 0, 0)
|
return newArtistsPage(contr, pool, pm, mp, im, "", 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newArtistsPage(
|
func newArtistsPage(
|
||||||
contr *controller.Controller,
|
contr *controller.Controller,
|
||||||
|
pool *util.WidgetPool,
|
||||||
pm *backend.PlaybackManager,
|
pm *backend.PlaybackManager,
|
||||||
mp mediaprovider.MediaProvider,
|
mp mediaprovider.MediaProvider,
|
||||||
im *backend.ImageManager,
|
im *backend.ImageManager,
|
||||||
@@ -58,6 +61,7 @@ func newArtistsPage(
|
|||||||
) *ArtistsPage {
|
) *ArtistsPage {
|
||||||
a := &ArtistsPage{
|
a := &ArtistsPage{
|
||||||
contr: contr,
|
contr: contr,
|
||||||
|
pool: pool,
|
||||||
pm: pm,
|
pm: pm,
|
||||||
mp: mp,
|
mp: mp,
|
||||||
im: im,
|
im: im,
|
||||||
@@ -74,7 +78,13 @@ func newArtistsPage(
|
|||||||
a.searcher = widgets.NewSearchEntry()
|
a.searcher = widgets.NewSearchEntry()
|
||||||
a.searcher.OnSearched = func(query string) { a.onSearched(query, false /*firstLoad*/) }
|
a.searcher.OnSearched = func(query string) { a.onSearched(query, false /*firstLoad*/) }
|
||||||
a.searcher.Entry.Text = searchText
|
a.searcher.Entry.Text = searchText
|
||||||
a.grid = widgets.NewFixedGridView(nil, a.im, myTheme.ArtistIcon)
|
if g := pool.Obtain(util.WidgetTypeGridView); g != nil {
|
||||||
|
a.grid = g.(*widgets.GridView)
|
||||||
|
a.grid.Placeholder = myTheme.ArtistIcon
|
||||||
|
a.grid.Clear()
|
||||||
|
} else {
|
||||||
|
a.grid = widgets.NewFixedGridView(nil, a.im, myTheme.ArtistIcon)
|
||||||
|
}
|
||||||
a.contr.ConnectArtistGridActions(a.grid)
|
a.contr.ConnectArtistGridActions(a.grid)
|
||||||
|
|
||||||
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
|
searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer())
|
||||||
@@ -158,6 +168,7 @@ func (a *ArtistsPage) Route() controller.Route {
|
|||||||
func (a *ArtistsPage) Save() SavedPage {
|
func (a *ArtistsPage) Save() SavedPage {
|
||||||
s := &savedArtistsPage{
|
s := &savedArtistsPage{
|
||||||
contr: a.contr,
|
contr: a.contr,
|
||||||
|
pool: a.pool,
|
||||||
im: a.im,
|
im: a.im,
|
||||||
pm: a.pm,
|
pm: a.pm,
|
||||||
mp: a.mp,
|
mp: a.mp,
|
||||||
@@ -169,11 +180,14 @@ func (a *ArtistsPage) Save() SavedPage {
|
|||||||
} else {
|
} else {
|
||||||
s.searchGridScrollPos = a.grid.GetScrollOffset()
|
s.searchGridScrollPos = a.grid.GetScrollOffset()
|
||||||
}
|
}
|
||||||
|
a.grid.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeGridView, a.grid)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
type savedArtistsPage struct {
|
type savedArtistsPage struct {
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
|
pool *util.WidgetPool
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
@@ -183,5 +197,5 @@ type savedArtistsPage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *savedArtistsPage) Restore() Page {
|
func (s *savedArtistsPage) Restore() Page {
|
||||||
return newArtistsPage(s.contr, s.pm, s.mp, s.im, s.searchText, s.fullGridScrollPos, s.searchGridScrollPos)
|
return newArtistsPage(s.contr, s.pool, s.pm, s.mp, s.im, s.searchText, s.fullGridScrollPos, s.searchGridScrollPos)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,17 +233,21 @@ func (b *BrowsingPane) GoHome() {
|
|||||||
|
|
||||||
func (b *BrowsingPane) GoBack() {
|
func (b *BrowsingPane) GoBack() {
|
||||||
if b.historyIdx > 0 {
|
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.addPageToHistory(b.curPage, false)
|
||||||
b.historyIdx -= 2
|
b.historyIdx -= 2
|
||||||
b.doSetPage(b.history[b.historyIdx].Restore())
|
b.doSetPage(p)
|
||||||
b.updateHistoryButtons()
|
b.updateHistoryButtons()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BrowsingPane) GoForward() {
|
func (b *BrowsingPane) GoForward() {
|
||||||
if b.historyIdx < len(b.history)-1 {
|
if b.historyIdx < len(b.history)-1 {
|
||||||
|
p := b.history[b.historyIdx+1].Restore()
|
||||||
b.addPageToHistory(b.curPage, false)
|
b.addPageToHistory(b.curPage, false)
|
||||||
b.doSetPage(b.history[b.historyIdx].Restore())
|
b.doSetPage(p)
|
||||||
b.updateHistoryButtons()
|
b.updateHistoryButtons()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,32 +23,36 @@ type FavoritesPage struct {
|
|||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
cfg *backend.FavoritesPageConfig
|
cfg *backend.FavoritesPageConfig
|
||||||
|
pool *util.WidgetPool
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
|
|
||||||
|
disposed bool
|
||||||
trackSort widgets.TracklistSort
|
trackSort widgets.TracklistSort
|
||||||
filter mediaprovider.AlbumFilter
|
filter mediaprovider.AlbumFilter
|
||||||
searchText string
|
searchText string
|
||||||
nowPlayingID string
|
nowPlayingID string
|
||||||
pendingViewSwitch bool
|
pendingViewSwitch bool
|
||||||
|
|
||||||
grid *widgets.GridView
|
grid *widgets.GridView
|
||||||
searchGrid *widgets.GridView
|
gridState *widgets.GridViewState
|
||||||
artistGrid *widgets.GridView
|
searchGridState *widgets.GridViewState
|
||||||
tracklistCtr *fyne.Container
|
artistGrid *widgets.GridView
|
||||||
searcher *widgets.SearchEntry
|
tracklistCtr *fyne.Container
|
||||||
filterBtn *widgets.AlbumFilterButton
|
searcher *widgets.SearchEntry
|
||||||
titleDisp *widget.RichText
|
filterBtn *widgets.AlbumFilterButton
|
||||||
toggleBtns *widgets.ToggleButtonGroup
|
titleDisp *widget.RichText
|
||||||
container *fyne.Container
|
toggleBtns *widgets.ToggleButtonGroup
|
||||||
|
container *fyne.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, mp mediaprovider.MediaProvider, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage {
|
func NewFavoritesPage(cfg *backend.FavoritesPageConfig, pool *util.WidgetPool, contr *controller.Controller, mp mediaprovider.MediaProvider, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage {
|
||||||
a := &FavoritesPage{
|
a := &FavoritesPage{
|
||||||
filter: mediaprovider.AlbumFilter{ExcludeUnfavorited: true},
|
filter: mediaprovider.AlbumFilter{ExcludeUnfavorited: true},
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
pool: pool,
|
||||||
contr: contr,
|
contr: contr,
|
||||||
pm: pm,
|
pm: pm,
|
||||||
mp: mp,
|
mp: mp,
|
||||||
@@ -56,8 +60,14 @@ func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Contro
|
|||||||
}
|
}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
a.createHeader(0)
|
a.createHeader(0)
|
||||||
iter := mp.IterateAlbums("", a.filter)
|
iter := widgets.NewGridViewAlbumIterator(mp.IterateAlbums("", a.filter))
|
||||||
a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im, myTheme.AlbumIcon)
|
if g := pool.Obtain(util.WidgetTypeGridView); g != nil {
|
||||||
|
a.grid = g.(*widgets.GridView)
|
||||||
|
a.grid.Placeholder = myTheme.AlbumIcon
|
||||||
|
a.grid.Reset(iter)
|
||||||
|
} else {
|
||||||
|
a.grid = widgets.NewGridView(iter, a.im, myTheme.AlbumIcon)
|
||||||
|
}
|
||||||
a.contr.ConnectAlbumGridActions(a.grid)
|
a.contr.ConnectAlbumGridActions(a.grid)
|
||||||
if cfg.InitialView == "Artists" {
|
if cfg.InitialView == "Artists" {
|
||||||
a.toggleBtns.SetActivatedButton(1)
|
a.toggleBtns.SetActivatedButton(1)
|
||||||
@@ -97,21 +107,30 @@ func (a *FavoritesPage) createContainer(initialView fyne.CanvasObject) {
|
|||||||
|
|
||||||
func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage {
|
func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage {
|
||||||
a := &FavoritesPage{
|
a := &FavoritesPage{
|
||||||
cfg: saved.cfg,
|
cfg: saved.cfg,
|
||||||
contr: saved.contr,
|
contr: saved.contr,
|
||||||
pm: saved.pm,
|
pool: saved.pool,
|
||||||
mp: saved.mp,
|
pm: saved.pm,
|
||||||
im: saved.im,
|
mp: saved.mp,
|
||||||
searchText: saved.searchText,
|
im: saved.im,
|
||||||
filter: saved.filter,
|
gridState: saved.gridState,
|
||||||
trackSort: saved.trackSort,
|
searchGridState: saved.searchGridState,
|
||||||
|
searchText: saved.searchText,
|
||||||
|
filter: saved.filter,
|
||||||
|
trackSort: saved.trackSort,
|
||||||
}
|
}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
a.createHeader(saved.activeToggleBtn)
|
a.createHeader(saved.activeToggleBtn)
|
||||||
a.grid = widgets.NewGridViewFromState(saved.gridState)
|
state := saved.gridState
|
||||||
|
|
||||||
if saved.searchText != "" {
|
if saved.searchText != "" {
|
||||||
a.searchGrid = widgets.NewGridViewFromState(saved.searchGridState)
|
state = saved.searchGridState
|
||||||
|
}
|
||||||
|
if g := a.pool.Obtain(util.WidgetTypeGridView); g != nil {
|
||||||
|
a.grid = g.(*widgets.GridView)
|
||||||
|
a.grid.Placeholder = myTheme.AlbumIcon
|
||||||
|
a.grid.ResetFromState(state)
|
||||||
|
} else {
|
||||||
|
a.grid = widgets.NewGridViewFromState(state)
|
||||||
}
|
}
|
||||||
a.toggleBtns.SetActivatedButton(saved.activeToggleBtn)
|
a.toggleBtns.SetActivatedButton(saved.activeToggleBtn)
|
||||||
|
|
||||||
@@ -119,8 +138,6 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage {
|
|||||||
a.onShowFavoriteArtists()
|
a.onShowFavoriteArtists()
|
||||||
} else if saved.activeToggleBtn == 2 {
|
} else if saved.activeToggleBtn == 2 {
|
||||||
a.onShowFavoriteSongs()
|
a.onShowFavoriteSongs()
|
||||||
} else if saved.searchText != "" {
|
|
||||||
a.createContainer(a.searchGrid)
|
|
||||||
} else {
|
} else {
|
||||||
a.createContainer(a.grid)
|
a.createContainer(a.grid)
|
||||||
}
|
}
|
||||||
@@ -154,6 +171,9 @@ func (a *FavoritesPage) Reload() {
|
|||||||
log.Printf("error getting starred items: %s", err.Error())
|
log.Printf("error getting starred items: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if a.disposed {
|
||||||
|
return
|
||||||
|
}
|
||||||
if a.tracklistCtr != nil {
|
if a.tracklistCtr != nil {
|
||||||
// refresh favorite songs view
|
// refresh favorite songs view
|
||||||
tr := a.tracklistCtr.Objects[0].(*widgets.Tracklist)
|
tr := a.tracklistCtr.Objects[0].(*widgets.Tracklist)
|
||||||
@@ -176,22 +196,36 @@ func (a *FavoritesPage) Reload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *FavoritesPage) Save() SavedPage {
|
func (a *FavoritesPage) Save() SavedPage {
|
||||||
|
a.disposed = true
|
||||||
sf := &savedFavoritesPage{
|
sf := &savedFavoritesPage{
|
||||||
cfg: a.cfg,
|
cfg: a.cfg,
|
||||||
contr: a.contr,
|
contr: a.contr,
|
||||||
|
pool: a.pool,
|
||||||
pm: a.pm,
|
pm: a.pm,
|
||||||
mp: a.mp,
|
mp: a.mp,
|
||||||
im: a.im,
|
im: a.im,
|
||||||
filter: a.filter,
|
filter: a.filter,
|
||||||
searchText: a.searchText,
|
searchText: a.searchText,
|
||||||
gridState: a.grid.SaveToState(),
|
gridState: a.gridState,
|
||||||
|
searchGridState: a.searchGridState,
|
||||||
activeToggleBtn: a.toggleBtns.ActivatedButtonIndex(),
|
activeToggleBtn: a.toggleBtns.ActivatedButtonIndex(),
|
||||||
}
|
}
|
||||||
if a.searchGrid != nil {
|
if a.searchText != "" {
|
||||||
sf.searchGridState = a.searchGrid.SaveToState()
|
sf.searchGridState = a.grid.SaveToState()
|
||||||
|
} else {
|
||||||
|
sf.gridState = a.grid.SaveToState()
|
||||||
|
}
|
||||||
|
a.grid.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeGridView, a.grid)
|
||||||
|
if a.artistGrid != nil {
|
||||||
|
a.artistGrid.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeGridView, a.artistGrid)
|
||||||
}
|
}
|
||||||
if a.tracklistCtr != nil {
|
if a.tracklistCtr != nil {
|
||||||
sf.trackSort = a.tracklistCtr.Objects[0].(*widgets.Tracklist).Sorting()
|
tl := a.tracklistCtr.Objects[0].(*widgets.Tracklist)
|
||||||
|
sf.trackSort = tl.Sorting()
|
||||||
|
tl.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeTracklist, tl)
|
||||||
}
|
}
|
||||||
return sf
|
return sf
|
||||||
}
|
}
|
||||||
@@ -203,16 +237,13 @@ func (a *FavoritesPage) SearchWidget() fyne.Focusable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *FavoritesPage) OnSearched(query string) {
|
func (a *FavoritesPage) OnSearched(query string) {
|
||||||
a.searchText = query
|
|
||||||
if query == "" {
|
if query == "" {
|
||||||
a.container.Objects[0] = a.grid
|
a.grid.ResetFromState(a.gridState)
|
||||||
if a.searchGrid != nil {
|
a.searchGridState = nil
|
||||||
a.searchGrid.Clear()
|
} else {
|
||||||
}
|
a.doSearchAlbums(query)
|
||||||
a.Refresh()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
a.doSearchAlbums(query)
|
a.searchText = query
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ CanShowNowPlaying = (*FavoritesPage)(nil)
|
var _ CanShowNowPlaying = (*FavoritesPage)(nil)
|
||||||
@@ -236,26 +267,18 @@ func (a *FavoritesPage) SelectAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *FavoritesPage) doSearchAlbums(query string) {
|
func (a *FavoritesPage) doSearchAlbums(query string) {
|
||||||
iter := a.mp.SearchAlbums(query, a.filter)
|
if a.searchText == "" {
|
||||||
if a.searchGrid == nil {
|
a.gridState = a.grid.SaveToState()
|
||||||
a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im, myTheme.AlbumIcon)
|
|
||||||
a.contr.ConnectAlbumGridActions(a.searchGrid)
|
|
||||||
} else {
|
|
||||||
a.searchGrid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
|
||||||
}
|
}
|
||||||
a.container.Objects[0] = a.searchGrid
|
iter := widgets.NewGridViewAlbumIterator(a.mp.SearchAlbums(query, a.filter))
|
||||||
a.Refresh()
|
a.grid.Reset(iter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *FavoritesPage) onShowFavoriteAlbums() {
|
func (a *FavoritesPage) onShowFavoriteAlbums() {
|
||||||
a.cfg.InitialView = "Albums" // save setting
|
a.cfg.InitialView = "Albums" // save setting
|
||||||
a.searcher.Entry.Show()
|
a.searcher.Entry.Show()
|
||||||
a.filterBtn.Show()
|
a.filterBtn.Show()
|
||||||
if a.searchText == "" {
|
a.container.Objects[0] = a.grid
|
||||||
a.container.Objects[0] = a.grid
|
|
||||||
} else {
|
|
||||||
a.container.Objects[0] = a.searchGrid
|
|
||||||
}
|
|
||||||
a.Refresh()
|
a.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,8 +300,17 @@ func (a *FavoritesPage) onShowFavoriteArtists() {
|
|||||||
log.Printf("error getting starred items: %s", err.Error())
|
log.Printf("error getting starred items: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if a.disposed {
|
||||||
|
return
|
||||||
|
}
|
||||||
model := buildArtistGridViewModel(fav.Artists)
|
model := buildArtistGridViewModel(fav.Artists)
|
||||||
a.artistGrid = widgets.NewFixedGridView(model, a.im, myTheme.ArtistIcon)
|
if g := a.pool.Obtain(util.WidgetTypeGridView); g != nil {
|
||||||
|
a.artistGrid = g.(*widgets.GridView)
|
||||||
|
a.artistGrid.Placeholder = myTheme.ArtistIcon
|
||||||
|
a.artistGrid.ResetFixed(model)
|
||||||
|
} else {
|
||||||
|
a.artistGrid = widgets.NewFixedGridView(model, a.im, myTheme.ArtistIcon)
|
||||||
|
}
|
||||||
a.contr.ConnectArtistGridActions(a.artistGrid)
|
a.contr.ConnectArtistGridActions(a.artistGrid)
|
||||||
a.container.Objects[0] = a.artistGrid
|
a.container.Objects[0] = a.artistGrid
|
||||||
a.Refresh()
|
a.Refresh()
|
||||||
@@ -325,8 +357,18 @@ func (a *FavoritesPage) onShowFavoriteSongs() {
|
|||||||
log.Printf("error getting starred items: %s", err.Error())
|
log.Printf("error getting starred items: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tracklist := widgets.NewTracklist(fav.Tracks)
|
if a.disposed {
|
||||||
tracklist.AutoNumber = true
|
return
|
||||||
|
}
|
||||||
|
var tracklist *widgets.Tracklist
|
||||||
|
if tl := a.pool.Obtain(util.WidgetTypeTracklist); tl != nil {
|
||||||
|
tracklist = tl.(*widgets.Tracklist)
|
||||||
|
tracklist.Reset()
|
||||||
|
tracklist.SetTracks(fav.Tracks)
|
||||||
|
} else {
|
||||||
|
tracklist = widgets.NewTracklist(fav.Tracks)
|
||||||
|
}
|
||||||
|
tracklist.Options = widgets.TracklistOptions{AutoNumber: true}
|
||||||
tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
|
tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
|
||||||
tracklist.SetSorting(a.trackSort)
|
tracklist.SetSorting(a.trackSort)
|
||||||
tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
@@ -355,11 +397,12 @@ func (a *FavoritesPage) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
type savedFavoritesPage struct {
|
type savedFavoritesPage struct {
|
||||||
cfg *backend.FavoritesPageConfig
|
cfg *backend.FavoritesPageConfig
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
|
pool *util.WidgetPool
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
gridState widgets.GridViewState
|
gridState *widgets.GridViewState
|
||||||
searchGridState widgets.GridViewState
|
searchGridState *widgets.GridViewState
|
||||||
filter mediaprovider.AlbumFilter
|
filter mediaprovider.AlbumFilter
|
||||||
searchText string
|
searchText string
|
||||||
activeToggleBtn int
|
activeToggleBtn int
|
||||||
|
|||||||
+76
-63
@@ -19,26 +19,29 @@ import (
|
|||||||
type GenrePage struct {
|
type GenrePage struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
genre string
|
genre string
|
||||||
contr *controller.Controller
|
pool *util.WidgetPool
|
||||||
im *backend.ImageManager
|
contr *controller.Controller
|
||||||
pm *backend.PlaybackManager
|
im *backend.ImageManager
|
||||||
mp mediaprovider.MediaProvider
|
pm *backend.PlaybackManager
|
||||||
grid *widgets.GridView
|
mp mediaprovider.MediaProvider
|
||||||
searchGrid *widgets.GridView
|
grid *widgets.GridView
|
||||||
searcher *widgets.SearchEntry
|
gridState *widgets.GridViewState
|
||||||
searchText string
|
searchGridState *widgets.GridViewState
|
||||||
filter mediaprovider.AlbumFilter
|
searcher *widgets.SearchEntry
|
||||||
filterBtn *widgets.AlbumFilterButton
|
searchText string
|
||||||
titleDisp *widget.RichText
|
filter mediaprovider.AlbumFilter
|
||||||
playRandom *widget.Button
|
filterBtn *widgets.AlbumFilterButton
|
||||||
|
titleDisp *widget.RichText
|
||||||
|
playRandom *widget.Button
|
||||||
|
|
||||||
container *fyne.Container
|
container *fyne.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGenrePage(genre string, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *GenrePage {
|
func NewGenrePage(genre string, pool *util.WidgetPool, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *GenrePage {
|
||||||
g := &GenrePage{
|
g := &GenrePage{
|
||||||
genre: genre,
|
genre: genre,
|
||||||
|
pool: pool,
|
||||||
filter: mediaprovider.AlbumFilter{Genres: []string{genre}},
|
filter: mediaprovider.AlbumFilter{Genres: []string{genre}},
|
||||||
contr: contr,
|
contr: contr,
|
||||||
pm: pm,
|
pm: pm,
|
||||||
@@ -52,11 +55,17 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba
|
|||||||
SizeName: theme.SizeNameHeadingText,
|
SizeName: theme.SizeNameHeadingText,
|
||||||
}
|
}
|
||||||
g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs)
|
g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs)
|
||||||
iter := g.mp.IterateAlbums("", g.filter)
|
iter := widgets.NewGridViewAlbumIterator(g.mp.IterateAlbums("", g.filter))
|
||||||
g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im, myTheme.AlbumIcon)
|
if gv := pool.Obtain(util.WidgetTypeGridView); gv != nil {
|
||||||
|
g.grid = gv.(*widgets.GridView)
|
||||||
|
g.grid.Placeholder = myTheme.AlbumIcon
|
||||||
|
g.grid.Reset(iter)
|
||||||
|
} else {
|
||||||
|
g.grid = widgets.NewGridView(iter, g.im, myTheme.AlbumIcon)
|
||||||
|
}
|
||||||
g.contr.ConnectAlbumGridActions(g.grid)
|
g.contr.ConnectAlbumGridActions(g.grid)
|
||||||
g.createSearchAndFilter()
|
g.createSearchAndFilter()
|
||||||
g.createContainer(false)
|
g.createContainer()
|
||||||
|
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
@@ -71,29 +80,28 @@ func (g *GenrePage) createSearchAndFilter() {
|
|||||||
g.filterBtn.OnChanged = g.Reload
|
g.filterBtn.OnChanged = g.Reload
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GenrePage) createContainer(searchGrid bool) {
|
func (g *GenrePage) createContainer() {
|
||||||
searchVbox := container.NewVBox(layout.NewSpacer(), g.searcher, layout.NewSpacer())
|
searchVbox := container.NewVBox(layout.NewSpacer(), g.searcher, layout.NewSpacer())
|
||||||
gr := g.grid
|
|
||||||
if searchGrid {
|
|
||||||
gr = g.searchGrid
|
|
||||||
}
|
|
||||||
playRandomVbox := container.NewVBox(layout.NewSpacer(), g.playRandom, layout.NewSpacer())
|
playRandomVbox := container.NewVBox(layout.NewSpacer(), g.playRandom, layout.NewSpacer())
|
||||||
g.container = container.NewBorder(
|
g.container = container.NewBorder(
|
||||||
container.NewHBox(util.NewHSpace(6),
|
container.NewHBox(util.NewHSpace(6),
|
||||||
g.titleDisp, playRandomVbox, layout.NewSpacer(), container.NewCenter(g.filterBtn), searchVbox, util.NewHSpace(15)),
|
g.titleDisp, playRandomVbox, layout.NewSpacer(), container.NewCenter(g.filterBtn), searchVbox, util.NewHSpace(15)),
|
||||||
nil, nil, nil, gr,
|
nil, nil, nil, g.grid,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreGenrePage(saved *savedGenrePage) *GenrePage {
|
func restoreGenrePage(saved *savedGenrePage) *GenrePage {
|
||||||
g := &GenrePage{
|
g := &GenrePage{
|
||||||
genre: saved.genre,
|
genre: saved.genre,
|
||||||
contr: saved.contr,
|
pool: saved.pool,
|
||||||
pm: saved.pm,
|
contr: saved.contr,
|
||||||
mp: saved.mp,
|
pm: saved.pm,
|
||||||
im: saved.im,
|
mp: saved.mp,
|
||||||
searchText: saved.searchText,
|
im: saved.im,
|
||||||
filter: saved.filter,
|
gridState: saved.gridState,
|
||||||
|
searchGridState: saved.searchGridState,
|
||||||
|
searchText: saved.searchText,
|
||||||
|
filter: saved.filter,
|
||||||
}
|
}
|
||||||
g.ExtendBaseWidget(g)
|
g.ExtendBaseWidget(g)
|
||||||
|
|
||||||
@@ -102,12 +110,19 @@ func restoreGenrePage(saved *savedGenrePage) *GenrePage {
|
|||||||
SizeName: theme.SizeNameHeadingText,
|
SizeName: theme.SizeNameHeadingText,
|
||||||
}
|
}
|
||||||
g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs)
|
g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs)
|
||||||
g.grid = widgets.NewGridViewFromState(saved.gridState)
|
state := saved.gridState
|
||||||
g.createSearchAndFilter()
|
|
||||||
if g.searchText != "" {
|
if g.searchText != "" {
|
||||||
g.searchGrid = widgets.NewGridViewFromState(saved.searchGridState)
|
state = saved.searchGridState
|
||||||
}
|
}
|
||||||
g.createContainer(saved.searchText != "")
|
if gv := g.pool.Obtain(util.WidgetTypeGridView); gv != nil {
|
||||||
|
g.grid = gv.(*widgets.GridView)
|
||||||
|
g.grid.Placeholder = myTheme.AlbumIcon
|
||||||
|
g.grid.ResetFromState(state)
|
||||||
|
} else {
|
||||||
|
g.grid = widgets.NewGridViewFromState(state)
|
||||||
|
}
|
||||||
|
g.createSearchAndFilter()
|
||||||
|
g.createContainer()
|
||||||
|
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
@@ -126,24 +141,29 @@ func (g *GenrePage) Reload() {
|
|||||||
} else {
|
} else {
|
||||||
iter := g.mp.IterateAlbums("", g.filter)
|
iter := g.mp.IterateAlbums("", g.filter)
|
||||||
g.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
g.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
||||||
g.grid.Refresh()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GenrePage) Save() SavedPage {
|
func (g *GenrePage) Save() SavedPage {
|
||||||
sg := &savedGenrePage{
|
sg := &savedGenrePage{
|
||||||
genre: g.genre,
|
genre: g.genre,
|
||||||
filter: g.filter,
|
pool: g.pool,
|
||||||
searchText: g.searchText,
|
filter: g.filter,
|
||||||
contr: g.contr,
|
searchText: g.searchText,
|
||||||
pm: g.pm,
|
contr: g.contr,
|
||||||
mp: g.mp,
|
pm: g.pm,
|
||||||
im: g.im,
|
mp: g.mp,
|
||||||
gridState: g.grid.SaveToState(),
|
im: g.im,
|
||||||
|
gridState: g.gridState,
|
||||||
|
searchGridState: g.searchGridState,
|
||||||
}
|
}
|
||||||
if g.searchGrid != nil {
|
if g.searchText != "" {
|
||||||
sg.searchGridState = g.searchGrid.SaveToState()
|
sg.searchGridState = g.grid.SaveToState()
|
||||||
|
} else {
|
||||||
|
sg.gridState = g.grid.SaveToState()
|
||||||
}
|
}
|
||||||
|
g.grid.Clear()
|
||||||
|
g.pool.Release(util.WidgetTypeGridView, g.grid)
|
||||||
return sg
|
return sg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,28 +174,20 @@ func (g *GenrePage) SearchWidget() fyne.Focusable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GenrePage) OnSearched(query string) {
|
func (g *GenrePage) OnSearched(query string) {
|
||||||
g.searchText = query
|
|
||||||
if query == "" {
|
if query == "" {
|
||||||
g.container.Objects[0] = g.grid
|
g.grid.ResetFromState(g.gridState)
|
||||||
if g.searchGrid != nil {
|
} else {
|
||||||
g.searchGrid.Clear()
|
g.doSearch(query)
|
||||||
}
|
|
||||||
g.Refresh()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
g.doSearch(query)
|
g.searchText = query
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GenrePage) doSearch(query string) {
|
func (g *GenrePage) doSearch(query string) {
|
||||||
iter := g.mp.SearchAlbums(query, g.filter)
|
if g.searchText == "" {
|
||||||
if g.searchGrid == nil {
|
g.gridState = g.grid.SaveToState()
|
||||||
g.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im, myTheme.AlbumIcon)
|
|
||||||
g.contr.ConnectAlbumGridActions(g.searchGrid)
|
|
||||||
} else {
|
|
||||||
g.searchGrid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
|
||||||
}
|
}
|
||||||
g.container.Objects[0] = g.searchGrid
|
iter := g.mp.SearchAlbums(query, g.filter)
|
||||||
g.Refresh()
|
g.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GenrePage) playRandomSongs() {
|
func (g *GenrePage) playRandomSongs() {
|
||||||
@@ -185,13 +197,14 @@ func (g *GenrePage) playRandomSongs() {
|
|||||||
type savedGenrePage struct {
|
type savedGenrePage struct {
|
||||||
genre string
|
genre string
|
||||||
searchText string
|
searchText string
|
||||||
|
pool *util.WidgetPool
|
||||||
filter mediaprovider.AlbumFilter
|
filter mediaprovider.AlbumFilter
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
gridState widgets.GridViewState
|
gridState *widgets.GridViewState
|
||||||
searchGridState widgets.GridViewState
|
searchGridState *widgets.GridViewState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *savedGenrePage) Restore() Page {
|
func (s *savedGenrePage) Restore() Page {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type NowPlayingPage struct {
|
|||||||
|
|
||||||
type nowPlayingPageState struct {
|
type nowPlayingPageState struct {
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
|
pool *util.WidgetPool
|
||||||
conf *backend.NowPlayingPageConfig
|
conf *backend.NowPlayingPageConfig
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
p *player.Player
|
p *player.Player
|
||||||
@@ -46,30 +47,40 @@ type nowPlayingPageState struct {
|
|||||||
func NewNowPlayingPage(
|
func NewNowPlayingPage(
|
||||||
highlightedTrackID string,
|
highlightedTrackID string,
|
||||||
contr *controller.Controller,
|
contr *controller.Controller,
|
||||||
|
pool *util.WidgetPool,
|
||||||
conf *backend.NowPlayingPageConfig,
|
conf *backend.NowPlayingPageConfig,
|
||||||
pm *backend.PlaybackManager,
|
pm *backend.PlaybackManager,
|
||||||
p *player.Player, // TODO: once other player backends are supported (eg uPnP), refactor
|
p *player.Player, // TODO: once other player backends are supported (eg uPnP), refactor
|
||||||
) *NowPlayingPage {
|
) *NowPlayingPage {
|
||||||
a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, conf: conf, pm: pm, p: p}}
|
a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{
|
||||||
|
contr: contr, pool: pool, conf: conf, pm: pm, p: p,
|
||||||
|
}}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
|
|
||||||
p.OnPaused(a.formatStatusLine)
|
p.OnPaused(a.formatStatusLine)
|
||||||
p.OnPlaying(a.formatStatusLine)
|
p.OnPlaying(a.formatStatusLine)
|
||||||
p.OnStopped(a.formatStatusLine)
|
p.OnStopped(a.formatStatusLine)
|
||||||
|
|
||||||
a.tracklist = widgets.NewTracklist(nil)
|
if t := a.pool.Obtain(util.WidgetTypeTracklist); t != nil {
|
||||||
|
a.tracklist = t.(*widgets.Tracklist)
|
||||||
|
a.tracklist.Reset()
|
||||||
|
} else {
|
||||||
|
a.tracklist = widgets.NewTracklist(nil)
|
||||||
|
}
|
||||||
a.tracklist.SetVisibleColumns(conf.TracklistColumns)
|
a.tracklist.SetVisibleColumns(conf.TracklistColumns)
|
||||||
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
a.conf.TracklistColumns = cols
|
a.conf.TracklistColumns = cols
|
||||||
}
|
}
|
||||||
a.tracklist.AutoNumber = true
|
a.tracklist.Options = widgets.TracklistOptions{
|
||||||
a.tracklist.DisablePlaybackMenu = true
|
AutoNumber: true,
|
||||||
|
DisablePlaybackMenu: true,
|
||||||
|
AuxiliaryMenuItems: []*fyne.MenuItem{
|
||||||
|
fyne.NewMenuItem("Remove from queue", a.onRemoveSelectedFromQueue),
|
||||||
|
},
|
||||||
|
}
|
||||||
contr.ConnectTracklistActions(a.tracklist)
|
contr.ConnectTracklistActions(a.tracklist)
|
||||||
// override the default OnPlayTrackAt handler b/c we don't need to re-load the tracks into the queue
|
// 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.OnPlayTrackAt = a.onPlayTrackAt
|
||||||
a.tracklist.AuxiliaryMenuItems = []*fyne.MenuItem{
|
|
||||||
fyne.NewMenuItem("Remove from queue", a.onRemoveSelectedFromQueue),
|
|
||||||
}
|
|
||||||
a.title = widget.NewRichTextWithText("Now Playing")
|
a.title = widget.NewRichTextWithText("Now Playing")
|
||||||
a.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName
|
a.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName
|
||||||
a.statusLabel = widget.NewRichTextWithText("Stopped")
|
a.statusLabel = widget.NewRichTextWithText("Stopped")
|
||||||
@@ -88,6 +99,8 @@ func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *NowPlayingPage) Save() SavedPage {
|
func (a *NowPlayingPage) Save() SavedPage {
|
||||||
|
a.tracklist.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeTracklist, a.tracklist)
|
||||||
nps := a.nowPlayingPageState
|
nps := a.nowPlayingPageState
|
||||||
return &nps
|
return &nps
|
||||||
}
|
}
|
||||||
@@ -205,5 +218,5 @@ func (a *NowPlayingPage) load(highlightedTrackID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *nowPlayingPageState) Restore() Page {
|
func (s *nowPlayingPageState) Restore() Page {
|
||||||
return NewNowPlayingPage("", s.contr, s.conf, s.pm, s.p)
|
return NewNowPlayingPage("", s.contr, s.pool, s.conf, s.pm, s.p)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type PlaylistPage struct {
|
|||||||
|
|
||||||
playlistPageState
|
playlistPageState
|
||||||
|
|
||||||
|
disposed bool
|
||||||
header *PlaylistPageHeader
|
header *PlaylistPageHeader
|
||||||
tracklist *widgets.Tracklist
|
tracklist *widgets.Tracklist
|
||||||
tracks []*mediaprovider.Track
|
tracks []*mediaprovider.Track
|
||||||
@@ -35,6 +36,7 @@ type playlistPageState struct {
|
|||||||
playlistID string
|
playlistID string
|
||||||
conf *backend.PlaylistPageConfig
|
conf *backend.PlaylistPageConfig
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
|
widgetPool *util.WidgetPool
|
||||||
sm *backend.ServerManager
|
sm *backend.ServerManager
|
||||||
pm *backend.PlaybackManager
|
pm *backend.PlaybackManager
|
||||||
im *backend.ImageManager
|
im *backend.ImageManager
|
||||||
@@ -44,27 +46,41 @@ type playlistPageState struct {
|
|||||||
func NewPlaylistPage(
|
func NewPlaylistPage(
|
||||||
playlistID string,
|
playlistID string,
|
||||||
conf *backend.PlaylistPageConfig,
|
conf *backend.PlaylistPageConfig,
|
||||||
|
pool *util.WidgetPool,
|
||||||
contr *controller.Controller,
|
contr *controller.Controller,
|
||||||
sm *backend.ServerManager,
|
sm *backend.ServerManager,
|
||||||
pm *backend.PlaybackManager,
|
pm *backend.PlaybackManager,
|
||||||
im *backend.ImageManager,
|
im *backend.ImageManager,
|
||||||
) *PlaylistPage {
|
) *PlaylistPage {
|
||||||
return newPlaylistPage(playlistID, conf, contr, sm, pm, im, widgets.TracklistSort{})
|
return newPlaylistPage(playlistID, conf, contr, pool, sm, pm, im, widgets.TracklistSort{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPlaylistPage(
|
func newPlaylistPage(
|
||||||
playlistID string,
|
playlistID string,
|
||||||
conf *backend.PlaylistPageConfig,
|
conf *backend.PlaylistPageConfig,
|
||||||
contr *controller.Controller,
|
contr *controller.Controller,
|
||||||
|
pool *util.WidgetPool,
|
||||||
sm *backend.ServerManager,
|
sm *backend.ServerManager,
|
||||||
pm *backend.PlaybackManager,
|
pm *backend.PlaybackManager,
|
||||||
im *backend.ImageManager,
|
im *backend.ImageManager,
|
||||||
trackSort widgets.TracklistSort,
|
trackSort widgets.TracklistSort,
|
||||||
) *PlaylistPage {
|
) *PlaylistPage {
|
||||||
a := &PlaylistPage{playlistPageState: playlistPageState{playlistID: playlistID, conf: conf, contr: contr, sm: sm, pm: pm, im: im}}
|
a := &PlaylistPage{playlistPageState: playlistPageState{playlistID: playlistID, conf: conf, contr: contr, widgetPool: pool, sm: sm, pm: pm, im: im}}
|
||||||
a.ExtendBaseWidget(a)
|
a.ExtendBaseWidget(a)
|
||||||
a.header = NewPlaylistPageHeader(a)
|
if h := a.widgetPool.Obtain(util.WidgetTypePlaylistPageHeader); h != nil {
|
||||||
a.tracklist = widgets.NewTracklist(nil)
|
a.header = h.(*PlaylistPageHeader)
|
||||||
|
a.header.page = a
|
||||||
|
a.header.Clear()
|
||||||
|
} else {
|
||||||
|
a.header = NewPlaylistPageHeader(a)
|
||||||
|
}
|
||||||
|
a.header.page = a
|
||||||
|
if tl := a.widgetPool.Obtain(util.WidgetTypeTracklist); tl != nil {
|
||||||
|
a.tracklist = tl.(*widgets.Tracklist)
|
||||||
|
a.tracklist.Reset()
|
||||||
|
} else {
|
||||||
|
a.tracklist = widgets.NewTracklist(nil)
|
||||||
|
}
|
||||||
a.tracklist.SetVisibleColumns(conf.TracklistColumns)
|
a.tracklist.SetVisibleColumns(conf.TracklistColumns)
|
||||||
a.tracklist.SetSorting(trackSort)
|
a.tracklist.SetSorting(trackSort)
|
||||||
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
@@ -77,8 +93,10 @@ func newPlaylistPage(
|
|||||||
fyne.NewMenuItem("Move down", a.onMoveSelectedDown),
|
fyne.NewMenuItem("Move down", a.onMoveSelectedDown),
|
||||||
fyne.NewMenuItem("Move to bottom", a.onMoveSelectedToBottom),
|
fyne.NewMenuItem("Move to bottom", a.onMoveSelectedToBottom),
|
||||||
}...)
|
}...)
|
||||||
a.tracklist.AuxiliaryMenuItems = []*fyne.MenuItem{reorderMenu,
|
a.tracklist.Options = widgets.TracklistOptions{
|
||||||
fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)}
|
AuxiliaryMenuItems: []*fyne.MenuItem{reorderMenu,
|
||||||
|
fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)},
|
||||||
|
}
|
||||||
// connect tracklist actions
|
// connect tracklist actions
|
||||||
a.contr.ConnectTracklistActions(a.tracklist)
|
a.contr.ConnectTracklistActions(a.tracklist)
|
||||||
|
|
||||||
@@ -94,8 +112,12 @@ func (a *PlaylistPage) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *PlaylistPage) Save() SavedPage {
|
func (a *PlaylistPage) Save() SavedPage {
|
||||||
|
a.disposed = true
|
||||||
p := a.playlistPageState
|
p := a.playlistPageState
|
||||||
p.trackSort = a.tracklist.Sorting()
|
p.trackSort = a.tracklist.Sorting()
|
||||||
|
p.widgetPool.Release(util.WidgetTypePlaylistPageHeader, a.header)
|
||||||
|
a.tracklist.Clear()
|
||||||
|
p.widgetPool.Release(util.WidgetTypeTracklist, a.tracklist)
|
||||||
return &p
|
return &p
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +154,9 @@ func (a *PlaylistPage) load() {
|
|||||||
log.Printf("Failed to get playlist: %s", err.Error())
|
log.Printf("Failed to get playlist: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if a.disposed {
|
||||||
|
return
|
||||||
|
}
|
||||||
renumberTracks(playlist.Tracks)
|
renumberTracks(playlist.Tracks)
|
||||||
a.tracks = playlist.Tracks
|
a.tracks = playlist.Tracks
|
||||||
a.tracklist.SetTracks(playlist.Tracks)
|
a.tracklist.SetTracks(playlist.Tracks)
|
||||||
@@ -200,7 +225,7 @@ func (a *PlaylistPage) onRemoveSelectedFromPlaylist() {
|
|||||||
}
|
}
|
||||||
a.sm.Server.EditPlaylistTracks(a.playlistID, nil, idxs)
|
a.sm.Server.EditPlaylistTracks(a.playlistID, nil, idxs)
|
||||||
a.tracklist.UnselectAll()
|
a.tracklist.UnselectAll()
|
||||||
go a.Reload()
|
a.Reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlaylistPageHeader struct {
|
type PlaylistPageHeader struct {
|
||||||
@@ -280,6 +305,14 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *PlaylistPageHeader) Clear() {
|
||||||
|
a.titleLabel.Segments[0].(*widget.TextSegment).Text = ""
|
||||||
|
a.createdAtLabel.Text = ""
|
||||||
|
a.descriptionLabel.Text = ""
|
||||||
|
a.ownerLabel.Text = ""
|
||||||
|
a.image.SetImage(nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *PlaylistPageHeader) CreateRenderer() fyne.WidgetRenderer {
|
func (a *PlaylistPageHeader) CreateRenderer() fyne.WidgetRenderer {
|
||||||
return widget.NewSimpleRenderer(a.container)
|
return widget.NewSimpleRenderer(a.container)
|
||||||
}
|
}
|
||||||
@@ -325,5 +358,5 @@ func (a *PlaylistPageHeader) formatPlaylistTrackTimeStr(p *mediaprovider.Playlis
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *playlistPageState) Restore() Page {
|
func (s *playlistPageState) Restore() Page {
|
||||||
return newPlaylistPage(s.playlistID, s.conf, s.contr, s.sm, s.pm, s.im, s.trackSort)
|
return newPlaylistPage(s.playlistID, s.conf, s.contr, s.widgetPool, s.sm, s.pm, s.im, s.trackSort)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/dweymouth/supersonic/ui/controller"
|
"github.com/dweymouth/supersonic/ui/controller"
|
||||||
"github.com/dweymouth/supersonic/ui/layouts"
|
"github.com/dweymouth/supersonic/ui/layouts"
|
||||||
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||||
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
"github.com/dweymouth/supersonic/ui/widgets"
|
"github.com/dweymouth/supersonic/ui/widgets"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
@@ -25,6 +26,7 @@ import (
|
|||||||
type PlaylistsPage struct {
|
type PlaylistsPage struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
|
pool *util.WidgetPool
|
||||||
cfg *backend.PlaylistsPageConfig
|
cfg *backend.PlaylistsPageConfig
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
@@ -39,16 +41,17 @@ type PlaylistsPage struct {
|
|||||||
gridView *widgets.GridView
|
gridView *widgets.GridView
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, mp mediaprovider.MediaProvider) *PlaylistsPage {
|
func NewPlaylistsPage(contr *controller.Controller, pool *util.WidgetPool, cfg *backend.PlaylistsPageConfig, mp mediaprovider.MediaProvider) *PlaylistsPage {
|
||||||
activeView := 0
|
activeView := 0
|
||||||
if cfg.InitialView == "Grid" {
|
if cfg.InitialView == "Grid" {
|
||||||
activeView = 1
|
activeView = 1
|
||||||
}
|
}
|
||||||
return newPlaylistsPage(contr, cfg, mp, "", activeView)
|
return newPlaylistsPage(contr, pool, cfg, mp, "", activeView)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, mp mediaprovider.MediaProvider, searchText string, activeView int) *PlaylistsPage {
|
func newPlaylistsPage(contr *controller.Controller, pool *util.WidgetPool, cfg *backend.PlaylistsPageConfig, mp mediaprovider.MediaProvider, searchText string, activeView int) *PlaylistsPage {
|
||||||
a := &PlaylistsPage{
|
a := &PlaylistsPage{
|
||||||
|
pool: pool,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
mp: mp,
|
mp: mp,
|
||||||
contr: contr,
|
contr: contr,
|
||||||
@@ -95,7 +98,13 @@ func (a *PlaylistsPage) createListView() {
|
|||||||
|
|
||||||
func (a *PlaylistsPage) createGridView(playlists []*mediaprovider.Playlist) {
|
func (a *PlaylistsPage) createGridView(playlists []*mediaprovider.Playlist) {
|
||||||
model := createPlaylistGridViewModel(playlists)
|
model := createPlaylistGridViewModel(playlists)
|
||||||
a.gridView = widgets.NewFixedGridView(model, a.contr.App.ImageManager, myTheme.PlaylistIcon)
|
if g := a.pool.Obtain(util.WidgetTypeGridView); g != nil {
|
||||||
|
a.gridView = g.(*widgets.GridView)
|
||||||
|
a.gridView.Placeholder = myTheme.PlaylistIcon
|
||||||
|
a.gridView.ResetFixed(model)
|
||||||
|
} else {
|
||||||
|
a.gridView = widgets.NewFixedGridView(model, a.contr.App.ImageManager, myTheme.PlaylistIcon)
|
||||||
|
}
|
||||||
a.gridView.OnPlay = func(id string, shuffle bool) {
|
a.gridView.OnPlay = func(id string, shuffle bool) {
|
||||||
go a.contr.App.PlaybackManager.PlayPlaylist(id, 0, shuffle)
|
go a.contr.App.PlaybackManager.PlayPlaylist(id, 0, shuffle)
|
||||||
}
|
}
|
||||||
@@ -103,6 +112,7 @@ func (a *PlaylistsPage) createGridView(playlists []*mediaprovider.Playlist) {
|
|||||||
go a.contr.App.PlaybackManager.LoadPlaylist(id, true, false)
|
go a.contr.App.PlaybackManager.LoadPlaylist(id, true, false)
|
||||||
}
|
}
|
||||||
a.gridView.OnShowItemPage = a.showPlaylistPage
|
a.gridView.OnShowItemPage = a.showPlaylistPage
|
||||||
|
a.gridView.OnShowSecondaryPage = nil
|
||||||
a.gridView.OnAddToPlaylist = func(id string) {
|
a.gridView.OnAddToPlaylist = func(id string) {
|
||||||
go func() {
|
go func() {
|
||||||
pl, err := a.contr.App.ServerManager.Server.GetPlaylist(id)
|
pl, err := a.contr.App.ServerManager.Server.GetPlaylist(id)
|
||||||
@@ -221,17 +231,24 @@ func (a *PlaylistsPage) Reload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *PlaylistsPage) Save() SavedPage {
|
func (a *PlaylistsPage) Save() SavedPage {
|
||||||
return &savedPlaylistsPage{
|
s := &savedPlaylistsPage{
|
||||||
contr: a.contr,
|
contr: a.contr,
|
||||||
|
pool: a.pool,
|
||||||
cfg: a.cfg,
|
cfg: a.cfg,
|
||||||
mp: a.mp,
|
mp: a.mp,
|
||||||
searchText: a.searcher.Entry.Text,
|
searchText: a.searcher.Entry.Text,
|
||||||
activeView: a.viewToggle.ActivatedButtonIndex(),
|
activeView: a.viewToggle.ActivatedButtonIndex(),
|
||||||
}
|
}
|
||||||
|
if a.gridView != nil {
|
||||||
|
a.gridView.Clear()
|
||||||
|
a.pool.Release(util.WidgetTypeGridView, a.gridView)
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
type savedPlaylistsPage struct {
|
type savedPlaylistsPage struct {
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
|
pool *util.WidgetPool
|
||||||
cfg *backend.PlaylistsPageConfig
|
cfg *backend.PlaylistsPageConfig
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
searchText string
|
searchText string
|
||||||
@@ -239,7 +256,7 @@ type savedPlaylistsPage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *savedPlaylistsPage) Restore() Page {
|
func (s *savedPlaylistsPage) Restore() Page {
|
||||||
return newPlaylistsPage(s.contr, s.cfg, s.mp, s.searchText, s.activeView)
|
return newPlaylistsPage(s.contr, s.pool, s.cfg, s.mp, s.searchText, s.activeView)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *PlaylistsPage) buildContainer(initialView fyne.CanvasObject) {
|
func (a *PlaylistsPage) buildContainer(initialView fyne.CanvasObject) {
|
||||||
|
|||||||
+13
-10
@@ -3,6 +3,7 @@ package browsing
|
|||||||
import (
|
import (
|
||||||
"github.com/dweymouth/supersonic/backend"
|
"github.com/dweymouth/supersonic/backend"
|
||||||
"github.com/dweymouth/supersonic/ui/controller"
|
"github.com/dweymouth/supersonic/ui/controller"
|
||||||
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NavigationHandler interface {
|
type NavigationHandler interface {
|
||||||
@@ -14,6 +15,7 @@ type Router struct {
|
|||||||
App *backend.App
|
App *backend.App
|
||||||
Controller *controller.Controller
|
Controller *controller.Controller
|
||||||
Nav NavigationHandler
|
Nav NavigationHandler
|
||||||
|
widgetPool *util.WidgetPool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(app *backend.App, controller *controller.Controller, nav NavigationHandler) Router {
|
func NewRouter(app *backend.App, controller *controller.Controller, nav NavigationHandler) Router {
|
||||||
@@ -21,6 +23,7 @@ func NewRouter(app *backend.App, controller *controller.Controller, nav Navigati
|
|||||||
App: app,
|
App: app,
|
||||||
Controller: controller,
|
Controller: controller,
|
||||||
Nav: nav,
|
Nav: nav,
|
||||||
|
widgetPool: util.NewWidgetPool(),
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -28,27 +31,27 @@ func NewRouter(app *backend.App, controller *controller.Controller, nav Navigati
|
|||||||
func (r Router) CreatePage(rte controller.Route) Page {
|
func (r Router) CreatePage(rte controller.Route) Page {
|
||||||
switch rte.Page {
|
switch rte.Page {
|
||||||
case controller.Album:
|
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.widgetPool, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
|
||||||
case controller.Albums:
|
case controller.Albums:
|
||||||
return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
|
return NewAlbumsPage(&r.App.Config.AlbumsPage, r.widgetPool, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
|
||||||
case controller.Artist:
|
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.widgetPool, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
|
||||||
case controller.Artists:
|
case controller.Artists:
|
||||||
return NewArtistsPage(r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
|
return NewArtistsPage(r.Controller, r.widgetPool, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
|
||||||
case controller.Favorites:
|
case controller.Favorites:
|
||||||
return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager)
|
return NewFavoritesPage(&r.App.Config.FavoritesPage, r.widgetPool, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager)
|
||||||
case controller.Genre:
|
case controller.Genre:
|
||||||
return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
|
return NewGenrePage(rte.Arg, r.widgetPool, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
|
||||||
case controller.Genres:
|
case controller.Genres:
|
||||||
return NewGenresPage(r.Controller, r.App.ServerManager.Server)
|
return NewGenresPage(r.Controller, r.App.ServerManager.Server)
|
||||||
case controller.NowPlaying:
|
case controller.NowPlaying:
|
||||||
return NewNowPlayingPage(rte.Arg, r.Controller, &r.App.Config.NowPlayingPage, r.App.PlaybackManager, r.App.Player)
|
return NewNowPlayingPage(rte.Arg, r.Controller, r.widgetPool, &r.App.Config.NowPlayingPage, r.App.PlaybackManager, r.App.Player)
|
||||||
case controller.Playlist:
|
case controller.Playlist:
|
||||||
return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
|
return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.widgetPool, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
|
||||||
case controller.Playlists:
|
case controller.Playlists:
|
||||||
return NewPlaylistsPage(r.Controller, &r.App.Config.PlaylistsPage, r.App.ServerManager.Server)
|
return NewPlaylistsPage(r.Controller, r.widgetPool, &r.App.Config.PlaylistsPage, r.App.ServerManager.Server)
|
||||||
case controller.Tracks:
|
case controller.Tracks:
|
||||||
return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.App.ServerManager.Server)
|
return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.widgetPool, r.App.ServerManager.Server)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/dweymouth/supersonic/ui/controller"
|
"github.com/dweymouth/supersonic/ui/controller"
|
||||||
"github.com/dweymouth/supersonic/ui/layouts"
|
"github.com/dweymouth/supersonic/ui/layouts"
|
||||||
"github.com/dweymouth/supersonic/ui/theme"
|
"github.com/dweymouth/supersonic/ui/theme"
|
||||||
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
"github.com/dweymouth/supersonic/ui/widgets"
|
"github.com/dweymouth/supersonic/ui/widgets"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
@@ -34,18 +35,26 @@ type TracksPage struct {
|
|||||||
|
|
||||||
type tracksPageState struct {
|
type tracksPageState struct {
|
||||||
searchText string
|
searchText string
|
||||||
|
widgetPool *util.WidgetPool
|
||||||
contr *controller.Controller
|
contr *controller.Controller
|
||||||
conf *backend.TracksPageConfig
|
conf *backend.TracksPageConfig
|
||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, mp mediaprovider.MediaProvider) *TracksPage {
|
func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, pool *util.WidgetPool, mp mediaprovider.MediaProvider) *TracksPage {
|
||||||
t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, mp: mp}}
|
t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, widgetPool: pool, mp: mp}}
|
||||||
t.ExtendBaseWidget(t)
|
t.ExtendBaseWidget(t)
|
||||||
|
|
||||||
t.tracklist = widgets.NewTracklist(nil)
|
if tl := t.widgetPool.Obtain(util.WidgetTypeTracklist); tl != nil {
|
||||||
t.tracklist.DisableSorting = true
|
t.tracklist = tl.(*widgets.Tracklist)
|
||||||
t.tracklist.AutoNumber = true
|
t.tracklist.Reset()
|
||||||
|
} else {
|
||||||
|
t.tracklist = widgets.NewTracklist(nil)
|
||||||
|
}
|
||||||
|
t.tracklist.Options = widgets.TracklistOptions{
|
||||||
|
DisableSorting: true,
|
||||||
|
AutoNumber: true,
|
||||||
|
}
|
||||||
t.tracklist.SetVisibleColumns(conf.TracklistColumns)
|
t.tracklist.SetVisibleColumns(conf.TracklistColumns)
|
||||||
t.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
t.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
t.conf.TracklistColumns = cols
|
t.conf.TracklistColumns = cols
|
||||||
@@ -119,7 +128,7 @@ func (t *TracksPage) OnSearched(query string) {
|
|||||||
func (t *TracksPage) doSearch(query string) {
|
func (t *TracksPage) doSearch(query string) {
|
||||||
if t.searchTracklist == nil {
|
if t.searchTracklist == nil {
|
||||||
t.searchTracklist = widgets.NewTracklist(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.SetVisibleColumns(t.conf.TracklistColumns)
|
||||||
t.searchTracklist.SetNowPlaying(t.nowPlayingID)
|
t.searchTracklist.SetNowPlaying(t.nowPlayingID)
|
||||||
t.searchTracklist.OnVisibleColumnsChanged = func(cols []string) {
|
t.searchTracklist.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
@@ -141,12 +150,15 @@ func (t *TracksPage) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TracksPage) Save() SavedPage {
|
func (t *TracksPage) Save() SavedPage {
|
||||||
|
t.loader.Dispose()
|
||||||
|
t.tracklist.Clear()
|
||||||
|
t.widgetPool.Release(util.WidgetTypeTracklist, t.tracklist)
|
||||||
state := t.tracksPageState
|
state := t.tracksPageState
|
||||||
return &state
|
return &state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *tracksPageState) Restore() Page {
|
func (s *tracksPageState) Restore() Page {
|
||||||
t := NewTracksPage(s.contr, s.conf, s.mp)
|
t := NewTracksPage(s.contr, s.conf, s.widgetPool, s.mp)
|
||||||
t.searchText = s.searchText
|
t.searchText = s.searchText
|
||||||
if t.searchText != "" {
|
if t.searchText != "" {
|
||||||
t.searcher.Entry.Text = t.searchText
|
t.searcher.Entry.Text = t.searchText
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WidgetType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
WidgetTypeAlbumPageHeader WidgetType = iota
|
||||||
|
WidgetTypeArtistPageHeader
|
||||||
|
WidgetTypeGridView
|
||||||
|
WidgetTypePlaylistPageHeader
|
||||||
|
WidgetTypeTracklist
|
||||||
|
|
||||||
|
// keep at bottom
|
||||||
|
numWidgetTypes
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
multipleItemsExpiry = 2 * time.Minute
|
||||||
|
singleItemExpiry = 5 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
// A pool to share commonly-used widgets across pages to reduce
|
||||||
|
// creation of new widgets and memory allocations.
|
||||||
|
type WidgetPool struct {
|
||||||
|
mut sync.Mutex
|
||||||
|
pools [][]pooledWidget
|
||||||
|
}
|
||||||
|
|
||||||
|
type pooledWidget struct {
|
||||||
|
widget fyne.CanvasObject
|
||||||
|
releasedAt int64 // unixMillis
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWidgetPool() *WidgetPool {
|
||||||
|
p := &WidgetPool{
|
||||||
|
pools: make([][]pooledWidget, numWidgetTypes),
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
t := time.NewTicker(2 * time.Minute)
|
||||||
|
for range t.C {
|
||||||
|
p.cleanUpExpiredItems()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain obtains a widget of the given type from the pool, if one exists.
|
||||||
|
// Returns nil if there is no available widget.
|
||||||
|
func (w *WidgetPool) Obtain(typ WidgetType) fyne.CanvasObject {
|
||||||
|
w.mut.Lock()
|
||||||
|
defer w.mut.Unlock()
|
||||||
|
var widget fyne.CanvasObject
|
||||||
|
if l := len(w.pools[typ]); l > 0 {
|
||||||
|
i := l - 1
|
||||||
|
widget = w.pools[typ][i].widget
|
||||||
|
w.pools[typ][i].widget = nil
|
||||||
|
w.pools[typ] = w.pools[typ][:i]
|
||||||
|
}
|
||||||
|
return widget
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases a widget into the pool.
|
||||||
|
// The widget must not be modified by the releaser after release,
|
||||||
|
// since it may be Obtained for a new use at any time.
|
||||||
|
func (w *WidgetPool) Release(typ WidgetType, wid fyne.CanvasObject) {
|
||||||
|
w.mut.Lock()
|
||||||
|
defer w.mut.Unlock()
|
||||||
|
w.pools[typ] = append(w.pools[typ], pooledWidget{
|
||||||
|
widget: wid,
|
||||||
|
releasedAt: time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WidgetPool) cleanUpExpiredItems() {
|
||||||
|
w.mut.Lock()
|
||||||
|
defer w.mut.Unlock()
|
||||||
|
for widTyp, pool := range w.pools {
|
||||||
|
newP := make([]pooledWidget, 0, len(pool))
|
||||||
|
l := len(pool)
|
||||||
|
for _, wid := range pool {
|
||||||
|
timeSinceRelease := time.Since(time.UnixMilli(wid.releasedAt))
|
||||||
|
if l > 1 && timeSinceRelease > multipleItemsExpiry {
|
||||||
|
l-- // let expire if >1 item in pool and released long enough ago
|
||||||
|
} else if l == 1 && timeSinceRelease > singleItemExpiry {
|
||||||
|
l-- // let expire if last item in pool and released long enough ago
|
||||||
|
} else {
|
||||||
|
newP = append(newP, wid) // not expired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// re-assign non-expired items back to this widget type pool
|
||||||
|
w.pools[widTyp] = newP
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_WidgetPool_CleanupExpiredItems(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
threeMinAgo := now.Add(-3 * time.Minute)
|
||||||
|
w := &WidgetPool{
|
||||||
|
pools: [][]pooledWidget{
|
||||||
|
{
|
||||||
|
{releasedAt: threeMinAgo.UnixMilli()},
|
||||||
|
{releasedAt: threeMinAgo.UnixMilli()},
|
||||||
|
{releasedAt: threeMinAgo.UnixMilli()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
w.cleanUpExpiredItems()
|
||||||
|
if l := len(w.pools[0]); l != 1 {
|
||||||
|
t.Errorf("Expected one widget in pool after cleanup, got %d", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
-24
@@ -71,6 +71,8 @@ func NewGridViewAlbumIterator(iter mediaprovider.AlbumIterator) GridViewIterator
|
|||||||
type GridView struct {
|
type GridView struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
|
stateMutex sync.RWMutex
|
||||||
|
fetchCancel context.CancelFunc
|
||||||
GridViewState
|
GridViewState
|
||||||
|
|
||||||
grid *xwidget.GridWrap
|
grid *xwidget.GridWrap
|
||||||
@@ -78,12 +80,10 @@ type GridView struct {
|
|||||||
|
|
||||||
type GridViewState struct {
|
type GridViewState struct {
|
||||||
items []GridViewItemModel
|
items []GridViewItemModel
|
||||||
itemsMutex sync.RWMutex
|
|
||||||
iter GridViewIterator
|
iter GridViewIterator
|
||||||
imageFetcher ImageFetcher
|
imageFetcher ImageFetcher
|
||||||
placeholder fyne.Resource
|
Placeholder fyne.Resource
|
||||||
highestShown int
|
highestShown int
|
||||||
fetchCancel context.CancelFunc
|
|
||||||
done bool
|
done bool
|
||||||
|
|
||||||
OnPlay func(id string, shuffle bool)
|
OnPlay func(id string, shuffle bool)
|
||||||
@@ -104,7 +104,7 @@ func NewFixedGridView(items []GridViewItemModel, fetch ImageFetcher, placeholder
|
|||||||
items: items,
|
items: items,
|
||||||
done: true,
|
done: true,
|
||||||
imageFetcher: fetch,
|
imageFetcher: fetch,
|
||||||
placeholder: placeholder,
|
Placeholder: placeholder,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
g.ExtendBaseWidget(g)
|
g.ExtendBaseWidget(g)
|
||||||
@@ -117,7 +117,7 @@ func NewGridView(iter GridViewIterator, fetch ImageFetcher, placeholder fyne.Res
|
|||||||
GridViewState: GridViewState{
|
GridViewState: GridViewState{
|
||||||
iter: iter,
|
iter: iter,
|
||||||
imageFetcher: fetch,
|
imageFetcher: fetch,
|
||||||
placeholder: placeholder,
|
Placeholder: placeholder,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
g.ExtendBaseWidget(g)
|
g.ExtendBaseWidget(g)
|
||||||
@@ -128,14 +128,16 @@ func NewGridView(iter GridViewIterator, fetch ImageFetcher, placeholder fyne.Res
|
|||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GridView) SaveToState() GridViewState {
|
func (g *GridView) SaveToState() *GridViewState {
|
||||||
|
g.stateMutex.RLock()
|
||||||
s := g.GridViewState
|
s := g.GridViewState
|
||||||
|
g.stateMutex.RUnlock()
|
||||||
s.scrollPos = g.grid.GetScrollOffset()
|
s.scrollPos = g.grid.GetScrollOffset()
|
||||||
return s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGridViewFromState(state GridViewState) *GridView {
|
func NewGridViewFromState(state *GridViewState) *GridView {
|
||||||
g := &GridView{GridViewState: state}
|
g := &GridView{GridViewState: *state}
|
||||||
g.ExtendBaseWidget(g)
|
g.ExtendBaseWidget(g)
|
||||||
g.createGridWrap()
|
g.createGridWrap()
|
||||||
g.Refresh() // needed to initialize the widget
|
g.Refresh() // needed to initialize the widget
|
||||||
@@ -144,8 +146,12 @@ func NewGridViewFromState(state GridViewState) *GridView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GridView) Clear() {
|
func (g *GridView) Clear() {
|
||||||
g.itemsMutex.Lock()
|
if g.fetchCancel != nil {
|
||||||
defer g.itemsMutex.Unlock()
|
g.fetchCancel()
|
||||||
|
g.fetchCancel = nil
|
||||||
|
}
|
||||||
|
g.stateMutex.Lock()
|
||||||
|
defer g.stateMutex.Unlock()
|
||||||
g.items = nil
|
g.items = nil
|
||||||
g.done = true
|
g.done = true
|
||||||
}
|
}
|
||||||
@@ -155,13 +161,27 @@ func (g *GridView) Reset(iter GridViewIterator) {
|
|||||||
g.fetchCancel()
|
g.fetchCancel()
|
||||||
g.fetchCancel = nil
|
g.fetchCancel = nil
|
||||||
}
|
}
|
||||||
g.itemsMutex.Lock()
|
g.stateMutex.Lock()
|
||||||
g.items = nil
|
g.items = nil
|
||||||
g.done = false
|
g.done = false
|
||||||
g.highestShown = 0
|
g.highestShown = 0
|
||||||
g.iter = iter
|
g.iter = iter
|
||||||
g.itemsMutex.Unlock()
|
g.stateMutex.Unlock()
|
||||||
g.fetchMoreItems(36)
|
g.fetchMoreItems(36)
|
||||||
|
g.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GridView) ResetFromState(state *GridViewState) {
|
||||||
|
if g.fetchCancel != nil {
|
||||||
|
g.fetchCancel()
|
||||||
|
g.fetchCancel = nil
|
||||||
|
}
|
||||||
|
g.stateMutex.Lock()
|
||||||
|
g.GridViewState = *state
|
||||||
|
g.stateMutex.Unlock()
|
||||||
|
g.grid.Refresh()
|
||||||
|
g.grid.ScrollToOffset(state.scrollPos)
|
||||||
|
g.grid.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GridView) ResetFixed(items []GridViewItemModel) {
|
func (g *GridView) ResetFixed(items []GridViewItemModel) {
|
||||||
@@ -169,12 +189,13 @@ func (g *GridView) ResetFixed(items []GridViewItemModel) {
|
|||||||
g.fetchCancel()
|
g.fetchCancel()
|
||||||
g.fetchCancel = nil
|
g.fetchCancel = nil
|
||||||
}
|
}
|
||||||
g.itemsMutex.Lock()
|
g.stateMutex.Lock()
|
||||||
g.items = items
|
g.items = items
|
||||||
g.itemsMutex.Unlock()
|
|
||||||
g.done = true
|
g.done = true
|
||||||
g.highestShown = 0
|
g.highestShown = 0
|
||||||
g.iter = nil
|
g.iter = nil
|
||||||
|
g.stateMutex.Unlock()
|
||||||
|
g.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GridView) GetScrollOffset() float32 {
|
func (g *GridView) GetScrollOffset() float32 {
|
||||||
@@ -192,7 +213,7 @@ func (g *GridView) createGridWrap() {
|
|||||||
},
|
},
|
||||||
// create func
|
// create func
|
||||||
func() fyne.CanvasObject {
|
func() fyne.CanvasObject {
|
||||||
card := NewGridViewItem(g.placeholder)
|
card := NewGridViewItem(g.Placeholder)
|
||||||
card.OnPlay = func(shuffle bool) {
|
card.OnPlay = func(shuffle bool) {
|
||||||
if g.OnPlay != nil {
|
if g.OnPlay != nil {
|
||||||
g.OnPlay(card.ItemID(), shuffle)
|
g.OnPlay(card.ItemID(), shuffle)
|
||||||
@@ -220,7 +241,7 @@ func (g *GridView) createGridWrap() {
|
|||||||
}
|
}
|
||||||
card.OnDownload = func() {
|
card.OnDownload = func() {
|
||||||
if g.OnDownload != nil {
|
if g.OnDownload != nil {
|
||||||
g.OnDownload(card.itemID)
|
g.OnDownload(card.ItemID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return card
|
return card
|
||||||
@@ -238,13 +259,14 @@ func (g *GridView) doUpdateItemCard(itemIdx int, card *GridViewItem) {
|
|||||||
g.highestShown = itemIdx
|
g.highestShown = itemIdx
|
||||||
}
|
}
|
||||||
var item GridViewItemModel
|
var item GridViewItemModel
|
||||||
g.itemsMutex.RLock()
|
g.stateMutex.RLock()
|
||||||
// itemIdx can rarely be out of range if the data is being updated
|
// itemIdx can rarely be out of range if the data is being updated
|
||||||
// as the view is requested to refresh
|
// as the view is requested to refresh
|
||||||
if itemIdx < len(g.items) {
|
if itemIdx < len(g.items) {
|
||||||
item = g.items[itemIdx]
|
item = g.items[itemIdx]
|
||||||
}
|
}
|
||||||
g.itemsMutex.RUnlock()
|
g.stateMutex.RUnlock()
|
||||||
|
card.Cover.Im.CenterIcon = g.Placeholder
|
||||||
if card.PrevID == item.ID {
|
if card.PrevID == item.ID {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return
|
return
|
||||||
@@ -290,8 +312,8 @@ func (g *GridView) doUpdateItemCard(itemIdx int, card *GridViewItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GridView) lenItems() int {
|
func (g *GridView) lenItems() int {
|
||||||
g.itemsMutex.RLock()
|
g.stateMutex.RLock()
|
||||||
defer g.itemsMutex.RUnlock()
|
defer g.stateMutex.RUnlock()
|
||||||
return len(g.items)
|
return len(g.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,15 +336,15 @@ func (g *GridView) fetchMoreItems(count int) {
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
g.itemsMutex.Lock()
|
g.stateMutex.Lock()
|
||||||
g.items = append(g.items, items...)
|
g.items = append(g.items, items...)
|
||||||
g.itemsMutex.Unlock()
|
g.stateMutex.Unlock()
|
||||||
if len(items) < batchFetchSize {
|
if len(items) < batchFetchSize {
|
||||||
g.done = true
|
g.done = true
|
||||||
}
|
}
|
||||||
n += len(items)
|
n += len(items)
|
||||||
if len(items) > 0 {
|
if len(items) > 0 {
|
||||||
g.Refresh()
|
g.grid.Refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,6 +211,10 @@ func (g *GridViewItem) Update(model GridViewItemModel) {
|
|||||||
g.Cover.ResetPlayButton()
|
g.Cover.ResetPlayButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GridViewItem) Refresh() {
|
||||||
|
g.BaseWidget.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GridViewItem) ItemID() string {
|
func (g *GridViewItem) ItemID() string {
|
||||||
return g.itemID
|
return g.itemID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import (
|
|||||||
// a placeholder with a rectangular border frame
|
// a placeholder with a rectangular border frame
|
||||||
// and an icon positioned in the center of the frame.
|
// and an icon positioned in the center of the frame.
|
||||||
type ImagePlaceholder struct {
|
type ImagePlaceholder struct {
|
||||||
ScaleMode canvas.ImageScale
|
ScaleMode canvas.ImageScale
|
||||||
|
CenterIcon fyne.Resource
|
||||||
|
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
content *fyne.Container
|
content *fyne.Container
|
||||||
@@ -30,7 +31,7 @@ type ImagePlaceholder struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewImagePlaceholder(centerIcon fyne.Resource, minSize float32) *ImagePlaceholder {
|
func NewImagePlaceholder(centerIcon fyne.Resource, minSize float32) *ImagePlaceholder {
|
||||||
i := &ImagePlaceholder{minSize: minSize}
|
i := &ImagePlaceholder{minSize: minSize, CenterIcon: centerIcon}
|
||||||
i.ExtendBaseWidget(i)
|
i.ExtendBaseWidget(i)
|
||||||
i.iconImage = canvas.NewImageFromResource(centerIcon)
|
i.iconImage = canvas.NewImageFromResource(centerIcon)
|
||||||
i.iconImage.FillMode = canvas.ImageFillContain
|
i.iconImage.FillMode = canvas.ImageFillContain
|
||||||
@@ -86,6 +87,7 @@ func (i *ImagePlaceholder) MinSize() fyne.Size {
|
|||||||
|
|
||||||
func (i *ImagePlaceholder) Refresh() {
|
func (i *ImagePlaceholder) Refresh() {
|
||||||
i.border.Hidden = i.HaveImage()
|
i.border.Hidden = i.HaveImage()
|
||||||
|
i.iconImage.Resource = i.CenterIcon
|
||||||
i.iconImage.Hidden = i.HaveImage()
|
i.iconImage.Hidden = i.HaveImage()
|
||||||
i.imageDisp.Hidden = !i.HaveImage()
|
i.imageDisp.Hidden = !i.HaveImage()
|
||||||
i.imageDisp.ScaleMode = i.ScaleMode
|
i.imageDisp.ScaleMode = i.ScaleMode
|
||||||
|
|||||||
+20
-9
@@ -47,9 +47,7 @@ type TracklistSort struct {
|
|||||||
ColumnName string
|
ColumnName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracklist struct {
|
type TracklistOptions struct {
|
||||||
widget.BaseWidget
|
|
||||||
|
|
||||||
// AutoNumber sets whether to auto-number the tracks 1..N in display order,
|
// AutoNumber sets whether to auto-number the tracks 1..N in display order,
|
||||||
// or to use the number from the track's metadata
|
// or to use the number from the track's metadata
|
||||||
AutoNumber bool
|
AutoNumber bool
|
||||||
@@ -68,6 +66,12 @@ type Tracklist struct {
|
|||||||
|
|
||||||
// Disables sorting the tracklist by clicking individual columns.
|
// Disables sorting the tracklist by clicking individual columns.
|
||||||
DisableSorting bool
|
DisableSorting bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tracklist struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
Options TracklistOptions
|
||||||
|
|
||||||
// user action callbacks
|
// user action callbacks
|
||||||
OnPlayTrackAt func(int)
|
OnPlayTrackAt func(int)
|
||||||
@@ -141,7 +145,7 @@ func NewTracklist(tracks []*mediaprovider.Track) *Tracklist {
|
|||||||
tr := item.(*TrackRow)
|
tr := item.(*TrackRow)
|
||||||
tr.trackIdx = itemID
|
tr.trackIdx = itemID
|
||||||
i := -1 // signal that we want to display the actual track num.
|
i := -1 // signal that we want to display the actual track num.
|
||||||
if t.AutoNumber {
|
if t.Options.AutoNumber {
|
||||||
i = itemID + 1
|
i = itemID + 1
|
||||||
}
|
}
|
||||||
tr.Update(t.trackModelAt(itemID), i)
|
tr.Update(t.trackModelAt(itemID), i)
|
||||||
@@ -153,6 +157,13 @@ func NewTracklist(tracks []*mediaprovider.Track) *Tracklist {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tracklist) Reset() {
|
||||||
|
t.Clear()
|
||||||
|
t.Options = TracklistOptions{}
|
||||||
|
t.ctxMenu = nil
|
||||||
|
t.SetSorting(TracklistSort{})
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tracklist) buildHeader() {
|
func (t *Tracklist) buildHeader() {
|
||||||
t.hdr = NewListHeader([]ListColumn{
|
t.hdr = NewListHeader([]ListColumn{
|
||||||
{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false},
|
{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false},
|
||||||
@@ -331,7 +342,7 @@ func (t *Tracklist) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracklist) Refresh() {
|
func (t *Tracklist) Refresh() {
|
||||||
t.hdr.DisableSorting = t.DisableSorting
|
t.hdr.DisableSorting = t.Options.DisableSorting
|
||||||
t.BaseWidget.Refresh()
|
t.BaseWidget.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,7 +498,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
|||||||
t.list.Refresh()
|
t.list.Refresh()
|
||||||
if t.ctxMenu == nil {
|
if t.ctxMenu == nil {
|
||||||
t.ctxMenu = fyne.NewMenu("")
|
t.ctxMenu = fyne.NewMenu("")
|
||||||
if !t.DisablePlaybackMenu {
|
if !t.Options.DisablePlaybackMenu {
|
||||||
t.ctxMenu.Items = append(t.ctxMenu.Items,
|
t.ctxMenu.Items = append(t.ctxMenu.Items,
|
||||||
fyne.NewMenuItem("Play", func() {
|
fyne.NewMenuItem("Play", func() {
|
||||||
if t.OnPlaySelection != nil {
|
if t.OnPlaySelection != nil {
|
||||||
@@ -530,9 +541,9 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
|||||||
t.onSetRatings(t.selectedTracks(), rating, true)
|
t.onSetRatings(t.selectedTracks(), rating, true)
|
||||||
})
|
})
|
||||||
t.ctxMenu.Items = append(t.ctxMenu.Items, ratingMenu)
|
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, 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)
|
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
|
var str string
|
||||||
if rowNum < 0 {
|
if rowNum < 0 {
|
||||||
rowNum = tr.TrackNumber
|
rowNum = tr.TrackNumber
|
||||||
if t.tracklist.ShowDiscNumber {
|
if t.tracklist.Options.ShowDiscNumber {
|
||||||
discNum = tr.DiscNumber
|
discNum = tr.DiscNumber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
// Component that manages lazily loading more tracks into a Tracklist
|
// Component that manages lazily loading more tracks into a Tracklist
|
||||||
// as the user scrolls near the bottom.
|
// as the user scrolls near the bottom.
|
||||||
type TracklistLoader struct {
|
type TracklistLoader struct {
|
||||||
|
disposed bool
|
||||||
|
|
||||||
tracklist *Tracklist
|
tracklist *Tracklist
|
||||||
iter mediaprovider.TrackIterator
|
iter mediaprovider.TrackIterator
|
||||||
|
|
||||||
@@ -28,11 +30,17 @@ func NewTracklistLoader(tracklist *Tracklist, iter mediaprovider.TrackIterator)
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cancels all asynchronous loads so that they will no longer modify the tracklist.
|
||||||
|
func (t *TracklistLoader) Dispose() {
|
||||||
|
t.disposed = true
|
||||||
|
t.tracklist.OnTrackShown = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TracklistLoader) onTrackShown(tracknum int) {
|
func (t *TracklistLoader) onTrackShown(tracknum int) {
|
||||||
if tracknum > t.highestShown {
|
if tracknum > t.highestShown {
|
||||||
t.highestShown = tracknum
|
t.highestShown = tracknum
|
||||||
}
|
}
|
||||||
if t.highestShown >= t.len-25 && !t.fetching && !t.done {
|
if t.highestShown >= t.len-25 && !t.fetching && !t.done && !t.disposed {
|
||||||
t.fetching = true
|
t.fetching = true
|
||||||
go t.loadMoreTracks(25)
|
go t.loadMoreTracks(25)
|
||||||
}
|
}
|
||||||
@@ -52,6 +60,12 @@ func (t *TracklistLoader) loadMoreTracks(num int) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
t.trackBuffer = append(t.trackBuffer, tr)
|
t.trackBuffer = append(t.trackBuffer, tr)
|
||||||
|
if t.disposed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.disposed {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
t.tracklist.AppendTracks(t.trackBuffer)
|
t.tracklist.AppendTracks(t.trackBuffer)
|
||||||
t.tracklist.Refresh()
|
t.tracklist.Refresh()
|
||||||
|
|||||||
Reference in New Issue
Block a user