diff --git a/ui/widgets/auxcontrols.go b/ui/widgets/auxcontrols.go index 1168a8e..41ebc0a 100644 --- a/ui/widgets/auxcontrols.go +++ b/ui/widgets/auxcontrols.go @@ -3,7 +3,6 @@ package widgets import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -56,37 +55,10 @@ func (v *volumeSlider) MinSize() fyne.Size { return fyne.NewSize(v.Width, h) } -type tappableIcon struct { - widget.Icon - - OnTapped func() -} - -func newTappableIcon(res fyne.Resource) *tappableIcon { - icon := &tappableIcon{} - icon.ExtendBaseWidget(icon) - icon.SetResource(res) - - return icon -} - -func (t *tappableIcon) Tapped(_ *fyne.PointEvent) { - if t.OnTapped != nil { - t.OnTapped() - } -} - -func (t *tappableIcon) TappedSecondary(_ *fyne.PointEvent) { -} - -func (t *tappableIcon) Cursor() desktop.Cursor { - return desktop.PointerCursor -} - type VolumeControl struct { widget.BaseWidget - icon *tappableIcon + icon *TappableIcon slider *volumeSlider OnVolumeChanged func(int) @@ -100,7 +72,7 @@ type VolumeControl struct { func NewVolumeControl(initialVol int) *VolumeControl { v := &VolumeControl{} v.ExtendBaseWidget(v) - v.icon = newTappableIcon(theme.VolumeUpIcon()) + v.icon = NewTappbaleIcon(theme.VolumeUpIcon()) v.icon.OnTapped = v.toggleMute v.slider = NewVolumeSlider(100) v.lastVol = initialVol diff --git a/ui/widgets/tappableicon.go b/ui/widgets/tappableicon.go new file mode 100644 index 0000000..4ca70f3 --- /dev/null +++ b/ui/widgets/tappableicon.go @@ -0,0 +1,34 @@ +package widgets + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/widget" +) + +type TappableIcon struct { + widget.Icon + + OnTapped func() +} + +func NewTappbaleIcon(res fyne.Resource) *TappableIcon { + icon := &TappableIcon{} + icon.ExtendBaseWidget(icon) + icon.SetResource(res) + + return icon +} + +func (t *TappableIcon) Tapped(_ *fyne.PointEvent) { + if t.OnTapped != nil { + t.OnTapped() + } +} + +func (t *TappableIcon) TappedSecondary(_ *fyne.PointEvent) { +} + +func (t *TappableIcon) Cursor() desktop.Cursor { + return desktop.PointerCursor +} diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index d85ed56..b70b075 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -4,6 +4,7 @@ import ( "log" "runtime" "strconv" + "supersonic/res" "supersonic/ui/layouts" "supersonic/ui/os" "supersonic/ui/util" @@ -19,12 +20,13 @@ import ( ) const ( - ColumnArtist = "Artist" - ColumnAlbum = "Album" - ColumnTime = "Time" - ColumnYear = "Year" - ColumnPlays = "Plays" - ColumnBitrate = "Bitrate" + ColumnArtist = "Artist" + ColumnAlbum = "Album" + ColumnTime = "Time" + ColumnYear = "Year" + ColumnFavorite = "Favorite" + ColumnPlays = "Plays" + ColumnBitrate = "Bitrate" ) type Tracklist struct { @@ -41,6 +43,7 @@ type Tracklist struct { OnPlaySelection func(tracks []*subsonic.Child) OnAddToQueue func(trackIDs []*subsonic.Child) OnAddToPlaylist func(trackIDs []string) + OnSetFavorite func(trackIDs []string, fav bool) visibleColumns []bool @@ -54,11 +57,12 @@ type Tracklist struct { } func NewTracklist(tracks []*subsonic.Child) *Tracklist { - t := &Tracklist{Tracks: tracks, nowPlayingIdx: -1, visibleColumns: make([]bool, 8)} + t := &Tracklist{Tracks: tracks, nowPlayingIdx: -1, visibleColumns: make([]bool, 9)} t.ExtendBaseWidget(t) t.selectionMgr = util.NewListSelectionManager(func() int { return len(t.Tracks) }) - t.colLayout = layouts.NewColumnsLayout([]float32{35, -1, -1, -1, 60, 60, 65, 75}) + // #, Title, Artist, Album, Time, Year, Favorite, Plays, Bitrate + t.colLayout = layouts.NewColumnsLayout([]float32{35, -1, -1, -1, 60, 60, 47, 65, 75}) t.buildHeader() t.hdr.OnColumnVisibilityChanged = t.setColumnVisible playingIcon := container.NewCenter(container.NewHBox(NewHSpace(2), widget.NewIcon(theme.MediaPlayIcon()))) @@ -93,6 +97,7 @@ func (t *Tracklist) buildHeader() { {Text: "Album", AlignTrailing: false, CanToggleVisible: true}, {Text: "Time", AlignTrailing: true, CanToggleVisible: true}, {Text: "Year", AlignTrailing: true, CanToggleVisible: true}, + {Text: "Fav.", AlignTrailing: false, CanToggleVisible: true}, {Text: "Plays", AlignTrailing: true, CanToggleVisible: true}, {Text: "Bitrate", AlignTrailing: true, CanToggleVisible: true}}, t.colLayout) @@ -239,6 +244,12 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { widget.ShowPopUpMenuAtPosition(t.ctxMenu, fyne.CurrentApp().Driver().CanvasForObject(t), e.AbsolutePosition) } +func (t *Tracklist) onSetFavorite(trackID string, fav bool) { + if t.OnSetFavorite != nil { + t.OnSetFavorite([]string{trackID}, fav) + } +} + func (t *Tracklist) selectedTracks() []*subsonic.Child { sel := t.selectionMgr.GetSelection() tracks := make([]*subsonic.Child, 0, len(sel)) @@ -272,10 +283,12 @@ func ColNumber(colName string) int { return 4 case ColumnYear: return 5 - case ColumnPlays: + case ColumnFavorite: return 6 - case ColumnBitrate: + case ColumnPlays: return 7 + case ColumnBitrate: + return 8 default: log.Printf("error: Tracklist: invalid column name %s", colName) return -100 @@ -294,8 +307,10 @@ func colName(i int) string { case 5: return ColumnYear case 6: - return ColumnPlays + return ColumnFavorite case 7: + return ColumnPlays + case 8: return ColumnBitrate default: return "" @@ -306,21 +321,23 @@ type TrackRow struct { widget.BaseWidget // internal state - tracklist *Tracklist - trackIdx int - trackID string - isPlaying bool - playCount int64 - tappedAt int64 // unixMillis + tracklist *Tracklist + trackIdx int + trackID string + isPlaying bool + isFavorite bool + playCount int64 + tappedAt int64 // unixMillis - num *widget.RichText - name *widget.RichText - artist *widget.RichText - album *widget.RichText - dur *widget.RichText - year *widget.RichText - bitrate *widget.RichText - plays *widget.RichText + num *widget.RichText + name *widget.RichText + artist *widget.RichText + album *widget.RichText + dur *widget.RichText + year *widget.RichText + favorite *fyne.Container + bitrate *widget.RichText + plays *widget.RichText OnTapped func() OnDoubleTapped func() @@ -346,6 +363,9 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow t.dur.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing t.year = widget.NewRichTextWithText("") t.year.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing + favorite := NewTappbaleIcon(res.ResHeartOutlineInvertPng) + favorite.OnTapped = t.toggleFavorited + t.favorite = container.NewCenter(favorite) t.plays = widget.NewRichTextWithText("") t.plays.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing t.bitrate = widget.NewRichTextWithText("") @@ -355,7 +375,7 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow t.selectionRect.Hidden = true t.container = container.NewMax(t.selectionRect, container.New(tracklist.colLayout, - t.num, t.name, t.artist, t.album, t.dur, t.year, t.plays, t.bitrate)) + t.num, t.name, t.artist, t.album, t.dur, t.year, t.favorite, t.plays, t.bitrate)) return t } @@ -374,6 +394,7 @@ func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { t.album.Segments[0].(*widget.TextSegment).Text = tr.Album t.dur.Segments[0].(*widget.TextSegment).Text = util.SecondsToTimeString(float64(tr.Duration)) t.year.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(tr.Year) + t.plays.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(int(tr.PlayCount)) t.bitrate.Segments[0].(*widget.TextSegment).Text = strconv.Itoa(tr.BitRate) @@ -392,16 +413,37 @@ func (t *TrackRow) Update(tr *subsonic.Child, isPlaying bool, rowNum int) { } } + if tr.Starred.IsZero() && t.isFavorite { + t.isFavorite = false + t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng + } else if !t.isFavorite { + t.isFavorite = true + t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng + } + t.artist.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnArtist)] t.album.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnAlbum)] t.dur.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnTime)] t.year.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnYear)] + t.favorite.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnFavorite)] t.plays.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnPlays)] t.bitrate.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnBitrate)] t.Refresh() } +func (t *TrackRow) toggleFavorited() { + if t.isFavorite { + t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartOutlineInvertPng + t.favorite.Refresh() + t.tracklist.onSetFavorite(t.trackID, false) + } else { + t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng + t.favorite.Refresh() + t.tracklist.onSetFavorite(t.trackID, true) + } +} + func (t *TrackRow) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(t.container) }