make peak meter horizontal, add ruler and L/R labels
This commit is contained in:
+143
-49
@@ -1,19 +1,21 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/theme"
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
|
|
||||||
|
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
meterRangeDB = 60
|
meterRangeDB = 62
|
||||||
rmsSmoothingFactor = 0.8
|
rmsSmoothingFactor = 0.8
|
||||||
peakHoldFrames = 60
|
peakHoldFrames = 60
|
||||||
)
|
)
|
||||||
@@ -33,12 +35,6 @@ type PeakMeter struct {
|
|||||||
rPeakHoldFrame uint64
|
rPeakHoldFrame uint64
|
||||||
frameCounter uint64
|
frameCounter uint64
|
||||||
|
|
||||||
lPeakRect canvas.Rectangle
|
|
||||||
rPeakRect canvas.Rectangle
|
|
||||||
lRMSRect canvas.Rectangle
|
|
||||||
rRMSRect canvas.Rectangle
|
|
||||||
lPeakHoldRect canvas.Rectangle
|
|
||||||
rPeakHoldRect canvas.Rectangle
|
|
||||||
anim *fyne.Animation
|
anim *fyne.Animation
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,53 +85,151 @@ func (p *PeakMeter) tick(_ float32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PeakMeter) CreateRenderer() fyne.WidgetRenderer {
|
func (p *PeakMeter) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return newPeakMeterRenderer(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
type peakMeterRenderer struct {
|
||||||
|
p *PeakMeter
|
||||||
|
|
||||||
|
lLabel canvas.Text
|
||||||
|
rLabel canvas.Text
|
||||||
|
lPeakRect canvas.Rectangle
|
||||||
|
rPeakRect canvas.Rectangle
|
||||||
|
lRMSRect canvas.Rectangle
|
||||||
|
rRMSRect canvas.Rectangle
|
||||||
|
lPeakHoldRect canvas.Rectangle
|
||||||
|
rPeakHoldRect canvas.Rectangle
|
||||||
|
|
||||||
|
rulerLines []canvas.Rectangle
|
||||||
|
rulerLabels []canvas.Text
|
||||||
|
|
||||||
|
fgColor color.Color
|
||||||
|
bgColor color.Color
|
||||||
|
ruleColor color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPeakMeterRenderer(pm *PeakMeter) *peakMeterRenderer {
|
||||||
|
p := &peakMeterRenderer{p: pm}
|
||||||
|
p.lLabel.Text = "L"
|
||||||
|
p.rLabel.Text = "R"
|
||||||
|
numRules := int(math.Ceil(float64(meterRangeDB) / 10))
|
||||||
|
p.rulerLines = make([]canvas.Rectangle, numRules)
|
||||||
|
p.rulerLabels = make([]canvas.Text, numRules)
|
||||||
|
x := 0
|
||||||
|
for i := range p.rulerLabels {
|
||||||
|
p.rulerLabels[i].Text = fmt.Sprintf("%d dB", x)
|
||||||
|
p.rulerLabels[i].Resize(p.rulerLabels[i].MinSize())
|
||||||
|
x -= 10
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *peakMeterRenderer) MinSize() fyne.Size {
|
||||||
|
return fyne.NewSize(275, 75)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *peakMeterRenderer) Layout(size fyne.Size) {
|
||||||
|
topSpacing := float32(5)
|
||||||
|
lrLabelWidth := float32(20)
|
||||||
|
overflowWidth := float32(10)
|
||||||
|
ruleLabelHeight := float32(10)
|
||||||
|
meterWidth := size.Width - lrLabelWidth - overflowWidth - topSpacing
|
||||||
|
|
||||||
|
lPeakWidth := float32(math.Max(0, meterRangeDB+l.p.lPeak)/meterRangeDB) * meterWidth
|
||||||
|
rPeakWidth := float32(math.Max(0, meterRangeDB+l.p.rPeak)/meterRangeDB) * meterWidth
|
||||||
|
lRMSWidth := float32(math.Max(0, meterRangeDB+l.p.lRMS)/meterRangeDB) * meterWidth
|
||||||
|
rRMSWidth := float32(math.Max(0, meterRangeDB+l.p.rRMS)/meterRangeDB) * meterWidth
|
||||||
|
lPeakHoldPos := float32(math.Max(0, meterRangeDB+l.p.lPeakHold)/meterRangeDB) * meterWidth
|
||||||
|
rPeakHoldPos := float32(math.Max(0, meterRangeDB+l.p.rPeakHold)/meterRangeDB) * meterWidth
|
||||||
|
|
||||||
|
barSpacing := float32(2)
|
||||||
|
barHeight := size.Height/2 - barSpacing - ruleLabelHeight
|
||||||
|
|
||||||
|
labelMin := l.lLabel.MinSize()
|
||||||
|
l.lLabel.Move(fyne.NewPos(4, (barHeight-labelMin.Height)/2+topSpacing))
|
||||||
|
l.lLabel.Resize(l.lLabel.MinSize())
|
||||||
|
l.rLabel.Move(fyne.NewPos(4, barHeight+barSpacing+topSpacing+(barHeight-labelMin.Height)/2))
|
||||||
|
|
||||||
|
l.lPeakRect.Move(fyne.NewPos(lrLabelWidth, topSpacing))
|
||||||
|
l.lPeakRect.Resize(fyne.NewSize(lPeakWidth, barHeight))
|
||||||
|
l.rPeakRect.Move(fyne.NewPos(lrLabelWidth, barHeight+barSpacing+topSpacing))
|
||||||
|
l.rPeakRect.Resize(fyne.NewSize(rPeakWidth, barHeight))
|
||||||
|
l.lRMSRect.Move(fyne.NewPos(lrLabelWidth, topSpacing))
|
||||||
|
l.lRMSRect.Resize(fyne.NewSize(lRMSWidth, barHeight))
|
||||||
|
l.rRMSRect.Move(fyne.NewPos(lrLabelWidth, barHeight+barSpacing+topSpacing))
|
||||||
|
l.rRMSRect.Resize(fyne.NewSize(rRMSWidth, barHeight))
|
||||||
|
|
||||||
|
peakHoldWidth := theme.SeparatorThicknessSize() * 2
|
||||||
|
l.lPeakHoldRect.Move(fyne.NewPos(lPeakHoldPos+lrLabelWidth, topSpacing))
|
||||||
|
l.lPeakHoldRect.Resize(fyne.NewSize(peakHoldWidth, barHeight))
|
||||||
|
l.rPeakHoldRect.Move(fyne.NewPos(rPeakHoldPos+lrLabelWidth, barHeight+barSpacing+topSpacing))
|
||||||
|
l.rPeakHoldRect.Resize(fyne.NewSize(peakHoldWidth, barHeight))
|
||||||
|
|
||||||
|
ruleWidth := peakHoldWidth * 0.667
|
||||||
|
x := lrLabelWidth + meterWidth
|
||||||
|
for i := range l.rulerLines {
|
||||||
|
bottom := (barHeight + barSpacing) * 2
|
||||||
|
l.rulerLines[i].Move(fyne.NewPos(x, topSpacing))
|
||||||
|
l.rulerLines[i].Resize(fyne.NewSize(ruleWidth, bottom))
|
||||||
|
l.rulerLabels[i].Move(fyne.NewPos(x-10, bottom+topSpacing))
|
||||||
|
x -= meterWidth * (10 / float32(meterRangeDB))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *peakMeterRenderer) Refresh() {
|
||||||
|
foreground := theme.ForegroundColor()
|
||||||
|
background := theme.BackgroundColor()
|
||||||
|
errC := theme.ErrorColor()
|
||||||
c := theme.PrimaryColor().(color.NRGBA)
|
c := theme.PrimaryColor().(color.NRGBA)
|
||||||
c.A = 128
|
c.A = 128
|
||||||
p.lPeakRect.FillColor = c
|
l.lLabel.Color = foreground
|
||||||
p.rPeakRect.FillColor = c
|
l.rLabel.Color = foreground
|
||||||
p.lRMSRect.FillColor = c
|
l.lLabel.TextSize = 16
|
||||||
p.rRMSRect.FillColor = c
|
l.rLabel.TextSize = 16
|
||||||
p.lPeakHoldRect.FillColor = theme.ForegroundColor()
|
l.lLabel.TextStyle.Bold = true
|
||||||
p.rPeakHoldRect.FillColor = p.lPeakHoldRect.FillColor
|
l.rLabel.TextStyle.Bold = true
|
||||||
return widget.NewSimpleRenderer(
|
l.lPeakRect.FillColor = c
|
||||||
container.New(
|
l.rPeakRect.FillColor = c
|
||||||
&peakMeterLayout{p},
|
l.lRMSRect.FillColor = c
|
||||||
&p.lPeakRect, &p.rPeakRect,
|
l.rRMSRect.FillColor = c
|
||||||
&p.lRMSRect, &p.rRMSRect,
|
|
||||||
&p.lPeakHoldRect, &p.rPeakHoldRect,
|
if l.p.lPeakHold >= -0.00001 {
|
||||||
),
|
l.lPeakHoldRect.FillColor = errC
|
||||||
)
|
} else {
|
||||||
|
l.lPeakHoldRect.FillColor = foreground
|
||||||
|
}
|
||||||
|
if l.p.rPeakHold >= -0.00001 {
|
||||||
|
l.rPeakHoldRect.FillColor = errC
|
||||||
|
} else {
|
||||||
|
l.rPeakHoldRect.FillColor = foreground
|
||||||
}
|
}
|
||||||
|
|
||||||
type peakMeterLayout struct {
|
if foreground != l.fgColor || background != l.bgColor {
|
||||||
p *PeakMeter
|
l.ruleColor = myTheme.BlendColors(foreground, background, 0.5)
|
||||||
|
l.fgColor = foreground
|
||||||
|
l.bgColor = background
|
||||||
|
}
|
||||||
|
for i := range l.rulerLines {
|
||||||
|
l.rulerLines[i].FillColor = l.ruleColor
|
||||||
|
l.rulerLabels[i].TextSize = 11
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *peakMeterLayout) MinSize(_ []fyne.CanvasObject) fyne.Size {
|
l.Layout(l.p.Size())
|
||||||
return fyne.NewSize(50, 150)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *peakMeterLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
|
func (l *peakMeterRenderer) Objects() []fyne.CanvasObject {
|
||||||
lPeakH := float32(math.Max(0, meterRangeDB+l.p.lPeak)/meterRangeDB) * size.Height
|
obj := make([]fyne.CanvasObject, 0, 6+len(l.rulerLines))
|
||||||
rPeakH := float32(math.Max(0, meterRangeDB+l.p.rPeak)/meterRangeDB) * size.Height
|
for i := range l.rulerLines {
|
||||||
lRMSH := float32(math.Max(0, meterRangeDB+l.p.lRMS)/meterRangeDB) * size.Height
|
obj = append(obj, &l.rulerLines[i], &l.rulerLabels[i])
|
||||||
rRMSH := float32(math.Max(0, meterRangeDB+l.p.rRMS)/meterRangeDB) * size.Height
|
}
|
||||||
lPeakHoldPos := float32(math.Max(0, meterRangeDB+l.p.lPeakHold)/meterRangeDB) * size.Height
|
return append(obj,
|
||||||
rPeakHoldPos := float32(math.Max(0, meterRangeDB+l.p.rPeakHold)/meterRangeDB) * size.Height
|
&l.lLabel, &l.rLabel,
|
||||||
|
&l.lPeakRect, &l.rPeakRect,
|
||||||
halfW := size.Width / 2
|
&l.lRMSRect, &l.rRMSRect,
|
||||||
objects[0].Move(fyne.NewPos(0, size.Height-lPeakH))
|
&l.lPeakHoldRect, &l.rPeakHoldRect)
|
||||||
objects[0].Resize(fyne.NewSize(halfW, lPeakH))
|
}
|
||||||
objects[1].Move(fyne.NewPos(halfW, size.Height-rPeakH))
|
|
||||||
objects[1].Resize(fyne.NewSize(halfW, rPeakH))
|
func (l *peakMeterRenderer) Destroy() {
|
||||||
objects[2].Move(fyne.NewPos(0, size.Height-lRMSH))
|
l.p.Stop()
|
||||||
objects[2].Resize(fyne.NewSize(halfW, lRMSH))
|
|
||||||
objects[3].Move(fyne.NewPos(halfW, size.Height-rRMSH))
|
|
||||||
objects[3].Resize(fyne.NewSize(halfW, rRMSH))
|
|
||||||
|
|
||||||
peakHoldHt := theme.SeparatorThicknessSize() * 2
|
|
||||||
objects[4].Move(fyne.NewPos(0, size.Height-lPeakHoldPos-peakHoldHt))
|
|
||||||
objects[4].Resize(fyne.NewSize(halfW, peakHoldHt))
|
|
||||||
objects[5].Move(fyne.NewPos(halfW, size.Height-rPeakHoldPos-peakHoldHt))
|
|
||||||
objects[5].Resize(fyne.NewSize(halfW, peakHoldHt))
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -117,7 +117,7 @@ func (m *MyTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Col
|
|||||||
// average the Foreground and Disabled colors
|
// average the Foreground and Disabled colors
|
||||||
foreground := colorOrDefault(colors.Foreground, defColors.Foreground, theme.ColorNameForeground, variant)
|
foreground := colorOrDefault(colors.Foreground, defColors.Foreground, theme.ColorNameForeground, variant)
|
||||||
disabled := colorOrDefault(colors.Disabled, defColors.Disabled, theme.ColorNameDisabled, variant)
|
disabled := colorOrDefault(colors.Disabled, defColors.Disabled, theme.ColorNameDisabled, variant)
|
||||||
return blendColors(foreground, disabled, 0.33)
|
return BlendColors(foreground, disabled, 0.33)
|
||||||
case ColorNameHoveredIconButton:
|
case ColorNameHoveredIconButton:
|
||||||
if variant == theme.VariantDark {
|
if variant == theme.VariantDark {
|
||||||
return color.White
|
return color.White
|
||||||
@@ -273,7 +273,7 @@ func (m *MyTheme) getVariant() fyne.ThemeVariant {
|
|||||||
return fyne.CurrentApp().Settings().ThemeVariant()
|
return fyne.CurrentApp().Settings().ThemeVariant()
|
||||||
}
|
}
|
||||||
|
|
||||||
func blendColors(a, b color.Color, fractionA float64) color.Color {
|
func BlendColors(a, b color.Color, fractionA float64) color.Color {
|
||||||
ra, ga, ba, aa := a.RGBA()
|
ra, ga, ba, aa := a.RGBA()
|
||||||
rb, gb, bb, ab := b.RGBA()
|
rb, gb, bb, ab := b.RGBA()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user