package vumeter import ( "image/color" "strconv" "git.martyn.berlin/martyn/fyne-widgets/internal/cache" col "git.martyn.berlin/martyn/fyne-widgets/internal/color" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) type VUMeter struct { widget.BaseWidget Min, Max, Value float64 TextFormatter func() string binder basicBinder } type vuMeterRenderer struct { objects []fyne.CanvasObject background, bar *canvas.Rectangle label *canvas.Text meter *VUMeter } func (p *vuMeterRenderer) MinSize() fyne.Size { var tsize fyne.Size if text := p.meter.TextFormatter; text != nil { tsize = fyne.MeasureText(text(), p.label.TextSize, p.label.TextStyle) } else { tsize = fyne.MeasureText("100%", p.label.TextSize, p.label.TextStyle) } return fyne.NewSize(tsize.Width+theme.Padding()*4, tsize.Height+theme.Padding()*2) } func (p *vuMeterRenderer) updateBar() { if p.meter.Value < p.meter.Min { p.meter.Value = p.meter.Min } if p.meter.Value > p.meter.Max { p.meter.Value = p.meter.Max } delta := float32(p.meter.Max - p.meter.Min) ratio := float32(p.meter.Value-p.meter.Min) / delta if text := p.meter.TextFormatter; text != nil { p.label.Text = text() } else { p.label.Text = strconv.Itoa(int(ratio*100)) + "%" } size := p.meter.Size() p.bar.Resize(fyne.NewSize(size.Width*ratio, size.Height)) } func (p *vuMeterRenderer) Layout(size fyne.Size) { p.background.Resize(size) p.label.Resize(size) p.updateBar() } func (p *vuMeterRenderer) applyTheme() { p.background.FillColor = vuMeterBackgroundColor() p.bar.FillColor = theme.PrimaryColor() p.label.Color = theme.ForegroundColor() p.label.TextSize = theme.TextSize() } func (p *vuMeterRenderer) Refresh() { p.applyTheme() p.updateBar() p.background.Refresh() p.bar.Refresh() p.meter.super() } func (r *vuMeterRenderer) Objects() []fyne.CanvasObject { return r.objects } // SetObjects updates the objects of the renderer. func (r *vuMeterRenderer) SetObjects(objects []fyne.CanvasObject) { r.objects = objects } func (r *vuMeterRenderer) Destroy() { } func (p *VUMeter) Bind(data binding.Float) { p.binder.SetCallback(p.updateFromData) p.binder.Bind(data) } func (p *VUMeter) SetValue(v float64) { p.Value = v p.Refresh() } func (p *VUMeter) MinSize() fyne.Size { p.ExtendBaseWidget(p) return p.BaseWidget.MinSize() } func (p *VUMeter) CreateRenderer() fyne.WidgetRenderer { p.ExtendBaseWidget(p) if p.Min == 0 && p.Max == 0 { p.Max = 1.0 } background := canvas.NewRectangle(vuMeterBackgroundColor()) bar := canvas.NewRectangle(theme.PrimaryColor()) label := canvas.NewText("0%", theme.ForegroundColor()) label.Alignment = fyne.TextAlignCenter return &vuMeterRenderer{[]fyne.CanvasObject{background, bar, label}, background, bar, label, p} } func (p *VUMeter) Unbind() { p.binder.Unbind() } func NewVUMeter() *VUMeter { p := &VUMeter{Min: 0, Max: 1} cache.Renderer(p).Layout(p.MinSize()) return p } func NewVUMeterWithData(data binding.Float) *VUMeter { p := NewVUMeter() p.Bind(data) return p } func vuMeterBackgroundColor() color.Color { r, g, b, a := col.ToNRGBA(theme.PrimaryColor()) faded := uint8(a) / 3 return &color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: faded} } func (p *VUMeter) updateFromData(data binding.DataItem) { if data == nil { return } floatSource, ok := data.(binding.Float) if !ok { return } val, err := floatSource.Get() if err != nil { fyne.LogError("Error getting current data value", err) return } p.SetValue(val) }