diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index 25ed12a..3efc688 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -90,7 +90,7 @@ func newAlbumPage( a.tracklist = t.(*widgets.Tracklist) a.tracklist.Reset() } else { - a.tracklist = widgets.NewTracklist(nil) + a.tracklist = widgets.NewTracklist(nil, a.im, true) } a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns) a.tracklist.SetSorting(sort) @@ -122,7 +122,7 @@ func (a *AlbumPage) Save() SavedPage { a.header.page = nil a.pool.Release(util.WidgetTypeAlbumPageHeader, a.header) a.tracklist.Clear() - a.pool.Release(util.WidgetTypeTracklist, a.tracklist) + a.pool.Release(util.WidgetTypeCompactTracklist, a.tracklist) return &s } diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index 180d5a1..35439fd 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -233,7 +233,7 @@ func (a *ArtistPage) showTopTracks() { tl.Reset() tl.SetTracks(ts) } else { - tl = widgets.NewTracklist(ts) + tl = widgets.NewTracklist(ts, a.im, false) } tl.Options = widgets.TracklistOptions{AutoNumber: true} _, canRate := a.mp.(mediaprovider.SupportsRating) diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index f026264..48c60d8 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -397,7 +397,7 @@ func (a *FavoritesPage) onShowFavoriteSongs() { tracklist.Reset() tracklist.SetTracks(fav.Tracks) } else { - tracklist = widgets.NewTracklist(fav.Tracks) + tracklist = widgets.NewTracklist(fav.Tracks, a.im, false) } tracklist.Options = widgets.TracklistOptions{AutoNumber: true} _, canRate := a.mp.(mediaprovider.SupportsRating) diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index 243a0b8..104e23d 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -79,7 +79,7 @@ func newPlaylistPage( a.tracklist = tl.(*widgets.Tracklist) a.tracklist.Reset() } else { - a.tracklist = widgets.NewTracklist(nil) + a.tracklist = widgets.NewTracklist(nil, a.im, false) } a.tracklist.SetVisibleColumns(conf.TracklistColumns) a.tracklist.SetSorting(trackSort) diff --git a/ui/browsing/router.go b/ui/browsing/router.go index d8967c0..fd7089a 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -54,7 +54,7 @@ func (r Router) CreatePage(rte controller.Route) Page { case controller.Playlists: return NewPlaylistsPage(r.Controller, r.widgetPool, &r.App.Config.PlaylistsPage, r.App.ServerManager.Server) case controller.Tracks: - return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.widgetPool, r.App.ServerManager.Server) + return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.widgetPool, r.App.ServerManager.Server, r.App.ImageManager) } return nil } diff --git a/ui/browsing/trackspage.go b/ui/browsing/trackspage.go index a9e4809..fea8fdc 100644 --- a/ui/browsing/trackspage.go +++ b/ui/browsing/trackspage.go @@ -38,12 +38,13 @@ type tracksPageState struct { contr *controller.Controller conf *backend.TracksPageConfig mp mediaprovider.MediaProvider + im *backend.ImageManager canRate bool canShare bool } -func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, pool *util.WidgetPool, mp mediaprovider.MediaProvider) *TracksPage { - t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, widgetPool: pool, mp: mp}} +func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, pool *util.WidgetPool, mp mediaprovider.MediaProvider, im *backend.ImageManager) *TracksPage { + t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, widgetPool: pool, mp: mp, im: im}} t.ExtendBaseWidget(t) t.tracklist = t.obtainTracklist() @@ -175,7 +176,7 @@ func (t *TracksPage) Save() SavedPage { } func (s *tracksPageState) Restore() Page { - t := NewTracksPage(s.contr, s.conf, s.widgetPool, s.mp) + t := NewTracksPage(s.contr, s.conf, s.widgetPool, s.mp, s.im) t.searchText = s.searchText if t.searchText != "" { t.searcher.Entry.Text = t.searchText @@ -194,5 +195,5 @@ func (t *TracksPage) obtainTracklist() *widgets.Tracklist { tracklist.Reset() return tracklist } - return widgets.NewTracklist(nil) + return widgets.NewTracklist(nil, t.im, false) } diff --git a/ui/util/widgetpool.go b/ui/util/widgetpool.go index b8c574e..f79d247 100644 --- a/ui/util/widgetpool.go +++ b/ui/util/widgetpool.go @@ -15,6 +15,7 @@ const ( WidgetTypeGridView WidgetTypePlaylistPageHeader WidgetTypeTracklist + WidgetTypeCompactTracklist WidgetTypeNowPlayingPage // keep at bottom diff --git a/ui/widgets/focuslist.go b/ui/widgets/focuslist.go index 2710704..6dabac7 100644 --- a/ui/widgets/focuslist.go +++ b/ui/widgets/focuslist.go @@ -25,6 +25,10 @@ type FocusListRow interface { fyne.Focusable ItemID() widget.ListItemID SetItemID(widget.ListItemID) + + SetOnTapped(func()) + SetOnDoubleTapped(func()) + SetOnFocusNeighbor(func(up bool)) } func NewFocusList(len func() int, create func() fyne.CanvasObject, update func(widget.GridWrapItemID, fyne.CanvasObject)) *FocusList { @@ -102,6 +106,18 @@ type FocusListRowBase struct { selectionRect *canvas.Rectangle } +func (l *FocusListRowBase) SetOnTapped(f func()) { + l.OnTapped = f +} + +func (l *FocusListRowBase) SetOnDoubleTapped(f func()) { + l.OnDoubleTapped = f +} + +func (l *FocusListRowBase) SetOnFocusNeighbor(f func(up bool)) { + l.OnFocusNeighbor = f +} + func (l *FocusListRowBase) ItemID() widget.ListItemID { return l.ListItemID } diff --git a/ui/widgets/playqueuelist.go b/ui/widgets/playqueuelist.go index 1d3de92..2c9d80f 100644 --- a/ui/widgets/playqueuelist.go +++ b/ui/widgets/playqueuelist.go @@ -21,6 +21,8 @@ import ( "github.com/dweymouth/supersonic/ui/util" ) +const playQueueListThumbnailSize = 52 + type PlayQueueList struct { widget.BaseWidget @@ -296,7 +298,7 @@ func NewPlayQueueListRow(playQueueList *PlayQueueList, im *backend.ImageManager, playingIcon: playingIcon, playQueueList: playQueueList, num: widget.NewLabel(""), - cover: NewImagePlaceholder(myTheme.TracksIcon, tracklistThumbnailSize), + cover: NewImagePlaceholder(myTheme.TracksIcon, playQueueListThumbnailSize), title: util.NewTruncatingLabel(), artist: NewMultiHyperlink(), time: util.NewTrailingAlignLabel(), diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index e607f35..0268fde 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -7,6 +7,7 @@ import ( "strings" "sync" + "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/layouts" @@ -79,6 +80,8 @@ type TracklistOptions struct { type Tracklist struct { widget.BaseWidget + compactRows bool + Options TracklistOptions // user action callbacks @@ -118,16 +121,22 @@ type Tracklist struct { container *fyne.Container } -func NewTracklist(tracks []*mediaprovider.Track) *Tracklist { - t := &Tracklist{visibleColumns: make([]bool, numColumns)} +func NewTracklist(tracks []*mediaprovider.Track, im *backend.ImageManager, useCompactRows bool) *Tracklist { + t := &Tracklist{visibleColumns: make([]bool, numColumns), compactRows: useCompactRows} t.ExtendBaseWidget(t) if len(tracks) > 0 { t._setTracks(tracks) } - // #, Title, Artist, Album, Time, Year, Favorite, Rating, Plays, Comment, Bitrate, Size, Path - t.colLayout = layouts.NewColumnsLayout([]float32{40, -1, -1, -1, 60, 60, 55, 100, 65, -1, 75, 75, -1}) + // #, Title/Artist, Album, Time, Year, Favorite, Rating, Plays, Comment, Bitrate, Size, Path + colWidths := []float32{40, -1, -1, 60, 60, 55, 100, 65, -1, 75, 75, -1} + if useCompactRows { + // #, Title, Artist, Album, Time, Year, Favorite, Rating, Plays, Comment, Bitrate, Size, Path + colWidths = []float32{40, -1, -1, -1, 60, 60, 55, 100, 65, -1, 75, 75, -1} + } + + t.colLayout = layouts.NewColumnsLayout(colWidths) t.buildHeader() t.hdr.OnColumnSortChanged = t.onSorted t.hdr.OnColumnVisibilityChanged = t.setColumnVisible @@ -144,17 +153,22 @@ func NewTracklist(tracks []*mediaprovider.Track) *Tracklist { t.list = NewFocusList( t.lenTracks, func() fyne.CanvasObject { - tr := NewCompactTracklistRow(t, playingIcon) - tr.OnTapped = func() { - t.onSelectTrack(tr.ListItemID) - } - tr.OnTappedSecondary = t.onShowContextMenu - tr.OnDoubleTapped = func() { - t.onPlayTrackAt(tr.ListItemID) - } - tr.OnFocusNeighbor = func(up bool) { - t.list.FocusNeighbor(tr.ListItemID, up) + var tr TracklistRow + if t.compactRows { + tr = NewCompactTracklistRow(t, playingIcon) + } else { + tr = NewExpandedTracklistRow(t, im, playingIcon) } + tr.SetOnTapped(func() { + t.onSelectTrack(tr.ItemID()) + }) + tr.SetOnTappedSecondary(t.onShowContextMenu) + tr.SetOnDoubleTapped(func() { + t.onPlayTrackAt(tr.ItemID()) + }) + tr.SetOnFocusNeighbor(func(up bool) { + t.list.FocusNeighbor(tr.ItemID(), up) + }) return tr }, func(itemID widget.ListItemID, item fyne.CanvasObject) { @@ -199,21 +213,31 @@ func (t *Tracklist) Scroll(amount float32) { } func (t *Tracklist) buildHeader() { - t.hdr = NewListHeader([]ListColumn{ - {Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}, - {Text: "Title", Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, - {Text: "Artist", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, - {Text: "Album", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, - {Text: "Time", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, - {Text: "Year", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, - {Text: " Fav.", Alignment: fyne.TextAlignCenter, CanToggleVisible: true}, - {Text: "Rating", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, - {Text: "Plays", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, - {Text: "Comment", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, - {Text: "Bitrate", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, - {Text: "Size", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, - {Text: "File Path", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, - t.colLayout) + cols := []ListColumn{{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}} + if t.compactRows { + cols = append(cols, + ListColumn{Text: "Title", Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, + ListColumn{Text: "Artist", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, + ) + } else { + cols = append(cols, + ListColumn{Text: " Title / Aritst", Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, + ) + } + cols = append(cols, + ListColumn{Text: "Album", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, + ListColumn{Text: "Time", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, + ListColumn{Text: "Year", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, + ListColumn{Text: " Fav.", Alignment: fyne.TextAlignCenter, CanToggleVisible: true}, + ListColumn{Text: "Rating", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, + ListColumn{Text: "Plays", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, + ListColumn{Text: "Comment", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, + ListColumn{Text: "Bitrate", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, + ListColumn{Text: "Size", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, + ListColumn{Text: "File Path", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, + ) + + t.hdr = NewListHeader(cols, t.colLayout) } // Gets the track at the given index. Thread-safe. @@ -230,15 +254,30 @@ func (t *Tracklist) TrackAt(idx int) *mediaprovider.Track { func (t *Tracklist) SetVisibleColumns(cols []string) { t.visibleColumns[0] = true t.visibleColumns[1] = true - for i := 2; i < len(t.visibleColumns); i++ { - t.visibleColumns[i] = false + if !t.compactRows { + // hard-code artist column to visible since it's part of title col + t.visibleColumns[2] = true + } + + l := len(t.visibleColumns) + if !t.compactRows { + l-- // expanded rows have one fewer column due to merged title/artist + } + for i := 2; i < l; i++ { t.hdr.SetColumnVisible(i, false) } for _, col := range cols { if num := ColNumber(col); num < 0 { log.Printf("Unknown tracklist column %q", col) } else { + if num == 2 && !t.compactRows { + // Artist column is hard-coded visible (part of title), skip + continue + } t.visibleColumns[num] = true + if !t.compactRows && num >= 3 { + num -= 1 // shift cols by 1 to account for Title/Artist combined col + } t.hdr.SetColumnVisible(num, true) } } @@ -259,6 +298,9 @@ func (t *Tracklist) setColumnVisible(colNum int, vis bool) { log.Printf("error: Tracklist.SetColumnVisible: column index %d out of range", colNum) return } + if !t.compactRows && colNum >= 2 { + colNum++ // account for off-by-one from merged title/artist column + } t.visibleColumns[colNum] = vis t.list.Refresh() if t.OnVisibleColumnsChanged != nil { diff --git a/ui/widgets/tracklistrow.go b/ui/widgets/tracklistrow.go index e84005f..0cec7df 100644 --- a/ui/widgets/tracklistrow.go +++ b/ui/widgets/tracklistrow.go @@ -15,7 +15,7 @@ import ( "github.com/dweymouth/supersonic/ui/util" ) -const tracklistThumbnailSize = 52 +const tracklistThumbnailSize = 48 type tracklistRowBase struct { FocusListRowBase @@ -49,8 +49,11 @@ type tracklistRowBase struct { } type TracklistRow interface { + fyne.CanvasObject FocusListRow + SetOnTappedSecondary(func(_ *fyne.PointEvent, trackNum int)) + TrackID() string Update(model *util.TrackListModel, rowNum int) } @@ -87,7 +90,7 @@ func NewExpandedTracklistRow(tracklist *Tracklist, im *backend.ImageManager, pla titleArtistImg := container.NewBorder(nil, nil, container.NewPadded(t.img) /*left*/, nil, - container.New(layout.NewCustomPaddedVBoxLayout(theme.Padding()-15), + container.New(layout.NewCustomPaddedVBoxLayout(theme.Padding()-16), t.name, t.artist)) v := makeVerticallyCentered // func alias @@ -138,6 +141,9 @@ func (t *tracklistRowBase) create(tracklist *Tracklist) { t.path = util.NewTruncatingLabel() } +func (t *tracklistRowBase) SetOnTappedSecondary(f func(*fyne.PointEvent, int)) { + t.OnTappedSecondary = f +} func (t *tracklistRowBase) TrackID() string { return t.trackID }