From beef8fe1e0068b91ed0287eaea97a07af3ff35fa Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:45:31 +0300 Subject: [PATCH] refactor(tracker/gioui): bind tracker.Int to NumericUpDown on Layout --- tracker/gioui/instrument_editor.go | 10 +-- tracker/gioui/keybindings.go | 2 +- tracker/gioui/note_editor.go | 11 +-- tracker/gioui/numericupdown.go | 92 +++++++++----------- tracker/gioui/oscilloscope.go | 131 ++++++++++++++--------------- tracker/gioui/songpanel.go | 34 ++++---- tracker/gioui/theme.yml | 1 - tracker/gioui/tracker.go | 8 +- 8 files changed, 136 insertions(+), 153 deletions(-) diff --git a/tracker/gioui/instrument_editor.go b/tracker/gioui/instrument_editor.go index 7bedb9b..57c3541 100644 --- a/tracker/gioui/instrument_editor.go +++ b/tracker/gioui/instrument_editor.go @@ -144,9 +144,9 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D { octave := func(gtx C) D { in := layout.UniformInset(unit.Dp(1)) - numStyle := NumUpDown(t.Theme, t.OctaveNumberInput, ie.octaveHint) - dims := in.Layout(gtx, numStyle.Layout) - return dims + return in.Layout(gtx, func(gtx C) D { + return t.OctaveNumberInput.Layout(gtx, t.Octave(), t.Theme, &t.Theme.NumericUpDown, "Octave") + }) } newBtnStyle := ActionIcon(gtx, t.Theme, ie.newInstrumentBtn, icons.ContentAdd, ie.addInstrumentHint) @@ -231,9 +231,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D { 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 { - numStyle := NumUpDown(t.Theme, t.InstrumentVoices, "Number of voices for this instrument") - dims := numStyle.Layout(gtx) - return dims + return t.InstrumentVoices.Layout(gtx, t.Model.InstrumentVoices(), t.Theme, &t.Theme.NumericUpDown, "Number of voices for this instrument") }), layout.Rigid(splitInstrumentBtnStyle.Layout), layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }), diff --git a/tracker/gioui/keybindings.go b/tracker/gioui/keybindings.go index 30b6aec..9afdb81 100644 --- a/tracker/gioui/keybindings.go +++ b/tracker/gioui/keybindings.go @@ -293,7 +293,7 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) { break } instr := t.InstrumentEditor.instrumentDragList.TrackerList.Selected() - n := noteAsValue(t.OctaveNumberInput.Int.Value(), val-12) + n := noteAsValue(t.Model.Octave().Value(), val-12) t.KeyNoteMap.Press(e.Name, tracker.NoteEvent{Channel: instr, Note: n}) } } diff --git a/tracker/gioui/note_editor.go b/tracker/gioui/note_editor.go index 5254479..7ba62dc 100644 --- a/tracker/gioui/note_editor.go +++ b/tracker/gioui/note_editor.go @@ -51,7 +51,7 @@ func init() { } type NoteEditor struct { - TrackVoices *NumberInput + TrackVoices *NumericUpDown NewTrackBtn *ActionClickable DeleteTrackBtn *ActionClickable SplitTrackBtn *ActionClickable @@ -76,7 +76,7 @@ type NoteEditor struct { func NewNoteEditor(model *tracker.Model) *NoteEditor { ret := &NoteEditor{ - TrackVoices: NewNumberInput(model.TrackVoices()), + TrackVoices: NewNumericUpDown(), NewTrackBtn: NewActionClickable(model.AddTrack()), DeleteTrackBtn: NewActionClickable(model.DeleteTrack()), SplitTrackBtn: NewActionClickable(model.SplitTrack()), @@ -174,8 +174,9 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D { newTrackBtnStyle := ActionIcon(gtx, t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint) in := layout.UniformInset(unit.Dp(1)) voiceUpDown := func(gtx C) D { - numStyle := NumUpDown(t.Theme, te.TrackVoices, "Track voices") - return in.Layout(gtx, numStyle.Layout) + return in.Layout(gtx, func(gtx C) D { + return te.TrackVoices.Layout(gtx, t.Model.TrackVoices(), t.Theme, &t.Theme.NumericUpDown, "Track voices") + }) } effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex") uniqueBtnStyle := ToggleIcon(gtx, t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip) @@ -390,7 +391,7 @@ func (te *NoteEditor) command(t *Tracker, e key.Event) { if err != nil { return } - n = noteAsValue(t.OctaveNumberInput.Int.Value(), val-12) + n = noteAsValue(t.Octave().Value(), val-12) ev := t.Model.Notes().Input(n) t.KeyNoteMap.Press(e.Name, ev) } diff --git a/tracker/gioui/numericupdown.go b/tracker/gioui/numericupdown.go index 0dd7097..c068cc6 100644 --- a/tracker/gioui/numericupdown.go +++ b/tracker/gioui/numericupdown.go @@ -23,8 +23,9 @@ import ( "gioui.org/text" ) -type NumberInput struct { - Int tracker.Int +type NumericUpDown struct { + DpPerStep unit.Dp + dragStartValue int dragStartXY float32 clickDecrease gesture.Click @@ -41,36 +42,19 @@ type NumericUpDownStyle struct { Width unit.Dp Height unit.Dp TextSize unit.Sp - DpPerStep unit.Dp + Font font.Font } -type NumericUpDown struct { - NumberInput *NumberInput - Tooltip component.Tooltip - Theme *Theme - Font font.Font - NumericUpDownStyle +func NewNumericUpDown() *NumericUpDown { + return &NumericUpDown{DpPerStep: unit.Dp(8)} } -func NewNumberInput(v tracker.Int) *NumberInput { - return &NumberInput{Int: v} -} - -func NumUpDown(th *Theme, number *NumberInput, tooltip string) NumericUpDown { - return NumericUpDown{ - NumberInput: number, - Theme: th, - Tooltip: Tooltip(th, tooltip), - NumericUpDownStyle: th.NumericUpDown, - } -} - -func (s *NumericUpDown) Update(gtx layout.Context) { +func (s *NumericUpDown) Update(gtx layout.Context, v tracker.Int) { // handle dragging pxPerStep := float32(gtx.Dp(s.DpPerStep)) for { ev, ok := gtx.Event(pointer.Filter{ - Target: s.NumberInput, + Target: s, Kinds: pointer.Press | pointer.Drag | pointer.Release, }) if !ok { @@ -79,46 +63,54 @@ func (s *NumericUpDown) Update(gtx layout.Context) { if e, ok := ev.(pointer.Event); ok { switch e.Kind { case pointer.Press: - s.NumberInput.dragStartValue = s.NumberInput.Int.Value() - s.NumberInput.dragStartXY = e.Position.X - e.Position.Y + s.dragStartValue = v.Value() + s.dragStartXY = e.Position.X - e.Position.Y case pointer.Drag: var deltaCoord float32 - deltaCoord = e.Position.X - e.Position.Y - s.NumberInput.dragStartXY - s.NumberInput.Int.SetValue(s.NumberInput.dragStartValue + int(deltaCoord/pxPerStep+0.5)) + deltaCoord = e.Position.X - e.Position.Y - s.dragStartXY + v.SetValue(s.dragStartValue + int(deltaCoord/pxPerStep+0.5)) } } } // handle decrease clicks - for ev, ok := s.NumberInput.clickDecrease.Update(gtx.Source); ok; ev, ok = s.NumberInput.clickDecrease.Update(gtx.Source) { + for ev, ok := s.clickDecrease.Update(gtx.Source); ok; ev, ok = s.clickDecrease.Update(gtx.Source) { if ev.Kind == gesture.KindClick { - s.NumberInput.Int.Add(-1) + v.Add(-1) } } // handle increase clicks - for ev, ok := s.NumberInput.clickIncrease.Update(gtx.Source); ok; ev, ok = s.NumberInput.clickIncrease.Update(gtx.Source) { + for ev, ok := s.clickIncrease.Update(gtx.Source); ok; ev, ok = s.clickIncrease.Update(gtx.Source) { if ev.Kind == gesture.KindClick { - s.NumberInput.Int.Add(1) + v.Add(1) } } } -func (s NumericUpDown) Layout(gtx C) D { - if s.Tooltip.Text.Text != "" { - return s.NumberInput.tipArea.Layout(gtx, s.Tooltip, s.actualLayout) +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) } - return s.actualLayout(gtx) } -func (s *NumericUpDown) actualLayout(gtx C) D { - s.Update(gtx) - gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(s.Width), gtx.Dp(s.Height))) - width := gtx.Dp(s.ButtonWidth) - height := gtx.Dp(s.Height) +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) return layout.Background{}.Layout(gtx, func(gtx C) D { - defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(s.CornerRadius)).Push(gtx.Ops).Pop() - paint.Fill(gtx.Ops, s.BgColor) - event.Op(gtx.Ops, s.NumberInput) // register drag inputs, if not hitting the clicks + 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 return D{Size: gtx.Constraints.Min} }, func(gtx C) D { @@ -128,25 +120,25 @@ func (s *NumericUpDown) actualLayout(gtx C) D { return layout.Background{}.Layout(gtx, func(gtx C) D { defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop() - s.NumberInput.clickDecrease.Add(gtx.Ops) + s.clickDecrease.Add(gtx.Ops) return D{Size: gtx.Constraints.Min} }, - func(gtx C) D { return s.Theme.Icon(icons.ContentRemove).Layout(gtx, s.IconColor) }, + func(gtx C) D { return th.Icon(icons.ContentRemove).Layout(gtx, st.IconColor) }, ) }), layout.Flexed(1, func(gtx C) D { - paint.ColorOp{Color: s.TextColor}.Add(gtx.Ops) - return widget.Label{Alignment: text.Middle}.Layout(gtx, s.Theme.Material.Shaper, s.Font, s.TextSize, strconv.Itoa(s.NumberInput.Int.Value()), op.CallOp{}) + 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{}) }), 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.NumberInput.clickIncrease.Add(gtx.Ops) + s.clickIncrease.Add(gtx.Ops) return D{Size: gtx.Constraints.Min} }, - func(gtx C) D { return s.Theme.Icon(icons.ContentAdd).Layout(gtx, s.IconColor) }, + func(gtx C) D { return th.Icon(icons.ContentAdd).Layout(gtx, st.IconColor) }, ) }), ) diff --git a/tracker/gioui/oscilloscope.go b/tracker/gioui/oscilloscope.go index b3843e4..0fa3256 100644 --- a/tracker/gioui/oscilloscope.go +++ b/tracker/gioui/oscilloscope.go @@ -19,8 +19,8 @@ type ( OscilloscopeState struct { onceBtn *BoolClickable wrapBtn *BoolClickable - lengthInBeatsNumber *NumberInput - triggerChannelNumber *NumberInput + lengthInBeatsNumber *NumericUpDown + triggerChannelNumber *NumericUpDown xScale int xOffset float32 yScale float64 @@ -34,98 +34,89 @@ type ( LimitColor color.NRGBA `yaml:",flow"` CursorColor color.NRGBA `yaml:",flow"` } - - Oscilloscope struct { - State *OscilloscopeState - Wave tracker.RingBuffer[[2]float32] - Theme *Theme - OscilloscopeStyle - } ) func NewOscilloscope(model *tracker.Model) *OscilloscopeState { return &OscilloscopeState{ onceBtn: NewBoolClickable(model.SignalAnalyzer().Once()), wrapBtn: NewBoolClickable(model.SignalAnalyzer().Wrap()), - lengthInBeatsNumber: NewNumberInput(model.SignalAnalyzer().LengthInBeats()), - triggerChannelNumber: NewNumberInput(model.SignalAnalyzer().TriggerChannel()), + lengthInBeatsNumber: NewNumericUpDown(), + triggerChannelNumber: NewNumericUpDown(), } } -func Scope(s *OscilloscopeState, wave tracker.RingBuffer[[2]float32], th *Theme) *Oscilloscope { - return &Oscilloscope{State: s, Wave: wave, Theme: th} -} - -func (s *Oscilloscope) Layout(gtx C) D { - wrapBtnStyle := ToggleButton(gtx, s.Theme, s.State.wrapBtn, "Wrap") - onceBtnStyle := ToggleButton(gtx, s.Theme, s.State.onceBtn, "Once") - triggerChannelStyle := NumUpDown(s.Theme, s.State.triggerChannelNumber, "Trigger channel") - lengthNumberStyle := NumUpDown(s.Theme, s.State.lengthInBeatsNumber, "Buffer length in beats") +func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, wave tracker.RingBuffer[[2]float32], th *Theme, st *OscilloscopeStyle) D { + wrapBtnStyle := ToggleButton(gtx, th, s.wrapBtn, "Wrap") + onceBtnStyle := ToggleButton(gtx, th, s.onceBtn, "Once") leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Flexed(1, func(gtx C) D { return s.layoutWave(gtx) }), + layout.Flexed(1, func(gtx C) D { return s.layoutWave(gtx, wave, th) }), layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(leftSpacer), - layout.Rigid(Label(s.Theme, &s.Theme.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.Rigid(onceBtnStyle.Layout), - layout.Rigid(triggerChannelStyle.Layout), + layout.Rigid(func(gtx C) D { + return s.triggerChannelNumber.Layout(gtx, vtrig, th, &th.NumericUpDown, "Trigger channel") + }), layout.Rigid(rightSpacer), ) }), layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(leftSpacer), - layout.Rigid(Label(s.Theme, &s.Theme.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.Rigid(wrapBtnStyle.Layout), - layout.Rigid(lengthNumberStyle.Layout), + layout.Rigid(func(gtx C) D { + return s.lengthInBeatsNumber.Layout(gtx, vlen, th, &th.NumericUpDown, "Buffer length in beats") + }), layout.Rigid(rightSpacer), ) }), ) } -func (s *Oscilloscope) layoutWave(gtx C) D { - s.update(gtx) +func (s *OscilloscopeState) layoutWave(gtx C, wave tracker.RingBuffer[[2]float32], th *Theme) D { + s.update(gtx, wave) if gtx.Constraints.Max.X == 0 || gtx.Constraints.Max.Y == 0 { return D{} } defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop() - event.Op(gtx.Ops, s.State) - paint.ColorOp{Color: s.Theme.Oscilloscope.CursorColor}.Add(gtx.Ops) - cursorX := int(s.sampleToPx(gtx, float32(s.Wave.Cursor))) + event.Op(gtx.Ops, s) + paint.ColorOp{Color: th.Oscilloscope.CursorColor}.Add(gtx.Ops) + cursorX := int(s.sampleToPx(gtx, float32(wave.Cursor), wave)) fillRect(gtx, clip.Rect{Min: image.Pt(cursorX, 0), Max: image.Pt(cursorX+1, gtx.Constraints.Max.Y)}) - paint.ColorOp{Color: s.Theme.Oscilloscope.LimitColor}.Add(gtx.Ops) + paint.ColorOp{Color: th.Oscilloscope.LimitColor}.Add(gtx.Ops) minusOneY := int(s.ampToY(gtx, -1)) fillRect(gtx, clip.Rect{Min: image.Pt(0, minusOneY), Max: image.Pt(gtx.Constraints.Max.X, minusOneY+1)}) plusOneY := int(s.ampToY(gtx, 1)) fillRect(gtx, clip.Rect{Min: image.Pt(0, plusOneY), Max: image.Pt(gtx.Constraints.Max.X, plusOneY+1)}) - leftX := int(s.sampleToPx(gtx, 0)) + leftX := int(s.sampleToPx(gtx, 0, wave)) fillRect(gtx, clip.Rect{Min: image.Pt(leftX, 0), Max: image.Pt(leftX+1, gtx.Constraints.Max.Y)}) - rightX := int(s.sampleToPx(gtx, float32(len(s.Wave.Buffer)-1))) + rightX := int(s.sampleToPx(gtx, float32(len(wave.Buffer)-1), wave)) fillRect(gtx, clip.Rect{Min: image.Pt(rightX, 0), Max: image.Pt(rightX+1, gtx.Constraints.Max.Y)}) for chn := range 2 { - paint.ColorOp{Color: s.Theme.Oscilloscope.CurveColors[chn]}.Add(gtx.Ops) + paint.ColorOp{Color: th.Oscilloscope.CurveColors[chn]}.Add(gtx.Ops) for px := range gtx.Constraints.Max.X { // left and right is the sample range covered by the pixel - left := int(s.pxToSample(gtx, float32(px)-0.5)) - right := int(s.pxToSample(gtx, float32(px)+0.5)) - if right < 0 || left >= len(s.Wave.Buffer) { + left := int(s.pxToSample(gtx, float32(px)-0.5, wave)) + right := int(s.pxToSample(gtx, float32(px)+0.5, wave)) + if right < 0 || left >= len(wave.Buffer) { continue } - right = min(right, len(s.Wave.Buffer)-1) + right = min(right, len(wave.Buffer)-1) left = max(left, 0) // smin and smax are the smallest and largest sample values in the pixel range smax := float32(math.Inf(-1)) smin := float32(math.Inf(1)) for x := left; x <= right; x++ { - smax = max(smax, s.Wave.Buffer[x][chn]) - smin = min(smin, s.Wave.Buffer[x][chn]) + smax = max(smax, wave.Buffer[x][chn]) + smin = min(smin, wave.Buffer[x][chn]) } // y1 and y2 are the pixel range covered by the sample value y1 := min(max(int(s.ampToY(gtx, smax)+0.5), 0), gtx.Constraints.Max.Y-1) @@ -142,10 +133,10 @@ func fillRect(gtx C, rect clip.Rect) { stack.Pop() } -func (o *Oscilloscope) update(gtx C) { +func (o *OscilloscopeState) update(gtx C, wave tracker.RingBuffer[[2]float32]) { for { ev, ok := gtx.Event(pointer.Filter{ - Target: o.State, + Target: o, Kinds: pointer.Scroll | pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel, ScrollY: pointer.ScrollRange{Min: -1e6, Max: 1e6}, }) @@ -155,59 +146,59 @@ func (o *Oscilloscope) update(gtx C) { if e, ok := ev.(pointer.Event); ok { switch e.Kind { case pointer.Scroll: - s1 := o.pxToSample(gtx, e.Position.X) - o.State.xScale += min(max(-1, int(e.Scroll.Y)), 1) - s2 := o.pxToSample(gtx, e.Position.X) - o.State.xOffset -= s1 - s2 + s1 := o.pxToSample(gtx, e.Position.X, wave) + o.xScale += min(max(-1, int(e.Scroll.Y)), 1) + s2 := o.pxToSample(gtx, e.Position.X, wave) + o.xOffset -= s1 - s2 case pointer.Press: if e.Buttons&pointer.ButtonSecondary != 0 { - o.State.xOffset = 0 - o.State.xScale = 0 - o.State.yScale = 0 + o.xOffset = 0 + o.xScale = 0 + o.yScale = 0 } if e.Buttons&pointer.ButtonPrimary != 0 { - o.State.dragging = true - o.State.dragId = e.PointerID - o.State.dragStartPoint = e.Position + o.dragging = true + o.dragId = e.PointerID + o.dragStartPoint = e.Position } case pointer.Drag: - if e.Buttons&pointer.ButtonPrimary != 0 && o.State.dragging && e.PointerID == o.State.dragId { - deltaX := o.pxToSample(gtx, e.Position.X) - o.pxToSample(gtx, o.State.dragStartPoint.X) - o.State.xOffset += deltaX + if e.Buttons&pointer.ButtonPrimary != 0 && o.dragging && e.PointerID == o.dragId { + deltaX := o.pxToSample(gtx, e.Position.X, wave) - o.pxToSample(gtx, o.dragStartPoint.X, wave) + o.xOffset += deltaX num := o.yToAmp(gtx, e.Position.Y) - den := o.yToAmp(gtx, o.State.dragStartPoint.Y) + den := o.yToAmp(gtx, o.dragStartPoint.Y) if l := math.Abs(float64(num / den)); l > 1e-3 && l < 1e3 { - o.State.yScale += math.Log(l) - o.State.yScale = min(max(o.State.yScale, -1e3), 1e3) + o.yScale += math.Log(l) + o.yScale = min(max(o.yScale, -1e3), 1e3) } - o.State.dragStartPoint = e.Position + o.dragStartPoint = e.Position } case pointer.Release | pointer.Cancel: - o.State.dragging = false + o.dragging = false } } } } -func (o *Oscilloscope) scaleFactor() float32 { - return float32(math.Pow(1.1, float64(o.State.xScale))) +func (o *OscilloscopeState) scaleFactor() float32 { + return float32(math.Pow(1.1, float64(o.xScale))) } -func (s *Oscilloscope) pxToSample(gtx C, px float32) float32 { - return px*s.scaleFactor()*float32(len(s.Wave.Buffer))/float32(gtx.Constraints.Max.X) - s.State.xOffset +func (s *OscilloscopeState) pxToSample(gtx C, px float32, wave tracker.RingBuffer[[2]float32]) float32 { + return px*s.scaleFactor()*float32(len(wave.Buffer))/float32(gtx.Constraints.Max.X) - s.xOffset } -func (s *Oscilloscope) sampleToPx(gtx C, sample float32) float32 { - return (sample + s.State.xOffset) * float32(gtx.Constraints.Max.X) / float32(len(s.Wave.Buffer)) / s.scaleFactor() +func (s *OscilloscopeState) sampleToPx(gtx C, sample float32, wave tracker.RingBuffer[[2]float32]) float32 { + return (sample + s.xOffset) * float32(gtx.Constraints.Max.X) / float32(len(wave.Buffer)) / s.scaleFactor() } -func (s *Oscilloscope) ampToY(gtx C, amp float32) float32 { - scale := float32(math.Exp(s.State.yScale)) +func (s *OscilloscopeState) ampToY(gtx C, amp float32) float32 { + scale := float32(math.Exp(s.yScale)) return (1 - amp*scale) / 2 * float32(gtx.Constraints.Max.Y-1) } -func (s *Oscilloscope) yToAmp(gtx C, y float32) float32 { - scale := float32(math.Exp(s.State.yScale)) +func (s *OscilloscopeState) yToAmp(gtx C, y float32) float32 { + scale := float32(math.Exp(s.yScale)) return (1 - y/float32(gtx.Constraints.Max.Y-1)*2) / scale } diff --git a/tracker/gioui/songpanel.go b/tracker/gioui/songpanel.go index 29dc76f..a27e5fb 100644 --- a/tracker/gioui/songpanel.go +++ b/tracker/gioui/songpanel.go @@ -25,11 +25,11 @@ type SongPanel struct { WeightingTypeBtn *Clickable OversamplingBtn *Clickable - BPM *NumberInput - RowsPerPattern *NumberInput - RowsPerBeat *NumberInput - Step *NumberInput - SongLength *NumberInput + BPM *NumericUpDown + RowsPerPattern *NumericUpDown + RowsPerBeat *NumericUpDown + Step *NumericUpDown + SongLength *NumericUpDown Scope *OscilloscopeState @@ -39,11 +39,11 @@ type SongPanel struct { func NewSongPanel(model *tracker.Model) *SongPanel { ret := &SongPanel{ - BPM: NewNumberInput(model.BPM()), - RowsPerPattern: NewNumberInput(model.RowsPerPattern()), - RowsPerBeat: NewNumberInput(model.RowsPerBeat()), - Step: NewNumberInput(model.Step()), - SongLength: NewNumberInput(model.SongLength()), + BPM: NewNumericUpDown(), + RowsPerPattern: NewNumericUpDown(), + RowsPerBeat: NewNumericUpDown(), + Step: NewNumericUpDown(), + SongLength: NewNumericUpDown(), Scope: NewOscilloscope(model), MenuBar: NewMenuBar(model), PlayBar: NewPlayBar(model), @@ -115,19 +115,19 @@ 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", NumUpDown(tr.Theme, t.BPM, "BPM").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "BPM", t.BPM.Widget(tr.Model.BPM(), tr.Theme, &tr.Theme.NumericUpDown, "BPM")) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, tr.Theme, "Song length", NumUpDown(tr.Theme, t.SongLength, "Song Length").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "Song length", t.SongLength.Widget(tr.Model.SongLength(), tr.Theme, &tr.Theme.NumericUpDown, "Song length")) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, tr.Theme, "Rows per pat", NumUpDown(tr.Theme, t.RowsPerPattern, "Rows per pattern").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "Rows per pat", t.RowsPerPattern.Widget(tr.Model.RowsPerPattern(), tr.Theme, &tr.Theme.NumericUpDown, "Rows per pattern")) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, tr.Theme, "Rows per beat", NumUpDown(tr.Theme, t.RowsPerBeat, "Rows per beat").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "Rows per beat", t.RowsPerBeat.Widget(tr.Model.RowsPerBeat(), tr.Theme, &tr.Theme.NumericUpDown, "Rows per beat")) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, tr.Theme, "Cursor step", NumUpDown(tr.Theme, t.Step, "Cursor step").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "Cursor step", t.Step.Widget(tr.Model.Step(), tr.Theme, &tr.Theme.NumericUpDown, "Cursor step")) }), layout.Rigid(func(gtx C) D { cpuload := tr.Model.CPULoad() @@ -200,7 +200,9 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { ) }), layout.Flexed(1, func(gtx C) D { - return t.ScopeExpander.Layout(gtx, tr.Theme, "Oscilloscope", func(gtx C) D { return D{} }, Scope(t.Scope, tr.SignalAnalyzer().Waveform(), tr.Theme).Layout) + return t.ScopeExpander.Layout(gtx, tr.Theme, "Oscilloscope", func(gtx C) D { return D{} }, func(gtx C) D { + return t.Scope.Layout(gtx, tr.Model.SignalAnalyzer().TriggerChannel(), tr.Model.SignalAnalyzer().LengthInBeats(), tr.Model.SignalAnalyzer().Waveform(), tr.Theme, &tr.Theme.Oscilloscope) + }) }), layout.Rigid(Label(tr.Theme, &tr.Theme.SongPanel.Version, version.VersionOrHash).Layout), ) diff --git a/tracker/gioui/theme.yml b/tracker/gioui/theme.yml index 5e23d39..c47eb22 100644 --- a/tracker/gioui/theme.yml +++ b/tracker/gioui/theme.yml @@ -64,7 +64,6 @@ numericupdown: iconcolor: *primarycolor cornerradius: 4 buttonwidth: 16 - dpperstep: 8 textsize: 14 width: 70 height: 20 diff --git a/tracker/gioui/tracker.go b/tracker/gioui/tracker.go index 81797d4..1f58467 100644 --- a/tracker/gioui/tracker.go +++ b/tracker/gioui/tracker.go @@ -28,8 +28,8 @@ var canQuit = true // set to false in init() if plugin tag is enabled type ( Tracker struct { Theme *Theme - OctaveNumberInput *NumberInput - InstrumentVoices *NumberInput + OctaveNumberInput *NumericUpDown + InstrumentVoices *NumericUpDown 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: NewNumberInput(model.Octave()), - InstrumentVoices: NewNumberInput(model.InstrumentVoices()), + OctaveNumberInput: NewNumericUpDown(), + InstrumentVoices: NewNumericUpDown(), TopHorizontalSplit: &Split{Ratio: -.5, MinSize1: 180, MinSize2: 180}, BottomHorizontalSplit: &Split{Ratio: -.6, MinSize1: 180, MinSize2: 180},