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]
### Added
- Theme can be user configured, in theme.yml. This theme.yml should be placed in
the usual sointu config directory (i.e.
`os.UserConfigDir()/sointu/theme.yml`). See
[theme.yml](tracker/gioui/theme.yml) for the default theme, and
[theme.go](tracker/gioui/theme.go) for what can be changed.
- Ctrl + scroll wheel adjusts the global scaling of the GUI ([#153][i153])
- The loudness detection supports LUFS, A-weighting, C-weighting or
RMS-weighting, and peak detection supports true peak or sample peak detection.

View File

@ -17,7 +17,6 @@ import (
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
"github.com/vsariola/sointu/tracker"
)
@ -58,27 +57,28 @@ func NewBoolClickable(b tracker.Bool) *BoolClickable {
}
}
func Tooltip(th *material.Theme, tip string) component.Tooltip {
tooltip := component.PlatformTooltip(th, tip)
tooltip.Bg = black
func Tooltip(th *Theme, tip string) component.Tooltip {
tooltip := component.PlatformTooltip(&th.Material, tip)
tooltip.Bg = th.Tooltip.Bg
tooltip.Text.Color = th.Tooltip.Color
return tooltip
}
func ActionIcon(gtx C, th *material.Theme, w *ActionClickable, icon []byte, tip string) TipIconButtonStyle {
func ActionIcon(gtx C, th *Theme, w *ActionClickable, icon []byte, tip string) TipIconButtonStyle {
ret := TipIcon(th, &w.TipClickable, icon, tip)
for w.Clickable.Clicked(gtx) {
w.Action.Do()
}
if !w.Action.Allowed() {
ret.IconButtonStyle.Color = disabledTextColor
ret.IconButtonStyle.Color = th.Button.Disabled.Color
}
return ret
}
func TipIcon(th *material.Theme, w *TipClickable, icon []byte, tip string) TipIconButtonStyle {
func TipIcon(th *Theme, w *TipClickable, icon []byte, tip string) TipIconButtonStyle {
iconButtonStyle := IconButton(th, &w.Clickable, widgetForIcon(icon), "")
iconButtonStyle.Color = primaryColor
iconButtonStyle.Background = transparent
iconButtonStyle.Color = th.Material.Palette.ContrastBg
iconButtonStyle.Background = color.NRGBA{}
iconButtonStyle.Inset = layout.UniformInset(unit.Dp(6))
return TipIconButtonStyle{
TipArea: &w.TipArea,
@ -87,7 +87,7 @@ func TipIcon(th *material.Theme, w *TipClickable, icon []byte, tip string) TipIc
}
}
func ToggleIcon(gtx C, th *material.Theme, w *BoolClickable, offIcon, onIcon []byte, offTip, onTip string) TipIconButtonStyle {
func ToggleIcon(gtx C, th *Theme, w *BoolClickable, offIcon, onIcon []byte, offTip, onTip string) TipIconButtonStyle {
icon := offIcon
tip := offTip
if w.Bool.Value() {
@ -98,11 +98,11 @@ func ToggleIcon(gtx C, th *material.Theme, w *BoolClickable, offIcon, onIcon []b
w.Bool.Toggle()
}
ibStyle := IconButton(th, &w.Clickable, widgetForIcon(icon), "")
ibStyle.Background = transparent
ibStyle.Background = color.NRGBA{}
ibStyle.Inset = layout.UniformInset(unit.Dp(6))
ibStyle.Color = primaryColor
ibStyle.Color = th.Material.Palette.ContrastBg
if !w.Bool.Enabled() {
ibStyle.Color = disabledTextColor
ibStyle.Color = th.Button.Disabled.Color
}
return TipIconButtonStyle{
TipArea: &w.TipArea,
@ -115,51 +115,27 @@ func (t *TipIconButtonStyle) Layout(gtx C) D {
return t.TipArea.Layout(gtx, t.Tooltip, t.IconButtonStyle.Layout)
}
func ActionButton(gtx C, th *material.Theme, w *ActionClickable, text string) ButtonStyle {
func ActionButton(gtx C, th *Theme, style *ButtonStyle, w *ActionClickable, text string) Button {
for w.Clickable.Clicked(gtx) {
w.Action.Do()
}
ret := Button(th, &w.Clickable, text)
ret.Color = th.Palette.Fg
if !w.Action.Allowed() {
ret.Color = disabledTextColor
return Btn(th, &th.Button.Disabled, &w.Clickable, text)
}
ret.Background = transparent
ret.Inset = layout.UniformInset(unit.Dp(6))
return ret
return Btn(th, style, &w.Clickable, text)
}
func ToggleButton(gtx C, th *material.Theme, b *BoolClickable, text string) ButtonStyle {
func ToggleButton(gtx C, th *Theme, b *BoolClickable, text string) Button {
for b.Clickable.Clicked(gtx) {
b.Bool.Toggle()
}
ret := Button(th, &b.Clickable, text)
ret.Background = transparent
ret.Inset = layout.UniformInset(unit.Dp(6))
if b.Bool.Value() {
ret.Color = th.Palette.ContrastFg
ret.Background = th.Palette.Fg
} else {
ret.Color = th.Palette.Fg
ret.Background = transparent
if !b.Bool.Enabled() {
return Btn(th, &th.Button.Disabled, &b.Clickable, text)
}
return ret
}
func LowEmphasisButton(th *material.Theme, w *Clickable, text string) ButtonStyle {
ret := Button(th, w, text)
ret.Color = th.Palette.Fg
ret.Background = transparent
ret.Inset = layout.UniformInset(unit.Dp(6))
return ret
}
func HighEmphasisButton(th *material.Theme, w *Clickable, text string) ButtonStyle {
ret := Button(th, w, text)
ret.Color = th.Palette.ContrastFg
ret.Background = th.Palette.Fg
ret.Inset = layout.UniformInset(unit.Dp(6))
return ret
if b.Bool.Value() {
return Btn(th, &th.Button.Filled, &b.Clickable, text)
}
return Btn(th, &th.Button.Text, &b.Clickable, text)
}
// Clickable represents a clickable area.
@ -277,22 +253,21 @@ func (b *Clickable) update(t event.Tag, gtx layout.Context) (widget.Click, bool)
}
type ButtonStyle struct {
Text string
// Color is the text color.
Color color.NRGBA
Font font.Font
TextSize unit.Sp
Background color.NRGBA
CornerRadius unit.Dp
Height unit.Dp
Inset layout.Inset
Button *Clickable
shaper *text.Shaper
}
type ButtonLayoutStyle struct {
Background color.NRGBA
CornerRadius unit.Dp
Button *Clickable
type Button struct {
Text string
Button *Clickable
shaper *text.Shaper
ButtonStyle
}
type IconButtonStyle struct {
@ -307,36 +282,20 @@ type IconButtonStyle struct {
Description string
}
func Button(th *material.Theme, button *Clickable, txt string) ButtonStyle {
b := ButtonStyle{
Text: txt,
Color: th.Palette.ContrastFg,
CornerRadius: 4,
Background: th.Palette.ContrastBg,
TextSize: th.TextSize * 14.0 / 16.0,
Inset: layout.Inset{
Top: 10, Bottom: 10,
Left: 12, Right: 12,
},
Button: button,
shaper: th.Shaper,
func Btn(th *Theme, style *ButtonStyle, button *Clickable, txt string) Button {
b := Button{
Text: txt,
ButtonStyle: *style,
Button: button,
shaper: th.Material.Shaper,
}
b.Font.Typeface = th.Face
return b
}
func ButtonLayout(th *material.Theme, button *Clickable) ButtonLayoutStyle {
return ButtonLayoutStyle{
Button: button,
Background: th.Palette.ContrastBg,
CornerRadius: 4,
}
}
func IconButton(th *material.Theme, button *Clickable, icon *widget.Icon, description string) IconButtonStyle {
func IconButton(th *Theme, button *Clickable, icon *widget.Icon, description string) IconButtonStyle {
return IconButtonStyle{
Background: th.Palette.ContrastBg,
Color: th.Palette.ContrastFg,
Background: th.Material.Palette.ContrastBg,
Color: th.Material.Palette.ContrastFg,
Icon: icon,
Size: 24,
Inset: layout.UniformInset(12),
@ -345,22 +304,9 @@ func IconButton(th *material.Theme, button *Clickable, icon *widget.Icon, descri
}
}
func (b ButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
return ButtonLayoutStyle{
Background: b.Background,
CornerRadius: b.CornerRadius,
Button: b.Button,
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
colMacro := op.Record(gtx.Ops)
paint.ColorOp{Color: b.Color}.Add(gtx.Ops)
return widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.TextSize, b.Text, colMacro.Stop())
})
})
}
func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
func (b *Button) Layout(gtx layout.Context) layout.Dimensions {
min := gtx.Constraints.Min
min.Y = gtx.Dp(b.Height)
return b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
semantic.Button.Add(gtx.Ops)
return layout.Background{}.Layout(gtx,
@ -380,7 +326,13 @@ func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Di
},
func(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Min = min
return layout.Center.Layout(gtx, w)
return layout.Center.Layout(gtx, func(gtx C) D {
return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
colMacro := op.Record(gtx.Ops)
paint.ColorOp{Color: b.Color}.Add(gtx.Ops)
return widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.TextSize, b.Text, colMacro.Stop())
})
})
},
)
})

View File

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

View File

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

View File

@ -1,8 +1,12 @@
package gioui
import (
"image/color"
"gioui.org/font"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
)
@ -18,7 +22,12 @@ type (
requestFocus bool
}
EditorStyle material.EditorStyle
EditorStyle struct {
Color color.NRGBA
HintColor color.NRGBA
Font font.Font
TextSize unit.Sp
}
)
func NewEditor(e widget.Editor) *Editor {
@ -36,8 +45,21 @@ func NewEditor(e widget.Editor) *Editor {
return ret
}
func MaterialEditor(th *material.Theme, e *Editor, hint string) EditorStyle {
return EditorStyle(material.Editor(th, &e.Editor, hint))
func (s *EditorStyle) AsLabelStyle() LabelStyle {
return LabelStyle{
Color: s.Color,
Font: s.Font,
TextSize: s.TextSize,
}
}
func MaterialEditor(th *Theme, style *EditorStyle, editor *Editor, hint string) material.EditorStyle {
ret := material.Editor(&th.Material, &editor.Editor, hint)
ret.Font = style.Font
ret.TextSize = style.TextSize
ret.Color = style.Color
ret.HintColor = style.HintColor
return ret
}
func (e *Editor) SetText(s string) {
@ -80,7 +102,3 @@ func (e *Editor) Cancelled(gtx C) bool {
func (e *Editor) Focus() {
e.requestFocus = true
}
func (e *EditorStyle) Layout(gtx C) D {
return material.EditorStyle(*e).Layout(gtx)
}

View File

@ -9,7 +9,6 @@ import (
"strconv"
"strings"
"gioui.org/font"
"gioui.org/io/clipboard"
"gioui.org/io/key"
"gioui.org/layout"
@ -130,7 +129,7 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
octave := func(gtx C) D {
in := layout.UniformInset(unit.Dp(1))
numStyle := NumericUpDown(t.Theme, t.OctaveNumberInput, ie.octaveHint)
numStyle := NumUpDown(t.Theme, t.OctaveNumberInput, ie.octaveHint)
dims := in.Layout(gtx, numStyle.Layout)
return dims
}
@ -144,7 +143,7 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
return ie.layoutInstrumentList(gtx, t)
}),
layout.Rigid(layout.Spacer{Width: 10}.Layout),
layout.Rigid(LabelStyle{Text: "Octave", Color: disabledTextColor, Alignment: layout.W, FontSize: t.Theme.TextSize * 14.0 / 16.0, Shaper: t.Theme.Shaper}.Layout),
layout.Rigid(Label(t.Theme, &t.Theme.InstrumentEditor.Octave, "Octave").Layout),
layout.Rigid(layout.Spacer{Width: 4}.Layout),
layout.Rigid(octave),
layout.Rigid(func(gtx C) D {
@ -186,7 +185,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
soloBtnStyle := ToggleIcon(gtx, t.Theme, ie.soloBtn, icons.SocialGroup, icons.SocialPerson, ie.soloHint, ie.unsoloHint)
muteBtnStyle := ToggleIcon(gtx, t.Theme, ie.muteBtn, icons.AVVolumeUp, icons.AVVolumeOff, ie.muteHint, ie.unmuteHint)
m := PopupMenu(&ie.presetMenu, t.Theme.Shaper)
m := PopupMenu(t.Theme, &t.Theme.Menu.Text, &ie.presetMenu)
for ie.copyInstrumentBtn.Clickable.Clicked(gtx) {
if contents, ok := t.Instruments().List().CopyElements(); ok {
@ -214,10 +213,10 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
header := func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(layout.Spacer{Width: 6}.Layout),
layout.Rigid(LabelStyle{Text: "Voices", Color: disabledTextColor, Alignment: layout.W, FontSize: t.Theme.TextSize * 14.0 / 16.0, Shaper: t.Theme.Shaper}.Layout),
layout.Rigid(Label(t.Theme, &t.Theme.InstrumentEditor.Voices, "Voices").Layout),
layout.Rigid(layout.Spacer{Width: 4}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
numStyle := NumericUpDown(t.Theme, t.InstrumentVoices, "Number of voices for this instrument")
numStyle := NumUpDown(t.Theme, t.InstrumentVoices, "Number of voices for this instrument")
dims := numStyle.Layout(gtx)
return dims
}),
@ -254,8 +253,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
for ie.commentEditor.Submitted(gtx) || ie.commentEditor.Cancelled(gtx) {
ie.instrumentDragList.Focus()
}
style := MaterialEditor(t.Theme, ie.commentEditor, "Comment")
style.Color = highEmphasisTextColor
style := MaterialEditor(t.Theme, &t.Theme.InstrumentEditor.InstrumentComment, ie.commentEditor, "Comment")
ret := layout.UniformInset(unit.Dp(6)).Layout(gtx, style.Layout)
ie.commentString.Set(ie.commentEditor.Text())
return ret
@ -270,33 +268,29 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
}
func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(36))
gtx.Constraints.Max.Y = gtx.Dp(36)
gtx.Constraints.Min.Y = gtx.Dp(36)
element := func(gtx C, i int) D {
gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36))
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(30))
grabhandle := LabelStyle{Text: strconv.Itoa(i + 1), ShadeColor: black, Color: mediumEmphasisTextColor, FontSize: unit.Sp(10), Alignment: layout.Center, Shaper: t.Theme.Shaper}
grabhandle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, strconv.Itoa(i+1))
label := func(gtx C) D {
name, level, mute, ok := (*tracker.Instruments)(t.Model).Item(i)
if !ok {
labelStyle := LabelStyle{Text: "", ShadeColor: black, Color: white, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper}
labelStyle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, "")
return layout.Center.Layout(gtx, labelStyle.Layout)
}
k := byte(255 - level*127)
color := color.NRGBA{R: 255, G: k, B: 255, A: 255}
s := t.Theme.InstrumentEditor.InstrumentList.Name
if mute {
s = t.Theme.InstrumentEditor.InstrumentList.NameMuted
}
s.Color = color
if i == ie.instrumentDragList.TrackerList.Selected() {
ie.nameEditor.SetText(name)
for ie.nameEditor.Submitted(gtx) || ie.nameEditor.Cancelled(gtx) {
ie.instrumentDragList.Focus()
}
style := MaterialEditor(t.Theme, ie.nameEditor, "Instr")
style.Color = color
style.HintColor = instrumentNameHintColor
style.TextSize = unit.Sp(12)
style.Font = labelDefaultFont
if mute {
style.Color = disabledTextColor
style.Font.Style = font.Italic
}
style := MaterialEditor(t.Theme, &s, ie.nameEditor, "Instr")
dims := layout.Center.Layout(gtx, func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
return style.Layout(gtx)
@ -307,29 +301,21 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
if name == "" {
name = "Instr"
}
labelStyle := LabelStyle{Text: name, ShadeColor: black, Color: color, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper}
if mute {
labelStyle.Color = disabledTextColor
labelStyle.Font.Style = font.Italic
}
return layout.Center.Layout(gtx, labelStyle.Layout)
l := s.AsLabelStyle()
return layout.Center.Layout(gtx, Label(t.Theme, &l, name).Layout)
}
return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6), Top: unit.Dp(4)}.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(grabhandle.Layout),
layout.Rigid(label),
)
return layout.Center.Layout(gtx, func(gtx C) D {
return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6)}.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(grabhandle.Layout),
layout.Rigid(label),
)
})
})
}
color := inactiveLightSurfaceColor
if ie.wasFocused {
color = activeLightSurfaceColor
}
instrumentList := FilledDragList(t.Theme, ie.instrumentDragList, element, nil)
instrumentList.SelectedColor = color
instrumentList.HoverColor = instrumentHoverColor
instrumentList.ScrollBarWidth = unit.Dp(6)
instrumentList.ScrollBar = t.Theme.InstrumentEditor.InstrumentList.ScrollBar
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
@ -367,8 +353,8 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
// TODO: how to ie.unitDragList.Focus()
addUnitBtnStyle := ActionIcon(gtx, t.Theme, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)")
addUnitBtnStyle.IconButtonStyle.Color = t.Theme.ContrastFg
addUnitBtnStyle.IconButtonStyle.Background = t.Theme.Fg
addUnitBtnStyle.IconButtonStyle.Color = t.Theme.Material.ContrastFg
addUnitBtnStyle.IconButtonStyle.Background = t.Theme.Material.ContrastBg
addUnitBtnStyle.IconButtonStyle.Inset = layout.UniformInset(unit.Dp(4))
var units [256]tracker.UnitListItem
@ -393,23 +379,23 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
return layout.Dimensions{Size: gtx.Constraints.Min}
}
u := units[i]
var color color.NRGBA = white
f := labelDefaultFont
editorStyle := t.Theme.InstrumentEditor.UnitList.Name
if u.Disabled {
editorStyle = t.Theme.InstrumentEditor.UnitList.NameDisabled
}
stackText := strconv.FormatInt(int64(u.StackAfter), 10)
if u.StackNeed > u.StackBefore {
color = errorColor
editorStyle.Color = t.Theme.InstrumentEditor.UnitList.Error
(*tracker.Alerts)(t.Model).AddNamed("UnitNeedsInputs", fmt.Sprintf("%v needs at least %v input signals, got %v", u.Type, u.StackNeed, u.StackBefore), tracker.Error)
} else if i == count-1 && u.StackAfter != 0 {
color = warningColor
editorStyle.Color = t.Theme.InstrumentEditor.UnitList.Warning
(*tracker.Alerts)(t.Model).AddNamed("InstrumentLeavesSignals", fmt.Sprintf("Instrument leaves %v signal(s) on the stack", u.StackAfter), tracker.Warning)
}
if u.Disabled {
color = disabledTextColor
f.Style = font.Italic
}
stackLabel := LabelStyle{Text: stackText, ShadeColor: black, Color: mediumEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper}
stackLabel := Label(t.Theme, &t.Theme.InstrumentEditor.UnitList.Stack, stackText)
rightMargin := layout.Inset{Right: unit.Dp(10)}
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(func(gtx C) D {
@ -435,24 +421,21 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
ie.searchEditor.SetText(str.Value())
ie.unitDragList.Focus()
}
style := MaterialEditor(t.Theme, ie.searchEditor, "---")
style.Color = color
style.HintColor = instrumentNameHintColor
style.TextSize = unit.Sp(12)
style.Font = f
style := MaterialEditor(t.Theme, &editorStyle, ie.searchEditor, "---")
ret := style.Layout(gtx)
str.Set(ie.searchEditor.Text())
return ret
} else {
unitNameLabel := LabelStyle{Text: u.Type, ShadeColor: black, Color: color, Font: f, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper}
if unitNameLabel.Text == "" {
unitNameLabel.Text = "---"
text := u.Type
if text == "" {
text = "---"
}
return unitNameLabel.Layout(gtx)
l := editorStyle.AsLabelStyle()
return Label(t.Theme, &l, text).Layout(gtx)
}
}),
layout.Flexed(1, func(gtx C) D {
unitNameLabel := LabelStyle{Text: u.Comment, ShadeColor: black, Color: mediumEmphasisTextColor, Font: f, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper}
unitNameLabel := Label(t.Theme, &t.Theme.InstrumentEditor.UnitList.Comment, u.Comment)
inset := layout.Inset{Left: unit.Dp(5)}
return inset.Layout(gtx, unitNameLabel.Layout)
}),

View File

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

View File

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

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

View File

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

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.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(h)})).Push(gtx.Ops).Pop()
gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6))
LabelStyle{
Alignment: layout.NW,
Text: t.Model.TrackTitle(i),
FontSize: unit.Sp(12),
Color: mediumEmphasisTextColor,
Shaper: t.Theme.Shaper,
}.Layout(gtx)
Label(t.Theme, &t.Theme.OrderEditor.TrackTitle, t.Model.TrackTitle(i)).Layout(gtx)
return D{Size: image.Pt(gtx.Dp(patternCellWidth), h)}
}
rowTitleBg := func(gtx C, j int) D {
if t.Model.Playing().Value() && j == t.PlayPosition().OrderRow {
paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Dp(patternCellHeight))}.Op())
paint.FillShape(gtx.Ops, t.Theme.OrderEditor.Play, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Dp(patternCellHeight))}.Op())
}
return D{}
}
rowMarkerPatternTextColorOp := colorOp(gtx, t.Theme.OrderEditor.RowTitle.Color)
loopMarkerColorOp := colorOp(gtx, t.Theme.OrderEditor.Loop)
rowTitle := func(gtx C, j int) D {
w := gtx.Dp(unit.Dp(30))
color := rowMarkerPatternTextColor
callOp := rowMarkerPatternTextColorOp
if l := t.Loop(); j >= l.Start && j < l.Start+l.Length {
color = loopMarkerColor
callOp = loopMarkerColorOp
}
paint.ColorOp{Color: color}.Add(gtx.Ops)
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j)), op.CallOp{})
widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.OrderEditor.RowTitle.Font, t.Theme.OrderEditor.RowTitle.TextSize, strings.ToUpper(fmt.Sprintf("%02x", j)), callOp)
return D{Size: image.Pt(w, gtx.Dp(patternCellHeight))}
}
selection := oe.scrollTable.Table.Range()
cellColorOp := colorOp(gtx, t.Theme.OrderEditor.Cell.Color)
cell := func(gtx C, x, y int) D {
val := patternIndexToString(t.Model.Order().Value(tracker.Point{X: x, Y: y}))
color := patternCellColor
color := t.Theme.OrderEditor.CellBg
point := tracker.Point{X: x, Y: y}
if selection.Contains(point) {
color = inactiveSelectionColor
color = t.Theme.Selection.Inactive
if oe.scrollTable.Focused() {
color = selectionColor
if point == oe.scrollTable.Table.Cursor() {
color = cursorColor
color = t.Theme.Selection.Active
}
if point == oe.scrollTable.Table.Cursor() {
color = t.Theme.Cursor.Inactive
if oe.scrollTable.Focused() {
color = t.Theme.Cursor.Active
}
}
}
paint.FillShape(gtx.Ops, color, clip.Rect{Min: image.Pt(1, 1), Max: image.Pt(gtx.Constraints.Min.X-1, gtx.Constraints.Min.X-1)}.Op())
paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops)
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, val, op.CallOp{})
widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.OrderEditor.Cell.Font, t.Theme.OrderEditor.Cell.TextSize, val, cellColorOp)
return D{Size: image.Pt(gtx.Dp(patternCellWidth), gtx.Dp(patternCellHeight))}
}

View File

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

View File

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

View File

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

View File

@ -14,7 +14,6 @@ import (
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/unit"
"gioui.org/widget/material"
"github.com/vsariola/sointu/tracker"
)
@ -72,7 +71,7 @@ func NewScrollTable(table tracker.Table, vertList, horizList tracker.List) *Scro
return ret
}
func FilledScrollTable(th *material.Theme, scrollTable *ScrollTable, element func(gtx C, x, y int) D, colTitle, rowTitle, colTitleBg, rowTitleBg func(gtx C, i int) D) ScrollTableStyle {
func FilledScrollTable(th *Theme, scrollTable *ScrollTable, element func(gtx C, x, y int) D, colTitle, rowTitle, colTitleBg, rowTitleBg func(gtx C, i int) D) ScrollTableStyle {
return ScrollTableStyle{
RowTitleStyle: FilledDragList(th, scrollTable.RowTitleList, rowTitle, rowTitleBg),
ColTitleStyle: FilledDragList(th, scrollTable.ColTitleList, colTitle, colTitleBg),

View File

@ -2,6 +2,7 @@ package gioui
import (
"image"
"image/color"
"gioui.org/f32"
"gioui.org/io/event"
@ -21,19 +22,27 @@ type ScrollBar struct {
tag bool
}
func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Position) D {
type ScrollBarStyle struct {
Color color.NRGBA
Width unit.Dp
Gradient color.NRGBA
}
func (s *ScrollBar) Layout(gtx C, style *ScrollBarStyle, numItems int, pos *layout.Position) D {
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
gradientSize := gtx.Dp(unit.Dp(4))
var totalPixelsEstimate, scrollBarRelLength float32
transparent := style.Gradient
transparent.A = 0
switch s.Axis {
case layout.Vertical:
if pos.First > 0 || pos.Offset > 0 {
paint.LinearGradientOp{Color1: black, Color2: transparent, Stop2: f32.Pt(0, float32(gradientSize))}.Add(gtx.Ops)
paint.LinearGradientOp{Color1: style.Gradient, Color2: transparent, Stop2: f32.Pt(0, float32(gradientSize))}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
}
if pos.BeforeEnd {
paint.LinearGradientOp{Color1: black, Color2: transparent, Stop1: f32.Pt(0, float32(gtx.Constraints.Min.Y)), Stop2: f32.Pt(0, float32(gtx.Constraints.Min.Y-gradientSize))}.Add(gtx.Ops)
paint.LinearGradientOp{Color1: style.Gradient, Color2: transparent, Stop1: f32.Pt(0, float32(gtx.Constraints.Min.Y)), Stop2: f32.Pt(0, float32(gtx.Constraints.Min.Y-gradientSize))}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
}
totalPixelsEstimate = float32(gtx.Constraints.Min.Y+pos.Offset-pos.OffsetLast) * float32(numItems) / float32(pos.Count)
@ -41,11 +50,11 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit
case layout.Horizontal:
if pos.First > 0 || pos.Offset > 0 {
paint.LinearGradientOp{Color1: black, Color2: transparent, Stop2: f32.Pt(float32(gradientSize), 0)}.Add(gtx.Ops)
paint.LinearGradientOp{Color1: style.Gradient, Color2: transparent, Stop2: f32.Pt(float32(gradientSize), 0)}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
}
if pos.BeforeEnd {
paint.LinearGradientOp{Color1: black, Color2: transparent, Stop1: f32.Pt(float32(gtx.Constraints.Min.X), 0), Stop2: f32.Pt(float32(gtx.Constraints.Min.X-gradientSize), 0)}.Add(gtx.Ops)
paint.LinearGradientOp{Color1: style.Gradient, Color2: transparent, Stop1: f32.Pt(float32(gtx.Constraints.Min.X), 0), Stop2: f32.Pt(float32(gtx.Constraints.Min.X-gradientSize), 0)}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
}
totalPixelsEstimate = float32(gtx.Constraints.Min.X+pos.Offset-pos.OffsetLast) * float32(numItems) / float32(pos.Count)
@ -56,7 +65,7 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit
}
scrollBarRelStart := (float32(pos.First)*totalPixelsEstimate/float32(numItems) + float32(pos.Offset)) / totalPixelsEstimate
scrWidth := gtx.Dp(width)
scrWidth := gtx.Dp(style.Width)
stack := op.Offset(image.Point{}).Push(gtx.Ops)
var area clip.Stack
@ -65,7 +74,7 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit
if scrollBarRelLength < 1 && (s.dragging || s.hovering) {
y1 := int(scrollBarRelStart * float32(gtx.Constraints.Min.Y))
y2 := int((scrollBarRelStart + scrollBarRelLength) * float32(gtx.Constraints.Min.Y))
paint.FillShape(gtx.Ops, scrollBarColor, clip.Rect{Min: image.Pt(gtx.Constraints.Min.X-scrWidth, y1), Max: image.Pt(gtx.Constraints.Min.X, y2)}.Op())
paint.FillShape(gtx.Ops, style.Color, clip.Rect{Min: image.Pt(gtx.Constraints.Min.X-scrWidth, y1), Max: image.Pt(gtx.Constraints.Min.X, y2)}.Op())
}
rect := image.Rect(gtx.Constraints.Min.X-scrWidth, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area = clip.Rect(rect).Push(gtx.Ops)
@ -73,7 +82,7 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit
if scrollBarRelLength < 1 && (s.dragging || s.hovering) {
x1 := int(scrollBarRelStart * float32(gtx.Constraints.Min.X))
x2 := int((scrollBarRelStart + scrollBarRelLength) * float32(gtx.Constraints.Min.X))
paint.FillShape(gtx.Ops, scrollBarColor, clip.Rect{Min: image.Pt(x1, gtx.Constraints.Min.Y-scrWidth), Max: image.Pt(x2, gtx.Constraints.Min.Y)}.Op())
paint.FillShape(gtx.Ops, style.Color, clip.Rect{Min: image.Pt(x1, gtx.Constraints.Min.Y-scrWidth), Max: image.Pt(x2, gtx.Constraints.Min.Y)}.Op())
}
rect := image.Rect(0, gtx.Constraints.Min.Y-scrWidth, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area = clip.Rect(rect).Push(gtx.Ops)

View File

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

View File

@ -1,82 +1,143 @@
package gioui
import (
_ "embed"
"fmt"
"image/color"
"gioui.org/font/gofont"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"golang.org/x/exp/shiny/materialdesign/icons"
"gopkg.in/yaml.v2"
)
var fontCollection []text.FontFace = gofont.Collection()
type Theme struct {
Define any // this is just needed for yaml.UnmarshalStrict, so we can have "defines" in the yaml
Material material.Theme
Button struct {
Filled ButtonStyle
Text ButtonStyle
Disabled ButtonStyle
Menu ButtonStyle
}
Oscilloscope OscilloscopeStyle
NumericUpDown NumericUpDownStyle
DialogTitle LabelStyle
DialogText LabelStyle
SongPanel struct {
RowHeader LabelStyle
RowValue LabelStyle
Expander LabelStyle
Version LabelStyle
ErrorColor color.NRGBA
Bg color.NRGBA
}
Alert struct {
Warning PopupAlertStyle
Error PopupAlertStyle
Info PopupAlertStyle
}
NoteEditor struct {
TrackTitle LabelStyle
OrderRow LabelStyle
PatternRow LabelStyle
Note LabelStyle
PatternNo LabelStyle
Unique LabelStyle
Loop color.NRGBA
Header LabelStyle
Play color.NRGBA
OneBeat color.NRGBA
TwoBeat color.NRGBA
}
Dialog struct {
Bg color.NRGBA
Title LabelStyle
Text LabelStyle
}
OrderEditor struct {
TrackTitle LabelStyle
RowTitle LabelStyle
Cell LabelStyle
Loop color.NRGBA
CellBg color.NRGBA
Play color.NRGBA
}
Menu struct {
Text LabelStyle
ShortCut color.NRGBA
Hover color.NRGBA
Disabled color.NRGBA
}
InstrumentEditor struct {
Octave LabelStyle
Voices LabelStyle
InstrumentComment EditorStyle
UnitComment EditorStyle
InstrumentList struct {
Number LabelStyle
Name EditorStyle
NameMuted EditorStyle
ScrollBar ScrollBarStyle
}
UnitList struct {
Name EditorStyle
NameDisabled EditorStyle
Comment LabelStyle
Stack LabelStyle
Disabled LabelStyle
Warning color.NRGBA
Error color.NRGBA
}
}
UnitEditor struct {
Hint LabelStyle
Chooser LabelStyle
ParameterName LabelStyle
InvalidParam color.NRGBA
SendTarget color.NRGBA
}
Cursor CursorStyle
Selection CursorStyle
Tooltip struct {
Color color.NRGBA
Bg color.NRGBA
}
Popup struct {
Bg color.NRGBA
Shadow color.NRGBA
}
ScrollBar ScrollBarStyle
}
var white = color.NRGBA{R: 255, G: 255, B: 255, A: 255}
var black = color.NRGBA{R: 0, G: 0, B: 0, A: 255}
var transparent = color.NRGBA{A: 0}
type CursorStyle struct {
Active color.NRGBA
ActiveAlt color.NRGBA // alternative color for the cursor, used e.g. when the midi input is active
Inactive color.NRGBA
}
var primaryColor = color.NRGBA{R: 206, G: 147, B: 216, A: 255}
var secondaryColor = color.NRGBA{R: 128, G: 222, B: 234, A: 255}
//go:embed theme.yml
var defaultTheme []byte
var highEmphasisTextColor = color.NRGBA{R: 222, G: 222, B: 222, A: 222}
var mediumEmphasisTextColor = color.NRGBA{R: 153, G: 153, B: 153, A: 153}
var disabledTextColor = color.NRGBA{R: 255, G: 255, B: 255, A: 97}
func NewTheme() *Theme {
var theme Theme
err := yaml.UnmarshalStrict(defaultTheme, &theme)
if err != nil {
panic(fmt.Errorf("failed to default theme: %w", err))
}
ReadCustomConfigYml("theme.yml", &theme)
theme.Material.Shaper = &text.Shaper{}
theme.Material.Icon.CheckBoxChecked = must(widget.NewIcon(icons.ToggleCheckBox))
theme.Material.Icon.CheckBoxUnchecked = must(widget.NewIcon(icons.ToggleCheckBoxOutlineBlank))
theme.Material.Icon.RadioChecked = must(widget.NewIcon(icons.ToggleRadioButtonChecked))
theme.Material.Icon.RadioUnchecked = must(widget.NewIcon(icons.ToggleRadioButtonUnchecked))
return &theme
}
var backgroundColor = color.NRGBA{R: 18, G: 18, B: 18, A: 255}
var labelDefaultFont = fontCollection[6].Font
var labelDefaultFontSize = unit.Sp(18)
var rowMarkerPatternTextColor = secondaryColor
var rowMarkerRowTextColor = mediumEmphasisTextColor
var trackerFont = fontCollection[6].Font
var trackerFontSize = unit.Sp(16)
var trackerInactiveTextColor = highEmphasisTextColor
var trackerActiveTextColor = color.NRGBA{R: 255, G: 255, B: 130, A: 255}
var trackerPlayColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255}
var trackerPatMarker = primaryColor
var oneBeatHighlight = color.NRGBA{R: 31, G: 37, B: 38, A: 255}
var twoBeatHighlight = color.NRGBA{R: 31, G: 51, B: 53, A: 255}
var patternPlayColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255}
var patternTextColor = primaryColor
var patternCellColor = color.NRGBA{R: 255, G: 255, B: 255, A: 3}
var loopMarkerColor = color.NRGBA{R: 252, G: 186, B: 3, A: 255}
var instrumentHoverColor = color.NRGBA{R: 30, G: 31, B: 38, A: 255}
var instrumentNameHintColor = color.NRGBA{R: 200, G: 200, B: 200, A: 255}
var songSurfaceColor = color.NRGBA{R: 24, G: 24, B: 24, A: 255}
var popupSurfaceColor = color.NRGBA{R: 50, G: 50, B: 51, A: 255}
var popupShadowColor = color.NRGBA{R: 0, G: 0, B: 0, A: 192}
var dragListSelectedColor = color.NRGBA{R: 55, G: 55, B: 61, A: 255}
var dragListHoverColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255}
var inactiveLightSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255}
var activeLightSurfaceColor = color.NRGBA{R: 45, G: 45, B: 45, A: 255}
var numberInputBgColor = color.NRGBA{R: 255, G: 255, B: 255, A: 3}
var cursorColor = color.NRGBA{R: 100, G: 140, B: 255, A: 48}
var selectionColor = color.NRGBA{R: 100, G: 140, B: 255, A: 12}
var inactiveSelectionColor = color.NRGBA{R: 140, G: 140, B: 140, A: 16}
var cursorForTrackMidiInColor = color.NRGBA{R: 255, G: 100, B: 140, A: 48}
var cursorNeighborForTrackMidiInColor = color.NRGBA{R: 255, G: 100, B: 140, A: 24}
var errorColor = color.NRGBA{R: 207, G: 102, B: 121, A: 255}
var menuHoverColor = color.NRGBA{R: 30, G: 31, B: 38, A: 255}
var scrollBarColor = color.NRGBA{R: 255, G: 255, B: 255, A: 32}
var warningColor = color.NRGBA{R: 251, G: 192, B: 45, A: 255}
var dialogBgColor = color.NRGBA{R: 0, G: 0, B: 0, A: 224}
var paramIsSendTargetColor = color.NRGBA{R: 120, G: 120, B: 210, A: 255}
var paramValueInvalidColor = color.NRGBA{R: 120, G: 120, B: 120, A: 190}
var oscilloscopeLimitColor = color.NRGBA{R: 255, G: 255, B: 255, A: 8}
var oscilloscopeCursorColor = color.NRGBA{R: 252, G: 186, B: 3, A: 255}
func must[T any](ic T, err error) T {
if err != nil {
panic(err)
}
return ic
}

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

View File

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

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