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 {
in := layout.UniformInset(unit.Dp(1))
return in.Layout(gtx, func(gtx C) D {
return t.OctaveNumberInput.Layout(gtx, t.Octave(), t.Theme, &t.Theme.NumericUpDown, "Octave")
})
octave := NumUpDown(t.Model.Octave(), t.Theme, t.OctaveNumberInput, "Octave")
return in.Layout(gtx, octave.Layout)
}
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")
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)
instrumentVoices := NumUpDown(t.Model.InstrumentVoices(), t.Theme, t.InstrumentVoices, "Number of voices for this instrument")
header := func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(layout.Spacer{Width: 6}.Layout),
layout.Rigid(Label(t.Theme, &t.Theme.InstrumentEditor.Voices, "Voices").Layout),
layout.Rigid(layout.Spacer{Width: 4}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return t.InstrumentVoices.Layout(gtx, t.Model.InstrumentVoices(), t.Theme, &t.Theme.NumericUpDown, "Number of voices for this instrument")
}),
layout.Rigid(instrumentVoices.Layout),
layout.Rigid(splitInstrumentBtn.Layout),
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
layout.Rigid(commentExpandedBtn.Layout),

View File

@ -51,7 +51,7 @@ func init() {
}
type NoteEditor struct {
TrackVoices *NumericUpDown
TrackVoices *NumericUpDownState
NewTrackBtn *Clickable
DeleteTrackBtn *Clickable
SplitTrackBtn *Clickable
@ -76,7 +76,7 @@ type NoteEditor struct {
func NewNoteEditor(model *tracker.Model) *NoteEditor {
ret := &NoteEditor{
TrackVoices: NewNumericUpDown(),
TrackVoices: NewNumericUpDownState(),
NewTrackBtn: new(Clickable),
DeleteTrackBtn: 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)
splitTrackBtn := ActionIconBtn(t.SplitTrack(), t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint)
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))
voiceUpDown := func(gtx C) D {
return in.Layout(gtx, func(gtx C) D {
return te.TrackVoices.Layout(gtx, t.Model.TrackVoices(), t.Theme, &t.Theme.NumericUpDown, "Track voices")
})
trackVoicesInsetted := func(gtx C) D {
return in.Layout(gtx, trackVoices.Layout)
}
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)
@ -193,7 +192,7 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
layout.Rigid(layout.Spacer{Width: 10}.Layout),
layout.Rigid(Label(t.Theme, &t.Theme.NoteEditor.Header, "Voices").Layout),
layout.Rigid(layout.Spacer{Width: 4}.Layout),
layout.Rigid(voiceUpDown),
layout.Rigid(trackVoicesInsetted),
layout.Rigid(splitTrackBtn.Layout),
layout.Rigid(midiInBtn.Layout),
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),

View File

@ -23,33 +23,53 @@ import (
"gioui.org/text"
)
type NumericUpDown struct {
DpPerStep unit.Dp
type (
NumericUpDownState struct {
DpPerStep unit.Dp
dragStartValue int
dragStartXY float32
clickDecrease gesture.Click
clickIncrease gesture.Click
tipArea component.TipArea
dragStartValue int
dragStartXY float32
clickDecrease gesture.Click
clickIncrease gesture.Click
tipArea component.TipArea
}
NumericUpDownStyle struct {
TextColor color.NRGBA `yaml:",flow"`
IconColor color.NRGBA `yaml:",flow"`
BgColor color.NRGBA `yaml:",flow"`
CornerRadius unit.Dp
ButtonWidth unit.Dp
Width unit.Dp
Height unit.Dp
TextSize unit.Sp
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)}
}
type NumericUpDownStyle struct {
TextColor color.NRGBA `yaml:",flow"`
IconColor color.NRGBA `yaml:",flow"`
BgColor color.NRGBA `yaml:",flow"`
CornerRadius unit.Dp
ButtonWidth unit.Dp
Width unit.Dp
Height unit.Dp
TextSize unit.Sp
Font font.Font
func NumUpDown(v tracker.Int, th *Theme, n *NumericUpDownState, tip string) NumericUpDown {
return NumericUpDown{
Int: v,
Theme: th,
State: n,
Style: &th.NumericUpDown,
Tip: tip,
}
}
func NewNumericUpDown() *NumericUpDown {
return &NumericUpDown{DpPerStep: unit.Dp(8)}
}
func (s *NumericUpDown) Update(gtx layout.Context, v tracker.Int) {
func (s *NumericUpDownState) Update(gtx layout.Context, v tracker.Int) {
// handle dragging
pxPerStep := float32(gtx.Dp(s.DpPerStep))
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 {
return func(gtx C) D {
return s.Layout(gtx, v, th, st, tooltip)
func (n *NumericUpDown) Layout(gtx C) D {
n.State.Update(gtx, n.Int)
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 {
s.Update(gtx, v)
if tooltip != "" {
return s.tipArea.Layout(gtx, Tooltip(th, tooltip), func(gtx C) D {
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)
func (n *NumericUpDown) actualLayout(gtx C) D {
gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(n.Style.Width), gtx.Dp(n.Style.Height)))
width := gtx.Dp(n.Style.ButtonWidth)
height := gtx.Dp(n.Style.Height)
return layout.Background{}.Layout(gtx,
func(gtx C) D {
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(st.CornerRadius)).Push(gtx.Ops).Pop()
paint.Fill(gtx.Ops, st.BgColor)
event.Op(gtx.Ops, s) // register drag inputs, if not hitting the clicks
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(n.Style.CornerRadius)).Push(gtx.Ops).Pop()
paint.Fill(gtx.Ops, n.Style.BgColor)
event.Op(gtx.Ops, n.State) // register drag inputs, if not hitting the clicks
return D{Size: gtx.Constraints.Min}
},
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,
func(gtx C) D {
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}
},
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 {
paint.ColorOp{Color: st.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{})
paint.ColorOp{Color: n.Style.TextColor}.Add(gtx.Ops)
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 {
gtx.Constraints = layout.Exact(image.Pt(width, height))
return layout.Background{}.Layout(gtx,
func(gtx C) D {
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}
},
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 {
onceBtn *Clickable
wrapBtn *Clickable
lengthInBeatsNumber *NumericUpDown
triggerChannelNumber *NumericUpDown
lengthInBeatsNumber *NumericUpDownState
triggerChannelNumber *NumericUpDownState
xScale int
xOffset float32
yScale float64
@ -40,8 +40,8 @@ func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
return &OscilloscopeState{
onceBtn: new(Clickable),
wrapBtn: new(Clickable),
lengthInBeatsNumber: NewNumericUpDown(),
triggerChannelNumber: NewNumericUpDown(),
lengthInBeatsNumber: NewNumericUpDownState(),
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
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")
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.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
layout.Rigid(onceBtn.Layout),
layout.Rigid(func(gtx C) D {
return s.triggerChannelNumber.Layout(gtx, vtrig, th, &th.NumericUpDown, "Trigger channel")
}),
layout.Rigid(triggerChannel.Layout),
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.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
layout.Rigid(wrapBtn.Layout),
layout.Rigid(func(gtx C) D {
return s.lengthInBeatsNumber.Layout(gtx, vlen, th, &th.NumericUpDown, "Buffer length in beats")
}),
layout.Rigid(lengthInBeats.Layout),
layout.Rigid(rightSpacer),
)
}),

View File

@ -25,11 +25,11 @@ type SongPanel struct {
WeightingTypeBtn *Clickable
OversamplingBtn *Clickable
BPM *NumericUpDown
RowsPerPattern *NumericUpDown
RowsPerBeat *NumericUpDown
Step *NumericUpDown
SongLength *NumericUpDown
BPM *NumericUpDownState
RowsPerPattern *NumericUpDownState
RowsPerBeat *NumericUpDownState
Step *NumericUpDownState
SongLength *NumericUpDownState
Scope *OscilloscopeState
@ -39,11 +39,11 @@ type SongPanel struct {
func NewSongPanel(model *tracker.Model) *SongPanel {
ret := &SongPanel{
BPM: NewNumericUpDown(),
RowsPerPattern: NewNumericUpDown(),
RowsPerBeat: NewNumericUpDown(),
Step: NewNumericUpDown(),
SongLength: NewNumericUpDown(),
BPM: NewNumericUpDownState(),
RowsPerPattern: NewNumericUpDownState(),
RowsPerBeat: NewNumericUpDownState(),
Step: NewNumericUpDownState(),
SongLength: NewNumericUpDownState(),
Scope: NewOscilloscope(model),
MenuBar: NewMenuBar(model),
PlayBar: NewPlayBar(),
@ -115,19 +115,24 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D {
func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
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 {
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 {
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 {
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 {
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 {
cpuload := tr.Model.CPULoad()

View File

@ -28,8 +28,8 @@ var canQuit = true // set to false in init() if plugin tag is enabled
type (
Tracker struct {
Theme *Theme
OctaveNumberInput *NumericUpDown
InstrumentVoices *NumericUpDown
OctaveNumberInput *NumericUpDownState
InstrumentVoices *NumericUpDownState
TopHorizontalSplit *Split
BottomHorizontalSplit *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 {
t := &Tracker{
OctaveNumberInput: NewNumericUpDown(),
InstrumentVoices: NewNumericUpDown(),
OctaveNumberInput: NewNumericUpDownState(),
InstrumentVoices: NewNumericUpDownState(),
TopHorizontalSplit: &Split{Ratio: -.5, MinSize1: 180, MinSize2: 180},
BottomHorizontalSplit: &Split{Ratio: -.6, MinSize1: 180, MinSize2: 180},