feat(tracker/gioui): add theme.yml which contains all styling

This commit is contained in:
5684185+vsariola@users.noreply.github.com 2025-05-01 23:49:07 +03:00
parent 8245fbda24
commit afb1fee4ed
22 changed files with 740 additions and 636 deletions

View File

@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added ### 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]) - Ctrl + scroll wheel adjusts the global scaling of the GUI ([#153][i153])
- The loudness detection supports LUFS, A-weighting, C-weighting or - The loudness detection supports LUFS, A-weighting, C-weighting or
RMS-weighting, and peak detection supports true peak or sample peak detection. RMS-weighting, and peak detection supports true peak or sample peak detection.

View File

@ -17,7 +17,6 @@ import (
"gioui.org/text" "gioui.org/text"
"gioui.org/unit" "gioui.org/unit"
"gioui.org/widget" "gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component" "gioui.org/x/component"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
@ -58,27 +57,28 @@ func NewBoolClickable(b tracker.Bool) *BoolClickable {
} }
} }
func Tooltip(th *material.Theme, tip string) component.Tooltip { func Tooltip(th *Theme, tip string) component.Tooltip {
tooltip := component.PlatformTooltip(th, tip) tooltip := component.PlatformTooltip(&th.Material, tip)
tooltip.Bg = black tooltip.Bg = th.Tooltip.Bg
tooltip.Text.Color = th.Tooltip.Color
return tooltip 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) ret := TipIcon(th, &w.TipClickable, icon, tip)
for w.Clickable.Clicked(gtx) { for w.Clickable.Clicked(gtx) {
w.Action.Do() w.Action.Do()
} }
if !w.Action.Allowed() { if !w.Action.Allowed() {
ret.IconButtonStyle.Color = disabledTextColor ret.IconButtonStyle.Color = th.Button.Disabled.Color
} }
return ret 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 := IconButton(th, &w.Clickable, widgetForIcon(icon), "")
iconButtonStyle.Color = primaryColor iconButtonStyle.Color = th.Material.Palette.ContrastBg
iconButtonStyle.Background = transparent iconButtonStyle.Background = color.NRGBA{}
iconButtonStyle.Inset = layout.UniformInset(unit.Dp(6)) iconButtonStyle.Inset = layout.UniformInset(unit.Dp(6))
return TipIconButtonStyle{ return TipIconButtonStyle{
TipArea: &w.TipArea, 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 icon := offIcon
tip := offTip tip := offTip
if w.Bool.Value() { if w.Bool.Value() {
@ -98,11 +98,11 @@ func ToggleIcon(gtx C, th *material.Theme, w *BoolClickable, offIcon, onIcon []b
w.Bool.Toggle() w.Bool.Toggle()
} }
ibStyle := IconButton(th, &w.Clickable, widgetForIcon(icon), "") ibStyle := IconButton(th, &w.Clickable, widgetForIcon(icon), "")
ibStyle.Background = transparent ibStyle.Background = color.NRGBA{}
ibStyle.Inset = layout.UniformInset(unit.Dp(6)) ibStyle.Inset = layout.UniformInset(unit.Dp(6))
ibStyle.Color = primaryColor ibStyle.Color = th.Material.Palette.ContrastBg
if !w.Bool.Enabled() { if !w.Bool.Enabled() {
ibStyle.Color = disabledTextColor ibStyle.Color = th.Button.Disabled.Color
} }
return TipIconButtonStyle{ return TipIconButtonStyle{
TipArea: &w.TipArea, TipArea: &w.TipArea,
@ -115,51 +115,27 @@ func (t *TipIconButtonStyle) Layout(gtx C) D {
return t.TipArea.Layout(gtx, t.Tooltip, t.IconButtonStyle.Layout) 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) { for w.Clickable.Clicked(gtx) {
w.Action.Do() w.Action.Do()
} }
ret := Button(th, &w.Clickable, text)
ret.Color = th.Palette.Fg
if !w.Action.Allowed() { if !w.Action.Allowed() {
ret.Color = disabledTextColor return Btn(th, &th.Button.Disabled, &w.Clickable, text)
} }
ret.Background = transparent return Btn(th, style, &w.Clickable, text)
ret.Inset = layout.UniformInset(unit.Dp(6))
return ret
} }
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) { for b.Clickable.Clicked(gtx) {
b.Bool.Toggle() b.Bool.Toggle()
} }
ret := Button(th, &b.Clickable, text) if !b.Bool.Enabled() {
ret.Background = transparent return Btn(th, &th.Button.Disabled, &b.Clickable, text)
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
} }
return ret if b.Bool.Value() {
} return Btn(th, &th.Button.Filled, &b.Clickable, text)
}
func LowEmphasisButton(th *material.Theme, w *Clickable, text string) ButtonStyle { return Btn(th, &th.Button.Text, &b.Clickable, text)
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
} }
// Clickable represents a clickable area. // 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 { type ButtonStyle struct {
Text string
// Color is the text color. // Color is the text color.
Color color.NRGBA Color color.NRGBA
Font font.Font Font font.Font
TextSize unit.Sp TextSize unit.Sp
Background color.NRGBA Background color.NRGBA
CornerRadius unit.Dp CornerRadius unit.Dp
Height unit.Dp
Inset layout.Inset Inset layout.Inset
Button *Clickable
shaper *text.Shaper
} }
type ButtonLayoutStyle struct { type Button struct {
Background color.NRGBA Text string
CornerRadius unit.Dp Button *Clickable
Button *Clickable shaper *text.Shaper
ButtonStyle
} }
type IconButtonStyle struct { type IconButtonStyle struct {
@ -307,36 +282,20 @@ type IconButtonStyle struct {
Description string Description string
} }
func Button(th *material.Theme, button *Clickable, txt string) ButtonStyle { func Btn(th *Theme, style *ButtonStyle, button *Clickable, txt string) Button {
b := ButtonStyle{ b := Button{
Text: txt, Text: txt,
Color: th.Palette.ContrastFg, ButtonStyle: *style,
CornerRadius: 4, Button: button,
Background: th.Palette.ContrastBg, shaper: th.Material.Shaper,
TextSize: th.TextSize * 14.0 / 16.0,
Inset: layout.Inset{
Top: 10, Bottom: 10,
Left: 12, Right: 12,
},
Button: button,
shaper: th.Shaper,
} }
b.Font.Typeface = th.Face
return b return b
} }
func ButtonLayout(th *material.Theme, button *Clickable) ButtonLayoutStyle { func IconButton(th *Theme, button *Clickable, icon *widget.Icon, description string) IconButtonStyle {
return ButtonLayoutStyle{
Button: button,
Background: th.Palette.ContrastBg,
CornerRadius: 4,
}
}
func IconButton(th *material.Theme, button *Clickable, icon *widget.Icon, description string) IconButtonStyle {
return IconButtonStyle{ return IconButtonStyle{
Background: th.Palette.ContrastBg, Background: th.Material.Palette.ContrastBg,
Color: th.Palette.ContrastFg, Color: th.Material.Palette.ContrastFg,
Icon: icon, Icon: icon,
Size: 24, Size: 24,
Inset: layout.UniformInset(12), 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 { func (b *Button) 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 {
min := gtx.Constraints.Min min := gtx.Constraints.Min
min.Y = gtx.Dp(b.Height)
return b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
semantic.Button.Add(gtx.Ops) semantic.Button.Add(gtx.Ops)
return layout.Background{}.Layout(gtx, 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 { func(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Min = min 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())
})
})
}, },
) )
}) })

View File

@ -1,10 +1,11 @@
package gioui package gioui
import ( import (
"image/color"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit" "gioui.org/unit"
"gioui.org/widget" "gioui.org/widget"
"gioui.org/widget/material" "gioui.org/widget/material"
@ -28,7 +29,7 @@ type DialogStyle struct {
AltStyle material.ButtonStyle AltStyle material.ButtonStyle
OkStyle material.ButtonStyle OkStyle material.ButtonStyle
CancelStyle material.ButtonStyle CancelStyle material.ButtonStyle
Shaper *text.Shaper Theme *Theme
} }
func NewDialog(ok, alt, cancel tracker.Action) *Dialog { func NewDialog(ok, alt, cancel tracker.Action) *Dialog {
@ -37,22 +38,22 @@ func NewDialog(ok, alt, cancel tracker.Action) *Dialog {
return ret 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{ ret := DialogStyle{
dialog: dialog, dialog: dialog,
Title: title, Title: title,
Text: text, Text: text,
Inset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12), Left: unit.Dp(20), Right: unit.Dp(20)}, 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)}, TextInset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12)},
AltStyle: material.Button(th, &dialog.BtnAlt, "Alt"), AltStyle: material.Button(&th.Material, &dialog.BtnAlt, "Alt"),
OkStyle: material.Button(th, &dialog.BtnOk, "Ok"), OkStyle: material.Button(&th.Material, &dialog.BtnOk, "Ok"),
CancelStyle: material.Button(th, &dialog.BtnCancel, "Cancel"), CancelStyle: material.Button(&th.Material, &dialog.BtnCancel, "Cancel"),
Shaper: th.Shaper, Theme: th,
} }
for _, b := range [...]*material.ButtonStyle{&ret.AltStyle, &ret.OkStyle, &ret.CancelStyle} { 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.Inset = layout.UniformInset(unit.Dp(6))
b.Color = th.Palette.Fg b.Color = th.Material.Palette.Fg
} }
return ret return ret
} }
@ -106,17 +107,14 @@ func (d *DialogStyle) Layout(gtx C) D {
gtx.Execute(key.FocusCmd{Tag: &d.dialog.BtnCancel}) gtx.Execute(key.FocusCmd{Tag: &d.dialog.BtnCancel})
} }
d.dialog.handleKeys(gtx) d.dialog.handleKeys(gtx)
paint.Fill(gtx.Ops, dialogBgColor) paint.Fill(gtx.Ops, d.Theme.Dialog.Bg)
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)
}
visible := true visible := true
return layout.Center.Layout(gtx, func(gtx C) D { 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 d.Inset.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(Label(d.Title, highEmphasisTextColor, d.Shaper)), layout.Rigid(Label(d.Theme, &d.Theme.Dialog.Title, d.Title).Layout),
layout.Rigid(text), layout.Rigid(Label(d.Theme, &d.Theme.Dialog.Text, d.Text).Layout),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layout.E.Layout(gtx, func(gtx C) D { return layout.E.Layout(gtx, func(gtx C) D {
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(120)) gtx.Constraints.Min.X = gtx.Dp(unit.Dp(120))

View File

@ -15,8 +15,6 @@ import (
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget/material"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
@ -34,27 +32,27 @@ type DragList struct {
} }
type FilledDragListStyle struct { type FilledDragListStyle struct {
dragList *DragList dragList *DragList
HoverColor color.NRGBA HoverColor color.NRGBA
SelectedColor color.NRGBA Cursor CursorStyle
CursorColor color.NRGBA Selection CursorStyle
ScrollBarWidth unit.Dp ScrollBar ScrollBarStyle
element, bg func(gtx C, i int) D element, bg func(gtx C, i int) D
} }
func NewDragList(model tracker.List, axis layout.Axis) *DragList { func NewDragList(model tracker.List, axis layout.Axis) *DragList {
return &DragList{TrackerList: model, List: &layout.List{Axis: axis}, HoverItem: -1, ScrollBar: &ScrollBar{Axis: axis}} 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{ return FilledDragListStyle{
dragList: dragList, dragList: dragList,
element: element, element: element,
bg: bg, bg: bg,
HoverColor: dragListHoverColor, HoverColor: hoveredColor(th.Selection.Active),
SelectedColor: dragListSelectedColor, Cursor: th.Cursor,
CursorColor: cursorColor, Selection: th.Selection,
ScrollBarWidth: unit.Dp(10), ScrollBar: th.ScrollBar,
} }
} }
@ -67,7 +65,7 @@ func (d *DragList) Focused() bool {
} }
func (s FilledDragListStyle) LayoutScrollBar(gtx C) D { 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 { func (s FilledDragListStyle) Layout(gtx C) D {
@ -147,12 +145,16 @@ func (s FilledDragListStyle) Layout(gtx C) D {
var color color.NRGBA var color color.NRGBA
if s.dragList.TrackerList.Selected() == index { if s.dragList.TrackerList.Selected() == index {
if s.dragList.focused { if s.dragList.focused {
color = s.CursorColor color = s.Cursor.Active
} else { } else {
color = s.SelectedColor color = s.Cursor.Inactive
} }
} else if between(s.dragList.TrackerList.Selected(), index, s.dragList.TrackerList.Selected2()) { } 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 { } else if s.dragList.HoverItem == index {
color = s.HoverColor color = s.HoverColor
} }

View File

@ -1,8 +1,12 @@
package gioui package gioui
import ( import (
"image/color"
"gioui.org/font"
"gioui.org/io/event" "gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/unit"
"gioui.org/widget" "gioui.org/widget"
"gioui.org/widget/material" "gioui.org/widget/material"
) )
@ -18,7 +22,12 @@ type (
requestFocus bool 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 { func NewEditor(e widget.Editor) *Editor {
@ -36,8 +45,21 @@ func NewEditor(e widget.Editor) *Editor {
return ret return ret
} }
func MaterialEditor(th *material.Theme, e *Editor, hint string) EditorStyle { func (s *EditorStyle) AsLabelStyle() LabelStyle {
return EditorStyle(material.Editor(th, &e.Editor, hint)) 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) { func (e *Editor) SetText(s string) {
@ -80,7 +102,3 @@ func (e *Editor) Cancelled(gtx C) bool {
func (e *Editor) Focus() { func (e *Editor) Focus() {
e.requestFocus = true e.requestFocus = true
} }
func (e *EditorStyle) Layout(gtx C) D {
return material.EditorStyle(*e).Layout(gtx)
}

View File

@ -9,7 +9,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"gioui.org/font"
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/layout" "gioui.org/layout"
@ -130,7 +129,7 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
octave := func(gtx C) D { octave := func(gtx C) D {
in := layout.UniformInset(unit.Dp(1)) in := layout.UniformInset(unit.Dp(1))
numStyle := NumericUpDown(t.Theme, t.OctaveNumberInput, ie.octaveHint) numStyle := NumUpDown(t.Theme, t.OctaveNumberInput, ie.octaveHint)
dims := in.Layout(gtx, numStyle.Layout) dims := in.Layout(gtx, numStyle.Layout)
return dims return dims
} }
@ -144,7 +143,7 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
return ie.layoutInstrumentList(gtx, t) return ie.layoutInstrumentList(gtx, t)
}), }),
layout.Rigid(layout.Spacer{Width: 10}.Layout), 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(layout.Spacer{Width: 4}.Layout),
layout.Rigid(octave), layout.Rigid(octave),
layout.Rigid(func(gtx C) D { 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) 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) 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) { for ie.copyInstrumentBtn.Clickable.Clicked(gtx) {
if contents, ok := t.Instruments().List().CopyElements(); ok { 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 { header := func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(layout.Spacer{Width: 6}.Layout), 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(layout.Spacer{Width: 4}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { 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) dims := numStyle.Layout(gtx)
return dims return dims
}), }),
@ -254,8 +253,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
for ie.commentEditor.Submitted(gtx) || ie.commentEditor.Cancelled(gtx) { for ie.commentEditor.Submitted(gtx) || ie.commentEditor.Cancelled(gtx) {
ie.instrumentDragList.Focus() ie.instrumentDragList.Focus()
} }
style := MaterialEditor(t.Theme, ie.commentEditor, "Comment") style := MaterialEditor(t.Theme, &t.Theme.InstrumentEditor.InstrumentComment, ie.commentEditor, "Comment")
style.Color = highEmphasisTextColor
ret := layout.UniformInset(unit.Dp(6)).Layout(gtx, style.Layout) ret := layout.UniformInset(unit.Dp(6)).Layout(gtx, style.Layout)
ie.commentString.Set(ie.commentEditor.Text()) ie.commentString.Set(ie.commentEditor.Text())
return ret return ret
@ -270,33 +268,29 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
} }
func (ie *InstrumentEditor) layoutInstrumentList(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 { element := func(gtx C, i int) D {
gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36)) grabhandle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, strconv.Itoa(i+1))
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}
label := func(gtx C) D { label := func(gtx C) D {
name, level, mute, ok := (*tracker.Instruments)(t.Model).Item(i) name, level, mute, ok := (*tracker.Instruments)(t.Model).Item(i)
if !ok { 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) return layout.Center.Layout(gtx, labelStyle.Layout)
} }
k := byte(255 - level*127) k := byte(255 - level*127)
color := color.NRGBA{R: 255, G: k, B: 255, A: 255} 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() { if i == ie.instrumentDragList.TrackerList.Selected() {
ie.nameEditor.SetText(name) ie.nameEditor.SetText(name)
for ie.nameEditor.Submitted(gtx) || ie.nameEditor.Cancelled(gtx) { for ie.nameEditor.Submitted(gtx) || ie.nameEditor.Cancelled(gtx) {
ie.instrumentDragList.Focus() ie.instrumentDragList.Focus()
} }
style := MaterialEditor(t.Theme, ie.nameEditor, "Instr") style := MaterialEditor(t.Theme, &s, 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
}
dims := layout.Center.Layout(gtx, func(gtx C) D { 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() defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
return style.Layout(gtx) return style.Layout(gtx)
@ -307,29 +301,21 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
if name == "" { if name == "" {
name = "Instr" name = "Instr"
} }
labelStyle := LabelStyle{Text: name, ShadeColor: black, Color: color, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} l := s.AsLabelStyle()
if mute { return layout.Center.Layout(gtx, Label(t.Theme, &l, name).Layout)
labelStyle.Color = disabledTextColor
labelStyle.Font.Style = font.Italic
}
return layout.Center.Layout(gtx, labelStyle.Layout)
} }
return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6), Top: unit.Dp(4)}.Layout(gtx, func(gtx C) D { return layout.Center.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6)}.Layout(gtx, func(gtx C) D {
layout.Rigid(grabhandle.Layout), return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(label), layout.Rigid(grabhandle.Layout),
) layout.Rigid(label),
)
})
}) })
} }
color := inactiveLightSurfaceColor
if ie.wasFocused {
color = activeLightSurfaceColor
}
instrumentList := FilledDragList(t.Theme, ie.instrumentDragList, element, nil) instrumentList := FilledDragList(t.Theme, ie.instrumentDragList, element, nil)
instrumentList.SelectedColor = color instrumentList.ScrollBar = t.Theme.InstrumentEditor.InstrumentList.ScrollBar
instrumentList.HoverColor = instrumentHoverColor
instrumentList.ScrollBarWidth = unit.Dp(6)
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() 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() 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 { func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
// TODO: how to ie.unitDragList.Focus() // TODO: how to ie.unitDragList.Focus()
addUnitBtnStyle := ActionIcon(gtx, t.Theme, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)") addUnitBtnStyle := ActionIcon(gtx, t.Theme, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)")
addUnitBtnStyle.IconButtonStyle.Color = t.Theme.ContrastFg addUnitBtnStyle.IconButtonStyle.Color = t.Theme.Material.ContrastFg
addUnitBtnStyle.IconButtonStyle.Background = t.Theme.Fg addUnitBtnStyle.IconButtonStyle.Background = t.Theme.Material.ContrastBg
addUnitBtnStyle.IconButtonStyle.Inset = layout.UniformInset(unit.Dp(4)) addUnitBtnStyle.IconButtonStyle.Inset = layout.UniformInset(unit.Dp(4))
var units [256]tracker.UnitListItem 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} return layout.Dimensions{Size: gtx.Constraints.Min}
} }
u := units[i] 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) stackText := strconv.FormatInt(int64(u.StackAfter), 10)
if u.StackNeed > u.StackBefore { 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) (*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 { } 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) (*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)} rightMargin := layout.Inset{Right: unit.Dp(10)}
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(func(gtx C) D { 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.searchEditor.SetText(str.Value())
ie.unitDragList.Focus() ie.unitDragList.Focus()
} }
style := MaterialEditor(t.Theme, ie.searchEditor, "---") style := MaterialEditor(t.Theme, &editorStyle, ie.searchEditor, "---")
style.Color = color
style.HintColor = instrumentNameHintColor
style.TextSize = unit.Sp(12)
style.Font = f
ret := style.Layout(gtx) ret := style.Layout(gtx)
str.Set(ie.searchEditor.Text()) str.Set(ie.searchEditor.Text())
return ret return ret
} else { } else {
unitNameLabel := LabelStyle{Text: u.Type, ShadeColor: black, Color: color, Font: f, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} text := u.Type
if unitNameLabel.Text == "" { if text == "" {
unitNameLabel.Text = "---" text = "---"
} }
return unitNameLabel.Layout(gtx) l := editorStyle.AsLabelStyle()
return Label(t.Theme, &l, text).Layout(gtx)
} }
}), }),
layout.Flexed(1, func(gtx C) D { 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)} inset := layout.Inset{Left: unit.Dp(5)}
return inset.Layout(gtx, unitNameLabel.Layout) return inset.Layout(gtx, unitNameLabel.Layout)
}), }),

View File

@ -5,7 +5,6 @@ import (
"image/color" "image/color"
"gioui.org/font" "gioui.org/font"
"gioui.org/layout"
"gioui.org/op" "gioui.org/op"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/text" "gioui.org/text"
@ -14,37 +13,38 @@ import (
) )
type LabelStyle struct { type LabelStyle struct {
Text string Color color.NRGBA
Color color.NRGBA ShadowColor color.NRGBA
ShadeColor color.NRGBA Alignment text.Alignment
Alignment layout.Direction Font font.Font
Font font.Font TextSize unit.Sp
FontSize unit.Sp
Shaper *text.Shaper
} }
func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions { type LabelWidget struct {
return l.Alignment.Layout(gtx, func(gtx C) D { Text string
gtx.Constraints.Min = image.Point{} Shaper *text.Shaper
paint.ColorOp{Color: l.ShadeColor}.Add(gtx.Ops) 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) offs := op.Offset(image.Pt(2, 2)).Push(gtx.Ops)
widget.Label{ t.Layout(gtx, l.Shaper, l.Font, l.TextSize, l.Text, shadowColor)
Alignment: text.Start,
MaxLines: 1,
}.Layout(gtx, l.Shaper, l.Font, l.FontSize, l.Text, op.CallOp{})
offs.Pop() offs.Pop()
paint.ColorOp{Color: l.Color}.Add(gtx.Ops) }
dims := widget.Label{ return t.Layout(gtx, l.Shaper, l.Font, l.TextSize, l.Text, textColor)
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,
}
})
} }
func Label(str string, color color.NRGBA, shaper *text.Shaper) layout.Widget { func Label(th *Theme, style *LabelStyle, txt string) LabelWidget {
return LabelStyle{Text: str, Color: color, ShadeColor: black, Font: labelDefaultFont, FontSize: labelDefaultFontSize, Alignment: layout.W, Shaper: shaper}.Layout return LabelWidget{Text: txt, Shaper: th.Material.Shaper, LabelStyle: *style}
} }

View File

@ -10,7 +10,6 @@ import (
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit" "gioui.org/unit"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
@ -27,13 +26,11 @@ type Menu struct {
type MenuStyle struct { type MenuStyle struct {
Menu *Menu Menu *Menu
Title string Title string
IconColor color.NRGBA
TextColor color.NRGBA
ShortCutColor color.NRGBA ShortCutColor color.NRGBA
FontSize unit.Sp
IconSize unit.Dp
HoverColor color.NRGBA HoverColor color.NRGBA
Shaper *text.Shaper Theme *Theme
LabelStyle LabelStyle
Disabled color.NRGBA
} }
type MenuItem struct { type MenuItem struct {
@ -100,21 +97,21 @@ func (m *MenuStyle) Layout(gtx C, items ...MenuItem) D {
macro = op.Record(gtx.Ops) macro = op.Record(gtx.Ops)
} }
icon := widgetForIcon(item.IconBytes) icon := widgetForIcon(item.IconBytes)
iconColor := m.IconColor iconColor := m.LabelStyle.Color
if !item.Doer.Allowed() {
iconColor = mediumEmphasisTextColor
}
iconInset := layout.Inset{Left: unit.Dp(12), Right: unit.Dp(6)} 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() { 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)} 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, dims := layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return iconInset.Layout(gtx, 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) gtx.Constraints.Min = image.Pt(p, p)
return icon.Layout(gtx, iconColor) return icon.Layout(gtx, iconColor)
}) })
@ -142,27 +139,25 @@ func (m *MenuStyle) Layout(gtx C, items ...MenuItem) D {
}) })
}), }),
layout.Expanded(func(gtx C) 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.NE = unit.Dp(0)
popup.ShadowN = unit.Dp(0) popup.ShadowN = unit.Dp(0)
popup.NW = unit.Dp(0) popup.NW = unit.Dp(0)
return popup.Layout(gtx, contents) return popup.Layout(gtx, contents)
} }
func PopupMenu(menu *Menu, shaper *text.Shaper) MenuStyle { func PopupMenu(th *Theme, s *LabelStyle, menu *Menu) MenuStyle {
return MenuStyle{ return MenuStyle{
Menu: menu, Menu: menu,
IconColor: white, ShortCutColor: th.Menu.ShortCut,
TextColor: white, LabelStyle: *s,
ShortCutColor: mediumEmphasisTextColor, HoverColor: th.Menu.Hover,
FontSize: unit.Sp(16), Disabled: th.Menu.Disabled,
IconSize: unit.Dp(16), Theme: th,
HoverColor: menuHoverColor,
Shaper: shaper,
} }
} }
@ -170,12 +165,10 @@ func (tr *Tracker) layoutMenu(gtx C, title string, clickable *Clickable, menu *M
for clickable.Clicked(gtx) { for clickable.Clicked(gtx) {
menu.Visible = true menu.Visible = true
} }
m := PopupMenu(menu, tr.Theme.Shaper) m := PopupMenu(tr.Theme, &tr.Theme.Menu.Text, menu)
return func(gtx C) D { return func(gtx C) D {
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
titleBtn := Button(tr.Theme, clickable, title) titleBtn := Btn(tr.Theme, &tr.Theme.Button.Menu, clickable, title)
titleBtn.Color = white
titleBtn.Background = transparent
titleBtn.CornerRadius = unit.Dp(0) titleBtn.CornerRadius = unit.Dp(0)
dims := titleBtn.Layout(gtx) dims := titleBtn.Layout(gtx)
op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops) op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops)

View File

@ -150,17 +150,17 @@ func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions {
func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D { 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 { 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") addSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.AddSemitoneBtn, "+1")
subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, te.SubtractSemitoneBtn, "-1") subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.SubtractSemitoneBtn, "-1")
addOctaveBtnStyle := ActionButton(gtx, t.Theme, te.AddOctaveBtn, "+12") addOctaveBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.AddOctaveBtn, "+12")
subtractOctaveBtnStyle := ActionButton(gtx, t.Theme, te.SubtractOctaveBtn, "-12") subtractOctaveBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.SubtractOctaveBtn, "-12")
noteOffBtnStyle := ActionButton(gtx, t.Theme, te.NoteOffBtn, "Note Off") noteOffBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.NoteOffBtn, "Note Off")
deleteTrackBtnStyle := ActionIcon(gtx, t.Theme, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint) deleteTrackBtnStyle := ActionIcon(gtx, t.Theme, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint)
splitTrackBtnStyle := ActionIcon(gtx, t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint) splitTrackBtnStyle := ActionIcon(gtx, t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint)
newTrackBtnStyle := ActionIcon(gtx, t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint) newTrackBtnStyle := ActionIcon(gtx, t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint)
in := layout.UniformInset(unit.Dp(1)) in := layout.UniformInset(unit.Dp(1))
voiceUpDown := func(gtx C) D { voiceUpDown := func(gtx C) D {
numStyle := NumericUpDown(t.Theme, te.TrackVoices, "Track voices") numStyle := NumUpDown(t.Theme, te.TrackVoices, "Track voices")
return in.Layout(gtx, numStyle.Layout) return in.Layout(gtx, numStyle.Layout)
} }
effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex") 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(effectBtnStyle.Layout),
layout.Rigid(uniqueBtnStyle.Layout), layout.Rigid(uniqueBtnStyle.Layout),
layout.Rigid(layout.Spacer{Width: 10}.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(layout.Spacer{Width: 4}.Layout),
layout.Rigid(voiceUpDown), layout.Rigid(voiceUpDown),
layout.Rigid(splitTrackBtnStyle.Layout), layout.Rigid(splitTrackBtnStyle.Layout),
@ -224,28 +224,26 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
colTitle := func(gtx C, i int) D { colTitle := func(gtx C, i int) D {
h := gtx.Dp(trackColTitleHeight) h := gtx.Dp(trackColTitleHeight)
gtx.Constraints = layout.Exact(image.Pt(pxWidth, h)) gtx.Constraints = layout.Exact(image.Pt(pxWidth, h))
LabelStyle{ Label(t.Theme, &t.Theme.NoteEditor.TrackTitle, t.Model.TrackTitle(i)).Layout(gtx)
Alignment: layout.N,
Text: t.Model.TrackTitle(i),
FontSize: unit.Sp(12),
Color: mediumEmphasisTextColor,
Shaper: t.Theme.Shaper,
}.Layout(gtx)
return D{Size: image.Pt(pxWidth, h)} return D{Size: image.Pt(pxWidth, h)}
} }
rowTitleBg := func(gtx C, j int) D { rowTitleBg := func(gtx C, j int) D {
if mod(j, beatMarkerDensity*2) == 0 { 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 { } 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 { 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{} 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 { rowTitle := func(gtx C, j int) D {
rpp := max(t.RowsPerPattern().Value(), 1) rpp := max(t.RowsPerPattern().Value(), 1)
pat := j / rpp pat := j / rpp
@ -253,16 +251,14 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
w := pxPatMarkWidth + pxRowMarkWidth w := pxPatMarkWidth + pxRowMarkWidth
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
if row == 0 { if row == 0 {
color := rowMarkerPatternTextColor op := orderRowOp
if l := t.Loop(); pat >= l.Start && pat < l.Start+l.Length { 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.Material.Shaper, t.Theme.NoteEditor.OrderRow.Font, t.Theme.NoteEditor.OrderRow.TextSize, strings.ToUpper(fmt.Sprintf("%02x", pat)), op)
widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", pat)), op.CallOp{})
} }
defer op.Offset(image.Pt(pxPatMarkWidth, 0)).Push(gtx.Ops).Pop() defer op.Offset(image.Pt(pxPatMarkWidth, 0)).Push(gtx.Ops).Pop()
paint.ColorOp{Color: rowMarkerRowTextColor}.Add(gtx.Ops) 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)
widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", row)), op.CallOp{})
return D{Size: image.Pt(w, pxHeight)} 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() selection := te.scrollTable.Table.Range()
hasTrackMidiIn := te.TrackMidiInBtn.Bool.Value() 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 { cell := func(gtx C, x, y int) D {
// draw the background, to indicate selection // draw the background, to indicate selection
color := transparent
point := tracker.Point{X: x, Y: y} point := tracker.Point{X: x, Y: y}
if drawSelection && selection.Contains(point) { if drawSelection && selection.Contains(point) {
color = inactiveSelectionColor color := t.Theme.Selection.Inactive
if te.scrollTable.Focused() { 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 // draw the cursor
if point == cursor { if point == cursor {
c := inactiveSelectionColor c := t.Theme.Cursor.Inactive
if te.scrollTable.Focused() { if te.scrollTable.Focused() {
c = cursorColor c = t.Theme.Cursor.Active
} }
if hasTrackMidiIn { if hasTrackMidiIn {
c = cursorForTrackMidiInColor c = t.Theme.Cursor.ActiveAlt
} }
te.paintColumnCell(gtx, x, t, c) te.paintColumnCell(gtx, x, t, c)
} }
@ -297,7 +296,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
if hasTrackMidiIn { if hasTrackMidiIn {
for _, trackIndex := range t.Model.TracksWithSameInstrumentAsCurrent() { for _, trackIndex := range t.Model.TracksWithSameInstrumentAsCurrent() {
if x == trackIndex && y == cursor.Y { 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() defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
s := t.Model.Order().Value(tracker.Point{X: x, Y: pat}) s := t.Model.Order().Value(tracker.Point{X: x, Y: pat})
if row == 0 { // draw the pattern marker if row == 0 { // draw the pattern marker
paint.ColorOp{Color: trackerPatMarker}.Add(gtx.Ops) widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.PatternNo.Font, t.Theme.NoteEditor.PatternNo.TextSize, patternIndexToString(s), patternNoOp)
widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, patternIndexToString(s), op.CallOp{})
} }
if row == 1 && t.Model.PatternUnique(x, s) { // draw a * if the pattern is unique 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.Material.Shaper, t.Theme.NoteEditor.Unique.Font, t.Theme.NoteEditor.Unique.TextSize, "*", uniqueOp)
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)
} }
op := noteOp
val := noteStr[byte(t.Model.Notes().Value(tracker.Point{X: x, Y: y}))] val := noteStr[byte(t.Model.Notes().Value(tracker.Point{X: x, Y: y}))]
if t.Model.Notes().Effect(x) { if t.Model.Notes().Effect(x) {
val = hexStr[byte(t.Model.Notes().Value(tracker.Point{X: x, Y: y}))] 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)} return D{Size: image.Pt(pxWidth, pxHeight)}
} }
table := FilledScrollTable(t.Theme, te.scrollTable, cell, colTitle, rowTitle, nil, rowTitleBg) 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) 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) { func (te *NoteEditor) paintColumnCell(gtx C, x int, t *Tracker, c color.NRGBA) {
cw := gtx.Constraints.Min.X cw := gtx.Constraints.Min.X
cx := 0 cx := 0

View File

@ -12,6 +12,7 @@ import (
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget" "gioui.org/widget"
"gioui.org/x/component" "gioui.org/x/component"
@ -20,8 +21,6 @@ import (
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/text" "gioui.org/text"
"gioui.org/unit"
"gioui.org/widget/material"
) )
type NumberInput struct { type NumberInput struct {
@ -34,48 +33,41 @@ type NumberInput struct {
} }
type NumericUpDownStyle struct { type NumericUpDownStyle struct {
NumberInput *NumberInput TextColor color.NRGBA `yaml:",flow"`
Color color.NRGBA IconColor color.NRGBA `yaml:",flow"`
Font font.Font BgColor color.NRGBA `yaml:",flow"`
TextSize unit.Sp CornerRadius unit.Dp
BorderColor color.NRGBA ButtonWidth unit.Dp
IconColor color.NRGBA Width unit.Dp
BackgroundColor color.NRGBA Height unit.Dp
CornerRadius unit.Dp TextSize unit.Sp
Border unit.Dp DpPerStep unit.Dp
ButtonWidth unit.Dp }
UnitsPerStep unit.Dp
Tooltip component.Tooltip type NumericUpDown struct {
Width unit.Dp NumberInput *NumberInput
Height unit.Dp Tooltip component.Tooltip
shaper text.Shaper Shaper *text.Shaper
Font font.Font
NumericUpDownStyle
} }
func NewNumberInput(v tracker.Int) *NumberInput { func NewNumberInput(v tracker.Int) *NumberInput {
return &NumberInput{Int: v} return &NumberInput{Int: v}
} }
func NumericUpDown(th *material.Theme, number *NumberInput, tooltip string) NumericUpDownStyle { func NumUpDown(th *Theme, number *NumberInput, tooltip string) NumericUpDown {
return NumericUpDownStyle{ return NumericUpDown{
NumberInput: number, NumberInput: number,
Color: white, Shaper: th.Material.Shaper,
IconColor: th.Palette.Fg, Tooltip: Tooltip(th, tooltip),
BackgroundColor: numberInputBgColor, NumericUpDownStyle: th.NumericUpDown,
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 (s *NumericUpDownStyle) Update(gtx layout.Context) { func (s *NumericUpDown) Update(gtx layout.Context) {
// handle dragging // handle dragging
pxPerStep := float32(gtx.Dp(s.UnitsPerStep)) pxPerStep := float32(gtx.Dp(s.DpPerStep))
for { for {
ev, ok := gtx.Event(pointer.Filter{ ev, ok := gtx.Event(pointer.Filter{
Target: s.NumberInput, Target: s.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 != "" { if s.Tooltip.Text.Text != "" {
return s.NumberInput.tipArea.Layout(gtx, s.Tooltip, s.actualLayout) return s.NumberInput.tipArea.Layout(gtx, s.Tooltip, s.actualLayout)
} }
return s.actualLayout(gtx) return s.actualLayout(gtx)
} }
func (s *NumericUpDownStyle) actualLayout(gtx C) D { func (s *NumericUpDown) actualLayout(gtx C) D {
s.Update(gtx) s.Update(gtx)
gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(s.Width), gtx.Dp(s.Height))) gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(s.Width), gtx.Dp(s.Height)))
width := gtx.Dp(s.ButtonWidth) width := gtx.Dp(s.ButtonWidth)
@ -125,7 +117,7 @@ func (s *NumericUpDownStyle) actualLayout(gtx C) D {
return layout.Background{}.Layout(gtx, return layout.Background{}.Layout(gtx,
func(gtx C) D { func(gtx C) D {
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(s.CornerRadius)).Push(gtx.Ops).Pop() defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(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 event.Op(gtx.Ops, s.NumberInput) // register drag inputs, if not hitting the clicks
return D{Size: gtx.Constraints.Min} return D{Size: gtx.Constraints.Min}
}, },
@ -143,8 +135,8 @@ func (s *NumericUpDownStyle) actualLayout(gtx C) D {
) )
}), }),
layout.Flexed(1, func(gtx C) D { layout.Flexed(1, func(gtx C) D {
paint.ColorOp{Color: s.Color}.Add(gtx.Ops) 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{}) 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 { layout.Rigid(func(gtx C) D {
gtx.Constraints = layout.Exact(image.Pt(width, height)) gtx.Constraints = layout.Exact(image.Pt(width, height))

View File

@ -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.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() 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)) gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6))
LabelStyle{ Label(t.Theme, &t.Theme.OrderEditor.TrackTitle, t.Model.TrackTitle(i)).Layout(gtx)
Alignment: layout.NW,
Text: t.Model.TrackTitle(i),
FontSize: unit.Sp(12),
Color: mediumEmphasisTextColor,
Shaper: t.Theme.Shaper,
}.Layout(gtx)
return D{Size: image.Pt(gtx.Dp(patternCellWidth), h)} return D{Size: image.Pt(gtx.Dp(patternCellWidth), h)}
} }
rowTitleBg := func(gtx C, j int) D { rowTitleBg := func(gtx C, j int) D {
if t.Model.Playing().Value() && j == t.PlayPosition().OrderRow { 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{} return D{}
} }
rowMarkerPatternTextColorOp := colorOp(gtx, t.Theme.OrderEditor.RowTitle.Color)
loopMarkerColorOp := colorOp(gtx, t.Theme.OrderEditor.Loop)
rowTitle := func(gtx C, j int) D { rowTitle := func(gtx C, j int) D {
w := gtx.Dp(unit.Dp(30)) w := gtx.Dp(unit.Dp(30))
color := rowMarkerPatternTextColor callOp := rowMarkerPatternTextColorOp
if l := t.Loop(); j >= l.Start && j < l.Start+l.Length { 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() 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))} return D{Size: image.Pt(w, gtx.Dp(patternCellHeight))}
} }
selection := oe.scrollTable.Table.Range() selection := oe.scrollTable.Table.Range()
cellColorOp := colorOp(gtx, t.Theme.OrderEditor.Cell.Color)
cell := func(gtx C, x, y int) D { cell := func(gtx C, x, y int) D {
val := patternIndexToString(t.Model.Order().Value(tracker.Point{X: x, Y: y})) 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} point := tracker.Point{X: x, Y: y}
if selection.Contains(point) { if selection.Contains(point) {
color = inactiveSelectionColor color = t.Theme.Selection.Inactive
if oe.scrollTable.Focused() { if oe.scrollTable.Focused() {
color = selectionColor color = t.Theme.Selection.Active
if point == oe.scrollTable.Table.Cursor() { }
color = cursorColor 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.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() 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))} return D{Size: image.Pt(gtx.Dp(patternCellWidth), gtx.Dp(patternCellHeight))}
} }

View File

@ -12,12 +12,11 @@ import (
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/unit" "gioui.org/unit"
"gioui.org/widget/material"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
type ( type (
Oscilloscope struct { OscilloscopeState struct {
onceBtn *BoolClickable onceBtn *BoolClickable
wrapBtn *BoolClickable wrapBtn *BoolClickable
lengthInBeatsNumber *NumberInput lengthInBeatsNumber *NumberInput
@ -31,16 +30,21 @@ type (
} }
OscilloscopeStyle struct { OscilloscopeStyle struct {
Oscilloscope *Oscilloscope CurveColors [2]color.NRGBA `yaml:",flow"`
Wave tracker.RingBuffer[[2]float32] LimitColor color.NRGBA `yaml:",flow"`
Colors [2]color.NRGBA CursorColor color.NRGBA `yaml:",flow"`
ClippedColor color.NRGBA }
Theme *material.Theme
Oscilloscope struct {
State *OscilloscopeState
Wave tracker.RingBuffer[[2]float32]
Theme *Theme
OscilloscopeStyle
} }
) )
func NewOscilloscope(model *tracker.Model) *Oscilloscope { func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
return &Oscilloscope{ return &OscilloscopeState{
onceBtn: NewBoolClickable(model.SignalAnalyzer().Once().Bool()), onceBtn: NewBoolClickable(model.SignalAnalyzer().Once().Bool()),
wrapBtn: NewBoolClickable(model.SignalAnalyzer().Wrap().Bool()), wrapBtn: NewBoolClickable(model.SignalAnalyzer().Wrap().Bool()),
lengthInBeatsNumber: NewNumberInput(model.SignalAnalyzer().LengthInBeats().Int()), 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 { func Scope(s *OscilloscopeState, wave tracker.RingBuffer[[2]float32], th *Theme) *Oscilloscope {
return &OscilloscopeStyle{Oscilloscope: s, Wave: wave, Colors: [2]color.NRGBA{primaryColor, secondaryColor}, Theme: th, ClippedColor: errorColor} return &Oscilloscope{State: s, Wave: wave, Theme: th}
} }
func (s *OscilloscopeStyle) Layout(gtx C) D { func (s *Oscilloscope) Layout(gtx C) D {
wrapBtnStyle := ToggleButton(gtx, s.Theme, s.Oscilloscope.wrapBtn, "Wrap") wrapBtnStyle := ToggleButton(gtx, s.Theme, s.State.wrapBtn, "Wrap")
onceBtnStyle := ToggleButton(gtx, s.Theme, s.Oscilloscope.onceBtn, "Once") onceBtnStyle := ToggleButton(gtx, s.Theme, s.State.onceBtn, "Once")
triggerChannelStyle := NumericUpDown(s.Theme, s.Oscilloscope.triggerChannelNumber, "Trigger channel") triggerChannelStyle := NumUpDown(s.Theme, s.State.triggerChannelNumber, "Trigger channel")
lengthNumberStyle := NumericUpDown(s.Theme, s.Oscilloscope.lengthInBeatsNumber, "Buffer length in beats") lengthNumberStyle := NumUpDown(s.Theme, s.State.lengthInBeatsNumber, "Buffer length in beats")
leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
@ -66,7 +70,7 @@ func (s *OscilloscopeStyle) Layout(gtx C) D {
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(leftSpacer), layout.Rigid(leftSpacer),
layout.Rigid(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.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
layout.Rigid(onceBtnStyle.Layout), layout.Rigid(onceBtnStyle.Layout),
layout.Rigid(triggerChannelStyle.Layout), layout.Rigid(triggerChannelStyle.Layout),
@ -76,7 +80,7 @@ func (s *OscilloscopeStyle) Layout(gtx C) D {
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(leftSpacer), layout.Rigid(leftSpacer),
layout.Rigid(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.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
layout.Rigid(wrapBtnStyle.Layout), layout.Rigid(wrapBtnStyle.Layout),
layout.Rigid(lengthNumberStyle.Layout), layout.Rigid(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) s.update(gtx)
if gtx.Constraints.Max.X == 0 || gtx.Constraints.Max.Y == 0 { if gtx.Constraints.Max.X == 0 || gtx.Constraints.Max.Y == 0 {
return D{} return D{}
} }
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
event.Op(gtx.Ops, s.Oscilloscope) event.Op(gtx.Ops, s.State)
paint.ColorOp{Color: oscilloscopeCursorColor}.Add(gtx.Ops) paint.ColorOp{Color: s.Theme.Oscilloscope.CursorColor}.Add(gtx.Ops)
cursorX := int(s.sampleToPx(gtx, float32(s.Wave.Cursor))) 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)}) 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)) minusOneY := int(s.ampToY(gtx, -1))
fillRect(gtx, clip.Rect{Min: image.Pt(0, minusOneY), Max: image.Pt(gtx.Constraints.Max.X, minusOneY+1)}) fillRect(gtx, clip.Rect{Min: image.Pt(0, minusOneY), Max: image.Pt(gtx.Constraints.Max.X, minusOneY+1)})
plusOneY := int(s.ampToY(gtx, 1)) plusOneY := int(s.ampToY(gtx, 1))
@ -106,7 +110,7 @@ func (s *OscilloscopeStyle) layoutWave(gtx C) D {
rightX := int(s.sampleToPx(gtx, float32(len(s.Wave.Buffer)-1))) 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)}) fillRect(gtx, clip.Rect{Min: image.Pt(rightX, 0), Max: image.Pt(rightX+1, gtx.Constraints.Max.Y)})
for chn := range 2 { for chn := range 2 {
paint.ColorOp{Color: s.Colors[chn]}.Add(gtx.Ops) paint.ColorOp{Color: s.Theme.Oscilloscope.CurveColors[chn]}.Add(gtx.Ops)
for px := range gtx.Constraints.Max.X { for px := range gtx.Constraints.Max.X {
// left and right is the sample range covered by the pixel // left and right is the sample range covered by the pixel
left := int(s.pxToSample(gtx, float32(px)-0.5)) left := int(s.pxToSample(gtx, float32(px)-0.5))
@ -138,10 +142,10 @@ func fillRect(gtx C, rect clip.Rect) {
stack.Pop() stack.Pop()
} }
func (o *OscilloscopeStyle) update(gtx C) { func (o *Oscilloscope) update(gtx C) {
for { for {
ev, ok := gtx.Event(pointer.Filter{ ev, ok := gtx.Event(pointer.Filter{
Target: o.Oscilloscope, Target: o.State,
Kinds: pointer.Scroll | pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel, Kinds: pointer.Scroll | pointer.Press | pointer.Drag | pointer.Release | pointer.Cancel,
ScrollY: pointer.ScrollRange{Min: -1e6, Max: 1e6}, ScrollY: pointer.ScrollRange{Min: -1e6, Max: 1e6},
}) })
@ -152,58 +156,58 @@ func (o *OscilloscopeStyle) update(gtx C) {
switch e.Kind { switch e.Kind {
case pointer.Scroll: case pointer.Scroll:
s1 := o.pxToSample(gtx, e.Position.X) s1 := o.pxToSample(gtx, e.Position.X)
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) s2 := o.pxToSample(gtx, e.Position.X)
o.Oscilloscope.xOffset -= s1 - s2 o.State.xOffset -= s1 - s2
case pointer.Press: case pointer.Press:
if e.Buttons&pointer.ButtonSecondary != 0 { if e.Buttons&pointer.ButtonSecondary != 0 {
o.Oscilloscope.xOffset = 0 o.State.xOffset = 0
o.Oscilloscope.xScale = 0 o.State.xScale = 0
o.Oscilloscope.yScale = 0 o.State.yScale = 0
} }
if e.Buttons&pointer.ButtonPrimary != 0 { if e.Buttons&pointer.ButtonPrimary != 0 {
o.Oscilloscope.dragging = true o.State.dragging = true
o.Oscilloscope.dragId = e.PointerID o.State.dragId = e.PointerID
o.Oscilloscope.dragStartPoint = e.Position o.State.dragStartPoint = e.Position
} }
case pointer.Drag: case pointer.Drag:
if e.Buttons&pointer.ButtonPrimary != 0 && o.Oscilloscope.dragging && e.PointerID == o.Oscilloscope.dragId { 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.Oscilloscope.dragStartPoint.X) deltaX := o.pxToSample(gtx, e.Position.X) - o.pxToSample(gtx, o.State.dragStartPoint.X)
o.Oscilloscope.xOffset += deltaX o.State.xOffset += deltaX
num := o.yToAmp(gtx, e.Position.Y) 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 { if l := math.Abs(float64(num / den)); l > 1e-3 && l < 1e3 {
o.Oscilloscope.yScale += math.Log(l) o.State.yScale += math.Log(l)
o.Oscilloscope.yScale = min(max(o.Oscilloscope.yScale, -1e3), 1e3) 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: case pointer.Release | pointer.Cancel:
o.Oscilloscope.dragging = false o.State.dragging = false
} }
} }
} }
} }
func (o *OscilloscopeStyle) scaleFactor() float32 { func (o *Oscilloscope) scaleFactor() float32 {
return float32(math.Pow(1.1, float64(o.Oscilloscope.xScale))) return float32(math.Pow(1.1, float64(o.State.xScale)))
} }
func (s *OscilloscopeStyle) pxToSample(gtx C, px float32) float32 { func (s *Oscilloscope) pxToSample(gtx C, px float32) float32 {
return px*s.scaleFactor()*float32(len(s.Wave.Buffer))/float32(gtx.Constraints.Max.X) - s.Oscilloscope.xOffset 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 { func (s *Oscilloscope) sampleToPx(gtx C, sample float32) float32 {
return (sample + s.Oscilloscope.xOffset) * float32(gtx.Constraints.Max.X) / float32(len(s.Wave.Buffer)) / s.scaleFactor() 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 { func (s *Oscilloscope) ampToY(gtx C, amp float32) float32 {
scale := float32(math.Exp(s.Oscilloscope.yScale)) scale := float32(math.Exp(s.State.yScale))
return (1 - amp*scale) / 2 * float32(gtx.Constraints.Max.Y-1) return (1 - amp*scale) / 2 * float32(gtx.Constraints.Max.Y-1)
} }
func (s *OscilloscopeStyle) yToAmp(gtx C, y float32) float32 { func (s *Oscilloscope) yToAmp(gtx C, y float32) float32 {
scale := float32(math.Exp(s.Oscilloscope.yScale)) scale := float32(math.Exp(s.State.yScale))
return (1 - y/float32(gtx.Constraints.Max.Y-1)*2) / scale return (1 - y/float32(gtx.Constraints.Max.Y-1)*2) / scale
} }

View File

@ -24,11 +24,11 @@ type PopupStyle struct {
SE, SW, NW, NE unit.Dp SE, SW, NW, NE unit.Dp
} }
func Popup(visible *bool) PopupStyle { func Popup(th *Theme, visible *bool) PopupStyle {
return PopupStyle{ return PopupStyle{
Visible: visible, Visible: visible,
SurfaceColor: popupSurfaceColor, SurfaceColor: th.Popup.Bg,
ShadowColor: popupShadowColor, ShadowColor: th.Popup.Shadow,
ShadowN: unit.Dp(2), ShadowN: unit.Dp(2),
ShadowE: unit.Dp(2), ShadowE: unit.Dp(2),
ShadowS: unit.Dp(2), ShadowS: unit.Dp(2),

View File

@ -9,7 +9,6 @@ import (
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit" "gioui.org/unit"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
@ -17,17 +16,21 @@ import (
type PopupAlert struct { type PopupAlert struct {
alerts *tracker.Alerts alerts *tracker.Alerts
prevUpdate time.Time prevUpdate time.Time
shaper *text.Shaper }
type PopupAlertStyle struct {
Bg color.NRGBA
Text LabelStyle
} }
var alertMargin = layout.UniformInset(unit.Dp(6)) var alertMargin = layout.UniformInset(unit.Dp(6))
var alertInset = layout.UniformInset(unit.Dp(6)) var alertInset = layout.UniformInset(unit.Dp(6))
func NewPopupAlert(alerts *tracker.Alerts, shaper *text.Shaper) *PopupAlert { func NewPopupAlert(alerts *tracker.Alerts) *PopupAlert {
return &PopupAlert{alerts: alerts, shaper: shaper, prevUpdate: time.Now()} 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() now := time.Now()
if a.alerts.Update(now.Sub(a.prevUpdate)) { if a.alerts.Update(now.Sub(a.prevUpdate)) {
gtx.Execute(op.InvalidateCmd{At: now.Add(50 * time.Millisecond)}) 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)) var totalY float64 = float64(gtx.Dp(38))
for _, alert := range a.alerts.Iterate { for _, alert := range a.alerts.Iterate {
var color, textColor, shadeColor color.NRGBA var alertStyle *PopupAlertStyle
switch alert.Priority { switch alert.Priority {
case tracker.Warning: case tracker.Warning:
color = warningColor alertStyle = &th.Alert.Warning
textColor = black
case tracker.Error: case tracker.Error:
color = errorColor alertStyle = &th.Alert.Error
textColor = black
default: default:
color = popupSurfaceColor alertStyle = &th.Alert.Info
textColor = white
shadeColor = black
} }
bgWidget := func(gtx C) D { bgWidget := func(gtx C) D {
paint.FillShape(gtx.Ops, color, clip.Rect{ paint.FillShape(gtx.Ops, alertStyle.Bg, clip.Rect{
Max: gtx.Constraints.Min, Max: gtx.Constraints.Min,
}.Op()) }.Op())
return D{Size: gtx.Constraints.Min} 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 { alertMargin.Layout(gtx, func(gtx C) D {
return layout.S.Layout(gtx, func(gtx C) D { return layout.S.Layout(gtx, func(gtx C) D {
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()

View File

@ -14,7 +14,6 @@ import (
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/unit" "gioui.org/unit"
"gioui.org/widget/material"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
@ -72,7 +71,7 @@ func NewScrollTable(table tracker.Table, vertList, horizList tracker.List) *Scro
return ret 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{ return ScrollTableStyle{
RowTitleStyle: FilledDragList(th, scrollTable.RowTitleList, rowTitle, rowTitleBg), RowTitleStyle: FilledDragList(th, scrollTable.RowTitleList, rowTitle, rowTitleBg),
ColTitleStyle: FilledDragList(th, scrollTable.ColTitleList, colTitle, colTitleBg), ColTitleStyle: FilledDragList(th, scrollTable.ColTitleList, colTitle, colTitleBg),

View File

@ -2,6 +2,7 @@ package gioui
import ( import (
"image" "image"
"image/color"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/io/event" "gioui.org/io/event"
@ -21,19 +22,27 @@ type ScrollBar struct {
tag bool 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 op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop() defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
gradientSize := gtx.Dp(unit.Dp(4)) gradientSize := gtx.Dp(unit.Dp(4))
var totalPixelsEstimate, scrollBarRelLength float32 var totalPixelsEstimate, scrollBarRelLength float32
transparent := style.Gradient
transparent.A = 0
switch s.Axis { switch s.Axis {
case layout.Vertical: case layout.Vertical:
if pos.First > 0 || pos.Offset > 0 { 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) paint.PaintOp{}.Add(gtx.Ops)
} }
if pos.BeforeEnd { 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) paint.PaintOp{}.Add(gtx.Ops)
} }
totalPixelsEstimate = float32(gtx.Constraints.Min.Y+pos.Offset-pos.OffsetLast) * float32(numItems) / float32(pos.Count) 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: case layout.Horizontal:
if pos.First > 0 || pos.Offset > 0 { 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) paint.PaintOp{}.Add(gtx.Ops)
} }
if pos.BeforeEnd { 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) paint.PaintOp{}.Add(gtx.Ops)
} }
totalPixelsEstimate = float32(gtx.Constraints.Min.X+pos.Offset-pos.OffsetLast) * float32(numItems) / float32(pos.Count) 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 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) stack := op.Offset(image.Point{}).Push(gtx.Ops)
var area clip.Stack 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) { if scrollBarRelLength < 1 && (s.dragging || s.hovering) {
y1 := int(scrollBarRelStart * float32(gtx.Constraints.Min.Y)) y1 := int(scrollBarRelStart * float32(gtx.Constraints.Min.Y))
y2 := int((scrollBarRelStart + scrollBarRelLength) * 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) rect := image.Rect(gtx.Constraints.Min.X-scrWidth, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area = clip.Rect(rect).Push(gtx.Ops) 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) { if scrollBarRelLength < 1 && (s.dragging || s.hovering) {
x1 := int(scrollBarRelStart * float32(gtx.Constraints.Min.X)) x1 := int(scrollBarRelStart * float32(gtx.Constraints.Min.X))
x2 := int((scrollBarRelStart + scrollBarRelLength) * 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) rect := image.Rect(0, gtx.Constraints.Min.Y-scrWidth, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area = clip.Rect(rect).Push(gtx.Ops) area = clip.Rect(rect).Push(gtx.Ops)

View File

@ -11,7 +11,6 @@ import (
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/unit" "gioui.org/unit"
"gioui.org/widget/material"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
"github.com/vsariola/sointu/version" "github.com/vsariola/sointu/version"
"golang.org/x/exp/shiny/materialdesign/icons" "golang.org/x/exp/shiny/materialdesign/icons"
@ -32,7 +31,7 @@ type SongPanel struct {
Step *NumberInput Step *NumberInput
SongLength *NumberInput SongLength *NumberInput
Scope *Oscilloscope Scope *OscilloscopeState
MenuBar *MenuBar MenuBar *MenuBar
PlayBar *PlayBar PlayBar *PlayBar
@ -76,17 +75,7 @@ func (s *SongPanel) Layout(gtx C, t *Tracker) D {
return s.MenuBar.Layout(gtx, t) return s.MenuBar.Layout(gtx, t)
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layout.Background{}.Layout(gtx, return s.PlayBar.Layout(gtx, t.Theme)
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)
},
)
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return s.layoutSongOptions(gtx, t) 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 { 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()) paint.FillShape(gtx.Ops, tr.Theme.SongPanel.Bg, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op())
scopeStyle := LineOscilloscope(t.Scope, tr.SignalAnalyzer().Waveform(), tr.Theme)
var weightingTxt string var weightingTxt string
switch tracker.WeightingType(tr.Model.DetectorWeighting().Value()) { switch tracker.WeightingType(tr.Model.DetectorWeighting().Value()) {
@ -111,38 +98,36 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D {
weightingTxt = "No weight (RMS)" weightingTxt = "No weight (RMS)"
} }
weightingBtn := LowEmphasisButton(tr.Theme, t.WeightingTypeBtn, weightingTxt) weightingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.WeightingTypeBtn, weightingTxt)
weightingBtn.Color = mediumEmphasisTextColor
oversamplingTxt := "Sample peak" oversamplingTxt := "Sample peak"
if tr.Model.Oversampling().Value() { if tr.Model.Oversampling().Value() {
oversamplingTxt = "True peak" oversamplingTxt = "True peak"
} }
oversamplingBtn := LowEmphasisButton(tr.Theme, t.OversamplingBtn, oversamplingTxt) oversamplingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.OversamplingBtn, oversamplingTxt)
oversamplingBtn.Color = mediumEmphasisTextColor
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return t.SongSettingsExpander.Layout(gtx, tr.Theme, "Song", return t.SongSettingsExpander.Layout(gtx, tr.Theme, "Song",
func(gtx C) D { 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 { func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "BPM", 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 { 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 { 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 { 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 { 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 { layout.Rigid(func(gtx C) D {
return t.LoudnessExpander.Layout(gtx, tr.Theme, "Loudness", return t.LoudnessExpander.Layout(gtx, tr.Theme, "Loudness",
func(gtx C) D { 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 { func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.End}.Layout(gtx, 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 { layout.Flexed(1, func(gtx C) D {
return t.ScopeExpander.Layout(gtx, tr.Theme, "Oscilloscope", func(gtx C) D { return D{} }, scopeStyle.Layout) 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(func(gtx C) D {
labelStyle := LabelStyle{Text: version.VersionOrHash, FontSize: unit.Sp(12), Color: mediumEmphasisTextColor, Shaper: tr.Theme.Shaper}
return labelStyle.Layout(gtx)
}), }),
layout.Rigid(Label(tr.Theme, &tr.Theme.SongPanel.Version, version.VersionOrHash).Layout),
) )
} }
func dbLabel(th *material.Theme, value tracker.Decibel) LabelStyle { func dbLabel(th *Theme, value tracker.Decibel) LabelWidget {
color := mediumEmphasisTextColor ret := Label(th, &th.SongPanel.RowValue, fmt.Sprintf("%.1f dB", value))
if value >= 0 { if value >= 0 {
color = errorColor ret.Color = th.SongPanel.ErrorColor
}
return LabelStyle{
Text: fmt.Sprintf("%.1f dB", value),
Color: color,
Alignment: layout.W,
FontSize: th.TextSize * 14.0 / 16.0,
Shaper: th.Shaper,
} }
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 leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(leftSpacer), layout.Rigid(leftSpacer),
layout.Rigid(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.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
layout.Rigid(widget), layout.Rigid(widget),
layout.Rigid(rightSpacer), 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) e.Update(gtx)
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D { return e.layoutHeader(gtx, th, title, smallWidget) }), 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, return layout.Background{}.Layout(gtx,
func(gtx C) D { func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)).Push(gtx.Ops).Pop() 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 leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(leftSpacer), layout.Rigid(leftSpacer),
layout.Rigid(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.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
if !e.Expanded { if !e.Expanded {
@ -302,7 +278,7 @@ func (e *Expander) layoutHeader(gtx C, th *material.Theme, title string, smallWi
icon = icons.NavigationExpandLess icon = icons.NavigationExpandLess
} }
gtx.Constraints.Min = image.Pt(gtx.Dp(unit.Dp(24)), gtx.Dp(unit.Dp(24))) 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) panicBtnStyle := ToggleIcon(gtx, tr.Theme, t.PanicBtn, icons.AlertErrorOutline, icons.AlertError, t.panicHint, t.panicHint)
if t.PanicBtn.Bool.Value() { if t.PanicBtn.Bool.Value() {
panicBtnStyle.IconButtonStyle.Color = errorColor panicBtnStyle.IconButtonStyle.Color = tr.Theme.SongPanel.ErrorColor
} }
menuLayouts := []layout.FlexChild{ menuLayouts := []layout.FlexChild{
layout.Rigid(tr.layoutMenu(gtx, "File", &t.Clickables[0], &t.Menus[0], unit.Dp(200), t.fileMenuItems...)), 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 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) rewindBtnStyle := ActionIcon(gtx, th, pb.RewindBtn, icons.AVFastRewind, pb.rewindHint)
playBtnStyle := ToggleIcon(gtx, th, pb.PlayingBtn, icons.AVPlayArrow, icons.AVStop, pb.playHint, pb.stopHint) 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) recordBtnStyle := ToggleIcon(gtx, th, pb.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, pb.recordHint, pb.stopRecordHint)

View File

@ -1,82 +1,143 @@
package gioui package gioui
import ( import (
_ "embed"
"fmt"
"image/color" "image/color"
"gioui.org/font/gofont"
"gioui.org/text" "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} type CursorStyle struct {
var black = color.NRGBA{R: 0, G: 0, B: 0, A: 255} Active color.NRGBA
var transparent = color.NRGBA{A: 0} 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} //go:embed theme.yml
var secondaryColor = color.NRGBA{R: 128, G: 222, B: 234, A: 255} var defaultTheme []byte
var highEmphasisTextColor = color.NRGBA{R: 222, G: 222, B: 222, A: 222} func NewTheme() *Theme {
var mediumEmphasisTextColor = color.NRGBA{R: 153, G: 153, B: 153, A: 153} var theme Theme
var disabledTextColor = color.NRGBA{R: 255, G: 255, B: 255, A: 97} 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} func must[T any](ic T, err error) T {
if err != nil {
var labelDefaultFont = fontCollection[6].Font panic(err)
var labelDefaultFontSize = unit.Sp(18) }
return ic
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}

170
tracker/gioui/theme.yml Normal file
View File

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

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"gioui.org/app" "gioui.org/app"
"gioui.org/font/gofont"
"gioui.org/io/event" "gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/io/pointer" "gioui.org/io/pointer"
@ -18,7 +19,6 @@ import (
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/text" "gioui.org/text"
"gioui.org/widget/material"
"gioui.org/x/explorer" "gioui.org/x/explorer"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
@ -27,7 +27,7 @@ var canQuit = true // set to false in init() if plugin tag is enabled
type ( type (
Tracker struct { Tracker struct {
Theme *material.Theme Theme *Theme
OctaveNumberInput *NumberInput OctaveNumberInput *NumberInput
InstrumentVoices *NumberInput InstrumentVoices *NumberInput
TopHorizontalSplit *Split 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 { func NewTracker(model *tracker.Model) *Tracker {
t := &Tracker{ t := &Tracker{
Theme: material.NewTheme(), Theme: NewTheme(),
OctaveNumberInput: NewNumberInput(model.Octave().Int()), OctaveNumberInput: NewNumberInput(model.Octave().Int()),
InstrumentVoices: NewNumberInput(model.InstrumentVoices().Int()), InstrumentVoices: NewNumberInput(model.InstrumentVoices().Int()),
@ -95,16 +95,14 @@ func NewTracker(model *tracker.Model) *Tracker {
filePathString: model.FilePath().String(), filePathString: model.FilePath().String(),
preferences: MakePreferences(), preferences: MakePreferences(),
} }
t.Theme.Shaper = text.NewShaper(text.WithCollection(fontCollection)) t.Theme.Material.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
t.PopupAlert = NewPopupAlert(model.Alerts(), t.Theme.Shaper) t.PopupAlert = NewPopupAlert(model.Alerts())
if t.preferences.YmlError != nil { if t.preferences.YmlError != nil {
model.Alerts().Add( model.Alerts().Add(
fmt.Sprintf("Preferences YML Error: %s", t.preferences.YmlError), fmt.Sprintf("Preferences YML Error: %s", t.preferences.YmlError),
tracker.Warning, tracker.Warning,
) )
} }
t.Theme.Palette.Fg = primaryColor
t.Theme.Palette.ContrastFg = black
t.TrackEditor.scrollTable.Focus() t.TrackEditor.scrollTable.Focus()
return t return t
} }
@ -194,7 +192,7 @@ func (t *Tracker) Layout(gtx layout.Context, w *app.Window) {
gtx.Metric.PxPerDp *= zoomFactor gtx.Metric.PxPerDp *= zoomFactor
gtx.Metric.PxPerSp *= zoomFactor gtx.Metric.PxPerSp *= zoomFactor
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
paint.Fill(gtx.Ops, backgroundColor) paint.Fill(gtx.Ops, t.Theme.Material.Bg)
event.Op(gtx.Ops, t) // area for capturing scroll events event.Op(gtx.Ops, t) // area for capturing scroll events
if t.InstrumentEditor.enlargeBtn.Bool.Value() { if t.InstrumentEditor.enlargeBtn.Bool.Value() {
@ -204,7 +202,7 @@ func (t *Tracker) Layout(gtx layout.Context, w *app.Window) {
t.layoutTop, t.layoutTop,
t.layoutBottom) t.layoutBottom)
} }
t.PopupAlert.Layout(gtx) t.PopupAlert.Layout(gtx, t.Theme)
t.showDialog(gtx) t.showDialog(gtx)
// this is the top level input handler for the whole app // this is the top level input handler for the whole app
// it handles all the global key events and clipboard events // it handles all the global key events and clipboard events

View File

@ -141,7 +141,7 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
} else { } else {
text = pe.caser.String(text) 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, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(deleteUnitBtnStyle.Layout), layout.Rigid(deleteUnitBtnStyle.Layout),
layout.Rigid(copyUnitBtnStyle.Layout), layout.Rigid(copyUnitBtnStyle.Layout),
@ -156,7 +156,7 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Dp(120) gtx.Constraints.Min.X = gtx.Dp(120)
return hintText(gtx) return hintText.Layout(gtx)
}), }),
layout.Flexed(1, func(gtx C) D { layout.Flexed(1, func(gtx C) D {
s := t.UnitComment().String() 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) { for pe.commentEditor.Submitted(gtx) || pe.commentEditor.Cancelled(gtx) {
t.InstrumentEditor.Focus() t.InstrumentEditor.Focus()
} }
commentStyle := MaterialEditor(t.Theme, pe.commentEditor, "---") commentStyle := MaterialEditor(t.Theme, &t.Theme.InstrumentEditor.UnitComment, pe.commentEditor, "---")
commentStyle.Font = labelDefaultFont
commentStyle.TextSize = labelDefaultFontSize
commentStyle.Color = mediumEmphasisTextColor
commentStyle.HintColor = mediumEmphasisTextColor
ret := commentStyle.Layout(gtx) ret := commentStyle.Layout(gtx)
s.Set(pe.commentEditor.Text()) s.Set(pe.commentEditor.Text())
return ret return ret
@ -185,7 +181,8 @@ func (pe *UnitEditor) layoutUnitTypeChooser(gtx C, t *Tracker) D {
names[i] = item names[i] = item
} }
element := func(gtx C, i int) D { 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() { if i == pe.searchList.TrackerList.Selected() {
for pe.SelectTypeBtn.Clicked(gtx) { for pe.SelectTypeBtn.Clicked(gtx) {
t.Units().SetSelectedType(names[i]) t.Units().SetSelectedType(names[i])
@ -248,17 +245,17 @@ type ParameterWidget struct {
type ParameterStyle struct { type ParameterStyle struct {
tracker *Tracker tracker *Tracker
w *ParameterWidget w *ParameterWidget
Theme *material.Theme Theme *Theme
SendTargetTheme *material.Theme SendTargetTheme *material.Theme
Focus bool Focus bool
} }
func (t *Tracker) ParamStyle(th *material.Theme, paramWidget *ParameterWidget) ParameterStyle { func (t *Tracker) ParamStyle(th *Theme, paramWidget *ParameterWidget) ParameterStyle {
sendTargetTheme := th.WithPalette(material.Palette{ sendTargetTheme := th.Material.WithPalette(material.Palette{
Bg: th.Bg, Bg: th.Material.Bg,
Fg: paramIsSendTargetColor, Fg: th.UnitEditor.SendTarget,
ContrastBg: th.ContrastBg, ContrastBg: th.Material.ContrastBg,
ContrastFg: th.ContrastFg, ContrastFg: th.Material.ContrastFg,
}) })
return ParameterStyle{ return ParameterStyle{
tracker: t, // TODO: we need this to pull the instrument names for ID style parameters, find out another way 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, return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(110)) 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 { layout.Rigid(func(gtx C) D {
switch p.w.Parameter.Type() { switch p.w.Parameter.Type() {
@ -298,10 +295,9 @@ func (p ParameterStyle) Layout(gtx C) D {
if !p.w.floatWidget.Dragging() { if !p.w.floatWidget.Dragging() {
p.w.floatWidget.Value = (float32(p.w.Parameter.Value()) - float32(ra.Min)) / float32(ra.Max-ra.Min) 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 := material.Slider(&p.Theme.Material, &p.w.floatWidget)
sliderStyle.Color = p.Theme.Fg
if isSendTarget { if isSendTarget {
sliderStyle.Color = paramIsSendTargetColor sliderStyle.Color = p.Theme.UnitEditor.SendTarget
} }
r := image.Rectangle{Max: gtx.Constraints.Min} r := image.Rectangle{Max: gtx.Constraints.Min}
defer clip.Rect(r).Push(gtx.Ops).Pop() 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)) gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(40))
ra := p.w.Parameter.Range() ra := p.w.Parameter.Range()
p.w.boolWidget.Value = p.w.Parameter.Value() > ra.Min p.w.boolWidget.Value = p.w.Parameter.Value() > ra.Min
boolStyle := material.Switch(p.Theme, &p.w.boolWidget, "Toggle boolean parameter") boolStyle := material.Switch(&p.Theme.Material, &p.w.boolWidget, "Toggle boolean parameter")
boolStyle.Color.Disabled = p.Theme.Fg boolStyle.Color.Disabled = p.Theme.Material.Fg
boolStyle.Color.Enabled = white
defer pointer.PassOp{}.Push(gtx.Ops).Pop() defer pointer.PassOp{}.Push(gtx.Ops).Pop()
dims := layout.Center.Layout(gtx, boolStyle.Layout) dims := layout.Center.Layout(gtx, boolStyle.Layout)
if p.w.boolWidget.Value { if p.w.boolWidget.Value {
@ -374,17 +369,16 @@ func (p ParameterStyle) Layout(gtx C) D {
}), }),
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
if p.w.Parameter.Type() != tracker.IDParameter { if p.w.Parameter.Type() != tracker.IDParameter {
color := white
hint := p.w.Parameter.Hint() hint := p.w.Parameter.Hint()
label := Label(p.tracker.Theme, &p.tracker.Theme.UnitEditor.Hint, hint.Label)
if !hint.Valid { if !hint.Valid {
color = paramValueInvalidColor label.Color = p.tracker.Theme.UnitEditor.InvalidParam
} }
label := Label(hint.Label, color, p.tracker.Theme.Shaper)
if info == "" { if info == "" {
return label(gtx) return label.Layout(gtx)
} }
tooltip := component.PlatformTooltip(p.SendTargetTheme, info) 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{} return D{}
}), }),

View File

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