From 64ec5f17f3010ad5c9de89dcccfbb71227626aa3 Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Sun, 22 Jun 2025 21:48:32 +0300 Subject: [PATCH] drafting new buttons --- tracker/gioui/buttons.go | 365 ++++++++++++++++------------- tracker/gioui/instrument_editor.go | 40 +++- tracker/gioui/menu.go | 7 +- tracker/gioui/note_editor.go | 22 +- tracker/gioui/oscilloscope.go | 7 +- tracker/gioui/songpanel.go | 43 ++-- tracker/gioui/theme.go | 8 +- tracker/gioui/theme.yml | 25 +- tracker/gioui/unit_editor.go | 17 +- 9 files changed, 315 insertions(+), 219 deletions(-) diff --git a/tracker/gioui/buttons.go b/tracker/gioui/buttons.go index b98d755..c364b1d 100644 --- a/tracker/gioui/buttons.go +++ b/tracker/gioui/buttons.go @@ -30,8 +30,8 @@ type ( } ClickableTip struct { - Clickable Clickable - TipArea component.TipArea + Clickable + component.TipArea } ButtonStyle struct { @@ -54,186 +54,237 @@ type ( Inset layout.Inset } - TipIconButton struct { - th *Theme - st *IconButtonStyle - t *ClickableTip - icon []byte - tip string - enabled bool + Button struct { + Theme *Theme + Style ButtonStyle + Text string + Tip string + *ClickableTip + } + + ActionButton struct { + act tracker.Action + DisabledStyle ButtonStyle + Button + } + + ToggleButton struct { + b tracker.Bool + DisabledStyle ButtonStyle + OffStyle ButtonStyle + Button + } + + IconButton struct { + Theme *Theme + Style IconButtonStyle + Icon *widget.Icon + Tip string + *ClickableTip + } + + ActionIconButton struct { + act tracker.Action + DisabledStyle IconButtonStyle + IconButton + } + + ToggleIconButton struct { + b tracker.Bool + DisabledStyle IconButtonStyle + OffIcon *widget.Icon + OffTip string + IconButton } ) -func TipIconBtn(th *Theme, st *IconButtonStyle, t *ClickableTip, icon []byte, tip string, enabled bool) TipIconButton { - return TipIconButton{ - th: th, - st: st, - t: t, - icon: icon, - tip: tip, - enabled: enabled, +func Btn(th *Theme, st *ButtonStyle, b *ClickableTip, txt string, tip string) Button { + return Button{ + Theme: th, + Style: *st, + ClickableTip: b, + Text: txt, + Tip: tip, } } -func (t TipIconButton) Layout(gtx C) D { - iconBtn := IconBtn(t.th, t.st, &t.t.Clickable, t.th.Icon(t.icon), t.enabled) - if t.tip != "" { - return t.t.TipArea.Layout(gtx, Tooltip(t.th, t.tip), iconBtn) - } else { - return iconBtn(gtx) +func ActionBtn(act tracker.Action, th *Theme, b *ClickableTip, txt string, tip string) ActionButton { + return ActionButton{ + act: act, + DisabledStyle: th.Button.Disabled, + Button: Btn(th, &th.Button.Text, b, txt, tip), } } -func ActionIconBtn(act tracker.Action, th *Theme, t *ClickableTip, icon []byte, tip string) layout.Widget { - return func(gtx C) D { - for t.Clickable.Clicked(gtx) { - act.Do() - } - iconBtn := IconBtn(th, &th.IconButton, &t.Clickable, th.Icon(icon), act.Enabled()) - if tip != "" { - return t.TipArea.Layout(gtx, Tooltip(th, tip), iconBtn) - } else { - return iconBtn(gtx) - } +func ToggleBtn(b tracker.Bool, th *Theme, c *ClickableTip, text string, tip string) ToggleButton { + return ToggleButton{ + b: b, + DisabledStyle: th.Button.Disabled, + OffStyle: th.Button.Text, + Button: Btn(th, &th.Button.Filled, c, text, tip), } } -func ToggleIconBtn(b tracker.Bool, th *Theme, t *ClickableTip, offIcon, onIcon []byte, offTip, onTip string) layout.Widget { - return func(gtx C) D { - icon := offIcon - tip := offTip - if b.Value() { - icon = onIcon - tip = onTip - } - for t.Clickable.Clicked(gtx) { - b.Toggle() - } - iconBtn := IconBtn(th, &th.IconButton, &t.Clickable, th.Icon(icon), b.Enabled()) - if tip != "" { - return t.TipArea.Layout(gtx, Tooltip(th, tip), iconBtn) - } else { - return iconBtn(gtx) - } +func IconBtn(th *Theme, st *IconButtonStyle, b *ClickableTip, icon []byte, tip string) IconButton { + return IconButton{ + Theme: th, + Style: *st, + ClickableTip: b, + Icon: th.Icon(icon), + Tip: tip, } } -func ActionBtn(a tracker.Action, th *Theme, c *ClickableTip, text string, tip string) layout.Widget { - return func(gtx C) D { - for c.Clickable.Clicked(gtx) { - a.Do() - } - var btn layout.Widget - if !a.Enabled() { - btn = Btn(th, &th.Button.Disabled, &c.Clickable, text) - } else { - btn = Btn(th, &th.Button.Text, &c.Clickable, text) - } - if tip != "" { - return c.TipArea.Layout(gtx, Tooltip(th, tip), btn) - } else { - return btn(gtx) - } +func ActionIconBtn(act tracker.Action, th *Theme, b *ClickableTip, icon []byte, tip string) ActionIconButton { + return ActionIconButton{ + act: act, + DisabledStyle: th.IconButton.Disabled, + IconButton: IconBtn(th, &th.IconButton.Enabled, b, icon, tip), } } -func ToggleBtn(b tracker.Bool, th *Theme, c *ClickableTip, text string, tip string) layout.Widget { - return func(gtx C) D { - for c.Clickable.Clicked(gtx) { - b.Toggle() - } - var btn layout.Widget - if !b.Enabled() { - btn = Btn(th, &th.Button.Disabled, &c.Clickable, text) - } else if b.Value() { - btn = Btn(th, &th.Button.Filled, &c.Clickable, text) - } else { - btn = Btn(th, &th.Button.Text, &c.Clickable, text) - } - if tip != "" { - return c.TipArea.Layout(gtx, Tooltip(th, tip), btn) - } else { - return btn(gtx) - } +func ToggleIconBtn(b tracker.Bool, th *Theme, c *ClickableTip, offIcon, onIcon []byte, offTip, onTip string) ToggleIconButton { + return ToggleIconButton{ + b: b, + DisabledStyle: th.IconButton.Disabled, + OffIcon: th.Icon(offIcon), + OffTip: offTip, + IconButton: IconBtn(th, &th.IconButton.Enabled, c, onIcon, onTip), } } -func Btn(th *Theme, st *ButtonStyle, b *Clickable, txt string) layout.Widget { - return func(gtx C) D { - min := gtx.Constraints.Min - min.Y = gtx.Dp(st.Height) - return b.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - semantic.Button.Add(gtx.Ops) - return layout.Background{}.Layout(gtx, - func(gtx layout.Context) layout.Dimensions { - rr := gtx.Dp(st.CornerRadius) - defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop() - background := st.Background - switch { - case b.Hovered(): - background = hoveredColor(background) - } - paint.Fill(gtx.Ops, background) - for _, c := range b.History() { - drawInk(gtx, (widget.Press)(c)) - } - return layout.Dimensions{Size: gtx.Constraints.Min} - }, - func(gtx layout.Context) layout.Dimensions { - gtx.Constraints.Min = min - return layout.Center.Layout(gtx, func(gtx C) D { - return st.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - colMacro := op.Record(gtx.Ops) - paint.ColorOp{Color: st.Color}.Add(gtx.Ops) - return widget.Label{Alignment: text.Middle}.Layout(gtx, th.Material.Shaper, st.Font, st.TextSize, txt, colMacro.Stop()) - }) +func (b *Button) Layout(gtx C) D { + if b.Tip != "" { + return b.ClickableTip.TipArea.Layout(gtx, Tooltip(b.Theme, b.Tip), b.actualLayout) + } + return b.actualLayout(gtx) +} + +func (b *Button) actualLayout(gtx C) D { + min := gtx.Constraints.Min + min.Y = gtx.Dp(b.Style.Height) + return b.Clickable.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + semantic.Button.Add(gtx.Ops) + return layout.Background{}.Layout(gtx, + func(gtx layout.Context) layout.Dimensions { + rr := gtx.Dp(b.Style.CornerRadius) + defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop() + background := b.Style.Background + switch { + case b.Clickable.Hovered(): + background = hoveredColor(background) + } + paint.Fill(gtx.Ops, background) + for _, c := range b.Clickable.History() { + drawInk(gtx, (widget.Press)(c)) + } + return layout.Dimensions{Size: gtx.Constraints.Min} + }, + func(gtx layout.Context) layout.Dimensions { + gtx.Constraints.Min = min + return layout.Center.Layout(gtx, func(gtx C) D { + return b.Style.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + colMacro := op.Record(gtx.Ops) + paint.ColorOp{Color: b.Style.Color}.Add(gtx.Ops) + return widget.Label{Alignment: text.Middle}.Layout(gtx, b.Theme.Material.Shaper, b.Style.Font, b.Style.TextSize, b.Text, colMacro.Stop()) }) - }, - ) - }) - } + }) + }, + ) + }) } -func IconBtn(th *Theme, st *IconButtonStyle, b *Clickable, icon *widget.Icon, enabled bool) layout.Widget { - return func(gtx C) D { - m := op.Record(gtx.Ops) - dims := b.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - semantic.Button.Add(gtx.Ops) - return layout.Background{}.Layout(gtx, - func(gtx layout.Context) layout.Dimensions { - rr := (gtx.Constraints.Min.X + gtx.Constraints.Min.Y) / 4 - defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop() - background := st.Background - switch { - case b.Hovered(): - background = hoveredColor(background) - } - paint.Fill(gtx.Ops, background) - for _, c := range b.History() { - drawInk(gtx, (widget.Press)(c)) - } - return layout.Dimensions{Size: gtx.Constraints.Min} - }, - func(gtx layout.Context) layout.Dimensions { - return st.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - size := gtx.Dp(st.Size) - if icon != nil { - gtx.Constraints.Min = image.Point{X: size} - icon.Layout(gtx, st.Color) - } - return layout.Dimensions{ - Size: image.Point{X: size, Y: size}, - } - }) - }, - ) - }) - c := m.Stop() - bounds := image.Rectangle{Max: dims.Size} - defer clip.Ellipse(bounds).Push(gtx.Ops).Pop() - c.Add(gtx.Ops) - return dims +func (b *ActionButton) Layout(gtx C) D { + for b.Clickable.Clicked(gtx) { + b.act.Do() } + if !b.act.Enabled() { + b.Style = b.DisabledStyle + } + return b.Button.Layout(gtx) +} + +func (b *ToggleButton) Layout(gtx C) D { + for b.Clickable.Clicked(gtx) { + b.b.Toggle() + } + if !b.b.Enabled() { + b.Style = b.DisabledStyle + } else if !b.b.Value() { + b.Style = b.OffStyle + } + return b.Button.Layout(gtx) +} + +func (i *IconButton) Layout(gtx C) D { + if i.Tip != "" { + return i.ClickableTip.TipArea.Layout(gtx, Tooltip(i.Theme, i.Tip), i.actualLayout) + } + return i.actualLayout(gtx) +} + +func (i *IconButton) actualLayout(gtx C) D { + m := op.Record(gtx.Ops) + dims := i.Clickable.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + semantic.Button.Add(gtx.Ops) + return layout.Background{}.Layout(gtx, + func(gtx layout.Context) layout.Dimensions { + rr := (gtx.Constraints.Min.X + gtx.Constraints.Min.Y) / 4 + defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop() + background := i.Style.Background + switch { + case i.Clickable.Hovered(): + background = hoveredColor(background) + } + paint.Fill(gtx.Ops, background) + for _, c := range i.Clickable.History() { + drawInk(gtx, (widget.Press)(c)) + } + return layout.Dimensions{Size: gtx.Constraints.Min} + }, + func(gtx layout.Context) layout.Dimensions { + return i.Style.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + size := gtx.Dp(i.Style.Size) + if i.Icon != nil { + gtx.Constraints.Min = image.Point{X: size} + i.Icon.Layout(gtx, i.Style.Color) + } + return layout.Dimensions{ + Size: image.Point{X: size, Y: size}, + } + }) + }, + ) + }) + c := m.Stop() + bounds := image.Rectangle{Max: dims.Size} + defer clip.Ellipse(bounds).Push(gtx.Ops).Pop() + c.Add(gtx.Ops) + return dims +} + +func (i *ActionIconButton) Layout(gtx C) D { + for i.Clickable.Clicked(gtx) { + i.act.Do() + } + if !i.act.Enabled() { + i.Style = i.DisabledStyle + } + return i.IconButton.Layout(gtx) +} + +func (i *ToggleIconButton) Layout(gtx C) D { + for i.Clickable.Clicked(gtx) { + i.b.Toggle() + } + if !i.b.Enabled() { + i.Style = i.DisabledStyle + } + if !i.b.Value() { + i.Icon = i.OffIcon + i.Tip = i.OffTip + } + return i.IconButton.Layout(gtx) } func Tooltip(th *Theme, tip string) component.Tooltip { diff --git a/tracker/gioui/instrument_editor.go b/tracker/gioui/instrument_editor.go index 98d354a..afc7943 100644 --- a/tracker/gioui/instrument_editor.go +++ b/tracker/gioui/instrument_editor.go @@ -159,13 +159,16 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D { layout.Rigid(layout.Spacer{Width: 4}.Layout), layout.Rigid(octave), layout.Rigid(func(gtx C) D { - return layout.E.Layout(gtx, ToggleIconBtn(t.Model.LinkInstrTrack(), t.Theme, ie.linkInstrTrackBtn, icons.NotificationSyncDisabled, icons.NotificationSync, ie.linkDisabledHint, ie.linkEnabledHint)) + linkInstrTrack := ToggleIconBtn(t.Model.LinkInstrTrack(), t.Theme, ie.linkInstrTrackBtn, icons.NotificationSyncDisabled, icons.NotificationSync, ie.linkDisabledHint, ie.linkEnabledHint) + return layout.E.Layout(gtx, linkInstrTrack.Layout) }), layout.Rigid(func(gtx C) D { - return layout.E.Layout(gtx, ToggleIconBtn(t.Model.InstrEnlarged(), t.Theme, ie.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, ie.enlargeHint, ie.shrinkHint)) + instrEnlarged := ToggleIconBtn(t.Model.InstrEnlarged(), t.Theme, ie.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, ie.enlargeHint, ie.shrinkHint) + return layout.E.Layout(gtx, instrEnlarged.Layout) }), layout.Rigid(func(gtx C) D { - return layout.E.Layout(gtx, ActionIconBtn(t.Model.AddInstrument(), t.Theme, ie.newInstrumentBtn, icons.ContentAdd, ie.addInstrumentHint)) + addInstrumetn := ActionIconBtn(t.Model.AddInstrument(), t.Theme, ie.newInstrumentBtn, icons.ContentAdd, ie.addInstrumentHint) + return layout.E.Layout(gtx, addInstrumetn.Layout) }), ) }), @@ -212,6 +215,15 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D { t.LoadInstrument(reader) } + splitInstrument := ActionIconBtn(t.SplitInstrument(), t.Theme, ie.splitInstrumentBtn, icons.CommunicationCallSplit, ie.splitInstrumentHint) + commentExpanded := ToggleIconBtn(t.CommentExpanded(), t.Theme, ie.commentExpandBtn, icons.NavigationExpandMore, icons.NavigationExpandLess, ie.expandCommentHint, ie.collapseCommentHint) + solo := ToggleIconBtn(t.Solo(), t.Theme, ie.soloBtn, icons.SocialGroup, icons.SocialPerson, ie.soloHint, ie.unsoloHint) + mute := ToggleIconBtn(t.Mute(), t.Theme, ie.muteBtn, icons.AVVolumeUp, icons.AVVolumeOff, ie.muteHint, ie.unmuteHint) + saveInstrument := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.saveInstrumentBtn, icons.ContentSave, "Save instrument") + loadInstrument := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.loadInstrumentBtn, icons.FileFolderOpen, "Load instrument") + copyInstrument := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument") + deleteInstrument := ActionIconBtn(t.DeleteInstrument(), t.Theme, ie.deleteInstrumentBtn, icons.ActionDelete, ie.deleteInstrumentHint) + header := func(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(layout.Spacer{Width: 6}.Layout), @@ -220,23 +232,24 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D { layout.Rigid(func(gtx layout.Context) layout.Dimensions { return t.InstrumentVoices.Layout(gtx, t.Model.InstrumentVoices(), t.Theme, &t.Theme.NumericUpDown, "Number of voices for this instrument") }), - layout.Rigid(ActionIconBtn(t.SplitInstrument(), t.Theme, ie.splitInstrumentBtn, icons.CommunicationCallSplit, ie.splitInstrumentHint)), + layout.Rigid(splitInstrument.Layout), layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }), - layout.Rigid(ToggleIconBtn(t.CommentExpanded(), t.Theme, ie.commentExpandBtn, icons.NavigationExpandMore, icons.NavigationExpandLess, ie.expandCommentHint, ie.collapseCommentHint)), - layout.Rigid(ToggleIconBtn(t.Solo(), t.Theme, ie.soloBtn, icons.SocialGroup, icons.SocialPerson, ie.soloHint, ie.unsoloHint)), - layout.Rigid(ToggleIconBtn(t.Mute(), t.Theme, ie.muteBtn, icons.AVVolumeUp, icons.AVVolumeOff, ie.muteHint, ie.unmuteHint)), + layout.Rigid(commentExpanded.Layout), + layout.Rigid(solo.Layout), + layout.Rigid(mute.Layout), layout.Rigid(func(gtx C) D { - dims := TipIconBtn(t.Theme, &t.Theme.IconButton, ie.presetMenuBtn, icons.NavigationMenu, "Load preset", true).Layout(gtx) + preset := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.presetMenuBtn, icons.NavigationMenu, "Load preset") + dims := preset.Layout(gtx) op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops) gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(500)) gtx.Constraints.Max.X = gtx.Dp(unit.Dp(180)) m.Layout(gtx, ie.presetMenuItems...) return dims }), - layout.Rigid(TipIconBtn(t.Theme, &t.Theme.IconButton, ie.saveInstrumentBtn, icons.ContentSave, "Save instrument", true).Layout), - layout.Rigid(TipIconBtn(t.Theme, &t.Theme.IconButton, ie.loadInstrumentBtn, icons.FileFolderOpen, "Load instrument", true).Layout), - layout.Rigid(TipIconBtn(t.Theme, &t.Theme.IconButton, ie.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument", true).Layout), - layout.Rigid(ActionIconBtn(t.DeleteInstrument(), t.Theme, ie.deleteInstrumentBtn, icons.ActionDelete, ie.deleteInstrumentHint)), + layout.Rigid(saveInstrument.Layout), + layout.Rigid(loadInstrument.Layout), + layout.Rigid(copyInstrument.Layout), + layout.Rigid(deleteInstrument.Layout), ) } @@ -467,7 +480,8 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D { t.AddUnit(false).Do() } margin := layout.Inset{Right: unit.Dp(20), Bottom: unit.Dp(1)} - return margin.Layout(gtx, TipIconBtn(t.Theme, &t.Theme.AddUnit, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)", true).Layout) + addUnit := IconBtn(t.Theme, &t.Theme.IconButton.Emphasis, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)") + return margin.Layout(gtx, addUnit.Layout) }), ) }) diff --git a/tracker/gioui/menu.go b/tracker/gioui/menu.go index efcc7b2..2a62b74 100644 --- a/tracker/gioui/menu.go +++ b/tracker/gioui/menu.go @@ -161,14 +161,15 @@ func PopupMenu(th *Theme, s *LabelStyle, menu *Menu) MenuStyle { } } -func (tr *Tracker) layoutMenu(gtx C, title string, clickable *Clickable, menu *Menu, width unit.Dp, items ...MenuItem) layout.Widget { - for clickable.Clicked(gtx) { +func (tr *Tracker) layoutMenu(gtx C, title string, ct *ClickableTip, menu *Menu, width unit.Dp, items ...MenuItem) layout.Widget { + for ct.Clicked(gtx) { menu.Visible = true } m := PopupMenu(tr.Theme, &tr.Theme.Menu.Text, menu) return func(gtx C) D { defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() - dims := Btn(tr.Theme, &tr.Theme.Button.Menu, clickable, title)(gtx) + btn := Btn(tr.Theme, &tr.Theme.Button.Menu, ct, title, "") + dims := btn.Layout(gtx) op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops) gtx.Constraints.Max.X = gtx.Dp(width) gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(300)) diff --git a/tracker/gioui/note_editor.go b/tracker/gioui/note_editor.go index 1db1612..a764efa 100644 --- a/tracker/gioui/note_editor.go +++ b/tracker/gioui/note_editor.go @@ -183,22 +183,22 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D { midiInBtnStyle := ToggleBtn(t.TrackMidiIn(), t.Theme, te.TrackMidiInBtn, "MIDI", "Input notes from MIDI keyboard") return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Dimensions{Size: image.Pt(gtx.Dp(unit.Dp(12)), 0)} }), - layout.Rigid(addSemitoneBtnStyle), - layout.Rigid(subtractSemitoneBtnStyle), - layout.Rigid(addOctaveBtnStyle), - layout.Rigid(subtractOctaveBtnStyle), - layout.Rigid(noteOffBtnStyle), - layout.Rigid(effectBtnStyle), - layout.Rigid(uniqueBtnStyle), + layout.Rigid(addSemitoneBtnStyle.Layout), + layout.Rigid(subtractSemitoneBtnStyle.Layout), + layout.Rigid(addOctaveBtnStyle.Layout), + layout.Rigid(subtractOctaveBtnStyle.Layout), + layout.Rigid(noteOffBtnStyle.Layout), + layout.Rigid(effectBtnStyle.Layout), + layout.Rigid(uniqueBtnStyle.Layout), layout.Rigid(layout.Spacer{Width: 10}.Layout), layout.Rigid(Label(t.Theme, &t.Theme.NoteEditor.Header, "Voices").Layout), layout.Rigid(layout.Spacer{Width: 4}.Layout), layout.Rigid(voiceUpDown), - layout.Rigid(splitTrackBtnStyle), - layout.Rigid(midiInBtnStyle), + layout.Rigid(splitTrackBtnStyle.Layout), + layout.Rigid(midiInBtnStyle.Layout), layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }), - layout.Rigid(deleteTrackBtnStyle), - layout.Rigid(newTrackBtnStyle)) + layout.Rigid(deleteTrackBtnStyle.Layout), + layout.Rigid(newTrackBtnStyle.Layout)) }) } diff --git a/tracker/gioui/oscilloscope.go b/tracker/gioui/oscilloscope.go index f9a7a5c..bc90123 100644 --- a/tracker/gioui/oscilloscope.go +++ b/tracker/gioui/oscilloscope.go @@ -49,6 +49,9 @@ func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, once, wrap tr leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout + onceBtn := ToggleBtn(once, th, s.onceBtn, "Once", "Trigger once on next event") + wrapBtn := ToggleBtn(wrap, th, s.wrapBtn, "Wrap", "Wrap buffer when full") + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Flexed(1, func(gtx C) D { return s.layoutWave(gtx, wave, th) }), layout.Rigid(func(gtx C) D { @@ -56,7 +59,7 @@ func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, once, wrap tr layout.Rigid(leftSpacer), layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Trigger").Layout), layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }), - layout.Rigid(ToggleBtn(once, th, s.onceBtn, "Once", "Trigger once on next event")), + layout.Rigid(onceBtn.Layout), layout.Rigid(func(gtx C) D { return s.triggerChannelNumber.Layout(gtx, vtrig, th, &th.NumericUpDown, "Trigger channel") }), @@ -68,7 +71,7 @@ func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, once, wrap tr layout.Rigid(leftSpacer), layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Buffer").Layout), layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }), - layout.Rigid(ToggleBtn(wrap, th, s.wrapBtn, "Wrap", "Wrap buffer when full")), + layout.Rigid(wrapBtn.Layout), layout.Rigid(func(gtx C) D { return s.lengthInBeatsNumber.Layout(gtx, vlen, th, &th.NumericUpDown, "Buffer length in beats") }), diff --git a/tracker/gioui/songpanel.go b/tracker/gioui/songpanel.go index 381f692..0129ae0 100644 --- a/tracker/gioui/songpanel.go +++ b/tracker/gioui/songpanel.go @@ -22,8 +22,8 @@ type SongPanel struct { LoudnessExpander *Expander PeakExpander *Expander - WeightingTypeBtn *Clickable - OversamplingBtn *Clickable + WeightingTypeBtn *ClickableTip + OversamplingBtn *ClickableTip BPM *NumericUpDown RowsPerPattern *NumericUpDown @@ -48,8 +48,8 @@ func NewSongPanel(model *tracker.Model) *SongPanel { MenuBar: NewMenuBar(model), PlayBar: NewPlayBar(), - WeightingTypeBtn: &Clickable{}, - OversamplingBtn: &Clickable{}, + WeightingTypeBtn: new(ClickableTip), + OversamplingBtn: new(ClickableTip), SongSettingsExpander: &Expander{Expanded: true}, ScopeExpander: &Expander{}, @@ -98,13 +98,13 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { weightingTxt = "No weight (RMS)" } - weightingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.WeightingTypeBtn, weightingTxt) + weightingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.WeightingTypeBtn, weightingTxt, "") oversamplingTxt := "Sample peak" if tr.Model.Oversampling().Value() { oversamplingTxt = "True peak" } - oversamplingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.OversamplingBtn, oversamplingTxt) + 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 { @@ -164,7 +164,7 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { }), layout.Rigid(func(gtx C) D { gtx.Constraints.Min.X = 0 - return weightingBtn(gtx) + return weightingBtn.Layout(gtx) }), ) }, @@ -193,7 +193,7 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D { }), layout.Rigid(func(gtx C) D { gtx.Constraints.Min.X = 0 - return oversamplingBtn(gtx) + return oversamplingBtn.Layout(gtx) }), ) }, @@ -296,7 +296,7 @@ func (e *Expander) layoutHeader(gtx C, th *Theme, title string, smallWidget layo } type MenuBar struct { - Clickables []Clickable + Clickables []ClickableTip Menus []Menu fileMenuItems []MenuItem @@ -309,7 +309,7 @@ type MenuBar struct { func NewMenuBar(model *tracker.Model) *MenuBar { ret := &MenuBar{ - Clickables: make([]Clickable, 3), + Clickables: make([]ClickableTip, 3), Menus: make([]Menu, 3), PanicBtn: new(ClickableTip), panicHint: makeHint("Panic", " (%s)", "PanicToggle"), @@ -343,12 +343,15 @@ func (t *MenuBar) Layout(gtx C, tr *Tracker) D { gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(36)) gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36)) - panicBtnStyle := ToggleIconBtn(tr.Panic(), tr.Theme, t.PanicBtn, icons.AlertErrorOutline, icons.AlertError, t.panicHint, t.panicHint) + panicBtn := ToggleIconBtn(tr.Panic(), tr.Theme, t.PanicBtn, icons.AlertErrorOutline, icons.AlertError, t.panicHint, t.panicHint) + if tr.Panic().Value() { + panicBtn.Style = tr.Theme.IconButton.Error + } flex := layout.Flex{Axis: layout.Horizontal, Alignment: layout.End} fileFC := layout.Rigid(tr.layoutMenu(gtx, "File", &t.Clickables[0], &t.Menus[0], unit.Dp(200), t.fileMenuItems...)) editFC := layout.Rigid(tr.layoutMenu(gtx, "Edit", &t.Clickables[1], &t.Menus[1], unit.Dp(200), t.editMenuItems...)) midiFC := layout.Rigid(tr.layoutMenu(gtx, "MIDI", &t.Clickables[2], &t.Menus[2], unit.Dp(200), t.midiMenuItems...)) - panicFC := layout.Flexed(1, func(gtx C) D { return layout.E.Layout(gtx, panicBtnStyle) }) + panicFC := layout.Flexed(1, func(gtx C) D { return layout.E.Layout(gtx, panicBtn.Layout) }) if len(t.midiMenuItems) > 0 { return flex.Layout(gtx, fileFC, editFC, midiFC, panicFC) } @@ -390,13 +393,19 @@ func NewPlayBar() *PlayBar { } func (pb *PlayBar) Layout(gtx C, tr *Tracker) D { + playBtn := ToggleIconBtn(tr.Playing(), tr.Theme, pb.PlayingBtn, icons.AVPlayArrow, icons.AVStop, pb.playHint, pb.stopHint) + rewindBtn := ActionIconBtn(tr.PlaySongStart(), tr.Theme, pb.RewindBtn, icons.AVFastRewind, pb.rewindHint) + recordBtn := ToggleIconBtn(tr.IsRecording(), tr.Theme, pb.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, pb.recordHint, pb.stopRecordHint) + followBtn := ToggleIconBtn(tr.Follow(), tr.Theme, pb.FollowBtn, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, pb.followOffHint, pb.followOnHint) + loopBtn := ToggleIconBtn(tr.LoopToggle(), tr.Theme, pb.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, pb.loopOffHint, pb.loopOnHint) + return Surface{Gray: 37}.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, - layout.Flexed(1, ToggleIconBtn(tr.Playing(), tr.Theme, pb.PlayingBtn, icons.AVPlayArrow, icons.AVStop, pb.playHint, pb.stopHint)), - layout.Rigid(ActionIconBtn(tr.PlaySongStart(), tr.Theme, pb.RewindBtn, icons.AVFastRewind, pb.rewindHint)), - layout.Rigid(ToggleIconBtn(tr.IsRecording(), tr.Theme, pb.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, pb.recordHint, pb.stopRecordHint)), - layout.Rigid(ToggleIconBtn(tr.Follow(), tr.Theme, pb.FollowBtn, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, pb.followOffHint, pb.followOnHint)), - layout.Rigid(ToggleIconBtn(tr.LoopToggle(), tr.Theme, pb.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, pb.loopOffHint, pb.loopOnHint)), + layout.Flexed(1, playBtn.Layout), + layout.Rigid(rewindBtn.Layout), + layout.Rigid(recordBtn.Layout), + layout.Rigid(followBtn.Layout), + layout.Rigid(loopBtn.Layout), ) }) } diff --git a/tracker/gioui/theme.go b/tracker/gioui/theme.go index a4a858c..2f22481 100644 --- a/tracker/gioui/theme.go +++ b/tracker/gioui/theme.go @@ -19,8 +19,12 @@ type Theme struct { Disabled ButtonStyle Menu ButtonStyle } - IconButton IconButtonStyle - AddUnit IconButtonStyle + IconButton struct { + Enabled IconButtonStyle + Disabled IconButtonStyle + Emphasis IconButtonStyle + Error IconButtonStyle + } Oscilloscope OscilloscopeStyle NumericUpDown NumericUpDownStyle DialogTitle LabelStyle diff --git a/tracker/gioui/theme.yml b/tracker/gioui/theme.yml index f182d1a..5d6dbea 100644 --- a/tracker/gioui/theme.yml +++ b/tracker/gioui/theme.yml @@ -55,14 +55,23 @@ button: height: *buttonheight inset: *buttoninset iconbutton: - color: *primarycolor - size: 24 - inset: { top: 6, bottom: 6, left: 6, right: 6 } -addunit: - color: *contrastfg - background: *primarycolor - size: 24 - inset: { top: 6, bottom: 6, left: 6, right: 6 } + enabled: + color: *primarycolor + size: 24 + inset: { top: 6, bottom: 6, left: 6, right: 6 } + disabled: + color: *disabled + size: 24 + inset: { top: 6, bottom: 6, left: 6, right: 6 } + emphasis: + color: *contrastfg + background: *primarycolor + size: 24 + inset: { top: 6, bottom: 6, left: 6, right: 6 } + error: + color: *errorcolor + size: 24 + inset: { top: 6, bottom: 6, left: 6, right: 6 } oscilloscope: curvecolors: [*primarycolor, *secondarycolor] limitcolor: { r: 255, g: 255, b: 255, a: 8 } diff --git a/tracker/gioui/unit_editor.go b/tracker/gioui/unit_editor.go index 6973e10..5d977ca 100644 --- a/tracker/gioui/unit_editor.go +++ b/tracker/gioui/unit_editor.go @@ -140,14 +140,19 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D { text = pe.caser.String(text) } hintText := Label(t.Theme, &t.Theme.UnitEditor.Hint, text) + deleteUnit := ActionIconBtn(t.DeleteUnit(), t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)") + copyUnit := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint) + disableUnit := ToggleIconBtn(t.UnitDisabled(), t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint) + return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(ActionIconBtn(t.DeleteUnit(), t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")), - layout.Rigid(TipIconBtn(t.Theme, &t.Theme.IconButton, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint, true).Layout), - layout.Rigid(ToggleIconBtn(t.UnitDisabled(), t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint)), + layout.Rigid(deleteUnit.Layout), + layout.Rigid(copyUnit.Layout), + layout.Rigid(disableUnit.Layout), layout.Rigid(func(gtx C) D { var dims D if t.Units().SelectedType() != "" { - dims = ActionIconBtn(t.ClearUnit(), t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit")(gtx) + clearUnit := ActionIconBtn(t.ClearUnit(), t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit") + dims = clearUnit.Layout(gtx) } return D{Size: image.Pt(gtx.Dp(unit.Dp(48)), dims.Size.Y)} }), @@ -218,9 +223,9 @@ func (pe *UnitEditor) command(e key.Event, t *Tracker) { type ParameterWidget struct { floatWidget widget.Float boolWidget widget.Bool - instrBtn Clickable + instrBtn ClickableTip instrMenu Menu - unitBtn Clickable + unitBtn ClickableTip unitMenu Menu Parameter tracker.Parameter tipArea component.TipArea