Files
supersonic/ui/widgets/waveformseekbar.go
T

228 lines
5.6 KiB
Go

package widgets
import (
"image"
"image/color"
"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"
)
type WaveformSeekbar struct {
widget.DisableableWidget
OnSeeked func(float64)
imgColorL color.Color
imgColorR color.Color
imgProgressPixel int
focused bool
img *canvas.Image
cursor *canvas.Rectangle
focus *canvas.Rectangle
}
func NewWaveformSeekbar() *WaveformSeekbar {
w := &WaveformSeekbar{
img: &canvas.Image{
ScaleMode: canvas.ImageScaleFastest,
Image: backend.NewWaveformImage(),
},
cursor: canvas.NewRectangle(color.Transparent),
focus: canvas.NewRectangle(color.Transparent),
}
w.ExtendBaseWidget(w)
w.cursor.Hidden = true
w.focus.Hidden = true
return w
}
func (w *WaveformSeekbar) UpdateImage(img *backend.WaveformImage) {
prm, fg, _ := w.getThemeColors()
recolorWaveformImage(img, prm, fg, 0, w.imgProgressPixel, true)
w.img.Image = img
w.img.Refresh()
}
func (w *WaveformSeekbar) Refresh() {
w.cursor.Resize(fyne.NewSize(1, w.Size().Height-4))
w.focus.Resize(fyne.NewSize(3, w.Size().Height-2))
prm, fg, focus := w.getThemeColors()
w.updateImageProgress(prm, fg, w.imgProgressPixel)
w.recolorCursor(prm, fg, w.cursor.Position().X)
w.focus.FillColor = focus
w.BaseWidget.Refresh()
}
var _ desktop.Hoverable = (*WaveformSeekbar)(nil)
func (w *WaveformSeekbar) MouseIn(e *desktop.MouseEvent) {
if w.Disabled() {
return
}
prm, fg, _ := w.getThemeColors()
w.recolorCursor(prm, fg, e.Position.X)
w.cursor.Resize(fyne.NewSize(1, w.Size().Height-4))
w.cursor.Move(fyne.NewPos(e.Position.X, 2))
w.cursor.Show()
}
func (w *WaveformSeekbar) MouseMoved(e *desktop.MouseEvent) {
if w.Disabled() {
return
}
prm, fg, _ := w.getThemeColors()
w.recolorCursor(prm, fg, e.Position.X)
w.cursor.Move(fyne.NewPos(e.Position.X, 2))
}
func (w *WaveformSeekbar) MouseOut() {
if !w.focused {
w.cursor.Hide()
}
}
var _ fyne.Focusable = (*WaveformSeekbar)(nil)
func (w *WaveformSeekbar) FocusGained() {
w.focused = true
prm, fg, _ := w.getThemeColors()
w.recolorCursor(prm, fg, w.cursor.Position().X)
w.cursor.Resize(fyne.NewSize(1, w.Size().Height-4))
w.moveCursorAndFocusToCurrentPosition()
w.cursor.Show()
w.focus.Show()
}
func (w *WaveformSeekbar) FocusLost() {
w.focused = false
w.cursor.Hide()
w.focus.Hide()
}
func (w *WaveformSeekbar) TypedKey(e *fyne.KeyEvent) {
progress := float32(w.imgProgressPixel) / 1024
switch e.Name {
case fyne.KeyLeft:
progress = max(progress-0.05, 0)
case fyne.KeyRight:
progress = min(progress+0.05, 1)
default:
return
}
w.Tapped(&fyne.PointEvent{Position: fyne.NewPos(w.Size().Width*progress, 0)})
}
func (w *WaveformSeekbar) TypedRune(r rune) {
}
var _ fyne.Tappable = (*WaveformSeekbar)(nil)
func (w *WaveformSeekbar) Tapped(e *fyne.PointEvent) {
if !w.Disabled() && w.OnSeeked != nil {
w.OnSeeked(float64(e.Position.X / w.Size().Width))
}
}
func (w *WaveformSeekbar) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(
container.NewStack(
container.New(layout.NewCustomPaddedLayout(4, 4, 0, 0), w.img),
container.NewWithoutLayout(w.cursor, w.focus),
),
)
}
// SetProgress sets how much of the seekbar has been played
// (ratio from 0 to 1)
func (w *WaveformSeekbar) SetProgress(v float64) {
prm, fg, _ := w.getThemeColors()
thresholdPixel := int(math.Round(1024.0 /*pixel width of waveform*/ * v))
if w.updateImageProgress(prm, fg, thresholdPixel) {
w.img.Refresh()
if w.focused {
w.moveCursorAndFocusToCurrentPosition()
}
}
}
func (w *WaveformSeekbar) getThemeColors() (primary, foreground, focus color.Color) {
th := w.Theme()
vnt := fyne.CurrentApp().Settings().ThemeVariant()
primary = th.Color(theme.ColorNamePrimary, vnt)
foreground = th.Color(theme.ColorNameForeground, vnt)
focus = th.Color(theme.ColorNameFocus, vnt)
return primary, foreground, focus
}
func (w *WaveformSeekbar) recolorCursor(prm, fg color.Color, posX float32) {
progress := float32(w.imgProgressPixel) / 1024 /*waveform image width*/
if posX/w.Size().Width < progress {
w.cursor.FillColor = fg
} else {
w.cursor.FillColor = prm
}
}
func (w *WaveformSeekbar) moveCursorAndFocusToCurrentPosition() {
progress := float32(w.imgProgressPixel) / 1024
pos := w.Size().Width * progress
w.cursor.Move(fyne.NewPos(pos, 2))
w.focus.Move(fyne.NewPos(pos-1, 1))
}
func (w *WaveformSeekbar) updateImageProgress(cL, cR color.Color, progress int) (updated bool) {
if w.img.Image == nil {
return false
}
if w.imgColorL == cL && w.imgColorR == cR && w.imgProgressPixel == progress {
return false
}
img := w.img.Image.(*image.NRGBA)
recolorWaveformImage(img, cL, cR, w.imgProgressPixel, progress, false)
w.imgColorL, w.imgColorR = cL, cR
w.imgProgressPixel = progress
return true
}
func recolorWaveformImage(img *image.NRGBA, cL, cR color.Color, oldProgress, newProgress int, fullRecolor bool) {
_r, _g, _b, _ := cL.RGBA()
rL, gL, bL := byte(_r>>8), byte(_g>>8), byte(_b>>8)
_r, _g, _b, _ = cR.RGBA()
rR, gR, bR := byte(_r>>8), byte(_g>>8), byte(_b>>8)
bnds := img.Rect.Bounds()
xMin, xMax := 0, bnds.Dx()
if !fullRecolor {
xMin = max(0, min(oldProgress, newProgress))
xMax = min(bnds.Dx(), max(oldProgress, newProgress))
}
for x := xMin; x < xMax; x++ {
for y := 0; y < bnds.Dy(); y++ {
if x < newProgress {
setPixelRGB(img, x, y, rL, gL, bL)
} else {
setPixelRGB(img, x, y, rR, gR, bR)
}
}
}
}
func setPixelRGB(img *image.NRGBA, x, y int, r, g, b byte) {
offset := img.PixOffset(x, y)
img.Pix[offset+0] = r
img.Pix[offset+1] = g
img.Pix[offset+2] = b
}