diff --git a/backend/playbackengine.go b/backend/playbackengine.go index 344a491..cd7a463 100644 --- a/backend/playbackengine.go +++ b/backend/playbackengine.go @@ -810,7 +810,7 @@ func (pm *playbackEngine) invokeNoArgCallbacks(cbs []func()) { func (p *playbackEngine) startPollTimePos() { ctx, cancel := context.WithCancel(p.ctx) p.cancelPollPos = cancel - pollingTick := time.NewTicker(250 * time.Millisecond) + pollingTick := time.NewTicker(125 * time.Millisecond) go func() { for { diff --git a/ui/bottompanel.go b/ui/bottompanel.go index 1359d05..699f284 100644 --- a/ui/bottompanel.go +++ b/ui/bottompanel.go @@ -125,7 +125,7 @@ func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr bp.imageLoader = util.NewThumbnailLoader(im, bp.NowPlaying.SetImage) - bp.container = container.New(layouts.NewLeftMiddleRightLayout(500), + bp.container = container.New(layouts.NewLeftMiddleRightLayout(300, 0.4), bp.NowPlaying, bp.Controls, bp.AuxControls) return bp } diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go index e666ae7..deecf88 100644 --- a/ui/browsing/browsingpane.go +++ b/ui/browsing/browsingpane.go @@ -109,7 +109,7 @@ func NewBrowsingPane(app *backend.App, contr *controller.Controller, onGoHome fu b.navBtnsPageMap = map[controller.PageName]fyne.Resource{} b.container = container.NewBorder(container.New( &layout.CustomPaddedLayout{LeftPadding: -5, RightPadding: -5}, - container.New(layouts.NewLeftMiddleRightLayout(0), + container.New(layouts.NewLeftMiddleRightLayout(0, 0), container.NewHBox(b.home, b.back, b.forward, b.reload), b.navBtnsContainer, container.NewHBox(layout.NewSpacer(), quickSearchBtn, b.settingsBtn))), nil, nil, nil, b.pageContainer) diff --git a/ui/layouts/leftmiddlerightlayout.go b/ui/layouts/leftmiddlerightlayout.go index 13bbe22..f4433ad 100644 --- a/ui/layouts/leftmiddlerightlayout.go +++ b/ui/layouts/leftmiddlerightlayout.go @@ -7,17 +7,20 @@ import ( ) // Lays out up to 3 objects such that the middle object, Objects[1], -// is centered in the available space and takes up a fixed width. -// The left, and right (if non-nil), split the leftover space equally. +// is centered in the available space and takes up a fixed width, +// or optionally a fraction of the overall space. +// The left, and right split the leftover space equally. type LeftMiddleRightLayout struct { - middleWidth float32 - hbox fyne.Layout + middleWidthMin float32 + middleWidthFraction float32 + hbox fyne.Layout } -func NewLeftMiddleRightLayout(middleWidth float32) *LeftMiddleRightLayout { +func NewLeftMiddleRightLayout(middleWidthMin, middleWidthFraction float32) *LeftMiddleRightLayout { return &LeftMiddleRightLayout{ - middleWidth: middleWidth, - hbox: layout.NewHBoxLayout(), + middleWidthMin: middleWidthMin, + middleWidthFraction: middleWidthFraction, + hbox: layout.NewHBoxLayout(), } } @@ -25,20 +28,21 @@ func (b *LeftMiddleRightLayout) MinSize(objects []fyne.CanvasObject) fyne.Size { hboxSize := b.hbox.MinSize(objects) return fyne.Size{ Height: hboxSize.Height, - Width: hboxSize.Width + fyne.Max(0, b.middleWidth-objects[1].MinSize().Width), + Width: hboxSize.Width + fyne.Max(0, b.middleWidthMin-objects[1].MinSize().Width), } } func (b *LeftMiddleRightLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) { pad := theme.Padding() - midW := fyne.Max(b.middleWidth, objects[1].MinSize().Width) + lrMinWidth := fyne.Max(objects[0].MinSize().Width, objects[2].MinSize().Width) + midMinWidth := fyne.Max(b.middleWidthMin, objects[1].MinSize().Width) + midMaxWidth := size.Width - lrMinWidth*2 - pad*4 + midW := fyne.Min(midMaxWidth, fyne.Max(midMinWidth, b.middleWidthFraction*size.Width)) lrW := (size.Width - midW - pad*4) / 2 objects[0].Resize(fyne.NewSize(lrW, size.Height)) objects[0].Move(fyne.NewPos(pad, 0)) objects[1].Resize(fyne.NewSize(midW, size.Height)) objects[1].Move(fyne.NewPos(lrW+pad*2, 0)) - if objects[2] != nil { - objects[2].Resize(fyne.NewSize(lrW, size.Height)) - objects[2].Move(fyne.NewPos(lrW+midW+pad*3, 0)) - } + objects[2].Resize(fyne.NewSize(lrW, size.Height)) + objects[2].Move(fyne.NewPos(lrW+midW+pad*3, 0)) } diff --git a/ui/widgets/playercontrols.go b/ui/widgets/playercontrols.go index d3a286e..216bd26 100644 --- a/ui/widgets/playercontrols.go +++ b/ui/widgets/playercontrols.go @@ -160,6 +160,7 @@ func (pc *PlayerControls) OnSeek(f func(float64)) { f(pos) } } + pc.waveform.OnSeeked = f } func (pc *PlayerControls) OnSeekPrevious(f func()) { @@ -208,10 +209,10 @@ func (pc *PlayerControls) UpdatePlayTime(curTime, totalTime float64) { pc.curTimeLabel.SetText(ct) updated = true } + pc.waveform.SetProgress(v) if updated { // Only update slider once a second when time label changes pc.slider.SetValue(v) - pc.waveform.SetProgress(v) } } } diff --git a/ui/widgets/waveformseekbar.go b/ui/widgets/waveformseekbar.go index 9c2b81d..52bd07f 100644 --- a/ui/widgets/waveformseekbar.go +++ b/ui/widgets/waveformseekbar.go @@ -3,50 +3,89 @@ package widgets import ( "image" "image/color" - "log" + "math" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "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" "github.com/dweymouth/supersonic/backend" + myTheme "github.com/dweymouth/supersonic/ui/theme" ) type WaveformSeekbar struct { widget.BaseWidget - imgColorL color.Color - imgColorR color.Color - imgProgress float64 + OnSeeked func(float64) - img *canvas.Image + imgColorL color.Color + imgColorR color.Color + imgProgressPixel int + + img *canvas.Image + cursor *myTheme.ThemedRectangle } func NewWaveformSeekbar() *WaveformSeekbar { w := &WaveformSeekbar{ - img: canvas.NewImageFromImage(nil), + img: &canvas.Image{ + ScaleMode: canvas.ImageScaleFastest, + }, + cursor: myTheme.NewThemedRectangle(theme.ColorNameForeground), } + w.cursor.Hide() w.ExtendBaseWidget(w) return w } func (w *WaveformSeekbar) UpdateImage(img *backend.WaveformImage) { w.img.Image = img + prm, fg := w.getThemeColors() + w.recolorImage(prm, fg, w.imgProgressPixel) w.Refresh() } func (w *WaveformSeekbar) Refresh() { + w.cursor.Resize(fyne.NewSize(1.5, w.Size().Height-4)) + w.cursor.Refresh() prm, fg := w.getThemeColors() - w.recolorImage(prm, fg, w.imgProgress) - w.img.Refresh() - //w.BaseWidget.Refresh() + if w.recolorImage(prm, fg, w.imgProgressPixel) { + w.img.Refresh() + } +} + +var _ desktop.Hoverable = (*WaveformSeekbar)(nil) + +func (w *WaveformSeekbar) MouseIn(e *desktop.MouseEvent) { + w.cursor.Move(fyne.NewPos(e.Position.X, 2)) + w.cursor.Show() +} + +func (w *WaveformSeekbar) MouseMoved(e *desktop.MouseEvent) { + w.cursor.Move(fyne.NewPos(e.Position.X, 2)) +} + +func (w *WaveformSeekbar) MouseOut() { + w.cursor.Hide() +} + +var _ fyne.Tappable = (*WaveformSeekbar)(nil) + +func (w *WaveformSeekbar) Tapped(e *fyne.PointEvent) { + if w.OnSeeked != nil { + w.OnSeeked(float64(e.Position.X / w.Size().Width)) + } } func (w *WaveformSeekbar) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer( - container.New(layout.NewCustomPaddedLayout(4, 4, 0, 0), w.img), + container.NewStack( + container.New(layout.NewCustomPaddedLayout(4, 4, 0, 0), w.img), + container.NewWithoutLayout(w.cursor), + ), ) } @@ -54,8 +93,10 @@ func (w *WaveformSeekbar) CreateRenderer() fyne.WidgetRenderer { // (ratio from 0 to 1) func (w *WaveformSeekbar) SetProgress(v float64) { prm, fg := w.getThemeColors() - w.recolorImage(prm, fg, v) - w.Refresh() + thresholdPixel := int(math.Round(1024.0 /*pixel width of waveform*/ * v)) + if w.recolorImage(prm, fg, thresholdPixel) { + w.img.Refresh() + } } func (w *WaveformSeekbar) getThemeColors() (primary, foreground color.Color) { @@ -66,12 +107,12 @@ func (w *WaveformSeekbar) getThemeColors() (primary, foreground color.Color) { return primary, foreground } -func (w *WaveformSeekbar) recolorImage(cL, cR color.Color, progress float64) { +func (w *WaveformSeekbar) recolorImage(cL, cR color.Color, progress int) (updated bool) { if w.img.Image == nil { - return + return false } - if w.imgColorL == cL && w.imgColorR == cR && w.imgProgress == progress { - return + if w.imgColorL == cL && w.imgColorR == cR && w.imgProgressPixel == progress { + return false } _r, _g, _b, _ := cL.RGBA() @@ -84,12 +125,9 @@ func (w *WaveformSeekbar) recolorImage(cL, cR color.Color, progress float64) { // and not iterate the whole thing every time img := w.img.Image.(*image.NRGBA) bnds := img.Rect.Bounds() - thresholdPixel := int(float64(bnds.Dx()) * progress) - log.Println("progress = ", progress, " bounds Dx = ", bnds.Dx()) - log.Println("thresholdPixel = ", thresholdPixel) for x := 0; x < bnds.Dx(); x++ { for y := 0; y < bnds.Dy(); y++ { - if x < thresholdPixel { + if x < progress { setPixelRGB(img, x, y, rL, gL, bL) } else { setPixelRGB(img, x, y, rR, gR, bR) @@ -98,7 +136,8 @@ func (w *WaveformSeekbar) recolorImage(cL, cR color.Color, progress float64) { } w.imgColorL, w.imgColorR = cL, cR - w.imgProgress = progress + w.imgProgressPixel = progress + return true } func setPixelRGB(img *image.NRGBA, x, y int, r, g, b byte) {