refactor: extract out TracklistRow interface and base impl
This commit is contained in:
@@ -24,6 +24,7 @@ type FocusList struct {
|
||||
type FocusListRow interface {
|
||||
fyne.Focusable
|
||||
ItemID() widget.ListItemID
|
||||
SetItemID(widget.ListItemID)
|
||||
}
|
||||
|
||||
func NewFocusList(len func() int, create func() fyne.CanvasObject, update func(widget.GridWrapItemID, fyne.CanvasObject)) *FocusList {
|
||||
@@ -105,6 +106,10 @@ func (l *FocusListRowBase) ItemID() widget.ListItemID {
|
||||
return l.ListItemID
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) SetItemID(id widget.ListItemID) {
|
||||
l.ListItemID = id
|
||||
}
|
||||
|
||||
func (l *FocusListRowBase) EnsureUnfocused() {
|
||||
if l.Focused {
|
||||
fyne.CurrentApp().Driver().CanvasForObject(l).Unfocus()
|
||||
|
||||
+4
-193
@@ -1,11 +1,9 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -146,7 +144,7 @@ func NewTracklist(tracks []*mediaprovider.Track) *Tracklist {
|
||||
t.list = NewFocusList(
|
||||
t.lenTracks,
|
||||
func() fyne.CanvasObject {
|
||||
tr := NewTrackRow(t, playingIcon)
|
||||
tr := NewCompactTracklistRow(t, playingIcon)
|
||||
tr.OnTapped = func() {
|
||||
t.onSelectTrack(tr.ListItemID)
|
||||
}
|
||||
@@ -171,10 +169,10 @@ func NewTracklist(tracks []*mediaprovider.Track) *Tracklist {
|
||||
model := t.tracks[itemID]
|
||||
t.tracksMutex.RUnlock()
|
||||
|
||||
tr := item.(*TrackRow)
|
||||
tr := item.(TracklistRow)
|
||||
t.list.SetItemForID(itemID, tr)
|
||||
if tr.trackID != model.Track.ID || tr.ListItemID != itemID {
|
||||
tr.ListItemID = itemID
|
||||
if tr.TrackID() != model.Track.ID || tr.ItemID() != itemID {
|
||||
tr.SetItemID(itemID)
|
||||
}
|
||||
i := -1 // signal that we want to display the actual track num.
|
||||
if t.Options.AutoNumber {
|
||||
@@ -704,190 +702,3 @@ func colName(i int) string {
|
||||
log.Println("notReached: Tracklist.colName")
|
||||
return ""
|
||||
}
|
||||
|
||||
type TrackRow struct {
|
||||
FocusListRowBase
|
||||
|
||||
// internal state
|
||||
tracklist *Tracklist
|
||||
trackNum int
|
||||
trackID string
|
||||
isPlaying bool
|
||||
isFavorite bool
|
||||
playCount int
|
||||
|
||||
num *widget.Label
|
||||
name *widget.RichText // for bold support
|
||||
artist *MultiHyperlink
|
||||
album *MultiHyperlink // for disabled support, if albumID is ""
|
||||
dur *widget.Label
|
||||
year *widget.Label
|
||||
favorite *fyne.Container
|
||||
rating *StarRating
|
||||
bitrate *widget.Label
|
||||
plays *widget.Label
|
||||
comment *widget.Label
|
||||
size *widget.Label
|
||||
path *widget.Label
|
||||
|
||||
OnTappedSecondary func(e *fyne.PointEvent, trackIdx int)
|
||||
|
||||
playingIcon fyne.CanvasObject
|
||||
}
|
||||
|
||||
func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow {
|
||||
t := &TrackRow{tracklist: tracklist, playingIcon: playingIcon}
|
||||
t.ExtendBaseWidget(t)
|
||||
t.num = util.NewTrailingAlignLabel()
|
||||
t.name = util.NewTruncatingRichText()
|
||||
t.artist = NewMultiHyperlink()
|
||||
t.artist.OnTapped = tracklist.onArtistTapped
|
||||
t.album = NewMultiHyperlink()
|
||||
t.album.OnTapped = func(id string) { tracklist.onAlbumTapped(id) }
|
||||
t.dur = util.NewTrailingAlignLabel()
|
||||
t.year = util.NewTrailingAlignLabel()
|
||||
favorite := NewFavoriteIcon()
|
||||
favorite.OnTapped = t.toggleFavorited
|
||||
t.favorite = container.NewCenter(favorite)
|
||||
t.rating = NewStarRating()
|
||||
t.rating.IsDisabled = t.tracklist.Options.DisableRating
|
||||
t.rating.StarSize = 16
|
||||
t.rating.OnRatingChanged = t.setTrackRating
|
||||
t.plays = util.NewTrailingAlignLabel()
|
||||
t.comment = util.NewTruncatingLabel()
|
||||
t.bitrate = util.NewTrailingAlignLabel()
|
||||
t.size = util.NewTrailingAlignLabel()
|
||||
t.path = util.NewTruncatingLabel()
|
||||
|
||||
t.Content = container.New(tracklist.colLayout,
|
||||
t.num, t.name, t.artist, t.album, t.dur, t.year, t.favorite, t.rating, t.plays, t.comment, t.bitrate, t.size, t.path)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *TrackRow) Update(tm *util.TrackListModel, rowNum int) {
|
||||
changed := false
|
||||
if tm.Selected != t.Selected {
|
||||
t.Selected = tm.Selected
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update info that can change if this row is bound to
|
||||
// a new track (*mediaprovider.Track)
|
||||
tr := tm.Track
|
||||
if tr.ID != t.trackID {
|
||||
t.EnsureUnfocused()
|
||||
t.trackID = tr.ID
|
||||
|
||||
t.name.Segments[0].(*widget.TextSegment).Text = tr.Name
|
||||
t.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs)
|
||||
t.album.BuildSegments([]string{tr.Album}, []string{tr.AlbumID})
|
||||
t.dur.Text = util.SecondsToTimeString(float64(tr.Duration))
|
||||
t.year.Text = strconv.Itoa(tr.Year)
|
||||
t.plays.Text = strconv.Itoa(int(tr.PlayCount))
|
||||
t.comment.Text = tr.Comment
|
||||
t.bitrate.Text = strconv.Itoa(tr.BitRate)
|
||||
t.size.Text = util.BytesToSizeString(tr.Size)
|
||||
t.path.Text = tr.FilePath
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update track num if needed
|
||||
// (which can change based on bound *mediaprovider.Track or tracklist.AutoNumber)
|
||||
if t.trackNum != rowNum {
|
||||
discNum := -1
|
||||
var str string
|
||||
if rowNum < 0 {
|
||||
rowNum = tr.TrackNumber
|
||||
if t.tracklist.Options.ShowDiscNumber {
|
||||
discNum = tr.DiscNumber
|
||||
}
|
||||
}
|
||||
t.trackNum = rowNum
|
||||
if discNum >= 0 {
|
||||
str = fmt.Sprintf("%d.%02d", discNum, rowNum)
|
||||
} else {
|
||||
str = strconv.Itoa(rowNum)
|
||||
}
|
||||
t.num.Text = str
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update play count if needed
|
||||
if tr.PlayCount != t.playCount {
|
||||
t.playCount = tr.PlayCount
|
||||
t.plays.Text = strconv.Itoa(int(tr.PlayCount))
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Render whether track is playing or not
|
||||
if isPlaying := t.tracklist.nowPlayingID == tr.ID; isPlaying != t.isPlaying {
|
||||
t.isPlaying = isPlaying
|
||||
t.name.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying
|
||||
|
||||
if isPlaying {
|
||||
t.Content.(*fyne.Container).Objects[0] = t.playingIcon
|
||||
} else {
|
||||
t.Content.(*fyne.Container).Objects[0] = t.num
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update favorite column
|
||||
if tr.Favorite != t.isFavorite {
|
||||
t.isFavorite = tr.Favorite
|
||||
t.favorite.Objects[0].(*FavoriteIcon).Favorite = tr.Favorite
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update rating column
|
||||
if t.rating.Rating != tr.Rating {
|
||||
t.rating.Rating = tr.Rating
|
||||
t.rating.Refresh()
|
||||
}
|
||||
if t.rating.IsDisabled != t.tracklist.Options.DisableRating {
|
||||
t.rating.IsDisabled = t.tracklist.Options.DisableRating
|
||||
t.rating.Refresh()
|
||||
}
|
||||
|
||||
// Show only columns configured to be visible
|
||||
updateHidden := func(hiddenPtr *bool, colName string) {
|
||||
colHidden := !t.tracklist.visibleColumns[ColNumber(colName)]
|
||||
if colHidden != *hiddenPtr {
|
||||
*hiddenPtr = colHidden
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
updateHidden(&t.artist.Hidden, ColumnArtist)
|
||||
updateHidden(&t.album.Hidden, ColumnAlbum)
|
||||
updateHidden(&t.dur.Hidden, ColumnTime)
|
||||
updateHidden(&t.year.Hidden, ColumnYear)
|
||||
updateHidden(&t.favorite.Hidden, ColumnFavorite)
|
||||
updateHidden(&t.rating.Hidden, ColumnRating)
|
||||
updateHidden(&t.plays.Hidden, ColumnPlays)
|
||||
updateHidden(&t.comment.Hidden, ColumnComment)
|
||||
updateHidden(&t.bitrate.Hidden, ColumnBitrate)
|
||||
updateHidden(&t.size.Hidden, ColumnSize)
|
||||
updateHidden(&t.path.Hidden, ColumnPath)
|
||||
|
||||
if changed {
|
||||
t.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TrackRow) toggleFavorited() {
|
||||
t.isFavorite = !t.isFavorite
|
||||
favIcon := t.favorite.Objects[0].(*FavoriteIcon)
|
||||
favIcon.Favorite = t.isFavorite
|
||||
t.favorite.Refresh()
|
||||
t.tracklist.onSetFavorite(t.trackID, t.isFavorite)
|
||||
}
|
||||
|
||||
func (t *TrackRow) setTrackRating(rating int) {
|
||||
t.tracklist.onSetRating(t.trackID, rating)
|
||||
}
|
||||
|
||||
func (t *TrackRow) TappedSecondary(e *fyne.PointEvent) {
|
||||
if t.OnTappedSecondary != nil {
|
||||
t.OnTappedSecondary(e, t.ListItemID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/dweymouth/supersonic/ui/util"
|
||||
)
|
||||
|
||||
type tracklistRowBase struct {
|
||||
FocusListRowBase
|
||||
|
||||
OnTappedSecondary func(e *fyne.PointEvent, trackIdx int)
|
||||
|
||||
// set by extending widget
|
||||
playingIcon fyne.CanvasObject
|
||||
|
||||
// internal state
|
||||
tracklist *Tracklist
|
||||
trackNum int
|
||||
trackID string
|
||||
isPlaying bool
|
||||
isFavorite bool
|
||||
playCount int
|
||||
|
||||
num *widget.Label
|
||||
name *widget.RichText // for bold support
|
||||
artist *MultiHyperlink
|
||||
album *MultiHyperlink // for disabled support, if albumID is ""
|
||||
dur *widget.Label
|
||||
year *widget.Label
|
||||
favorite *fyne.Container
|
||||
rating *StarRating
|
||||
bitrate *widget.Label
|
||||
plays *widget.Label
|
||||
comment *widget.Label
|
||||
size *widget.Label
|
||||
path *widget.Label
|
||||
}
|
||||
|
||||
type TracklistRow interface {
|
||||
FocusListRow
|
||||
|
||||
TrackID() string
|
||||
Update(model *util.TrackListModel, rowNum int)
|
||||
}
|
||||
|
||||
type ExpandedTracklistRow struct {
|
||||
tracklistRowBase
|
||||
}
|
||||
|
||||
type CompactTracklistRow struct {
|
||||
tracklistRowBase
|
||||
}
|
||||
|
||||
var (
|
||||
_ TracklistRow = (*CompactTracklistRow)(nil)
|
||||
_ TracklistRow = (*ExpandedTracklistRow)(nil)
|
||||
)
|
||||
|
||||
func NewTracklistRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *ExpandedTracklistRow {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewCompactTracklistRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *CompactTracklistRow {
|
||||
t := &CompactTracklistRow{}
|
||||
t.ExtendBaseWidget(t)
|
||||
t.tracklistRowBase.create(tracklist)
|
||||
t.playingIcon = playingIcon
|
||||
|
||||
t.Content = container.New(tracklist.colLayout,
|
||||
t.num, t.name, t.artist, t.album, t.dur, t.year, t.favorite, t.rating, t.plays, t.comment, t.bitrate, t.size, t.path)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *tracklistRowBase) create(tracklist *Tracklist) {
|
||||
t.tracklist = tracklist
|
||||
t.num = util.NewTrailingAlignLabel()
|
||||
t.name = util.NewTruncatingRichText()
|
||||
t.artist = NewMultiHyperlink()
|
||||
t.artist.OnTapped = tracklist.onArtistTapped
|
||||
t.album = NewMultiHyperlink()
|
||||
t.album.OnTapped = func(id string) { tracklist.onAlbumTapped(id) }
|
||||
t.dur = util.NewTrailingAlignLabel()
|
||||
t.year = util.NewTrailingAlignLabel()
|
||||
favorite := NewFavoriteIcon()
|
||||
favorite.OnTapped = t.toggleFavorited
|
||||
t.favorite = container.NewCenter(favorite)
|
||||
t.rating = NewStarRating()
|
||||
t.rating.IsDisabled = t.tracklist.Options.DisableRating
|
||||
t.rating.StarSize = 16
|
||||
t.rating.OnRatingChanged = t.setTrackRating
|
||||
t.plays = util.NewTrailingAlignLabel()
|
||||
t.comment = util.NewTruncatingLabel()
|
||||
t.bitrate = util.NewTrailingAlignLabel()
|
||||
t.size = util.NewTrailingAlignLabel()
|
||||
t.path = util.NewTruncatingLabel()
|
||||
}
|
||||
|
||||
func (t *tracklistRowBase) TrackID() string {
|
||||
return t.trackID
|
||||
}
|
||||
|
||||
func (t *tracklistRowBase) Update(tm *util.TrackListModel, rowNum int) {
|
||||
changed := false
|
||||
if tm.Selected != t.Selected {
|
||||
t.Selected = tm.Selected
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update info that can change if this row is bound to
|
||||
// a new track (*mediaprovider.Track)
|
||||
tr := tm.Track
|
||||
if tr.ID != t.trackID {
|
||||
t.EnsureUnfocused()
|
||||
t.trackID = tr.ID
|
||||
|
||||
t.name.Segments[0].(*widget.TextSegment).Text = tr.Name
|
||||
t.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs)
|
||||
t.album.BuildSegments([]string{tr.Album}, []string{tr.AlbumID})
|
||||
t.dur.Text = util.SecondsToTimeString(float64(tr.Duration))
|
||||
t.year.Text = strconv.Itoa(tr.Year)
|
||||
t.plays.Text = strconv.Itoa(int(tr.PlayCount))
|
||||
t.comment.Text = tr.Comment
|
||||
t.bitrate.Text = strconv.Itoa(tr.BitRate)
|
||||
t.size.Text = util.BytesToSizeString(tr.Size)
|
||||
t.path.Text = tr.FilePath
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update track num if needed
|
||||
// (which can change based on bound *mediaprovider.Track or tracklist.AutoNumber)
|
||||
if t.trackNum != rowNum {
|
||||
discNum := -1
|
||||
var str string
|
||||
if rowNum < 0 {
|
||||
rowNum = tr.TrackNumber
|
||||
if t.tracklist.Options.ShowDiscNumber {
|
||||
discNum = tr.DiscNumber
|
||||
}
|
||||
}
|
||||
t.trackNum = rowNum
|
||||
if discNum >= 0 {
|
||||
str = fmt.Sprintf("%d.%02d", discNum, rowNum)
|
||||
} else {
|
||||
str = strconv.Itoa(rowNum)
|
||||
}
|
||||
t.num.Text = str
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update play count if needed
|
||||
if tr.PlayCount != t.playCount {
|
||||
t.playCount = tr.PlayCount
|
||||
t.plays.Text = strconv.Itoa(int(tr.PlayCount))
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Render whether track is playing or not
|
||||
if isPlaying := t.tracklist.nowPlayingID == tr.ID; isPlaying != t.isPlaying {
|
||||
t.isPlaying = isPlaying
|
||||
t.name.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying
|
||||
|
||||
if isPlaying {
|
||||
t.Content.(*fyne.Container).Objects[0] = t.playingIcon
|
||||
} else {
|
||||
t.Content.(*fyne.Container).Objects[0] = t.num
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update favorite column
|
||||
if tr.Favorite != t.isFavorite {
|
||||
t.isFavorite = tr.Favorite
|
||||
t.favorite.Objects[0].(*FavoriteIcon).Favorite = tr.Favorite
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update rating column
|
||||
if t.rating.Rating != tr.Rating {
|
||||
t.rating.Rating = tr.Rating
|
||||
t.rating.Refresh()
|
||||
}
|
||||
if t.rating.IsDisabled != t.tracklist.Options.DisableRating {
|
||||
t.rating.IsDisabled = t.tracklist.Options.DisableRating
|
||||
t.rating.Refresh()
|
||||
}
|
||||
|
||||
// Show only columns configured to be visible
|
||||
updateHidden := func(hiddenPtr *bool, colName string) {
|
||||
colHidden := !t.tracklist.visibleColumns[ColNumber(colName)]
|
||||
if colHidden != *hiddenPtr {
|
||||
*hiddenPtr = colHidden
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
updateHidden(&t.artist.Hidden, ColumnArtist)
|
||||
updateHidden(&t.album.Hidden, ColumnAlbum)
|
||||
updateHidden(&t.dur.Hidden, ColumnTime)
|
||||
updateHidden(&t.year.Hidden, ColumnYear)
|
||||
updateHidden(&t.favorite.Hidden, ColumnFavorite)
|
||||
updateHidden(&t.rating.Hidden, ColumnRating)
|
||||
updateHidden(&t.plays.Hidden, ColumnPlays)
|
||||
updateHidden(&t.comment.Hidden, ColumnComment)
|
||||
updateHidden(&t.bitrate.Hidden, ColumnBitrate)
|
||||
updateHidden(&t.size.Hidden, ColumnSize)
|
||||
updateHidden(&t.path.Hidden, ColumnPath)
|
||||
|
||||
if changed {
|
||||
t.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tracklistRowBase) toggleFavorited() {
|
||||
t.isFavorite = !t.isFavorite
|
||||
favIcon := t.favorite.Objects[0].(*FavoriteIcon)
|
||||
favIcon.Favorite = t.isFavorite
|
||||
t.favorite.Refresh()
|
||||
t.tracklist.onSetFavorite(t.trackID, t.isFavorite)
|
||||
}
|
||||
|
||||
func (t *tracklistRowBase) setTrackRating(rating int) {
|
||||
t.tracklist.onSetRating(t.trackID, rating)
|
||||
}
|
||||
|
||||
func (t *tracklistRowBase) TappedSecondary(e *fyne.PointEvent) {
|
||||
if t.OnTappedSecondary != nil {
|
||||
t.OnTappedSecondary(e, t.ListItemID)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user