refactor(tracker/gioui): avoid heap escapes in NumericUpDown

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-06-23 09:43:10 +03:00
parent db2ccf977d
commit 31007515b5
6 changed files with 103 additions and 90 deletions

View File

@ -142,9 +142,8 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
octave := func(gtx C) D { octave := func(gtx C) D {
in := layout.UniformInset(unit.Dp(1)) in := layout.UniformInset(unit.Dp(1))
return in.Layout(gtx, func(gtx C) D { octave := NumUpDown(t.Model.Octave(), t.Theme, t.OctaveNumberInput, "Octave")
return t.OctaveNumberInput.Layout(gtx, t.Octave(), t.Theme, &t.Theme.NumericUpDown, "Octave") return in.Layout(gtx, octave.Layout)
})
} }
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx, ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
@ -223,15 +222,14 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
loadInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.loadInstrumentBtn, icons.FileFolderOpen, "Load instrument") loadInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.loadInstrumentBtn, icons.FileFolderOpen, "Load instrument")
copyInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument") copyInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument")
deleteInstrumentBtn := ActionIconBtn(t.DeleteInstrument(), t.Theme, ie.deleteInstrumentBtn, icons.ActionDelete, ie.deleteInstrumentHint) deleteInstrumentBtn := ActionIconBtn(t.DeleteInstrument(), t.Theme, ie.deleteInstrumentBtn, icons.ActionDelete, ie.deleteInstrumentHint)
instrumentVoices := NumUpDown(t.Model.InstrumentVoices(), t.Theme, t.InstrumentVoices, "Number of voices for this instrument")
header := func(gtx C) D { header := func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(layout.Spacer{Width: 6}.Layout), layout.Rigid(layout.Spacer{Width: 6}.Layout),
layout.Rigid(Label(t.Theme, &t.Theme.InstrumentEditor.Voices, "Voices").Layout), layout.Rigid(Label(t.Theme, &t.Theme.InstrumentEditor.Voices, "Voices").Layout),
layout.Rigid(layout.Spacer{Width: 4}.Layout), layout.Rigid(layout.Spacer{Width: 4}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(instrumentVoices.Layout),
return t.InstrumentVoices.Layout(gtx, t.Model.InstrumentVoices(), t.Theme, &t.Theme.NumericUpDown, "Number of voices for this instrument")
}),
layout.Rigid(splitInstrumentBtn.Layout), layout.Rigid(splitInstrumentBtn.Layout),
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }), layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
layout.Rigid(commentExpandedBtn.Layout), layout.Rigid(commentExpandedBtn.Layout),

View File

@ -51,7 +51,7 @@ func init() {
} }
type NoteEditor struct { type NoteEditor struct {
TrackVoices *NumericUpDown TrackVoices *NumericUpDownState
NewTrackBtn *Clickable NewTrackBtn *Clickable
DeleteTrackBtn *Clickable DeleteTrackBtn *Clickable
SplitTrackBtn *Clickable SplitTrackBtn *Clickable
@ -76,7 +76,7 @@ type NoteEditor struct {
func NewNoteEditor(model *tracker.Model) *NoteEditor { func NewNoteEditor(model *tracker.Model) *NoteEditor {
ret := &NoteEditor{ ret := &NoteEditor{
TrackVoices: NewNumericUpDown(), TrackVoices: NewNumericUpDownState(),
NewTrackBtn: new(Clickable), NewTrackBtn: new(Clickable),
DeleteTrackBtn: new(Clickable), DeleteTrackBtn: new(Clickable),
SplitTrackBtn: new(Clickable), SplitTrackBtn: new(Clickable),
@ -172,11 +172,10 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
deleteTrackBtn := ActionIconBtn(t.DeleteTrack(), t.Theme, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint) deleteTrackBtn := ActionIconBtn(t.DeleteTrack(), t.Theme, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint)
splitTrackBtn := ActionIconBtn(t.SplitTrack(), t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint) splitTrackBtn := ActionIconBtn(t.SplitTrack(), t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint)
newTrackBtn := ActionIconBtn(t.AddTrack(), t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint) newTrackBtn := ActionIconBtn(t.AddTrack(), t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint)
trackVoices := NumUpDown(t.Model.TrackVoices(), t.Theme, te.TrackVoices, "Track voices")
in := layout.UniformInset(unit.Dp(1)) in := layout.UniformInset(unit.Dp(1))
voiceUpDown := func(gtx C) D { trackVoicesInsetted := func(gtx C) D {
return in.Layout(gtx, func(gtx C) D { return in.Layout(gtx, trackVoices.Layout)
return te.TrackVoices.Layout(gtx, t.Model.TrackVoices(), t.Theme, &t.Theme.NumericUpDown, "Track voices")
})
} }
effectBtn := ToggleBtn(t.Effect(), t.Theme, te.EffectBtn, "Hex", "Input notes as hex values") effectBtn := ToggleBtn(t.Effect(), t.Theme, te.EffectBtn, "Hex", "Input notes as hex values")
uniqueBtn := ToggleIconBtn(t.UniquePatterns(), t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip) uniqueBtn := ToggleIconBtn(t.UniquePatterns(), t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip)
@ -193,7 +192,7 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
layout.Rigid(layout.Spacer{Width: 10}.Layout), layout.Rigid(layout.Spacer{Width: 10}.Layout),
layout.Rigid(Label(t.Theme, &t.Theme.NoteEditor.Header, "Voices").Layout), layout.Rigid(Label(t.Theme, &t.Theme.NoteEditor.Header, "Voices").Layout),
layout.Rigid(layout.Spacer{Width: 4}.Layout), layout.Rigid(layout.Spacer{Width: 4}.Layout),
layout.Rigid(voiceUpDown), layout.Rigid(trackVoicesInsetted),
layout.Rigid(splitTrackBtn.Layout), layout.Rigid(splitTrackBtn.Layout),
layout.Rigid(midiInBtn.Layout), layout.Rigid(midiInBtn.Layout),
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }), layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),

View File

@ -23,7 +23,8 @@ import (
"gioui.org/text" "gioui.org/text"
) )
type NumericUpDown struct { type (
NumericUpDownState struct {
DpPerStep unit.Dp DpPerStep unit.Dp
dragStartValue int dragStartValue int
@ -31,9 +32,9 @@ type NumericUpDown struct {
clickDecrease gesture.Click clickDecrease gesture.Click
clickIncrease gesture.Click clickIncrease gesture.Click
tipArea component.TipArea tipArea component.TipArea
} }
type NumericUpDownStyle struct { NumericUpDownStyle struct {
TextColor color.NRGBA `yaml:",flow"` TextColor color.NRGBA `yaml:",flow"`
IconColor color.NRGBA `yaml:",flow"` IconColor color.NRGBA `yaml:",flow"`
BgColor color.NRGBA `yaml:",flow"` BgColor color.NRGBA `yaml:",flow"`
@ -43,13 +44,32 @@ type NumericUpDownStyle struct {
Height unit.Dp Height unit.Dp
TextSize unit.Sp TextSize unit.Sp
Font font.Font Font font.Font
}
NumericUpDown struct {
Int tracker.Int
Theme *Theme
State *NumericUpDownState
Style *NumericUpDownStyle
Tip string
}
)
func NewNumericUpDownState() *NumericUpDownState {
return &NumericUpDownState{DpPerStep: unit.Dp(8)}
} }
func NewNumericUpDown() *NumericUpDown { func NumUpDown(v tracker.Int, th *Theme, n *NumericUpDownState, tip string) NumericUpDown {
return &NumericUpDown{DpPerStep: unit.Dp(8)} return NumericUpDown{
Int: v,
Theme: th,
State: n,
Style: &th.NumericUpDown,
Tip: tip,
}
} }
func (s *NumericUpDown) Update(gtx layout.Context, v tracker.Int) { func (s *NumericUpDownState) Update(gtx layout.Context, v tracker.Int) {
// handle dragging // handle dragging
pxPerStep := float32(gtx.Dp(s.DpPerStep)) pxPerStep := float32(gtx.Dp(s.DpPerStep))
for { for {
@ -86,31 +106,23 @@ func (s *NumericUpDown) Update(gtx layout.Context, v tracker.Int) {
} }
} }
func (s *NumericUpDown) Widget(v tracker.Int, th *Theme, st *NumericUpDownStyle, tooltip string) func(gtx C) D { func (n *NumericUpDown) Layout(gtx C) D {
return func(gtx C) D { n.State.Update(gtx, n.Int)
return s.Layout(gtx, v, th, st, tooltip) if n.Tip != "" {
return n.State.tipArea.Layout(gtx, Tooltip(n.Theme, n.Tip), n.actualLayout)
} }
return n.actualLayout(gtx)
} }
func (s *NumericUpDown) Layout(gtx C, v tracker.Int, th *Theme, st *NumericUpDownStyle, tooltip string) D { func (n *NumericUpDown) actualLayout(gtx C) D {
s.Update(gtx, v) gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(n.Style.Width), gtx.Dp(n.Style.Height)))
if tooltip != "" { width := gtx.Dp(n.Style.ButtonWidth)
return s.tipArea.Layout(gtx, Tooltip(th, tooltip), func(gtx C) D { height := gtx.Dp(n.Style.Height)
return s.actualLayout(gtx, v, th, st)
})
}
return s.actualLayout(gtx, v, th, st)
}
func (s *NumericUpDown) actualLayout(gtx C, v tracker.Int, th *Theme, st *NumericUpDownStyle) D {
gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(st.Width), gtx.Dp(st.Height)))
width := gtx.Dp(st.ButtonWidth)
height := gtx.Dp(st.Height)
return layout.Background{}.Layout(gtx, return layout.Background{}.Layout(gtx,
func(gtx C) D { func(gtx C) D {
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(st.CornerRadius)).Push(gtx.Ops).Pop() defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(n.Style.CornerRadius)).Push(gtx.Ops).Pop()
paint.Fill(gtx.Ops, st.BgColor) paint.Fill(gtx.Ops, n.Style.BgColor)
event.Op(gtx.Ops, s) // register drag inputs, if not hitting the clicks event.Op(gtx.Ops, n.State) // register drag inputs, if not hitting the clicks
return D{Size: gtx.Constraints.Min} return D{Size: gtx.Constraints.Min}
}, },
func(gtx C) D { func(gtx C) D {
@ -120,25 +132,25 @@ func (s *NumericUpDown) actualLayout(gtx C, v tracker.Int, th *Theme, st *Numeri
return layout.Background{}.Layout(gtx, return layout.Background{}.Layout(gtx,
func(gtx C) D { func(gtx C) D {
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
s.clickDecrease.Add(gtx.Ops) n.State.clickDecrease.Add(gtx.Ops)
return D{Size: gtx.Constraints.Min} return D{Size: gtx.Constraints.Min}
}, },
func(gtx C) D { return th.Icon(icons.ContentRemove).Layout(gtx, st.IconColor) }, func(gtx C) D { return n.Theme.Icon(icons.ContentRemove).Layout(gtx, n.Style.IconColor) },
) )
}), }),
layout.Flexed(1, func(gtx C) D { layout.Flexed(1, func(gtx C) D {
paint.ColorOp{Color: st.TextColor}.Add(gtx.Ops) paint.ColorOp{Color: n.Style.TextColor}.Add(gtx.Ops)
return widget.Label{Alignment: text.Middle}.Layout(gtx, th.Material.Shaper, st.Font, st.TextSize, strconv.Itoa(v.Value()), op.CallOp{}) return widget.Label{Alignment: text.Middle}.Layout(gtx, n.Theme.Material.Shaper, n.Style.Font, n.Style.TextSize, strconv.Itoa(n.Int.Value()), op.CallOp{})
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
gtx.Constraints = layout.Exact(image.Pt(width, height)) gtx.Constraints = layout.Exact(image.Pt(width, height))
return layout.Background{}.Layout(gtx, return layout.Background{}.Layout(gtx,
func(gtx C) D { func(gtx C) D {
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
s.clickIncrease.Add(gtx.Ops) n.State.clickIncrease.Add(gtx.Ops)
return D{Size: gtx.Constraints.Min} return D{Size: gtx.Constraints.Min}
}, },
func(gtx C) D { return th.Icon(icons.ContentAdd).Layout(gtx, st.IconColor) }, func(gtx C) D { return n.Theme.Icon(icons.ContentAdd).Layout(gtx, n.Style.IconColor) },
) )
}), }),
) )

View File

@ -19,8 +19,8 @@ type (
OscilloscopeState struct { OscilloscopeState struct {
onceBtn *Clickable onceBtn *Clickable
wrapBtn *Clickable wrapBtn *Clickable
lengthInBeatsNumber *NumericUpDown lengthInBeatsNumber *NumericUpDownState
triggerChannelNumber *NumericUpDown triggerChannelNumber *NumericUpDownState
xScale int xScale int
xOffset float32 xOffset float32
yScale float64 yScale float64
@ -40,8 +40,8 @@ func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
return &OscilloscopeState{ return &OscilloscopeState{
onceBtn: new(Clickable), onceBtn: new(Clickable),
wrapBtn: new(Clickable), wrapBtn: new(Clickable),
lengthInBeatsNumber: NewNumericUpDown(), lengthInBeatsNumber: NewNumericUpDownState(),
triggerChannelNumber: NewNumericUpDown(), triggerChannelNumber: NewNumericUpDownState(),
} }
} }
@ -49,6 +49,9 @@ func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, once, wrap tr
leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
triggerChannel := NumUpDown(vtrig, th, s.triggerChannelNumber, "Trigger channel")
lengthInBeats := NumUpDown(vlen, th, s.lengthInBeatsNumber, "Buffer length in beats")
onceBtn := ToggleBtn(once, th, s.onceBtn, "Once", "Trigger once on next event") onceBtn := ToggleBtn(once, th, s.onceBtn, "Once", "Trigger once on next event")
wrapBtn := ToggleBtn(wrap, th, s.wrapBtn, "Wrap", "Wrap buffer when full") wrapBtn := ToggleBtn(wrap, th, s.wrapBtn, "Wrap", "Wrap buffer when full")
@ -60,9 +63,7 @@ func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, once, wrap tr
layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Trigger").Layout), layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Trigger").Layout),
layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }), layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
layout.Rigid(onceBtn.Layout), layout.Rigid(onceBtn.Layout),
layout.Rigid(func(gtx C) D { layout.Rigid(triggerChannel.Layout),
return s.triggerChannelNumber.Layout(gtx, vtrig, th, &th.NumericUpDown, "Trigger channel")
}),
layout.Rigid(rightSpacer), layout.Rigid(rightSpacer),
) )
}), }),
@ -72,9 +73,7 @@ func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, once, wrap tr
layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Buffer").Layout), layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Buffer").Layout),
layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }), layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
layout.Rigid(wrapBtn.Layout), layout.Rigid(wrapBtn.Layout),
layout.Rigid(func(gtx C) D { layout.Rigid(lengthInBeats.Layout),
return s.lengthInBeatsNumber.Layout(gtx, vlen, th, &th.NumericUpDown, "Buffer length in beats")
}),
layout.Rigid(rightSpacer), layout.Rigid(rightSpacer),
) )
}), }),

View File

@ -25,11 +25,11 @@ type SongPanel struct {
WeightingTypeBtn *Clickable WeightingTypeBtn *Clickable
OversamplingBtn *Clickable OversamplingBtn *Clickable
BPM *NumericUpDown BPM *NumericUpDownState
RowsPerPattern *NumericUpDown RowsPerPattern *NumericUpDownState
RowsPerBeat *NumericUpDown RowsPerBeat *NumericUpDownState
Step *NumericUpDown Step *NumericUpDownState
SongLength *NumericUpDown SongLength *NumericUpDownState
Scope *OscilloscopeState Scope *OscilloscopeState
@ -39,11 +39,11 @@ type SongPanel struct {
func NewSongPanel(model *tracker.Model) *SongPanel { func NewSongPanel(model *tracker.Model) *SongPanel {
ret := &SongPanel{ ret := &SongPanel{
BPM: NewNumericUpDown(), BPM: NewNumericUpDownState(),
RowsPerPattern: NewNumericUpDown(), RowsPerPattern: NewNumericUpDownState(),
RowsPerBeat: NewNumericUpDown(), RowsPerBeat: NewNumericUpDownState(),
Step: NewNumericUpDown(), Step: NewNumericUpDownState(),
SongLength: NewNumericUpDown(), SongLength: NewNumericUpDownState(),
Scope: NewOscilloscope(model), Scope: NewOscilloscope(model),
MenuBar: NewMenuBar(model), MenuBar: NewMenuBar(model),
PlayBar: NewPlayBar(), PlayBar: NewPlayBar(),
@ -115,19 +115,24 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D {
func(gtx C) D { func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "BPM", t.BPM.Widget(tr.Model.BPM(), tr.Theme, &tr.Theme.NumericUpDown, "BPM")) bpm := NumUpDown(tr.BPM(), tr.Theme, t.BPM, "BPM")
return layoutSongOptionRow(gtx, tr.Theme, "BPM", bpm.Layout)
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Song length", t.SongLength.Widget(tr.Model.SongLength(), tr.Theme, &tr.Theme.NumericUpDown, "Song length")) songLength := NumUpDown(tr.SongLength(), tr.Theme, t.SongLength, "Song length")
return layoutSongOptionRow(gtx, tr.Theme, "Song length", songLength.Layout)
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Rows per pat", t.RowsPerPattern.Widget(tr.Model.RowsPerPattern(), tr.Theme, &tr.Theme.NumericUpDown, "Rows per pattern")) rowsPerPattern := NumUpDown(tr.RowsPerPattern(), tr.Theme, t.RowsPerPattern, "Rows per pattern")
return layoutSongOptionRow(gtx, tr.Theme, "Rows per pat", rowsPerPattern.Layout)
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Rows per beat", t.RowsPerBeat.Widget(tr.Model.RowsPerBeat(), tr.Theme, &tr.Theme.NumericUpDown, "Rows per beat")) rowsPerBeat := NumUpDown(tr.RowsPerBeat(), tr.Theme, t.RowsPerBeat, "Rows per beat")
return layoutSongOptionRow(gtx, tr.Theme, "Rows per beat", rowsPerBeat.Layout)
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Cursor step", t.Step.Widget(tr.Model.Step(), tr.Theme, &tr.Theme.NumericUpDown, "Cursor step")) step := NumUpDown(tr.Step(), tr.Theme, t.Step, "Cursor step")
return layoutSongOptionRow(gtx, tr.Theme, "Cursor step", step.Layout)
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
cpuload := tr.Model.CPULoad() cpuload := tr.Model.CPULoad()

View File

@ -28,8 +28,8 @@ var canQuit = true // set to false in init() if plugin tag is enabled
type ( type (
Tracker struct { Tracker struct {
Theme *Theme Theme *Theme
OctaveNumberInput *NumericUpDown OctaveNumberInput *NumericUpDownState
InstrumentVoices *NumericUpDown InstrumentVoices *NumericUpDownState
TopHorizontalSplit *Split TopHorizontalSplit *Split
BottomHorizontalSplit *Split BottomHorizontalSplit *Split
VerticalSplit *Split VerticalSplit *Split
@ -70,8 +70,8 @@ var ZoomFactors = []float32{.25, 1. / 3, .5, 2. / 3, .75, .8, 1, 1.1, 1.25, 1.5,
func NewTracker(model *tracker.Model) *Tracker { func NewTracker(model *tracker.Model) *Tracker {
t := &Tracker{ t := &Tracker{
OctaveNumberInput: NewNumericUpDown(), OctaveNumberInput: NewNumericUpDownState(),
InstrumentVoices: NewNumericUpDown(), InstrumentVoices: NewNumericUpDownState(),
TopHorizontalSplit: &Split{Ratio: -.5, MinSize1: 180, MinSize2: 180}, TopHorizontalSplit: &Split{Ratio: -.5, MinSize1: 180, MinSize2: 180},
BottomHorizontalSplit: &Split{Ratio: -.6, MinSize1: 180, MinSize2: 180}, BottomHorizontalSplit: &Split{Ratio: -.6, MinSize1: 180, MinSize2: 180},