mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-19 21:44:38 -04:00
refactor(tracker/gioui): bind tracker.Int to NumericUpDown on Layout
This commit is contained in:
parent
289bfb0605
commit
beef8fe1e0
@ -144,9 +144,9 @@ 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))
|
||||||
numStyle := NumUpDown(t.Theme, t.OctaveNumberInput, ie.octaveHint)
|
return in.Layout(gtx, func(gtx C) D {
|
||||||
dims := in.Layout(gtx, numStyle.Layout)
|
return t.OctaveNumberInput.Layout(gtx, t.Octave(), t.Theme, &t.Theme.NumericUpDown, "Octave")
|
||||||
return dims
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
newBtnStyle := ActionIcon(gtx, t.Theme, ie.newInstrumentBtn, icons.ContentAdd, ie.addInstrumentHint)
|
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(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(func(gtx layout.Context) layout.Dimensions {
|
||||||
numStyle := NumUpDown(t.Theme, t.InstrumentVoices, "Number of voices for this instrument")
|
return t.InstrumentVoices.Layout(gtx, t.Model.InstrumentVoices(), t.Theme, &t.Theme.NumericUpDown, "Number of voices for this instrument")
|
||||||
dims := numStyle.Layout(gtx)
|
|
||||||
return dims
|
|
||||||
}),
|
}),
|
||||||
layout.Rigid(splitInstrumentBtnStyle.Layout),
|
layout.Rigid(splitInstrumentBtnStyle.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} }),
|
||||||
|
@ -293,7 +293,7 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
instr := t.InstrumentEditor.instrumentDragList.TrackerList.Selected()
|
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})
|
t.KeyNoteMap.Press(e.Name, tracker.NoteEvent{Channel: instr, Note: n})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NoteEditor struct {
|
type NoteEditor struct {
|
||||||
TrackVoices *NumberInput
|
TrackVoices *NumericUpDown
|
||||||
NewTrackBtn *ActionClickable
|
NewTrackBtn *ActionClickable
|
||||||
DeleteTrackBtn *ActionClickable
|
DeleteTrackBtn *ActionClickable
|
||||||
SplitTrackBtn *ActionClickable
|
SplitTrackBtn *ActionClickable
|
||||||
@ -76,7 +76,7 @@ type NoteEditor struct {
|
|||||||
|
|
||||||
func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
||||||
ret := &NoteEditor{
|
ret := &NoteEditor{
|
||||||
TrackVoices: NewNumberInput(model.TrackVoices()),
|
TrackVoices: NewNumericUpDown(),
|
||||||
NewTrackBtn: NewActionClickable(model.AddTrack()),
|
NewTrackBtn: NewActionClickable(model.AddTrack()),
|
||||||
DeleteTrackBtn: NewActionClickable(model.DeleteTrack()),
|
DeleteTrackBtn: NewActionClickable(model.DeleteTrack()),
|
||||||
SplitTrackBtn: NewActionClickable(model.SplitTrack()),
|
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)
|
newTrackBtnStyle := ActionIcon(gtx, t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint)
|
||||||
in := layout.UniformInset(unit.Dp(1))
|
in := layout.UniformInset(unit.Dp(1))
|
||||||
voiceUpDown := func(gtx C) D {
|
voiceUpDown := func(gtx C) D {
|
||||||
numStyle := NumUpDown(t.Theme, te.TrackVoices, "Track voices")
|
return in.Layout(gtx, func(gtx C) D {
|
||||||
return in.Layout(gtx, numStyle.Layout)
|
return te.TrackVoices.Layout(gtx, t.Model.TrackVoices(), t.Theme, &t.Theme.NumericUpDown, "Track voices")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex")
|
effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex")
|
||||||
uniqueBtnStyle := ToggleIcon(gtx, t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n = noteAsValue(t.OctaveNumberInput.Int.Value(), val-12)
|
n = noteAsValue(t.Octave().Value(), val-12)
|
||||||
ev := t.Model.Notes().Input(n)
|
ev := t.Model.Notes().Input(n)
|
||||||
t.KeyNoteMap.Press(e.Name, ev)
|
t.KeyNoteMap.Press(e.Name, ev)
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,9 @@ import (
|
|||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NumberInput struct {
|
type NumericUpDown struct {
|
||||||
Int tracker.Int
|
DpPerStep unit.Dp
|
||||||
|
|
||||||
dragStartValue int
|
dragStartValue int
|
||||||
dragStartXY float32
|
dragStartXY float32
|
||||||
clickDecrease gesture.Click
|
clickDecrease gesture.Click
|
||||||
@ -41,36 +42,19 @@ type NumericUpDownStyle struct {
|
|||||||
Width unit.Dp
|
Width unit.Dp
|
||||||
Height unit.Dp
|
Height unit.Dp
|
||||||
TextSize unit.Sp
|
TextSize unit.Sp
|
||||||
DpPerStep unit.Dp
|
Font font.Font
|
||||||
}
|
}
|
||||||
|
|
||||||
type NumericUpDown struct {
|
func NewNumericUpDown() *NumericUpDown {
|
||||||
NumberInput *NumberInput
|
return &NumericUpDown{DpPerStep: unit.Dp(8)}
|
||||||
Tooltip component.Tooltip
|
|
||||||
Theme *Theme
|
|
||||||
Font font.Font
|
|
||||||
NumericUpDownStyle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNumberInput(v tracker.Int) *NumberInput {
|
func (s *NumericUpDown) Update(gtx layout.Context, v tracker.Int) {
|
||||||
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) {
|
|
||||||
// handle dragging
|
// handle dragging
|
||||||
pxPerStep := float32(gtx.Dp(s.DpPerStep))
|
pxPerStep := float32(gtx.Dp(s.DpPerStep))
|
||||||
for {
|
for {
|
||||||
ev, ok := gtx.Event(pointer.Filter{
|
ev, ok := gtx.Event(pointer.Filter{
|
||||||
Target: s.NumberInput,
|
Target: s,
|
||||||
Kinds: pointer.Press | pointer.Drag | pointer.Release,
|
Kinds: pointer.Press | pointer.Drag | pointer.Release,
|
||||||
})
|
})
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -79,46 +63,54 @@ func (s *NumericUpDown) Update(gtx layout.Context) {
|
|||||||
if e, ok := ev.(pointer.Event); ok {
|
if e, ok := ev.(pointer.Event); ok {
|
||||||
switch e.Kind {
|
switch e.Kind {
|
||||||
case pointer.Press:
|
case pointer.Press:
|
||||||
s.NumberInput.dragStartValue = s.NumberInput.Int.Value()
|
s.dragStartValue = v.Value()
|
||||||
s.NumberInput.dragStartXY = e.Position.X - e.Position.Y
|
s.dragStartXY = e.Position.X - e.Position.Y
|
||||||
case pointer.Drag:
|
case pointer.Drag:
|
||||||
var deltaCoord float32
|
var deltaCoord float32
|
||||||
deltaCoord = e.Position.X - e.Position.Y - s.NumberInput.dragStartXY
|
deltaCoord = e.Position.X - e.Position.Y - s.dragStartXY
|
||||||
s.NumberInput.Int.SetValue(s.NumberInput.dragStartValue + int(deltaCoord/pxPerStep+0.5))
|
v.SetValue(s.dragStartValue + int(deltaCoord/pxPerStep+0.5))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// handle decrease clicks
|
// 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 {
|
if ev.Kind == gesture.KindClick {
|
||||||
s.NumberInput.Int.Add(-1)
|
v.Add(-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// handle increase clicks
|
// 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 {
|
if ev.Kind == gesture.KindClick {
|
||||||
s.NumberInput.Int.Add(1)
|
v.Add(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s NumericUpDown) Layout(gtx C) D {
|
func (s *NumericUpDown) Widget(v tracker.Int, th *Theme, st *NumericUpDownStyle, tooltip string) func(gtx C) D {
|
||||||
if s.Tooltip.Text.Text != "" {
|
return func(gtx C) D {
|
||||||
return s.NumberInput.tipArea.Layout(gtx, s.Tooltip, s.actualLayout)
|
return s.Layout(gtx, v, th, st, tooltip)
|
||||||
}
|
}
|
||||||
return s.actualLayout(gtx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *NumericUpDown) actualLayout(gtx C) D {
|
func (s *NumericUpDown) Layout(gtx C, v tracker.Int, th *Theme, st *NumericUpDownStyle, tooltip string) D {
|
||||||
s.Update(gtx)
|
s.Update(gtx, v)
|
||||||
gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(s.Width), gtx.Dp(s.Height)))
|
if tooltip != "" {
|
||||||
width := gtx.Dp(s.ButtonWidth)
|
return s.tipArea.Layout(gtx, Tooltip(th, tooltip), func(gtx C) D {
|
||||||
height := gtx.Dp(s.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(s.CornerRadius)).Push(gtx.Ops).Pop()
|
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(st.CornerRadius)).Push(gtx.Ops).Pop()
|
||||||
paint.Fill(gtx.Ops, s.BgColor)
|
paint.Fill(gtx.Ops, st.BgColor)
|
||||||
event.Op(gtx.Ops, s.NumberInput) // register drag inputs, if not hitting the clicks
|
event.Op(gtx.Ops, s) // 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 {
|
||||||
@ -128,25 +120,25 @@ func (s *NumericUpDown) actualLayout(gtx C) D {
|
|||||||
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.NumberInput.clickDecrease.Add(gtx.Ops)
|
s.clickDecrease.Add(gtx.Ops)
|
||||||
return D{Size: gtx.Constraints.Min}
|
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 {
|
layout.Flexed(1, func(gtx C) D {
|
||||||
paint.ColorOp{Color: s.TextColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: st.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{})
|
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 {
|
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.NumberInput.clickIncrease.Add(gtx.Ops)
|
s.clickIncrease.Add(gtx.Ops)
|
||||||
return D{Size: gtx.Constraints.Min}
|
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) },
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -19,8 +19,8 @@ type (
|
|||||||
OscilloscopeState struct {
|
OscilloscopeState struct {
|
||||||
onceBtn *BoolClickable
|
onceBtn *BoolClickable
|
||||||
wrapBtn *BoolClickable
|
wrapBtn *BoolClickable
|
||||||
lengthInBeatsNumber *NumberInput
|
lengthInBeatsNumber *NumericUpDown
|
||||||
triggerChannelNumber *NumberInput
|
triggerChannelNumber *NumericUpDown
|
||||||
xScale int
|
xScale int
|
||||||
xOffset float32
|
xOffset float32
|
||||||
yScale float64
|
yScale float64
|
||||||
@ -34,98 +34,89 @@ type (
|
|||||||
LimitColor color.NRGBA `yaml:",flow"`
|
LimitColor color.NRGBA `yaml:",flow"`
|
||||||
CursorColor 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 {
|
func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
|
||||||
return &OscilloscopeState{
|
return &OscilloscopeState{
|
||||||
onceBtn: NewBoolClickable(model.SignalAnalyzer().Once()),
|
onceBtn: NewBoolClickable(model.SignalAnalyzer().Once()),
|
||||||
wrapBtn: NewBoolClickable(model.SignalAnalyzer().Wrap()),
|
wrapBtn: NewBoolClickable(model.SignalAnalyzer().Wrap()),
|
||||||
lengthInBeatsNumber: NewNumberInput(model.SignalAnalyzer().LengthInBeats()),
|
lengthInBeatsNumber: NewNumericUpDown(),
|
||||||
triggerChannelNumber: NewNumberInput(model.SignalAnalyzer().TriggerChannel()),
|
triggerChannelNumber: NewNumericUpDown(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Scope(s *OscilloscopeState, wave tracker.RingBuffer[[2]float32], th *Theme) *Oscilloscope {
|
func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, wave tracker.RingBuffer[[2]float32], th *Theme, st *OscilloscopeStyle) D {
|
||||||
return &Oscilloscope{State: s, Wave: wave, Theme: th}
|
wrapBtnStyle := ToggleButton(gtx, th, s.wrapBtn, "Wrap")
|
||||||
}
|
onceBtnStyle := ToggleButton(gtx, th, s.onceBtn, "Once")
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
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 {
|
layout.Rigid(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(leftSpacer),
|
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.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
|
||||||
layout.Rigid(onceBtnStyle.Layout),
|
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(rightSpacer),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(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(leftSpacer),
|
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.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
|
||||||
layout.Rigid(wrapBtnStyle.Layout),
|
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),
|
layout.Rigid(rightSpacer),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Oscilloscope) layoutWave(gtx C) D {
|
func (s *OscilloscopeState) layoutWave(gtx C, wave tracker.RingBuffer[[2]float32], th *Theme) D {
|
||||||
s.update(gtx)
|
s.update(gtx, wave)
|
||||||
if gtx.Constraints.Max.X == 0 || gtx.Constraints.Max.Y == 0 {
|
if gtx.Constraints.Max.X == 0 || gtx.Constraints.Max.Y == 0 {
|
||||||
return D{}
|
return D{}
|
||||||
}
|
}
|
||||||
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
|
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
|
||||||
event.Op(gtx.Ops, s.State)
|
event.Op(gtx.Ops, s)
|
||||||
paint.ColorOp{Color: s.Theme.Oscilloscope.CursorColor}.Add(gtx.Ops)
|
paint.ColorOp{Color: th.Oscilloscope.CursorColor}.Add(gtx.Ops)
|
||||||
cursorX := int(s.sampleToPx(gtx, float32(s.Wave.Cursor)))
|
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)})
|
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))
|
minusOneY := int(s.ampToY(gtx, -1))
|
||||||
fillRect(gtx, clip.Rect{Min: image.Pt(0, minusOneY), Max: image.Pt(gtx.Constraints.Max.X, minusOneY+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))
|
plusOneY := int(s.ampToY(gtx, 1))
|
||||||
fillRect(gtx, clip.Rect{Min: image.Pt(0, plusOneY), Max: image.Pt(gtx.Constraints.Max.X, plusOneY+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)})
|
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)})
|
fillRect(gtx, clip.Rect{Min: image.Pt(rightX, 0), Max: image.Pt(rightX+1, gtx.Constraints.Max.Y)})
|
||||||
for chn := range 2 {
|
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 {
|
for px := range gtx.Constraints.Max.X {
|
||||||
// left and right is the sample range covered by the pixel
|
// left and right is the sample range covered by the pixel
|
||||||
left := int(s.pxToSample(gtx, float32(px)-0.5))
|
left := int(s.pxToSample(gtx, float32(px)-0.5, wave))
|
||||||
right := int(s.pxToSample(gtx, float32(px)+0.5))
|
right := int(s.pxToSample(gtx, float32(px)+0.5, wave))
|
||||||
if right < 0 || left >= len(s.Wave.Buffer) {
|
if right < 0 || left >= len(wave.Buffer) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
right = min(right, len(s.Wave.Buffer)-1)
|
right = min(right, len(wave.Buffer)-1)
|
||||||
left = max(left, 0)
|
left = max(left, 0)
|
||||||
// smin and smax are the smallest and largest sample values in the pixel range
|
// smin and smax are the smallest and largest sample values in the pixel range
|
||||||
smax := float32(math.Inf(-1))
|
smax := float32(math.Inf(-1))
|
||||||
smin := float32(math.Inf(1))
|
smin := float32(math.Inf(1))
|
||||||
for x := left; x <= right; x++ {
|
for x := left; x <= right; x++ {
|
||||||
smax = max(smax, s.Wave.Buffer[x][chn])
|
smax = max(smax, wave.Buffer[x][chn])
|
||||||
smin = min(smin, s.Wave.Buffer[x][chn])
|
smin = min(smin, wave.Buffer[x][chn])
|
||||||
}
|
}
|
||||||
// y1 and y2 are the pixel range covered by the sample value
|
// 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)
|
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()
|
stack.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Oscilloscope) update(gtx C) {
|
func (o *OscilloscopeState) update(gtx C, wave tracker.RingBuffer[[2]float32]) {
|
||||||
for {
|
for {
|
||||||
ev, ok := gtx.Event(pointer.Filter{
|
ev, ok := gtx.Event(pointer.Filter{
|
||||||
Target: o.State,
|
Target: o,
|
||||||
Kinds: pointer.Scroll | pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
|
Kinds: pointer.Scroll | pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
|
||||||
ScrollY: pointer.ScrollRange{Min: -1e6, Max: 1e6},
|
ScrollY: pointer.ScrollRange{Min: -1e6, Max: 1e6},
|
||||||
})
|
})
|
||||||
@ -155,59 +146,59 @@ func (o *Oscilloscope) update(gtx C) {
|
|||||||
if e, ok := ev.(pointer.Event); ok {
|
if e, ok := ev.(pointer.Event); ok {
|
||||||
switch e.Kind {
|
switch e.Kind {
|
||||||
case pointer.Scroll:
|
case pointer.Scroll:
|
||||||
s1 := o.pxToSample(gtx, e.Position.X)
|
s1 := o.pxToSample(gtx, e.Position.X, wave)
|
||||||
o.State.xScale += min(max(-1, int(e.Scroll.Y)), 1)
|
o.xScale += min(max(-1, int(e.Scroll.Y)), 1)
|
||||||
s2 := o.pxToSample(gtx, e.Position.X)
|
s2 := o.pxToSample(gtx, e.Position.X, wave)
|
||||||
o.State.xOffset -= s1 - s2
|
o.xOffset -= s1 - s2
|
||||||
case pointer.Press:
|
case pointer.Press:
|
||||||
if e.Buttons&pointer.ButtonSecondary != 0 {
|
if e.Buttons&pointer.ButtonSecondary != 0 {
|
||||||
o.State.xOffset = 0
|
o.xOffset = 0
|
||||||
o.State.xScale = 0
|
o.xScale = 0
|
||||||
o.State.yScale = 0
|
o.yScale = 0
|
||||||
}
|
}
|
||||||
if e.Buttons&pointer.ButtonPrimary != 0 {
|
if e.Buttons&pointer.ButtonPrimary != 0 {
|
||||||
o.State.dragging = true
|
o.dragging = true
|
||||||
o.State.dragId = e.PointerID
|
o.dragId = e.PointerID
|
||||||
o.State.dragStartPoint = e.Position
|
o.dragStartPoint = e.Position
|
||||||
}
|
}
|
||||||
case pointer.Drag:
|
case pointer.Drag:
|
||||||
if e.Buttons&pointer.ButtonPrimary != 0 && o.State.dragging && e.PointerID == o.State.dragId {
|
if e.Buttons&pointer.ButtonPrimary != 0 && o.dragging && e.PointerID == o.dragId {
|
||||||
deltaX := o.pxToSample(gtx, e.Position.X) - o.pxToSample(gtx, o.State.dragStartPoint.X)
|
deltaX := o.pxToSample(gtx, e.Position.X, wave) - o.pxToSample(gtx, o.dragStartPoint.X, wave)
|
||||||
o.State.xOffset += deltaX
|
o.xOffset += deltaX
|
||||||
num := o.yToAmp(gtx, e.Position.Y)
|
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 {
|
if l := math.Abs(float64(num / den)); l > 1e-3 && l < 1e3 {
|
||||||
o.State.yScale += math.Log(l)
|
o.yScale += math.Log(l)
|
||||||
o.State.yScale = min(max(o.State.yScale, -1e3), 1e3)
|
o.yScale = min(max(o.yScale, -1e3), 1e3)
|
||||||
}
|
}
|
||||||
o.State.dragStartPoint = e.Position
|
o.dragStartPoint = e.Position
|
||||||
|
|
||||||
}
|
}
|
||||||
case pointer.Release | pointer.Cancel:
|
case pointer.Release | pointer.Cancel:
|
||||||
o.State.dragging = false
|
o.dragging = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Oscilloscope) scaleFactor() float32 {
|
func (o *OscilloscopeState) scaleFactor() float32 {
|
||||||
return float32(math.Pow(1.1, float64(o.State.xScale)))
|
return float32(math.Pow(1.1, float64(o.xScale)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Oscilloscope) pxToSample(gtx C, px float32) float32 {
|
func (s *OscilloscopeState) pxToSample(gtx C, px float32, wave tracker.RingBuffer[[2]float32]) float32 {
|
||||||
return px*s.scaleFactor()*float32(len(s.Wave.Buffer))/float32(gtx.Constraints.Max.X) - s.State.xOffset
|
return px*s.scaleFactor()*float32(len(wave.Buffer))/float32(gtx.Constraints.Max.X) - s.xOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Oscilloscope) sampleToPx(gtx C, sample float32) float32 {
|
func (s *OscilloscopeState) sampleToPx(gtx C, sample float32, wave tracker.RingBuffer[[2]float32]) float32 {
|
||||||
return (sample + s.State.xOffset) * float32(gtx.Constraints.Max.X) / float32(len(s.Wave.Buffer)) / s.scaleFactor()
|
return (sample + s.xOffset) * float32(gtx.Constraints.Max.X) / float32(len(wave.Buffer)) / s.scaleFactor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Oscilloscope) ampToY(gtx C, amp float32) float32 {
|
func (s *OscilloscopeState) ampToY(gtx C, amp float32) float32 {
|
||||||
scale := float32(math.Exp(s.State.yScale))
|
scale := float32(math.Exp(s.yScale))
|
||||||
return (1 - amp*scale) / 2 * float32(gtx.Constraints.Max.Y-1)
|
return (1 - amp*scale) / 2 * float32(gtx.Constraints.Max.Y-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Oscilloscope) yToAmp(gtx C, y float32) float32 {
|
func (s *OscilloscopeState) yToAmp(gtx C, y float32) float32 {
|
||||||
scale := float32(math.Exp(s.State.yScale))
|
scale := float32(math.Exp(s.yScale))
|
||||||
return (1 - y/float32(gtx.Constraints.Max.Y-1)*2) / scale
|
return (1 - y/float32(gtx.Constraints.Max.Y-1)*2) / scale
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,11 @@ type SongPanel struct {
|
|||||||
WeightingTypeBtn *Clickable
|
WeightingTypeBtn *Clickable
|
||||||
OversamplingBtn *Clickable
|
OversamplingBtn *Clickable
|
||||||
|
|
||||||
BPM *NumberInput
|
BPM *NumericUpDown
|
||||||
RowsPerPattern *NumberInput
|
RowsPerPattern *NumericUpDown
|
||||||
RowsPerBeat *NumberInput
|
RowsPerBeat *NumericUpDown
|
||||||
Step *NumberInput
|
Step *NumericUpDown
|
||||||
SongLength *NumberInput
|
SongLength *NumericUpDown
|
||||||
|
|
||||||
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: NewNumberInput(model.BPM()),
|
BPM: NewNumericUpDown(),
|
||||||
RowsPerPattern: NewNumberInput(model.RowsPerPattern()),
|
RowsPerPattern: NewNumericUpDown(),
|
||||||
RowsPerBeat: NewNumberInput(model.RowsPerBeat()),
|
RowsPerBeat: NewNumericUpDown(),
|
||||||
Step: NewNumberInput(model.Step()),
|
Step: NewNumericUpDown(),
|
||||||
SongLength: NewNumberInput(model.SongLength()),
|
SongLength: NewNumericUpDown(),
|
||||||
Scope: NewOscilloscope(model),
|
Scope: NewOscilloscope(model),
|
||||||
MenuBar: NewMenuBar(model),
|
MenuBar: NewMenuBar(model),
|
||||||
PlayBar: NewPlayBar(model),
|
PlayBar: NewPlayBar(model),
|
||||||
@ -115,19 +115,19 @@ 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", 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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
layout.Rigid(func(gtx C) D {
|
||||||
cpuload := tr.Model.CPULoad()
|
cpuload := tr.Model.CPULoad()
|
||||||
@ -200,7 +200,9 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D {
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
layout.Flexed(1, func(gtx C) 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),
|
layout.Rigid(Label(tr.Theme, &tr.Theme.SongPanel.Version, version.VersionOrHash).Layout),
|
||||||
)
|
)
|
||||||
|
@ -64,7 +64,6 @@ numericupdown:
|
|||||||
iconcolor: *primarycolor
|
iconcolor: *primarycolor
|
||||||
cornerradius: 4
|
cornerradius: 4
|
||||||
buttonwidth: 16
|
buttonwidth: 16
|
||||||
dpperstep: 8
|
|
||||||
textsize: 14
|
textsize: 14
|
||||||
width: 70
|
width: 70
|
||||||
height: 20
|
height: 20
|
||||||
|
@ -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 *NumberInput
|
OctaveNumberInput *NumericUpDown
|
||||||
InstrumentVoices *NumberInput
|
InstrumentVoices *NumericUpDown
|
||||||
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: NewNumberInput(model.Octave()),
|
OctaveNumberInput: NewNumericUpDown(),
|
||||||
InstrumentVoices: NewNumberInput(model.InstrumentVoices()),
|
InstrumentVoices: NewNumericUpDown(),
|
||||||
|
|
||||||
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},
|
||||||
|
Reference in New Issue
Block a user