move shuffle and repeat controls into main player controls (#862)

Shuffle moves to the far left of the transport buttons and repeat to the far right, flanking prev/play/next. Both use a new IconButtonSizeSmallest (10% smaller than IconButtonSizeSmaller) with 2dp spacing separating them from the adjacent transport buttons. Wiring updated in bottompanel.go accordingly.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Drew Weymouth
2026-02-20 18:53:02 -08:00
committed by GitHub
co-authored by Claude Sonnet 4.6
parent e31f546e09
commit b40c154f7e
4 changed files with 72 additions and 62 deletions
+12 -12
View File
@@ -88,7 +88,7 @@ func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr
contr.ShowShareDialog(tr.ID) contr.ShowShareDialog(tr.ID)
} }
} }
bp.Controls = widgets.NewPlayerControls(cfg.Playback.UseWaveformSeekbar) bp.Controls = widgets.NewPlayerControls(cfg.Playback.UseWaveformSeekbar, pm.GetLoopMode(), pm.IsShuffle())
bp.Controls.OnPlayPause(func() { bp.Controls.OnPlayPause(func() {
pm.PlayPause() pm.PlayPause()
}) })
@@ -101,14 +101,20 @@ func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr
bp.Controls.OnSeek(func(f float64) { bp.Controls.OnSeek(func(f float64) {
pm.SeekFraction(f) pm.SeekFraction(f)
}) })
bp.Controls.OnChangeLoopMode(func() {
bp.AuxControls = widgets.NewAuxControls(pm.Volume(), pm.GetLoopMode(), pm.IsAutoplay(), pm.IsShuffle()) pm.SetNextLoopMode()
})
bp.Controls.OnChangeShuffle = func(shuffle bool) {
pm.SetShuffle(shuffle)
}
pm.OnLoopModeChange(func(lm backend.LoopMode) { pm.OnLoopModeChange(func(lm backend.LoopMode) {
fyne.Do(func() { bp.AuxControls.SetLoopMode(lm) }) fyne.Do(func() { bp.Controls.SetLoopMode(lm) })
}) })
pm.OnShuffleChange(func(lm bool) { pm.OnShuffleChange(func(sh bool) {
fyne.Do(func() { bp.AuxControls.SetShuffle(lm) }) fyne.Do(func() { bp.Controls.SetShuffle(sh) })
}) })
bp.AuxControls = widgets.NewAuxControls(pm.Volume(), pm.IsAutoplay())
pm.OnVolumeChange(func(vol int) { pm.OnVolumeChange(func(vol int) {
fyne.Do(func() { bp.AuxControls.VolumeControl.SetVolume(vol) }) fyne.Do(func() { bp.AuxControls.VolumeControl.SetVolume(vol) })
}) })
@@ -119,15 +125,9 @@ func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr
bp.AuxControls.VolumeControl.OnSetVolume = func(v int) { bp.AuxControls.VolumeControl.OnSetVolume = func(v int) {
pm.SetVolume(v) pm.SetVolume(v)
} }
bp.AuxControls.OnChangeLoopMode(func() {
pm.SetNextLoopMode()
})
bp.AuxControls.OnChangeAutoplay = func(autoplay bool) { bp.AuxControls.OnChangeAutoplay = func(autoplay bool) {
pm.SetAutoplay(autoplay) pm.SetAutoplay(autoplay)
} }
bp.AuxControls.OnChangeShuffle = func(shuffle bool) {
pm.SetShuffle(shuffle)
}
bp.AuxControls.OnShowPlayQueue(contr.ShowPopUpPlayQueue) bp.AuxControls.OnShowPlayQueue(contr.ShowPopUpPlayQueue)
bp.AuxControls.OnShowCastMenu(contr.ShowCastMenu) bp.AuxControls.OnShowCastMenu(contr.ShowCastMenu)
+2 -48
View File
@@ -11,7 +11,6 @@ import (
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/backend"
myTheme "github.com/dweymouth/supersonic/ui/theme" myTheme "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/util"
) )
@@ -22,42 +21,23 @@ type AuxControls struct {
widget.BaseWidget widget.BaseWidget
OnChangeAutoplay func(autoplay bool) OnChangeAutoplay func(autoplay bool)
OnChangeShuffle func(shuffle bool)
VolumeControl *VolumeControl VolumeControl *VolumeControl
shuffle *IconButton
autoplay *IconButton autoplay *IconButton
loop *IconButton
cast *IconButton cast *IconButton
showQueue *IconButton showQueue *IconButton
container *fyne.Container container *fyne.Container
} }
func NewAuxControls(initialVolume int, initialLoopMode backend.LoopMode, initialAutoplay bool, initialShuffle bool) *AuxControls { func NewAuxControls(initialVolume int, initialAutoplay bool) *AuxControls {
a := &AuxControls{ a := &AuxControls{
VolumeControl: NewVolumeControl(initialVolume), VolumeControl: NewVolumeControl(initialVolume),
shuffle: NewIconButton(myTheme.ShuffleIcon, nil),
autoplay: NewIconButton(myTheme.AutoplayIcon, nil), autoplay: NewIconButton(myTheme.AutoplayIcon, nil),
loop: NewIconButton(myTheme.RepeatIcon, nil),
cast: NewIconButton(myTheme.CastIcon, nil), cast: NewIconButton(myTheme.CastIcon, nil),
showQueue: NewIconButton(myTheme.PlayQueueIcon, nil), showQueue: NewIconButton(myTheme.PlayQueueIcon, nil),
} }
a.shuffle.Highlighted = initialShuffle
a.shuffle.IconSize = IconButtonSizeSmaller
a.shuffle.SetToolTip(lang.L("Shuffle"))
a.shuffle.OnTapped = func() {
a.SetShuffle(!a.shuffle.Highlighted)
if a.OnChangeShuffle != nil {
a.OnChangeShuffle(a.shuffle.Highlighted)
}
}
a.loop.IconSize = IconButtonSizeSmaller
a.loop.SetToolTip(lang.L("Repeat"))
a.SetLoopMode(initialLoopMode)
a.cast.IconSize = IconButtonSizeSmaller a.cast.IconSize = IconButtonSizeSmaller
a.cast.SetToolTip(lang.L("Cast to device")) a.cast.SetToolTip(lang.L("Cast to device"))
@@ -81,7 +61,7 @@ func NewAuxControls(initialVolume int, initialLoopMode backend.LoopMode, initial
a.VolumeControl, a.VolumeControl,
container.New( container.New(
layout.NewCustomPaddedHBoxLayout(theme.Padding()*1.5), layout.NewCustomPaddedHBoxLayout(theme.Padding()*1.5),
layout.NewSpacer(), a.autoplay, a.shuffle, a.loop, a.cast, a.showQueue, util.NewHSpace(5)), layout.NewSpacer(), a.autoplay, a.cast, a.showQueue, util.NewHSpace(5)),
layout.NewSpacer(), layout.NewSpacer(),
), ),
) )
@@ -93,32 +73,6 @@ func (a *AuxControls) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(a.container) return widget.NewSimpleRenderer(a.container)
} }
func (a *AuxControls) OnChangeLoopMode(f func()) {
a.loop.OnTapped = f
}
func (a *AuxControls) SetLoopMode(mode backend.LoopMode) {
switch mode {
case backend.LoopAll:
a.loop.Highlighted = true
a.loop.SetIcon(myTheme.RepeatIcon)
case backend.LoopOne:
a.loop.Highlighted = true
a.loop.SetIcon(myTheme.RepeatOneIcon)
case backend.LoopNone:
a.loop.Highlighted = false
a.loop.SetIcon(myTheme.RepeatIcon)
}
}
func (a *AuxControls) SetShuffle(isShuffle bool) {
if isShuffle == a.shuffle.Highlighted {
return
}
a.shuffle.Highlighted = isShuffle
a.shuffle.Refresh()
}
func (a *AuxControls) DisableCastButton() { func (a *AuxControls) DisableCastButton() {
a.cast.Disable() a.cast.Disable()
} }
+6
View File
@@ -16,8 +16,10 @@ type IconButtonSize int
const ( const (
IconButtonSizeNormal IconButtonSize = iota IconButtonSizeNormal IconButtonSize = iota
IconButtonSizeSlightlyBigger
IconButtonSizeBigger IconButtonSizeBigger
IconButtonSizeSmaller IconButtonSizeSmaller
IconButtonSizeSmallest
) )
type IconButton struct { type IconButton struct {
@@ -141,8 +143,12 @@ func (i *IconButton) iconSize() fyne.Size {
switch i.IconSize { switch i.IconSize {
case IconButtonSizeBigger: case IconButtonSizeBigger:
return fyne.NewSquareSize(theme.IconInlineSize() * 2) return fyne.NewSquareSize(theme.IconInlineSize() * 2)
case IconButtonSizeSlightlyBigger:
return fyne.NewSquareSize(theme.IconInlineSize() * 1.37)
case IconButtonSizeSmaller: case IconButtonSizeSmaller:
return fyne.NewSquareSize(theme.IconInlineSize()) return fyne.NewSquareSize(theme.IconInlineSize())
case IconButtonSizeSmallest:
return fyne.NewSquareSize(theme.IconInlineSize() * 0.9)
default: default:
return fyne.NewSquareSize(theme.IconInlineSize() * 1.3333) return fyne.NewSquareSize(theme.IconInlineSize() * 1.3333)
} }
+52 -2
View File
@@ -2,6 +2,7 @@ package widgets
import ( import (
"github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend"
myTheme "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/util"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
@@ -84,13 +85,17 @@ type PlayerControls struct {
UseWaveformSeekbar bool UseWaveformSeekbar bool
OnChangeShuffle func(shuffle bool)
slider *TrackPosSlider slider *TrackPosSlider
waveform *WaveformSeekbar waveform *WaveformSeekbar
curTimeLabel *labelMinSize curTimeLabel *labelMinSize
totalTimeLabel *labelMinSize totalTimeLabel *labelMinSize
shuffle *IconButton
prev *IconButton prev *IconButton
playpause *IconButton playpause *IconButton
next *IconButton next *IconButton
loop *IconButton
container *fyne.Container container *fyne.Container
totalTime float64 totalTime float64
@@ -114,7 +119,7 @@ func NewLabelMinSize(text string, minWidth float32) *labelMinSize {
} }
// NewPlayerControls sets up the seek bar, and transport buttons. // NewPlayerControls sets up the seek bar, and transport buttons.
func NewPlayerControls(useWaveformSeekbar bool) *PlayerControls { func NewPlayerControls(useWaveformSeekbar bool, initialLoopMode backend.LoopMode, initialShuffle bool) *PlayerControls {
pc := &PlayerControls{UseWaveformSeekbar: useWaveformSeekbar} pc := &PlayerControls{UseWaveformSeekbar: useWaveformSeekbar}
pc.ExtendBaseWidget(pc) pc.ExtendBaseWidget(pc)
@@ -139,15 +144,34 @@ func NewPlayerControls(useWaveformSeekbar bool) *PlayerControls {
} }
} }
pc.shuffle = NewIconButton(myTheme.ShuffleIcon, nil)
pc.shuffle.IconSize = IconButtonSizeSmallest
pc.shuffle.SetToolTip(lang.L("Shuffle"))
pc.shuffle.OnTapped = func() {
pc.SetShuffle(!pc.shuffle.Highlighted)
if pc.OnChangeShuffle != nil {
pc.OnChangeShuffle(pc.shuffle.Highlighted)
}
}
pc.loop = NewIconButton(myTheme.RepeatIcon, nil)
pc.loop.IconSize = IconButtonSizeSmallest
pc.loop.SetToolTip(lang.L("Repeat"))
pc.shuffle.Highlighted = initialShuffle
pc.SetLoopMode(initialLoopMode)
pc.prev = NewIconButton(theme.MediaSkipPreviousIcon(), func() {}) pc.prev = NewIconButton(theme.MediaSkipPreviousIcon(), func() {})
pc.prev.SetToolTip(lang.L("Previous")) pc.prev.SetToolTip(lang.L("Previous"))
pc.prev.IconSize = IconButtonSizeSlightlyBigger
pc.next = NewIconButton(theme.MediaSkipNextIcon(), func() {}) pc.next = NewIconButton(theme.MediaSkipNextIcon(), func() {})
pc.next.SetToolTip(lang.L("Next")) pc.next.SetToolTip(lang.L("Next"))
pc.next.IconSize = IconButtonSizeSlightlyBigger
pc.playpause = NewIconButton(theme.MediaPlayIcon(), func() {}) pc.playpause = NewIconButton(theme.MediaPlayIcon(), func() {})
pc.playpause.SetToolTip(lang.L("Play")) pc.playpause.SetToolTip(lang.L("Play"))
pc.playpause.IconSize = IconButtonSizeBigger pc.playpause.IconSize = IconButtonSizeBigger
buttons := container.NewHBox(layout.NewSpacer(), pc.prev, pc.playpause, pc.next, layout.NewSpacer()) buttons := container.NewHBox(layout.NewSpacer(), pc.shuffle, util.NewHSpace(2), pc.prev, pc.playpause, pc.next, util.NewHSpace(2), pc.loop, layout.NewSpacer())
seekCtrl := container.NewStack( seekCtrl := container.NewStack(
pc.slider, pc.slider,
@@ -174,6 +198,32 @@ func (pc *PlayerControls) OnSeekPrevious(f func()) {
pc.prev.OnTapped = f pc.prev.OnTapped = f
} }
func (pc *PlayerControls) OnChangeLoopMode(f func()) {
pc.loop.OnTapped = f
}
func (pc *PlayerControls) SetLoopMode(mode backend.LoopMode) {
switch mode {
case backend.LoopAll:
pc.loop.Highlighted = true
pc.loop.SetIcon(myTheme.RepeatIcon)
case backend.LoopOne:
pc.loop.Highlighted = true
pc.loop.SetIcon(myTheme.RepeatOneIcon)
case backend.LoopNone:
pc.loop.Highlighted = false
pc.loop.SetIcon(myTheme.RepeatIcon)
}
}
func (pc *PlayerControls) SetShuffle(isShuffle bool) {
if isShuffle == pc.shuffle.Highlighted {
return
}
pc.shuffle.Highlighted = isShuffle
pc.shuffle.Refresh()
}
func (pc *PlayerControls) OnSeekNext(f func()) { func (pc *PlayerControls) OnSeekNext(f func()) {
pc.next.OnTapped = f pc.next.OnTapped = f
} }