seeking, layout update

This commit is contained in:
Drew Weymouth
2025-07-24 19:26:54 -07:00
parent 43f6d501c9
commit 7dae735d10
6 changed files with 82 additions and 38 deletions
+1 -1
View File
@@ -810,7 +810,7 @@ func (pm *playbackEngine) invokeNoArgCallbacks(cbs []func()) {
func (p *playbackEngine) startPollTimePos() { func (p *playbackEngine) startPollTimePos() {
ctx, cancel := context.WithCancel(p.ctx) ctx, cancel := context.WithCancel(p.ctx)
p.cancelPollPos = cancel p.cancelPollPos = cancel
pollingTick := time.NewTicker(250 * time.Millisecond) pollingTick := time.NewTicker(125 * time.Millisecond)
go func() { go func() {
for { for {
+1 -1
View File
@@ -125,7 +125,7 @@ func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr
bp.imageLoader = util.NewThumbnailLoader(im, bp.NowPlaying.SetImage) 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) bp.NowPlaying, bp.Controls, bp.AuxControls)
return bp return bp
} }
+1 -1
View File
@@ -109,7 +109,7 @@ func NewBrowsingPane(app *backend.App, contr *controller.Controller, onGoHome fu
b.navBtnsPageMap = map[controller.PageName]fyne.Resource{} b.navBtnsPageMap = map[controller.PageName]fyne.Resource{}
b.container = container.NewBorder(container.New( b.container = container.NewBorder(container.New(
&layout.CustomPaddedLayout{LeftPadding: -5, RightPadding: -5}, &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(b.home, b.back, b.forward, b.reload), b.navBtnsContainer,
container.NewHBox(layout.NewSpacer(), quickSearchBtn, b.settingsBtn))), container.NewHBox(layout.NewSpacer(), quickSearchBtn, b.settingsBtn))),
nil, nil, nil, b.pageContainer) nil, nil, nil, b.pageContainer)
+17 -13
View File
@@ -7,17 +7,20 @@ import (
) )
// Lays out up to 3 objects such that the middle object, Objects[1], // 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. // is centered in the available space and takes up a fixed width,
// The left, and right (if non-nil), split the leftover space equally. // or optionally a fraction of the overall space.
// The left, and right split the leftover space equally.
type LeftMiddleRightLayout struct { type LeftMiddleRightLayout struct {
middleWidth float32 middleWidthMin float32
hbox fyne.Layout middleWidthFraction float32
hbox fyne.Layout
} }
func NewLeftMiddleRightLayout(middleWidth float32) *LeftMiddleRightLayout { func NewLeftMiddleRightLayout(middleWidthMin, middleWidthFraction float32) *LeftMiddleRightLayout {
return &LeftMiddleRightLayout{ return &LeftMiddleRightLayout{
middleWidth: middleWidth, middleWidthMin: middleWidthMin,
hbox: layout.NewHBoxLayout(), middleWidthFraction: middleWidthFraction,
hbox: layout.NewHBoxLayout(),
} }
} }
@@ -25,20 +28,21 @@ func (b *LeftMiddleRightLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
hboxSize := b.hbox.MinSize(objects) hboxSize := b.hbox.MinSize(objects)
return fyne.Size{ return fyne.Size{
Height: hboxSize.Height, 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) { func (b *LeftMiddleRightLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
pad := theme.Padding() 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 lrW := (size.Width - midW - pad*4) / 2
objects[0].Resize(fyne.NewSize(lrW, size.Height)) objects[0].Resize(fyne.NewSize(lrW, size.Height))
objects[0].Move(fyne.NewPos(pad, 0)) objects[0].Move(fyne.NewPos(pad, 0))
objects[1].Resize(fyne.NewSize(midW, size.Height)) objects[1].Resize(fyne.NewSize(midW, size.Height))
objects[1].Move(fyne.NewPos(lrW+pad*2, 0)) objects[1].Move(fyne.NewPos(lrW+pad*2, 0))
if objects[2] != nil { objects[2].Resize(fyne.NewSize(lrW, size.Height))
objects[2].Resize(fyne.NewSize(lrW, size.Height)) objects[2].Move(fyne.NewPos(lrW+midW+pad*3, 0))
objects[2].Move(fyne.NewPos(lrW+midW+pad*3, 0))
}
} }
+2 -1
View File
@@ -160,6 +160,7 @@ func (pc *PlayerControls) OnSeek(f func(float64)) {
f(pos) f(pos)
} }
} }
pc.waveform.OnSeeked = f
} }
func (pc *PlayerControls) OnSeekPrevious(f func()) { func (pc *PlayerControls) OnSeekPrevious(f func()) {
@@ -208,10 +209,10 @@ func (pc *PlayerControls) UpdatePlayTime(curTime, totalTime float64) {
pc.curTimeLabel.SetText(ct) pc.curTimeLabel.SetText(ct)
updated = true updated = true
} }
pc.waveform.SetProgress(v)
if updated { if updated {
// Only update slider once a second when time label changes // Only update slider once a second when time label changes
pc.slider.SetValue(v) pc.slider.SetValue(v)
pc.waveform.SetProgress(v)
} }
} }
} }
+60 -21
View File
@@ -3,50 +3,89 @@ package widgets
import ( import (
"image" "image"
"image/color" "image/color"
"log" "math"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"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" "github.com/dweymouth/supersonic/backend"
myTheme "github.com/dweymouth/supersonic/ui/theme"
) )
type WaveformSeekbar struct { type WaveformSeekbar struct {
widget.BaseWidget widget.BaseWidget
imgColorL color.Color OnSeeked func(float64)
imgColorR color.Color
imgProgress float64
img *canvas.Image imgColorL color.Color
imgColorR color.Color
imgProgressPixel int
img *canvas.Image
cursor *myTheme.ThemedRectangle
} }
func NewWaveformSeekbar() *WaveformSeekbar { func NewWaveformSeekbar() *WaveformSeekbar {
w := &WaveformSeekbar{ w := &WaveformSeekbar{
img: canvas.NewImageFromImage(nil), img: &canvas.Image{
ScaleMode: canvas.ImageScaleFastest,
},
cursor: myTheme.NewThemedRectangle(theme.ColorNameForeground),
} }
w.cursor.Hide()
w.ExtendBaseWidget(w) w.ExtendBaseWidget(w)
return w return w
} }
func (w *WaveformSeekbar) UpdateImage(img *backend.WaveformImage) { func (w *WaveformSeekbar) UpdateImage(img *backend.WaveformImage) {
w.img.Image = img w.img.Image = img
prm, fg := w.getThemeColors()
w.recolorImage(prm, fg, w.imgProgressPixel)
w.Refresh() w.Refresh()
} }
func (w *WaveformSeekbar) Refresh() { func (w *WaveformSeekbar) Refresh() {
w.cursor.Resize(fyne.NewSize(1.5, w.Size().Height-4))
w.cursor.Refresh()
prm, fg := w.getThemeColors() prm, fg := w.getThemeColors()
w.recolorImage(prm, fg, w.imgProgress) if w.recolorImage(prm, fg, w.imgProgressPixel) {
w.img.Refresh() w.img.Refresh()
//w.BaseWidget.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 { func (w *WaveformSeekbar) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer( 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) // (ratio from 0 to 1)
func (w *WaveformSeekbar) SetProgress(v float64) { func (w *WaveformSeekbar) SetProgress(v float64) {
prm, fg := w.getThemeColors() prm, fg := w.getThemeColors()
w.recolorImage(prm, fg, v) thresholdPixel := int(math.Round(1024.0 /*pixel width of waveform*/ * v))
w.Refresh() if w.recolorImage(prm, fg, thresholdPixel) {
w.img.Refresh()
}
} }
func (w *WaveformSeekbar) getThemeColors() (primary, foreground color.Color) { func (w *WaveformSeekbar) getThemeColors() (primary, foreground color.Color) {
@@ -66,12 +107,12 @@ func (w *WaveformSeekbar) getThemeColors() (primary, foreground color.Color) {
return primary, foreground 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 { if w.img.Image == nil {
return return false
} }
if w.imgColorL == cL && w.imgColorR == cR && w.imgProgress == progress { if w.imgColorL == cL && w.imgColorR == cR && w.imgProgressPixel == progress {
return return false
} }
_r, _g, _b, _ := cL.RGBA() _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 // and not iterate the whole thing every time
img := w.img.Image.(*image.NRGBA) img := w.img.Image.(*image.NRGBA)
bnds := img.Rect.Bounds() 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 x := 0; x < bnds.Dx(); x++ {
for y := 0; y < bnds.Dy(); y++ { for y := 0; y < bnds.Dy(); y++ {
if x < thresholdPixel { if x < progress {
setPixelRGB(img, x, y, rL, gL, bL) setPixelRGB(img, x, y, rL, gL, bL)
} else { } else {
setPixelRGB(img, x, y, rR, gR, bR) 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.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) { func setPixelRGB(img *image.NRGBA, x, y int, r, g, b byte) {