refactor(gioui): update gioui to v0.5.0

This commit is contained in:
5684185+vsariola@users.noreply.github.com 2024-03-01 22:11:44 +02:00
parent 267973e061
commit 1c020fffa3
19 changed files with 662 additions and 354 deletions

10
go.mod
View File

@ -3,12 +3,12 @@ module github.com/vsariola/sointu
go 1.21
require (
gioui.org v0.3.1
gioui.org/x v0.1.0
gioui.org v0.5.0
gioui.org/x v0.5.0
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/hajimehoshi/oto v0.6.6
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
golang.org/x/text v0.9.0
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
pipelined.dev/audio/vst2 v0.10.1-0.20240223162706-41e9b65fb5c2
@ -29,10 +29,10 @@ require (
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/image v0.7.0 // indirect
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/sys v0.12.0 // indirect
pipelined.dev/pipe v0.11.0 // indirect
pipelined.dev/signal v0.10.0 // indirect
)

20
go.sum
View File

@ -1,13 +1,14 @@
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
gioui.org v0.3.1 h1:hslYkrkIWvx28Mxe3A87opl+8s9mnWsnWmPDh11+zco=
gioui.org v0.3.1/go.mod h1:2atiYR4upH71/6ehnh6XsUELa7JZOrOHHNMDxGBZF0Q=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
gioui.org v0.5.0 h1:07g7/LY1MFuTncfO4A5DIKMMsQV6PkPHyx0MhDqgmYY=
gioui.org v0.5.0/go.mod h1:2atiYR4upH71/6ehnh6XsUELa7JZOrOHHNMDxGBZF0Q=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
gioui.org/x v0.1.0 h1:CvphvaQSroRaNEZ+JbXBkV3J3klA76U3JpieyEwHFX4=
gioui.org/x v0.1.0/go.mod h1:5qZxjtK/TVznMlcEOyn8OheiCZlArxF3IKnLqSehKXQ=
gioui.org/x v0.5.0 h1:NVKTn5AZuYhkAnF7MYcy1dIes36+U1N4gUTsgBhfr4A=
gioui.org/x v0.5.0/go.mod h1:X4UBhvanAN+8S16L3K6jDMrVo7Dii7NptgBpOLBD7E4=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -22,6 +23,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
@ -49,8 +51,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5U
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@ -84,8 +86,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -110,8 +112,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
pipelined.dev/audio/vst2 v0.10.1-0.20231016195025-8c5c6a64c826 h1:4c7O6PJ/Zl677O2VhXHUZK7LJyVBhUI7Q39+ri+gKUs=
pipelined.dev/audio/vst2 v0.10.1-0.20231016195025-8c5c6a64c826/go.mod h1:wETLxsbBPftj6t4iVBCXvH/Xgd27ZgIC4hNnHDYNuz8=
pipelined.dev/audio/vst2 v0.10.1-0.20240223162706-41e9b65fb5c2 h1:qrI7YY5ZH4pJflMfzum2TKvA1NaX+H4feaA6jweX2R8=
pipelined.dev/audio/vst2 v0.10.1-0.20240223162706-41e9b65fb5c2/go.mod h1:wETLxsbBPftj6t4iVBCXvH/Xgd27ZgIC4hNnHDYNuz8=
pipelined.dev/pipe v0.10.0/go.mod h1:aIt+NPlW0QLYByqYniG77lTxSvl7OtCNLws/m+Xz5ww=

View File

@ -51,9 +51,9 @@ func Tooltip(th *material.Theme, tip string) component.Tooltip {
return tooltip
}
func ActionIcon(th *material.Theme, w *ActionClickable, icon []byte, tip string) TipIconButtonStyle {
func ActionIcon(gtx C, th *material.Theme, w *ActionClickable, icon []byte, tip string) TipIconButtonStyle {
ret := TipIcon(th, &w.TipClickable, icon, tip)
for w.Clickable.Clicked() {
for w.Clickable.Clicked(gtx) {
w.Action.Do()
}
if !w.Action.Allowed() {
@ -74,14 +74,14 @@ func TipIcon(th *material.Theme, w *TipClickable, icon []byte, tip string) TipIc
}
}
func ToggleIcon(th *material.Theme, w *BoolClickable, offIcon, onIcon []byte, offTip, onTip string) TipIconButtonStyle {
func ToggleIcon(gtx C, th *material.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() {
for w.Clickable.Clicked(gtx) {
w.Bool.Toggle()
}
ibStyle := material.IconButton(th, &w.Clickable, widgetForIcon(icon), "")
@ -102,8 +102,8 @@ func (t *TipIconButtonStyle) Layout(gtx C) D {
return t.TipArea.Layout(gtx, t.Tooltip, t.IconButtonStyle.Layout)
}
func ActionButton(th *material.Theme, w *ActionClickable, text string) material.ButtonStyle {
for w.Clickable.Clicked() {
func ActionButton(gtx C, th *material.Theme, w *ActionClickable, text string) material.ButtonStyle {
for w.Clickable.Clicked(gtx) {
w.Action.Do()
}
ret := material.Button(th, &w.Clickable, text)
@ -116,8 +116,8 @@ func ActionButton(th *material.Theme, w *ActionClickable, text string) material.
return ret
}
func ToggleButton(th *material.Theme, b *BoolClickable, text string) material.ButtonStyle {
for b.Clickable.Clicked() {
func ToggleButton(gtx C, th *material.Theme, b *BoolClickable, text string) material.ButtonStyle {
for b.Clickable.Clicked(gtx) {
b.Bool.Toggle()
}
ret := material.Button(th, &b.Clickable, text)

View File

@ -1,6 +1,7 @@
package gioui
import (
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/layout"
"gioui.org/op/paint"
@ -11,10 +12,11 @@ import (
)
type Dialog struct {
BtnAlt *ActionClickable
BtnOk *ActionClickable
BtnCancel *ActionClickable
tag bool
BtnAlt *ActionClickable
BtnOk *ActionClickable
BtnCancel *ActionClickable
tag bool
keyFilters []event.Filter
}
type DialogStyle struct {
@ -30,45 +32,77 @@ type DialogStyle struct {
}
func NewDialog(ok, alt, cancel tracker.Action) *Dialog {
return &Dialog{
ret := &Dialog{
BtnOk: NewActionClickable(ok),
BtnAlt: NewActionClickable(alt),
BtnCancel: NewActionClickable(cancel),
}
return ret
}
func ConfirmDialog(th *material.Theme, dialog *Dialog, title, text string) DialogStyle {
func ConfirmDialog(gtx C, th *material.Theme, dialog *Dialog, title, text string) DialogStyle {
ret := DialogStyle{
dialog: dialog,
Title: title,
Text: text,
Inset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12), Left: unit.Dp(20), Right: unit.Dp(20)},
TextInset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12)},
AltStyle: ActionButton(th, dialog.BtnAlt, "Alt"),
OkStyle: ActionButton(th, dialog.BtnOk, "Ok"),
CancelStyle: ActionButton(th, dialog.BtnCancel, "Cancel"),
AltStyle: ActionButton(gtx, th, dialog.BtnAlt, "Alt"),
OkStyle: ActionButton(gtx, th, dialog.BtnOk, "Ok"),
CancelStyle: ActionButton(gtx, th, dialog.BtnCancel, "Cancel"),
Shaper: th.Shaper,
}
return ret
}
func (d *DialogStyle) Layout(gtx C) D {
if !d.dialog.BtnOk.Clickable.Focused() && !d.dialog.BtnCancel.Clickable.Focused() && !d.dialog.BtnAlt.Clickable.Focused() {
d.dialog.BtnCancel.Clickable.Focus()
func (d *Dialog) handleKeysForButton(gtx C, btn, next, prev *ActionClickable) {
for {
e, ok := gtx.Event(
key.Filter{Focus: &btn.Clickable, Name: key.NameLeftArrow},
key.Filter{Focus: &btn.Clickable, Name: key.NameRightArrow},
key.Filter{Focus: &btn.Clickable, Name: key.NameEscape},
key.Filter{Focus: &btn.Clickable, Name: key.NameTab, Optional: key.ModShift},
)
if !ok {
break
}
if e, ok := e.(key.Event); ok && e.State == key.Press {
switch {
case e.Name == key.NameLeftArrow || (e.Name == key.NameTab && e.Modifiers.Contain(key.ModShift)):
gtx.Execute(key.FocusCmd{Tag: &prev.Clickable})
case e.Name == key.NameRightArrow || (e.Name == key.NameTab && !e.Modifiers.Contain(key.ModShift)):
gtx.Execute(key.FocusCmd{Tag: &next.Clickable})
case e.Name == key.NameEscape:
d.BtnCancel.Action.Do()
}
}
}
}
func (d *Dialog) handleKeys(gtx C) {
if d.BtnAlt.Action.Allowed() {
d.handleKeysForButton(gtx, d.BtnAlt, d.BtnCancel, d.BtnOk)
d.handleKeysForButton(gtx, d.BtnCancel, d.BtnOk, d.BtnAlt)
d.handleKeysForButton(gtx, d.BtnOk, d.BtnAlt, d.BtnCancel)
} else {
d.handleKeysForButton(gtx, d.BtnOk, d.BtnCancel, d.BtnCancel)
d.handleKeysForButton(gtx, d.BtnCancel, d.BtnOk, d.BtnOk)
}
}
func (d *DialogStyle) Layout(gtx C) D {
if !gtx.Source.Focused(&d.dialog.BtnOk.Clickable) && !gtx.Source.Focused(&d.dialog.BtnCancel.Clickable) && !gtx.Source.Focused(&d.dialog.BtnAlt.Clickable) {
gtx.Execute(key.FocusCmd{Tag: &d.dialog.BtnCancel.Clickable})
}
d.dialog.handleKeys(gtx)
paint.Fill(gtx.Ops, dialogBgColor)
text := func(gtx C) D {
return d.TextInset.Layout(gtx, LabelStyle{Text: d.Text, Color: highEmphasisTextColor, Font: labelDefaultFont, FontSize: unit.Sp(14), Shaper: d.Shaper}.Layout)
}
for _, e := range gtx.Events(&d.dialog.tag) {
if e, ok := e.(key.Event); ok && e.State == key.Press {
d.command(e)
}
}
visible := true
return layout.Center.Layout(gtx, func(gtx C) D {
return Popup(&visible).Layout(gtx, func(gtx C) D {
key.InputOp{Tag: &d.dialog.tag, Keys: "⎋|←|→|Tab"}.Add(gtx.Ops)
return d.Inset.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(Label(d.Title, highEmphasisTextColor, d.Shaper)),
@ -94,36 +128,3 @@ func (d *DialogStyle) Layout(gtx C) D {
})
})
}
func (d *DialogStyle) command(e key.Event) {
switch e.Name {
case key.NameEscape:
d.dialog.BtnCancel.Action.Do()
case key.NameLeftArrow:
switch {
case d.dialog.BtnOk.Clickable.Focused():
d.dialog.BtnCancel.Clickable.Focus()
case d.dialog.BtnCancel.Clickable.Focused():
if d.dialog.BtnAlt.Action.Allowed() {
d.dialog.BtnAlt.Clickable.Focus()
} else {
d.dialog.BtnOk.Clickable.Focus()
}
case d.dialog.BtnAlt.Clickable.Focused():
d.dialog.BtnOk.Clickable.Focus()
}
case key.NameRightArrow, key.NameTab:
switch {
case d.dialog.BtnOk.Clickable.Focused():
if d.dialog.BtnAlt.Action.Allowed() {
d.dialog.BtnAlt.Clickable.Focus()
} else {
d.dialog.BtnCancel.Clickable.Focus()
}
case d.dialog.BtnCancel.Clickable.Focused():
d.dialog.BtnOk.Clickable.Focus()
case d.dialog.BtnAlt.Clickable.Focused():
d.dialog.BtnCancel.Clickable.Focus()
}
}
}

View File

@ -1,12 +1,16 @@
package gioui
import (
"bytes"
"image"
"image/color"
"io"
"gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/transfer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
@ -27,7 +31,6 @@ type DragList struct {
swapped bool
focused bool
requestFocus bool
mainTag bool
}
type FilledDragListStyle struct {
@ -72,11 +75,7 @@ func (s FilledDragListStyle) Layout(gtx C) D {
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
keys := key.Set("↑|↓|Ctrl-↑|Ctrl-↓|Shift-↑|Shift-↓|⇞|⇟|Ctrl-⇞|Ctrl-⇟|Ctrl-A|Ctrl-C|Ctrl-X|Ctrl-V|⌦|Ctrl-⌫")
if s.dragList.List.Axis == layout.Horizontal {
keys = key.Set("←|→|Ctrl-←|Ctrl-→|Shift-←|Shift-→|Home|End|Ctrl-Home|Ctrl-End|Ctrl-A|Ctrl-C|Ctrl-X|Ctrl-V|⌦|Ctrl-⌫")
}
key.InputOp{Tag: &s.dragList.mainTag, Keys: keys}.Add(gtx.Ops)
event.Op(gtx.Ops, s.dragList)
if s.dragList.List.Axis == layout.Horizontal {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
@ -86,11 +85,39 @@ func (s FilledDragListStyle) Layout(gtx C) D {
if s.dragList.requestFocus {
s.dragList.requestFocus = false
key.FocusOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
gtx.Execute(key.FocusCmd{Tag: s.dragList})
}
for _, ke := range gtx.Events(&s.dragList.mainTag) {
switch ke := ke.(type) {
prevKey := key.NameUpArrow
nextKey := key.NameDownArrow
firstKey := key.NamePageUp
lastKey := key.NamePageDown
if s.dragList.List.Axis == layout.Horizontal {
prevKey = key.NameLeftArrow
nextKey = key.NameRightArrow
firstKey = key.NameHome
lastKey = key.NameEnd
}
for {
event, ok := gtx.Event(
key.FocusFilter{Target: s.dragList},
transfer.TargetFilter{Target: s.dragList, Type: "application/text"},
key.Filter{Focus: s.dragList, Name: prevKey, Optional: key.ModShift | key.ModShortcut},
key.Filter{Focus: s.dragList, Name: nextKey, Optional: key.ModShift | key.ModShortcut},
key.Filter{Focus: s.dragList, Name: firstKey, Optional: key.ModShift | key.ModShortcut},
key.Filter{Focus: s.dragList, Name: lastKey, Optional: key.ModShift | key.ModShortcut},
key.Filter{Focus: s.dragList, Name: "A", Required: key.ModShortcut},
key.Filter{Focus: s.dragList, Name: "C", Required: key.ModShortcut},
key.Filter{Focus: s.dragList, Name: "X", Required: key.ModShortcut},
key.Filter{Focus: s.dragList, Name: "V", Required: key.ModShortcut},
key.Filter{Focus: s.dragList, Name: key.NameDeleteBackward, Required: key.ModShortcut},
key.Filter{Focus: s.dragList, Name: key.NameDeleteForward},
)
if !ok {
break
}
switch ke := event.(type) {
case key.FocusEvent:
s.dragList.focused = ke.Focus
if !s.dragList.focused {
@ -101,10 +128,13 @@ func (s FilledDragListStyle) Layout(gtx C) D {
break
}
s.dragList.command(gtx, ke)
case clipboard.Event:
s.dragList.TrackerList.PasteElements([]byte(ke.Text))
case transfer.DataEvent:
if b, err := io.ReadAll(ke.Open()); err == nil {
s.dragList.TrackerList.PasteElements([]byte(b))
}
}
op.InvalidateOp{}.Add(gtx.Ops)
gtx.Execute(op.InvalidateCmd{})
}
_, isMutable := s.dragList.TrackerList.ListData.(tracker.MutableListData)
@ -128,12 +158,19 @@ func (s FilledDragListStyle) Layout(gtx C) D {
}
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
for _, ev := range gtx.Events(&s.dragList.tags[index]) {
for {
ev, ok := gtx.Event(pointer.Filter{
Target: &s.dragList.tags[index],
Kinds: pointer.Press | pointer.Enter | pointer.Leave,
})
if !ok {
break
}
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
switch e.Kind {
case pointer.Enter:
s.dragList.HoverItem = index
case pointer.Leave:
@ -148,22 +185,28 @@ func (s FilledDragListStyle) Layout(gtx C) D {
if !e.Modifiers.Contain(key.ModShift) {
s.dragList.TrackerList.SetSelected2(index)
}
key.FocusOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
gtx.Execute(key.FocusCmd{Tag: s.dragList})
}
}
rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area := clip.Rect(rect).Push(gtx.Ops)
pointer.InputOp{Tag: &s.dragList.tags[index],
Types: pointer.Press | pointer.Enter | pointer.Leave,
}.Add(gtx.Ops)
event.Op(gtx.Ops, &s.dragList.tags[index])
area.Pop()
if index == s.dragList.TrackerList.Selected() && isMutable {
for _, ev := range gtx.Events(&s.dragList.focused) {
for {
target := &s.dragList.focused
if s.dragList.drag {
target = nil
}
ev, ok := gtx.Event(pointer.Filter{Target: target, Kinds: pointer.Drag | pointer.Press | pointer.Release | pointer.Cancel})
if !ok {
break
}
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
switch e.Kind {
case pointer.Press:
s.dragList.dragID = e.PointerID
s.dragList.drag = true
@ -186,17 +229,12 @@ func (s FilledDragListStyle) Layout(gtx C) D {
swap = 1
}
}
case pointer.Release:
fallthrough
case pointer.Cancel:
case pointer.Release, pointer.Cancel:
s.dragList.drag = false
}
}
area := clip.Rect(rect).Push(gtx.Ops)
pointer.InputOp{Tag: &s.dragList.focused,
Types: pointer.Drag | pointer.Press | pointer.Release,
Grab: s.dragList.drag,
}.Add(gtx.Ops)
event.Op(gtx.Ops, &s.dragList.focused)
pointer.CursorGrab.Add(gtx.Ops)
area.Pop()
}
@ -225,7 +263,7 @@ func (s FilledDragListStyle) Layout(gtx C) D {
dims := s.dragList.List.Layout(gtx, count, listElem)
if !s.dragList.swapped && swap != 0 {
if s.dragList.TrackerList.MoveElements(swap) {
op.InvalidateOp{}.Add(gtx.Ops)
gtx.Execute(op.InvalidateCmd{})
}
s.dragList.swapped = true
} else {
@ -238,12 +276,12 @@ func (e *DragList) command(gtx layout.Context, k key.Event) {
if k.Modifiers.Contain(key.ModShortcut) {
switch k.Name {
case "V":
clipboard.ReadOp{Tag: &e.mainTag}.Add(gtx.Ops)
gtx.Execute(clipboard.ReadCmd{Tag: e})
return
case "C", "X":
data, ok := e.TrackerList.CopyElements()
if ok && (k.Name == "C" || e.TrackerList.DeleteElements(false)) {
clipboard.WriteOp{Text: string(data)}.Add(gtx.Ops)
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(data))})
}
return
case "A":

View File

@ -1,14 +1,17 @@
package gioui
import (
"bytes"
"fmt"
"image"
"image/color"
"io"
"strconv"
"strings"
"gioui.org/font"
"gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/layout"
"gioui.org/op"
@ -45,6 +48,9 @@ type InstrumentEditor struct {
wasFocused bool
presetMenuItems []MenuItem
presetMenu Menu
commentKeyFilters []event.Filter
searchkeyFilters []event.Filter
nameKeyFilters []event.Filter
}
func NewInstrumentEditor(model *tracker.Model) *InstrumentEditor {
@ -72,6 +78,17 @@ func NewInstrumentEditor(model *tracker.Model) *InstrumentEditor {
ret.presetMenuItems = append(ret.presetMenuItems, MenuItem{Text: name, IconBytes: icons.ImageAudiotrack, Doer: model.LoadPreset(index)})
return true
})
for k := range noteMap {
ret.commentKeyFilters = append(ret.commentKeyFilters, key.Filter{Name: k, Focus: ret.commentEditor})
ret.searchkeyFilters = append(ret.searchkeyFilters, key.Filter{Name: k, Focus: ret.searchEditor})
ret.nameKeyFilters = append(ret.nameKeyFilters, key.Filter{Name: k, Focus: ret.nameEditor})
}
ret.commentKeyFilters = append(ret.commentKeyFilters, key.Filter{Name: key.NameEscape, Focus: ret.commentEditor})
ret.searchkeyFilters = append(ret.searchkeyFilters, key.Filter{Name: key.NameEscape, Focus: ret.searchEditor})
ret.nameKeyFilters = append(ret.nameKeyFilters, key.Filter{Name: key.NameEscape, Focus: ret.nameEditor})
ret.commentKeyFilters = append(ret.commentKeyFilters, key.Filter{Name: key.NameSpace, Focus: ret.commentEditor})
ret.searchkeyFilters = append(ret.searchkeyFilters, key.Filter{Name: key.NameSpace, Focus: ret.searchEditor})
ret.nameKeyFilters = append(ret.nameKeyFilters, key.Filter{Name: key.NameSpace, Focus: ret.nameEditor})
return ret
}
@ -83,14 +100,16 @@ func (ie *InstrumentEditor) Focused() bool {
return ie.unitDragList.focused
}
func (ie *InstrumentEditor) ChildFocused() bool {
return ie.unitEditor.sliderList.Focused() || ie.instrumentDragList.Focused() || ie.commentEditor.Focused() || ie.nameEditor.Focused() || ie.searchEditor.Focused() ||
ie.addUnitBtn.Clickable.Focused() || ie.commentExpandBtn.Clickable.Focused() || ie.presetMenuBtn.Clickable.Focused() || ie.deleteInstrumentBtn.Clickable.Focused() || ie.copyInstrumentBtn.Clickable.Focused()
func (ie *InstrumentEditor) childFocused(gtx C) bool {
return ie.unitEditor.sliderList.Focused() ||
ie.instrumentDragList.Focused() || 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.deleteInstrumentBtn.Clickable) || gtx.Source.Focused(ie.copyInstrumentBtn.Clickable)
}
func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
ie.wasFocused = ie.Focused() || ie.ChildFocused()
fullscreenBtnStyle := ToggleIcon(t.Theme, ie.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, "Enlarge (Ctrl+E)", "Shrink (Ctrl+E)")
ie.wasFocused = ie.Focused() || ie.childFocused(gtx)
fullscreenBtnStyle := ToggleIcon(gtx, t.Theme, ie.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, "Enlarge (Ctrl+E)", "Shrink (Ctrl+E)")
octave := func(gtx C) D {
in := layout.UniformInset(unit.Dp(1))
@ -99,7 +118,7 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
return dims
}
newBtnStyle := ActionIcon(t.Theme, ie.newInstrumentBtn, icons.ContentAdd, "Add\ninstrument\n(Ctrl+I)")
newBtnStyle := ActionIcon(gtx, t.Theme, ie.newInstrumentBtn, icons.ContentAdd, "Add\ninstrument\n(Ctrl+I)")
ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return layout.Flex{}.Layout(
@ -142,23 +161,23 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
header := func(gtx C) D {
commentExpandBtnStyle := ToggleIcon(t.Theme, ie.commentExpandBtn, icons.NavigationExpandMore, icons.NavigationExpandLess, "Expand comment", "Collapse comment")
commentExpandBtnStyle := ToggleIcon(gtx, t.Theme, ie.commentExpandBtn, icons.NavigationExpandMore, icons.NavigationExpandLess, "Expand comment", "Collapse comment")
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(t.Theme, ie.deleteInstrumentBtn, icons.ActionDelete, "Delete\ninstrument")
deleteInstrumentBtnStyle := ActionIcon(gtx, t.Theme, ie.deleteInstrumentBtn, icons.ActionDelete, "Delete\ninstrument")
m := PopupMenu(&ie.presetMenu, t.Theme.Shaper)
for ie.copyInstrumentBtn.Clickable.Clicked() {
for ie.copyInstrumentBtn.Clickable.Clicked(gtx) {
if contents, ok := t.Instruments().List().CopyElements(); ok {
clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops)
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
t.Alerts().Add("Instrument copied to clipboard", tracker.Info)
}
}
for ie.saveInstrumentBtn.Clickable.Clicked() {
for ie.saveInstrumentBtn.Clickable.Clicked(gtx) {
writer, err := t.Explorer.CreateFile(t.InstrumentName().Value() + ".yml")
if err != nil {
continue
@ -166,7 +185,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
t.SaveInstrument(writer)
}
for ie.loadInstrumentBtn.Clickable.Clicked() {
for ie.loadInstrumentBtn.Clickable.Clicked(gtx) {
reader, err := t.Explorer.ChooseFile(".yml", ".json", ".4ki", ".4kp")
if err != nil {
continue
@ -199,11 +218,11 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
layout.Rigid(deleteInstrumentBtnStyle.Layout))
}
for ie.presetMenuBtn.Clickable.Clicked() {
for ie.presetMenuBtn.Clickable.Clicked(gtx) {
ie.presetMenu.Visible = true
}
if ie.commentExpandBtn.Bool.Value() || ie.commentEditor.Focused() { // we draw once the widget after it manages to lose focus
if ie.commentExpandBtn.Bool.Value() || gtx.Source.Focused(ie.commentEditor) { // we draw once the widget after it manages to lose focus
if ie.commentEditor.Text() != ie.commentString.Value() {
ie.commentEditor.SetText(ie.commentString.Value())
}
@ -211,8 +230,11 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
layout.Rigid(header),
layout.Rigid(func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: &ie.unitDragList, Keys: globalKeys + "|⎋"}.Add(gtx.Ops)
for _, event := range gtx.Events(&ie.unitDragList) {
for {
event, ok := gtx.Event(ie.commentKeyFilters...)
if !ok {
break
}
if e, ok := event.(key.Event); ok && e.State == key.Press && e.Name == key.NameEscape {
ie.instrumentDragList.Focus()
}
@ -249,13 +271,6 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
k := byte(255 - level*127)
color := color.NRGBA{R: 255, G: k, B: 255, A: 255}
if i == ie.instrumentDragList.TrackerList.Selected() {
for _, ev := range ie.nameEditor.Events() {
_, ok := ev.(widget.SubmitEvent)
if ok {
ie.instrumentDragList.Focus()
continue
}
}
if n := name; n != ie.nameEditor.Text() {
ie.nameEditor.SetText(n)
}
@ -264,11 +279,30 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
editor.HintColor = instrumentNameHintColor
editor.TextSize = unit.Sp(12)
editor.Font = labelDefaultFont
for {
ev, ok := ie.nameEditor.Update(gtx)
if !ok {
break
}
_, ok = ev.(widget.SubmitEvent)
if ok {
ie.instrumentDragList.Focus()
continue
}
}
dims := layout.Center.Layout(gtx, func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: &ie.nameEditor, Keys: globalKeys}.Add(gtx.Ops)
return editor.Layout(gtx)
})
for { // don't let key presses flow through from the editor
event, ok := gtx.Event(ie.nameKeyFilters...)
if !ok {
break
}
if e, ok := event.(key.Event); ok && e.State == key.Press && e.Name == key.NameEscape {
ie.instrumentDragList.Focus()
}
}
ie.nameString.Set(ie.nameEditor.Text())
return dims
}
@ -297,9 +331,15 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: ie.instrumentDragList, Keys: "↓|⏎|⌤"}.Add(gtx.Ops)
for _, event := range gtx.Events(ie.instrumentDragList) {
for {
event, ok := gtx.Event(
key.Filter{Focus: ie.instrumentDragList, Name: key.NameDownArrow},
key.Filter{Focus: ie.instrumentDragList, Name: key.NameReturn},
key.Filter{Focus: ie.instrumentDragList, Name: key.NameEnter},
)
if !ok {
break
}
switch e := event.(type) {
case key.Event:
switch e.State {
@ -308,7 +348,7 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
case key.NameDownArrow:
ie.unitDragList.Focus()
case key.NameReturn, key.NameEnter:
ie.nameEditor.Focus()
gtx.Execute(key.FocusCmd{Tag: ie.nameEditor})
l := len(ie.nameEditor.Text())
ie.nameEditor.SetCaret(l, l)
}
@ -321,9 +361,10 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
instrumentList.LayoutScrollBar(gtx)
return dims
}
func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
// TODO: how to ie.unitDragList.Focus()
addUnitBtnStyle := ActionIcon(t.Theme, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)")
addUnitBtnStyle := ActionIcon(gtx, t.Theme, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)")
addUnitBtnStyle.IconButtonStyle.Color = t.Theme.ContrastFg
addUnitBtnStyle.IconButtonStyle.Background = t.Theme.Fg
addUnitBtnStyle.IconButtonStyle.Inset = layout.UniformInset(unit.Dp(4))
@ -365,8 +406,26 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Flexed(1, func(gtx C) D {
if i == ie.unitDragList.TrackerList.Selected() {
for _, ev := range ie.searchEditor.Events() {
_, ok := ev.(widget.SubmitEvent)
editor := material.Editor(t.Theme, ie.searchEditor, "---")
editor.Color = color
editor.HintColor = instrumentNameHintColor
editor.TextSize = unit.Sp(12)
editor.Font = f
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
txt := u.Type
str := tracker.String{StringData: (*tracker.UnitSearch)(t.Model)}
if t.UnitSearching().Value() {
txt = str.Value()
}
if ie.searchEditor.Text() != txt {
ie.searchEditor.SetText(txt)
}
for {
ev, ok := ie.searchEditor.Update(gtx)
if !ok {
break
}
_, ok = ev.(widget.SubmitEvent)
if ok {
txt := ""
ie.unitDragList.Focus()
@ -383,23 +442,16 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
continue
}
}
editor := material.Editor(t.Theme, ie.searchEditor, "---")
editor.Color = color
editor.HintColor = instrumentNameHintColor
editor.TextSize = unit.Sp(12)
editor.Font = f
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: &ie.searchEditor, Keys: globalKeys}.Add(gtx.Ops)
txt := u.Type
str := tracker.String{StringData: (*tracker.UnitSearch)(t.Model)}
if t.UnitSearching().Value() {
txt = str.Value()
}
if ie.searchEditor.Text() != txt {
ie.searchEditor.SetText(txt)
}
ret := editor.Layout(gtx)
for { // don't let key presses flow through from the editor
event, ok := gtx.Event(ie.searchkeyFilters...)
if !ok {
break
}
if e, ok := event.(key.Event); ok && e.State == key.Press && e.Name == key.NameEscape {
ie.instrumentDragList.Focus()
}
}
if ie.searchEditor.Text() != txt {
str.Set(ie.searchEditor.Text())
}
@ -424,8 +476,18 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
return layout.Stack{Alignment: layout.SE}.Layout(gtx,
layout.Expanded(func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: ie.unitDragList, Keys: "→|⏎|Ctrl-⏎|⌫|⎋"}.Add(gtx.Ops)
for _, event := range gtx.Events(ie.unitDragList) {
for {
event, ok := gtx.Event(
key.Filter{Focus: ie.unitDragList, Name: key.NameRightArrow},
key.Filter{Focus: ie.unitDragList, Name: key.NameEnter, Optional: key.ModCtrl},
key.Filter{Focus: ie.unitDragList, Name: key.NameReturn, Optional: key.ModCtrl},
key.Filter{Focus: ie.unitDragList, Name: key.NameDeleteBackward},
key.Filter{Focus: ie.unitDragList, Name: key.NameEscape},
)
if !ok {
break
}
switch e := event.(type) {
case key.Event:
switch e.State {
@ -437,13 +499,13 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
ie.unitEditor.sliderList.Focus()
case key.NameDeleteBackward:
t.Units().SetSelectedType("")
ie.searchEditor.Focus()
gtx.Execute(key.FocusCmd{Tag: ie.searchEditor})
l := len(ie.searchEditor.Text())
ie.searchEditor.SetCaret(l, l)
case key.NameReturn:
case key.NameEnter, key.NameReturn:
t.Model.AddUnit(e.Modifiers.Contain(key.ModCtrl)).Do()
ie.searchEditor.SetText("")
ie.searchEditor.Focus()
gtx.Execute(key.FocusCmd{Tag: ie.searchEditor})
l := len(ie.searchEditor.Text())
ie.searchEditor.SetCaret(l, l)
}

View File

@ -3,14 +3,9 @@ package gioui
import (
"gioui.org/io/clipboard"
"gioui.org/io/key"
"gioui.org/op"
)
// globalKeys is a list of keys that are handled globally by the app.
// All Editors should capture these keys to prevent them flowing to the global handler.
var globalKeys = key.Set("Space|\\|<|>|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|1|2|3|4|5|6|7|8|9|0|,|.")
var noteMap = map[string]int{
var noteMap = map[key.Name]int{
"Z": -12,
"S": -11,
"X": -10,
@ -46,12 +41,12 @@ var noteMap = map[string]int{
}
// KeyEvent handles incoming key events and returns true if repaint is needed.
func (t *Tracker) KeyEvent(e key.Event, o *op.Ops) {
func (t *Tracker) KeyEvent(e key.Event, gtx C) {
if e.State == key.Press {
switch e.Name {
case "V":
if e.Modifiers.Contain(key.ModShortcut) {
clipboard.ReadOp{Tag: t}.Add(o)
gtx.Execute(clipboard.ReadCmd{Tag: t})
return
}
case "Z":

View File

@ -4,6 +4,7 @@ import (
"image"
"image/color"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
@ -65,12 +66,19 @@ func (m *MenuStyle) Layout(gtx C, items ...MenuItem) D {
m.Menu.tags = append(m.Menu.tags, false)
}
// handle pointer events for this item
for _, ev := range gtx.Events(&m.Menu.tags[i]) {
for {
ev, ok := gtx.Event(pointer.Filter{
Target: &m.Menu.tags[i],
Kinds: pointer.Press | pointer.Enter | pointer.Leave,
})
if !ok {
break
}
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
switch e.Kind {
case pointer.Press:
item.Doer.Do()
m.Menu.Visible = false
@ -130,9 +138,7 @@ func (m *MenuStyle) Layout(gtx C, items ...MenuItem) D {
if item.Doer.Allowed() {
rect := image.Rect(0, 0, dims.Size.X, dims.Size.Y)
area := clip.Rect(rect).Push(gtx.Ops)
pointer.InputOp{Tag: &m.Menu.tags[i],
Types: pointer.Press | pointer.Enter | pointer.Leave,
}.Add(gtx.Ops)
event.Op(gtx.Ops, &m.Menu.tags[i])
area.Pop()
}
return dims
@ -163,8 +169,8 @@ func PopupMenu(menu *Menu, shaper *text.Shaper) MenuStyle {
}
}
func (tr *Tracker) layoutMenu(title string, clickable *widget.Clickable, menu *Menu, width unit.Dp, items ...MenuItem) layout.Widget {
for clickable.Clicked() {
func (tr *Tracker) layoutMenu(gtx C, title string, clickable *widget.Clickable, menu *Menu, width unit.Dp, items ...MenuItem) layout.Widget {
for clickable.Clicked(gtx) {
menu.Visible = true
}
m := PopupMenu(menu, tr.Theme.Shaper)

View File

@ -83,7 +83,50 @@ func NewNoteEditor(model *tracker.Model) *NoteEditor {
}
func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions {
for _, e := range gtx.Events(&te.tag) {
for {
e, ok := gtx.Event(
key.Filter{Focus: te.scrollTable, Name: "A"},
key.Filter{Focus: te.scrollTable, Name: "B"},
key.Filter{Focus: te.scrollTable, Name: "C"},
key.Filter{Focus: te.scrollTable, Name: "D"},
key.Filter{Focus: te.scrollTable, Name: "E"},
key.Filter{Focus: te.scrollTable, Name: "F"},
key.Filter{Focus: te.scrollTable, Name: "G"},
key.Filter{Focus: te.scrollTable, Name: "H"},
key.Filter{Focus: te.scrollTable, Name: "I"},
key.Filter{Focus: te.scrollTable, Name: "J"},
key.Filter{Focus: te.scrollTable, Name: "K"},
key.Filter{Focus: te.scrollTable, Name: "L"},
key.Filter{Focus: te.scrollTable, Name: "M"},
key.Filter{Focus: te.scrollTable, Name: "N"},
key.Filter{Focus: te.scrollTable, Name: "O"},
key.Filter{Focus: te.scrollTable, Name: "P"},
key.Filter{Focus: te.scrollTable, Name: "Q"},
key.Filter{Focus: te.scrollTable, Name: "R"},
key.Filter{Focus: te.scrollTable, Name: "S"},
key.Filter{Focus: te.scrollTable, Name: "T"},
key.Filter{Focus: te.scrollTable, Name: "U"},
key.Filter{Focus: te.scrollTable, Name: "V"},
key.Filter{Focus: te.scrollTable, Name: "W"},
key.Filter{Focus: te.scrollTable, Name: "X"},
key.Filter{Focus: te.scrollTable, Name: "Y"},
key.Filter{Focus: te.scrollTable, Name: "Z"},
key.Filter{Focus: te.scrollTable, Name: "0"},
key.Filter{Focus: te.scrollTable, Name: "1"},
key.Filter{Focus: te.scrollTable, Name: "2"},
key.Filter{Focus: te.scrollTable, Name: "3"},
key.Filter{Focus: te.scrollTable, Name: "4"},
key.Filter{Focus: te.scrollTable, Name: "5"},
key.Filter{Focus: te.scrollTable, Name: "6"},
key.Filter{Focus: te.scrollTable, Name: "7"},
key.Filter{Focus: te.scrollTable, Name: "8"},
key.Filter{Focus: te.scrollTable, Name: "9"},
key.Filter{Focus: te.scrollTable, Name: ","},
key.Filter{Focus: te.scrollTable, Name: "."},
)
if !ok {
break
}
switch e := e.(type) {
case key.Event:
if e.State == key.Release {
@ -99,7 +142,6 @@ func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions {
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: &te.tag, Keys: "Ctrl-⌫|Ctrl-⌦|⏎|Ctrl-⏎|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|,|."}.Add(gtx.Ops)
return Surface{Gray: 24, Focus: te.scrollTable.Focused()}.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
@ -115,19 +157,19 @@ func (te *NoteEditor) Layout(gtx layout.Context, t *Tracker) layout.Dimensions {
func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
return Surface{Gray: 37, Focus: te.scrollTable.Focused() || te.scrollTable.ChildFocused(), FitSize: true}.Layout(gtx, func(gtx C) D {
addSemitoneBtnStyle := ActionButton(t.Theme, te.AddSemitoneBtn, "+1")
subtractSemitoneBtnStyle := ActionButton(t.Theme, te.SubtractSemitoneBtn, "-1")
addOctaveBtnStyle := ActionButton(t.Theme, te.AddOctaveBtn, "+12")
subtractOctaveBtnStyle := ActionButton(t.Theme, te.SubtractOctaveBtn, "-12")
noteOffBtnStyle := ActionButton(t.Theme, te.NoteOffBtn, "Note Off")
deleteTrackBtnStyle := ActionIcon(t.Theme, te.DeleteTrackBtn, icons.ActionDelete, "Delete track\n(Ctrl+Shift+T)")
newTrackBtnStyle := ActionIcon(t.Theme, te.NewTrackBtn, icons.ContentAdd, "Add track\n(Ctrl+T)")
addSemitoneBtnStyle := ActionButton(gtx, t.Theme, te.AddSemitoneBtn, "+1")
subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, te.SubtractSemitoneBtn, "-1")
addOctaveBtnStyle := ActionButton(gtx, t.Theme, te.AddOctaveBtn, "+12")
subtractOctaveBtnStyle := ActionButton(gtx, t.Theme, te.SubtractOctaveBtn, "-12")
noteOffBtnStyle := ActionButton(gtx, t.Theme, te.NoteOffBtn, "Note Off")
deleteTrackBtnStyle := ActionIcon(gtx, t.Theme, te.DeleteTrackBtn, icons.ActionDelete, "Delete track\n(Ctrl+Shift+T)")
newTrackBtnStyle := ActionIcon(gtx, t.Theme, te.NewTrackBtn, icons.ContentAdd, "Add track\n(Ctrl+T)")
in := layout.UniformInset(unit.Dp(1))
voiceUpDown := func(gtx C) D {
numStyle := NumericUpDown(t.Theme, te.TrackVoices, "Number of voices for this track")
return in.Layout(gtx, numStyle.Layout)
}
effectBtnStyle := ToggleButton(t.Theme, te.EffectBtn, "Hex")
effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex")
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),
@ -304,7 +346,7 @@ func (te *NoteEditor) command(gtx C, t *Tracker, e key.Event) {
}
var n byte
if t.Model.Notes().Effect(te.scrollTable.Table.Cursor().X) {
if nibbleValue, err := strconv.ParseInt(e.Name, 16, 8); err == nil {
if nibbleValue, err := strconv.ParseInt(string(e.Name), 16, 8); err == nil {
n = t.Model.Notes().Value(te.scrollTable.Table.Cursor())
t.Model.Notes().FillNibble(byte(nibbleValue), t.Model.Notes().LowNibble())
goto validNote

View File

@ -15,6 +15,7 @@ import (
"gioui.org/x/component"
"gioui.org/gesture"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
@ -160,9 +161,16 @@ func (s *NumericUpDownStyle) layoutText(gtx C) D {
func (s *NumericUpDownStyle) layoutDrag(gtx layout.Context) layout.Dimensions {
{ // handle dragging
pxPerStep := float32(gtx.Dp(s.UnitsPerStep))
for _, ev := range gtx.Events(s.NumberInput) {
for {
ev, ok := gtx.Event(pointer.Filter{
Target: s.NumberInput,
Kinds: pointer.Press | pointer.Drag | pointer.Release,
})
if !ok {
break
}
if e, ok := ev.(pointer.Event); ok {
switch e.Type {
switch e.Kind {
case pointer.Press:
s.NumberInput.dragStartValue = s.NumberInput.Int.Value()
s.NumberInput.dragStartXY = e.Position.X - e.Position.Y
@ -180,10 +188,7 @@ func (s *NumericUpDownStyle) layoutDrag(gtx layout.Context) layout.Dimensions {
// register for input
dragRect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area := clip.Rect(dragRect).Push(gtx.Ops)
pointer.InputOp{
Tag: s.NumberInput,
Types: pointer.Press | pointer.Drag | pointer.Release,
}.Add(gtx.Ops)
event.Op(gtx.Ops, s.NumberInput)
area.Pop()
stack.Pop()
}
@ -192,9 +197,13 @@ func (s *NumericUpDownStyle) layoutDrag(gtx layout.Context) layout.Dimensions {
func (s *NumericUpDownStyle) layoutClick(gtx layout.Context, delta int, click *gesture.Click) layout.Dimensions {
// handle clicking
for _, e := range click.Events(gtx) {
switch e.Type {
case gesture.TypeClick:
for {
ev, ok := click.Update(gtx.Source)
if !ok {
break
}
switch ev.Kind {
case gesture.KindClick:
s.NumberInput.Int.Add(delta)
}
}

View File

@ -8,6 +8,7 @@ import (
"strings"
"gioui.org/f32"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/layout"
"gioui.org/op"
@ -19,9 +20,9 @@ import (
"github.com/vsariola/sointu/tracker"
)
const patternCellHeight = 16
const patternCellWidth = 16
const patternRowMarkerWidth = 30
const patternCellHeight = unit.Dp(16)
const patternCellWidth = unit.Dp(16)
const patternRowMarkerWidth = unit.Dp(30)
const orderTitleHeight = unit.Dp(52)
type OrderEditor struct {
@ -57,18 +58,11 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
t.TrackEditor.scrollTable.RowTitleList.CenterOn(cursor.Y)
}
for _, e := range gtx.Events(&oe.tag) {
switch e := e.(type) {
case key.Event:
if e.State != key.Press {
continue
}
oe.command(gtx, t, e)
}
}
oe.handleEvents(gtx, t)
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: &oe.tag, Keys: "Ctrl-⌫|Ctrl-⌦|⏎|Ctrl-⏎|0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z"}.Add(gtx.Ops)
event.Op(gtx.Ops, &oe.tag)
colTitle := func(gtx C, i int) D {
h := gtx.Dp(orderTitleHeight)
@ -77,13 +71,13 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6))
title := t.Model.Order().Title(i)
LabelStyle{Alignment: layout.NW, Text: title, FontSize: unit.Sp(12), Color: mediumEmphasisTextColor, Shaper: t.Theme.Shaper}.Layout(gtx)
return D{Size: image.Pt(patternCellWidth, h)}
return D{Size: image.Pt(gtx.Dp(patternCellWidth), h)}
}
rowTitle := func(gtx C, j int) D {
w := gtx.Dp(unit.Dp(30))
if playPos := t.PlayPosition(); t.SongPanel.PlayingBtn.Bool.Value() && j == playPos.OrderRow {
paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}.Op())
paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Dp(patternCellHeight))}.Op())
}
color := rowMarkerPatternTextColor
if l := t.Loop(); j >= l.Start && j < l.Start+l.Length {
@ -92,7 +86,7 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
paint.ColorOp{Color: color}.Add(gtx.Ops)
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j)), op.CallOp{})
return D{Size: image.Pt(w, patternCellHeight)}
return D{Size: image.Pt(w, gtx.Dp(patternCellHeight))}
}
selection := oe.scrollTable.Table.Range()
@ -114,7 +108,7 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops)
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, val, op.CallOp{})
return D{Size: image.Pt(patternCellWidth, patternCellHeight)}
return D{Size: image.Pt(gtx.Dp(patternCellWidth), gtx.Dp(patternCellHeight))}
}
table := FilledScrollTable(t.Theme, oe.scrollTable, cell, colTitle, rowTitle, nil, nil)
@ -123,6 +117,61 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
return table.Layout(gtx)
}
func (oe *OrderEditor) handleEvents(gtx C, t *Tracker) {
for {
e, ok := gtx.Event(
key.Filter{Focus: oe.scrollTable, Name: key.NameDeleteBackward, Optional: key.ModShortcut},
key.Filter{Focus: oe.scrollTable, Name: key.NameDeleteForward, Optional: key.ModShortcut},
key.Filter{Focus: oe.scrollTable, Name: key.NameReturn, Optional: key.ModShortcut},
key.Filter{Focus: oe.scrollTable, Name: "0"},
key.Filter{Focus: oe.scrollTable, Name: "1"},
key.Filter{Focus: oe.scrollTable, Name: "2"},
key.Filter{Focus: oe.scrollTable, Name: "3"},
key.Filter{Focus: oe.scrollTable, Name: "4"},
key.Filter{Focus: oe.scrollTable, Name: "5"},
key.Filter{Focus: oe.scrollTable, Name: "6"},
key.Filter{Focus: oe.scrollTable, Name: "7"},
key.Filter{Focus: oe.scrollTable, Name: "8"},
key.Filter{Focus: oe.scrollTable, Name: "9"},
key.Filter{Focus: oe.scrollTable, Name: "A"},
key.Filter{Focus: oe.scrollTable, Name: "B"},
key.Filter{Focus: oe.scrollTable, Name: "C"},
key.Filter{Focus: oe.scrollTable, Name: "D"},
key.Filter{Focus: oe.scrollTable, Name: "E"},
key.Filter{Focus: oe.scrollTable, Name: "F"},
key.Filter{Focus: oe.scrollTable, Name: "G"},
key.Filter{Focus: oe.scrollTable, Name: "H"},
key.Filter{Focus: oe.scrollTable, Name: "I"},
key.Filter{Focus: oe.scrollTable, Name: "J"},
key.Filter{Focus: oe.scrollTable, Name: "K"},
key.Filter{Focus: oe.scrollTable, Name: "L"},
key.Filter{Focus: oe.scrollTable, Name: "M"},
key.Filter{Focus: oe.scrollTable, Name: "N"},
key.Filter{Focus: oe.scrollTable, Name: "O"},
key.Filter{Focus: oe.scrollTable, Name: "P"},
key.Filter{Focus: oe.scrollTable, Name: "Q"},
key.Filter{Focus: oe.scrollTable, Name: "R"},
key.Filter{Focus: oe.scrollTable, Name: "S"},
key.Filter{Focus: oe.scrollTable, Name: "T"},
key.Filter{Focus: oe.scrollTable, Name: "U"},
key.Filter{Focus: oe.scrollTable, Name: "V"},
key.Filter{Focus: oe.scrollTable, Name: "W"},
key.Filter{Focus: oe.scrollTable, Name: "X"},
key.Filter{Focus: oe.scrollTable, Name: "Y"},
key.Filter{Focus: oe.scrollTable, Name: "Z"},
)
if !ok {
break
}
if e, ok := e.(key.Event); ok {
if e.State != key.Press {
continue
}
oe.command(gtx, t, e)
}
}
}
func (oe *OrderEditor) command(gtx C, t *Tracker, e key.Event) {
switch e.Name {
case key.NameDeleteBackward:
@ -140,7 +189,7 @@ func (oe *OrderEditor) command(gtx C, t *Tracker, e key.Event) {
}
t.Model.AddOrderRow(!e.Modifiers.Contain(key.ModShortcut)).Do()
}
if iv, err := strconv.Atoi(e.Name); err == nil {
if iv, err := strconv.Atoi(string(e.Name)); err == nil {
t.Model.Order().SetValue(oe.scrollTable.Table.Cursor(), iv)
oe.scrollTable.EnsureCursorVisible()
}

View File

@ -4,6 +4,7 @@ import (
"image"
"image/color"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
@ -44,13 +45,19 @@ func (s PopupStyle) Layout(gtx C, contents layout.Widget) D {
return D{}
}
for _, ev := range gtx.Events(s.Visible) {
e, ok := ev.(pointer.Event)
for {
event, ok := gtx.Event(pointer.Filter{
Target: s.Visible,
Kinds: pointer.Press,
})
if !ok {
break
}
e, ok := event.(pointer.Event)
if !ok {
continue
}
switch e.Type {
switch e.Kind {
case pointer.Press:
*s.Visible = false
}
@ -70,16 +77,10 @@ func (s PopupStyle) Layout(gtx C, contents layout.Widget) D {
paint.FillShape(gtx.Ops, s.ShadowColor, rrect2.Op(gtx.Ops))
paint.FillShape(gtx.Ops, s.SurfaceColor, rrect.Op(gtx.Ops))
area := clip.Rect(image.Rect(-1e6, -1e6, 1e6, 1e6)).Push(gtx.Ops)
pointer.InputOp{Tag: s.Visible,
Types: pointer.Press,
Grab: true,
}.Add(gtx.Ops)
event.Op(gtx.Ops, s.Visible)
area.Pop()
area = clip.Rect(rrect2.Rect).Push(gtx.Ops)
pointer.InputOp{Tag: &dummyTag,
Types: pointer.Press,
Grab: true,
}.Add(gtx.Ops)
event.Op(gtx.Ops, &dummyTag)
area.Pop()
return D{Size: gtx.Constraints.Min}
}

View File

@ -31,7 +31,7 @@ func NewPopupAlert(alerts *tracker.Alerts, shaper *text.Shaper) *PopupAlert {
func (a *PopupAlert) Layout(gtx C) D {
now := time.Now()
if a.alerts.Update(now.Sub(a.prevUpdate)) {
op.InvalidateOp{At: now.Add(50 * time.Millisecond)}.Add(gtx.Ops)
gtx.Execute(op.InvalidateCmd{At: now.Add(50 * time.Millisecond)})
}
a.prevUpdate = now

View File

@ -1,11 +1,15 @@
package gioui
import (
"bytes"
"image"
"io"
"gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/transfer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
@ -20,7 +24,6 @@ type ScrollTable struct {
Table tracker.Table
focused bool
requestFocus bool
tag bool
colTag bool
rowTag bool
cursorMoved bool
@ -85,55 +88,10 @@ func (st *ScrollTable) ChildFocused() bool {
func (s ScrollTableStyle) Layout(gtx C) D {
p := image.Pt(gtx.Dp(s.RowTitleWidth), gtx.Dp(s.ColumnTitleHeight))
for _, e := range gtx.Events(&s.ScrollTable.tag) {
switch e := e.(type) {
case key.FocusEvent:
s.ScrollTable.focused = e.Focus
case pointer.Event:
if e.Position.X >= float32(p.X) && e.Position.Y >= float32(p.Y) {
if e.Type == pointer.Press {
key.FocusOp{Tag: &s.ScrollTable.tag}.Add(gtx.Ops)
}
dx := (int(e.Position.X) + s.ScrollTable.ColTitleList.List.Position.Offset - p.X) / gtx.Dp(s.CellWidth)
dy := (int(e.Position.Y) + s.ScrollTable.RowTitleList.List.Position.Offset - p.Y) / gtx.Dp(s.CellHeight)
x := dx + s.ScrollTable.ColTitleList.List.Position.First
y := dy + s.ScrollTable.RowTitleList.List.Position.First
s.ScrollTable.Table.SetCursor(
tracker.Point{X: x, Y: y},
)
if !e.Modifiers.Contain(key.ModShift) {
s.ScrollTable.Table.SetCursor2(s.ScrollTable.Table.Cursor())
}
s.ScrollTable.cursorMoved = true
}
case key.Event:
if e.State == key.Press {
s.ScrollTable.command(gtx, e)
}
case clipboard.Event:
s.ScrollTable.Table.Paste([]byte(e.Text))
}
}
for _, e := range gtx.Events(&s.ScrollTable.rowTag) {
if e, ok := e.(key.Event); ok && e.State == key.Press {
s.ScrollTable.Focus()
}
}
for _, e := range gtx.Events(&s.ScrollTable.colTag) {
if e, ok := e.(key.Event); ok && e.State == key.Press {
s.ScrollTable.Focus()
}
}
s.handleEvents(gtx)
return Surface{Gray: 24, Focus: s.ScrollTable.Focused() || s.ScrollTable.ChildFocused()}.Layout(gtx, func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
pointer.InputOp{
Tag: &s.ScrollTable.tag,
Types: pointer.Press,
}.Add(gtx.Ops)
dims := gtx.Constraints.Max
s.layoutColTitles(gtx, p)
s.layoutRowTitles(gtx, p)
@ -146,14 +104,105 @@ func (s ScrollTableStyle) Layout(gtx C) D {
})
}
func (s *ScrollTableStyle) handleEvents(gtx layout.Context) {
for {
e, ok := gtx.Event(
key.FocusFilter{Target: s.ScrollTable},
transfer.TargetFilter{Target: s.ScrollTable, Type: "application/text"},
pointer.Filter{Target: s.ScrollTable, Kinds: pointer.Press},
key.Filter{Focus: s.ScrollTable, Name: key.NameLeftArrow, Optional: key.ModShift | key.ModCtrl | key.ModAlt},
key.Filter{Focus: s.ScrollTable, Name: key.NameUpArrow, Optional: key.ModShift | key.ModCtrl | key.ModAlt},
key.Filter{Focus: s.ScrollTable, Name: key.NameRightArrow, Optional: key.ModShift | key.ModCtrl | key.ModAlt},
key.Filter{Focus: s.ScrollTable, Name: key.NameDownArrow, Optional: key.ModShift | key.ModCtrl | key.ModAlt},
key.Filter{Focus: s.ScrollTable, Name: key.NamePageUp, Optional: key.ModShift},
key.Filter{Focus: s.ScrollTable, Name: key.NamePageDown, Optional: key.ModShift},
key.Filter{Focus: s.ScrollTable, Name: key.NameHome, Optional: key.ModShift},
key.Filter{Focus: s.ScrollTable, Name: key.NameEnd, Optional: key.ModShift},
key.Filter{Focus: s.ScrollTable, Name: key.NameDeleteBackward},
key.Filter{Focus: s.ScrollTable, Name: key.NameDeleteForward},
key.Filter{Focus: s.ScrollTable, Name: "C", Required: key.ModShortcut},
key.Filter{Focus: s.ScrollTable, Name: "V", Required: key.ModShortcut},
key.Filter{Focus: s.ScrollTable, Name: "X", Required: key.ModShortcut},
key.Filter{Focus: s.ScrollTable, Name: ",", Required: key.ModShift},
key.Filter{Focus: s.ScrollTable, Name: ".", Required: key.ModShift},
)
if !ok {
break
}
switch e := e.(type) {
case key.FocusEvent:
s.ScrollTable.focused = e.Focus
case pointer.Event:
if e.Kind == pointer.Press {
gtx.Execute(key.FocusCmd{Tag: s.ScrollTable})
}
dx := (int(e.Position.X) + s.ScrollTable.ColTitleList.List.Position.Offset) / gtx.Dp(s.CellWidth)
dy := (int(e.Position.Y) + s.ScrollTable.RowTitleList.List.Position.Offset) / gtx.Dp(s.CellHeight)
x := dx + s.ScrollTable.ColTitleList.List.Position.First
y := dy + s.ScrollTable.RowTitleList.List.Position.First
s.ScrollTable.Table.SetCursor(
tracker.Point{X: x, Y: y},
)
if !e.Modifiers.Contain(key.ModShift) {
s.ScrollTable.Table.SetCursor2(s.ScrollTable.Table.Cursor())
}
s.ScrollTable.cursorMoved = true
case key.Event:
if e.State == key.Press {
s.ScrollTable.command(gtx, e)
}
case transfer.DataEvent:
if b, err := io.ReadAll(e.Open()); err == nil {
s.ScrollTable.Table.Paste(b)
}
}
}
for {
e, ok := gtx.Event(
key.FocusFilter{
Target: &s.ScrollTable.rowTag,
},
key.Filter{
Focus: &s.ScrollTable.rowTag,
Name: "→",
},
)
if !ok {
break
}
if e, ok := e.(key.Event); ok && e.State == key.Press {
s.ScrollTable.Focus()
}
}
for {
e, ok := gtx.Event(
key.FocusFilter{
Target: &s.ScrollTable.colTag,
},
key.Filter{
Focus: &s.ScrollTable.colTag,
Name: "↓",
},
)
if !ok {
break
}
if e, ok := e.(key.Event); ok && e.State == key.Press {
s.ScrollTable.Focus()
}
}
}
func (s ScrollTableStyle) layoutTable(gtx C, p image.Point) {
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
if s.ScrollTable.requestFocus {
s.ScrollTable.requestFocus = false
key.FocusOp{Tag: &s.ScrollTable.tag}.Add(gtx.Ops)
gtx.Execute(key.FocusCmd{Tag: s.ScrollTable})
}
key.InputOp{Tag: &s.ScrollTable.tag, Keys: "←|→|↑|↓|Shift-←|Shift-→|Shift-↑|Shift-↓|Ctrl-←|Ctrl-→|Ctrl-↑|Ctrl-↓|Ctrl-Shift-←|Ctrl-Shift-→|Ctrl-Shift-↑|Ctrl-Shift-↓|Alt-←|Alt-→|Alt-↑|Alt-↓|Alt-Shift-←|Alt-Shift-→|Alt-Shift-↑|Alt-Shift-↓|⇱|⇲|Shift-⇱|Shift-⇲|⌫|⌦|⇞|⇟|Shift-⇞|Shift-⇟|Ctrl-C|Ctrl-V|Ctrl-X|Shift-,|Shift-."}.Add(gtx.Ops)
event.Op(gtx.Ops, s.ScrollTable)
cellWidth := gtx.Dp(s.CellWidth)
cellHeight := gtx.Dp(s.CellHeight)
@ -162,14 +211,12 @@ func (s ScrollTableStyle) layoutTable(gtx C, p image.Point) {
colP := s.ColTitleStyle.dragList.List.Position
rowP := s.RowTitleStyle.dragList.List.Position
defer op.Offset(image.Pt(-colP.Offset, -rowP.Offset)).Push(gtx.Ops).Pop()
for x := colP.First; x < colP.First+colP.Count; x++ {
offs := op.Offset(image.Point{}).Push(gtx.Ops)
for y := rowP.First; y < rowP.First+rowP.Count; y++ {
s.element(gtx, x, y)
op.Offset(image.Pt(0, cellHeight)).Add(gtx.Ops)
for x := 0; x < colP.Count; x++ {
for y := 0; y < rowP.Count; y++ {
o := op.Offset(image.Pt(cellWidth*x, cellHeight*y)).Push(gtx.Ops)
s.element(gtx, x+colP.First, y+rowP.First)
o.Pop()
}
offs.Pop()
op.Offset(image.Pt(cellWidth, 0)).Add(gtx.Ops)
}
}
@ -179,7 +226,7 @@ func (s *ScrollTableStyle) layoutRowTitles(gtx C, p image.Point) {
gtx.Constraints.Max.Y -= p.Y
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
key.InputOp{Tag: &s.ScrollTable.rowTag, Keys: "→"}.Add(gtx.Ops)
event.Op(gtx.Ops, &s.ScrollTable.rowTag)
s.RowTitleStyle.Layout(gtx)
}
@ -189,7 +236,7 @@ func (s *ScrollTableStyle) layoutColTitles(gtx C, p image.Point) {
gtx.Constraints.Max.X -= p.X
gtx.Constraints.Min.X = gtx.Constraints.Max.X
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
key.InputOp{Tag: &s.ScrollTable.colTag, Keys: "↓"}.Add(gtx.Ops)
event.Op(gtx.Ops, &s.ScrollTable.colTag)
s.ColTitleStyle.Layout(gtx)
}
@ -210,7 +257,7 @@ func (s *ScrollTable) command(gtx C, e key.Event) {
if !ok {
return
}
clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops)
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
if e.Name == "X" {
s.Table.Clear()
}
@ -218,7 +265,7 @@ func (s *ScrollTable) command(gtx C, e key.Event) {
}
case "V":
if e.Modifiers.Contain(key.ModShortcut) {
clipboard.ReadOp{Tag: &s.tag}.Add(gtx.Ops)
gtx.Execute(clipboard.ReadCmd{Tag: s})
}
return
case key.NameDeleteBackward, key.NameDeleteForward:

View File

@ -4,6 +4,7 @@ import (
"image"
"gioui.org/f32"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
@ -77,19 +78,22 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit
rect := image.Rect(0, gtx.Constraints.Min.Y-scrWidth, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area = clip.Rect(rect).Push(gtx.Ops)
}
pointer.InputOp{Tag: &s.dragStart,
Types: pointer.Drag | pointer.Press | pointer.Cancel | pointer.Release,
Grab: s.dragging,
}.Add(gtx.Ops)
event.Op(gtx.Ops, &s.dragStart)
area.Pop()
stack.Pop()
for _, ev := range gtx.Events(&s.dragStart) {
for {
ev, ok := gtx.Event(
pointer.Filter{Target: &s.dragStart, Kinds: pointer.Press | pointer.Cancel | pointer.Release | pointer.Drag},
)
if !ok {
break
}
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
switch e.Kind {
case pointer.Press:
if s.Axis == layout.Horizontal {
s.dragStart = e.Position.X
@ -114,17 +118,22 @@ func (s *ScrollBar) Layout(gtx C, width unit.Dp, numItems int, pos *layout.Posit
rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area2 := clip.Rect(rect).Push(gtx.Ops)
defer pointer.PassOp{}.Push(gtx.Ops).Pop()
pointer.InputOp{Tag: &s.tag,
Types: pointer.Enter | pointer.Leave,
}.Add(gtx.Ops)
event.Op(gtx.Ops, &s.tag)
area2.Pop()
for _, ev := range gtx.Events(&s.tag) {
for {
ev, ok := gtx.Event(pointer.Filter{
Target: &s.tag,
Kinds: pointer.Enter | pointer.Leave,
})
if !ok {
break
}
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
switch e.Kind {
case pointer.Enter:
s.hovering = true
case pointer.Leave:

View File

@ -93,8 +93,8 @@ func (t *SongPanel) layoutMenuBar(gtx C, tr *Tracker) D {
gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36))
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(tr.layoutMenu("File", &t.MenuBar[0], &t.Menus[0], unit.Dp(200), t.fileMenuItems...)),
layout.Rigid(tr.layoutMenu("Edit", &t.MenuBar[1], &t.Menus[1], unit.Dp(200), t.editMenuItems...)),
layout.Rigid(tr.layoutMenu(gtx, "File", &t.MenuBar[0], &t.Menus[0], unit.Dp(200), t.fileMenuItems...)),
layout.Rigid(tr.layoutMenu(gtx, "Edit", &t.MenuBar[1], &t.Menus[1], unit.Dp(200), t.editMenuItems...)),
)
}
@ -103,12 +103,12 @@ func (t *SongPanel) layoutSongOptions(gtx C, tr *Tracker) D {
in := layout.UniformInset(unit.Dp(1))
panicBtnStyle := ToggleButton(tr.Theme, t.PanicBtn, "Panic (F12)")
rewindBtnStyle := ActionIcon(tr.Theme, t.RewindBtn, icons.AVFastRewind, "Rewind\n(F5)")
playBtnStyle := ToggleIcon(tr.Theme, t.PlayingBtn, icons.AVPlayArrow, icons.AVStop, "Play (F6 / Space)", "Stop (F6 / Space)")
recordBtnStyle := ToggleIcon(tr.Theme, t.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, "Record (F7)", "Stop (F7)")
noteTrackBtnStyle := ToggleIcon(tr.Theme, t.NoteTracking, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, "Follow\nOff\n(F8)", "Follow\nOn\n(F8)")
loopBtnStyle := ToggleIcon(tr.Theme, t.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, "Loop\nOff\n(Ctrl+L)", "Loop\nOn\n(Ctrl+L)")
panicBtnStyle := ToggleButton(gtx, tr.Theme, t.PanicBtn, "Panic (F12)")
rewindBtnStyle := ActionIcon(gtx, tr.Theme, t.RewindBtn, icons.AVFastRewind, "Rewind\n(F5)")
playBtnStyle := ToggleIcon(gtx, tr.Theme, t.PlayingBtn, icons.AVPlayArrow, icons.AVStop, "Play (F6 / Space)", "Stop (F6 / Space)")
recordBtnStyle := ToggleIcon(gtx, tr.Theme, t.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, "Record (F7)", "Stop (F7)")
noteTrackBtnStyle := ToggleIcon(gtx, tr.Theme, t.NoteTracking, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, "Follow\nOff\n(F8)", "Follow\nOn\n(F8)")
loopBtnStyle := ToggleIcon(gtx, tr.Theme, t.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, "Loop\nOff\n(Ctrl+L)", "Loop\nOn\n(Ctrl+L)")
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {

View File

@ -3,6 +3,7 @@ package gioui
import (
"image"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
@ -48,14 +49,21 @@ func (s *Split) Layout(gtx layout.Context, first, second layout.Widget) layout.D
{ // handle input
// Avoid affecting the input tree with pointer events.
for _, ev := range gtx.Events(s) {
for {
ev, ok := gtx.Event(pointer.Filter{
Target: s,
Kinds: pointer.Press | pointer.Drag | pointer.Release,
// TODO: there should be a grab; there was Grab: s.drag,
})
if !ok {
break
}
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
switch e.Kind {
case pointer.Press:
if s.drag {
break
@ -123,10 +131,7 @@ func (s *Split) Layout(gtx layout.Context, first, second layout.Widget) layout.D
barRect = image.Rect(0, firstSize, gtx.Constraints.Max.X, secondOffset)
}
area := clip.Rect(barRect).Push(gtx.Ops)
pointer.InputOp{Tag: s,
Types: pointer.Press | pointer.Drag | pointer.Release,
Grab: s.drag,
}.Add(gtx.Ops)
event.Op(gtx.Ops, s)
area.Pop()
}

View File

@ -5,14 +5,14 @@ import (
"image"
"io"
"path/filepath"
"strings"
"sync"
"time"
"gioui.org/app"
"gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/io/system"
"gioui.org/io/transfer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
@ -34,7 +34,7 @@ type (
TopHorizontalSplit *Split
BottomHorizontalSplit *Split
VerticalSplit *Split
KeyPlaying map[string]tracker.NoteID
KeyPlaying map[key.Name]tracker.NoteID
PopupAlert *PopupAlert
SaveChangesDialog *Dialog
@ -76,7 +76,7 @@ func NewTracker(model *tracker.Model) *Tracker {
BottomHorizontalSplit: &Split{Ratio: -.6},
VerticalSplit: &Split{Axis: layout.Vertical},
KeyPlaying: make(map[string]tracker.NoteID),
KeyPlaying: make(map[key.Name]tracker.NoteID),
SaveChangesDialog: NewDialog(model.SaveSong(), model.DiscardSong(), model.Cancel()),
WaveTypeDialog: NewDialog(model.ExportInt16(), model.ExportFloat(), model.Cancel()),
InstrumentEditor: NewInstrumentEditor(model),
@ -107,6 +107,11 @@ func (t *Tracker) Main() {
t.InstrumentEditor.Focus()
recoveryTicker := time.NewTicker(time.Second * 30)
t.Explorer = explorer.NewExplorer(w)
// Make a channel to read window events from.
events := make(chan event.Event)
// Make a channel to signal the end of processing a window event.
acks := make(chan struct{})
go eventLoop(w, events, acks)
var ops op.Ops
for {
if titleFooter != t.filePathString.Value() {
@ -121,9 +126,10 @@ func (t *Tracker) Main() {
case e := <-t.PlayerMessages:
t.ProcessPlayerMessage(e)
w.Invalidate()
case e := <-w.Events():
case e := <-events:
switch e := e.(type) {
case system.DestroyEvent:
case app.DestroyEvent:
acks <- struct{}{}
if canQuit {
t.Quit().Do()
}
@ -134,14 +140,18 @@ func (t *Tracker) Main() {
app.Title("Sointu Tracker"),
)
t.Explorer = explorer.NewExplorer(w)
go eventLoop(w, events, acks)
}
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
if t.SongPanel.PlayingBtn.Bool.Value() && t.SongPanel.NoteTracking.Bool.Value() {
t.TrackEditor.scrollTable.RowTitleList.CenterOn(t.PlaySongRow())
}
t.Layout(gtx, w)
e.Frame(gtx.Ops)
acks <- struct{}{}
default:
acks <- struct{}{}
}
case <-recoveryTicker.C:
t.SaveRecovery()
@ -158,6 +168,19 @@ func (t *Tracker) Main() {
t.quitWG.Done()
}
func eventLoop(w *app.Window, events chan<- event.Event, acks <-chan struct{}) {
// Iterate window events, sending each to the old event loop and waiting for
// a signal that processing is complete before iterating again.
for {
ev := w.NextEvent()
events <- ev
<-acks
if _, ok := ev.(app.DestroyEvent); ok {
return
}
}
}
func (t *Tracker) Exec() chan<- func() {
return t.execChan
}
@ -167,22 +190,6 @@ func (t *Tracker) WaitQuitted() {
}
func (t *Tracker) Layout(gtx layout.Context, w *app.Window) {
// this is the top level input handler for the whole app
// it handles all the global key events and clipboard events
// we need to tell gio that we handle tabs too; otherwise
// it will steal them for focus switching
key.InputOp{Tag: t, Keys: "Tab|Shift-Tab"}.Add(gtx.Ops)
for _, ev := range gtx.Events(t) {
switch e := ev.(type) {
case key.Event:
t.KeyEvent(e, gtx.Ops)
case clipboard.Event:
stringReader := strings.NewReader(e.Text)
stringReadCloser := io.NopCloser(stringReader)
t.ReadSong(stringReadCloser)
}
}
paint.FillShape(gtx.Ops, backgroundColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op())
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
t.layoutTop(gtx)
@ -193,6 +200,27 @@ func (t *Tracker) Layout(gtx layout.Context, w *app.Window) {
}
t.PopupAlert.Layout(gtx)
t.showDialog(gtx)
// this is the top level input handler for the whole app
// it handles all the global key events and clipboard events
// we need to tell gio that we handle tabs too; otherwise
// it will steal them for focus switching
for {
ev, ok := gtx.Event(
key.Filter{Name: "", Optional: key.ModAlt | key.ModCommand | key.ModShift | key.ModShortcut | key.ModSuper},
key.Filter{Name: key.NameTab, Optional: key.ModShift},
transfer.TargetFilter{Target: t, Type: "application/text"},
)
if !ok {
break
}
switch e := ev.(type) {
case key.Event:
t.KeyEvent(e, gtx)
case transfer.DataEvent:
t.ReadSong(e.Open())
}
}
}
func (t *Tracker) showDialog(gtx C) {
@ -201,12 +229,12 @@ func (t *Tracker) showDialog(gtx C) {
}
switch t.Dialog() {
case tracker.NewSongChanges, tracker.OpenSongChanges, tracker.QuitChanges:
dstyle := ConfirmDialog(t.Theme, t.SaveChangesDialog, "Save changes to song?", "Your changes will be lost if you don't save them.")
dstyle := ConfirmDialog(gtx, t.Theme, t.SaveChangesDialog, "Save changes to song?", "Your changes will be lost if you don't save them.")
dstyle.OkStyle.Text = "Save"
dstyle.AltStyle.Text = "Don't save"
dstyle.Layout(gtx)
case tracker.Export:
dstyle := ConfirmDialog(t.Theme, t.WaveTypeDialog, "", "Export .wav in int16 or float32 sample format?")
dstyle := ConfirmDialog(gtx, t.Theme, t.WaveTypeDialog, "", "Export .wav in int16 or float32 sample format?")
dstyle.OkStyle.Text = "Int16"
dstyle.AltStyle.Text = "Float32"
dstyle.Layout(gtx)

View File

@ -1,11 +1,14 @@
package gioui
import (
"bytes"
"fmt"
"image"
"io"
"math"
"gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/layout"
@ -29,7 +32,6 @@ type UnitEditor struct {
ClearUnitBtn *ActionClickable
DisableUnitBtn *BoolClickable
SelectTypeBtn *widget.Clickable
tag bool
caser cases.Caser
}
@ -48,7 +50,15 @@ func NewUnitEditor(m *tracker.Model) *UnitEditor {
}
func (pe *UnitEditor) Layout(gtx C, t *Tracker) D {
for _, e := range gtx.Events(&pe.tag) {
for {
e, ok := gtx.Event(
key.Filter{Focus: pe.sliderList, Name: key.NameLeftArrow, Optional: key.ModShift},
key.Filter{Focus: pe.sliderList, Name: key.NameRightArrow, Optional: key.ModShift},
key.Filter{Focus: pe.sliderList, Name: key.NameEscape},
)
if !ok {
break
}
switch e := e.(type) {
case key.Event:
if e.State == key.Press {
@ -58,8 +68,6 @@ func (pe *UnitEditor) Layout(gtx C, t *Tracker) D {
}
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: &pe.tag, Keys: "←|Shift-←|→|Shift-→|⎋"}.Add(gtx.Ops)
editorFunc := pe.layoutSliders
if t.UnitSearching().Value() || pe.sliderList.TrackerList.Count() == 0 {
@ -108,15 +116,15 @@ func (pe *UnitEditor) layoutSliders(gtx C, t *Tracker) D {
}
func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
for pe.CopyUnitBtn.Clickable.Clicked() {
for pe.CopyUnitBtn.Clickable.Clicked(gtx) {
if contents, ok := t.Units().List().CopyElements(); ok {
clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops)
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
t.Alerts().Add("Unit copied to clipboard", tracker.Info)
}
}
copyUnitBtnStyle := TipIcon(t.Theme, pe.CopyUnitBtn, icons.ContentContentCopy, "Copy unit (Ctrl+C)")
deleteUnitBtnStyle := ActionIcon(t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
disableUnitBtnStyle := ToggleIcon(t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, "Disable unit (Ctrl-D)", "Enable unit (Ctrl-D)")
deleteUnitBtnStyle := ActionIcon(gtx, t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
disableUnitBtnStyle := ToggleIcon(gtx, t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, "Disable unit (Ctrl-D)", "Enable unit (Ctrl-D)")
text := t.Units().SelectedType()
if text == "" {
text = "Choose unit type"
@ -131,7 +139,7 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
layout.Rigid(func(gtx C) D {
var dims D
if t.Units().SelectedType() != "" {
clearUnitBtnStyle := ActionIcon(t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit")
clearUnitBtnStyle := ActionIcon(gtx, t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit")
dims = clearUnitBtnStyle.Layout(gtx)
}
return D{Size: image.Pt(gtx.Dp(unit.Dp(48)), dims.Size.Y)}
@ -151,7 +159,7 @@ func (pe *UnitEditor) layoutUnitTypeChooser(gtx C, t *Tracker) D {
element := func(gtx C, i int) D {
w := LabelStyle{Text: names[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper}
if i == pe.searchList.TrackerList.Selected() {
for pe.SelectTypeBtn.Clicked() {
for pe.SelectTypeBtn.Clicked(gtx) {
t.Units().SetSelectedType(names[i])
}
return pe.SelectTypeBtn.Layout(gtx, w.Layout)
@ -232,28 +240,36 @@ func (p ParameterStyle) Layout(gtx C) D {
layout.Rigid(func(gtx C) D {
switch p.w.Parameter.Type() {
case tracker.IntegerParameter:
for _, e := range gtx.Events(&p.w.floatWidget) {
if ev, ok := e.(pointer.Event); ok && ev.Type == pointer.Scroll {
for p.Focus {
e, ok := gtx.Event(pointer.Filter{
Target: &p.w.floatWidget,
Kinds: pointer.Scroll,
ScrollBounds: image.Rectangle{Min: image.Pt(0, -1e6), Max: image.Pt(0, 1e6)},
})
if !ok {
break
}
if ev, ok := e.(pointer.Event); ok && ev.Kind == pointer.Scroll {
delta := math.Min(math.Max(float64(ev.Scroll.Y), -1), 1)
tracker.Int{IntData: p.w.Parameter}.Add(-int(delta))
}
}
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(200))
gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(40))
if !p.w.floatWidget.Dragging() {
p.w.floatWidget.Value = float32(p.w.Parameter.Value())
}
ra := p.w.Parameter.Range()
sliderStyle := material.Slider(p.Theme, &p.w.floatWidget, float32(ra.Min), float32(ra.Max))
if !p.w.floatWidget.Dragging() {
p.w.floatWidget.Value = (float32(p.w.Parameter.Value()) - float32(ra.Min)) / float32(ra.Max-ra.Min)
}
sliderStyle := material.Slider(p.Theme, &p.w.floatWidget)
sliderStyle.Color = p.Theme.Fg
r := image.Rectangle{Max: gtx.Constraints.Min}
area := clip.Rect(r).Push(gtx.Ops)
if p.Focus {
pointer.InputOp{Tag: &p.w.floatWidget, Types: pointer.Scroll, ScrollBounds: image.Rectangle{Min: image.Pt(0, -1e6), Max: image.Pt(0, 1e6)}}.Add(gtx.Ops)
event.Op(gtx.Ops, &p.w.floatWidget)
}
dims := sliderStyle.Layout(gtx)
area.Pop()
tracker.Int{IntData: p.w.Parameter}.Set(int(p.w.floatWidget.Value + 0.5))
tracker.Int{IntData: p.w.Parameter}.Set(int(p.w.floatWidget.Value*float32(ra.Max-ra.Min) + float32(ra.Min) + 0.5))
return dims
case tracker.BoolParameter:
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(60))
@ -305,10 +321,10 @@ func (p ParameterStyle) Layout(gtx C) D {
}
}
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(p.tracker.layoutMenu(instrName, &p.w.instrBtn, &p.w.instrMenu, unit.Dp(200),
layout.Rigid(p.tracker.layoutMenu(gtx, instrName, &p.w.instrBtn, &p.w.instrMenu, unit.Dp(200),
instrItems...,
)),
layout.Rigid(p.tracker.layoutMenu(unitName, &p.w.unitBtn, &p.w.unitMenu, unit.Dp(200),
layout.Rigid(p.tracker.layoutMenu(gtx, unitName, &p.w.unitBtn, &p.w.unitMenu, unit.Dp(200),
unitItems...,
)),
)