From ec8c51b003166c25723b9c713cce776d8b0e255c Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Fri, 2 May 2025 18:28:04 +0300 Subject: [PATCH] drafting --- tracker/gioui/buttons.go | 109 ++++++++--------------------- tracker/gioui/instrument_editor.go | 4 +- tracker/gioui/menu.go | 2 +- tracker/gioui/note_editor.go | 16 ++--- tracker/gioui/numericupdown.go | 54 +++++++++----- tracker/gioui/oscilloscope.go | 94 +++++++++++++------------ tracker/gioui/songpanel.go | 20 +++--- tracker/gioui/theme.go | 29 +++----- tracker/gioui/theme.yml | 36 ++++++++-- tracker/gioui/unit_editor.go | 2 - 10 files changed, 179 insertions(+), 187 deletions(-) diff --git a/tracker/gioui/buttons.go b/tracker/gioui/buttons.go index 109fd60..1d868fc 100644 --- a/tracker/gioui/buttons.go +++ b/tracker/gioui/buttons.go @@ -115,51 +115,27 @@ func (t *TipIconButtonStyle) Layout(gtx C) D { return t.TipArea.Layout(gtx, t.Tooltip, t.IconButtonStyle.Layout) } -func ActionButton(gtx C, th *material.Theme, w *ActionClickable, text string) ButtonStyle { +func ActionButton(gtx C, th *Theme, style *ButtonStyle, w *ActionClickable, text string) Button { for w.Clickable.Clicked(gtx) { w.Action.Do() } - ret := Button(th, &w.Clickable, text) - ret.Color = th.Palette.Fg if !w.Action.Allowed() { - ret.Color = disabledTextColor + return Btn(th, &th.DisabledButton, &w.Clickable, text) } - ret.Background = transparent - ret.Inset = layout.UniformInset(unit.Dp(6)) - return ret + return Btn(th, style, &w.Clickable, text) } -func ToggleButton(gtx C, th *material.Theme, b *BoolClickable, text string) ButtonStyle { +func ToggleButton(gtx C, th *Theme, b *BoolClickable, text string) Button { for b.Clickable.Clicked(gtx) { b.Bool.Toggle() } - ret := Button(th, &b.Clickable, text) - ret.Background = transparent - ret.Inset = layout.UniformInset(unit.Dp(6)) - if b.Bool.Value() { - ret.Color = th.Palette.ContrastFg - ret.Background = th.Palette.ContrastBg - } else { - ret.Color = th.Palette.ContrastBg - ret.Background = transparent + if !b.Bool.Enabled() { + return Btn(th, &th.DisabledButton, &b.Clickable, text) } - return ret -} - -func LowEmphasisButton(th *material.Theme, w *Clickable, text string) ButtonStyle { - ret := Button(th, w, text) - ret.Color = th.Palette.Fg - ret.Background = transparent - ret.Inset = layout.UniformInset(unit.Dp(6)) - return ret -} - -func HighEmphasisButton(th *material.Theme, w *Clickable, text string) ButtonStyle { - ret := Button(th, w, text) - ret.Color = th.Palette.ContrastFg - ret.Background = th.Palette.Fg - ret.Inset = layout.UniformInset(unit.Dp(6)) - return ret + if b.Bool.Value() { + return Btn(th, &th.FilledButton, &b.Clickable, text) + } + return Btn(th, &th.TextButton, &b.Clickable, text) } // Clickable represents a clickable area. @@ -277,7 +253,6 @@ func (b *Clickable) update(t event.Tag, gtx layout.Context) (widget.Click, bool) } type ButtonStyle struct { - Text string // Color is the text color. Color color.NRGBA Font font.Font @@ -285,14 +260,13 @@ type ButtonStyle struct { Background color.NRGBA CornerRadius unit.Dp Inset layout.Inset - Button *Clickable - shaper *text.Shaper } -type ButtonLayoutStyle struct { - Background color.NRGBA - CornerRadius unit.Dp - Button *Clickable +type Button struct { + Text string + Button *Clickable + shaper *text.Shaper + ButtonStyle } type IconButtonStyle struct { @@ -307,32 +281,17 @@ type IconButtonStyle struct { Description string } -func Button(th *material.Theme, button *Clickable, txt string) ButtonStyle { - b := ButtonStyle{ - Text: txt, - Color: th.Palette.ContrastFg, - CornerRadius: 4, - Background: th.Palette.ContrastBg, - TextSize: th.TextSize * 14.0 / 16.0, - Inset: layout.Inset{ - Top: 10, Bottom: 10, - Left: 12, Right: 12, - }, - Button: button, - shaper: th.Shaper, +func Btn(th *Theme, style *ButtonStyle, button *Clickable, txt string) Button { + b := Button{ + Text: txt, + ButtonStyle: *style, + Button: button, + shaper: th.Material.Shaper, } - b.Font.Typeface = th.Face + b.Font = labelDefaultFont return b } -func ButtonLayout(th *material.Theme, button *Clickable) ButtonLayoutStyle { - return ButtonLayoutStyle{ - Button: button, - Background: th.Palette.ContrastBg, - CornerRadius: 4, - } -} - func IconButton(th *material.Theme, button *Clickable, icon *widget.Icon, description string) IconButtonStyle { return IconButtonStyle{ Background: th.Palette.ContrastBg, @@ -345,21 +304,7 @@ func IconButton(th *material.Theme, button *Clickable, icon *widget.Icon, descri } } -func (b ButtonStyle) Layout(gtx layout.Context) layout.Dimensions { - return ButtonLayoutStyle{ - Background: b.Background, - CornerRadius: b.CornerRadius, - Button: b.Button, - }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - colMacro := op.Record(gtx.Ops) - paint.ColorOp{Color: b.Color}.Add(gtx.Ops) - return widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.TextSize, b.Text, colMacro.Stop()) - }) - }) -} - -func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { +func (b Button) Layout(gtx layout.Context) layout.Dimensions { min := gtx.Constraints.Min return b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions { semantic.Button.Add(gtx.Ops) @@ -380,7 +325,13 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di }, func(gtx layout.Context) layout.Dimensions { gtx.Constraints.Min = min - return layout.Center.Layout(gtx, w) + return layout.Center.Layout(gtx, func(gtx C) D { + return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + colMacro := op.Record(gtx.Ops) + paint.ColorOp{Color: b.Color}.Add(gtx.Ops) + return widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.TextSize, b.Text, colMacro.Stop()) + }) + }) }, ) }) diff --git a/tracker/gioui/instrument_editor.go b/tracker/gioui/instrument_editor.go index baa8070..315bc30 100644 --- a/tracker/gioui/instrument_editor.go +++ b/tracker/gioui/instrument_editor.go @@ -130,7 +130,7 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D { octave := func(gtx C) D { in := layout.UniformInset(unit.Dp(1)) - numStyle := NumericUpDown(&t.Theme.Material, t.OctaveNumberInput, ie.octaveHint) + numStyle := NumUpDown(t.Theme, t.OctaveNumberInput, ie.octaveHint) dims := in.Layout(gtx, numStyle.Layout) return dims } @@ -217,7 +217,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D { layout.Rigid(LabelStyle{Text: "Voices", Color: disabledTextColor, Alignment: layout.W, FontSize: t.Theme.Material.TextSize * 14.0 / 16.0, Shaper: t.Theme.Material.Shaper}.Layout), layout.Rigid(layout.Spacer{Width: 4}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - numStyle := NumericUpDown(&t.Theme.Material, t.InstrumentVoices, "Number of voices for this instrument") + numStyle := NumUpDown(t.Theme, t.InstrumentVoices, "Number of voices for this instrument") dims := numStyle.Layout(gtx) return dims }), diff --git a/tracker/gioui/menu.go b/tracker/gioui/menu.go index e419eb5..b1090b0 100644 --- a/tracker/gioui/menu.go +++ b/tracker/gioui/menu.go @@ -173,7 +173,7 @@ func (tr *Tracker) layoutMenu(gtx C, title string, clickable *Clickable, menu *M m := PopupMenu(menu, tr.Theme.Material.Shaper) return func(gtx C) D { defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() - titleBtn := Button(&tr.Theme.Material, clickable, title) + titleBtn := Btn(tr.Theme, &tr.Theme.MenuButton, clickable, title) titleBtn.Color = white titleBtn.Background = transparent titleBtn.CornerRadius = unit.Dp(0) diff --git a/tracker/gioui/note_editor.go b/tracker/gioui/note_editor.go index b330db8..b7707ef 100644 --- a/tracker/gioui/note_editor.go +++ b/tracker/gioui/note_editor.go @@ -150,22 +150,22 @@ func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions { func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D { return Surface{Gray: 37, Focus: te.scrollTable.Focused() || te.scrollTable.ChildFocused()}.Layout(gtx, func(gtx C) D { - addSemitoneBtnStyle := ActionButton(gtx, &t.Theme.Material, te.AddSemitoneBtn, "+1") - subtractSemitoneBtnStyle := ActionButton(gtx, &t.Theme.Material, te.SubtractSemitoneBtn, "-1") - addOctaveBtnStyle := ActionButton(gtx, &t.Theme.Material, te.AddOctaveBtn, "+12") - subtractOctaveBtnStyle := ActionButton(gtx, &t.Theme.Material, te.SubtractOctaveBtn, "-12") - noteOffBtnStyle := ActionButton(gtx, &t.Theme.Material, te.NoteOffBtn, "Note Off") + addSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.TextButton, te.AddSemitoneBtn, "+1") + subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.TextButton, te.SubtractSemitoneBtn, "-1") + addOctaveBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.TextButton, te.AddOctaveBtn, "+12") + subtractOctaveBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.TextButton, te.SubtractOctaveBtn, "-12") + noteOffBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.TextButton, te.NoteOffBtn, "Note Off") deleteTrackBtnStyle := ActionIcon(gtx, &t.Theme.Material, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint) splitTrackBtnStyle := ActionIcon(gtx, &t.Theme.Material, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint) newTrackBtnStyle := ActionIcon(gtx, &t.Theme.Material, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint) in := layout.UniformInset(unit.Dp(1)) voiceUpDown := func(gtx C) D { - numStyle := NumericUpDown(&t.Theme.Material, te.TrackVoices, "Track voices") + numStyle := NumUpDown(t.Theme, te.TrackVoices, "Track voices") return in.Layout(gtx, numStyle.Layout) } - effectBtnStyle := ToggleButton(gtx, &t.Theme.Material, te.EffectBtn, "Hex") + effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex") uniqueBtnStyle := ToggleIcon(gtx, &t.Theme.Material, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip) - midiInBtnStyle := ToggleButton(gtx, &t.Theme.Material, te.TrackMidiInBtn, "MIDI") + midiInBtnStyle := ToggleButton(gtx, t.Theme, te.TrackMidiInBtn, "MIDI") return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Dimensions{Size: image.Pt(gtx.Dp(unit.Dp(12)), 0)} }), layout.Rigid(addSemitoneBtnStyle.Layout), diff --git a/tracker/gioui/numericupdown.go b/tracker/gioui/numericupdown.go index 7e1c4e5..29231f2 100644 --- a/tracker/gioui/numericupdown.go +++ b/tracker/gioui/numericupdown.go @@ -2,14 +2,17 @@ package gioui import ( "image" + "image/color" "strconv" "github.com/vsariola/sointu/tracker" "golang.org/x/exp/shiny/materialdesign/icons" + "gioui.org/font" "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" + "gioui.org/unit" "gioui.org/widget" "gioui.org/x/component" @@ -30,26 +33,41 @@ type NumberInput struct { } type NumericUpDownStyle struct { - Theme *Theme + 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 + DpPerStep unit.Dp +} + +type NumericUpDown struct { NumberInput *NumberInput Tooltip component.Tooltip + Shaper *text.Shaper + Font font.Font + NumericUpDownStyle } func NewNumberInput(v tracker.Int) *NumberInput { return &NumberInput{Int: v} } -func NumericUpDown(th *Theme, number *NumberInput, tooltip string) NumericUpDownStyle { - return NumericUpDownStyle{ - NumberInput: number, - Theme: th, - Tooltip: Tooltip(&th.Material, tooltip), +func NumUpDown(th *Theme, number *NumberInput, tooltip string) NumericUpDown { + return NumericUpDown{ + NumberInput: number, + Shaper: th.Material.Shaper, + Tooltip: Tooltip(&th.Material, tooltip), + NumericUpDownStyle: th.NumericUpDown, } } -func (s *NumericUpDownStyle) Update(gtx layout.Context) { +func (s *NumericUpDown) Update(gtx layout.Context) { // handle dragging - pxPerStep := float32(gtx.Dp(s.Theme.NumericUpDown.DpPerStep)) + pxPerStep := float32(gtx.Dp(s.DpPerStep)) for { ev, ok := gtx.Event(pointer.Filter{ Target: s.NumberInput, @@ -84,22 +102,22 @@ func (s *NumericUpDownStyle) Update(gtx layout.Context) { } } -func (s NumericUpDownStyle) Layout(gtx C) D { +func (s NumericUpDown) Layout(gtx C) D { if s.Tooltip.Text.Text != "" { return s.NumberInput.tipArea.Layout(gtx, s.Tooltip, s.actualLayout) } return s.actualLayout(gtx) } -func (s *NumericUpDownStyle) actualLayout(gtx C) D { +func (s *NumericUpDown) actualLayout(gtx C) D { s.Update(gtx) - gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(s.Theme.NumericUpDown.Width), gtx.Dp(s.Theme.NumericUpDown.Height))) - width := gtx.Dp(s.Theme.NumericUpDown.ButtonWidth) - height := gtx.Dp(s.Theme.NumericUpDown.Height) + gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(s.Width), gtx.Dp(s.Height))) + width := gtx.Dp(s.ButtonWidth) + height := gtx.Dp(s.Height) return layout.Background{}.Layout(gtx, func(gtx C) D { - defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(s.Theme.NumericUpDown.CornerRadius)).Push(gtx.Ops).Pop() - paint.Fill(gtx.Ops, s.Theme.NumericUpDown.BgColor) + 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 return D{Size: gtx.Constraints.Min} }, @@ -113,12 +131,12 @@ func (s *NumericUpDownStyle) actualLayout(gtx C) D { s.NumberInput.clickDecrease.Add(gtx.Ops) return D{Size: gtx.Constraints.Min} }, - func(gtx C) D { return widgetForIcon(icons.ContentRemove).Layout(gtx, s.Theme.NumericUpDown.IconColor) }, + func(gtx C) D { return widgetForIcon(icons.ContentRemove).Layout(gtx, s.IconColor) }, ) }), layout.Flexed(1, func(gtx C) D { - paint.ColorOp{Color: s.Theme.NumericUpDown.TextColor}.Add(gtx.Ops) - return widget.Label{Alignment: text.Middle}.Layout(gtx, s.Theme.Material.Shaper, s.Font, s.Theme.NumericUpDown.TextSize, strconv.Itoa(s.NumberInput.Int.Value()), op.CallOp{}) + paint.ColorOp{Color: s.TextColor}.Add(gtx.Ops) + return widget.Label{Alignment: text.Middle}.Layout(gtx, s.Shaper, s.Font, s.TextSize, strconv.Itoa(s.NumberInput.Int.Value()), op.CallOp{}) }), layout.Rigid(func(gtx C) D { gtx.Constraints = layout.Exact(image.Pt(width, height)) diff --git a/tracker/gioui/oscilloscope.go b/tracker/gioui/oscilloscope.go index bd4d92c..2dd8484 100644 --- a/tracker/gioui/oscilloscope.go +++ b/tracker/gioui/oscilloscope.go @@ -2,6 +2,7 @@ package gioui import ( "image" + "image/color" "math" "gioui.org/f32" @@ -15,7 +16,7 @@ import ( ) type ( - Oscilloscope struct { + OscilloscopeState struct { onceBtn *BoolClickable wrapBtn *BoolClickable lengthInBeatsNumber *NumberInput @@ -29,14 +30,21 @@ type ( } OscilloscopeStyle struct { - Oscilloscope *Oscilloscope - Wave tracker.RingBuffer[[2]float32] - Theme *Theme + CurveColors [2]color.NRGBA `yaml:",flow"` + 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) *Oscilloscope { - return &Oscilloscope{ +func NewOscilloscope(model *tracker.Model) *OscilloscopeState { + return &OscilloscopeState{ onceBtn: NewBoolClickable(model.SignalAnalyzer().Once().Bool()), wrapBtn: NewBoolClickable(model.SignalAnalyzer().Wrap().Bool()), lengthInBeatsNumber: NewNumberInput(model.SignalAnalyzer().LengthInBeats().Int()), @@ -44,15 +52,15 @@ func NewOscilloscope(model *tracker.Model) *Oscilloscope { } } -func LineOscilloscope(s *Oscilloscope, wave tracker.RingBuffer[[2]float32], th *Theme) *OscilloscopeStyle { - return &OscilloscopeStyle{Oscilloscope: s, Wave: wave, Theme: th} +func Scope(s *OscilloscopeState, wave tracker.RingBuffer[[2]float32], th *Theme) *Oscilloscope { + return &Oscilloscope{State: s, Wave: wave, Theme: th} } -func (s *OscilloscopeStyle) Layout(gtx C) D { - wrapBtnStyle := ToggleButton(gtx, &s.Theme.Material, s.Oscilloscope.wrapBtn, "Wrap") - onceBtnStyle := ToggleButton(gtx, &s.Theme.Material, s.Oscilloscope.onceBtn, "Once") - triggerChannelStyle := NumericUpDown(&s.Theme.Material, s.Oscilloscope.triggerChannelNumber, "Trigger channel") - lengthNumberStyle := NumericUpDown(&s.Theme.Material, s.Oscilloscope.lengthInBeatsNumber, "Buffer length in beats") +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 rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout @@ -82,13 +90,13 @@ func (s *OscilloscopeStyle) Layout(gtx C) D { ) } -func (s *OscilloscopeStyle) layoutWave(gtx C) D { +func (s *Oscilloscope) layoutWave(gtx C) D { s.update(gtx) 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.Oscilloscope) + 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))) fillRect(gtx, clip.Rect{Min: image.Pt(cursorX, 0), Max: image.Pt(cursorX+1, gtx.Constraints.Max.Y)}) @@ -134,10 +142,10 @@ func fillRect(gtx C, rect clip.Rect) { stack.Pop() } -func (o *OscilloscopeStyle) update(gtx C) { +func (o *Oscilloscope) update(gtx C) { for { ev, ok := gtx.Event(pointer.Filter{ - Target: o.Oscilloscope, + Target: o.State, Kinds: pointer.Scroll | pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel, ScrollY: pointer.ScrollRange{Min: -1e6, Max: 1e6}, }) @@ -148,58 +156,58 @@ func (o *OscilloscopeStyle) update(gtx C) { switch e.Kind { case pointer.Scroll: s1 := o.pxToSample(gtx, e.Position.X) - o.Oscilloscope.xScale += min(max(-1, int(e.Scroll.Y)), 1) + o.State.xScale += min(max(-1, int(e.Scroll.Y)), 1) s2 := o.pxToSample(gtx, e.Position.X) - o.Oscilloscope.xOffset -= s1 - s2 + o.State.xOffset -= s1 - s2 case pointer.Press: if e.Buttons&pointer.ButtonSecondary != 0 { - o.Oscilloscope.xOffset = 0 - o.Oscilloscope.xScale = 0 - o.Oscilloscope.yScale = 0 + o.State.xOffset = 0 + o.State.xScale = 0 + o.State.yScale = 0 } if e.Buttons&pointer.ButtonPrimary != 0 { - o.Oscilloscope.dragging = true - o.Oscilloscope.dragId = e.PointerID - o.Oscilloscope.dragStartPoint = e.Position + o.State.dragging = true + o.State.dragId = e.PointerID + o.State.dragStartPoint = e.Position } case pointer.Drag: - if e.Buttons&pointer.ButtonPrimary != 0 && o.Oscilloscope.dragging && e.PointerID == o.Oscilloscope.dragId { - deltaX := o.pxToSample(gtx, e.Position.X) - o.pxToSample(gtx, o.Oscilloscope.dragStartPoint.X) - o.Oscilloscope.xOffset += deltaX + 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 num := o.yToAmp(gtx, e.Position.Y) - den := o.yToAmp(gtx, o.Oscilloscope.dragStartPoint.Y) + den := o.yToAmp(gtx, o.State.dragStartPoint.Y) if l := math.Abs(float64(num / den)); l > 1e-3 && l < 1e3 { - o.Oscilloscope.yScale += math.Log(l) - o.Oscilloscope.yScale = min(max(o.Oscilloscope.yScale, -1e3), 1e3) + o.State.yScale += math.Log(l) + o.State.yScale = min(max(o.State.yScale, -1e3), 1e3) } - o.Oscilloscope.dragStartPoint = e.Position + o.State.dragStartPoint = e.Position } case pointer.Release | pointer.Cancel: - o.Oscilloscope.dragging = false + o.State.dragging = false } } } } -func (o *OscilloscopeStyle) scaleFactor() float32 { - return float32(math.Pow(1.1, float64(o.Oscilloscope.xScale))) +func (o *Oscilloscope) scaleFactor() float32 { + return float32(math.Pow(1.1, float64(o.State.xScale))) } -func (s *OscilloscopeStyle) pxToSample(gtx C, px float32) float32 { - return px*s.scaleFactor()*float32(len(s.Wave.Buffer))/float32(gtx.Constraints.Max.X) - s.Oscilloscope.xOffset +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 *OscilloscopeStyle) sampleToPx(gtx C, sample float32) float32 { - return (sample + s.Oscilloscope.xOffset) * float32(gtx.Constraints.Max.X) / float32(len(s.Wave.Buffer)) / s.scaleFactor() +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 *OscilloscopeStyle) ampToY(gtx C, amp float32) float32 { - scale := float32(math.Exp(s.Oscilloscope.yScale)) +func (s *Oscilloscope) ampToY(gtx C, amp float32) float32 { + scale := float32(math.Exp(s.State.yScale)) return (1 - amp*scale) / 2 * float32(gtx.Constraints.Max.Y-1) } -func (s *OscilloscopeStyle) yToAmp(gtx C, y float32) float32 { - scale := float32(math.Exp(s.Oscilloscope.yScale)) +func (s *Oscilloscope) yToAmp(gtx C, y float32) float32 { + scale := float32(math.Exp(s.State.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 cfae65a..a55e94e 100644 --- a/tracker/gioui/songpanel.go +++ b/tracker/gioui/songpanel.go @@ -32,7 +32,7 @@ type SongPanel struct { Step *NumberInput SongLength *NumberInput - Scope *Oscilloscope + Scope *OscilloscopeState MenuBar *MenuBar PlayBar *PlayBar @@ -97,7 +97,7 @@ func (s *SongPanel) Layout(gtx C, t *Tracker) D { func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { paint.FillShape(gtx.Ops, songSurfaceColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) - scopeStyle := LineOscilloscope(t.Scope, tr.SignalAnalyzer().Waveform(), tr.Theme) + scopeStyle := Scope(t.Scope, tr.SignalAnalyzer().Waveform(), tr.Theme) var weightingTxt string switch tracker.WeightingType(tr.Model.DetectorWeighting().Value()) { @@ -111,15 +111,13 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { weightingTxt = "No weight (RMS)" } - weightingBtn := LowEmphasisButton(&tr.Theme.Material, t.WeightingTypeBtn, weightingTxt) - weightingBtn.Color = mediumEmphasisTextColor + weightingBtn := Btn(tr.Theme, &tr.Theme.TextButton, t.WeightingTypeBtn, weightingTxt) oversamplingTxt := "Sample peak" if tr.Model.Oversampling().Value() { oversamplingTxt = "True peak" } - oversamplingBtn := LowEmphasisButton(&tr.Theme.Material, t.OversamplingBtn, oversamplingTxt) - oversamplingBtn.Color = mediumEmphasisTextColor + oversamplingBtn := Btn(tr.Theme, &tr.Theme.TextButton, t.OversamplingBtn, oversamplingTxt) return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -130,19 +128,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.Material, "BPM", NumericUpDown(&tr.Theme.Material, t.BPM, "Song Length").Layout) + return layoutSongOptionRow(gtx, &tr.Theme.Material, "BPM", NumUpDown(tr.Theme, t.BPM, "Song Length").Layout) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, &tr.Theme.Material, "Song length", NumericUpDown(&tr.Theme.Material, t.SongLength, "Song Length").Layout) + return layoutSongOptionRow(gtx, &tr.Theme.Material, "Song length", NumUpDown(tr.Theme, t.SongLength, "Song Length").Layout) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, &tr.Theme.Material, "Rows per pat", NumericUpDown(&tr.Theme.Material, t.RowsPerPattern, "Rows per pattern").Layout) + return layoutSongOptionRow(gtx, &tr.Theme.Material, "Rows per pat", NumUpDown(tr.Theme, t.RowsPerPattern, "Rows per pattern").Layout) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, &tr.Theme.Material, "Rows per beat", NumericUpDown(&tr.Theme.Material, t.RowsPerBeat, "Rows per beat").Layout) + return layoutSongOptionRow(gtx, &tr.Theme.Material, "Rows per beat", NumUpDown(tr.Theme, t.RowsPerBeat, "Rows per beat").Layout) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, &tr.Theme.Material, "Cursor step", NumericUpDown(&tr.Theme.Material, t.Step, "Cursor step").Layout) + return layoutSongOptionRow(gtx, &tr.Theme.Material, "Cursor step", NumUpDown(tr.Theme, t.Step, "Cursor step").Layout) }), ) }) diff --git a/tracker/gioui/theme.go b/tracker/gioui/theme.go index a89c3f7..3ecdb9c 100644 --- a/tracker/gioui/theme.go +++ b/tracker/gioui/theme.go @@ -15,23 +15,13 @@ import ( ) type Theme struct { - Material material.Theme - Oscilloscope struct { - CurveColors [2]color.NRGBA `yaml:",flow"` - LimitColor color.NRGBA `yaml:",flow"` - CursorColor color.NRGBA `yaml:",flow"` - } - NumericUpDown 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 - DpPerStep unit.Dp - } + Material material.Theme + FilledButton ButtonStyle + TextButton ButtonStyle + DisabledButton ButtonStyle + MenuButton ButtonStyle + Oscilloscope OscilloscopeStyle + NumericUpDown NumericUpDownStyle } //go:embed theme.yml @@ -39,7 +29,10 @@ var defaultTheme []byte func NewTheme() *Theme { var theme Theme - yaml.Unmarshal(defaultTheme, &theme) + err := yaml.Unmarshal(defaultTheme, &theme) + if err != nil { + panic(fmt.Errorf("failed to default theme: %w", err)) + } str, _ := yaml.Marshal(theme) fmt.Printf(string(str)) ReadCustomConfigYml("theme.yml", &theme) diff --git a/tracker/gioui/theme.yml b/tracker/gioui/theme.yml index 20fa0ed..d7ea684 100644 --- a/tracker/gioui/theme.yml +++ b/tracker/gioui/theme.yml @@ -1,23 +1,49 @@ +primarycolor: &primarycolor { r: 243, g: 187, b: 252, a: 255 } +secondarycolor: &secondarycolor { r: 187, g: 245, b: 252, a: 255 } +transparentcolor: &transparentcolor { r: 0, g: 0, b: 0, a: 0 } material: textsize: 16 fingersize: 38 palette: bg: &bg { r: 18, g: 18, b: 18, a: 255 } fg: &fg { r: 255, g: 255, b: 255, a: 255 } - contrastbg: &contrastbg { r: 206, g: 147, b: 216, a: 255 } + contrastbg: *primarycolor contrastfg: &contrastfg { r: 0, g: 0, b: 0, a: 255 } +filledbutton: + background: *primarycolor + color: *contrastfg + textsize: &buttontextsize 14 + cornerradius: &buttoncornerradius 12 + inset: &buttoninset { top: 0, bottom: 0, left: 6, right: 6 } +textbutton: + background: *transparentcolor + color: *primarycolor + textsize: *buttontextsize + cornerradius: *buttoncornerradius + inset: *buttoninset +disabledbutton: + background: { r: 53, g: 51, b: 55, a: 255 } + color: { r: 120, g: 116, b: 121, a: 255 } + textsize: *buttontextsize + cornerradius: *buttoncornerradius + inset: *buttoninset +menubutton: + background: *transparentcolor + color: { r: 255, g: 255, b: 255, a: 255 } + textsize: *buttontextsize + cornerradius: 0 + inset: *buttoninset oscilloscope: - curvecolors: - [{ r: 206, g: 147, b: 216, a: 255 }, { r: 128, g: 222, b: 234, a: 255 }] + curvecolors: [*primarycolor, *secondarycolor] limitcolor: { r: 255, g: 255, b: 255, a: 8 } cursorcolor: { r: 252, g: 186, b: 3, a: 255 } numericupdown: bgcolor: { r: 255, g: 255, b: 255, A: 3 } textcolor: *fg - iconcolor: *contrastbg + iconcolor: *primarycolor cornerradius: 4 buttonwidth: 16 - unitsperstep: 8 + dpperstep: 8 textsize: 14 width: 70 height: 20 diff --git a/tracker/gioui/unit_editor.go b/tracker/gioui/unit_editor.go index 1176a96..260213c 100644 --- a/tracker/gioui/unit_editor.go +++ b/tracker/gioui/unit_editor.go @@ -299,7 +299,6 @@ func (p ParameterStyle) Layout(gtx C) D { p.w.floatWidget.Value = (float32(p.w.Parameter.Value()) - float32(ra.Min)) / float32(ra.Max-ra.Min) } sliderStyle := material.Slider(p.Theme, &p.w.floatWidget) - sliderStyle.Color = p.Theme.Fg if isSendTarget { sliderStyle.Color = paramIsSendTargetColor } @@ -319,7 +318,6 @@ func (p ParameterStyle) Layout(gtx C) D { p.w.boolWidget.Value = p.w.Parameter.Value() > ra.Min boolStyle := material.Switch(p.Theme, &p.w.boolWidget, "Toggle boolean parameter") boolStyle.Color.Disabled = p.Theme.Fg - boolStyle.Color.Enabled = p.Theme.ContrastBg defer pointer.PassOp{}.Push(gtx.Ops).Pop() dims := layout.Center.Layout(gtx, boolStyle.Layout) if p.w.boolWidget.Value {