mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-21 06:24:32 -04:00
refactor(tracker/gioui): rewrote Button(s) to bind to Model during layout
The old mechanism made it difficult to follow exactly what happens when a button was clicked, because the Action/Bool that gets executed / toggled was declared ages ago, in the constructor. In the new mechanism, the Action / Bool is bound to the button at the last minute, right before Layout. ActionButton, ToggleButton, ActionIconButton and ToggleIconButton were done to avoid heap escapes: if the corresponding functions woudl've returned layout.Widget, a heap allocation would've been needed.
This commit is contained in:
parent
0ea20ea5bf
commit
db2ccf977d
@ -22,41 +22,273 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
TipClickable struct {
|
Clickable struct {
|
||||||
Clickable Clickable
|
click gesture.Click
|
||||||
TipArea component.TipArea
|
history []widget.Press
|
||||||
|
|
||||||
|
requestClicks int
|
||||||
|
TipArea component.TipArea // since almost all buttons have tooltips, we include the state for a tooltip here for convenience
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionClickable struct {
|
ButtonStyle struct {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButtonStyle struct {
|
||||||
|
Background color.NRGBA
|
||||||
|
// Color is the icon color.
|
||||||
|
Color color.NRGBA
|
||||||
|
// Size is the icon size.
|
||||||
|
Size unit.Dp
|
||||||
|
Inset layout.Inset
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button is a text button
|
||||||
|
Button struct {
|
||||||
|
Theme *Theme
|
||||||
|
Style *ButtonStyle
|
||||||
|
Text string
|
||||||
|
Tip string
|
||||||
|
Clickable *Clickable
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionButton is a text button that executes an action when clicked.
|
||||||
|
ActionButton struct {
|
||||||
Action tracker.Action
|
Action tracker.Action
|
||||||
TipClickable
|
DisabledStyle *ButtonStyle
|
||||||
|
Button
|
||||||
}
|
}
|
||||||
|
|
||||||
TipIconButtonStyle struct {
|
// ToggleButton is a text button that toggles a boolean value when clicked.
|
||||||
TipArea *component.TipArea
|
ToggleButton struct {
|
||||||
IconButtonStyle IconButtonStyle
|
|
||||||
Tooltip component.Tooltip
|
|
||||||
}
|
|
||||||
|
|
||||||
BoolClickable struct {
|
|
||||||
Clickable Clickable
|
|
||||||
TipArea component.TipArea
|
|
||||||
Bool tracker.Bool
|
Bool tracker.Bool
|
||||||
|
DisabledStyle *ButtonStyle
|
||||||
|
OffStyle *ButtonStyle
|
||||||
|
Button
|
||||||
|
}
|
||||||
|
|
||||||
|
// IconButton is a button with an icon.
|
||||||
|
IconButton struct {
|
||||||
|
Theme *Theme
|
||||||
|
Style *IconButtonStyle
|
||||||
|
Icon *widget.Icon
|
||||||
|
Tip string
|
||||||
|
Clickable *Clickable
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionIconButton is an icon button that executes an action when clicked.
|
||||||
|
ActionIconButton struct {
|
||||||
|
Action tracker.Action
|
||||||
|
DisabledStyle *IconButtonStyle
|
||||||
|
IconButton
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleIconButton is an icon button that toggles a boolean value when clicked.
|
||||||
|
ToggleIconButton struct {
|
||||||
|
Bool tracker.Bool
|
||||||
|
DisabledStyle *IconButtonStyle
|
||||||
|
OffIcon *widget.Icon
|
||||||
|
OffTip string
|
||||||
|
IconButton
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewActionClickable(a tracker.Action) *ActionClickable {
|
func Btn(th *Theme, st *ButtonStyle, c *Clickable, txt string, tip string) Button {
|
||||||
return &ActionClickable{
|
return Button{
|
||||||
Action: a,
|
Theme: th,
|
||||||
|
Style: st,
|
||||||
|
Clickable: c,
|
||||||
|
Text: txt,
|
||||||
|
Tip: tip,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBoolClickable(b tracker.Bool) *BoolClickable {
|
func ActionBtn(act tracker.Action, th *Theme, c *Clickable, txt string, tip string) ActionButton {
|
||||||
return &BoolClickable{
|
return ActionButton{
|
||||||
Bool: b,
|
Action: act,
|
||||||
|
DisabledStyle: &th.Button.Disabled,
|
||||||
|
Button: Btn(th, &th.Button.Text, c, txt, tip),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToggleBtn(b tracker.Bool, th *Theme, c *Clickable, text string, tip string) ToggleButton {
|
||||||
|
return ToggleButton{
|
||||||
|
Bool: b,
|
||||||
|
DisabledStyle: &th.Button.Disabled,
|
||||||
|
OffStyle: &th.Button.Text,
|
||||||
|
Button: Btn(th, &th.Button.Filled, c, text, tip),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IconBtn(th *Theme, st *IconButtonStyle, c *Clickable, icon []byte, tip string) IconButton {
|
||||||
|
return IconButton{
|
||||||
|
Theme: th,
|
||||||
|
Style: st,
|
||||||
|
Clickable: c,
|
||||||
|
Icon: th.Icon(icon),
|
||||||
|
Tip: tip,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActionIconBtn(act tracker.Action, th *Theme, c *Clickable, icon []byte, tip string) ActionIconButton {
|
||||||
|
return ActionIconButton{
|
||||||
|
Action: act,
|
||||||
|
DisabledStyle: &th.IconButton.Disabled,
|
||||||
|
IconButton: IconBtn(th, &th.IconButton.Enabled, c, icon, tip),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToggleIconBtn(b tracker.Bool, th *Theme, c *Clickable, offIcon, onIcon []byte, offTip, onTip string) ToggleIconButton {
|
||||||
|
return ToggleIconButton{
|
||||||
|
Bool: b,
|
||||||
|
DisabledStyle: &th.IconButton.Disabled,
|
||||||
|
OffIcon: th.Icon(offIcon),
|
||||||
|
OffTip: offTip,
|
||||||
|
IconButton: IconBtn(th, &th.IconButton.Enabled, c, onIcon, onTip),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Button) Layout(gtx C) D {
|
||||||
|
if b.Tip != "" {
|
||||||
|
return b.Clickable.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 (b *ActionButton) Layout(gtx C) D {
|
||||||
|
for b.Clickable.Clicked(gtx) {
|
||||||
|
b.Action.Do()
|
||||||
|
}
|
||||||
|
if !b.Action.Enabled() {
|
||||||
|
b.Style = b.DisabledStyle
|
||||||
|
}
|
||||||
|
return b.Button.Layout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ToggleButton) Layout(gtx C) D {
|
||||||
|
for b.Clickable.Clicked(gtx) {
|
||||||
|
b.Bool.Toggle()
|
||||||
|
}
|
||||||
|
if !b.Bool.Enabled() {
|
||||||
|
b.Style = b.DisabledStyle
|
||||||
|
} else if !b.Bool.Value() {
|
||||||
|
b.Style = b.OffStyle
|
||||||
|
}
|
||||||
|
return b.Button.Layout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *IconButton) Layout(gtx C) D {
|
||||||
|
if b.Tip != "" {
|
||||||
|
return b.Clickable.TipArea.Layout(gtx, Tooltip(b.Theme, b.Tip), b.actualLayout)
|
||||||
|
}
|
||||||
|
return b.actualLayout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *IconButton) actualLayout(gtx C) D {
|
||||||
|
m := op.Record(gtx.Ops)
|
||||||
|
dims := 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.Constraints.Min.X + gtx.Constraints.Min.Y) / 4
|
||||||
|
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 {
|
||||||
|
return b.Style.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
size := gtx.Dp(b.Style.Size)
|
||||||
|
if b.Icon != nil {
|
||||||
|
gtx.Constraints.Min = image.Point{X: size}
|
||||||
|
b.Icon.Layout(gtx, b.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 (b *ActionIconButton) Layout(gtx C) D {
|
||||||
|
for b.Clickable.Clicked(gtx) {
|
||||||
|
b.Action.Do()
|
||||||
|
}
|
||||||
|
if !b.Action.Enabled() {
|
||||||
|
b.Style = b.DisabledStyle
|
||||||
|
}
|
||||||
|
return b.IconButton.Layout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ToggleIconButton) Layout(gtx C) D {
|
||||||
|
for b.Clickable.Clicked(gtx) {
|
||||||
|
b.Bool.Toggle()
|
||||||
|
}
|
||||||
|
if !b.Bool.Enabled() {
|
||||||
|
b.Style = b.DisabledStyle
|
||||||
|
}
|
||||||
|
if !b.Bool.Value() {
|
||||||
|
b.Icon = b.OffIcon
|
||||||
|
b.Tip = b.OffTip
|
||||||
|
}
|
||||||
|
return b.IconButton.Layout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
func Tooltip(th *Theme, tip string) component.Tooltip {
|
func Tooltip(th *Theme, tip string) component.Tooltip {
|
||||||
tooltip := component.PlatformTooltip(&th.Material, tip)
|
tooltip := component.PlatformTooltip(&th.Material, tip)
|
||||||
tooltip.Bg = th.Tooltip.Bg
|
tooltip.Bg = th.Tooltip.Bg
|
||||||
@ -64,88 +296,6 @@ func Tooltip(th *Theme, tip string) component.Tooltip {
|
|||||||
return tooltip
|
return tooltip
|
||||||
}
|
}
|
||||||
|
|
||||||
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.Enabled() {
|
|
||||||
ret.IconButtonStyle.Color = th.Button.Disabled.Color
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func TipIcon(th *Theme, w *TipClickable, icon []byte, tip string) TipIconButtonStyle {
|
|
||||||
iconButtonStyle := IconButton(th, &w.Clickable, th.Icon(icon), "")
|
|
||||||
iconButtonStyle.Color = th.Material.Palette.ContrastBg
|
|
||||||
iconButtonStyle.Background = color.NRGBA{}
|
|
||||||
iconButtonStyle.Inset = layout.UniformInset(unit.Dp(6))
|
|
||||||
return TipIconButtonStyle{
|
|
||||||
TipArea: &w.TipArea,
|
|
||||||
IconButtonStyle: iconButtonStyle,
|
|
||||||
Tooltip: Tooltip(th, tip),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToggleIcon(gtx C, th *Theme, w *BoolClickable, offIcon, onIcon []byte, offTip, onTip string) TipIconButtonStyle {
|
|
||||||
icon := offIcon
|
|
||||||
tip := offTip
|
|
||||||
if w.Bool.Value() {
|
|
||||||
icon = onIcon
|
|
||||||
tip = onTip
|
|
||||||
}
|
|
||||||
for w.Clickable.Clicked(gtx) {
|
|
||||||
w.Bool.Toggle()
|
|
||||||
}
|
|
||||||
ibStyle := IconButton(th, &w.Clickable, th.Icon(icon), "")
|
|
||||||
ibStyle.Background = color.NRGBA{}
|
|
||||||
ibStyle.Inset = layout.UniformInset(unit.Dp(6))
|
|
||||||
ibStyle.Color = th.Material.Palette.ContrastBg
|
|
||||||
if !w.Bool.Enabled() {
|
|
||||||
ibStyle.Color = th.Button.Disabled.Color
|
|
||||||
}
|
|
||||||
return TipIconButtonStyle{
|
|
||||||
TipArea: &w.TipArea,
|
|
||||||
IconButtonStyle: ibStyle,
|
|
||||||
Tooltip: Tooltip(th, tip),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TipIconButtonStyle) Layout(gtx C) D {
|
|
||||||
return t.TipArea.Layout(gtx, t.Tooltip, t.IconButtonStyle.Layout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ActionButton(gtx C, th *Theme, style *ButtonStyle, w *ActionClickable, text string) Button {
|
|
||||||
for w.Clickable.Clicked(gtx) {
|
|
||||||
w.Action.Do()
|
|
||||||
}
|
|
||||||
if !w.Action.Enabled() {
|
|
||||||
return Btn(th, &th.Button.Disabled, &w.Clickable, text)
|
|
||||||
}
|
|
||||||
return Btn(th, style, &w.Clickable, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToggleButton(gtx C, th *Theme, b *BoolClickable, text string) Button {
|
|
||||||
for b.Clickable.Clicked(gtx) {
|
|
||||||
b.Bool.Toggle()
|
|
||||||
}
|
|
||||||
if !b.Bool.Enabled() {
|
|
||||||
return Btn(th, &th.Button.Disabled, &b.Clickable, text)
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
type Clickable struct {
|
|
||||||
click gesture.Click
|
|
||||||
history []widget.Press
|
|
||||||
|
|
||||||
requestClicks int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Click executes a simple programmatic click.
|
// Click executes a simple programmatic click.
|
||||||
func (b *Clickable) Click() {
|
func (b *Clickable) Click() {
|
||||||
b.requestClicks++
|
b.requestClicks++
|
||||||
@ -252,135 +402,6 @@ func (b *Clickable) update(_ event.Tag, gtx layout.Context) (widget.Click, bool)
|
|||||||
return widget.Click{}, false
|
return widget.Click{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
type ButtonStyle struct {
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
type Button struct {
|
|
||||||
Text string
|
|
||||||
Button *Clickable
|
|
||||||
shaper *text.Shaper
|
|
||||||
ButtonStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
type IconButtonStyle struct {
|
|
||||||
Background color.NRGBA
|
|
||||||
// Color is the icon color.
|
|
||||||
Color color.NRGBA
|
|
||||||
Icon *widget.Icon
|
|
||||||
// Size is the icon size.
|
|
||||||
Size unit.Dp
|
|
||||||
Inset layout.Inset
|
|
||||||
Button *Clickable
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Btn(th *Theme, style *ButtonStyle, button *Clickable, txt string) Button {
|
|
||||||
b := Button{
|
|
||||||
Text: txt,
|
|
||||||
ButtonStyle: *style,
|
|
||||||
Button: button,
|
|
||||||
shaper: th.Material.Shaper,
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func IconButton(th *Theme, button *Clickable, icon *widget.Icon, description string) IconButtonStyle {
|
|
||||||
return IconButtonStyle{
|
|
||||||
Background: th.Material.Palette.ContrastBg,
|
|
||||||
Color: th.Material.Palette.ContrastFg,
|
|
||||||
Icon: icon,
|
|
||||||
Size: 24,
|
|
||||||
Inset: layout.UniformInset(12),
|
|
||||||
Button: button,
|
|
||||||
Description: description,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
|
||||||
rr := gtx.Dp(b.CornerRadius)
|
|
||||||
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
|
|
||||||
background := b.Background
|
|
||||||
switch {
|
|
||||||
case b.Button.Hovered():
|
|
||||||
background = hoveredColor(background)
|
|
||||||
}
|
|
||||||
paint.Fill(gtx.Ops, background)
|
|
||||||
for _, c := range b.Button.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.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 IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions {
|
|
||||||
m := op.Record(gtx.Ops)
|
|
||||||
dims := b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
semantic.Button.Add(gtx.Ops)
|
|
||||||
if d := b.Description; d != "" {
|
|
||||||
semantic.DescriptionOp(b.Description).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 := b.Background
|
|
||||||
switch {
|
|
||||||
case b.Button.Hovered():
|
|
||||||
background = hoveredColor(background)
|
|
||||||
}
|
|
||||||
paint.Fill(gtx.Ops, background)
|
|
||||||
for _, c := range b.Button.History() {
|
|
||||||
drawInk(gtx, (widget.Press)(c))
|
|
||||||
}
|
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Min}
|
|
||||||
},
|
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
size := gtx.Dp(b.Size)
|
|
||||||
if b.Icon != nil {
|
|
||||||
gtx.Constraints.Min = image.Point{X: size}
|
|
||||||
b.Icon.Layout(gtx, b.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 drawInk(gtx layout.Context, c widget.Press) {
|
func drawInk(gtx layout.Context, c widget.Press) {
|
||||||
// duration is the number of seconds for the
|
// duration is the number of seconds for the
|
||||||
// completed animation: expand while fading in, then
|
// completed animation: expand while fading in, then
|
||||||
|
@ -23,19 +23,19 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
InstrumentEditor struct {
|
InstrumentEditor struct {
|
||||||
newInstrumentBtn *ActionClickable
|
newInstrumentBtn *Clickable
|
||||||
enlargeBtn *BoolClickable
|
enlargeBtn *Clickable
|
||||||
deleteInstrumentBtn *ActionClickable
|
deleteInstrumentBtn *Clickable
|
||||||
linkInstrTrackBtn *BoolClickable
|
linkInstrTrackBtn *Clickable
|
||||||
splitInstrumentBtn *ActionClickable
|
splitInstrumentBtn *Clickable
|
||||||
copyInstrumentBtn *TipClickable
|
copyInstrumentBtn *Clickable
|
||||||
saveInstrumentBtn *TipClickable
|
saveInstrumentBtn *Clickable
|
||||||
loadInstrumentBtn *TipClickable
|
loadInstrumentBtn *Clickable
|
||||||
addUnitBtn *ActionClickable
|
addUnitBtn *Clickable
|
||||||
presetMenuBtn *TipClickable
|
presetMenuBtn *Clickable
|
||||||
commentExpandBtn *BoolClickable
|
commentExpandBtn *Clickable
|
||||||
soloBtn *BoolClickable
|
soloBtn *Clickable
|
||||||
muteBtn *BoolClickable
|
muteBtn *Clickable
|
||||||
commentEditor *Editor
|
commentEditor *Editor
|
||||||
commentString tracker.String
|
commentString tracker.String
|
||||||
nameEditor *Editor
|
nameEditor *Editor
|
||||||
@ -68,18 +68,19 @@ type (
|
|||||||
|
|
||||||
func NewInstrumentEditor(model *tracker.Model) *InstrumentEditor {
|
func NewInstrumentEditor(model *tracker.Model) *InstrumentEditor {
|
||||||
ret := &InstrumentEditor{
|
ret := &InstrumentEditor{
|
||||||
newInstrumentBtn: NewActionClickable(model.AddInstrument()),
|
newInstrumentBtn: new(Clickable),
|
||||||
enlargeBtn: NewBoolClickable(model.InstrEnlarged()),
|
enlargeBtn: new(Clickable),
|
||||||
deleteInstrumentBtn: NewActionClickable(model.DeleteInstrument()),
|
deleteInstrumentBtn: new(Clickable),
|
||||||
linkInstrTrackBtn: NewBoolClickable(model.LinkInstrTrack()),
|
linkInstrTrackBtn: new(Clickable),
|
||||||
splitInstrumentBtn: NewActionClickable(model.SplitInstrument()),
|
splitInstrumentBtn: new(Clickable),
|
||||||
copyInstrumentBtn: new(TipClickable),
|
copyInstrumentBtn: new(Clickable),
|
||||||
saveInstrumentBtn: new(TipClickable),
|
saveInstrumentBtn: new(Clickable),
|
||||||
loadInstrumentBtn: new(TipClickable),
|
loadInstrumentBtn: new(Clickable),
|
||||||
commentExpandBtn: NewBoolClickable(model.CommentExpanded()),
|
commentExpandBtn: new(Clickable),
|
||||||
presetMenuBtn: new(TipClickable),
|
presetMenuBtn: new(Clickable),
|
||||||
soloBtn: NewBoolClickable(model.Solo()),
|
soloBtn: new(Clickable),
|
||||||
muteBtn: NewBoolClickable(model.Mute()),
|
muteBtn: new(Clickable),
|
||||||
|
addUnitBtn: new(Clickable),
|
||||||
commentEditor: NewEditor(false, false, text.Start),
|
commentEditor: NewEditor(false, false, text.Start),
|
||||||
nameEditor: NewEditor(true, true, text.Middle),
|
nameEditor: NewEditor(true, true, text.Middle),
|
||||||
searchEditor: NewEditor(true, true, text.Start),
|
searchEditor: NewEditor(true, true, text.Start),
|
||||||
@ -95,7 +96,6 @@ func NewInstrumentEditor(model *tracker.Model) *InstrumentEditor {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
ret.addUnit = model.AddUnit(false)
|
ret.addUnit = model.AddUnit(false)
|
||||||
ret.addUnitBtn = NewActionClickable(tracker.MakeEnabledAction(ret.AddUnitThenFocus()))
|
|
||||||
ret.enlargeHint = makeHint("Enlarge", " (%s)", "InstrEnlargedToggle")
|
ret.enlargeHint = makeHint("Enlarge", " (%s)", "InstrEnlargedToggle")
|
||||||
ret.shrinkHint = makeHint("Shrink", " (%s)", "InstrEnlargedToggle")
|
ret.shrinkHint = makeHint("Shrink", " (%s)", "InstrEnlargedToggle")
|
||||||
ret.addInstrumentHint = makeHint("Add\ninstrument", "\n(%s)", "AddInstrument")
|
ret.addInstrumentHint = makeHint("Add\ninstrument", "\n(%s)", "AddInstrument")
|
||||||
@ -133,14 +133,12 @@ func (ie *InstrumentEditor) Focused(gtx C) bool {
|
|||||||
func (ie *InstrumentEditor) childFocused(gtx C) bool {
|
func (ie *InstrumentEditor) childFocused(gtx C) bool {
|
||||||
return ie.unitEditor.sliderList.Focused(gtx) ||
|
return ie.unitEditor.sliderList.Focused(gtx) ||
|
||||||
ie.instrumentDragList.Focused(gtx) || gtx.Source.Focused(ie.commentEditor) || gtx.Source.Focused(ie.nameEditor) || gtx.Source.Focused(ie.searchEditor) ||
|
ie.instrumentDragList.Focused(gtx) || gtx.Source.Focused(ie.commentEditor) || gtx.Source.Focused(ie.nameEditor) || gtx.Source.Focused(ie.searchEditor) ||
|
||||||
gtx.Source.Focused(ie.addUnitBtn.Clickable) || gtx.Source.Focused(ie.commentExpandBtn.Clickable) || gtx.Source.Focused(ie.presetMenuBtn.Clickable) ||
|
gtx.Source.Focused(ie.addUnitBtn) || gtx.Source.Focused(ie.commentExpandBtn) || gtx.Source.Focused(ie.presetMenuBtn) ||
|
||||||
gtx.Source.Focused(ie.deleteInstrumentBtn.Clickable) || gtx.Source.Focused(ie.copyInstrumentBtn.Clickable)
|
gtx.Source.Focused(ie.deleteInstrumentBtn) || gtx.Source.Focused(ie.copyInstrumentBtn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
|
func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
|
||||||
ie.wasFocused = ie.Focused(gtx) || ie.childFocused(gtx)
|
ie.wasFocused = ie.Focused(gtx) || ie.childFocused(gtx)
|
||||||
fullscreenBtnStyle := ToggleIcon(gtx, t.Theme, ie.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, ie.enlargeHint, ie.shrinkHint)
|
|
||||||
linkBtnStyle := ToggleIcon(gtx, t.Theme, ie.linkInstrTrackBtn, icons.NotificationSyncDisabled, icons.NotificationSync, ie.linkDisabledHint, ie.linkEnabledHint)
|
|
||||||
|
|
||||||
octave := func(gtx C) D {
|
octave := func(gtx C) D {
|
||||||
in := layout.UniformInset(unit.Dp(1))
|
in := layout.UniformInset(unit.Dp(1))
|
||||||
@ -149,7 +147,6 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
newBtnStyle := ActionIcon(gtx, t.Theme, ie.newInstrumentBtn, icons.ContentAdd, ie.addInstrumentHint)
|
|
||||||
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(
|
||||||
@ -162,13 +159,16 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
|
|||||||
layout.Rigid(layout.Spacer{Width: 4}.Layout),
|
layout.Rigid(layout.Spacer{Width: 4}.Layout),
|
||||||
layout.Rigid(octave),
|
layout.Rigid(octave),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return layout.E.Layout(gtx, linkBtnStyle.Layout)
|
linkInstrTrackBtn := ToggleIconBtn(t.Model.LinkInstrTrack(), t.Theme, ie.linkInstrTrackBtn, icons.NotificationSyncDisabled, icons.NotificationSync, ie.linkDisabledHint, ie.linkEnabledHint)
|
||||||
|
return layout.E.Layout(gtx, linkInstrTrackBtn.Layout)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return layout.E.Layout(gtx, fullscreenBtnStyle.Layout)
|
instrEnlargedBtn := ToggleIconBtn(t.Model.InstrEnlarged(), t.Theme, ie.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, ie.enlargeHint, ie.shrinkHint)
|
||||||
|
return layout.E.Layout(gtx, instrEnlargedBtn.Layout)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return layout.E.Layout(gtx, newBtnStyle.Layout)
|
addInstrumentBtn := ActionIconBtn(t.Model.AddInstrument(), t.Theme, ie.newInstrumentBtn, icons.ContentAdd, ie.addInstrumentHint)
|
||||||
|
return layout.E.Layout(gtx, addInstrumentBtn.Layout)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@ -190,26 +190,16 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
|
|||||||
|
|
||||||
func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
|
func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
|
||||||
header := func(gtx C) D {
|
header := func(gtx C) D {
|
||||||
commentExpandBtnStyle := ToggleIcon(gtx, t.Theme, ie.commentExpandBtn, icons.NavigationExpandMore, icons.NavigationExpandLess, ie.expandCommentHint, ie.collapseCommentHint)
|
|
||||||
presetMenuBtnStyle := TipIcon(t.Theme, ie.presetMenuBtn, icons.NavigationMenu, "Load preset")
|
|
||||||
copyInstrumentBtnStyle := TipIcon(t.Theme, ie.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument")
|
|
||||||
saveInstrumentBtnStyle := TipIcon(t.Theme, ie.saveInstrumentBtn, icons.ContentSave, "Save instrument")
|
|
||||||
loadInstrumentBtnStyle := TipIcon(t.Theme, ie.loadInstrumentBtn, icons.FileFolderOpen, "Load instrument")
|
|
||||||
deleteInstrumentBtnStyle := ActionIcon(gtx, t.Theme, ie.deleteInstrumentBtn, icons.ActionDelete, ie.deleteInstrumentHint)
|
|
||||||
splitInstrumentBtnStyle := ActionIcon(gtx, t.Theme, ie.splitInstrumentBtn, icons.CommunicationCallSplit, ie.splitInstrumentHint)
|
|
||||||
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(t.Theme, &t.Theme.Menu.Text, &ie.presetMenu)
|
m := PopupMenu(t.Theme, &t.Theme.Menu.Text, &ie.presetMenu)
|
||||||
|
|
||||||
for ie.copyInstrumentBtn.Clickable.Clicked(gtx) {
|
for ie.copyInstrumentBtn.Clicked(gtx) {
|
||||||
if contents, ok := t.Instruments().List().CopyElements(); ok {
|
if contents, ok := t.Instruments().List().CopyElements(); ok {
|
||||||
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
|
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
|
||||||
t.Alerts().Add("Instrument copied to clipboard", tracker.Info)
|
t.Alerts().Add("Instrument copied to clipboard", tracker.Info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ie.saveInstrumentBtn.Clickable.Clicked(gtx) {
|
for ie.saveInstrumentBtn.Clicked(gtx) {
|
||||||
writer, err := t.Explorer.CreateFile(t.InstrumentName().Value() + ".yml")
|
writer, err := t.Explorer.CreateFile(t.InstrumentName().Value() + ".yml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@ -217,7 +207,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
|
|||||||
t.SaveInstrument(writer)
|
t.SaveInstrument(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ie.loadInstrumentBtn.Clickable.Clicked(gtx) {
|
for ie.loadInstrumentBtn.Clicked(gtx) {
|
||||||
reader, err := t.Explorer.ChooseFile(".yml", ".json", ".4ki", ".4kp")
|
reader, err := t.Explorer.ChooseFile(".yml", ".json", ".4ki", ".4kp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@ -225,6 +215,15 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
|
|||||||
t.LoadInstrument(reader)
|
t.LoadInstrument(reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splitInstrumentBtn := ActionIconBtn(t.SplitInstrument(), t.Theme, ie.splitInstrumentBtn, icons.CommunicationCallSplit, ie.splitInstrumentHint)
|
||||||
|
commentExpandedBtn := ToggleIconBtn(t.CommentExpanded(), t.Theme, ie.commentExpandBtn, icons.NavigationExpandMore, icons.NavigationExpandLess, ie.expandCommentHint, ie.collapseCommentHint)
|
||||||
|
soloBtn := ToggleIconBtn(t.Solo(), t.Theme, ie.soloBtn, icons.SocialGroup, icons.SocialPerson, ie.soloHint, ie.unsoloHint)
|
||||||
|
muteBtn := ToggleIconBtn(t.Mute(), t.Theme, ie.muteBtn, icons.AVVolumeUp, icons.AVVolumeOff, ie.muteHint, ie.unmuteHint)
|
||||||
|
saveInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.saveInstrumentBtn, icons.ContentSave, "Save instrument")
|
||||||
|
loadInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.loadInstrumentBtn, icons.FileFolderOpen, "Load instrument")
|
||||||
|
copyInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument")
|
||||||
|
deleteInstrumentBtn := ActionIconBtn(t.DeleteInstrument(), t.Theme, ie.deleteInstrumentBtn, icons.ActionDelete, ie.deleteInstrumentHint)
|
||||||
|
|
||||||
header := func(gtx C) D {
|
header := func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Rigid(layout.Spacer{Width: 6}.Layout),
|
layout.Rigid(layout.Spacer{Width: 6}.Layout),
|
||||||
@ -233,31 +232,32 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
|
|||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
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")
|
return t.InstrumentVoices.Layout(gtx, t.Model.InstrumentVoices(), t.Theme, &t.Theme.NumericUpDown, "Number of voices for this instrument")
|
||||||
}),
|
}),
|
||||||
layout.Rigid(splitInstrumentBtnStyle.Layout),
|
layout.Rigid(splitInstrumentBtn.Layout),
|
||||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
||||||
layout.Rigid(commentExpandBtnStyle.Layout),
|
layout.Rigid(commentExpandedBtn.Layout),
|
||||||
layout.Rigid(soloBtnStyle.Layout),
|
layout.Rigid(soloBtn.Layout),
|
||||||
layout.Rigid(muteBtnStyle.Layout),
|
layout.Rigid(muteBtn.Layout),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
//defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
presetBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, ie.presetMenuBtn, icons.NavigationMenu, "Load preset")
|
||||||
dims := presetMenuBtnStyle.Layout(gtx)
|
dims := presetBtn.Layout(gtx)
|
||||||
op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops)
|
op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops)
|
||||||
gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(500))
|
gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(500))
|
||||||
gtx.Constraints.Max.X = gtx.Dp(unit.Dp(180))
|
gtx.Constraints.Max.X = gtx.Dp(unit.Dp(180))
|
||||||
m.Layout(gtx, ie.presetMenuItems...)
|
m.Layout(gtx, ie.presetMenuItems...)
|
||||||
return dims
|
return dims
|
||||||
}),
|
}),
|
||||||
layout.Rigid(saveInstrumentBtnStyle.Layout),
|
layout.Rigid(saveInstrumentBtn.Layout),
|
||||||
layout.Rigid(loadInstrumentBtnStyle.Layout),
|
layout.Rigid(loadInstrumentBtn.Layout),
|
||||||
layout.Rigid(copyInstrumentBtnStyle.Layout),
|
layout.Rigid(copyInstrumentBtn.Layout),
|
||||||
layout.Rigid(deleteInstrumentBtnStyle.Layout))
|
layout.Rigid(deleteInstrumentBtn.Layout),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ie.presetMenuBtn.Clickable.Clicked(gtx) {
|
for ie.presetMenuBtn.Clicked(gtx) {
|
||||||
ie.presetMenu.Visible = true
|
ie.presetMenu.Visible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ie.commentExpandBtn.Bool.Value() || gtx.Source.Focused(ie.commentEditor) { // we draw once the widget after it manages to lose focus
|
if t.CommentExpanded().Value() || gtx.Source.Focused(ie.commentEditor) { // we draw once the widget after it manages to lose focus
|
||||||
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(header),
|
layout.Rigid(header),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
@ -356,12 +356,6 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
|
func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
|
||||||
// TODO: how to ie.unitDragList.Focus()
|
|
||||||
addUnitBtnStyle := ActionIcon(gtx, t.Theme, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)")
|
|
||||||
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
|
var units [256]tracker.UnitListItem
|
||||||
for i, item := range (*tracker.Units)(t.Model).Iterate {
|
for i, item := range (*tracker.Units)(t.Model).Iterate {
|
||||||
if i >= 256 {
|
if i >= 256 {
|
||||||
@ -482,8 +476,12 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
|
|||||||
return dims
|
return dims
|
||||||
}),
|
}),
|
||||||
layout.Stacked(func(gtx C) D {
|
layout.Stacked(func(gtx C) D {
|
||||||
|
for ie.addUnitBtn.Clicked(gtx) {
|
||||||
|
t.AddUnit(false).Do()
|
||||||
|
}
|
||||||
margin := layout.Inset{Right: unit.Dp(20), Bottom: unit.Dp(1)}
|
margin := layout.Inset{Right: unit.Dp(20), Bottom: unit.Dp(1)}
|
||||||
return margin.Layout(gtx, addUnitBtnStyle.Layout)
|
addUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Emphasis, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)")
|
||||||
|
return margin.Layout(gtx, addUnitBtn.Layout)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -263,7 +263,7 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
|||||||
case t.TrackEditor.scrollTable.Focused(gtx):
|
case t.TrackEditor.scrollTable.Focused(gtx):
|
||||||
t.OrderEditor.scrollTable.Focus()
|
t.OrderEditor.scrollTable.Focus()
|
||||||
case t.InstrumentEditor.Focused(gtx):
|
case t.InstrumentEditor.Focused(gtx):
|
||||||
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
if t.InstrEnlarged().Value() {
|
||||||
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
||||||
} else {
|
} else {
|
||||||
t.TrackEditor.scrollTable.Focus()
|
t.TrackEditor.scrollTable.Focus()
|
||||||
@ -280,7 +280,7 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
|||||||
case t.InstrumentEditor.Focused(gtx):
|
case t.InstrumentEditor.Focused(gtx):
|
||||||
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
||||||
default:
|
default:
|
||||||
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
if t.InstrEnlarged().Value() {
|
||||||
t.InstrumentEditor.Focus()
|
t.InstrumentEditor.Focus()
|
||||||
} else {
|
} else {
|
||||||
t.OrderEditor.scrollTable.Focus()
|
t.OrderEditor.scrollTable.Focus()
|
||||||
|
@ -168,9 +168,8 @@ func (tr *Tracker) layoutMenu(gtx C, title string, clickable *Clickable, menu *M
|
|||||||
m := PopupMenu(tr.Theme, &tr.Theme.Menu.Text, menu)
|
m := PopupMenu(tr.Theme, &tr.Theme.Menu.Text, menu)
|
||||||
return func(gtx C) D {
|
return func(gtx C) D {
|
||||||
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
|
||||||
titleBtn := Btn(tr.Theme, &tr.Theme.Button.Menu, clickable, title)
|
btn := Btn(tr.Theme, &tr.Theme.Button.Menu, clickable, title, "")
|
||||||
titleBtn.CornerRadius = unit.Dp(0)
|
dims := btn.Layout(gtx)
|
||||||
dims := titleBtn.Layout(gtx)
|
|
||||||
op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops)
|
op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops)
|
||||||
gtx.Constraints.Max.X = gtx.Dp(width)
|
gtx.Constraints.Max.X = gtx.Dp(width)
|
||||||
gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(300))
|
gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(300))
|
||||||
|
@ -52,18 +52,18 @@ func init() {
|
|||||||
|
|
||||||
type NoteEditor struct {
|
type NoteEditor struct {
|
||||||
TrackVoices *NumericUpDown
|
TrackVoices *NumericUpDown
|
||||||
NewTrackBtn *ActionClickable
|
NewTrackBtn *Clickable
|
||||||
DeleteTrackBtn *ActionClickable
|
DeleteTrackBtn *Clickable
|
||||||
SplitTrackBtn *ActionClickable
|
SplitTrackBtn *Clickable
|
||||||
|
|
||||||
AddSemitoneBtn *ActionClickable
|
AddSemitoneBtn *Clickable
|
||||||
SubtractSemitoneBtn *ActionClickable
|
SubtractSemitoneBtn *Clickable
|
||||||
AddOctaveBtn *ActionClickable
|
AddOctaveBtn *Clickable
|
||||||
SubtractOctaveBtn *ActionClickable
|
SubtractOctaveBtn *Clickable
|
||||||
NoteOffBtn *ActionClickable
|
NoteOffBtn *Clickable
|
||||||
EffectBtn *BoolClickable
|
EffectBtn *Clickable
|
||||||
UniqueBtn *BoolClickable
|
UniqueBtn *Clickable
|
||||||
TrackMidiInBtn *BoolClickable
|
TrackMidiInBtn *Clickable
|
||||||
|
|
||||||
scrollTable *ScrollTable
|
scrollTable *ScrollTable
|
||||||
eventFilters []event.Filter
|
eventFilters []event.Filter
|
||||||
@ -77,17 +77,17 @@ type NoteEditor struct {
|
|||||||
func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
||||||
ret := &NoteEditor{
|
ret := &NoteEditor{
|
||||||
TrackVoices: NewNumericUpDown(),
|
TrackVoices: NewNumericUpDown(),
|
||||||
NewTrackBtn: NewActionClickable(model.AddTrack()),
|
NewTrackBtn: new(Clickable),
|
||||||
DeleteTrackBtn: NewActionClickable(model.DeleteTrack()),
|
DeleteTrackBtn: new(Clickable),
|
||||||
SplitTrackBtn: NewActionClickable(model.SplitTrack()),
|
SplitTrackBtn: new(Clickable),
|
||||||
AddSemitoneBtn: NewActionClickable(model.AddSemitone()),
|
AddSemitoneBtn: new(Clickable),
|
||||||
SubtractSemitoneBtn: NewActionClickable(model.SubtractSemitone()),
|
SubtractSemitoneBtn: new(Clickable),
|
||||||
AddOctaveBtn: NewActionClickable(model.AddOctave()),
|
AddOctaveBtn: new(Clickable),
|
||||||
SubtractOctaveBtn: NewActionClickable(model.SubtractOctave()),
|
SubtractOctaveBtn: new(Clickable),
|
||||||
NoteOffBtn: NewActionClickable(model.EditNoteOff()),
|
NoteOffBtn: new(Clickable),
|
||||||
EffectBtn: NewBoolClickable(model.Effect()),
|
EffectBtn: new(Clickable),
|
||||||
UniqueBtn: NewBoolClickable(model.UniquePatterns()),
|
UniqueBtn: new(Clickable),
|
||||||
TrackMidiInBtn: NewBoolClickable(model.TrackMidiIn()),
|
TrackMidiInBtn: new(Clickable),
|
||||||
scrollTable: NewScrollTable(
|
scrollTable: NewScrollTable(
|
||||||
model.Notes().Table(),
|
model.Notes().Table(),
|
||||||
model.Tracks().List(),
|
model.Tracks().List(),
|
||||||
@ -164,42 +164,41 @@ func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions {
|
|||||||
|
|
||||||
func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
|
func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
|
||||||
return Surface{Gray: 37, Focus: te.scrollTable.Focused(gtx) || te.scrollTable.ChildFocused(gtx)}.Layout(gtx, func(gtx C) D {
|
return Surface{Gray: 37, Focus: te.scrollTable.Focused(gtx) || te.scrollTable.ChildFocused(gtx)}.Layout(gtx, func(gtx C) D {
|
||||||
addSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.AddSemitoneBtn, "+1")
|
addSemitoneBtn := ActionBtn(t.AddSemitone(), t.Theme, te.AddSemitoneBtn, "+1", "Add semitone")
|
||||||
subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.SubtractSemitoneBtn, "-1")
|
subtractSemitoneBtn := ActionBtn(t.SubtractSemitone(), t.Theme, te.SubtractSemitoneBtn, "-1", "Subtract semitone")
|
||||||
addOctaveBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.AddOctaveBtn, "+12")
|
addOctaveBtn := ActionBtn(t.AddOctave(), t.Theme, te.AddOctaveBtn, "+12", "Add octave")
|
||||||
subtractOctaveBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.SubtractOctaveBtn, "-12")
|
subtractOctaveBtn := ActionBtn(t.SubtractOctave(), t.Theme, te.SubtractOctaveBtn, "-12", "Subtract octave")
|
||||||
noteOffBtnStyle := ActionButton(gtx, t.Theme, &t.Theme.Button.Text, te.NoteOffBtn, "Note Off")
|
noteOffBtn := ActionBtn(t.EditNoteOff(), t.Theme, te.NoteOffBtn, "Note Off", "")
|
||||||
deleteTrackBtnStyle := ActionIcon(gtx, t.Theme, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint)
|
deleteTrackBtn := ActionIconBtn(t.DeleteTrack(), t.Theme, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint)
|
||||||
splitTrackBtnStyle := ActionIcon(gtx, t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint)
|
splitTrackBtn := ActionIconBtn(t.SplitTrack(), t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint)
|
||||||
newTrackBtnStyle := ActionIcon(gtx, t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint)
|
newTrackBtn := ActionIconBtn(t.AddTrack(), t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint)
|
||||||
in := layout.UniformInset(unit.Dp(1))
|
in := layout.UniformInset(unit.Dp(1))
|
||||||
voiceUpDown := func(gtx C) D {
|
voiceUpDown := func(gtx C) D {
|
||||||
return in.Layout(gtx, func(gtx C) D {
|
return in.Layout(gtx, func(gtx C) D {
|
||||||
return te.TrackVoices.Layout(gtx, t.Model.TrackVoices(), t.Theme, &t.Theme.NumericUpDown, "Track voices")
|
return te.TrackVoices.Layout(gtx, t.Model.TrackVoices(), t.Theme, &t.Theme.NumericUpDown, "Track voices")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex")
|
effectBtn := ToggleBtn(t.Effect(), t.Theme, te.EffectBtn, "Hex", "Input notes as hex values")
|
||||||
uniqueBtnStyle := ToggleIcon(gtx, t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip)
|
uniqueBtn := ToggleIconBtn(t.UniquePatterns(), t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip)
|
||||||
midiInBtnStyle := ToggleButton(gtx, t.Theme, te.TrackMidiInBtn, "MIDI")
|
midiInBtn := ToggleBtn(t.TrackMidiIn(), t.Theme, te.TrackMidiInBtn, "MIDI", "Input notes from MIDI keyboard")
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Rigid(func(gtx C) D { return layout.Dimensions{Size: image.Pt(gtx.Dp(unit.Dp(12)), 0)} }),
|
layout.Rigid(func(gtx C) D { return layout.Dimensions{Size: image.Pt(gtx.Dp(unit.Dp(12)), 0)} }),
|
||||||
layout.Rigid(addSemitoneBtnStyle.Layout),
|
layout.Rigid(addSemitoneBtn.Layout),
|
||||||
layout.Rigid(subtractSemitoneBtnStyle.Layout),
|
layout.Rigid(subtractSemitoneBtn.Layout),
|
||||||
layout.Rigid(addOctaveBtnStyle.Layout),
|
layout.Rigid(addOctaveBtn.Layout),
|
||||||
layout.Rigid(subtractOctaveBtnStyle.Layout),
|
layout.Rigid(subtractOctaveBtn.Layout),
|
||||||
layout.Rigid(noteOffBtnStyle.Layout),
|
layout.Rigid(noteOffBtn.Layout),
|
||||||
layout.Rigid(effectBtnStyle.Layout),
|
layout.Rigid(effectBtn.Layout),
|
||||||
layout.Rigid(uniqueBtnStyle.Layout),
|
layout.Rigid(uniqueBtn.Layout),
|
||||||
layout.Rigid(layout.Spacer{Width: 10}.Layout),
|
layout.Rigid(layout.Spacer{Width: 10}.Layout),
|
||||||
layout.Rigid(Label(t.Theme, &t.Theme.NoteEditor.Header, "Voices").Layout),
|
layout.Rigid(Label(t.Theme, &t.Theme.NoteEditor.Header, "Voices").Layout),
|
||||||
layout.Rigid(layout.Spacer{Width: 4}.Layout),
|
layout.Rigid(layout.Spacer{Width: 4}.Layout),
|
||||||
layout.Rigid(voiceUpDown),
|
layout.Rigid(voiceUpDown),
|
||||||
layout.Rigid(splitTrackBtnStyle.Layout),
|
layout.Rigid(splitTrackBtn.Layout),
|
||||||
|
layout.Rigid(midiInBtn.Layout),
|
||||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
||||||
layout.Rigid(midiInBtnStyle.Layout),
|
layout.Rigid(deleteTrackBtn.Layout),
|
||||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
layout.Rigid(newTrackBtn.Layout))
|
||||||
layout.Rigid(deleteTrackBtnStyle.Layout),
|
|
||||||
layout.Rigid(newTrackBtnStyle.Layout))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +279,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
|||||||
cursor := te.scrollTable.Table.Cursor()
|
cursor := te.scrollTable.Table.Cursor()
|
||||||
drawSelection := cursor != te.scrollTable.Table.Cursor2()
|
drawSelection := cursor != te.scrollTable.Table.Cursor2()
|
||||||
selection := te.scrollTable.Table.Range()
|
selection := te.scrollTable.Table.Range()
|
||||||
hasTrackMidiIn := te.TrackMidiInBtn.Bool.Value()
|
hasTrackMidiIn := t.Model.TrackMidiIn().Value()
|
||||||
|
|
||||||
patternNoOp := colorOp(gtx, t.Theme.NoteEditor.PatternNo.Color)
|
patternNoOp := colorOp(gtx, t.Theme.NoteEditor.PatternNo.Color)
|
||||||
uniqueOp := colorOp(gtx, t.Theme.NoteEditor.Unique.Color)
|
uniqueOp := colorOp(gtx, t.Theme.NoteEditor.Unique.Color)
|
||||||
|
@ -17,8 +17,8 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
OscilloscopeState struct {
|
OscilloscopeState struct {
|
||||||
onceBtn *BoolClickable
|
onceBtn *Clickable
|
||||||
wrapBtn *BoolClickable
|
wrapBtn *Clickable
|
||||||
lengthInBeatsNumber *NumericUpDown
|
lengthInBeatsNumber *NumericUpDown
|
||||||
triggerChannelNumber *NumericUpDown
|
triggerChannelNumber *NumericUpDown
|
||||||
xScale int
|
xScale int
|
||||||
@ -38,20 +38,20 @@ type (
|
|||||||
|
|
||||||
func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
|
func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
|
||||||
return &OscilloscopeState{
|
return &OscilloscopeState{
|
||||||
onceBtn: NewBoolClickable(model.SignalAnalyzer().Once()),
|
onceBtn: new(Clickable),
|
||||||
wrapBtn: NewBoolClickable(model.SignalAnalyzer().Wrap()),
|
wrapBtn: new(Clickable),
|
||||||
lengthInBeatsNumber: NewNumericUpDown(),
|
lengthInBeatsNumber: NewNumericUpDown(),
|
||||||
triggerChannelNumber: NewNumericUpDown(),
|
triggerChannelNumber: NewNumericUpDown(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, wave tracker.RingBuffer[[2]float32], th *Theme, st *OscilloscopeStyle) D {
|
func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, once, wrap tracker.Bool, wave tracker.RingBuffer[[2]float32], th *Theme, st *OscilloscopeStyle) D {
|
||||||
wrapBtnStyle := ToggleButton(gtx, th, s.wrapBtn, "Wrap")
|
|
||||||
onceBtnStyle := ToggleButton(gtx, th, s.onceBtn, "Once")
|
|
||||||
|
|
||||||
leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout
|
leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout
|
||||||
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
|
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
|
||||||
|
|
||||||
|
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,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Flexed(1, func(gtx C) D { return s.layoutWave(gtx, wave, th) }),
|
layout.Flexed(1, func(gtx C) D { return s.layoutWave(gtx, wave, th) }),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
@ -59,7 +59,7 @@ func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, wave tracker.
|
|||||||
layout.Rigid(leftSpacer),
|
layout.Rigid(leftSpacer),
|
||||||
layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Trigger").Layout),
|
layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Trigger").Layout),
|
||||||
layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
|
layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
|
||||||
layout.Rigid(onceBtnStyle.Layout),
|
layout.Rigid(onceBtn.Layout),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return s.triggerChannelNumber.Layout(gtx, vtrig, th, &th.NumericUpDown, "Trigger channel")
|
return s.triggerChannelNumber.Layout(gtx, vtrig, th, &th.NumericUpDown, "Trigger channel")
|
||||||
}),
|
}),
|
||||||
@ -71,7 +71,7 @@ func (s *OscilloscopeState) Layout(gtx C, vtrig, vlen tracker.Int, wave tracker.
|
|||||||
layout.Rigid(leftSpacer),
|
layout.Rigid(leftSpacer),
|
||||||
layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Buffer").Layout),
|
layout.Rigid(Label(th, &th.SongPanel.RowHeader, "Buffer").Layout),
|
||||||
layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
|
layout.Flexed(1, func(gtx C) D { return D{Size: gtx.Constraints.Min} }),
|
||||||
layout.Rigid(wrapBtnStyle.Layout),
|
layout.Rigid(wrapBtn.Layout),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return s.lengthInBeatsNumber.Layout(gtx, vlen, th, &th.NumericUpDown, "Buffer length in beats")
|
return s.lengthInBeatsNumber.Layout(gtx, vlen, th, &th.NumericUpDown, "Buffer length in beats")
|
||||||
}),
|
}),
|
||||||
|
@ -46,10 +46,10 @@ func NewSongPanel(model *tracker.Model) *SongPanel {
|
|||||||
SongLength: NewNumericUpDown(),
|
SongLength: NewNumericUpDown(),
|
||||||
Scope: NewOscilloscope(model),
|
Scope: NewOscilloscope(model),
|
||||||
MenuBar: NewMenuBar(model),
|
MenuBar: NewMenuBar(model),
|
||||||
PlayBar: NewPlayBar(model),
|
PlayBar: NewPlayBar(),
|
||||||
|
|
||||||
WeightingTypeBtn: &Clickable{},
|
WeightingTypeBtn: new(Clickable),
|
||||||
OversamplingBtn: &Clickable{},
|
OversamplingBtn: new(Clickable),
|
||||||
|
|
||||||
SongSettingsExpander: &Expander{Expanded: true},
|
SongSettingsExpander: &Expander{Expanded: true},
|
||||||
ScopeExpander: &Expander{},
|
ScopeExpander: &Expander{},
|
||||||
@ -75,7 +75,7 @@ func (s *SongPanel) Layout(gtx C, t *Tracker) D {
|
|||||||
return s.MenuBar.Layout(gtx, t)
|
return s.MenuBar.Layout(gtx, t)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return s.PlayBar.Layout(gtx, t.Theme)
|
return s.PlayBar.Layout(gtx, t)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
return s.layoutSongOptions(gtx, t)
|
return s.layoutSongOptions(gtx, t)
|
||||||
@ -98,13 +98,13 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D {
|
|||||||
weightingTxt = "No weight (RMS)"
|
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"
|
oversamplingTxt := "Sample peak"
|
||||||
if tr.Model.Oversampling().Value() {
|
if tr.Model.Oversampling().Value() {
|
||||||
oversamplingTxt = "True peak"
|
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,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
@ -201,7 +201,7 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D {
|
|||||||
}),
|
}),
|
||||||
layout.Flexed(1, func(gtx C) D {
|
layout.Flexed(1, func(gtx C) D {
|
||||||
return t.ScopeExpander.Layout(gtx, tr.Theme, "Oscilloscope", func(gtx C) D { return D{} }, func(gtx C) D {
|
return t.ScopeExpander.Layout(gtx, tr.Theme, "Oscilloscope", func(gtx C) D { return D{} }, func(gtx C) D {
|
||||||
return t.Scope.Layout(gtx, tr.Model.SignalAnalyzer().TriggerChannel(), tr.Model.SignalAnalyzer().LengthInBeats(), tr.Model.SignalAnalyzer().Waveform(), tr.Theme, &tr.Theme.Oscilloscope)
|
return t.Scope.Layout(gtx, tr.Model.SignalAnalyzer().TriggerChannel(), tr.Model.SignalAnalyzer().LengthInBeats(), tr.Model.SignalAnalyzer().Once(), tr.Model.SignalAnalyzer().Wrap(), tr.Model.SignalAnalyzer().Waveform(), tr.Theme, &tr.Theme.Oscilloscope)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
layout.Rigid(Label(tr.Theme, &tr.Theme.SongPanel.Version, version.VersionOrHash).Layout),
|
layout.Rigid(Label(tr.Theme, &tr.Theme.SongPanel.Version, version.VersionOrHash).Layout),
|
||||||
@ -304,14 +304,14 @@ type MenuBar struct {
|
|||||||
midiMenuItems []MenuItem
|
midiMenuItems []MenuItem
|
||||||
|
|
||||||
panicHint string
|
panicHint string
|
||||||
PanicBtn *BoolClickable
|
PanicBtn *Clickable
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMenuBar(model *tracker.Model) *MenuBar {
|
func NewMenuBar(model *tracker.Model) *MenuBar {
|
||||||
ret := &MenuBar{
|
ret := &MenuBar{
|
||||||
Clickables: make([]Clickable, 3),
|
Clickables: make([]Clickable, 3),
|
||||||
Menus: make([]Menu, 3),
|
Menus: make([]Menu, 3),
|
||||||
PanicBtn: NewBoolClickable(model.Panic()),
|
PanicBtn: new(Clickable),
|
||||||
panicHint: makeHint("Panic", " (%s)", "PanicToggle"),
|
panicHint: makeHint("Panic", " (%s)", "PanicToggle"),
|
||||||
}
|
}
|
||||||
ret.fileMenuItems = []MenuItem{
|
ret.fileMenuItems = []MenuItem{
|
||||||
@ -343,15 +343,15 @@ func (t *MenuBar) Layout(gtx C, tr *Tracker) D {
|
|||||||
gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(36))
|
gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(36))
|
||||||
gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36))
|
gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36))
|
||||||
|
|
||||||
panicBtnStyle := ToggleIcon(gtx, 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 t.PanicBtn.Bool.Value() {
|
if tr.Panic().Value() {
|
||||||
panicBtnStyle.IconButtonStyle.Color = tr.Theme.SongPanel.ErrorColor
|
panicBtn.Style = &tr.Theme.IconButton.Error
|
||||||
}
|
}
|
||||||
flex := layout.Flex{Axis: layout.Horizontal, Alignment: layout.End}
|
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...))
|
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...))
|
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...))
|
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.Layout) })
|
panicFC := layout.Flexed(1, func(gtx C) D { return layout.E.Layout(gtx, panicBtn.Layout) })
|
||||||
if len(t.midiMenuItems) > 0 {
|
if len(t.midiMenuItems) > 0 {
|
||||||
return flex.Layout(gtx, fileFC, editFC, midiFC, panicFC)
|
return flex.Layout(gtx, fileFC, editFC, midiFC, panicFC)
|
||||||
}
|
}
|
||||||
@ -359,11 +359,11 @@ func (t *MenuBar) Layout(gtx C, tr *Tracker) D {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PlayBar struct {
|
type PlayBar struct {
|
||||||
RewindBtn *ActionClickable
|
RewindBtn *Clickable
|
||||||
PlayingBtn *BoolClickable
|
PlayingBtn *Clickable
|
||||||
RecordBtn *BoolClickable
|
RecordBtn *Clickable
|
||||||
FollowBtn *BoolClickable
|
FollowBtn *Clickable
|
||||||
LoopBtn *BoolClickable
|
LoopBtn *Clickable
|
||||||
// Hints
|
// Hints
|
||||||
rewindHint string
|
rewindHint string
|
||||||
playHint, stopHint string
|
playHint, stopHint string
|
||||||
@ -372,13 +372,13 @@ type PlayBar struct {
|
|||||||
loopOffHint, loopOnHint string
|
loopOffHint, loopOnHint string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlayBar(model *tracker.Model) *PlayBar {
|
func NewPlayBar() *PlayBar {
|
||||||
ret := &PlayBar{
|
ret := &PlayBar{
|
||||||
LoopBtn: NewBoolClickable(model.LoopToggle()),
|
LoopBtn: new(Clickable),
|
||||||
RecordBtn: NewBoolClickable(model.IsRecording()),
|
RecordBtn: new(Clickable),
|
||||||
FollowBtn: NewBoolClickable(model.Follow()),
|
FollowBtn: new(Clickable),
|
||||||
PlayingBtn: NewBoolClickable(model.Playing()),
|
PlayingBtn: new(Clickable),
|
||||||
RewindBtn: NewActionClickable(model.PlaySongStart()),
|
RewindBtn: new(Clickable),
|
||||||
}
|
}
|
||||||
ret.rewindHint = makeHint("Rewind", "\n(%s)", "PlaySongStartUnfollow")
|
ret.rewindHint = makeHint("Rewind", "\n(%s)", "PlaySongStartUnfollow")
|
||||||
ret.playHint = makeHint("Play", " (%s)", "PlayCurrentPosUnfollow")
|
ret.playHint = makeHint("Play", " (%s)", "PlayCurrentPosUnfollow")
|
||||||
@ -392,20 +392,20 @@ func NewPlayBar(model *tracker.Model) *PlayBar {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pb *PlayBar) Layout(gtx C, th *Theme) D {
|
func (pb *PlayBar) Layout(gtx C, tr *Tracker) D {
|
||||||
rewindBtnStyle := ActionIcon(gtx, th, pb.RewindBtn, icons.AVFastRewind, pb.rewindHint)
|
playBtn := ToggleIconBtn(tr.Playing(), tr.Theme, pb.PlayingBtn, icons.AVPlayArrow, icons.AVStop, pb.playHint, pb.stopHint)
|
||||||
playBtnStyle := ToggleIcon(gtx, th, pb.PlayingBtn, icons.AVPlayArrow, icons.AVStop, pb.playHint, pb.stopHint)
|
rewindBtn := ActionIconBtn(tr.PlaySongStart(), tr.Theme, pb.RewindBtn, icons.AVFastRewind, pb.rewindHint)
|
||||||
recordBtnStyle := ToggleIcon(gtx, th, pb.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, pb.recordHint, pb.stopRecordHint)
|
recordBtn := ToggleIconBtn(tr.IsRecording(), tr.Theme, pb.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, pb.recordHint, pb.stopRecordHint)
|
||||||
noteTrackBtnStyle := ToggleIcon(gtx, th, pb.FollowBtn, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, pb.followOffHint, pb.followOnHint)
|
followBtn := ToggleIconBtn(tr.Follow(), tr.Theme, pb.FollowBtn, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, pb.followOffHint, pb.followOnHint)
|
||||||
loopBtnStyle := ToggleIcon(gtx, th, pb.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, pb.loopOffHint, pb.loopOnHint)
|
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 Surface{Gray: 37}.Layout(gtx, func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Flexed(1, playBtnStyle.Layout),
|
layout.Flexed(1, playBtn.Layout),
|
||||||
layout.Rigid(rewindBtnStyle.Layout),
|
layout.Rigid(rewindBtn.Layout),
|
||||||
layout.Rigid(recordBtnStyle.Layout),
|
layout.Rigid(recordBtn.Layout),
|
||||||
layout.Rigid(noteTrackBtnStyle.Layout),
|
layout.Rigid(followBtn.Layout),
|
||||||
layout.Rigid(loopBtnStyle.Layout),
|
layout.Rigid(loopBtn.Layout),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,12 @@ type Theme struct {
|
|||||||
Disabled ButtonStyle
|
Disabled ButtonStyle
|
||||||
Menu ButtonStyle
|
Menu ButtonStyle
|
||||||
}
|
}
|
||||||
|
IconButton struct {
|
||||||
|
Enabled IconButtonStyle
|
||||||
|
Disabled IconButtonStyle
|
||||||
|
Emphasis IconButtonStyle
|
||||||
|
Error IconButtonStyle
|
||||||
|
}
|
||||||
Oscilloscope OscilloscopeStyle
|
Oscilloscope OscilloscopeStyle
|
||||||
NumericUpDown NumericUpDownStyle
|
NumericUpDown NumericUpDownStyle
|
||||||
DialogTitle LabelStyle
|
DialogTitle LabelStyle
|
||||||
|
@ -54,6 +54,24 @@ button:
|
|||||||
cornerradius: 0
|
cornerradius: 0
|
||||||
height: *buttonheight
|
height: *buttonheight
|
||||||
inset: *buttoninset
|
inset: *buttoninset
|
||||||
|
iconbutton:
|
||||||
|
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:
|
oscilloscope:
|
||||||
curvecolors: [*primarycolor, *secondarycolor]
|
curvecolors: [*primarycolor, *secondarycolor]
|
||||||
limitcolor: { r: 255, g: 255, b: 255, a: 8 }
|
limitcolor: { r: 255, g: 255, b: 255, a: 8 }
|
||||||
|
@ -210,7 +210,7 @@ func (t *Tracker) Layout(gtx layout.Context, w *app.Window) {
|
|||||||
paint.Fill(gtx.Ops, t.Theme.Material.Bg)
|
paint.Fill(gtx.Ops, t.Theme.Material.Bg)
|
||||||
event.Op(gtx.Ops, t) // area for capturing scroll events
|
event.Op(gtx.Ops, t) // area for capturing scroll events
|
||||||
|
|
||||||
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
if t.InstrEnlarged().Value() {
|
||||||
t.layoutTop(gtx)
|
t.layoutTop(gtx)
|
||||||
} else {
|
} else {
|
||||||
t.VerticalSplit.Layout(gtx,
|
t.VerticalSplit.Layout(gtx,
|
||||||
|
@ -30,10 +30,10 @@ type UnitEditor struct {
|
|||||||
sliderList *DragList
|
sliderList *DragList
|
||||||
searchList *DragList
|
searchList *DragList
|
||||||
Parameters []*ParameterWidget
|
Parameters []*ParameterWidget
|
||||||
DeleteUnitBtn *ActionClickable
|
DeleteUnitBtn *Clickable
|
||||||
CopyUnitBtn *TipClickable
|
CopyUnitBtn *Clickable
|
||||||
ClearUnitBtn *ActionClickable
|
ClearUnitBtn *Clickable
|
||||||
DisableUnitBtn *BoolClickable
|
DisableUnitBtn *Clickable
|
||||||
SelectTypeBtn *Clickable
|
SelectTypeBtn *Clickable
|
||||||
commentEditor *Editor
|
commentEditor *Editor
|
||||||
caser cases.Caser
|
caser cases.Caser
|
||||||
@ -45,10 +45,10 @@ type UnitEditor struct {
|
|||||||
|
|
||||||
func NewUnitEditor(m *tracker.Model) *UnitEditor {
|
func NewUnitEditor(m *tracker.Model) *UnitEditor {
|
||||||
ret := &UnitEditor{
|
ret := &UnitEditor{
|
||||||
DeleteUnitBtn: NewActionClickable(m.DeleteUnit()),
|
DeleteUnitBtn: new(Clickable),
|
||||||
ClearUnitBtn: NewActionClickable(m.ClearUnit()),
|
ClearUnitBtn: new(Clickable),
|
||||||
DisableUnitBtn: NewBoolClickable(m.UnitDisabled()),
|
DisableUnitBtn: new(Clickable),
|
||||||
CopyUnitBtn: new(TipClickable),
|
CopyUnitBtn: new(Clickable),
|
||||||
SelectTypeBtn: new(Clickable),
|
SelectTypeBtn: new(Clickable),
|
||||||
commentEditor: NewEditor(true, true, text.Start),
|
commentEditor: NewEditor(true, true, text.Start),
|
||||||
sliderList: NewDragList(m.Params().List(), layout.Vertical),
|
sliderList: NewDragList(m.Params().List(), layout.Vertical),
|
||||||
@ -127,15 +127,12 @@ func (pe *UnitEditor) layoutSliders(gtx C, t *Tracker) D {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
|
func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
|
||||||
for pe.CopyUnitBtn.Clickable.Clicked(gtx) {
|
for pe.CopyUnitBtn.Clicked(gtx) {
|
||||||
if contents, ok := t.Units().List().CopyElements(); ok {
|
if contents, ok := t.Units().List().CopyElements(); ok {
|
||||||
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
|
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
|
||||||
t.Alerts().Add("Unit copied to clipboard", tracker.Info)
|
t.Alerts().Add("Unit copied to clipboard", tracker.Info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
copyUnitBtnStyle := TipIcon(t.Theme, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint)
|
|
||||||
deleteUnitBtnStyle := ActionIcon(gtx, t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
|
|
||||||
disableUnitBtnStyle := ToggleIcon(gtx, t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint)
|
|
||||||
text := t.Units().SelectedType()
|
text := t.Units().SelectedType()
|
||||||
if text == "" {
|
if text == "" {
|
||||||
text = "Choose unit type"
|
text = "Choose unit type"
|
||||||
@ -143,15 +140,19 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
|
|||||||
text = pe.caser.String(text)
|
text = pe.caser.String(text)
|
||||||
}
|
}
|
||||||
hintText := Label(t.Theme, &t.Theme.UnitEditor.Hint, text)
|
hintText := Label(t.Theme, &t.Theme.UnitEditor.Hint, text)
|
||||||
|
deleteUnitBtn := ActionIconBtn(t.DeleteUnit(), t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
|
||||||
|
copyUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint)
|
||||||
|
disableUnitBtn := 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,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Rigid(deleteUnitBtnStyle.Layout),
|
layout.Rigid(deleteUnitBtn.Layout),
|
||||||
layout.Rigid(copyUnitBtnStyle.Layout),
|
layout.Rigid(copyUnitBtn.Layout),
|
||||||
layout.Rigid(disableUnitBtnStyle.Layout),
|
layout.Rigid(disableUnitBtn.Layout),
|
||||||
layout.Rigid(func(gtx C) D {
|
layout.Rigid(func(gtx C) D {
|
||||||
var dims D
|
var dims D
|
||||||
if t.Units().SelectedType() != "" {
|
if t.Units().SelectedType() != "" {
|
||||||
clearUnitBtnStyle := ActionIcon(gtx, t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit")
|
clearUnitBtn := ActionIconBtn(t.ClearUnit(), t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit")
|
||||||
dims = clearUnitBtnStyle.Layout(gtx)
|
dims = clearUnitBtn.Layout(gtx)
|
||||||
}
|
}
|
||||||
return D{Size: image.Pt(gtx.Dp(unit.Dp(48)), dims.Size.Y)}
|
return D{Size: image.Pt(gtx.Dp(unit.Dp(48)), dims.Size.Y)}
|
||||||
}),
|
}),
|
||||||
|
Reference in New Issue
Block a user