WIP - doesn't quite work yet

This commit is contained in:
Drew Weymouth
2024-05-29 20:04:06 -07:00
parent a9b6d0cc2a
commit 680cfb42d2
11 changed files with 112 additions and 44 deletions
+2 -2
View File
@@ -90,7 +90,7 @@ func newAlbumPage(
a.tracklist = t.(*widgets.Tracklist) a.tracklist = t.(*widgets.Tracklist)
a.tracklist.Reset() a.tracklist.Reset()
} else { } else {
a.tracklist = widgets.NewTracklist(nil) a.tracklist = widgets.NewTracklist(nil, a.im, true)
} }
a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns) a.tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
a.tracklist.SetSorting(sort) a.tracklist.SetSorting(sort)
@@ -122,7 +122,7 @@ func (a *AlbumPage) Save() SavedPage {
a.header.page = nil a.header.page = nil
a.pool.Release(util.WidgetTypeAlbumPageHeader, a.header) a.pool.Release(util.WidgetTypeAlbumPageHeader, a.header)
a.tracklist.Clear() a.tracklist.Clear()
a.pool.Release(util.WidgetTypeTracklist, a.tracklist) a.pool.Release(util.WidgetTypeCompactTracklist, a.tracklist)
return &s return &s
} }
+1 -1
View File
@@ -233,7 +233,7 @@ func (a *ArtistPage) showTopTracks() {
tl.Reset() tl.Reset()
tl.SetTracks(ts) tl.SetTracks(ts)
} else { } else {
tl = widgets.NewTracklist(ts) tl = widgets.NewTracklist(ts, a.im, false)
} }
tl.Options = widgets.TracklistOptions{AutoNumber: true} tl.Options = widgets.TracklistOptions{AutoNumber: true}
_, canRate := a.mp.(mediaprovider.SupportsRating) _, canRate := a.mp.(mediaprovider.SupportsRating)
+1 -1
View File
@@ -397,7 +397,7 @@ func (a *FavoritesPage) onShowFavoriteSongs() {
tracklist.Reset() tracklist.Reset()
tracklist.SetTracks(fav.Tracks) tracklist.SetTracks(fav.Tracks)
} else { } else {
tracklist = widgets.NewTracklist(fav.Tracks) tracklist = widgets.NewTracklist(fav.Tracks, a.im, false)
} }
tracklist.Options = widgets.TracklistOptions{AutoNumber: true} tracklist.Options = widgets.TracklistOptions{AutoNumber: true}
_, canRate := a.mp.(mediaprovider.SupportsRating) _, canRate := a.mp.(mediaprovider.SupportsRating)
+1 -1
View File
@@ -79,7 +79,7 @@ func newPlaylistPage(
a.tracklist = tl.(*widgets.Tracklist) a.tracklist = tl.(*widgets.Tracklist)
a.tracklist.Reset() a.tracklist.Reset()
} else { } else {
a.tracklist = widgets.NewTracklist(nil) a.tracklist = widgets.NewTracklist(nil, a.im, false)
} }
a.tracklist.SetVisibleColumns(conf.TracklistColumns) a.tracklist.SetVisibleColumns(conf.TracklistColumns)
a.tracklist.SetSorting(trackSort) a.tracklist.SetSorting(trackSort)
+1 -1
View File
@@ -54,7 +54,7 @@ func (r Router) CreatePage(rte controller.Route) Page {
case controller.Playlists: case controller.Playlists:
return NewPlaylistsPage(r.Controller, r.widgetPool, &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.widgetPool, r.App.ServerManager.Server) return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.widgetPool, r.App.ServerManager.Server, r.App.ImageManager)
} }
return nil return nil
} }
+5 -4
View File
@@ -38,12 +38,13 @@ type tracksPageState struct {
contr *controller.Controller contr *controller.Controller
conf *backend.TracksPageConfig conf *backend.TracksPageConfig
mp mediaprovider.MediaProvider mp mediaprovider.MediaProvider
im *backend.ImageManager
canRate bool canRate bool
canShare bool canShare bool
} }
func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, pool *util.WidgetPool, mp mediaprovider.MediaProvider) *TracksPage { 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}} t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, widgetPool: pool, mp: mp, im: im}}
t.ExtendBaseWidget(t) t.ExtendBaseWidget(t)
t.tracklist = t.obtainTracklist() t.tracklist = t.obtainTracklist()
@@ -175,7 +176,7 @@ func (t *TracksPage) Save() SavedPage {
} }
func (s *tracksPageState) Restore() Page { 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 t.searchText = s.searchText
if t.searchText != "" { if t.searchText != "" {
t.searcher.Entry.Text = t.searchText t.searcher.Entry.Text = t.searchText
@@ -194,5 +195,5 @@ func (t *TracksPage) obtainTracklist() *widgets.Tracklist {
tracklist.Reset() tracklist.Reset()
return tracklist return tracklist
} }
return widgets.NewTracklist(nil) return widgets.NewTracklist(nil, t.im, false)
} }
+1
View File
@@ -15,6 +15,7 @@ const (
WidgetTypeGridView WidgetTypeGridView
WidgetTypePlaylistPageHeader WidgetTypePlaylistPageHeader
WidgetTypeTracklist WidgetTypeTracklist
WidgetTypeCompactTracklist
WidgetTypeNowPlayingPage WidgetTypeNowPlayingPage
// keep at bottom // keep at bottom
+16
View File
@@ -25,6 +25,10 @@ type FocusListRow interface {
fyne.Focusable fyne.Focusable
ItemID() widget.ListItemID ItemID() widget.ListItemID
SetItemID(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 { 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 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 { func (l *FocusListRowBase) ItemID() widget.ListItemID {
return l.ListItemID return l.ListItemID
} }
+3 -1
View File
@@ -21,6 +21,8 @@ import (
"github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/util"
) )
const playQueueListThumbnailSize = 52
type PlayQueueList struct { type PlayQueueList struct {
widget.BaseWidget widget.BaseWidget
@@ -296,7 +298,7 @@ func NewPlayQueueListRow(playQueueList *PlayQueueList, im *backend.ImageManager,
playingIcon: playingIcon, playingIcon: playingIcon,
playQueueList: playQueueList, playQueueList: playQueueList,
num: widget.NewLabel(""), num: widget.NewLabel(""),
cover: NewImagePlaceholder(myTheme.TracksIcon, tracklistThumbnailSize), cover: NewImagePlaceholder(myTheme.TracksIcon, playQueueListThumbnailSize),
title: util.NewTruncatingLabel(), title: util.NewTruncatingLabel(),
artist: NewMultiHyperlink(), artist: NewMultiHyperlink(),
time: util.NewTrailingAlignLabel(), time: util.NewTrailingAlignLabel(),
+73 -31
View File
@@ -7,6 +7,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/layouts" "github.com/dweymouth/supersonic/ui/layouts"
@@ -79,6 +80,8 @@ type TracklistOptions struct {
type Tracklist struct { type Tracklist struct {
widget.BaseWidget widget.BaseWidget
compactRows bool
Options TracklistOptions Options TracklistOptions
// user action callbacks // user action callbacks
@@ -118,16 +121,22 @@ type Tracklist struct {
container *fyne.Container container *fyne.Container
} }
func NewTracklist(tracks []*mediaprovider.Track) *Tracklist { func NewTracklist(tracks []*mediaprovider.Track, im *backend.ImageManager, useCompactRows bool) *Tracklist {
t := &Tracklist{visibleColumns: make([]bool, numColumns)} t := &Tracklist{visibleColumns: make([]bool, numColumns), compactRows: useCompactRows}
t.ExtendBaseWidget(t) t.ExtendBaseWidget(t)
if len(tracks) > 0 { if len(tracks) > 0 {
t._setTracks(tracks) t._setTracks(tracks)
} }
// #, Title, Artist, Album, Time, Year, Favorite, Rating, Plays, Comment, Bitrate, Size, Path // #, 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}) 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.buildHeader()
t.hdr.OnColumnSortChanged = t.onSorted t.hdr.OnColumnSortChanged = t.onSorted
t.hdr.OnColumnVisibilityChanged = t.setColumnVisible t.hdr.OnColumnVisibilityChanged = t.setColumnVisible
@@ -144,17 +153,22 @@ func NewTracklist(tracks []*mediaprovider.Track) *Tracklist {
t.list = NewFocusList( t.list = NewFocusList(
t.lenTracks, t.lenTracks,
func() fyne.CanvasObject { func() fyne.CanvasObject {
tr := NewCompactTracklistRow(t, playingIcon) var tr TracklistRow
tr.OnTapped = func() { if t.compactRows {
t.onSelectTrack(tr.ListItemID) tr = NewCompactTracklistRow(t, playingIcon)
} } else {
tr.OnTappedSecondary = t.onShowContextMenu tr = NewExpandedTracklistRow(t, im, playingIcon)
tr.OnDoubleTapped = func() {
t.onPlayTrackAt(tr.ListItemID)
}
tr.OnFocusNeighbor = func(up bool) {
t.list.FocusNeighbor(tr.ListItemID, up)
} }
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 return tr
}, },
func(itemID widget.ListItemID, item fyne.CanvasObject) { func(itemID widget.ListItemID, item fyne.CanvasObject) {
@@ -199,21 +213,31 @@ func (t *Tracklist) Scroll(amount float32) {
} }
func (t *Tracklist) buildHeader() { func (t *Tracklist) buildHeader() {
t.hdr = NewListHeader([]ListColumn{ cols := []ListColumn{{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}}
{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}, if t.compactRows {
{Text: "Title", Alignment: fyne.TextAlignLeading, CanToggleVisible: false}, cols = append(cols,
{Text: "Artist", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, ListColumn{Text: "Title", Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: "Album", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, ListColumn{Text: "Artist", Alignment: fyne.TextAlignLeading, CanToggleVisible: true},
{Text: "Time", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, )
{Text: "Year", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, } else {
{Text: " Fav.", Alignment: fyne.TextAlignCenter, CanToggleVisible: true}, cols = append(cols,
{Text: "Rating", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, ListColumn{Text: " Title / Aritst", Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: "Plays", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, )
{Text: "Comment", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}, }
{Text: "Bitrate", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, cols = append(cols,
{Text: "Size", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}, ListColumn{Text: "Album", Alignment: fyne.TextAlignLeading, CanToggleVisible: true},
{Text: "File Path", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}}, ListColumn{Text: "Time", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true},
t.colLayout) 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. // 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) { func (t *Tracklist) SetVisibleColumns(cols []string) {
t.visibleColumns[0] = true t.visibleColumns[0] = true
t.visibleColumns[1] = true t.visibleColumns[1] = true
for i := 2; i < len(t.visibleColumns); i++ { if !t.compactRows {
t.visibleColumns[i] = false // 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) t.hdr.SetColumnVisible(i, false)
} }
for _, col := range cols { for _, col := range cols {
if num := ColNumber(col); num < 0 { if num := ColNumber(col); num < 0 {
log.Printf("Unknown tracklist column %q", col) log.Printf("Unknown tracklist column %q", col)
} else { } else {
if num == 2 && !t.compactRows {
// Artist column is hard-coded visible (part of title), skip
continue
}
t.visibleColumns[num] = true 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) 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) log.Printf("error: Tracklist.SetColumnVisible: column index %d out of range", colNum)
return return
} }
if !t.compactRows && colNum >= 2 {
colNum++ // account for off-by-one from merged title/artist column
}
t.visibleColumns[colNum] = vis t.visibleColumns[colNum] = vis
t.list.Refresh() t.list.Refresh()
if t.OnVisibleColumnsChanged != nil { if t.OnVisibleColumnsChanged != nil {
+8 -2
View File
@@ -15,7 +15,7 @@ import (
"github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/util"
) )
const tracklistThumbnailSize = 52 const tracklistThumbnailSize = 48
type tracklistRowBase struct { type tracklistRowBase struct {
FocusListRowBase FocusListRowBase
@@ -49,8 +49,11 @@ type tracklistRowBase struct {
} }
type TracklistRow interface { type TracklistRow interface {
fyne.CanvasObject
FocusListRow FocusListRow
SetOnTappedSecondary(func(_ *fyne.PointEvent, trackNum int))
TrackID() string TrackID() string
Update(model *util.TrackListModel, rowNum int) Update(model *util.TrackListModel, rowNum int)
} }
@@ -87,7 +90,7 @@ func NewExpandedTracklistRow(tracklist *Tracklist, im *backend.ImageManager, pla
titleArtistImg := container.NewBorder(nil, nil, titleArtistImg := container.NewBorder(nil, nil,
container.NewPadded(t.img) /*left*/, 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)) t.name, t.artist))
v := makeVerticallyCentered // func alias v := makeVerticallyCentered // func alias
@@ -138,6 +141,9 @@ func (t *tracklistRowBase) create(tracklist *Tracklist) {
t.path = util.NewTruncatingLabel() t.path = util.NewTruncatingLabel()
} }
func (t *tracklistRowBase) SetOnTappedSecondary(f func(*fyne.PointEvent, int)) {
t.OnTappedSecondary = f
}
func (t *tracklistRowBase) TrackID() string { func (t *tracklistRowBase) TrackID() string {
return t.trackID return t.trackID
} }