refactor(tracker/gioui): bind tracker.Int to NumericUpDown on Layout

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-06-21 11:45:31 +03:00
parent 289bfb0605
commit beef8fe1e0
8 changed files with 136 additions and 153 deletions

View File

@ -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} }),

View File

@ -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})
}
}

View File

@ -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)
}

View File

@ -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) },
)
}),
)

View File

@ -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
}

View File

@ -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),
)

View File

@ -64,7 +64,6 @@ numericupdown:
iconcolor: *primarycolor
cornerradius: 4
buttonwidth: 16
dpperstep: 8
textsize: 14
width: 70
height: 20

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 *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},