fyne-widgets/pkg/vumeter/vumeter.go

229 lines
5.8 KiB
Go

package vumeter
import (
"fmt"
"image/color"
"strconv"
"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 vuRenderer struct {
label *canvas.Text
background, bar,
optimumBar, peakBar,
lowalphaGreen,
lowalphaAmber,
lowalphaRed *canvas.Rectangle
objects []fyne.CanvasObject
meter *vuMeter
}
// MinSize calculates the minimum size of the VU meter. Code shamelessly stolen from progressbar for now.
func (v *vuRenderer) MinSize() fyne.Size {
var tsize fyne.Size
if text := v.meter.TextFormatter; text != nil {
tsize = fyne.MeasureText(text(), v.label.TextSize, v.label.TextStyle)
} else {
tsize = fyne.MeasureText("100%", v.label.TextSize, v.label.TextStyle)
}
return fyne.NewSize(tsize.Width+theme.Padding()*4, tsize.Height+theme.Padding()*2)
}
func (v *vuRenderer) updateBars() {
if v.meter.Value < v.meter.Min {
v.meter.Value = v.meter.Min
}
if v.meter.Value > v.meter.Max {
v.meter.Value = v.meter.Max
}
delta := float64(v.meter.Max - v.meter.Min)
ratio := float64(v.meter.Value-v.meter.Min) / delta
if text := v.meter.TextFormatter; text != nil {
v.label.Text = text()
} else {
v.label.Text = strconv.Itoa(int(ratio*100)) + "%"
}
size := v.meter.Size()
greenWidth := 0.0
amberWidth := 0.0
redWidth := 0.0
if ratio > (v.meter.OptimumValueMin / 100) {
if ratio > (v.meter.OptimumValueMax / 100) {
greenWidth = float64(size.Width) * v.meter.OptimumValueMin / 100
amberWidth = float64(size.Width) * v.meter.OptimumValueMax / 100
redWidth = float64(size.Width) * ratio
} else {
greenWidth = float64(size.Width) * v.meter.OptimumValueMin / 100
amberWidth = float64(size.Width) * ratio
}
} else {
greenWidth = float64(size.Width) * ratio
}
redWidth = redWidth - amberWidth
if redWidth < 0 {
redWidth = 0
}
amberWidth = amberWidth - greenWidth
if amberWidth < 0 {
amberWidth = 0
}
lowalphaGreenWidth := float64(size.Width) * v.meter.OptimumValueMin / 100
lowalphaAmberWidth := float64(size.Width) * v.meter.OptimumValueMax / 100
lowalphaRedWidth := float64(size.Width)
lowalphaRedWidth = lowalphaRedWidth - lowalphaAmberWidth
lowalphaAmberWidth = lowalphaAmberWidth - lowalphaGreenWidth
v.lowalphaGreen.Resize(fyne.NewSize(float32(lowalphaGreenWidth), size.Height))
v.lowalphaAmber.Resize(fyne.NewSize(float32(lowalphaAmberWidth), size.Height))
v.lowalphaAmber.Move(fyne.NewPos(float32(lowalphaGreenWidth), 0))
v.lowalphaRed.Resize(fyne.NewSize(float32(lowalphaRedWidth), size.Height))
v.lowalphaRed.Move(fyne.NewPos(float32(lowalphaAmberWidth+lowalphaGreenWidth), 0))
v.bar.Resize(fyne.NewSize(float32(greenWidth), size.Height))
v.optimumBar.Resize(fyne.NewSize(float32(amberWidth), size.Height))
v.optimumBar.Move(fyne.NewPos(float32(greenWidth), 0))
v.peakBar.Resize(fyne.NewSize(float32(redWidth), size.Height))
v.peakBar.Move(fyne.NewPos(float32(greenWidth+amberWidth), 0))
}
// Layout the components of the widget
func (v *vuRenderer) Layout(size fyne.Size) {
v.background.Resize(size)
v.label.Resize(size)
v.updateBars()
}
// ApplyTheme is called when the vuMeter may need to update it's look
func (v *vuRenderer) ApplyTheme() {
v.label.Color = theme.ForegroundColor()
v.Refresh()
}
func (v *vuRenderer) BackgroundColor() color.Color {
return theme.ButtonColor()
}
func (v *vuRenderer) Refresh() {
v.label.Text = fmt.Sprintf("%f %%", v.meter.Value)
fmt.Printf("%f %%\n", v.meter.Value)
v.Layout(v.meter.Size())
canvas.Refresh(v.meter)
}
func (v *vuRenderer) Objects() []fyne.CanvasObject {
return v.objects
}
func (v *vuRenderer) Destroy() {
}
// vuMeter widget is a kind of custom progressbar but has "zones" of different color for peaking.
type vuMeter struct {
widget.BaseWidget
TextFormatter func() string
Value, Min, Max,
OptimumValueMin, OptimumValueMax float64
binder basicBinder
}
func (m *vuMeter) CreateRenderer() fyne.WidgetRenderer {
m.ExtendBaseWidget(m)
if m.Min == 0 && m.Max == 0 {
m.Max = 1.0
}
background := canvas.NewRectangle(theme.BackgroundColor())
lowalphaGreen := canvas.NewRectangle(color.RGBA{0, 255, 0, 64})
lowalphaAmber := canvas.NewRectangle(color.RGBA{255, 200, 0, 64})
lowalphaRed := canvas.NewRectangle(color.RGBA{255, 0, 0, 64})
bar := canvas.NewRectangle(color.RGBA{0, 255, 0, 255})
optimumBar := canvas.NewRectangle(color.RGBA{255, 200, 0, 255})
peakBar := canvas.NewRectangle(color.RGBA{255, 0, 0, 255})
label := canvas.NewText("0%", theme.ForegroundColor())
label.Alignment = fyne.TextAlignCenter
objects := []fyne.CanvasObject{
background,
lowalphaGreen,
lowalphaAmber,
lowalphaRed,
bar,
optimumBar,
peakBar,
label,
}
return &vuRenderer{label, background, bar, optimumBar, peakBar,
lowalphaGreen,
lowalphaAmber,
lowalphaRed, objects, m}
}
// SetValue changes the current value of this progress bar (from p.Min to p.Max).
// The widget will be refreshed to indicate the change.
func (m *vuMeter) SetValue(v float64) {
m.Value = v
m.Refresh()
}
func (m *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
}
m.SetValue(val)
}
func (m *vuMeter) MinSize() fyne.Size {
m.ExtendBaseWidget(m)
return m.BaseWidget.MinSize()
}
func (m *vuMeter) Bind(data binding.Float) {
m.binder.SetCallback(m.updateFromData)
m.binder.Bind(data)
}
func (m *vuMeter) Unbind() {
m.binder.Unbind()
}
// NewVUMeter creates a new meter widget with the specified value
func NewVUMeter(value float64) *vuMeter {
meter := &vuMeter{Value: value}
meter.OptimumValueMin = 75
meter.OptimumValueMax = 85
meter.ExtendBaseWidget(meter)
return meter
}
func NewVUMeterWithData(data binding.Float) *vuMeter {
f, err := data.Get()
if err != nil {
f = 25.0
}
m := NewVUMeter(f)
m.Bind(data)
return m
}