diff --git a/go.mod b/go.mod index dc95f667..c5799653 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 github.com/cenkalti/dominantcolor v1.0.2 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 + github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee diff --git a/go.sum b/go.sum index 335b6a50..784aacb1 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+ github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe h1:owGwqph+Y+PqjDiWjZjOFOhlo8QsLs+LHrHojaaBo34= +github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe/go.mod h1:sbOhla4VcfFb4OjXiUFTLXMPTnhRUlVrDMhB8HtWR4o= github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 h1:RUIrnGY034rDMlcOui/daurwX5b+52KdUKhH9aXaDSg= github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64/go.mod h1:3YrjFDHMlhCsSZ/OvmJCxWm9QHSgOVWZBxnraZz9Z7c= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240604143614-256525c6a602 h1:k3jFLjmAuPJ5ZFNF57szZp8XrLIb6mIdEEGPkm6EZ7Q= diff --git a/sharedutil/sharedutil.go b/sharedutil/sharedutil.go index bad0e27a..cf5bccb5 100644 --- a/sharedutil/sharedutil.go +++ b/sharedutil/sharedutil.go @@ -1,9 +1,6 @@ package sharedutil import ( - "math" - "slices" - "github.com/dweymouth/supersonic/backend/mediaprovider" ) @@ -106,90 +103,33 @@ func TracksToIDs(tracks []*mediaprovider.Track) []string { }) } -type TrackReorderOp int - -const ( - MoveToTop TrackReorderOp = iota - MoveToBottom - MoveUp - MoveDown -) - // Reorder items and return a new track slice. // idxToMove must contain only valid indexes into tracks, and no repeats -func ReorderItems[T any](items []T, idxToMove []int, op TrackReorderOp) []T { - newItems := make([]T, len(items)) - switch op { - case MoveToTop: - topIdx := 0 - botIdx := len(idxToMove) - idxToMoveSet := ToSet(idxToMove) - for i, t := range items { - if _, ok := idxToMoveSet[i]; ok { - newItems[topIdx] = t - topIdx++ - } else { - newItems[botIdx] = t - botIdx++ - } - } - case MoveToBottom: - topIdx := 0 - botIdx := len(items) - len(idxToMove) - idxToMoveSet := ToSet(idxToMove) - for i, t := range items { - if _, ok := idxToMoveSet[i]; ok { - newItems[botIdx] = t - botIdx++ - } else { - newItems[topIdx] = t - topIdx++ - } - } - case MoveUp: - first := firstIdxCanMoveUp(idxToMove) - copy(newItems, items) - for _, i := range idxToMove { - if i < first { - continue - } - newItems[i-1], newItems[i] = newItems[i], newItems[i-1] +func ReorderItems[T any](items []T, idxToMove []int, insertIdx int) []T { + idxToMoveSet := ToSet(idxToMove) + + newItems := make([]T, 0, len(items)) + + // collect items that will end up before the insertion set + i := 0 + for ; i < len(items); i++ { + if insertIdx == i { + break } - case MoveDown: - last := lastIdxCanMoveDown(idxToMove, len(items)) - copy(newItems, items) - for i := len(idxToMove) - 1; i >= 0; i-- { - idx := idxToMove[i] - if idx > last { - continue - } - newItems[idx+1], newItems[idx] = newItems[idx], newItems[idx+1] + if _, ok := idxToMoveSet[i]; !ok { + newItems = append(newItems, items[i]) } } - return newItems -} -func firstIdxCanMoveUp(idxs []int) int { - prevIdx := -1 - slices.Sort(idxs) - for _, idx := range idxs { - if idx > prevIdx+1 { - return idx - } - prevIdx = idx + for _, idx := range idxToMove { + newItems = append(newItems, items[idx]) } - return math.MaxInt -} -func lastIdxCanMoveDown(idxs []int, lenSlice int) int { - prevIdx := lenSlice - slices.Sort(idxs) - for i := len(idxs) - 1; i >= 0; i-- { - idx := idxs[i] - if idx < prevIdx-1 { - return idx + for ; i < len(items); i++ { + if _, ok := idxToMoveSet[i]; !ok { + newItems = append(newItems, items[i]) } - prevIdx = idx } - return -1 + + return newItems } diff --git a/sharedutil/sharedutil_test.go b/sharedutil/sharedutil_test.go index 4d395e4d..f4d6e184 100644 --- a/sharedutil/sharedutil_test.go +++ b/sharedutil/sharedutil_test.go @@ -8,6 +8,7 @@ import ( ) func Test_ReorderItems(t *testing.T) { + tracks := []*mediaprovider.Track{ {ID: "a"}, // 0 {ID: "b"}, // 1 @@ -27,7 +28,7 @@ func Test_ReorderItems(t *testing.T) { {ID: "b"}, {ID: "e"}, } - newTracks := ReorderItems(tracks, idxToMove, MoveToTop) + newTracks := ReorderItems(tracks, idxToMove, 0) if !tracklistsEqual(t, newTracks, want) { t.Error("ReorderTracks: MoveToTop order incorrect") } @@ -42,40 +43,10 @@ func Test_ReorderItems(t *testing.T) { {ID: "c"}, {ID: "f"}, } - newTracks = ReorderItems(tracks, idxToMove, MoveToBottom) + newTracks = ReorderItems(tracks, idxToMove, len(tracks)) if !tracklistsEqual(t, newTracks, want) { t.Error("ReorderTracks: MoveToBottom order incorrect") } - - // test MoveUp: - idxToMove = []int{0, 1, 3, 5} - want = []*mediaprovider.Track{ - {ID: "a"}, - {ID: "b"}, - {ID: "d"}, - {ID: "c"}, - {ID: "f"}, - {ID: "e"}, - } - newTracks = ReorderItems(tracks, idxToMove, MoveUp) - if !tracklistsEqual(t, newTracks, want) { - t.Error("ReorderTracks: MoveUp order incorrect") - } - - // test MoveDown: - idxToMove = []int{2, 4, 5} - want = []*mediaprovider.Track{ - {ID: "a"}, - {ID: "b"}, - {ID: "d"}, - {ID: "c"}, - {ID: "e"}, - {ID: "f"}, - } - newTracks = ReorderItems(tracks, idxToMove, MoveDown) - if !tracklistsEqual(t, newTracks, want) { - t.Error("ReorderTracks: MoveDown order incorrect") - } } func tracklistsEqual(t *testing.T, a, b []*mediaprovider.Track) bool { diff --git a/ui/browsing/genrespage.go b/ui/browsing/genrespage.go index a8afdad7..504d9110 100644 --- a/ui/browsing/genrespage.go +++ b/ui/browsing/genrespage.go @@ -212,7 +212,6 @@ func NewGenreList(sorting widgets.ListHeaderSort) *GenreList { }, func(id widget.ListItemID, item fyne.CanvasObject) { row := item.(*GenreListRow) - a.list.SetItemForID(id, row) if row.Item != a.genres[id] { row.EnsureUnfocused() row.ListItemID = id diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index 8a63fab3..46f7076e 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -129,6 +129,7 @@ func NewNowPlayingPage( a.queueList = widgets.NewPlayQueueList(a.im, false) a.relatedList = widgets.NewPlayQueueList(a.im, true) + a.queueList.Reorderable = true a.queueList.OnReorderItems = a.doSetNewTrackOrder a.queueList.OnDownload = contr.ShowDownloadDialog a.queueList.OnShare = func(tracks []*mediaprovider.Track) { @@ -502,7 +503,7 @@ func (a *NowPlayingPage) Refresh() { a.BaseWidget.Refresh() } -func (a *NowPlayingPage) doSetNewTrackOrder(trackIDs []string, op sharedutil.TrackReorderOp) { +func (a *NowPlayingPage) doSetNewTrackOrder(trackIDs []string, insertPos int) { trackIDSet := sharedutil.ToSet(trackIDs) idxs := make([]int, 0, len(trackIDs)) for i, tr := range a.queue { @@ -510,7 +511,7 @@ func (a *NowPlayingPage) doSetNewTrackOrder(trackIDs []string, op sharedutil.Tra idxs = append(idxs, i) } } - newTracks := sharedutil.ReorderItems(a.queue, idxs, op) + newTracks := sharedutil.ReorderItems(a.queue, idxs, insertPos) a.pm.UpdatePlayQueue(newTracks) } diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index 4eb9f4b7..58f7f136 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -86,17 +86,16 @@ func newPlaylistPage( a.tracklist.OnVisibleColumnsChanged = func(cols []string) { conf.TracklistColumns = cols } + a.tracklist.OnReorderTracks = a.doSetNewTrackOrder _, canRate := a.sm.Server.(mediaprovider.SupportsRating) _, canShare := a.sm.Server.(mediaprovider.SupportsSharing) remove := fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist) remove.Icon = theme.ContentClearIcon() a.tracklist.Options = widgets.TracklistOptions{ - DisableRating: !canRate, - DisableSharing: !canShare, - AuxiliaryMenuItems: []*fyne.MenuItem{ - util.NewReorderTracksSubmenu(a.doSetNewTrackOrder), - remove, - }, + Reorderable: true, + DisableRating: !canRate, + DisableSharing: !canShare, + AuxiliaryMenuItems: []*fyne.MenuItem{remove}, } // connect tracklist actions a.contr.ConnectTracklistActions(a.tracklist) @@ -118,6 +117,7 @@ func (a *PlaylistPage) Save() SavedPage { p.trackSort = a.tracklist.Sorting() p.widgetPool.Release(util.WidgetTypePlaylistPageHeader, a.header) a.tracklist.Clear() + a.tracklist.OnReorderTracks = nil p.widgetPool.Release(util.WidgetTypeTracklist, a.tracklist) return &p } @@ -178,28 +178,32 @@ func renumberTracks(tracks []*mediaprovider.Track) { } } -func (a *PlaylistPage) doSetNewTrackOrder(op sharedutil.TrackReorderOp) { +func (a *PlaylistPage) doSetNewTrackOrder(ids []string, newPos int) { // Since the tracklist view may be sorted in a different order than the // actual running order, we need to get the IDs of the selected tracks // from the tracklist and convert them to indices in the *original* run order - idSet := sharedutil.ToSet(a.tracklist.SelectedTrackIDs()) + idSet := sharedutil.ToSet(ids) idxs := make([]int, 0, len(idSet)) for i, tr := range a.tracks { if _, ok := idSet[tr.ID]; ok { idxs = append(idxs, i) } } - newTracks := sharedutil.ReorderItems(a.tracks, idxs, op) - ids := sharedutil.TracksToIDs(newTracks) - if err := a.sm.Server.ReplacePlaylistTracks(a.playlistID, ids); err != nil { - log.Printf("error updating playlist: %s", err.Error()) - } else { - renumberTracks(newTracks) - // force-switch back to unsorted view to show new track order - a.tracklist.SetSorting(widgets.TracklistSort{}) - a.tracklist.SetTracks(newTracks) - a.tracklist.UnselectAll() - } + newTracks := sharedutil.ReorderItems(a.tracks, idxs, newPos) + // we can't block the UI waiting for the server so assume it will succeed + go func() { + ids = sharedutil.TracksToIDs(newTracks) + if err := a.sm.Server.ReplacePlaylistTracks(a.playlistID, ids); err != nil { + log.Printf("error updating playlist: %s", err.Error()) + } + }() + + renumberTracks(newTracks) + // force-switch back to unsorted view to show new track order + a.tracklist.SetSorting(widgets.TracklistSort{}) + a.tracklist.SetTracks(newTracks) + a.tracklist.UnselectAll() + a.tracks = newTracks } func (a *PlaylistPage) onRemoveSelectedFromPlaylist() { diff --git a/ui/browsing/playlistspage.go b/ui/browsing/playlistspage.go index 305c0705..95c4ac19 100644 --- a/ui/browsing/playlistspage.go +++ b/ui/browsing/playlistspage.go @@ -354,7 +354,6 @@ func NewPlaylistList(initialSort widgets.ListHeaderSort) *PlaylistList { row := item.(*PlaylistListRow) if row.PlaylistID != a.playlists[id].ID { row.EnsureUnfocused() - a.list.SetItemForID(id, row) row.ListItemID = id row.PlaylistID = a.playlists[id].ID row.nameLabel.Text = a.playlists[id].Name diff --git a/ui/browsing/radiospage.go b/ui/browsing/radiospage.go index 82b6f14c..2cb04c9e 100644 --- a/ui/browsing/radiospage.go +++ b/ui/browsing/radiospage.go @@ -279,7 +279,6 @@ func NewRadioList(nowPlayingIDPtr *string) *RadioList { }, func(id widget.ListItemID, item fyne.CanvasObject) { row := item.(*RadioListRow) - a.list.SetItemForID(id, row) changed := false if row.Item != a.radios[id] { row.EnsureUnfocused() diff --git a/ui/util/util.go b/ui/util/util.go index d237ac59..884e715f 100644 --- a/ui/util/util.go +++ b/ui/util/util.go @@ -17,7 +17,6 @@ import ( "fyne.io/fyne/v2/widget" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/res" - "github.com/dweymouth/supersonic/sharedutil" myTheme "github.com/dweymouth/supersonic/ui/theme" "golang.org/x/net/html" ) @@ -227,18 +226,6 @@ func NewRatingSubmenu(onSetRating func(int)) *fyne.MenuItem { return ratingMenu } -func NewReorderTracksSubmenu(onReorderTracks func(sharedutil.TrackReorderOp)) *fyne.MenuItem { - reorderMenu := fyne.NewMenuItem("Reorder tracks", nil) - reorderMenu.Icon = myTheme.SortIcon - reorderMenu.ChildMenu = fyne.NewMenu("", []*fyne.MenuItem{ - fyne.NewMenuItem("Move to top", func() { onReorderTracks(sharedutil.MoveToTop) }), - fyne.NewMenuItem("Move up", func() { onReorderTracks(sharedutil.MoveUp) }), - fyne.NewMenuItem("Move down", func() { onReorderTracks(sharedutil.MoveDown) }), - fyne.NewMenuItem("Move to bottom", func() { onReorderTracks(sharedutil.MoveToBottom) }), - }...) - return reorderMenu -} - func AddHeaderBackground(obj fyne.CanvasObject) *fyne.Container { return AddHeaderBackgroundWithColorName(obj, myTheme.ColorNamePageHeader) } diff --git a/ui/widgets/albumfilterbutton.go b/ui/widgets/albumfilterbutton.go index 78c18293..68f92df5 100644 --- a/ui/widgets/albumfilterbutton.go +++ b/ui/widgets/albumfilterbutton.go @@ -274,7 +274,6 @@ func NewGenreFilterSubsection(onChanged func([]string), initialSelectedGenres [] _, selected := g.selectedGenres[genre] g.selectedGenresMutex.RUnlock() row := obj.(*genreListViewRow) - g.genreListView.SetItemForID(id, row) row.ListItemID = id row.Content.(*widget.Check).Text = genre row.Content.(*widget.Check).Checked = selected diff --git a/ui/widgets/focuslist.go b/ui/widgets/focuslist.go index 77c0d320..3c1c2c6b 100644 --- a/ui/widgets/focuslist.go +++ b/ui/widgets/focuslist.go @@ -9,16 +9,17 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + + list "github.com/dweymouth/fyne-advanced-list" ) // FocusList extends List to be disabled so that the focus manager // considers it unfocusable, and adds utilities for handling our // own focus navigation on the rows directly (with FocusListRow type FocusList struct { - widget.List + list.List - mutex sync.Mutex - itemForIndex map[widget.ListItemID]FocusListRow + mutex sync.Mutex } type FocusListRow interface { @@ -33,13 +34,12 @@ type FocusListRow interface { func NewFocusList(len func() int, create func() fyne.CanvasObject, update func(widget.GridWrapItemID, fyne.CanvasObject)) *FocusList { g := &FocusList{ - List: widget.List{ + List: list.List{ HideSeparators: true, Length: len, CreateItem: create, UpdateItem: update, }, - itemForIndex: make(map[int]FocusListRow), } g.ExtendBaseWidget(g) return g @@ -53,21 +53,6 @@ func (g *FocusList) Disable() {} func (g *FocusList) Enable() {} -// MUST be called *before* updating the ListItemID field to the -// new ItemID this row will be bound to. -func (g *FocusList) SetItemForID(id widget.ListItemID, item FocusListRow) { - g.mutex.Lock() - if other, ok := g.itemForIndex[id]; ok && other == item { - delete(g.itemForIndex, other.ItemID()) - } - g.itemForIndex[id] = item - g.mutex.Unlock() -} - -func (g *FocusList) ClearItemForIDMap() { - g.itemForIndex = make(map[int]FocusListRow) -} - func (g *FocusList) FocusNeighbor(curItem widget.ListItemID, up bool) { focusIdx := curItem + 1 if up { @@ -77,10 +62,10 @@ func (g *FocusList) FocusNeighbor(curItem widget.ListItemID, up bool) { g.ScrollTo(focusIdx) } g.mutex.Lock() - other, ok := g.itemForIndex[focusIdx] + other := g.ItemForID(focusIdx) g.mutex.Unlock() - if ok { - fyne.CurrentApp().Driver().CanvasForObject(g).Focus(other) + if other != nil { + fyne.CurrentApp().Driver().CanvasForObject(g).Focus(other.(fyne.Focusable)) } } diff --git a/ui/widgets/playqueuelist.go b/ui/widgets/playqueuelist.go index 57b6cbd5..e3e0c244 100644 --- a/ui/widgets/playqueuelist.go +++ b/ui/widgets/playqueuelist.go @@ -31,6 +31,7 @@ type PlayQueueListModel struct { type PlayQueueList struct { widget.BaseWidget + Reorderable bool DisableRating bool DisableSharing bool @@ -46,7 +47,7 @@ type PlayQueueList struct { OnDownload func(tracks []*mediaprovider.Track, downloadName string) OnShare func(tracks []*mediaprovider.Track) OnShowArtistPage func(artistID string) - OnReorderItems func(itemIDs []string, op sharedutil.TrackReorderOp) + OnReorderItems func(itemIDs []string, reorderTo int) useNonQueueMenu bool menu *widget.PopUpMenu // ctx menu for when only tracks are selected @@ -96,20 +97,29 @@ func NewPlayQueueList(im *backend.ImageManager, useNonQueueMenu bool) *PlayQueue p.tracksMutex.RUnlock() tr := item.(*PlayQueueListRow) - p.list.SetItemForID(itemID, tr) if tr.trackID != model.Item.Metadata().ID || tr.ListItemID != itemID { tr.ListItemID = itemID } tr.Update(model, itemID+1) }, ) + p.list.OnDragBegin = func(id int) { + if !p.items[id].Selected { + p.selectTrack(id) + p.list.Refresh() + } + } + p.list.OnDragEnd = func(dragged, insertPos int) { + if p.OnReorderItems != nil { + p.OnReorderItems(p.selectedItemIDs(), insertPos) + } + } return p } func (p *PlayQueueList) SetTracks(trs []*mediaprovider.Track) { p.tracksMutex.Lock() - p.list.ClearItemForIDMap() p.items = util.ToTrackListModels(trs) p.tracksMutex.Unlock() p.Refresh() @@ -117,7 +127,6 @@ func (p *PlayQueueList) SetTracks(trs []*mediaprovider.Track) { func (p *PlayQueueList) SetItems(items []mediaprovider.MediaItem) { p.tracksMutex.Lock() - p.list.ClearItemForIDMap() p.items = sharedutil.MapSlice(items, func(item mediaprovider.MediaItem) *util.TrackListModel { return &util.TrackListModel{Item: item} }) @@ -152,7 +161,12 @@ func (p *PlayQueueList) UnselectAll() { p.tracksMutex.RLock() util.UnselectAllItems(p.items) p.tracksMutex.RUnlock() - p.Refresh() + p.list.Refresh() +} + +func (p *PlayQueueList) Refresh() { + p.list.EnableDragging = p.Reorderable + p.BaseWidget.Refresh() } func (p *PlayQueueList) lenTracks() int { @@ -295,15 +309,9 @@ func (p *PlayQueueList) ensureTracksMenu() { } }) remove.Icon = theme.ContentRemoveIcon() - reorder := util.NewReorderTracksSubmenu(func(tro sharedutil.TrackReorderOp) { - if p.OnReorderItems != nil { - p.OnReorderItems(p.selectedItemIDs(), tro) - } - }) menuItems = append(menuItems, fyne.NewMenuItemSeparator(), - remove, - reorder) + remove) } p.menu = widget.NewPopUpMenu( @@ -322,13 +330,8 @@ func (p *PlayQueueList) ensureRadiosMenu() { } }) remove.Icon = theme.ContentRemoveIcon() - reorder := util.NewReorderTracksSubmenu(func(tro sharedutil.TrackReorderOp) { - if p.OnReorderItems != nil { - p.OnReorderItems(p.selectedItemIDs(), tro) - } - }) p.radiosMenu = widget.NewPopUpMenu( - fyne.NewMenu("", remove, reorder), + fyne.NewMenu("", remove), fyne.CurrentApp().Driver().CanvasForObject(p), ) } diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index feb8d119..6cd9c555 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -33,6 +33,9 @@ type TracklistOptions struct { // or to use the number from the track's metadata AutoNumber bool + // Reorderable sets whether the tracklist supports drag-and-drop reordering. + Reorderable bool + // ShowDiscNumber sets whether to display the disc number as part of the '#' column, // (with format %d.%02d). Only applies if AutoNumber==false. ShowDiscNumber bool @@ -74,6 +77,7 @@ type Tracklist struct { OnDownload func(tracks []*mediaprovider.Track, downloadName string) OnShare func(trackID string) OnPlaySongRadio func(track *mediaprovider.Track) + OnReorderTracks func(trackIDs []string, insertPos int) OnShowArtistPage func(artistID string) OnShowAlbumPage func(albumID string) @@ -170,7 +174,6 @@ func NewTracklist(tracks []*mediaprovider.Track, im *backend.ImageManager, useCo t.tracksMutex.RUnlock() tr := item.(TracklistRow) - t.list.SetItemForID(itemID, tr) if tr.TrackID() != model.Item.Metadata().ID || tr.ItemID() != itemID { tr.SetItemID(itemID) } @@ -183,6 +186,17 @@ func NewTracklist(tracks []*mediaprovider.Track, im *backend.ImageManager, useCo t.OnTrackShown(itemID) } }) + t.list.OnDragBegin = func(id int) { + if !t.tracks[id].Selected { + t.selectTrack(id) + t.list.Refresh() + } + } + t.list.OnDragEnd = func(dragged, insertPos int) { + if t.OnReorderTracks != nil { + t.OnReorderTracks(t.SelectedTrackIDs(), insertPos) + } + } t.container = container.NewBorder(t.hdr, nil, nil, nil, t.list) return t } @@ -300,7 +314,6 @@ func (t *Tracklist) Clear() { defer t.tracksMutex.Unlock() t.tracks = nil t.tracksOrigOrder = nil - t.list.ClearItemForIDMap() } // Sets the tracks in the tracklist. Thread-safe. @@ -312,9 +325,6 @@ func (t *Tracklist) SetTracks(trs []*mediaprovider.Track) { func (t *Tracklist) _setTracks(trs []*mediaprovider.Track) { t.tracksMutex.Lock() defer t.tracksMutex.Unlock() - if t.list != nil { - t.list.ClearItemForIDMap() - } t.tracksOrigOrder = util.ToTrackListModels(trs) t.doSortTracks() } @@ -376,6 +386,7 @@ func (t *Tracklist) CreateRenderer() fyne.WidgetRenderer { } func (t *Tracklist) Refresh() { + t.list.EnableDragging = t.Options.Reorderable t.hdr.DisableSorting = t.Options.DisableSorting t.BaseWidget.Refresh() }