From afb1fee4edbbb294711b1097769c8e88f21e2113 Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Thu, 1 May 2025 23:49:07 +0300 Subject: [PATCH] feat(tracker/gioui): add theme.yml which contains all styling --- CHANGELOG.md | 5 + tracker/gioui/buttons.go | 142 +++++++------------- tracker/gioui/dialog.go | 30 ++--- tracker/gioui/draglist.go | 42 +++--- tracker/gioui/editor.go | 32 ++++- tracker/gioui/instrument_editor.go | 105 +++++++-------- tracker/gioui/label.go | 56 ++++---- tracker/gioui/menu.go | 49 +++---- tracker/gioui/note_editor.go | 81 ++++++------ tracker/gioui/numericupdown.go | 70 +++++----- tracker/gioui/order_editor.go | 37 +++--- tracker/gioui/oscilloscope.go | 106 +++++++-------- tracker/gioui/popup.go | 6 +- tracker/gioui/popup_alert.go | 29 ++--- tracker/gioui/scroll_table.go | 3 +- tracker/gioui/scrollbar.go | 25 ++-- tracker/gioui/songpanel.go | 76 ++++------- tracker/gioui/theme.go | 201 +++++++++++++++++++---------- tracker/gioui/theme.yml | 170 ++++++++++++++++++++++++ tracker/gioui/tracker.go | 16 +-- tracker/gioui/unit_editor.go | 48 +++---- tracker/gioui/vumeter.go | 47 ------- 22 files changed, 740 insertions(+), 636 deletions(-) create mode 100644 tracker/gioui/theme.yml delete mode 100644 tracker/gioui/vumeter.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 804dc36..405c21e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added +- Theme can be user configured, in theme.yml. This theme.yml should be placed in + the usual sointu config directory (i.e. + `os.UserConfigDir()/sointu/theme.yml`). See + [theme.yml](tracker/gioui/theme.yml) for the default theme, and + [theme.go](tracker/gioui/theme.go) for what can be changed. - Ctrl + scroll wheel adjusts the global scaling of the GUI ([#153][i153]) - The loudness detection supports LUFS, A-weighting, C-weighting or RMS-weighting, and peak detection supports true peak or sample peak detection. diff --git a/tracker/gioui/buttons.go b/tracker/gioui/buttons.go index 024ab10..f5de38a 100644 --- a/tracker/gioui/buttons.go +++ b/tracker/gioui/buttons.go @@ -17,7 +17,6 @@ import ( "gioui.org/text" "gioui.org/unit" "gioui.org/widget" - "gioui.org/widget/material" "gioui.org/x/component" "github.com/vsariola/sointu/tracker" ) @@ -58,27 +57,28 @@ func NewBoolClickable(b tracker.Bool) *BoolClickable { } } -func Tooltip(th *material.Theme, tip string) component.Tooltip { - tooltip := component.PlatformTooltip(th, tip) - tooltip.Bg = black +func Tooltip(th *Theme, tip string) component.Tooltip { + tooltip := component.PlatformTooltip(&th.Material, tip) + tooltip.Bg = th.Tooltip.Bg + tooltip.Text.Color = th.Tooltip.Color return tooltip } -func ActionIcon(gtx C, th *material.Theme, w *ActionClickable, icon []byte, tip string) TipIconButtonStyle { +func ActionIcon(gtx C, th *Theme, w *ActionClickable, icon []byte, tip string) TipIconButtonStyle { ret := TipIcon(th, &w.TipClickable, icon, tip) for w.Clickable.Clicked(gtx) { w.Action.Do() } if !w.Action.Allowed() { - ret.IconButtonStyle.Color = disabledTextColor + ret.IconButtonStyle.Color = th.Button.Disabled.Color } return ret } -func TipIcon(th *material.Theme, w *TipClickable, icon []byte, tip string) TipIconButtonStyle { +func TipIcon(th *Theme, w *TipClickable, icon []byte, tip string) TipIconButtonStyle { iconButtonStyle := IconButton(th, &w.Clickable, widgetForIcon(icon), "") - iconButtonStyle.Color = primaryColor - iconButtonStyle.Background = transparent + iconButtonStyle.Color = th.Material.Palette.ContrastBg + iconButtonStyle.Background = color.NRGBA{} iconButtonStyle.Inset = layout.UniformInset(unit.Dp(6)) return TipIconButtonStyle{ TipArea: &w.TipArea, @@ -87,7 +87,7 @@ func TipIcon(th *material.Theme, w *TipClickable, icon []byte, tip string) TipIc } } -func ToggleIcon(gtx C, th *material.Theme, w *BoolClickable, offIcon, onIcon []byte, offTip, onTip string) TipIconButtonStyle { +func ToggleIcon(gtx C, th *Theme, w *BoolClickable, offIcon, onIcon []byte, offTip, onTip string) TipIconButtonStyle { icon := offIcon tip := offTip if w.Bool.Value() { @@ -98,11 +98,11 @@ func ToggleIcon(gtx C, th *material.Theme, w *BoolClickable, offIcon, onIcon []b w.Bool.Toggle() } ibStyle := IconButton(th, &w.Clickable, widgetForIcon(icon), "") - ibStyle.Background = transparent + ibStyle.Background = color.NRGBA{} ibStyle.Inset = layout.UniformInset(unit.Dp(6)) - ibStyle.Color = primaryColor + ibStyle.Color = th.Material.Palette.ContrastBg if !w.Bool.Enabled() { - ibStyle.Color = disabledTextColor + ibStyle.Color = th.Button.Disabled.Color } return TipIconButtonStyle{ TipArea: &w.TipArea, @@ -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.Button.Disabled, &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.Fg - } else { - ret.Color = th.Palette.Fg - ret.Background = transparent + if !b.Bool.Enabled() { + return Btn(th, &th.Button.Disabled, &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.Button.Filled, &b.Clickable, text) + } + return Btn(th, &th.Button.Text, &b.Clickable, text) } // Clickable represents a clickable area. @@ -277,22 +253,21 @@ 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 TextSize unit.Sp Background color.NRGBA CornerRadius unit.Dp + Height 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,36 +282,20 @@ 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 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 { +func IconButton(th *Theme, button *Clickable, icon *widget.Icon, description string) IconButtonStyle { return IconButtonStyle{ - Background: th.Palette.ContrastBg, - Color: th.Palette.ContrastFg, + Background: th.Material.Palette.ContrastBg, + Color: th.Material.Palette.ContrastFg, Icon: icon, Size: 24, Inset: layout.UniformInset(12), @@ -345,22 +304,9 @@ 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 + min.Y = gtx.Dp(b.Height) return b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions { semantic.Button.Add(gtx.Ops) return layout.Background{}.Layout(gtx, @@ -380,7 +326,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/dialog.go b/tracker/gioui/dialog.go index 2577a67..d7451d7 100644 --- a/tracker/gioui/dialog.go +++ b/tracker/gioui/dialog.go @@ -1,10 +1,11 @@ package gioui import ( + "image/color" + "gioui.org/io/key" "gioui.org/layout" "gioui.org/op/paint" - "gioui.org/text" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" @@ -28,7 +29,7 @@ type DialogStyle struct { AltStyle material.ButtonStyle OkStyle material.ButtonStyle CancelStyle material.ButtonStyle - Shaper *text.Shaper + Theme *Theme } func NewDialog(ok, alt, cancel tracker.Action) *Dialog { @@ -37,22 +38,22 @@ func NewDialog(ok, alt, cancel tracker.Action) *Dialog { return ret } -func ConfirmDialog(gtx C, th *material.Theme, dialog *Dialog, title, text string) DialogStyle { +func ConfirmDialog(gtx C, th *Theme, dialog *Dialog, title, text string) DialogStyle { ret := DialogStyle{ dialog: dialog, Title: title, Text: text, Inset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12), Left: unit.Dp(20), Right: unit.Dp(20)}, TextInset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12)}, - AltStyle: material.Button(th, &dialog.BtnAlt, "Alt"), - OkStyle: material.Button(th, &dialog.BtnOk, "Ok"), - CancelStyle: material.Button(th, &dialog.BtnCancel, "Cancel"), - Shaper: th.Shaper, + AltStyle: material.Button(&th.Material, &dialog.BtnAlt, "Alt"), + OkStyle: material.Button(&th.Material, &dialog.BtnOk, "Ok"), + CancelStyle: material.Button(&th.Material, &dialog.BtnCancel, "Cancel"), + Theme: th, } for _, b := range [...]*material.ButtonStyle{&ret.AltStyle, &ret.OkStyle, &ret.CancelStyle} { - b.Background = transparent + b.Background = color.NRGBA{} b.Inset = layout.UniformInset(unit.Dp(6)) - b.Color = th.Palette.Fg + b.Color = th.Material.Palette.Fg } return ret } @@ -106,17 +107,14 @@ func (d *DialogStyle) Layout(gtx C) D { gtx.Execute(key.FocusCmd{Tag: &d.dialog.BtnCancel}) } d.dialog.handleKeys(gtx) - paint.Fill(gtx.Ops, dialogBgColor) - text := func(gtx C) D { - return d.TextInset.Layout(gtx, LabelStyle{Text: d.Text, Color: highEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(14), Shaper: d.Shaper}.Layout) - } + paint.Fill(gtx.Ops, d.Theme.Dialog.Bg) visible := true return layout.Center.Layout(gtx, func(gtx C) D { - return Popup(&visible).Layout(gtx, func(gtx C) D { + return Popup(d.Theme, &visible).Layout(gtx, func(gtx C) D { return d.Inset.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(Label(d.Title, highEmphasisTextColor, d.Shaper)), - layout.Rigid(text), + layout.Rigid(Label(d.Theme, &d.Theme.Dialog.Title, d.Title).Layout), + layout.Rigid(Label(d.Theme, &d.Theme.Dialog.Text, d.Text).Layout), layout.Rigid(func(gtx C) D { return layout.E.Layout(gtx, func(gtx C) D { gtx.Constraints.Min.X = gtx.Dp(unit.Dp(120)) diff --git a/tracker/gioui/draglist.go b/tracker/gioui/draglist.go index 993750a..cbe22ef 100644 --- a/tracker/gioui/draglist.go +++ b/tracker/gioui/draglist.go @@ -15,8 +15,6 @@ import ( "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" - "gioui.org/unit" - "gioui.org/widget/material" "github.com/vsariola/sointu/tracker" ) @@ -34,27 +32,27 @@ type DragList struct { } type FilledDragListStyle struct { - dragList *DragList - HoverColor color.NRGBA - SelectedColor color.NRGBA - CursorColor color.NRGBA - ScrollBarWidth unit.Dp - element, bg func(gtx C, i int) D + dragList *DragList + HoverColor color.NRGBA + Cursor CursorStyle + Selection CursorStyle + ScrollBar ScrollBarStyle + element, bg func(gtx C, i int) D } func NewDragList(model tracker.List, axis layout.Axis) *DragList { return &DragList{TrackerList: model, List: &layout.List{Axis: axis}, HoverItem: -1, ScrollBar: &ScrollBar{Axis: axis}} } -func FilledDragList(th *material.Theme, dragList *DragList, element, bg func(gtx C, i int) D) FilledDragListStyle { +func FilledDragList(th *Theme, dragList *DragList, element, bg func(gtx C, i int) D) FilledDragListStyle { return FilledDragListStyle{ - dragList: dragList, - element: element, - bg: bg, - HoverColor: dragListHoverColor, - SelectedColor: dragListSelectedColor, - CursorColor: cursorColor, - ScrollBarWidth: unit.Dp(10), + dragList: dragList, + element: element, + bg: bg, + HoverColor: hoveredColor(th.Selection.Active), + Cursor: th.Cursor, + Selection: th.Selection, + ScrollBar: th.ScrollBar, } } @@ -67,7 +65,7 @@ func (d *DragList) Focused() bool { } func (s FilledDragListStyle) LayoutScrollBar(gtx C) D { - return s.dragList.ScrollBar.Layout(gtx, s.ScrollBarWidth, s.dragList.TrackerList.Count(), &s.dragList.List.Position) + return s.dragList.ScrollBar.Layout(gtx, &s.ScrollBar, s.dragList.TrackerList.Count(), &s.dragList.List.Position) } func (s FilledDragListStyle) Layout(gtx C) D { @@ -147,12 +145,16 @@ func (s FilledDragListStyle) Layout(gtx C) D { var color color.NRGBA if s.dragList.TrackerList.Selected() == index { if s.dragList.focused { - color = s.CursorColor + color = s.Cursor.Active } else { - color = s.SelectedColor + color = s.Cursor.Inactive } } else if between(s.dragList.TrackerList.Selected(), index, s.dragList.TrackerList.Selected2()) { - color = s.SelectedColor + if s.dragList.focused { + color = s.Selection.Active + } else { + color = s.Selection.Inactive + } } else if s.dragList.HoverItem == index { color = s.HoverColor } diff --git a/tracker/gioui/editor.go b/tracker/gioui/editor.go index 82199d4..39e8b48 100644 --- a/tracker/gioui/editor.go +++ b/tracker/gioui/editor.go @@ -1,8 +1,12 @@ package gioui import ( + "image/color" + + "gioui.org/font" "gioui.org/io/event" "gioui.org/io/key" + "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" ) @@ -18,7 +22,12 @@ type ( requestFocus bool } - EditorStyle material.EditorStyle + EditorStyle struct { + Color color.NRGBA + HintColor color.NRGBA + Font font.Font + TextSize unit.Sp + } ) func NewEditor(e widget.Editor) *Editor { @@ -36,8 +45,21 @@ func NewEditor(e widget.Editor) *Editor { return ret } -func MaterialEditor(th *material.Theme, e *Editor, hint string) EditorStyle { - return EditorStyle(material.Editor(th, &e.Editor, hint)) +func (s *EditorStyle) AsLabelStyle() LabelStyle { + return LabelStyle{ + Color: s.Color, + Font: s.Font, + TextSize: s.TextSize, + } +} + +func MaterialEditor(th *Theme, style *EditorStyle, editor *Editor, hint string) material.EditorStyle { + ret := material.Editor(&th.Material, &editor.Editor, hint) + ret.Font = style.Font + ret.TextSize = style.TextSize + ret.Color = style.Color + ret.HintColor = style.HintColor + return ret } func (e *Editor) SetText(s string) { @@ -80,7 +102,3 @@ func (e *Editor) Cancelled(gtx C) bool { func (e *Editor) Focus() { e.requestFocus = true } - -func (e *EditorStyle) Layout(gtx C) D { - return material.EditorStyle(*e).Layout(gtx) -} diff --git a/tracker/gioui/instrument_editor.go b/tracker/gioui/instrument_editor.go index 1d30721..7fbbc72 100644 --- a/tracker/gioui/instrument_editor.go +++ b/tracker/gioui/instrument_editor.go @@ -9,7 +9,6 @@ import ( "strconv" "strings" - "gioui.org/font" "gioui.org/io/clipboard" "gioui.org/io/key" "gioui.org/layout" @@ -130,7 +129,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, t.OctaveNumberInput, ie.octaveHint) + numStyle := NumUpDown(t.Theme, t.OctaveNumberInput, ie.octaveHint) dims := in.Layout(gtx, numStyle.Layout) return dims } @@ -144,7 +143,7 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D { return ie.layoutInstrumentList(gtx, t) }), layout.Rigid(layout.Spacer{Width: 10}.Layout), - layout.Rigid(LabelStyle{Text: "Octave", Color: disabledTextColor, Alignment: layout.W, FontSize: t.Theme.TextSize * 14.0 / 16.0, Shaper: t.Theme.Shaper}.Layout), + layout.Rigid(Label(t.Theme, &t.Theme.InstrumentEditor.Octave, "Octave").Layout), layout.Rigid(layout.Spacer{Width: 4}.Layout), layout.Rigid(octave), layout.Rigid(func(gtx C) D { @@ -186,7 +185,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D { soloBtnStyle := ToggleIcon(gtx, t.Theme, ie.soloBtn, icons.SocialGroup, icons.SocialPerson, ie.soloHint, ie.unsoloHint) muteBtnStyle := ToggleIcon(gtx, t.Theme, ie.muteBtn, icons.AVVolumeUp, icons.AVVolumeOff, ie.muteHint, ie.unmuteHint) - m := PopupMenu(&ie.presetMenu, t.Theme.Shaper) + m := PopupMenu(t.Theme, &t.Theme.Menu.Text, &ie.presetMenu) for ie.copyInstrumentBtn.Clickable.Clicked(gtx) { if contents, ok := t.Instruments().List().CopyElements(); ok { @@ -214,10 +213,10 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D { 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(LabelStyle{Text: "Voices", Color: disabledTextColor, Alignment: layout.W, FontSize: t.Theme.TextSize * 14.0 / 16.0, Shaper: t.Theme.Shaper}.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 { - numStyle := NumericUpDown(t.Theme, 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 }), @@ -254,8 +253,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D { for ie.commentEditor.Submitted(gtx) || ie.commentEditor.Cancelled(gtx) { ie.instrumentDragList.Focus() } - style := MaterialEditor(t.Theme, ie.commentEditor, "Comment") - style.Color = highEmphasisTextColor + style := MaterialEditor(t.Theme, &t.Theme.InstrumentEditor.InstrumentComment, ie.commentEditor, "Comment") ret := layout.UniformInset(unit.Dp(6)).Layout(gtx, style.Layout) ie.commentString.Set(ie.commentEditor.Text()) return ret @@ -270,33 +268,29 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D { } func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D { - gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(36)) + gtx.Constraints.Max.Y = gtx.Dp(36) + gtx.Constraints.Min.Y = gtx.Dp(36) element := func(gtx C, i int) D { - gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36)) - gtx.Constraints.Min.X = gtx.Dp(unit.Dp(30)) - grabhandle := LabelStyle{Text: strconv.Itoa(i + 1), ShadeColor: black, Color: mediumEmphasisTextColor, FontSize: unit.Sp(10), Alignment: layout.Center, Shaper: t.Theme.Shaper} + grabhandle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, strconv.Itoa(i+1)) label := func(gtx C) D { name, level, mute, ok := (*tracker.Instruments)(t.Model).Item(i) if !ok { - labelStyle := LabelStyle{Text: "", ShadeColor: black, Color: white, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} + labelStyle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, "") return layout.Center.Layout(gtx, labelStyle.Layout) } k := byte(255 - level*127) color := color.NRGBA{R: 255, G: k, B: 255, A: 255} + s := t.Theme.InstrumentEditor.InstrumentList.Name + if mute { + s = t.Theme.InstrumentEditor.InstrumentList.NameMuted + } + s.Color = color if i == ie.instrumentDragList.TrackerList.Selected() { ie.nameEditor.SetText(name) for ie.nameEditor.Submitted(gtx) || ie.nameEditor.Cancelled(gtx) { ie.instrumentDragList.Focus() } - style := MaterialEditor(t.Theme, ie.nameEditor, "Instr") - style.Color = color - style.HintColor = instrumentNameHintColor - style.TextSize = unit.Sp(12) - style.Font = labelDefaultFont - if mute { - style.Color = disabledTextColor - style.Font.Style = font.Italic - } + style := MaterialEditor(t.Theme, &s, ie.nameEditor, "Instr") dims := layout.Center.Layout(gtx, func(gtx C) D { defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() return style.Layout(gtx) @@ -307,29 +301,21 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D { if name == "" { name = "Instr" } - labelStyle := LabelStyle{Text: name, ShadeColor: black, Color: color, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} - if mute { - labelStyle.Color = disabledTextColor - labelStyle.Font.Style = font.Italic - } - return layout.Center.Layout(gtx, labelStyle.Layout) + l := s.AsLabelStyle() + return layout.Center.Layout(gtx, Label(t.Theme, &l, name).Layout) } - return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6), Top: unit.Dp(4)}.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(grabhandle.Layout), - layout.Rigid(label), - ) + return layout.Center.Layout(gtx, func(gtx C) D { + return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6)}.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(grabhandle.Layout), + layout.Rigid(label), + ) + }) }) } - color := inactiveLightSurfaceColor - if ie.wasFocused { - color = activeLightSurfaceColor - } instrumentList := FilledDragList(t.Theme, ie.instrumentDragList, element, nil) - instrumentList.SelectedColor = color - instrumentList.HoverColor = instrumentHoverColor - instrumentList.ScrollBarWidth = unit.Dp(6) + instrumentList.ScrollBar = t.Theme.InstrumentEditor.InstrumentList.ScrollBar defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() @@ -367,8 +353,8 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D { func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D { // TODO: how to ie.unitDragList.Focus() addUnitBtnStyle := ActionIcon(gtx, t.Theme, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)") - addUnitBtnStyle.IconButtonStyle.Color = t.Theme.ContrastFg - addUnitBtnStyle.IconButtonStyle.Background = t.Theme.Fg + addUnitBtnStyle.IconButtonStyle.Color = t.Theme.Material.ContrastFg + addUnitBtnStyle.IconButtonStyle.Background = t.Theme.Material.ContrastBg addUnitBtnStyle.IconButtonStyle.Inset = layout.UniformInset(unit.Dp(4)) var units [256]tracker.UnitListItem @@ -393,23 +379,23 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D { return layout.Dimensions{Size: gtx.Constraints.Min} } u := units[i] - var color color.NRGBA = white - f := labelDefaultFont + + editorStyle := t.Theme.InstrumentEditor.UnitList.Name + if u.Disabled { + editorStyle = t.Theme.InstrumentEditor.UnitList.NameDisabled + } stackText := strconv.FormatInt(int64(u.StackAfter), 10) if u.StackNeed > u.StackBefore { - color = errorColor + editorStyle.Color = t.Theme.InstrumentEditor.UnitList.Error (*tracker.Alerts)(t.Model).AddNamed("UnitNeedsInputs", fmt.Sprintf("%v needs at least %v input signals, got %v", u.Type, u.StackNeed, u.StackBefore), tracker.Error) } else if i == count-1 && u.StackAfter != 0 { - color = warningColor + editorStyle.Color = t.Theme.InstrumentEditor.UnitList.Warning (*tracker.Alerts)(t.Model).AddNamed("InstrumentLeavesSignals", fmt.Sprintf("Instrument leaves %v signal(s) on the stack", u.StackAfter), tracker.Warning) } - if u.Disabled { - color = disabledTextColor - f.Style = font.Italic - } - stackLabel := LabelStyle{Text: stackText, ShadeColor: black, Color: mediumEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} + stackLabel := Label(t.Theme, &t.Theme.InstrumentEditor.UnitList.Stack, stackText) + rightMargin := layout.Inset{Right: unit.Dp(10)} return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -435,24 +421,21 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D { ie.searchEditor.SetText(str.Value()) ie.unitDragList.Focus() } - style := MaterialEditor(t.Theme, ie.searchEditor, "---") - style.Color = color - style.HintColor = instrumentNameHintColor - style.TextSize = unit.Sp(12) - style.Font = f + style := MaterialEditor(t.Theme, &editorStyle, ie.searchEditor, "---") ret := style.Layout(gtx) str.Set(ie.searchEditor.Text()) return ret } else { - unitNameLabel := LabelStyle{Text: u.Type, ShadeColor: black, Color: color, Font: f, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} - if unitNameLabel.Text == "" { - unitNameLabel.Text = "---" + text := u.Type + if text == "" { + text = "---" } - return unitNameLabel.Layout(gtx) + l := editorStyle.AsLabelStyle() + return Label(t.Theme, &l, text).Layout(gtx) } }), layout.Flexed(1, func(gtx C) D { - unitNameLabel := LabelStyle{Text: u.Comment, ShadeColor: black, Color: mediumEmphasisTextColor, Font: f, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} + unitNameLabel := Label(t.Theme, &t.Theme.InstrumentEditor.UnitList.Comment, u.Comment) inset := layout.Inset{Left: unit.Dp(5)} return inset.Layout(gtx, unitNameLabel.Layout) }), diff --git a/tracker/gioui/label.go b/tracker/gioui/label.go index 1c2081a..fa97e22 100644 --- a/tracker/gioui/label.go +++ b/tracker/gioui/label.go @@ -5,7 +5,6 @@ import ( "image/color" "gioui.org/font" - "gioui.org/layout" "gioui.org/op" "gioui.org/op/paint" "gioui.org/text" @@ -14,37 +13,38 @@ import ( ) type LabelStyle struct { - Text string - Color color.NRGBA - ShadeColor color.NRGBA - Alignment layout.Direction - Font font.Font - FontSize unit.Sp - Shaper *text.Shaper + Color color.NRGBA + ShadowColor color.NRGBA + Alignment text.Alignment + Font font.Font + TextSize unit.Sp } -func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions { - return l.Alignment.Layout(gtx, func(gtx C) D { - gtx.Constraints.Min = image.Point{} - paint.ColorOp{Color: l.ShadeColor}.Add(gtx.Ops) +type LabelWidget struct { + Text string + Shaper *text.Shaper + LabelStyle +} + +func (l LabelWidget) Layout(gtx C) D { + textColorMacro := op.Record(gtx.Ops) + paint.ColorOp{Color: l.Color}.Add(gtx.Ops) + textColor := textColorMacro.Stop() + t := widget.Label{ + Alignment: l.Alignment, + MaxLines: 1, + } + if l.ShadowColor.A > 0 { + shadowColorMacro := op.Record(gtx.Ops) + paint.ColorOp{Color: l.ShadowColor}.Add(gtx.Ops) + shadowColor := shadowColorMacro.Stop() offs := op.Offset(image.Pt(2, 2)).Push(gtx.Ops) - widget.Label{ - Alignment: text.Start, - MaxLines: 1, - }.Layout(gtx, l.Shaper, l.Font, l.FontSize, l.Text, op.CallOp{}) + t.Layout(gtx, l.Shaper, l.Font, l.TextSize, l.Text, shadowColor) offs.Pop() - paint.ColorOp{Color: l.Color}.Add(gtx.Ops) - dims := widget.Label{ - Alignment: text.Start, - MaxLines: 1, - }.Layout(gtx, l.Shaper, l.Font, l.FontSize, l.Text, op.CallOp{}) - return layout.Dimensions{ - Size: dims.Size, - Baseline: dims.Baseline, - } - }) + } + return t.Layout(gtx, l.Shaper, l.Font, l.TextSize, l.Text, textColor) } -func Label(str string, color color.NRGBA, shaper *text.Shaper) layout.Widget { - return LabelStyle{Text: str, Color: color, ShadeColor: black, Font: labelDefaultFont, FontSize: labelDefaultFontSize, Alignment: layout.W, Shaper: shaper}.Layout +func Label(th *Theme, style *LabelStyle, txt string) LabelWidget { + return LabelWidget{Text: txt, Shaper: th.Material.Shaper, LabelStyle: *style} } diff --git a/tracker/gioui/menu.go b/tracker/gioui/menu.go index baac7df..8f1aadb 100644 --- a/tracker/gioui/menu.go +++ b/tracker/gioui/menu.go @@ -10,7 +10,6 @@ import ( "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" - "gioui.org/text" "gioui.org/unit" "github.com/vsariola/sointu/tracker" ) @@ -27,13 +26,11 @@ type Menu struct { type MenuStyle struct { Menu *Menu Title string - IconColor color.NRGBA - TextColor color.NRGBA ShortCutColor color.NRGBA - FontSize unit.Sp - IconSize unit.Dp HoverColor color.NRGBA - Shaper *text.Shaper + Theme *Theme + LabelStyle LabelStyle + Disabled color.NRGBA } type MenuItem struct { @@ -100,21 +97,21 @@ func (m *MenuStyle) Layout(gtx C, items ...MenuItem) D { macro = op.Record(gtx.Ops) } icon := widgetForIcon(item.IconBytes) - iconColor := m.IconColor - if !item.Doer.Allowed() { - iconColor = mediumEmphasisTextColor - } + iconColor := m.LabelStyle.Color iconInset := layout.Inset{Left: unit.Dp(12), Right: unit.Dp(6)} - textLabel := LabelStyle{Text: item.Text, FontSize: m.FontSize, Color: m.TextColor, Shaper: m.Shaper} + textLabel := Label(m.Theme, &m.Theme.Menu.Text, item.Text) + shortcutLabel := Label(m.Theme, &m.Theme.Menu.Text, item.ShortcutText) + shortcutLabel.Color = m.ShortCutColor if !item.Doer.Allowed() { - textLabel.Color = mediumEmphasisTextColor + iconColor = m.Disabled + textLabel.Color = m.Disabled + shortcutLabel.Color = m.Disabled } - shortcutLabel := LabelStyle{Text: item.ShortcutText, FontSize: m.FontSize, Color: m.ShortCutColor, Shaper: m.Shaper} shortcutInset := layout.Inset{Left: unit.Dp(12), Right: unit.Dp(12), Bottom: unit.Dp(2), Top: unit.Dp(2)} dims := layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { return iconInset.Layout(gtx, func(gtx C) D { - p := gtx.Dp(unit.Dp(m.IconSize)) + p := gtx.Dp(unit.Dp(m.LabelStyle.TextSize)) gtx.Constraints.Min = image.Pt(p, p) return icon.Layout(gtx, iconColor) }) @@ -142,27 +139,25 @@ func (m *MenuStyle) Layout(gtx C, items ...MenuItem) D { }) }), layout.Expanded(func(gtx C) D { - return m.Menu.scrollBar.Layout(gtx, unit.Dp(10), len(items), &m.Menu.list.Position) + return m.Menu.scrollBar.Layout(gtx, &m.Theme.ScrollBar, len(items), &m.Menu.list.Position) }), ) } - popup := Popup(&m.Menu.Visible) + popup := Popup(m.Theme, &m.Menu.Visible) popup.NE = unit.Dp(0) popup.ShadowN = unit.Dp(0) popup.NW = unit.Dp(0) return popup.Layout(gtx, contents) } -func PopupMenu(menu *Menu, shaper *text.Shaper) MenuStyle { +func PopupMenu(th *Theme, s *LabelStyle, menu *Menu) MenuStyle { return MenuStyle{ Menu: menu, - IconColor: white, - TextColor: white, - ShortCutColor: mediumEmphasisTextColor, - FontSize: unit.Sp(16), - IconSize: unit.Dp(16), - HoverColor: menuHoverColor, - Shaper: shaper, + ShortCutColor: th.Menu.ShortCut, + LabelStyle: *s, + HoverColor: th.Menu.Hover, + Disabled: th.Menu.Disabled, + Theme: th, } } @@ -170,12 +165,10 @@ func (tr *Tracker) layoutMenu(gtx C, title string, clickable *Clickable, menu *M for clickable.Clicked(gtx) { menu.Visible = true } - m := PopupMenu(menu, tr.Theme.Shaper) + m := PopupMenu(tr.Theme, &tr.Theme.Menu.Text, menu) return func(gtx C) D { defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() - titleBtn := Button(tr.Theme, clickable, title) - titleBtn.Color = white - titleBtn.Background = transparent + titleBtn := Btn(tr.Theme, &tr.Theme.Button.Menu, clickable, title) titleBtn.CornerRadius = unit.Dp(0) dims := titleBtn.Layout(gtx) op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops) diff --git a/tracker/gioui/note_editor.go b/tracker/gioui/note_editor.go index 39173bd..43bf09c 100644 --- a/tracker/gioui/note_editor.go +++ b/tracker/gioui/note_editor.go @@ -150,17 +150,17 @@ 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, te.AddSemitoneBtn, "+1") - subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, te.SubtractSemitoneBtn, "-1") - addOctaveBtnStyle := ActionButton(gtx, t.Theme, te.AddOctaveBtn, "+12") - subtractOctaveBtnStyle := ActionButton(gtx, t.Theme, te.SubtractOctaveBtn, "-12") - noteOffBtnStyle := ActionButton(gtx, t.Theme, te.NoteOffBtn, "Note Off") + addSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.AddSemitoneBtn, "+1") + subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.SubtractSemitoneBtn, "-1") + addOctaveBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.AddOctaveBtn, "+12") + subtractOctaveBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.SubtractOctaveBtn, "-12") + noteOffBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.NoteOffBtn, "Note Off") deleteTrackBtnStyle := ActionIcon(gtx, t.Theme, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint) splitTrackBtnStyle := ActionIcon(gtx, t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint) newTrackBtnStyle := ActionIcon(gtx, t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint) in := layout.UniformInset(unit.Dp(1)) voiceUpDown := func(gtx C) D { - numStyle := NumericUpDown(t.Theme, te.TrackVoices, "Track voices") + numStyle := NumUpDown(t.Theme, te.TrackVoices, "Track voices") return in.Layout(gtx, numStyle.Layout) } effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex") @@ -176,7 +176,7 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D { layout.Rigid(effectBtnStyle.Layout), layout.Rigid(uniqueBtnStyle.Layout), layout.Rigid(layout.Spacer{Width: 10}.Layout), - layout.Rigid(LabelStyle{Text: "Voices", Color: disabledTextColor, Alignment: layout.W, FontSize: t.Theme.TextSize * 14.0 / 16.0, Shaper: t.Theme.Shaper}.Layout), + layout.Rigid(Label(t.Theme, &t.Theme.NoteEditor.Header, "Voices").Layout), layout.Rigid(layout.Spacer{Width: 4}.Layout), layout.Rigid(voiceUpDown), layout.Rigid(splitTrackBtnStyle.Layout), @@ -224,28 +224,26 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D { colTitle := func(gtx C, i int) D { h := gtx.Dp(trackColTitleHeight) gtx.Constraints = layout.Exact(image.Pt(pxWidth, h)) - LabelStyle{ - Alignment: layout.N, - Text: t.Model.TrackTitle(i), - FontSize: unit.Sp(12), - Color: mediumEmphasisTextColor, - Shaper: t.Theme.Shaper, - }.Layout(gtx) + Label(t.Theme, &t.Theme.NoteEditor.TrackTitle, t.Model.TrackTitle(i)).Layout(gtx) return D{Size: image.Pt(pxWidth, h)} } rowTitleBg := func(gtx C, j int) D { if mod(j, beatMarkerDensity*2) == 0 { - paint.FillShape(gtx.Ops, twoBeatHighlight, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op()) + paint.FillShape(gtx.Ops, t.Theme.NoteEditor.TwoBeat, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op()) } else if mod(j, beatMarkerDensity) == 0 { - paint.FillShape(gtx.Ops, oneBeatHighlight, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op()) + paint.FillShape(gtx.Ops, t.Theme.NoteEditor.OneBeat, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op()) } if t.Model.Playing().Value() && j == playSongRow { - paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op()) + paint.FillShape(gtx.Ops, t.Theme.NoteEditor.Play, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op()) } return D{} } + orderRowOp := colorOp(gtx, t.Theme.NoteEditor.OrderRow.Color) + loopColorOp := colorOp(gtx, t.Theme.OrderEditor.Loop) + patternRowOp := colorOp(gtx, t.Theme.NoteEditor.PatternRow.Color) + rowTitle := func(gtx C, j int) D { rpp := max(t.RowsPerPattern().Value(), 1) pat := j / rpp @@ -253,16 +251,14 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D { w := pxPatMarkWidth + pxRowMarkWidth defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() if row == 0 { - color := rowMarkerPatternTextColor + op := orderRowOp if l := t.Loop(); pat >= l.Start && pat < l.Start+l.Length { - color = loopMarkerColor + op = loopColorOp } - paint.ColorOp{Color: color}.Add(gtx.Ops) - widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", pat)), op.CallOp{}) + widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.OrderRow.Font, t.Theme.NoteEditor.OrderRow.TextSize, strings.ToUpper(fmt.Sprintf("%02x", pat)), op) } defer op.Offset(image.Pt(pxPatMarkWidth, 0)).Push(gtx.Ops).Pop() - paint.ColorOp{Color: rowMarkerRowTextColor}.Add(gtx.Ops) - widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", row)), op.CallOp{}) + widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.PatternRow.Font, t.Theme.NoteEditor.PatternRow.TextSize, strings.ToUpper(fmt.Sprintf("%02x", row)), patternRowOp) return D{Size: image.Pt(w, pxHeight)} } @@ -271,25 +267,28 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D { selection := te.scrollTable.Table.Range() hasTrackMidiIn := te.TrackMidiInBtn.Bool.Value() + patternNoOp := colorOp(gtx, t.Theme.NoteEditor.PatternNo.Color) + uniqueOp := colorOp(gtx, t.Theme.NoteEditor.Unique.Color) + noteOp := colorOp(gtx, t.Theme.NoteEditor.Note.Color) + cell := func(gtx C, x, y int) D { // draw the background, to indicate selection - color := transparent point := tracker.Point{X: x, Y: y} if drawSelection && selection.Contains(point) { - color = inactiveSelectionColor + color := t.Theme.Selection.Inactive if te.scrollTable.Focused() { - color = selectionColor + color = t.Theme.Selection.Active } + paint.FillShape(gtx.Ops, color, clip.Rect{Min: image.Pt(0, 0), Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op()) } - paint.FillShape(gtx.Ops, color, clip.Rect{Min: image.Pt(0, 0), Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op()) // draw the cursor if point == cursor { - c := inactiveSelectionColor + c := t.Theme.Cursor.Inactive if te.scrollTable.Focused() { - c = cursorColor + c = t.Theme.Cursor.Active } if hasTrackMidiIn { - c = cursorForTrackMidiInColor + c = t.Theme.Cursor.ActiveAlt } te.paintColumnCell(gtx, x, t, c) } @@ -297,7 +296,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D { if hasTrackMidiIn { for _, trackIndex := range t.Model.TracksWithSameInstrumentAsCurrent() { if x == trackIndex && y == cursor.Y { - te.paintColumnCell(gtx, x, t, cursorNeighborForTrackMidiInColor) + te.paintColumnCell(gtx, x, t, t.Theme.Selection.ActiveAlt) } } } @@ -309,23 +308,17 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D { defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() s := t.Model.Order().Value(tracker.Point{X: x, Y: pat}) if row == 0 { // draw the pattern marker - paint.ColorOp{Color: trackerPatMarker}.Add(gtx.Ops) - widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, patternIndexToString(s), op.CallOp{}) + widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.PatternNo.Font, t.Theme.NoteEditor.PatternNo.TextSize, patternIndexToString(s), patternNoOp) } if row == 1 && t.Model.PatternUnique(x, s) { // draw a * if the pattern is unique - paint.ColorOp{Color: mediumEmphasisTextColor}.Add(gtx.Ops) - widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, "*", op.CallOp{}) - } - if te.scrollTable.Table.Cursor() == point && te.scrollTable.Focused() { - paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops) - } else { - paint.ColorOp{Color: trackerInactiveTextColor}.Add(gtx.Ops) + widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.Unique.Font, t.Theme.NoteEditor.Unique.TextSize, "*", uniqueOp) } + op := noteOp val := noteStr[byte(t.Model.Notes().Value(tracker.Point{X: x, Y: y}))] if t.Model.Notes().Effect(x) { val = hexStr[byte(t.Model.Notes().Value(tracker.Point{X: x, Y: y}))] } - widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, val, op.CallOp{}) + widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.Note.Font, t.Theme.NoteEditor.Note.TextSize, val, op) return D{Size: image.Pt(pxWidth, pxHeight)} } table := FilledScrollTable(t.Theme, te.scrollTable, cell, colTitle, rowTitle, nil, rowTitleBg) @@ -336,6 +329,12 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D { return table.Layout(gtx) } +func colorOp(gtx C, c color.NRGBA) op.CallOp { + macro := op.Record(gtx.Ops) + paint.ColorOp{Color: c}.Add(gtx.Ops) + return macro.Stop() +} + func (te *NoteEditor) paintColumnCell(gtx C, x int, t *Tracker, c color.NRGBA) { cw := gtx.Constraints.Min.X cx := 0 diff --git a/tracker/gioui/numericupdown.go b/tracker/gioui/numericupdown.go index bfa231d..374c238 100644 --- a/tracker/gioui/numericupdown.go +++ b/tracker/gioui/numericupdown.go @@ -12,6 +12,7 @@ import ( "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" + "gioui.org/unit" "gioui.org/widget" "gioui.org/x/component" @@ -20,8 +21,6 @@ import ( "gioui.org/io/pointer" "gioui.org/layout" "gioui.org/text" - "gioui.org/unit" - "gioui.org/widget/material" ) type NumberInput struct { @@ -34,48 +33,41 @@ type NumberInput struct { } type NumericUpDownStyle struct { - NumberInput *NumberInput - Color color.NRGBA - Font font.Font - TextSize unit.Sp - BorderColor color.NRGBA - IconColor color.NRGBA - BackgroundColor color.NRGBA - CornerRadius unit.Dp - Border unit.Dp - ButtonWidth unit.Dp - UnitsPerStep unit.Dp - Tooltip component.Tooltip - Width unit.Dp - Height unit.Dp - shaper text.Shaper + 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 *material.Theme, number *NumberInput, tooltip string) NumericUpDownStyle { - return NumericUpDownStyle{ - NumberInput: number, - Color: white, - IconColor: th.Palette.Fg, - BackgroundColor: numberInputBgColor, - CornerRadius: unit.Dp(4), - ButtonWidth: unit.Dp(16), - Border: unit.Dp(1), - UnitsPerStep: unit.Dp(8), - TextSize: th.TextSize * 14 / 16, - Tooltip: Tooltip(th, tooltip), - Width: unit.Dp(70), - Height: unit.Dp(20), - shaper: *th.Shaper, +func NumUpDown(th *Theme, number *NumberInput, tooltip string) NumericUpDown { + return NumericUpDown{ + NumberInput: number, + Shaper: th.Material.Shaper, + Tooltip: Tooltip(th, 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.UnitsPerStep)) + pxPerStep := float32(gtx.Dp(s.DpPerStep)) for { ev, ok := gtx.Event(pointer.Filter{ Target: s.NumberInput, @@ -110,14 +102,14 @@ 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.Width), gtx.Dp(s.Height))) width := gtx.Dp(s.ButtonWidth) @@ -125,7 +117,7 @@ func (s *NumericUpDownStyle) actualLayout(gtx C) D { 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.BackgroundColor) + 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} }, @@ -143,8 +135,8 @@ func (s *NumericUpDownStyle) actualLayout(gtx C) D { ) }), layout.Flexed(1, func(gtx C) D { - paint.ColorOp{Color: s.Color}.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{}) + 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/order_editor.go b/tracker/gioui/order_editor.go index 679b145..90b4bfd 100644 --- a/tracker/gioui/order_editor.go +++ b/tracker/gioui/order_editor.go @@ -68,54 +68,53 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D { defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(h)})).Push(gtx.Ops).Pop() gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6)) - LabelStyle{ - Alignment: layout.NW, - Text: t.Model.TrackTitle(i), - FontSize: unit.Sp(12), - Color: mediumEmphasisTextColor, - Shaper: t.Theme.Shaper, - }.Layout(gtx) + Label(t.Theme, &t.Theme.OrderEditor.TrackTitle, t.Model.TrackTitle(i)).Layout(gtx) return D{Size: image.Pt(gtx.Dp(patternCellWidth), h)} } rowTitleBg := func(gtx C, j int) D { if t.Model.Playing().Value() && j == t.PlayPosition().OrderRow { - paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Dp(patternCellHeight))}.Op()) + paint.FillShape(gtx.Ops, t.Theme.OrderEditor.Play, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Dp(patternCellHeight))}.Op()) } return D{} } + rowMarkerPatternTextColorOp := colorOp(gtx, t.Theme.OrderEditor.RowTitle.Color) + loopMarkerColorOp := colorOp(gtx, t.Theme.OrderEditor.Loop) + rowTitle := func(gtx C, j int) D { w := gtx.Dp(unit.Dp(30)) - color := rowMarkerPatternTextColor + callOp := rowMarkerPatternTextColorOp if l := t.Loop(); j >= l.Start && j < l.Start+l.Length { - color = loopMarkerColor + callOp = loopMarkerColorOp } - paint.ColorOp{Color: color}.Add(gtx.Ops) defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() - widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j)), op.CallOp{}) + widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.OrderEditor.RowTitle.Font, t.Theme.OrderEditor.RowTitle.TextSize, strings.ToUpper(fmt.Sprintf("%02x", j)), callOp) return D{Size: image.Pt(w, gtx.Dp(patternCellHeight))} } selection := oe.scrollTable.Table.Range() + cellColorOp := colorOp(gtx, t.Theme.OrderEditor.Cell.Color) cell := func(gtx C, x, y int) D { val := patternIndexToString(t.Model.Order().Value(tracker.Point{X: x, Y: y})) - color := patternCellColor + color := t.Theme.OrderEditor.CellBg point := tracker.Point{X: x, Y: y} if selection.Contains(point) { - color = inactiveSelectionColor + color = t.Theme.Selection.Inactive if oe.scrollTable.Focused() { - color = selectionColor - if point == oe.scrollTable.Table.Cursor() { - color = cursorColor + color = t.Theme.Selection.Active + } + if point == oe.scrollTable.Table.Cursor() { + color = t.Theme.Cursor.Inactive + if oe.scrollTable.Focused() { + color = t.Theme.Cursor.Active } } } paint.FillShape(gtx.Ops, color, clip.Rect{Min: image.Pt(1, 1), Max: image.Pt(gtx.Constraints.Min.X-1, gtx.Constraints.Min.X-1)}.Op()) - paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops) defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() - widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, val, op.CallOp{}) + widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.OrderEditor.Cell.Font, t.Theme.OrderEditor.Cell.TextSize, val, cellColorOp) return D{Size: image.Pt(gtx.Dp(patternCellWidth), gtx.Dp(patternCellHeight))} } diff --git a/tracker/gioui/oscilloscope.go b/tracker/gioui/oscilloscope.go index 3720329..06ea35f 100644 --- a/tracker/gioui/oscilloscope.go +++ b/tracker/gioui/oscilloscope.go @@ -12,12 +12,11 @@ import ( "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" - "gioui.org/widget/material" "github.com/vsariola/sointu/tracker" ) type ( - Oscilloscope struct { + OscilloscopeState struct { onceBtn *BoolClickable wrapBtn *BoolClickable lengthInBeatsNumber *NumberInput @@ -31,16 +30,21 @@ type ( } OscilloscopeStyle struct { - Oscilloscope *Oscilloscope - Wave tracker.RingBuffer[[2]float32] - Colors [2]color.NRGBA - ClippedColor color.NRGBA - Theme *material.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()), @@ -48,15 +52,15 @@ func NewOscilloscope(model *tracker.Model) *Oscilloscope { } } -func LineOscilloscope(s *Oscilloscope, wave tracker.RingBuffer[[2]float32], th *material.Theme) *OscilloscopeStyle { - return &OscilloscopeStyle{Oscilloscope: s, Wave: wave, Colors: [2]color.NRGBA{primaryColor, secondaryColor}, Theme: th, ClippedColor: errorColor} +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, s.Oscilloscope.wrapBtn, "Wrap") - onceBtnStyle := ToggleButton(gtx, s.Theme, s.Oscilloscope.onceBtn, "Once") - triggerChannelStyle := NumericUpDown(s.Theme, s.Oscilloscope.triggerChannelNumber, "Trigger channel") - lengthNumberStyle := NumericUpDown(s.Theme, 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 @@ -66,7 +70,7 @@ func (s *OscilloscopeStyle) Layout(gtx C) D { layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(leftSpacer), - layout.Rigid(LabelStyle{Text: "Trigger", Color: disabledTextColor, Alignment: layout.W, FontSize: s.Theme.TextSize * 14.0 / 16.0, Shaper: s.Theme.Shaper}.Layout), + layout.Rigid(Label(s.Theme, &s.Theme.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), @@ -76,7 +80,7 @@ func (s *OscilloscopeStyle) Layout(gtx C) D { layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(leftSpacer), - layout.Rigid(LabelStyle{Text: "Buffer", Color: disabledTextColor, Alignment: layout.W, FontSize: s.Theme.TextSize * 14.0 / 16.0, Shaper: s.Theme.Shaper}.Layout), + layout.Rigid(Label(s.Theme, &s.Theme.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), @@ -86,17 +90,17 @@ 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) - paint.ColorOp{Color: oscilloscopeCursorColor}.Add(gtx.Ops) + 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)}) - paint.ColorOp{Color: oscilloscopeLimitColor}.Add(gtx.Ops) + paint.ColorOp{Color: s.Theme.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)) @@ -106,7 +110,7 @@ func (s *OscilloscopeStyle) layoutWave(gtx C) D { rightX := int(s.sampleToPx(gtx, float32(len(s.Wave.Buffer)-1))) 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.Colors[chn]}.Add(gtx.Ops) + paint.ColorOp{Color: s.Theme.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)) @@ -138,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}, }) @@ -152,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/popup.go b/tracker/gioui/popup.go index c477317..70cb19e 100644 --- a/tracker/gioui/popup.go +++ b/tracker/gioui/popup.go @@ -24,11 +24,11 @@ type PopupStyle struct { SE, SW, NW, NE unit.Dp } -func Popup(visible *bool) PopupStyle { +func Popup(th *Theme, visible *bool) PopupStyle { return PopupStyle{ Visible: visible, - SurfaceColor: popupSurfaceColor, - ShadowColor: popupShadowColor, + SurfaceColor: th.Popup.Bg, + ShadowColor: th.Popup.Shadow, ShadowN: unit.Dp(2), ShadowE: unit.Dp(2), ShadowS: unit.Dp(2), diff --git a/tracker/gioui/popup_alert.go b/tracker/gioui/popup_alert.go index 0971f0b..ee316cd 100644 --- a/tracker/gioui/popup_alert.go +++ b/tracker/gioui/popup_alert.go @@ -9,7 +9,6 @@ import ( "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" - "gioui.org/text" "gioui.org/unit" "github.com/vsariola/sointu/tracker" ) @@ -17,17 +16,21 @@ import ( type PopupAlert struct { alerts *tracker.Alerts prevUpdate time.Time - shaper *text.Shaper +} + +type PopupAlertStyle struct { + Bg color.NRGBA + Text LabelStyle } var alertMargin = layout.UniformInset(unit.Dp(6)) var alertInset = layout.UniformInset(unit.Dp(6)) -func NewPopupAlert(alerts *tracker.Alerts, shaper *text.Shaper) *PopupAlert { - return &PopupAlert{alerts: alerts, shaper: shaper, prevUpdate: time.Now()} +func NewPopupAlert(alerts *tracker.Alerts) *PopupAlert { + return &PopupAlert{alerts: alerts, prevUpdate: time.Now()} } -func (a *PopupAlert) Layout(gtx C) D { +func (a *PopupAlert) Layout(gtx C, th *Theme) D { now := time.Now() if a.alerts.Update(now.Sub(a.prevUpdate)) { gtx.Execute(op.InvalidateCmd{At: now.Add(50 * time.Millisecond)}) @@ -36,26 +39,22 @@ func (a *PopupAlert) Layout(gtx C) D { var totalY float64 = float64(gtx.Dp(38)) for _, alert := range a.alerts.Iterate { - var color, textColor, shadeColor color.NRGBA + var alertStyle *PopupAlertStyle switch alert.Priority { case tracker.Warning: - color = warningColor - textColor = black + alertStyle = &th.Alert.Warning case tracker.Error: - color = errorColor - textColor = black + alertStyle = &th.Alert.Error default: - color = popupSurfaceColor - textColor = white - shadeColor = black + alertStyle = &th.Alert.Info } bgWidget := func(gtx C) D { - paint.FillShape(gtx.Ops, color, clip.Rect{ + paint.FillShape(gtx.Ops, alertStyle.Bg, clip.Rect{ Max: gtx.Constraints.Min, }.Op()) return D{Size: gtx.Constraints.Min} } - labelStyle := LabelStyle{Text: alert.Message, Color: textColor, ShadeColor: shadeColor, Font: labelDefaultFont, Alignment: layout.Center, FontSize: unit.Sp(16), Shaper: a.shaper} + labelStyle := Label(th, &alertStyle.Text, alert.Message) alertMargin.Layout(gtx, func(gtx C) D { return layout.S.Layout(gtx, func(gtx C) D { defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() diff --git a/tracker/gioui/scroll_table.go b/tracker/gioui/scroll_table.go index a279aa0..26af0bc 100644 --- a/tracker/gioui/scroll_table.go +++ b/tracker/gioui/scroll_table.go @@ -14,7 +14,6 @@ import ( "gioui.org/op" "gioui.org/op/clip" "gioui.org/unit" - "gioui.org/widget/material" "github.com/vsariola/sointu/tracker" ) @@ -72,7 +71,7 @@ func NewScrollTable(table tracker.Table, vertList, horizList tracker.List) *Scro return ret } -func FilledScrollTable(th *material.Theme, scrollTable *ScrollTable, element func(gtx C, x, y int) D, colTitle, rowTitle, colTitleBg, rowTitleBg func(gtx C, i int) D) ScrollTableStyle { +func FilledScrollTable(th *Theme, scrollTable *ScrollTable, element func(gtx C, x, y int) D, colTitle, rowTitle, colTitleBg, rowTitleBg func(gtx C, i int) D) ScrollTableStyle { return ScrollTableStyle{ RowTitleStyle: FilledDragList(th, scrollTable.RowTitleList, rowTitle, rowTitleBg), ColTitleStyle: FilledDragList(th, scrollTable.ColTitleList, colTitle, colTitleBg), diff --git a/tracker/gioui/scrollbar.go b/tracker/gioui/scrollbar.go index 03fc21a..506bdd1 100644 --- a/tracker/gioui/scrollbar.go +++ b/tracker/gioui/scrollbar.go @@ -2,6 +2,7 @@ package gioui import ( "image" + "image/color" "gioui.org/f32" "gioui.org/io/event" @@ -21,19 +22,27 @@ type ScrollBar struct { tag bool } -func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Position) D { +type ScrollBarStyle struct { + Color color.NRGBA + Width unit.Dp + Gradient color.NRGBA +} + +func (s *ScrollBar) Layout(gtx C, style *ScrollBarStyle, numItems int, pos *layout.Position) D { defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop() gradientSize := gtx.Dp(unit.Dp(4)) var totalPixelsEstimate, scrollBarRelLength float32 + transparent := style.Gradient + transparent.A = 0 switch s.Axis { case layout.Vertical: if pos.First > 0 || pos.Offset > 0 { - paint.LinearGradientOp{Color1: black, Color2: transparent, Stop2: f32.Pt(0, float32(gradientSize))}.Add(gtx.Ops) + paint.LinearGradientOp{Color1: style.Gradient, Color2: transparent, Stop2: f32.Pt(0, float32(gradientSize))}.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) } if pos.BeforeEnd { - paint.LinearGradientOp{Color1: black, Color2: transparent, Stop1: f32.Pt(0, float32(gtx.Constraints.Min.Y)), Stop2: f32.Pt(0, float32(gtx.Constraints.Min.Y-gradientSize))}.Add(gtx.Ops) + paint.LinearGradientOp{Color1: style.Gradient, Color2: transparent, Stop1: f32.Pt(0, float32(gtx.Constraints.Min.Y)), Stop2: f32.Pt(0, float32(gtx.Constraints.Min.Y-gradientSize))}.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) } totalPixelsEstimate = float32(gtx.Constraints.Min.Y+pos.Offset-pos.OffsetLast) * float32(numItems) / float32(pos.Count) @@ -41,11 +50,11 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit case layout.Horizontal: if pos.First > 0 || pos.Offset > 0 { - paint.LinearGradientOp{Color1: black, Color2: transparent, Stop2: f32.Pt(float32(gradientSize), 0)}.Add(gtx.Ops) + paint.LinearGradientOp{Color1: style.Gradient, Color2: transparent, Stop2: f32.Pt(float32(gradientSize), 0)}.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) } if pos.BeforeEnd { - paint.LinearGradientOp{Color1: black, Color2: transparent, Stop1: f32.Pt(float32(gtx.Constraints.Min.X), 0), Stop2: f32.Pt(float32(gtx.Constraints.Min.X-gradientSize), 0)}.Add(gtx.Ops) + paint.LinearGradientOp{Color1: style.Gradient, Color2: transparent, Stop1: f32.Pt(float32(gtx.Constraints.Min.X), 0), Stop2: f32.Pt(float32(gtx.Constraints.Min.X-gradientSize), 0)}.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) } totalPixelsEstimate = float32(gtx.Constraints.Min.X+pos.Offset-pos.OffsetLast) * float32(numItems) / float32(pos.Count) @@ -56,7 +65,7 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit } scrollBarRelStart := (float32(pos.First)*totalPixelsEstimate/float32(numItems) + float32(pos.Offset)) / totalPixelsEstimate - scrWidth := gtx.Dp(width) + scrWidth := gtx.Dp(style.Width) stack := op.Offset(image.Point{}).Push(gtx.Ops) var area clip.Stack @@ -65,7 +74,7 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit if scrollBarRelLength < 1 && (s.dragging || s.hovering) { y1 := int(scrollBarRelStart * float32(gtx.Constraints.Min.Y)) y2 := int((scrollBarRelStart + scrollBarRelLength) * float32(gtx.Constraints.Min.Y)) - paint.FillShape(gtx.Ops, scrollBarColor, clip.Rect{Min: image.Pt(gtx.Constraints.Min.X-scrWidth, y1), Max: image.Pt(gtx.Constraints.Min.X, y2)}.Op()) + paint.FillShape(gtx.Ops, style.Color, clip.Rect{Min: image.Pt(gtx.Constraints.Min.X-scrWidth, y1), Max: image.Pt(gtx.Constraints.Min.X, y2)}.Op()) } rect := image.Rect(gtx.Constraints.Min.X-scrWidth, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y) area = clip.Rect(rect).Push(gtx.Ops) @@ -73,7 +82,7 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit if scrollBarRelLength < 1 && (s.dragging || s.hovering) { x1 := int(scrollBarRelStart * float32(gtx.Constraints.Min.X)) x2 := int((scrollBarRelStart + scrollBarRelLength) * float32(gtx.Constraints.Min.X)) - paint.FillShape(gtx.Ops, scrollBarColor, clip.Rect{Min: image.Pt(x1, gtx.Constraints.Min.Y-scrWidth), Max: image.Pt(x2, gtx.Constraints.Min.Y)}.Op()) + paint.FillShape(gtx.Ops, style.Color, clip.Rect{Min: image.Pt(x1, gtx.Constraints.Min.Y-scrWidth), Max: image.Pt(x2, gtx.Constraints.Min.Y)}.Op()) } rect := image.Rect(0, gtx.Constraints.Min.Y-scrWidth, gtx.Constraints.Min.X, gtx.Constraints.Min.Y) area = clip.Rect(rect).Push(gtx.Ops) diff --git a/tracker/gioui/songpanel.go b/tracker/gioui/songpanel.go index 085c9d0..5b04922 100644 --- a/tracker/gioui/songpanel.go +++ b/tracker/gioui/songpanel.go @@ -11,7 +11,6 @@ import ( "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" - "gioui.org/widget/material" "github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/version" "golang.org/x/exp/shiny/materialdesign/icons" @@ -32,7 +31,7 @@ type SongPanel struct { Step *NumberInput SongLength *NumberInput - Scope *Oscilloscope + Scope *OscilloscopeState MenuBar *MenuBar PlayBar *PlayBar @@ -76,17 +75,7 @@ func (s *SongPanel) Layout(gtx C, t *Tracker) D { return s.MenuBar.Layout(gtx, t) }), layout.Rigid(func(gtx C) D { - return layout.Background{}.Layout(gtx, - func(gtx C) D { - // push defer clip op - defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)).Push(gtx.Ops).Pop() - paint.FillShape(gtx.Ops, songSurfaceColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) - return D{Size: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)} - }, - func(gtx C) D { - return s.PlayBar.Layout(gtx, t.Theme) - }, - ) + return s.PlayBar.Layout(gtx, t.Theme) }), layout.Rigid(func(gtx C) D { return s.layoutSongOptions(gtx, t) @@ -95,9 +84,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) + paint.FillShape(gtx.Ops, tr.Theme.SongPanel.Bg, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) var weightingTxt string switch tracker.WeightingType(tr.Model.DetectorWeighting().Value()) { @@ -111,38 +98,36 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { weightingTxt = "No weight (RMS)" } - weightingBtn := LowEmphasisButton(tr.Theme, t.WeightingTypeBtn, weightingTxt) - weightingBtn.Color = mediumEmphasisTextColor + weightingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.WeightingTypeBtn, weightingTxt) oversamplingTxt := "Sample peak" if tr.Model.Oversampling().Value() { oversamplingTxt = "True peak" } - oversamplingBtn := LowEmphasisButton(tr.Theme, t.OversamplingBtn, oversamplingTxt) - oversamplingBtn.Color = mediumEmphasisTextColor + oversamplingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.OversamplingBtn, oversamplingTxt) return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { return t.SongSettingsExpander.Layout(gtx, tr.Theme, "Song", func(gtx C) D { - return LabelStyle{Text: strconv.Itoa(tr.BPM().Value()) + " BPM", Color: mediumEmphasisTextColor, Alignment: layout.W, FontSize: tr.Theme.TextSize * 14.0 / 16.0, Shaper: tr.Theme.Shaper}.Layout(gtx) + return Label(tr.Theme, &tr.Theme.SongPanel.RowHeader, strconv.Itoa(tr.BPM().Value())+" BPM").Layout(gtx) }, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, tr.Theme, "BPM", NumericUpDown(tr.Theme, t.BPM, "Song Length").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "BPM", NumUpDown(tr.Theme, t.BPM, "Song Length").Layout) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, tr.Theme, "Song length", NumericUpDown(tr.Theme, t.SongLength, "Song Length").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "Song length", NumUpDown(tr.Theme, t.SongLength, "Song Length").Layout) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, tr.Theme, "Rows per pat", NumericUpDown(tr.Theme, t.RowsPerPattern, "Rows per pattern").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "Rows per pat", NumUpDown(tr.Theme, t.RowsPerPattern, "Rows per pattern").Layout) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, tr.Theme, "Rows per beat", NumericUpDown(tr.Theme, t.RowsPerBeat, "Rows per beat").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "Rows per beat", NumUpDown(tr.Theme, t.RowsPerBeat, "Rows per beat").Layout) }), layout.Rigid(func(gtx C) D { - return layoutSongOptionRow(gtx, tr.Theme, "Cursor step", NumericUpDown(tr.Theme, t.Step, "Cursor step").Layout) + return layoutSongOptionRow(gtx, tr.Theme, "Cursor step", NumUpDown(tr.Theme, t.Step, "Cursor step").Layout) }), ) }) @@ -150,7 +135,7 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { layout.Rigid(func(gtx C) D { return t.LoudnessExpander.Layout(gtx, tr.Theme, "Loudness", func(gtx C) D { - return LabelStyle{Text: fmt.Sprintf("%.1f dB", tr.Model.DetectorResult().Loudness[tracker.LoudnessShortTerm]), Color: mediumEmphasisTextColor, Alignment: layout.W, FontSize: tr.Theme.TextSize * 14.0 / 16.0, Shaper: tr.Theme.Shaper}.Layout(gtx) + return Label(tr.Theme, &tr.Theme.SongPanel.RowHeader, fmt.Sprintf("%.1f dB", tr.Model.DetectorResult().Loudness[tracker.LoudnessShortTerm])).Layout(gtx) }, func(gtx C) D { return layout.Flex{Axis: layout.Vertical, Alignment: layout.End}.Layout(gtx, @@ -207,36 +192,27 @@ 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{} }, scopeStyle.Layout) - }), - layout.Rigid(func(gtx C) D { - labelStyle := LabelStyle{Text: version.VersionOrHash, FontSize: unit.Sp(12), Color: mediumEmphasisTextColor, Shaper: tr.Theme.Shaper} - return labelStyle.Layout(gtx) + return t.ScopeExpander.Layout(gtx, tr.Theme, "Oscilloscope", func(gtx C) D { return D{} }, Scope(t.Scope, tr.SignalAnalyzer().Waveform(), tr.Theme).Layout) }), + layout.Rigid(Label(tr.Theme, &tr.Theme.SongPanel.Version, version.VersionOrHash).Layout), ) } -func dbLabel(th *material.Theme, value tracker.Decibel) LabelStyle { - color := mediumEmphasisTextColor +func dbLabel(th *Theme, value tracker.Decibel) LabelWidget { + ret := Label(th, &th.SongPanel.RowValue, fmt.Sprintf("%.1f dB", value)) if value >= 0 { - color = errorColor - } - return LabelStyle{ - Text: fmt.Sprintf("%.1f dB", value), - Color: color, - Alignment: layout.W, - FontSize: th.TextSize * 14.0 / 16.0, - Shaper: th.Shaper, + ret.Color = th.SongPanel.ErrorColor } + return ret } -func layoutSongOptionRow(gtx C, th *material.Theme, label string, widget layout.Widget) D { +func layoutSongOptionRow(gtx C, th *Theme, label string, widget layout.Widget) D { 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.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(leftSpacer), - layout.Rigid(LabelStyle{Text: label, Color: mediumEmphasisTextColor, Alignment: layout.W, FontSize: th.TextSize * 14.0 / 16.0, Shaper: th.Shaper}.Layout), + layout.Rigid(Label(th, &th.SongPanel.RowHeader, label).Layout), layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }), layout.Rigid(widget), layout.Rigid(rightSpacer), @@ -257,7 +233,7 @@ func (e *Expander) Update(gtx C) { } } -func (e *Expander) Layout(gtx C, th *material.Theme, title string, smallWidget, largeWidget layout.Widget) D { +func (e *Expander) Layout(gtx C, th *Theme, title string, smallWidget, largeWidget layout.Widget) D { e.Update(gtx) return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { return e.layoutHeader(gtx, th, title, smallWidget) }), @@ -275,7 +251,7 @@ func (e *Expander) Layout(gtx C, th *material.Theme, title string, smallWidget, ) } -func (e *Expander) layoutHeader(gtx C, th *material.Theme, title string, smallWidget layout.Widget) D { +func (e *Expander) layoutHeader(gtx C, th *Theme, title string, smallWidget layout.Widget) D { return layout.Background{}.Layout(gtx, func(gtx C) D { defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)).Push(gtx.Ops).Pop() @@ -287,7 +263,7 @@ func (e *Expander) layoutHeader(gtx C, th *material.Theme, title string, smallWi leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(leftSpacer), - layout.Rigid(LabelStyle{Text: title, Color: disabledTextColor, Alignment: layout.W, FontSize: th.TextSize * 14.0 / 16.0, Shaper: th.Shaper}.Layout), + layout.Rigid(Label(th, &th.SongPanel.Expander, title).Layout), layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }), layout.Rigid(func(gtx C) D { if !e.Expanded { @@ -302,7 +278,7 @@ func (e *Expander) layoutHeader(gtx C, th *material.Theme, title string, smallWi icon = icons.NavigationExpandLess } gtx.Constraints.Min = image.Pt(gtx.Dp(unit.Dp(24)), gtx.Dp(unit.Dp(24))) - return widgetForIcon(icon).Layout(gtx, th.Palette.Fg) + return widgetForIcon(icon).Layout(gtx, th.SongPanel.Expander.Color) }), ) }, @@ -359,7 +335,7 @@ func (t *MenuBar) Layout(gtx C, tr *Tracker) D { panicBtnStyle := ToggleIcon(gtx, tr.Theme, t.PanicBtn, icons.AlertErrorOutline, icons.AlertError, t.panicHint, t.panicHint) if t.PanicBtn.Bool.Value() { - panicBtnStyle.IconButtonStyle.Color = errorColor + panicBtnStyle.IconButtonStyle.Color = tr.Theme.SongPanel.ErrorColor } menuLayouts := []layout.FlexChild{ layout.Rigid(tr.layoutMenu(gtx, "File", &t.Clickables[0], &t.Menus[0], unit.Dp(200), t.fileMenuItems...)), @@ -411,7 +387,7 @@ func NewPlayBar(model *tracker.Model) *PlayBar { return ret } -func (pb *PlayBar) Layout(gtx C, th *material.Theme) D { +func (pb *PlayBar) Layout(gtx C, th *Theme) D { rewindBtnStyle := ActionIcon(gtx, th, pb.RewindBtn, icons.AVFastRewind, pb.rewindHint) playBtnStyle := ToggleIcon(gtx, th, pb.PlayingBtn, icons.AVPlayArrow, icons.AVStop, pb.playHint, pb.stopHint) recordBtnStyle := ToggleIcon(gtx, th, pb.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, pb.recordHint, pb.stopRecordHint) diff --git a/tracker/gioui/theme.go b/tracker/gioui/theme.go index 7110a43..c263934 100644 --- a/tracker/gioui/theme.go +++ b/tracker/gioui/theme.go @@ -1,82 +1,143 @@ package gioui import ( + _ "embed" + "fmt" "image/color" - "gioui.org/font/gofont" "gioui.org/text" - "gioui.org/unit" + "gioui.org/widget" + "gioui.org/widget/material" + "golang.org/x/exp/shiny/materialdesign/icons" + "gopkg.in/yaml.v2" ) -var fontCollection []text.FontFace = gofont.Collection() +type Theme struct { + Define any // this is just needed for yaml.UnmarshalStrict, so we can have "defines" in the yaml + Material material.Theme + Button struct { + Filled ButtonStyle + Text ButtonStyle + Disabled ButtonStyle + Menu ButtonStyle + } + Oscilloscope OscilloscopeStyle + NumericUpDown NumericUpDownStyle + DialogTitle LabelStyle + DialogText LabelStyle + SongPanel struct { + RowHeader LabelStyle + RowValue LabelStyle + Expander LabelStyle + Version LabelStyle + ErrorColor color.NRGBA + Bg color.NRGBA + } + Alert struct { + Warning PopupAlertStyle + Error PopupAlertStyle + Info PopupAlertStyle + } + NoteEditor struct { + TrackTitle LabelStyle + OrderRow LabelStyle + PatternRow LabelStyle + Note LabelStyle + PatternNo LabelStyle + Unique LabelStyle + Loop color.NRGBA + Header LabelStyle + Play color.NRGBA + OneBeat color.NRGBA + TwoBeat color.NRGBA + } + Dialog struct { + Bg color.NRGBA + Title LabelStyle + Text LabelStyle + } + OrderEditor struct { + TrackTitle LabelStyle + RowTitle LabelStyle + Cell LabelStyle + Loop color.NRGBA + CellBg color.NRGBA + Play color.NRGBA + } + Menu struct { + Text LabelStyle + ShortCut color.NRGBA + Hover color.NRGBA + Disabled color.NRGBA + } + InstrumentEditor struct { + Octave LabelStyle + Voices LabelStyle + InstrumentComment EditorStyle + UnitComment EditorStyle + InstrumentList struct { + Number LabelStyle + Name EditorStyle + NameMuted EditorStyle + ScrollBar ScrollBarStyle + } + UnitList struct { + Name EditorStyle + NameDisabled EditorStyle + Comment LabelStyle + Stack LabelStyle + Disabled LabelStyle + Warning color.NRGBA + Error color.NRGBA + } + } + UnitEditor struct { + Hint LabelStyle + Chooser LabelStyle + ParameterName LabelStyle + InvalidParam color.NRGBA + SendTarget color.NRGBA + } + Cursor CursorStyle + Selection CursorStyle + Tooltip struct { + Color color.NRGBA + Bg color.NRGBA + } + Popup struct { + Bg color.NRGBA + Shadow color.NRGBA + } + ScrollBar ScrollBarStyle +} -var white = color.NRGBA{R: 255, G: 255, B: 255, A: 255} -var black = color.NRGBA{R: 0, G: 0, B: 0, A: 255} -var transparent = color.NRGBA{A: 0} +type CursorStyle struct { + Active color.NRGBA + ActiveAlt color.NRGBA // alternative color for the cursor, used e.g. when the midi input is active + Inactive color.NRGBA +} -var primaryColor = color.NRGBA{R: 206, G: 147, B: 216, A: 255} -var secondaryColor = color.NRGBA{R: 128, G: 222, B: 234, A: 255} +//go:embed theme.yml +var defaultTheme []byte -var highEmphasisTextColor = color.NRGBA{R: 222, G: 222, B: 222, A: 222} -var mediumEmphasisTextColor = color.NRGBA{R: 153, G: 153, B: 153, A: 153} -var disabledTextColor = color.NRGBA{R: 255, G: 255, B: 255, A: 97} +func NewTheme() *Theme { + var theme Theme + err := yaml.UnmarshalStrict(defaultTheme, &theme) + if err != nil { + panic(fmt.Errorf("failed to default theme: %w", err)) + } + ReadCustomConfigYml("theme.yml", &theme) + theme.Material.Shaper = &text.Shaper{} + theme.Material.Icon.CheckBoxChecked = must(widget.NewIcon(icons.ToggleCheckBox)) + theme.Material.Icon.CheckBoxUnchecked = must(widget.NewIcon(icons.ToggleCheckBoxOutlineBlank)) + theme.Material.Icon.RadioChecked = must(widget.NewIcon(icons.ToggleRadioButtonChecked)) + theme.Material.Icon.RadioUnchecked = must(widget.NewIcon(icons.ToggleRadioButtonUnchecked)) + return &theme +} -var backgroundColor = color.NRGBA{R: 18, G: 18, B: 18, A: 255} - -var labelDefaultFont = fontCollection[6].Font -var labelDefaultFontSize = unit.Sp(18) - -var rowMarkerPatternTextColor = secondaryColor -var rowMarkerRowTextColor = mediumEmphasisTextColor - -var trackerFont = fontCollection[6].Font -var trackerFontSize = unit.Sp(16) -var trackerInactiveTextColor = highEmphasisTextColor -var trackerActiveTextColor = color.NRGBA{R: 255, G: 255, B: 130, A: 255} -var trackerPlayColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255} -var trackerPatMarker = primaryColor -var oneBeatHighlight = color.NRGBA{R: 31, G: 37, B: 38, A: 255} -var twoBeatHighlight = color.NRGBA{R: 31, G: 51, B: 53, A: 255} - -var patternPlayColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255} -var patternTextColor = primaryColor -var patternCellColor = color.NRGBA{R: 255, G: 255, B: 255, A: 3} -var loopMarkerColor = color.NRGBA{R: 252, G: 186, B: 3, A: 255} - -var instrumentHoverColor = color.NRGBA{R: 30, G: 31, B: 38, A: 255} -var instrumentNameHintColor = color.NRGBA{R: 200, G: 200, B: 200, A: 255} - -var songSurfaceColor = color.NRGBA{R: 24, G: 24, B: 24, A: 255} - -var popupSurfaceColor = color.NRGBA{R: 50, G: 50, B: 51, A: 255} -var popupShadowColor = color.NRGBA{R: 0, G: 0, B: 0, A: 192} - -var dragListSelectedColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255} -var dragListHoverColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255} - -var inactiveLightSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255} -var activeLightSurfaceColor = color.NRGBA{R: 45, G: 45, B: 45, A: 255} - -var numberInputBgColor = color.NRGBA{R: 255, G: 255, B: 255, A: 3} - -var cursorColor = color.NRGBA{R: 100, G: 140, B: 255, A: 48} -var selectionColor = color.NRGBA{R: 100, G: 140, B: 255, A: 12} -var inactiveSelectionColor = color.NRGBA{R: 140, G: 140, B: 140, A: 16} -var cursorForTrackMidiInColor = color.NRGBA{R: 255, G: 100, B: 140, A: 48} -var cursorNeighborForTrackMidiInColor = color.NRGBA{R: 255, G: 100, B: 140, A: 24} - -var errorColor = color.NRGBA{R: 207, G: 102, B: 121, A: 255} - -var menuHoverColor = color.NRGBA{R: 30, G: 31, B: 38, A: 255} - -var scrollBarColor = color.NRGBA{R: 255, G: 255, B: 255, A: 32} - -var warningColor = color.NRGBA{R: 251, G: 192, B: 45, A: 255} - -var dialogBgColor = color.NRGBA{R: 0, G: 0, B: 0, A: 224} - -var paramIsSendTargetColor = color.NRGBA{R: 120, G: 120, B: 210, A: 255} -var paramValueInvalidColor = color.NRGBA{R: 120, G: 120, B: 120, A: 190} - -var oscilloscopeLimitColor = color.NRGBA{R: 255, G: 255, B: 255, A: 8} -var oscilloscopeCursorColor = color.NRGBA{R: 252, G: 186, B: 3, A: 255} +func must[T any](ic T, err error) T { + if err != nil { + panic(err) + } + return ic +} diff --git a/tracker/gioui/theme.yml b/tracker/gioui/theme.yml new file mode 100644 index 0000000..f5bcf36 --- /dev/null +++ b/tracker/gioui/theme.yml @@ -0,0 +1,170 @@ +# Because we use yaml.UnmarshalStrict, we needed to have "Define any" field for +# all the defines; UnmarshalStrict thrwows an error if a field is not defined +define: + [ + &primarycolor { r: 206, g: 147, b: 216, a: 255 }, + &secondarycolor { r: 128, g: 222, b: 234, a: 255 }, + &transparentcolor { r: 0, g: 0, b: 0, a: 0 }, + &mediumemphasis { r: 153, g: 153, b: 153, a: 255 }, + &highemphasis { r: 222, g: 222, b: 222, a: 255 }, + &disabled { r: 255, g: 255, b: 255, a: 97 }, + &errorcolor { r: 207, g: 102, b: 121, a: 255 }, + &warningcolor { r: 251, g: 192, b: 45, a: 255 }, + &white { r: 255, g: 255, b: 255, a: 255 }, + &black { r: 0, g: 0, b: 0, a: 255 }, + &loopcolor { r: 252, g: 186, b: 3, a: 255 }, + &scrollbarcolor { r: 255, g: 255, b: 255, a: 32 }, + ] + +# from here on starts the structs defined in the theme.go +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: *primarycolor + contrastfg: &contrastfg { r: 0, g: 0, b: 0, a: 255 } +button: + filled: + background: *primarycolor + color: *contrastfg + textsize: &buttontextsize 14 + cornerradius: &buttoncornerradius 18 + height: &buttonheight 36 + inset: &buttoninset { top: 0, bottom: 0, left: 6, right: 6 } + text: + background: *transparentcolor + color: *primarycolor + textsize: *buttontextsize + cornerradius: *buttoncornerradius + height: *buttonheight + inset: *buttoninset + disabled: + background: { r: 53, g: 51, b: 55, a: 255 } + color: { r: 120, g: 116, b: 121, a: 255 } + textsize: *buttontextsize + cornerradius: *buttoncornerradius + height: *buttonheight + inset: *buttoninset + menu: + background: *transparentcolor + color: { r: 255, g: 255, b: 255, a: 255 } + textsize: *buttontextsize + cornerradius: 0 + height: *buttonheight + inset: *buttoninset +oscilloscope: + 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: *primarycolor + cornerradius: 4 + buttonwidth: 16 + dpperstep: 8 + textsize: 14 + width: 70 + height: 20 +songpanel: + bg: { r: 24, g: 24, b: 24, a: 255 } + rowheader: + textsize: 14 + color: *mediumemphasis + rowvalue: + textsize: 14 + color: *mediumemphasis + expander: + textsize: 14 + color: *highemphasis + errorcolor: *errorcolor + version: + textsize: 12 + color: *mediumemphasis +alert: + error: + bg: *errorcolor + text: { textsize: 16, color: *black } + warning: + bg: *warningcolor + text: { textsize: 16, color: *black } + info: + bg: { r: 50, g: 50, b: 51, a: 255 } + text: { textsize: 16, color: *highemphasis, shadowcolor: *black } +dialog: + bg: { r: 0, g: 0, b: 0, a: 224 } + title: { textsize: 16, color: *highemphasis, shadowcolor: *black } + text: { textsize: 16, color: *highemphasis, shadowcolor: *black } +ordereditor: + tracktitle: { textsize: 12, color: *mediumemphasis } + rowtitle: + { textsize: 16, color: *secondarycolor, font: { typeface: "Go Mono" } } + cell: { textsize: 16, color: *primarycolor, font: { typeface: "Go Mono" } } + loop: *loopcolor + cellbg: { r: 255, g: 255, b: 255, a: 3 } + play: { r: 55, g: 55, b: 61, a: 255 } +noteeditor: + tracktitle: { textsize: 12, color: *mediumemphasis, alignment: 2 } + orderrow: + { textsize: 16, color: *secondarycolor, font: { typeface: "Go Mono" } } + patternrow: + { textsize: 16, color: *mediumemphasis, font: { typeface: "Go Mono" } } + note: { textsize: 16, color: *highemphasis, font: { typeface: "Go Mono" } } + patternno: + { textsize: 16, color: *primarycolor, font: { typeface: "Go Mono" } } + unique: + { textsize: 16, color: *secondarycolor, font: { typeface: "Go Mono" } } + loop: *loopcolor + header: { textsize: 14, color: *disabled } + play: { r: 55, g: 55, b: 61, a: 255 } + onebeat: { r: 31, g: 37, b: 38, a: 255 } + twobeat: { r: 31, g: 51, b: 53, a: 255 } +menu: + text: { textsize: 16, color: *highemphasis, shadowcolor: *black } + shortcut: *mediumemphasis + hover: { r: 100, g: 140, b: 255, a: 48 } + disabled: *disabled +instrumenteditor: + octave: { textsize: 14, color: *disabled } + voices: { textsize: 14, color: *disabled } + instrumentcomment: + { textsize: 14, color: *highemphasis, hintcolor: *disabled } + unitcomment: { textsize: 14, color: *highemphasis, hintcolor: *disabled } + instrumentlist: + number: { textsize: 10, color: *mediumemphasis } + name: { textsize: 12, color: *white, hintcolor: *disabled } + namemuted: { textsize: 12, color: *disabled, hintcolor: *disabled } + scrollbar: { width: 6, color: *scrollbarcolor } + unitlist: + name: { textsize: 12, color: *white, hintcolor: *disabled } + namedisabled: + textsize: 12 + color: *disabled + hintcolor: *disabled + font: { style: 1 } + comment: { textsize: 12, color: *disabled } + stack: { textsize: 12, color: *mediumemphasis, shadowcolor: *black } + disabled: { textsize: 12, color: *disabled } + warning: *warningcolor + error: *errorcolor +uniteditor: + hint: { textsize: 16, color: *highemphasis, shadowcolor: *black } + chooser: { textsize: 12, color: *white, shadowcolor: *black } + parametername: { textsize: 16, color: *white, shadowcolor: *black } + invalidparam: { r: 120, g: 120, b: 120, a: 190 } + sendtarget: { r: 120, g: 120, b: 210, a: 255 } +cursor: + active: { r: 100, g: 140, b: 255, a: 48 } + activealt: { r: 255, g: 100, b: 140, a: 48 } + inactive: { r: 140, g: 140, b: 140, a: 48 } +selection: + active: { r: 100, g: 140, b: 255, a: 16 } + activealt: { r: 255, g: 100, b: 140, a: 24 } + inactive: { r: 140, g: 140, b: 140, a: 16 } +scrollbar: { width: 10, color: *scrollbarcolor, gradient: *black } +tooltip: { color: *white, bg: *black } +popup: + bg: { r: 50, g: 50, b: 51, a: 255 } + shadow: { r: 0, g: 0, b: 0, a: 192 } diff --git a/tracker/gioui/tracker.go b/tracker/gioui/tracker.go index 3e95be6..fb46f2c 100644 --- a/tracker/gioui/tracker.go +++ b/tracker/gioui/tracker.go @@ -8,6 +8,7 @@ import ( "time" "gioui.org/app" + "gioui.org/font/gofont" "gioui.org/io/event" "gioui.org/io/key" "gioui.org/io/pointer" @@ -18,7 +19,6 @@ import ( "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/text" - "gioui.org/widget/material" "gioui.org/x/explorer" "github.com/vsariola/sointu/tracker" ) @@ -27,7 +27,7 @@ var canQuit = true // set to false in init() if plugin tag is enabled type ( Tracker struct { - Theme *material.Theme + Theme *Theme OctaveNumberInput *NumberInput InstrumentVoices *NumberInput TopHorizontalSplit *Split @@ -71,7 +71,7 @@ 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{ - Theme: material.NewTheme(), + Theme: NewTheme(), OctaveNumberInput: NewNumberInput(model.Octave().Int()), InstrumentVoices: NewNumberInput(model.InstrumentVoices().Int()), @@ -95,16 +95,14 @@ func NewTracker(model *tracker.Model) *Tracker { filePathString: model.FilePath().String(), preferences: MakePreferences(), } - t.Theme.Shaper = text.NewShaper(text.WithCollection(fontCollection)) - t.PopupAlert = NewPopupAlert(model.Alerts(), t.Theme.Shaper) + t.Theme.Material.Shaper = text.NewShaper(text.WithCollection(gofont.Collection())) + t.PopupAlert = NewPopupAlert(model.Alerts()) if t.preferences.YmlError != nil { model.Alerts().Add( fmt.Sprintf("Preferences YML Error: %s", t.preferences.YmlError), tracker.Warning, ) } - t.Theme.Palette.Fg = primaryColor - t.Theme.Palette.ContrastFg = black t.TrackEditor.scrollTable.Focus() return t } @@ -194,7 +192,7 @@ func (t *Tracker) Layout(gtx layout.Context, w *app.Window) { gtx.Metric.PxPerDp *= zoomFactor gtx.Metric.PxPerSp *= zoomFactor defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop() - paint.Fill(gtx.Ops, backgroundColor) + paint.Fill(gtx.Ops, t.Theme.Material.Bg) event.Op(gtx.Ops, t) // area for capturing scroll events if t.InstrumentEditor.enlargeBtn.Bool.Value() { @@ -204,7 +202,7 @@ func (t *Tracker) Layout(gtx layout.Context, w *app.Window) { t.layoutTop, t.layoutBottom) } - t.PopupAlert.Layout(gtx) + t.PopupAlert.Layout(gtx, t.Theme) t.showDialog(gtx) // this is the top level input handler for the whole app // it handles all the global key events and clipboard events diff --git a/tracker/gioui/unit_editor.go b/tracker/gioui/unit_editor.go index 34a40a0..ba20b1d 100644 --- a/tracker/gioui/unit_editor.go +++ b/tracker/gioui/unit_editor.go @@ -141,7 +141,7 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D { } else { text = pe.caser.String(text) } - hintText := Label(text, white, t.Theme.Shaper) + hintText := Label(t.Theme, &t.Theme.UnitEditor.Hint, text) return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(deleteUnitBtnStyle.Layout), layout.Rigid(copyUnitBtnStyle.Layout), @@ -156,7 +156,7 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D { }), layout.Rigid(func(gtx C) D { gtx.Constraints.Min.X = gtx.Dp(120) - return hintText(gtx) + return hintText.Layout(gtx) }), layout.Flexed(1, func(gtx C) D { s := t.UnitComment().String() @@ -164,11 +164,7 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D { for pe.commentEditor.Submitted(gtx) || pe.commentEditor.Cancelled(gtx) { t.InstrumentEditor.Focus() } - commentStyle := MaterialEditor(t.Theme, pe.commentEditor, "---") - commentStyle.Font = labelDefaultFont - commentStyle.TextSize = labelDefaultFontSize - commentStyle.Color = mediumEmphasisTextColor - commentStyle.HintColor = mediumEmphasisTextColor + commentStyle := MaterialEditor(t.Theme, &t.Theme.InstrumentEditor.UnitComment, pe.commentEditor, "---") ret := commentStyle.Layout(gtx) s.Set(pe.commentEditor.Text()) return ret @@ -185,7 +181,8 @@ func (pe *UnitEditor) layoutUnitTypeChooser(gtx C, t *Tracker) D { names[i] = item } element := func(gtx C, i int) D { - w := LabelStyle{Text: names[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} + w := Label(t.Theme, &t.Theme.UnitEditor.Chooser, names[i]) + if i == pe.searchList.TrackerList.Selected() { for pe.SelectTypeBtn.Clicked(gtx) { t.Units().SetSelectedType(names[i]) @@ -248,17 +245,17 @@ type ParameterWidget struct { type ParameterStyle struct { tracker *Tracker w *ParameterWidget - Theme *material.Theme + Theme *Theme SendTargetTheme *material.Theme Focus bool } -func (t *Tracker) ParamStyle(th *material.Theme, paramWidget *ParameterWidget) ParameterStyle { - sendTargetTheme := th.WithPalette(material.Palette{ - Bg: th.Bg, - Fg: paramIsSendTargetColor, - ContrastBg: th.ContrastBg, - ContrastFg: th.ContrastFg, +func (t *Tracker) ParamStyle(th *Theme, paramWidget *ParameterWidget) ParameterStyle { + sendTargetTheme := th.Material.WithPalette(material.Palette{ + Bg: th.Material.Bg, + Fg: th.UnitEditor.SendTarget, + ContrastBg: th.Material.ContrastBg, + ContrastFg: th.Material.ContrastFg, }) return ParameterStyle{ tracker: t, // TODO: we need this to pull the instrument names for ID style parameters, find out another way @@ -273,7 +270,7 @@ func (p ParameterStyle) Layout(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { gtx.Constraints.Min.X = gtx.Dp(unit.Dp(110)) - return layout.E.Layout(gtx, Label(p.w.Parameter.Name(), white, p.tracker.Theme.Shaper)) + return layout.E.Layout(gtx, Label(p.Theme, &p.Theme.UnitEditor.ParameterName, p.w.Parameter.Name()).Layout) }), layout.Rigid(func(gtx C) D { switch p.w.Parameter.Type() { @@ -298,10 +295,9 @@ func (p ParameterStyle) Layout(gtx C) D { if !p.w.floatWidget.Dragging() { 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 + sliderStyle := material.Slider(&p.Theme.Material, &p.w.floatWidget) if isSendTarget { - sliderStyle.Color = paramIsSendTargetColor + sliderStyle.Color = p.Theme.UnitEditor.SendTarget } r := image.Rectangle{Max: gtx.Constraints.Min} defer clip.Rect(r).Push(gtx.Ops).Pop() @@ -317,9 +313,8 @@ func (p ParameterStyle) Layout(gtx C) D { gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(40)) ra := p.w.Parameter.Range() 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 = white + boolStyle := material.Switch(&p.Theme.Material, &p.w.boolWidget, "Toggle boolean parameter") + boolStyle.Color.Disabled = p.Theme.Material.Fg defer pointer.PassOp{}.Push(gtx.Ops).Pop() dims := layout.Center.Layout(gtx, boolStyle.Layout) if p.w.boolWidget.Value { @@ -374,17 +369,16 @@ func (p ParameterStyle) Layout(gtx C) D { }), layout.Rigid(func(gtx C) D { if p.w.Parameter.Type() != tracker.IDParameter { - color := white hint := p.w.Parameter.Hint() + label := Label(p.tracker.Theme, &p.tracker.Theme.UnitEditor.Hint, hint.Label) if !hint.Valid { - color = paramValueInvalidColor + label.Color = p.tracker.Theme.UnitEditor.InvalidParam } - label := Label(hint.Label, color, p.tracker.Theme.Shaper) if info == "" { - return label(gtx) + return label.Layout(gtx) } tooltip := component.PlatformTooltip(p.SendTargetTheme, info) - return p.w.tipArea.Layout(gtx, tooltip, label) + return p.w.tipArea.Layout(gtx, tooltip, label.Layout) } return D{} }), diff --git a/tracker/gioui/vumeter.go b/tracker/gioui/vumeter.go deleted file mode 100644 index 7a47183..0000000 --- a/tracker/gioui/vumeter.go +++ /dev/null @@ -1,47 +0,0 @@ -package gioui - -import ( - "image" - - "gioui.org/op" - "gioui.org/op/clip" - "gioui.org/op/paint" - "gioui.org/unit" - "github.com/vsariola/sointu/tracker" -) - -type VuMeter struct { - Loudness tracker.Decibel - Peak [2]tracker.Decibel - Range float32 -} - -func (v VuMeter) Layout(gtx C) D { - defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() - gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(12)) - height := gtx.Dp(unit.Dp(6)) - for j := 0; j < 2; j++ { - value := float32(v.Loudness) + v.Range - if value > 0 { - x := int(value/v.Range*float32(gtx.Constraints.Max.X) + 0.5) - if x > gtx.Constraints.Max.X { - x = gtx.Constraints.Max.X - } - paint.FillShape(gtx.Ops, mediumEmphasisTextColor, clip.Rect(image.Rect(0, 0, x, height)).Op()) - } - valueMax := float32(v.Peak[j]) + v.Range - if valueMax > 0 { - color := white - if valueMax >= v.Range { - color = errorColor - } - x := int(valueMax/v.Range*float32(gtx.Constraints.Max.X) + 0.5) - if x > gtx.Constraints.Max.X { - x = gtx.Constraints.Max.X - } - paint.FillShape(gtx.Ops, color, clip.Rect(image.Rect(x-1, 0, x, height)).Op()) - } - op.Offset(image.Point{0, height}).Add(gtx.Ops) - } - return D{Size: gtx.Constraints.Max} -}