fullscreen play queue renders correctly
This commit is contained in:
@@ -25,10 +25,12 @@ type FullscreenPage struct {
|
||||
fullscreenPageState
|
||||
|
||||
queue []*mediaprovider.Track
|
||||
queueList *widgets.PlayQueueList
|
||||
imageLoadCancel context.CancelFunc
|
||||
card *widgets.LargeNowPlayingCard
|
||||
nowPlayingID string
|
||||
albumID string
|
||||
container *fyne.Container
|
||||
}
|
||||
|
||||
type fullscreenPageState struct {
|
||||
@@ -66,25 +68,28 @@ func NewFullscreenPage(
|
||||
a.contr.SetTrackRatings([]string{a.nowPlayingID}, rating)
|
||||
}
|
||||
|
||||
a.queueList = widgets.NewPlayQueueList(a.im)
|
||||
paddedLayout := &layouts.PercentPadLayout{
|
||||
LeftRightObjectPercent: .8,
|
||||
TopBottomObjectPercent: .8,
|
||||
}
|
||||
a.container = container.NewGridWithColumns(2,
|
||||
container.New(paddedLayout, a.card),
|
||||
container.New(paddedLayout,
|
||||
util.AddHeaderBackground(
|
||||
container.NewAppTabs(
|
||||
container.NewTabItem("Play Queue",
|
||||
container.NewBorder(layout.NewSpacer(), nil, nil, nil, a.queueList)),
|
||||
container.NewTabItem("Lyrics", layout.NewSpacer()),
|
||||
))),
|
||||
)
|
||||
|
||||
a.Reload()
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *FullscreenPage) CreateRenderer() fyne.WidgetRenderer {
|
||||
paddedLayout := &layouts.PercentPadLayout{
|
||||
LeftRightObjectPercent: .8,
|
||||
TopBottomObjectPercent: .8,
|
||||
}
|
||||
container := container.NewGridWithColumns(2,
|
||||
container.New(paddedLayout, a.card),
|
||||
container.New(paddedLayout,
|
||||
util.AddHeaderBackground(
|
||||
container.NewAppTabs(
|
||||
container.NewTabItem("Up Next", layout.NewSpacer()),
|
||||
container.NewTabItem("Lyrics", layout.NewSpacer()),
|
||||
))),
|
||||
)
|
||||
return widget.NewSimpleRenderer(container)
|
||||
return widget.NewSimpleRenderer(a.container)
|
||||
}
|
||||
|
||||
func (a *FullscreenPage) Save() SavedPage {
|
||||
@@ -115,10 +120,12 @@ func (a *FullscreenPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Tr
|
||||
a.card.SetCoverImage(img)
|
||||
}
|
||||
})
|
||||
a.queueList.SetNowPlaying(song.ID)
|
||||
}
|
||||
|
||||
func (a *FullscreenPage) Reload() {
|
||||
a.queue = a.pm.GetPlayQueue()
|
||||
a.queueList.SetTracks(a.queue)
|
||||
}
|
||||
|
||||
func (s *fullscreenPageState) Restore() Page {
|
||||
|
||||
@@ -58,6 +58,16 @@ func SelectTrackRange(tracks []*TrackListModel, idx int) {
|
||||
}
|
||||
}
|
||||
|
||||
func FindTrackByID(tracks []*TrackListModel, id string) (*mediaprovider.Track, int) {
|
||||
idx := sharedutil.Find(tracks, func(tr *TrackListModel) bool {
|
||||
return tr.Track.ID == id
|
||||
})
|
||||
if idx >= 0 {
|
||||
return tracks[idx].Track, idx
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
|
||||
+134
-14
@@ -1,20 +1,30 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"image"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/dweymouth/supersonic/backend"
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
"github.com/dweymouth/supersonic/ui/layouts"
|
||||
"github.com/dweymouth/supersonic/ui/os"
|
||||
"github.com/dweymouth/supersonic/ui/theme"
|
||||
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||
"github.com/dweymouth/supersonic/ui/util"
|
||||
)
|
||||
|
||||
const thumbnailSize = 60
|
||||
|
||||
type PlayQueueList struct {
|
||||
FocusList
|
||||
widget.BaseWidget
|
||||
|
||||
list *FocusList
|
||||
|
||||
OnShowArtistPage func(artistID string)
|
||||
OnPlayTrackAt func(idx int)
|
||||
@@ -22,10 +32,84 @@ type PlayQueueList struct {
|
||||
nowPlayingID string
|
||||
colLayout *layouts.ColumnsLayout
|
||||
|
||||
tracksMutex sync.Mutex
|
||||
tracksMutex sync.RWMutex
|
||||
tracks []*util.TrackListModel
|
||||
}
|
||||
|
||||
func NewPlayQueueList(im *backend.ImageManager) *PlayQueueList {
|
||||
p := &PlayQueueList{}
|
||||
p.ExtendBaseWidget(p)
|
||||
|
||||
// #, Cover, Title/Artist, Time
|
||||
p.colLayout = layouts.NewColumnsLayout([]float32{40, thumbnailSize, -1, 60})
|
||||
|
||||
playIconResource := theme.NewThemedResource(theme.MediaPlayIcon())
|
||||
playIconResource.ColorName = theme.ColorNamePrimary
|
||||
playIconImg := canvas.NewImageFromResource(playIconResource)
|
||||
playIconImg.FillMode = canvas.ImageFillContain
|
||||
playIconImg.SetMinSize(fyne.NewSquareSize(theme.IconInlineSize() * 1.5))
|
||||
|
||||
playingIcon := container.NewCenter(playIconImg)
|
||||
|
||||
p.list = NewFocusList(
|
||||
p.lenTracks,
|
||||
func() fyne.CanvasObject {
|
||||
return NewPlayQueueListRow(p, im, playingIcon)
|
||||
},
|
||||
func(itemID widget.ListItemID, item fyne.CanvasObject) {
|
||||
p.tracksMutex.RLock()
|
||||
// we could have removed tracks from the list in between
|
||||
// Fyne calling the length callback and this update callback
|
||||
// so the itemID may be out of bounds. if so, do nothing.
|
||||
if itemID >= len(p.tracks) {
|
||||
p.tracksMutex.RUnlock()
|
||||
return
|
||||
}
|
||||
model := p.tracks[itemID]
|
||||
p.tracksMutex.RUnlock()
|
||||
|
||||
tr := item.(*PlayQueueListRow)
|
||||
p.list.SetItemForID(itemID, tr)
|
||||
if tr.trackID != model.Track.ID || tr.ListItemID != itemID {
|
||||
tr.ListItemID = itemID
|
||||
}
|
||||
tr.Update(model, itemID+1)
|
||||
},
|
||||
)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PlayQueueList) SetTracks(trs []*mediaprovider.Track) {
|
||||
p.tracksMutex.Lock()
|
||||
p.list.ClearItemForIDMap()
|
||||
p.tracks = util.ToTrackListModels(trs)
|
||||
p.tracksMutex.Unlock()
|
||||
p.Refresh()
|
||||
}
|
||||
|
||||
// Sets the currently playing track ID and updates the list rendering
|
||||
func (p *PlayQueueList) SetNowPlaying(trackID string) {
|
||||
prevNowPlaying := p.nowPlayingID
|
||||
p.tracksMutex.RLock()
|
||||
trPrev, idxPrev := util.FindTrackByID(p.tracks, prevNowPlaying)
|
||||
tr, idx := util.FindTrackByID(p.tracks, trackID)
|
||||
p.tracksMutex.RUnlock()
|
||||
p.nowPlayingID = trackID
|
||||
if trPrev != nil {
|
||||
p.list.RefreshItem(idxPrev)
|
||||
}
|
||||
if tr != nil {
|
||||
p.list.RefreshItem(idx)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlayQueueList) lenTracks() int {
|
||||
p.tracksMutex.RLock()
|
||||
defer p.tracksMutex.RUnlock()
|
||||
return len(p.tracks)
|
||||
}
|
||||
|
||||
func (t *PlayQueueList) onArtistTapped(artistID string) {
|
||||
if t.OnShowArtistPage != nil {
|
||||
t.OnShowArtistPage(artistID)
|
||||
@@ -55,26 +139,37 @@ func (p *PlayQueueList) onSelectTrack(idx int) {
|
||||
}
|
||||
|
||||
func (p *PlayQueueList) selectTrack(idx int) {
|
||||
p.tracksMutex.Lock()
|
||||
defer p.tracksMutex.Unlock()
|
||||
p.tracksMutex.RLock()
|
||||
defer p.tracksMutex.RUnlock()
|
||||
util.SelectTrack(p.tracks, idx)
|
||||
}
|
||||
|
||||
func (p *PlayQueueList) selectAddOrRemove(idx int) {
|
||||
p.tracksMutex.Lock()
|
||||
defer p.tracksMutex.Unlock()
|
||||
p.tracksMutex.RLock()
|
||||
defer p.tracksMutex.RUnlock()
|
||||
p.tracks[idx].Selected = !p.tracks[idx].Selected
|
||||
}
|
||||
|
||||
func (p *PlayQueueList) selectRange(idx int) {
|
||||
p.tracksMutex.Lock()
|
||||
defer p.tracksMutex.Unlock()
|
||||
p.tracksMutex.RLock()
|
||||
defer p.tracksMutex.RUnlock()
|
||||
util.SelectTrackRange(p.tracks, idx)
|
||||
}
|
||||
|
||||
func (p *PlayQueueList) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
||||
|
||||
}
|
||||
|
||||
func (p *PlayQueueList) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(p.list)
|
||||
}
|
||||
|
||||
type PlayQueueListRow struct {
|
||||
FocusListRowBase
|
||||
|
||||
OnTappedSecondary func(e *fyne.PointEvent, trackIdx int)
|
||||
|
||||
imageLoader util.ThumbnailLoader
|
||||
playQueueList *PlayQueueList
|
||||
trackID string
|
||||
isPlaying bool
|
||||
@@ -87,16 +182,18 @@ type PlayQueueListRow struct {
|
||||
time *widget.Label
|
||||
}
|
||||
|
||||
func NewPlayQueueListRow(playQueueList *PlayQueueList, playingIcon fyne.CanvasObject) *PlayQueueListRow {
|
||||
func NewPlayQueueListRow(playQueueList *PlayQueueList, im *backend.ImageManager, playingIcon fyne.CanvasObject) *PlayQueueListRow {
|
||||
p := &PlayQueueListRow{
|
||||
playingIcon: playingIcon,
|
||||
playQueueList: playQueueList,
|
||||
num: widget.NewLabel(""),
|
||||
cover: NewImagePlaceholder(theme.TracksIcon, 64),
|
||||
cover: NewImagePlaceholder(myTheme.TracksIcon, thumbnailSize),
|
||||
title: util.NewTruncatingLabel(),
|
||||
artist: NewMultiHyperlink(),
|
||||
time: util.NewTrailingAlignLabel(),
|
||||
}
|
||||
p.ExtendBaseWidget(p)
|
||||
|
||||
p.artist.OnTapped = playQueueList.onArtistTapped
|
||||
p.OnDoubleTapped = func() {
|
||||
playQueueList.onPlayTrackAt(p.ItemID())
|
||||
@@ -104,8 +201,18 @@ func NewPlayQueueListRow(playQueueList *PlayQueueList, playingIcon fyne.CanvasOb
|
||||
p.OnTapped = func() {
|
||||
playQueueList.onSelectTrack(p.ItemID())
|
||||
}
|
||||
//p.title.TextStyle.Bold = true
|
||||
p.ExtendBaseWidget(p)
|
||||
p.OnTappedSecondary = playQueueList.onShowContextMenu
|
||||
p.OnFocusNeighbor = func(up bool) {
|
||||
playQueueList.list.FocusNeighbor(p.ItemID(), up)
|
||||
}
|
||||
|
||||
p.imageLoader = util.NewThumbnailLoader(im, func(i image.Image) {
|
||||
p.cover.SetImage(i, false)
|
||||
})
|
||||
p.imageLoader.OnBeforeLoad = func() {
|
||||
p.cover.SetImage(nil, false)
|
||||
}
|
||||
|
||||
p.Content = container.New(playQueueList.colLayout,
|
||||
container.NewCenter(p.num),
|
||||
p.cover,
|
||||
@@ -116,6 +223,12 @@ func NewPlayQueueListRow(playQueueList *PlayQueueList, playingIcon fyne.CanvasOb
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PlayQueueListRow) TappedSecondary(e *fyne.PointEvent) {
|
||||
if p.OnTappedSecondary != nil {
|
||||
p.OnTappedSecondary(e, p.ListItemID)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
|
||||
changed := false
|
||||
if tm.Selected != p.Selected {
|
||||
@@ -123,12 +236,19 @@ func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
|
||||
changed = true
|
||||
}
|
||||
|
||||
if num := strconv.Itoa(rowNum); p.num.Text != num {
|
||||
p.num.Text = num
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Update info that can change if this row is bound to
|
||||
// a new track (*mediaprovider.Track)
|
||||
tr := tm.Track
|
||||
if tr.ID != p.trackID {
|
||||
p.imageLoader.Load(tm.Track.CoverArtID)
|
||||
p.EnsureUnfocused()
|
||||
p.trackID = tr.ID
|
||||
p.title.Text = tr.Name
|
||||
p.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs)
|
||||
p.time.Text = util.SecondsToTimeString(float64(tr.Duration))
|
||||
changed = true
|
||||
@@ -142,7 +262,7 @@ func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
|
||||
if isPlaying {
|
||||
p.Content.(*fyne.Container).Objects[0] = p.playingIcon
|
||||
} else {
|
||||
p.Content.(*fyne.Container).Objects[0] = p.num
|
||||
p.Content.(*fyne.Container).Objects[0] = container.NewCenter(p.num)
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
|
||||
+5
-15
@@ -279,8 +279,8 @@ func (t *Tracklist) SetSorting(sorting TracklistSort) {
|
||||
func (t *Tracklist) SetNowPlaying(trackID string) {
|
||||
prevNowPlaying := t.nowPlayingID
|
||||
t.tracksMutex.RLock()
|
||||
trPrev, idxPrev := t.findTrackByID(prevNowPlaying)
|
||||
tr, idx := t.findTrackByID(trackID)
|
||||
trPrev, idxPrev := util.FindTrackByID(t.tracks, prevNowPlaying)
|
||||
tr, idx := util.FindTrackByID(t.tracks, trackID)
|
||||
t.tracksMutex.RUnlock()
|
||||
t.nowPlayingID = trackID
|
||||
if trPrev != nil {
|
||||
@@ -294,7 +294,7 @@ func (t *Tracklist) SetNowPlaying(trackID string) {
|
||||
// Increments the play count of the given track and updates the list rendering
|
||||
func (t *Tracklist) IncrementPlayCount(trackID string) {
|
||||
t.tracksMutex.RLock()
|
||||
tr, idx := t.findTrackByID(trackID)
|
||||
tr, idx := util.FindTrackByID(t.tracks, trackID)
|
||||
t.tracksMutex.RUnlock()
|
||||
if tr != nil {
|
||||
tr.PlayCount += 1
|
||||
@@ -572,7 +572,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
||||
|
||||
func (t *Tracklist) onSetFavorite(trackID string, fav bool) {
|
||||
t.tracksMutex.RLock()
|
||||
tr, _ := t.findTrackByID(trackID)
|
||||
tr, _ := util.FindTrackByID(t.tracks, trackID)
|
||||
t.tracksMutex.RUnlock()
|
||||
t.onSetFavorites([]*mediaprovider.Track{tr}, fav, false)
|
||||
}
|
||||
@@ -593,7 +593,7 @@ func (t *Tracklist) onSetFavorites(tracks []*mediaprovider.Track, fav bool, need
|
||||
func (t *Tracklist) onSetRating(trackID string, rating int) {
|
||||
// update our own track model
|
||||
t.tracksMutex.RLock()
|
||||
tr, _ := t.findTrackByID(trackID)
|
||||
tr, _ := util.FindTrackByID(t.tracks, trackID)
|
||||
t.tracksMutex.RUnlock()
|
||||
t.onSetRatings([]*mediaprovider.Track{tr}, rating, false)
|
||||
}
|
||||
@@ -629,16 +629,6 @@ func (t *Tracklist) onDownload(tracks []*mediaprovider.Track, downloadName strin
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracklist) findTrackByID(id string) (*mediaprovider.Track, int) {
|
||||
idx := sharedutil.Find(t.tracks, func(tr *util.TrackListModel) bool {
|
||||
return tr.Track.ID == id
|
||||
})
|
||||
if idx >= 0 {
|
||||
return t.tracks[idx].Track, idx
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (t *Tracklist) selectedTrackModels() []*util.TrackListModel {
|
||||
t.tracksMutex.RLock()
|
||||
defer t.tracksMutex.RUnlock()
|
||||
|
||||
Reference in New Issue
Block a user