From 9928861db90c68631bb9344dad5a656432bebc75 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 29 May 2024 16:56:37 -0700 Subject: [PATCH] refactor: extract out TracklistRow interface and base impl --- ui/widgets/focuslist.go | 5 + ui/widgets/tracklist.go | 197 +------------------------------ ui/widgets/tracklistrow.go | 233 +++++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 193 deletions(-) create mode 100644 ui/widgets/tracklistrow.go diff --git a/ui/widgets/focuslist.go b/ui/widgets/focuslist.go index 7b30460..2710704 100644 --- a/ui/widgets/focuslist.go +++ b/ui/widgets/focuslist.go @@ -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() diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 0d9dc2e..e607f35 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -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) - } -} diff --git a/ui/widgets/tracklistrow.go b/ui/widgets/tracklistrow.go new file mode 100644 index 0000000..8d6ccb6 --- /dev/null +++ b/ui/widgets/tracklistrow.go @@ -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) + } +}