enable drag and drop reordering
This commit is contained in:
@@ -8,6 +8,7 @@ require (
|
|||||||
github.com/Microsoft/go-winio v0.6.2
|
github.com/Microsoft/go-winio v0.6.2
|
||||||
github.com/cenkalti/dominantcolor v1.0.2
|
github.com/cenkalti/dominantcolor v1.0.2
|
||||||
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
|
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
|
||||||
|
github.com/dweymouth/fyne-advanced-list v0.0.0-20240622222843-deee68d6c703
|
||||||
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64
|
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64
|
||||||
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645
|
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645
|
||||||
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee
|
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee
|
||||||
@@ -28,7 +29,6 @@ require (
|
|||||||
github.com/danieljoos/wincred v1.1.0 // indirect
|
github.com/danieljoos/wincred v1.1.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/disintegration/imaging v1.6.2 // indirect
|
github.com/disintegration/imaging v1.6.2 // indirect
|
||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20240614152514-d7bef361f680 // indirect
|
|
||||||
github.com/fredbi/uri v1.1.0 // indirect
|
github.com/fredbi/uri v1.1.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
|
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
|
||||||
|
|||||||
@@ -84,10 +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/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 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20240614151622-9f11c64def19 h1:AfKaUmPlcXyQRlCH/3tX2A+oIOKLC14Ns3AfIjn4NC4=
|
github.com/dweymouth/fyne-advanced-list v0.0.0-20240622222843-deee68d6c703 h1:SHIBiCpGHdhJfVApZ0H/+hxu0/pg8YjnSP082rNoI0A=
|
||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20240614151622-9f11c64def19/go.mod h1:5bICtCEhLzJ4MlRORlUa3L0CadB6xQTNW9DhTXl/UN0=
|
github.com/dweymouth/fyne-advanced-list v0.0.0-20240622222843-deee68d6c703/go.mod h1:sbOhla4VcfFb4OjXiUFTLXMPTnhRUlVrDMhB8HtWR4o=
|
||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20240614152514-d7bef361f680 h1:PDffxh0kv4czCCrFV2uSWDRKnIgpaDZhqohB4mYX9Vg=
|
|
||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20240614152514-d7bef361f680/go.mod h1:5bICtCEhLzJ4MlRORlUa3L0CadB6xQTNW9DhTXl/UN0=
|
|
||||||
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 h1:RUIrnGY034rDMlcOui/daurwX5b+52KdUKhH9aXaDSg=
|
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-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=
|
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240604143614-256525c6a602 h1:k3jFLjmAuPJ5ZFNF57szZp8XrLIb6mIdEEGPkm6EZ7Q=
|
||||||
|
|||||||
+23
-83
@@ -1,9 +1,6 @@
|
|||||||
package sharedutil
|
package sharedutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"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.
|
// Reorder items and return a new track slice.
|
||||||
// idxToMove must contain only valid indexes into tracks, and no repeats
|
// idxToMove must contain only valid indexes into tracks, and no repeats
|
||||||
func ReorderItems[T any](items []T, idxToMove []int, op TrackReorderOp) []T {
|
func ReorderItems[T any](items []T, idxToMove []int, insertIdx int) []T {
|
||||||
newItems := make([]T, len(items))
|
idxToMoveSet := ToSet(idxToMove)
|
||||||
switch op {
|
|
||||||
case MoveToTop:
|
newItems := make([]T, 0, len(items))
|
||||||
topIdx := 0
|
|
||||||
botIdx := len(idxToMove)
|
// collect items that will end up before the insertion set
|
||||||
idxToMoveSet := ToSet(idxToMove)
|
i := 0
|
||||||
for i, t := range items {
|
for ; i < len(items); i++ {
|
||||||
if _, ok := idxToMoveSet[i]; ok {
|
if insertIdx == i {
|
||||||
newItems[topIdx] = t
|
break
|
||||||
topIdx++
|
|
||||||
} else {
|
|
||||||
newItems[botIdx] = t
|
|
||||||
botIdx++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case MoveToBottom:
|
if _, ok := idxToMoveSet[i]; !ok {
|
||||||
topIdx := 0
|
newItems = append(newItems, items[i])
|
||||||
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]
|
|
||||||
}
|
|
||||||
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]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, idx := range idxToMove {
|
||||||
|
newItems = append(newItems, items[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; i < len(items); i++ {
|
||||||
|
if _, ok := idxToMoveSet[i]; !ok {
|
||||||
|
newItems = append(newItems, items[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return newItems
|
return newItems
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstIdxCanMoveUp(idxs []int) int {
|
|
||||||
prevIdx := -1
|
|
||||||
slices.Sort(idxs)
|
|
||||||
for _, idx := range idxs {
|
|
||||||
if idx > prevIdx+1 {
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
prevIdx = 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
|
|
||||||
}
|
|
||||||
prevIdx = idx
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Test_ReorderItems(t *testing.T) {
|
func Test_ReorderItems(t *testing.T) {
|
||||||
|
|
||||||
tracks := []*mediaprovider.Track{
|
tracks := []*mediaprovider.Track{
|
||||||
{ID: "a"}, // 0
|
{ID: "a"}, // 0
|
||||||
{ID: "b"}, // 1
|
{ID: "b"}, // 1
|
||||||
@@ -27,7 +28,7 @@ func Test_ReorderItems(t *testing.T) {
|
|||||||
{ID: "b"},
|
{ID: "b"},
|
||||||
{ID: "e"},
|
{ID: "e"},
|
||||||
}
|
}
|
||||||
newTracks := ReorderItems(tracks, idxToMove, MoveToTop)
|
newTracks := ReorderItems(tracks, idxToMove, 0)
|
||||||
if !tracklistsEqual(t, newTracks, want) {
|
if !tracklistsEqual(t, newTracks, want) {
|
||||||
t.Error("ReorderTracks: MoveToTop order incorrect")
|
t.Error("ReorderTracks: MoveToTop order incorrect")
|
||||||
}
|
}
|
||||||
@@ -42,40 +43,10 @@ func Test_ReorderItems(t *testing.T) {
|
|||||||
{ID: "c"},
|
{ID: "c"},
|
||||||
{ID: "f"},
|
{ID: "f"},
|
||||||
}
|
}
|
||||||
newTracks = ReorderItems(tracks, idxToMove, MoveToBottom)
|
newTracks = ReorderItems(tracks, idxToMove, len(tracks))
|
||||||
if !tracklistsEqual(t, newTracks, want) {
|
if !tracklistsEqual(t, newTracks, want) {
|
||||||
t.Error("ReorderTracks: MoveToBottom order incorrect")
|
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 {
|
func tracklistsEqual(t *testing.T, a, b []*mediaprovider.Track) bool {
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ func NewNowPlayingPage(
|
|||||||
|
|
||||||
a.queueList = widgets.NewPlayQueueList(a.im, false)
|
a.queueList = widgets.NewPlayQueueList(a.im, false)
|
||||||
a.relatedList = widgets.NewPlayQueueList(a.im, true)
|
a.relatedList = widgets.NewPlayQueueList(a.im, true)
|
||||||
|
a.queueList.Reorderable = true
|
||||||
a.queueList.OnReorderItems = a.doSetNewTrackOrder
|
a.queueList.OnReorderItems = a.doSetNewTrackOrder
|
||||||
a.queueList.OnDownload = contr.ShowDownloadDialog
|
a.queueList.OnDownload = contr.ShowDownloadDialog
|
||||||
a.queueList.OnShare = func(tracks []*mediaprovider.Track) {
|
a.queueList.OnShare = func(tracks []*mediaprovider.Track) {
|
||||||
@@ -502,7 +503,7 @@ func (a *NowPlayingPage) Refresh() {
|
|||||||
a.BaseWidget.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)
|
trackIDSet := sharedutil.ToSet(trackIDs)
|
||||||
idxs := make([]int, 0, len(trackIDs))
|
idxs := make([]int, 0, len(trackIDs))
|
||||||
for i, tr := range a.queue {
|
for i, tr := range a.queue {
|
||||||
@@ -510,7 +511,7 @@ func (a *NowPlayingPage) doSetNewTrackOrder(trackIDs []string, op sharedutil.Tra
|
|||||||
idxs = append(idxs, i)
|
idxs = append(idxs, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newTracks := sharedutil.ReorderItems(a.queue, idxs, op)
|
newTracks := sharedutil.ReorderItems(a.queue, idxs, insertPos)
|
||||||
a.pm.UpdatePlayQueue(newTracks)
|
a.pm.UpdatePlayQueue(newTracks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -86,17 +86,16 @@ func newPlaylistPage(
|
|||||||
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
|
||||||
conf.TracklistColumns = cols
|
conf.TracklistColumns = cols
|
||||||
}
|
}
|
||||||
|
a.tracklist.OnReorderTracks = a.doSetNewTrackOrder
|
||||||
_, canRate := a.sm.Server.(mediaprovider.SupportsRating)
|
_, canRate := a.sm.Server.(mediaprovider.SupportsRating)
|
||||||
_, canShare := a.sm.Server.(mediaprovider.SupportsSharing)
|
_, canShare := a.sm.Server.(mediaprovider.SupportsSharing)
|
||||||
remove := fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)
|
remove := fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)
|
||||||
remove.Icon = theme.ContentClearIcon()
|
remove.Icon = theme.ContentClearIcon()
|
||||||
a.tracklist.Options = widgets.TracklistOptions{
|
a.tracklist.Options = widgets.TracklistOptions{
|
||||||
DisableRating: !canRate,
|
Reorderable: true,
|
||||||
DisableSharing: !canShare,
|
DisableRating: !canRate,
|
||||||
AuxiliaryMenuItems: []*fyne.MenuItem{
|
DisableSharing: !canShare,
|
||||||
util.NewReorderTracksSubmenu(a.doSetNewTrackOrder),
|
AuxiliaryMenuItems: []*fyne.MenuItem{remove},
|
||||||
remove,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
// connect tracklist actions
|
// connect tracklist actions
|
||||||
a.contr.ConnectTracklistActions(a.tracklist)
|
a.contr.ConnectTracklistActions(a.tracklist)
|
||||||
@@ -118,6 +117,7 @@ func (a *PlaylistPage) Save() SavedPage {
|
|||||||
p.trackSort = a.tracklist.Sorting()
|
p.trackSort = a.tracklist.Sorting()
|
||||||
p.widgetPool.Release(util.WidgetTypePlaylistPageHeader, a.header)
|
p.widgetPool.Release(util.WidgetTypePlaylistPageHeader, a.header)
|
||||||
a.tracklist.Clear()
|
a.tracklist.Clear()
|
||||||
|
a.tracklist.OnReorderTracks = nil
|
||||||
p.widgetPool.Release(util.WidgetTypeTracklist, a.tracklist)
|
p.widgetPool.Release(util.WidgetTypeTracklist, a.tracklist)
|
||||||
return &p
|
return &p
|
||||||
}
|
}
|
||||||
@@ -178,19 +178,19 @@ 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
|
// 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
|
// 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
|
// 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))
|
idxs := make([]int, 0, len(idSet))
|
||||||
for i, tr := range a.tracks {
|
for i, tr := range a.tracks {
|
||||||
if _, ok := idSet[tr.ID]; ok {
|
if _, ok := idSet[tr.ID]; ok {
|
||||||
idxs = append(idxs, i)
|
idxs = append(idxs, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newTracks := sharedutil.ReorderItems(a.tracks, idxs, op)
|
newTracks := sharedutil.ReorderItems(a.tracks, idxs, newPos)
|
||||||
ids := sharedutil.TracksToIDs(newTracks)
|
ids = sharedutil.TracksToIDs(newTracks)
|
||||||
if err := a.sm.Server.ReplacePlaylistTracks(a.playlistID, ids); err != nil {
|
if err := a.sm.Server.ReplacePlaylistTracks(a.playlistID, ids); err != nil {
|
||||||
log.Printf("error updating playlist: %s", err.Error())
|
log.Printf("error updating playlist: %s", err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
"github.com/dweymouth/supersonic/res"
|
"github.com/dweymouth/supersonic/res"
|
||||||
"github.com/dweymouth/supersonic/sharedutil"
|
|
||||||
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
)
|
)
|
||||||
@@ -227,18 +226,6 @@ func NewRatingSubmenu(onSetRating func(int)) *fyne.MenuItem {
|
|||||||
return ratingMenu
|
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 {
|
func AddHeaderBackground(obj fyne.CanvasObject) *fyne.Container {
|
||||||
return AddHeaderBackgroundWithColorName(obj, myTheme.ColorNamePageHeader)
|
return AddHeaderBackgroundWithColorName(obj, myTheme.ColorNamePageHeader)
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-15
@@ -31,6 +31,7 @@ type PlayQueueListModel struct {
|
|||||||
type PlayQueueList struct {
|
type PlayQueueList struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
|
Reorderable bool
|
||||||
DisableRating bool
|
DisableRating bool
|
||||||
DisableSharing bool
|
DisableSharing bool
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ type PlayQueueList struct {
|
|||||||
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
|
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
|
||||||
OnShare func(tracks []*mediaprovider.Track)
|
OnShare func(tracks []*mediaprovider.Track)
|
||||||
OnShowArtistPage func(artistID string)
|
OnShowArtistPage func(artistID string)
|
||||||
OnReorderItems func(itemIDs []string, op sharedutil.TrackReorderOp)
|
OnReorderItems func(itemIDs []string, reorderTo int)
|
||||||
|
|
||||||
useNonQueueMenu bool
|
useNonQueueMenu bool
|
||||||
menu *widget.PopUpMenu // ctx menu for when only tracks are selected
|
menu *widget.PopUpMenu // ctx menu for when only tracks are selected
|
||||||
@@ -102,6 +103,17 @@ func NewPlayQueueList(im *backend.ImageManager, useNonQueueMenu bool) *PlayQueue
|
|||||||
tr.Update(model, itemID+1)
|
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
|
return p
|
||||||
}
|
}
|
||||||
@@ -149,7 +161,12 @@ func (p *PlayQueueList) UnselectAll() {
|
|||||||
p.tracksMutex.RLock()
|
p.tracksMutex.RLock()
|
||||||
util.UnselectAllItems(p.items)
|
util.UnselectAllItems(p.items)
|
||||||
p.tracksMutex.RUnlock()
|
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 {
|
func (p *PlayQueueList) lenTracks() int {
|
||||||
@@ -292,15 +309,9 @@ func (p *PlayQueueList) ensureTracksMenu() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
remove.Icon = theme.ContentRemoveIcon()
|
remove.Icon = theme.ContentRemoveIcon()
|
||||||
reorder := util.NewReorderTracksSubmenu(func(tro sharedutil.TrackReorderOp) {
|
|
||||||
if p.OnReorderItems != nil {
|
|
||||||
p.OnReorderItems(p.selectedItemIDs(), tro)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
menuItems = append(menuItems,
|
menuItems = append(menuItems,
|
||||||
fyne.NewMenuItemSeparator(),
|
fyne.NewMenuItemSeparator(),
|
||||||
remove,
|
remove)
|
||||||
reorder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.menu = widget.NewPopUpMenu(
|
p.menu = widget.NewPopUpMenu(
|
||||||
@@ -319,13 +330,8 @@ func (p *PlayQueueList) ensureRadiosMenu() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
remove.Icon = theme.ContentRemoveIcon()
|
remove.Icon = theme.ContentRemoveIcon()
|
||||||
reorder := util.NewReorderTracksSubmenu(func(tro sharedutil.TrackReorderOp) {
|
|
||||||
if p.OnReorderItems != nil {
|
|
||||||
p.OnReorderItems(p.selectedItemIDs(), tro)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
p.radiosMenu = widget.NewPopUpMenu(
|
p.radiosMenu = widget.NewPopUpMenu(
|
||||||
fyne.NewMenu("", remove, reorder),
|
fyne.NewMenu("", remove),
|
||||||
fyne.CurrentApp().Driver().CanvasForObject(p),
|
fyne.CurrentApp().Driver().CanvasForObject(p),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ type TracklistOptions struct {
|
|||||||
// or to use the number from the track's metadata
|
// or to use the number from the track's metadata
|
||||||
AutoNumber bool
|
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,
|
// ShowDiscNumber sets whether to display the disc number as part of the '#' column,
|
||||||
// (with format %d.%02d). Only applies if AutoNumber==false.
|
// (with format %d.%02d). Only applies if AutoNumber==false.
|
||||||
ShowDiscNumber bool
|
ShowDiscNumber bool
|
||||||
@@ -74,6 +77,7 @@ type Tracklist struct {
|
|||||||
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
|
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
|
||||||
OnShare func(trackID string)
|
OnShare func(trackID string)
|
||||||
OnPlaySongRadio func(track *mediaprovider.Track)
|
OnPlaySongRadio func(track *mediaprovider.Track)
|
||||||
|
OnReorderTracks func(trackIDs []string, insertPos int)
|
||||||
|
|
||||||
OnShowArtistPage func(artistID string)
|
OnShowArtistPage func(artistID string)
|
||||||
OnShowAlbumPage func(albumID string)
|
OnShowAlbumPage func(albumID string)
|
||||||
@@ -182,6 +186,17 @@ func NewTracklist(tracks []*mediaprovider.Track, im *backend.ImageManager, useCo
|
|||||||
t.OnTrackShown(itemID)
|
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)
|
t.container = container.NewBorder(t.hdr, nil, nil, nil, t.list)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
@@ -371,6 +386,7 @@ func (t *Tracklist) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracklist) Refresh() {
|
func (t *Tracklist) Refresh() {
|
||||||
|
t.list.EnableDragging = t.Options.Reorderable
|
||||||
t.hdr.DisableSorting = t.Options.DisableSorting
|
t.hdr.DisableSorting = t.Options.DisableSorting
|
||||||
t.BaseWidget.Refresh()
|
t.BaseWidget.Refresh()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user