seeking, layout update
This commit is contained in:
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user