diff --git a/tracker/gioui/instrument_editor.go b/tracker/gioui/instrument_editor.go index 5663623..17012b8 100644 --- a/tracker/gioui/instrument_editor.go +++ b/tracker/gioui/instrument_editor.go @@ -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), diff --git a/tracker/gioui/note_editor.go b/tracker/gioui/note_editor.go index ebc983c..6da334c 100644 --- a/tracker/gioui/note_editor.go +++ b/tracker/gioui/note_editor.go @@ -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} }), diff --git a/tracker/gioui/numericupdown.go b/tracker/gioui/numericupdown.go index c068cc6..2d2b435 100644 --- a/tracker/gioui/numericupdown.go +++ b/tracker/gioui/numericupdown.go @@ -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) }, ) }), ) diff --git a/tracker/gioui/oscilloscope.go b/tracker/gioui/oscilloscope.go index 568efa0..e6d73c4 100644 --- a/tracker/gioui/oscilloscope.go +++ b/tracker/gioui/oscilloscope.go @@ -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), ) }), diff --git a/tracker/gioui/songpanel.go b/tracker/gioui/songpanel.go index add8592..9f79b9b 100644 --- a/tracker/gioui/songpanel.go +++ b/tracker/gioui/songpanel.go @@ -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() diff --git a/tracker/gioui/tracker.go b/tracker/gioui/tracker.go index d24d16e..28065ee 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 *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},