diff --git a/README.md b/README.md index ef2f2c4..79e666d 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,22 @@ Slightly outdated screenshots of Supersonic running against the Navidrome ScrobbleThreshold { log.Printf("Scrobbling %q", song.Title) diff --git a/player/player.go b/player/player.go index 26e5108..571104a 100644 --- a/player/player.go +++ b/player/player.go @@ -131,6 +131,14 @@ func (p *Player) PlayFile(url string) error { return err } +// Removes the item at the given index from the internal playqueue. +func (p *Player) RemoveTrackAt(idx int) error { + if p.mpv == nil { + return ErrUnitialized + } + return p.mpv.Command([]string{"playlist-remove", strconv.Itoa(idx)}) +} + // Stops playback and clears the play queue. func (p *Player) Stop() error { if p.mpv == nil { @@ -230,6 +238,9 @@ func (p *Player) PlayFromBeginning() error { // Start playback from the specified track index in the play queue. func (p *Player) PlayTrackAt(idx int) error { err := p.mpv.Command([]string{"playlist-play-index", strconv.Itoa(idx)}) + if p.GetStatus().State == Paused { + err = p.setPaused(false) + } if err == nil { p.setState(Playing) } diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index 7e90870..51ca39d 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -26,8 +26,6 @@ type AlbumPage struct { tracklist *widgets.Tracklist nowPlayingID string container *fyne.Container - - OnPlayAlbum func(string, int) } type albumPageState struct { @@ -36,7 +34,7 @@ type albumPageState struct { pm *backend.PlaybackManager im *backend.ImageManager sm *backend.ServerManager - contr *controller.Controller + contr controller.Controller nav func(Route) } @@ -46,7 +44,7 @@ func NewAlbumPage( pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager, - contr *controller.Controller, + contr controller.Controller, nav func(Route), ) *AlbumPage { a := &AlbumPage{ @@ -84,10 +82,6 @@ func (a *AlbumPage) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(a.container) } -func (a *AlbumPage) SetPlayAlbumCallback(cb func(string, int)) { - a.OnPlayAlbum = cb -} - func (a *AlbumPage) Save() SavedPage { s := a.albumPageState return &s @@ -114,10 +108,12 @@ func (a *AlbumPage) Tapped(*fyne.PointEvent) { a.tracklist.UnselectAll() } +func (a *AlbumPage) SelectAll() { + a.tracklist.SelectAll() +} + func (a *AlbumPage) onPlayTrackAt(tracknum int) { - if a.OnPlayAlbum != nil { - a.OnPlayAlbum(a.albumID, tracknum) - } + a.pm.PlayAlbum(a.albumID, tracknum) } func (a *AlbumPage) loadAsync() { diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 3368d00..c5b83c8 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -16,18 +16,18 @@ var _ fyne.Widget = (*AlbumsPage)(nil) type AlbumsPage struct { widget.BaseWidget - title string - im *backend.ImageManager - lm *backend.LibraryManager - nav func(Route) - grid *widgets.AlbumGrid - searchGrid *widgets.AlbumGrid - searcher *widgets.Searcher - searchText string - titleDisp *widget.RichText - sortOrder *selectWidget - container *fyne.Container - OnPlayAlbum func(string, int) + title string + pm *backend.PlaybackManager + im *backend.ImageManager + lm *backend.LibraryManager + nav func(Route) + grid *widgets.AlbumGrid + searchGrid *widgets.AlbumGrid + searcher *widgets.Searcher + searchText string + titleDisp *widget.RichText + sortOrder *selectWidget + container *fyne.Container } type selectWidget struct { @@ -51,9 +51,10 @@ func (s *selectWidget) MinSize() fyne.Size { return fyne.NewSize(170, s.height) } -func NewAlbumsPage(title string, sortOrder string, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *AlbumsPage { +func NewAlbumsPage(title string, sortOrder string, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *AlbumsPage { a := &AlbumsPage{ title: title, + pm: pm, lm: lm, im: im, nav: nav, @@ -98,6 +99,7 @@ func (a *AlbumsPage) createContainer(searchgrid bool) { func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage { a := &AlbumsPage{ title: saved.title, + pm: saved.pm, lm: saved.lm, im: saved.im, nav: saved.nav, @@ -146,10 +148,6 @@ func (a *AlbumsPage) SearchWidget() fyne.Focusable { return a.searcher.Entry } -func (a *AlbumsPage) SetPlayAlbumCallback(cb func(string, int)) { - a.OnPlayAlbum = cb -} - func (a *AlbumsPage) Reload() { if a.searchText != "" { a.doSearch(a.searchText) @@ -162,6 +160,7 @@ func (a *AlbumsPage) Reload() { func (a *AlbumsPage) Save() SavedPage { sa := &savedAlbumsPage{ title: a.title, + pm: a.pm, lm: a.lm, im: a.im, nav: a.nav, @@ -189,9 +188,7 @@ func (a *AlbumsPage) doSearch(query string) { } func (a *AlbumsPage) onPlayAlbum(albumID string) { - if a.OnPlayAlbum != nil { - a.OnPlayAlbum(albumID, 0) - } + a.pm.PlayAlbum(albumID, 0) } func (a *AlbumsPage) onShowArtistPage(artistID string) { @@ -218,6 +215,7 @@ func (a *AlbumsPage) CreateRenderer() fyne.WidgetRenderer { type savedAlbumsPage struct { title string searchText string + pm *backend.PlaybackManager lm *backend.LibraryManager im *backend.ImageManager nav func(Route) diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index dc1da27..b4f66a5 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -27,7 +27,7 @@ type artistPageState struct { sm *backend.ServerManager im *backend.ImageManager nav func(Route) - contr *controller.Controller + contr controller.Controller } type ArtistPage struct { @@ -41,7 +41,7 @@ type ArtistPage struct { OnPlayAlbum func(string, int) } -func NewArtistPage(artistID string, sm *backend.ServerManager, im *backend.ImageManager, contr *controller.Controller, nav func(Route)) *ArtistPage { +func NewArtistPage(artistID string, sm *backend.ServerManager, im *backend.ImageManager, contr controller.Controller, nav func(Route)) *ArtistPage { a := &ArtistPage{artistPageState: artistPageState{ artistID: artistID, sm: sm, diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go index 4efed1c..8a6670a 100644 --- a/ui/browsing/browsingpane.go +++ b/ui/browsing/browsingpane.go @@ -31,8 +31,8 @@ type Searchable interface { SearchWidget() fyne.Focusable } -type CanPlayAlbum interface { - SetPlayAlbumCallback(func(albumID string, startingTrack int)) +type CanSelectAll interface { + SelectAll() } type CanShowNowPlaying interface { @@ -93,16 +93,17 @@ func (b *BrowsingPane) GetSearchBarIfAny() fyne.Focusable { return nil } +func (b *BrowsingPane) SelectAll() { + if s, ok := b.curPage.(CanSelectAll); ok { + s.SelectAll() + } +} + func (b *BrowsingPane) doSetPage(p Page) bool { if b.curPage != nil && b.curPage.Route() == p.Route() { return false } b.curPage = p - if pa, ok := p.(CanPlayAlbum); ok { - pa.SetPlayAlbumCallback(func(albumID string, firstTrack int) { - _ = b.app.PlaybackManager.PlayAlbum(albumID, firstTrack) - }) - } if np, ok := p.(CanShowNowPlaying); ok { np.OnSongChange(b.app.PlaybackManager.NowPlaying()) } diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 82e2b3f..16f6832 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -16,6 +16,7 @@ import ( type FavoritesPage struct { widget.BaseWidget + pm *backend.PlaybackManager im *backend.ImageManager sm *backend.ServerManager lm *backend.LibraryManager @@ -26,12 +27,11 @@ type FavoritesPage struct { searchText string titleDisp *widget.RichText container *fyne.Container - - OnPlayAlbum func(string, int) } -func NewFavoritesPage(sm *backend.ServerManager, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *FavoritesPage { +func NewFavoritesPage(sm *backend.ServerManager, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager, nav func(Route)) *FavoritesPage { a := &FavoritesPage{ + pm: pm, lm: lm, sm: sm, im: im, @@ -66,6 +66,7 @@ func (a *FavoritesPage) createContainer() { func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage { a := &FavoritesPage{ + pm: saved.pm, lm: saved.lm, sm: saved.sm, im: saved.im, @@ -88,16 +89,13 @@ func (a *FavoritesPage) Route() Route { return FavoritesRoute() } -func (a *FavoritesPage) SetPlayAlbumCallback(cb func(string, int)) { - a.OnPlayAlbum = cb -} - func (a *FavoritesPage) Reload() { a.grid.Reset(a.lm.StarredIter()) } func (a *FavoritesPage) Save() SavedPage { return &savedFavoritesPage{ + pm: a.pm, sm: a.sm, im: a.im, lm: a.lm, @@ -142,9 +140,7 @@ func (a *FavoritesPage) doSearch(query string) { } func (a *FavoritesPage) onPlayAlbum(albumID string) { - if a.OnPlayAlbum != nil { - a.OnPlayAlbum(albumID, 0) - } + a.pm.PlayAlbum(albumID, 0) } func (a *FavoritesPage) onShowAlbumPage(albumID string) { @@ -161,6 +157,7 @@ func (a *FavoritesPage) CreateRenderer() fyne.WidgetRenderer { } type savedFavoritesPage struct { + pm *backend.PlaybackManager sm *backend.ServerManager im *backend.ImageManager lm *backend.LibraryManager diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go new file mode 100644 index 0000000..55f9df1 --- /dev/null +++ b/ui/browsing/nowplayingpage.go @@ -0,0 +1,111 @@ +package browsing + +import ( + "supersonic/backend" + "supersonic/ui/controller" + "supersonic/ui/layouts" + "supersonic/ui/widgets" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" + "github.com/dweymouth/go-subsonic/subsonic" +) + +type NowPlayingPage struct { + widget.BaseWidget + + nowPlayingPageState + + title *widget.RichText + tracklist *widgets.Tracklist + nowPlayingID string + container *fyne.Container +} + +type nowPlayingPageState struct { + contr controller.Controller + sm *backend.ServerManager + pm *backend.PlaybackManager + nav func(Route) +} + +func NewNowPlayingPage( + contr controller.Controller, + sm *backend.ServerManager, + pm *backend.PlaybackManager, + nav func(Route), +) *NowPlayingPage { + a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, sm: sm, pm: pm, nav: nav}} + a.ExtendBaseWidget(a) + a.tracklist = widgets.NewTracklist(nil) + a.tracklist.AutoNumber = true + a.tracklist.DisablePlaybackMenu = true + a.tracklist.OnPlayTrackAt = a.onPlayTrackAt + a.tracklist.OnAddToPlaylist = a.contr.DoAddTracksToPlaylistWorkflow + a.tracklist.AuxiliaryMenuItems = []*fyne.MenuItem{ + fyne.NewMenuItem("Remove from queue", a.onRemoveSelectedFromQueue), + } + a.title = widget.NewRichTextWithText("Now Playing") + a.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName + a.container = container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, + container.NewBorder(a.title, nil, nil, nil, a.tracklist)) + a.loadAsync() + return a +} + +func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(a.container) +} + +func (a *NowPlayingPage) Save() SavedPage { + nps := a.nowPlayingPageState + return &nps +} + +func (a *NowPlayingPage) Route() Route { + return NowPlayingRoute() +} + +func (a *NowPlayingPage) Tapped(*fyne.PointEvent) { + a.tracklist.UnselectAll() +} + +func (a *NowPlayingPage) SelectAll() { + a.tracklist.SelectAll() +} + +func (a *NowPlayingPage) OnSongChange(song *subsonic.Child) { + if song == nil { + a.nowPlayingID = "" + } else { + a.nowPlayingID = song.ID + } + a.tracklist.SetNowPlaying(a.nowPlayingID) +} + +func (a *NowPlayingPage) Reload() { + a.loadAsync() +} + +func (a *NowPlayingPage) onPlayTrackAt(tracknum int) { + _ = a.pm.PlayTrackAt(tracknum) +} + +func (a *NowPlayingPage) onRemoveSelectedFromQueue() { + a.pm.RemoveTracksFromQueue(a.tracklist.SelectedTrackIndexes()) + a.tracklist.UnselectAll() + go a.Reload() +} + +func (a *NowPlayingPage) loadAsync() { + go func() { + queue := a.pm.GetPlayQueue() + a.tracklist.Tracks = queue + a.tracklist.SetNowPlaying(a.nowPlayingID) + }() +} + +func (s *nowPlayingPageState) Restore() Page { + return NewNowPlayingPage(s.contr, s.sm, s.pm, s.nav) +} diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index fee6f9c..c41daf6 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -30,7 +30,7 @@ type PlaylistPage struct { type playlistPageState struct { playlistID string - contr *controller.Controller + contr controller.Controller sm *backend.ServerManager pm *backend.PlaybackManager im *backend.ImageManager @@ -39,7 +39,7 @@ type playlistPageState struct { func NewPlaylistPage( playlistID string, - contr *controller.Controller, + contr controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, im *backend.ImageManager, @@ -79,7 +79,7 @@ func (a *PlaylistPage) Save() SavedPage { } func (a *PlaylistPage) Route() Route { - return AlbumRoute(a.playlistID) + return PlaylistRoute(a.playlistID) } func (a *PlaylistPage) OnSongChange(song *subsonic.Child) { @@ -99,6 +99,10 @@ func (a *PlaylistPage) Tapped(*fyne.PointEvent) { a.tracklist.UnselectAll() } +func (a *PlaylistPage) SelectAll() { + a.tracklist.SelectAll() +} + func (a *PlaylistPage) onPlayTrackAt(tracknum int) { a.pm.PlayPlaylist(a.playlistID, tracknum) } @@ -119,6 +123,7 @@ func (a *PlaylistPage) loadAsync() { func (a *PlaylistPage) onRemoveSelectedFromPlaylist() { a.sm.Server.UpdatePlaylistTracks(a.playlistID, nil, a.tracklist.SelectedTrackIndexes()) + a.tracklist.UnselectAll() go a.Reload() } diff --git a/ui/browsing/router.go b/ui/browsing/router.go index 32ac87b..ae006b2 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -3,7 +3,6 @@ package browsing import ( "supersonic/backend" "supersonic/ui/controller" - "supersonic/ui/util" ) type PageName int @@ -17,6 +16,7 @@ const ( Genre Genres Favorites + NowPlaying Playlist Playlists ) @@ -61,19 +61,21 @@ func ArtistsRoute() Route { return Route{Page: Artists} } +func NowPlayingRoute() Route { + return Route{Page: NowPlaying} +} + type NavigationHandler interface { SetPage(Page) } type Router struct { App *backend.App - Controller *controller.Controller + Controller controller.Controller Nav NavigationHandler - - pop util.PopUpProvider } -func NewRouter(app *backend.App, controller *controller.Controller, nav NavigationHandler) Router { +func NewRouter(app *backend.App, controller controller.Controller, nav NavigationHandler) Router { r := Router{ App: app, Controller: controller, @@ -87,17 +89,19 @@ func (r Router) CreatePage(rte Route) Page { case Album: return NewAlbumPage(rte.Arg, r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.Controller, r.OpenRoute) case Albums: - return NewAlbumsPage("Albums", rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) + return NewAlbumsPage("Albums", rte.Arg, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Artist: return NewArtistPage(rte.Arg, r.App.ServerManager, r.App.ImageManager, r.Controller, r.OpenRoute) case Artists: return NewArtistsGenresPage(false, r.App.ServerManager, r.OpenRoute) case Favorites: - return NewFavoritesPage(r.App.ServerManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) + return NewFavoritesPage(r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Genre: return NewGenrePage(rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Genres: return NewArtistsGenresPage(true, r.App.ServerManager, r.OpenRoute) + case NowPlaying: + return NewNowPlayingPage(r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.OpenRoute) case Playlist: return NewPlaylistPage(rte.Arg, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager, r.OpenRoute) case Playlists: diff --git a/ui/controller/controller.go b/ui/controller/controller.go index bed132a..69dd137 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -17,7 +17,7 @@ type Controller struct { App *backend.App } -func (m *Controller) ShowPopUpImage(img image.Image) { +func (m Controller) ShowPopUpImage(img image.Image) { im := canvas.NewImageFromImage(img) im.FillMode = canvas.ImageFillContain pop := widget.NewPopUp(im, m.MainWindow.Canvas()) @@ -41,7 +41,7 @@ func (m *Controller) ShowPopUpImage(img image.Image) { // Show dialog to prompt for playlist. // Depending on the results of that dialog, potentially create a new playlist // Add tracks to the user-specified playlist -func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { +func (m Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { pls, err := m.App.LibraryManager.GetUserOwnedPlaylists() if err != nil { // TODO: surface this error to user diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 399ad14..44a5966 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -27,7 +27,7 @@ type MainWindow struct { App *backend.App Router browsing.Router - Controller *controller.Controller + Controller controller.Controller BrowsingPane *browsing.BrowsingPane BottomPanel *BottomPanel @@ -45,7 +45,7 @@ func NewMainWindow(fyneApp fyne.App, appName string, app *backend.App, size fyne BrowsingPane: browsing.NewBrowsingPane(app), } - m.Controller = &controller.Controller{ + m.Controller = controller.Controller{ MainWindow: m.Window, App: app, } @@ -82,6 +82,9 @@ func (m *MainWindow) PromptForFirstServer(cb func(string, string, string, string } func (m *MainWindow) addNavigationButtons() { + m.BrowsingPane.AddNavigationButton(res.ResHeadphonesInvertPng, func() { + m.Router.OpenRoute(browsing.NowPlayingRoute()) + }) m.BrowsingPane.AddNavigationButton(res.ResHeartFilledInvertPng, func() { m.Router.OpenRoute(browsing.FavoritesRoute()) }) @@ -117,6 +120,9 @@ func (m *MainWindow) addShortcuts() { m.Window.Canvas().Focus(s) } }) + m.Canvas().AddShortcut(&fyne.ShortcutSelectAll{}, func(_ fyne.Shortcut) { + m.BrowsingPane.SelectAll() + }) m.Canvas().SetOnTypedKey(func(e *fyne.KeyEvent) { if e.Name == fyne.KeySpace { diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 32fde47..d7b14aa 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -34,12 +34,13 @@ type TrackRow struct { OnDoubleTapped func() OnTappedSecondary func(e *fyne.PointEvent, trackIdx int) + playingIcon fyne.CanvasObject selectionRect *canvas.Rectangle container *fyne.Container } -func NewTrackRow(layout *layouts.ColumnsLayout) *TrackRow { - t := &TrackRow{} +func NewTrackRow(layout *layouts.ColumnsLayout, playingIcon fyne.CanvasObject) *TrackRow { + t := &TrackRow{playingIcon: playingIcon} t.ExtendBaseWidget(t) t.num = widget.NewRichTextWithText("") t.num.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing @@ -73,11 +74,16 @@ func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { t.artist.Segments[0].(*widget.TextSegment).Text = tr.Artist t.dur.Segments[0].(*widget.TextSegment).Text = util.SecondsToTimeString(float64(tr.Duration)) - t.num.Segments[0].(*widget.TextSegment).Style.TextStyle = fyne.TextStyle{Bold: isPlaying} - t.name.Segments[0].(*widget.TextSegment).Style.TextStyle = fyne.TextStyle{Bold: isPlaying, Italic: isPlaying} - t.artist.Segments[0].(*widget.TextSegment).Style.TextStyle = fyne.TextStyle{Bold: isPlaying, Italic: isPlaying} + t.name.Segments[0].(*widget.TextSegment).Style.TextStyle = fyne.TextStyle{Bold: isPlaying} + t.artist.Segments[0].(*widget.TextSegment).Style.TextStyle = fyne.TextStyle{Bold: isPlaying} t.dur.Segments[0].(*widget.TextSegment).Style.TextStyle = fyne.TextStyle{Bold: isPlaying} + if isPlaying { + t.container.Objects[1].(*fyne.Container).Objects[0] = container.NewCenter(t.playingIcon) + } else { + t.container.Objects[1].(*fyne.Container).Objects[0] = t.num + } + t.Refresh() } @@ -113,7 +119,8 @@ type Tracklist struct { Tracks []*subsonic.Child AutoNumber bool // must be set before the context menu is shown for the first time - AuxiliaryMenuItems []*fyne.MenuItem + AuxiliaryMenuItems []*fyne.MenuItem + DisablePlaybackMenu bool // user action callbacks OnPlayTrackAt func(int) @@ -136,10 +143,11 @@ func NewTracklist(tracks []*subsonic.Child) *Tracklist { t.selectionMgr = util.NewListSelectionManager(func() int { return len(t.Tracks) }) t.colLayout = layouts.NewColumnsLayout([]float32{35, -1, -1, 60}) t.hdr = NewListHeader([]ListColumn{{"#", true}, {"Title", false}, {"Artist", false}, {"Time", true}}, t.colLayout) + playingIcon := container.NewCenter(container.NewHBox(NewHSpace(2), widget.NewIcon(theme.MediaPlayIcon()))) t.list = widget.NewList( func() int { return len(t.Tracks) }, func() fyne.CanvasObject { - tr := NewTrackRow(t.colLayout) + tr := NewTrackRow(t.colLayout, playingIcon) tr.OnTapped = func() { t.onSelectTrack(tr.trackIdx) } tr.OnTappedSecondary = t.onShowContextMenu tr.OnDoubleTapped = func() { t.onPlayTrackAt(tr.trackIdx) } @@ -170,6 +178,11 @@ func (t *Tracklist) SetNowPlaying(trackID string) { t.list.Refresh() } +func (t *Tracklist) SelectAll() { + t.selectionMgr.SelectAll() + t.Refresh() +} + func (t *Tracklist) UnselectAll() { t.selectionMgr.UnselectAll() t.Refresh() @@ -204,23 +217,27 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { t.selectionMgr.Select(trackIdx) t.Refresh() if t.ctxMenu == nil { - t.ctxMenu = fyne.NewMenu("", - fyne.NewMenuItem("Play", func() { - if t.OnPlaySelection != nil { - t.OnPlaySelection(t.selectedTracks()) - } - }), - fyne.NewMenuItem("Add to queue", func() { - if t.OnPlaySelection != nil { - t.OnAddToQueue(t.selectedTracks()) - } - }), + t.ctxMenu = fyne.NewMenu("") + if !t.DisablePlaybackMenu { + t.ctxMenu.Items = append(t.ctxMenu.Items, + fyne.NewMenuItem("Play", func() { + if t.OnPlaySelection != nil { + t.OnPlaySelection(t.selectedTracks()) + } + })) + t.ctxMenu.Items = append(t.ctxMenu.Items, + fyne.NewMenuItem("Add to queue", func() { + if t.OnPlaySelection != nil { + t.OnAddToQueue(t.selectedTracks()) + } + })) + } + t.ctxMenu.Items = append(t.ctxMenu.Items, fyne.NewMenuItem("Add to playlist...", func() { if t.OnAddToPlaylist != nil { t.OnAddToPlaylist(t.selectedTrackIDs()) } - }), - ) + })) if len(t.AuxiliaryMenuItems) > 0 { t.ctxMenu.Items = append(t.ctxMenu.Items, fyne.NewMenuItemSeparator()) t.ctxMenu.Items = append(t.ctxMenu.Items, t.AuxiliaryMenuItems...)