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 go 1.21
require ( require (
gioui.org v0.3.1 gioui.org v0.5.0
gioui.org/x v0.1.0 gioui.org/x v0.5.0
github.com/Masterminds/sprig v2.22.0+incompatible github.com/Masterminds/sprig v2.22.0+incompatible
github.com/hajimehoshi/oto v0.6.6 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/exp/shiny v0.0.0-20220827204233-334a2380cb91
golang.org/x/text v0.9.0
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
pipelined.dev/audio/vst2 v0.10.1-0.20240223162706-41e9b65fb5c2 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/mitchellh/reflectwalk v1.0.0 // indirect
github.com/stretchr/testify v1.6.1 // indirect github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // 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/image v0.7.0 // indirect
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f // indirect golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f // indirect
golang.org/x/sys v0.7.0 // indirect golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.9.0 // indirect
pipelined.dev/pipe v0.11.0 // indirect pipelined.dev/pipe v0.11.0 // indirect
pipelined.dev/signal v0.10.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= eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
gioui.org v0.3.1 h1:hslYkrkIWvx28Mxe3A87opl+8s9mnWsnWmPDh11+zco= eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
gioui.org v0.3.1/go.mod h1:2atiYR4upH71/6ehnh6XsUELa7JZOrOHHNMDxGBZF0Q= 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-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 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= 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 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= 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.5.0 h1:NVKTn5AZuYhkAnF7MYcy1dIes36+U1N4gUTsgBhfr4A=
gioui.org/x v0.1.0/go.mod h1:5qZxjtK/TVznMlcEOyn8OheiCZlArxF3IKnLqSehKXQ= 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 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo= 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= 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 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= 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 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 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 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/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-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-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-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 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 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= 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= 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-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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 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-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 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:qrI7YY5ZH4pJflMfzum2TKvA1NaX+H4feaA6jweX2R8=
pipelined.dev/audio/vst2 v0.10.1-0.20240223162706-41e9b65fb5c2/go.mod h1:wETLxsbBPftj6t4iVBCXvH/Xgd27ZgIC4hNnHDYNuz8= 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= 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 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) ret := TipIcon(th, &w.TipClickable, icon, tip)
for w.Clickable.Clicked() { for w.Clickable.Clicked(gtx) {
w.Action.Do() w.Action.Do()
} }
if !w.Action.Allowed() { 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 icon := offIcon
tip := offTip tip := offTip
if w.Bool.Value() { if w.Bool.Value() {
icon = onIcon icon = onIcon
tip = onTip tip = onTip
} }
for w.Clickable.Clicked() { for w.Clickable.Clicked(gtx) {
w.Bool.Toggle() w.Bool.Toggle()
} }
ibStyle := material.IconButton(th, &w.Clickable, widgetForIcon(icon), "") 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) return t.TipArea.Layout(gtx, t.Tooltip, t.IconButtonStyle.Layout)
} }
func ActionButton(th *material.Theme, w *ActionClickable, text string) material.ButtonStyle { func ActionButton(gtx C, th *material.Theme, w *ActionClickable, text string) material.ButtonStyle {
for w.Clickable.Clicked() { for w.Clickable.Clicked(gtx) {
w.Action.Do() w.Action.Do()
} }
ret := material.Button(th, &w.Clickable, text) ret := material.Button(th, &w.Clickable, text)
@ -116,8 +116,8 @@ func ActionButton(th *material.Theme, w *ActionClickable, text string) material.
return ret return ret
} }
func ToggleButton(th *material.Theme, b *BoolClickable, text string) material.ButtonStyle { func ToggleButton(gtx C, th *material.Theme, b *BoolClickable, text string) material.ButtonStyle {
for b.Clickable.Clicked() { for b.Clickable.Clicked(gtx) {
b.Bool.Toggle() b.Bool.Toggle()
} }
ret := material.Button(th, &b.Clickable, text) ret := material.Button(th, &b.Clickable, text)

View File

@ -1,6 +1,7 @@
package gioui package gioui
import ( import (
"gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op/paint" "gioui.org/op/paint"
@ -11,10 +12,11 @@ import (
) )
type Dialog struct { type Dialog struct {
BtnAlt *ActionClickable BtnAlt *ActionClickable
BtnOk *ActionClickable BtnOk *ActionClickable
BtnCancel *ActionClickable BtnCancel *ActionClickable
tag bool tag bool
keyFilters []event.Filter
} }
type DialogStyle struct { type DialogStyle struct {
@ -30,45 +32,77 @@ type DialogStyle struct {
} }
func NewDialog(ok, alt, cancel tracker.Action) *Dialog { func NewDialog(ok, alt, cancel tracker.Action) *Dialog {
return &Dialog{ ret := &Dialog{
BtnOk: NewActionClickable(ok), BtnOk: NewActionClickable(ok),
BtnAlt: NewActionClickable(alt), BtnAlt: NewActionClickable(alt),
BtnCancel: NewActionClickable(cancel), 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{ ret := DialogStyle{
dialog: dialog, dialog: dialog,
Title: title, Title: title,
Text: text, Text: text,
Inset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12), Left: unit.Dp(20), Right: unit.Dp(20)}, Inset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12), Left: unit.Dp(20), Right: unit.Dp(20)},
TextInset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12)}, TextInset: layout.Inset{Top: unit.Dp(12), Bottom: unit.Dp(12)},
AltStyle: ActionButton(th, dialog.BtnAlt, "Alt"), AltStyle: ActionButton(gtx, th, dialog.BtnAlt, "Alt"),
OkStyle: ActionButton(th, dialog.BtnOk, "Ok"), OkStyle: ActionButton(gtx, th, dialog.BtnOk, "Ok"),
CancelStyle: ActionButton(th, dialog.BtnCancel, "Cancel"), CancelStyle: ActionButton(gtx, th, dialog.BtnCancel, "Cancel"),
Shaper: th.Shaper, Shaper: th.Shaper,
} }
return ret return ret
} }
func (d *DialogStyle) Layout(gtx C) D { func (d *Dialog) handleKeysForButton(gtx C, btn, next, prev *ActionClickable) {
if !d.dialog.BtnOk.Clickable.Focused() && !d.dialog.BtnCancel.Clickable.Focused() && !d.dialog.BtnAlt.Clickable.Focused() { for {
d.dialog.BtnCancel.Clickable.Focus() 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) paint.Fill(gtx.Ops, dialogBgColor)
text := func(gtx C) D { 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) 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 visible := true
return layout.Center.Layout(gtx, func(gtx C) D { return layout.Center.Layout(gtx, func(gtx C) D {
return Popup(&visible).Layout(gtx, func(gtx C) D { return Popup(&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 d.Inset.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(Label(d.Title, highEmphasisTextColor, d.Shaper)), layout.Rigid(Label(d.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 package gioui
import ( import (
"bytes"
"image" "image"
"image/color" "image/color"
"io"
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/io/transfer"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
@ -27,7 +31,6 @@ type DragList struct {
swapped bool swapped bool
focused bool focused bool
requestFocus bool requestFocus bool
mainTag bool
} }
type FilledDragListStyle struct { type FilledDragListStyle struct {
@ -72,11 +75,7 @@ func (s FilledDragListStyle) Layout(gtx C) D {
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
keys := key.Set("↑|↓|Ctrl-↑|Ctrl-↓|Shift-↑|Shift-↓|⇞|⇟|Ctrl-⇞|Ctrl-⇟|Ctrl-A|Ctrl-C|Ctrl-X|Ctrl-V|⌦|Ctrl-⌫") event.Op(gtx.Ops, s.dragList)
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)
if s.dragList.List.Axis == layout.Horizontal { if s.dragList.List.Axis == layout.Horizontal {
gtx.Constraints.Min.X = gtx.Constraints.Max.X gtx.Constraints.Min.X = gtx.Constraints.Max.X
@ -86,11 +85,39 @@ func (s FilledDragListStyle) Layout(gtx C) D {
if s.dragList.requestFocus { if s.dragList.requestFocus {
s.dragList.requestFocus = false 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) { prevKey := key.NameUpArrow
switch ke := ke.(type) { 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: case key.FocusEvent:
s.dragList.focused = ke.Focus s.dragList.focused = ke.Focus
if !s.dragList.focused { if !s.dragList.focused {
@ -101,10 +128,13 @@ func (s FilledDragListStyle) Layout(gtx C) D {
break break
} }
s.dragList.command(gtx, ke) s.dragList.command(gtx, ke)
case clipboard.Event: case transfer.DataEvent:
s.dragList.TrackerList.PasteElements([]byte(ke.Text)) 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) _, 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()) 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) e, ok := ev.(pointer.Event)
if !ok { if !ok {
continue continue
} }
switch e.Type { switch e.Kind {
case pointer.Enter: case pointer.Enter:
s.dragList.HoverItem = index s.dragList.HoverItem = index
case pointer.Leave: case pointer.Leave:
@ -148,22 +185,28 @@ func (s FilledDragListStyle) Layout(gtx C) D {
if !e.Modifiers.Contain(key.ModShift) { if !e.Modifiers.Contain(key.ModShift) {
s.dragList.TrackerList.SetSelected2(index) 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) rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area := clip.Rect(rect).Push(gtx.Ops) area := clip.Rect(rect).Push(gtx.Ops)
pointer.InputOp{Tag: &s.dragList.tags[index], event.Op(gtx.Ops, &s.dragList.tags[index])
Types: pointer.Press | pointer.Enter | pointer.Leave,
}.Add(gtx.Ops)
area.Pop() area.Pop()
if index == s.dragList.TrackerList.Selected() && isMutable { 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) e, ok := ev.(pointer.Event)
if !ok { if !ok {
continue continue
} }
switch e.Type { switch e.Kind {
case pointer.Press: case pointer.Press:
s.dragList.dragID = e.PointerID s.dragList.dragID = e.PointerID
s.dragList.drag = true s.dragList.drag = true
@ -186,17 +229,12 @@ func (s FilledDragListStyle) Layout(gtx C) D {
swap = 1 swap = 1
} }
} }
case pointer.Release: case pointer.Release, pointer.Cancel:
fallthrough
case pointer.Cancel:
s.dragList.drag = false s.dragList.drag = false
} }
} }
area := clip.Rect(rect).Push(gtx.Ops) area := clip.Rect(rect).Push(gtx.Ops)
pointer.InputOp{Tag: &s.dragList.focused, event.Op(gtx.Ops, &s.dragList.focused)
Types: pointer.Drag | pointer.Press | pointer.Release,
Grab: s.dragList.drag,
}.Add(gtx.Ops)
pointer.CursorGrab.Add(gtx.Ops) pointer.CursorGrab.Add(gtx.Ops)
area.Pop() area.Pop()
} }
@ -225,7 +263,7 @@ func (s FilledDragListStyle) Layout(gtx C) D {
dims := s.dragList.List.Layout(gtx, count, listElem) dims := s.dragList.List.Layout(gtx, count, listElem)
if !s.dragList.swapped && swap != 0 { if !s.dragList.swapped && swap != 0 {
if s.dragList.TrackerList.MoveElements(swap) { if s.dragList.TrackerList.MoveElements(swap) {
op.InvalidateOp{}.Add(gtx.Ops) gtx.Execute(op.InvalidateCmd{})
} }
s.dragList.swapped = true s.dragList.swapped = true
} else { } else {
@ -238,12 +276,12 @@ func (e *DragList) command(gtx layout.Context, k key.Event) {
if k.Modifiers.Contain(key.ModShortcut) { if k.Modifiers.Contain(key.ModShortcut) {
switch k.Name { switch k.Name {
case "V": case "V":
clipboard.ReadOp{Tag: &e.mainTag}.Add(gtx.Ops) gtx.Execute(clipboard.ReadCmd{Tag: e})
return return
case "C", "X": case "C", "X":
data, ok := e.TrackerList.CopyElements() data, ok := e.TrackerList.CopyElements()
if ok && (k.Name == "C" || e.TrackerList.DeleteElements(false)) { 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 return
case "A": case "A":

View File

@ -1,14 +1,17 @@
package gioui package gioui
import ( import (
"bytes"
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"io"
"strconv" "strconv"
"strings" "strings"
"gioui.org/font" "gioui.org/font"
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "gioui.org/op"
@ -45,6 +48,9 @@ type InstrumentEditor struct {
wasFocused bool wasFocused bool
presetMenuItems []MenuItem presetMenuItems []MenuItem
presetMenu Menu presetMenu Menu
commentKeyFilters []event.Filter
searchkeyFilters []event.Filter
nameKeyFilters []event.Filter
} }
func NewInstrumentEditor(model *tracker.Model) *InstrumentEditor { 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)}) ret.presetMenuItems = append(ret.presetMenuItems, MenuItem{Text: name, IconBytes: icons.ImageAudiotrack, Doer: model.LoadPreset(index)})
return true 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 return ret
} }
@ -83,14 +100,16 @@ func (ie *InstrumentEditor) Focused() bool {
return ie.unitDragList.focused return ie.unitDragList.focused
} }
func (ie *InstrumentEditor) ChildFocused() bool { func (ie *InstrumentEditor) childFocused(gtx C) bool {
return ie.unitEditor.sliderList.Focused() || ie.instrumentDragList.Focused() || ie.commentEditor.Focused() || ie.nameEditor.Focused() || ie.searchEditor.Focused() || return ie.unitEditor.sliderList.Focused() ||
ie.addUnitBtn.Clickable.Focused() || ie.commentExpandBtn.Clickable.Focused() || ie.presetMenuBtn.Clickable.Focused() || ie.deleteInstrumentBtn.Clickable.Focused() || ie.copyInstrumentBtn.Clickable.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 { func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
ie.wasFocused = ie.Focused() || ie.ChildFocused() ie.wasFocused = ie.Focused() || ie.childFocused(gtx)
fullscreenBtnStyle := ToggleIcon(t.Theme, ie.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, "Enlarge (Ctrl+E)", "Shrink (Ctrl+E)") fullscreenBtnStyle := ToggleIcon(gtx, t.Theme, ie.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, "Enlarge (Ctrl+E)", "Shrink (Ctrl+E)")
octave := func(gtx C) D { octave := func(gtx C) D {
in := layout.UniformInset(unit.Dp(1)) in := layout.UniformInset(unit.Dp(1))
@ -99,7 +118,7 @@ func (ie *InstrumentEditor) Layout(gtx C, t *Tracker) D {
return dims 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, ret := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
return layout.Flex{}.Layout( 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 { func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
header := func(gtx C) 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") presetMenuBtnStyle := TipIcon(t.Theme, ie.presetMenuBtn, icons.NavigationMenu, "Load preset")
copyInstrumentBtnStyle := TipIcon(t.Theme, ie.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument") copyInstrumentBtnStyle := TipIcon(t.Theme, ie.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument")
saveInstrumentBtnStyle := TipIcon(t.Theme, ie.saveInstrumentBtn, icons.ContentSave, "Save instrument") saveInstrumentBtnStyle := TipIcon(t.Theme, ie.saveInstrumentBtn, icons.ContentSave, "Save instrument")
loadInstrumentBtnStyle := TipIcon(t.Theme, ie.loadInstrumentBtn, icons.FileFolderOpen, "Load 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) 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 { 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) 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") writer, err := t.Explorer.CreateFile(t.InstrumentName().Value() + ".yml")
if err != nil { if err != nil {
continue continue
@ -166,7 +185,7 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
t.SaveInstrument(writer) t.SaveInstrument(writer)
} }
for ie.loadInstrumentBtn.Clickable.Clicked() { for ie.loadInstrumentBtn.Clickable.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
@ -199,11 +218,11 @@ func (ie *InstrumentEditor) layoutInstrumentHeader(gtx C, t *Tracker) D {
layout.Rigid(deleteInstrumentBtnStyle.Layout)) layout.Rigid(deleteInstrumentBtnStyle.Layout))
} }
for ie.presetMenuBtn.Clickable.Clicked() { for ie.presetMenuBtn.Clickable.Clicked(gtx) {
ie.presetMenu.Visible = true 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() { if ie.commentEditor.Text() != ie.commentString.Value() {
ie.commentEditor.SetText(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(header),
layout.Rigid(func(gtx C) D { 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() 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 {
for _, event := range gtx.Events(&ie.unitDragList) { event, ok := gtx.Event(ie.commentKeyFilters...)
if !ok {
break
}
if e, ok := event.(key.Event); ok && e.State == key.Press && e.Name == key.NameEscape { if e, ok := event.(key.Event); ok && e.State == key.Press && e.Name == key.NameEscape {
ie.instrumentDragList.Focus() ie.instrumentDragList.Focus()
} }
@ -249,13 +271,6 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
k := byte(255 - level*127) k := byte(255 - level*127)
color := color.NRGBA{R: 255, G: k, B: 255, A: 255} color := color.NRGBA{R: 255, G: k, B: 255, A: 255}
if i == ie.instrumentDragList.TrackerList.Selected() { 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() { if n := name; n != ie.nameEditor.Text() {
ie.nameEditor.SetText(n) ie.nameEditor.SetText(n)
} }
@ -264,11 +279,30 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
editor.HintColor = instrumentNameHintColor editor.HintColor = instrumentNameHintColor
editor.TextSize = unit.Sp(12) editor.TextSize = unit.Sp(12)
editor.Font = labelDefaultFont 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 { dims := layout.Center.Layout(gtx, func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: &ie.nameEditor, Keys: globalKeys}.Add(gtx.Ops)
return editor.Layout(gtx) 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()) ie.nameString.Set(ie.nameEditor.Text())
return dims 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 op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: ie.instrumentDragList, Keys: "↓|⏎|⌤"}.Add(gtx.Ops) for {
event, ok := gtx.Event(
for _, event := range gtx.Events(ie.instrumentDragList) { 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) { switch e := event.(type) {
case key.Event: case key.Event:
switch e.State { switch e.State {
@ -308,7 +348,7 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
case key.NameDownArrow: case key.NameDownArrow:
ie.unitDragList.Focus() ie.unitDragList.Focus()
case key.NameReturn, key.NameEnter: case key.NameReturn, key.NameEnter:
ie.nameEditor.Focus() gtx.Execute(key.FocusCmd{Tag: ie.nameEditor})
l := len(ie.nameEditor.Text()) l := len(ie.nameEditor.Text())
ie.nameEditor.SetCaret(l, l) ie.nameEditor.SetCaret(l, l)
} }
@ -321,9 +361,10 @@ func (ie *InstrumentEditor) layoutInstrumentList(gtx C, t *Tracker) D {
instrumentList.LayoutScrollBar(gtx) instrumentList.LayoutScrollBar(gtx)
return dims return dims
} }
func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D { func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
// TODO: how to ie.unitDragList.Focus() // TODO: how to ie.unitDragList.Focus()
addUnitBtnStyle := ActionIcon(t.Theme, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)") addUnitBtnStyle := ActionIcon(gtx, t.Theme, ie.addUnitBtn, icons.ContentAdd, "Add unit (Enter)")
addUnitBtnStyle.IconButtonStyle.Color = t.Theme.ContrastFg addUnitBtnStyle.IconButtonStyle.Color = t.Theme.ContrastFg
addUnitBtnStyle.IconButtonStyle.Background = t.Theme.Fg addUnitBtnStyle.IconButtonStyle.Background = t.Theme.Fg
addUnitBtnStyle.IconButtonStyle.Inset = layout.UniformInset(unit.Dp(4)) 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, return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Flexed(1, func(gtx C) D { layout.Flexed(1, func(gtx C) D {
if i == ie.unitDragList.TrackerList.Selected() { if i == ie.unitDragList.TrackerList.Selected() {
for _, ev := range ie.searchEditor.Events() { editor := material.Editor(t.Theme, ie.searchEditor, "---")
_, ok := ev.(widget.SubmitEvent) 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 { if ok {
txt := "" txt := ""
ie.unitDragList.Focus() ie.unitDragList.Focus()
@ -383,23 +442,16 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
continue 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) 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 { if ie.searchEditor.Text() != txt {
str.Set(ie.searchEditor.Text()) 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, return layout.Stack{Alignment: layout.SE}.Layout(gtx,
layout.Expanded(func(gtx C) D { 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() 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 {
for _, event := range gtx.Events(ie.unitDragList) { 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) { switch e := event.(type) {
case key.Event: case key.Event:
switch e.State { switch e.State {
@ -437,13 +499,13 @@ func (ie *InstrumentEditor) layoutUnitList(gtx C, t *Tracker) D {
ie.unitEditor.sliderList.Focus() ie.unitEditor.sliderList.Focus()
case key.NameDeleteBackward: case key.NameDeleteBackward:
t.Units().SetSelectedType("") t.Units().SetSelectedType("")
ie.searchEditor.Focus() gtx.Execute(key.FocusCmd{Tag: ie.searchEditor})
l := len(ie.searchEditor.Text()) l := len(ie.searchEditor.Text())
ie.searchEditor.SetCaret(l, l) ie.searchEditor.SetCaret(l, l)
case key.NameReturn: case key.NameEnter, key.NameReturn:
t.Model.AddUnit(e.Modifiers.Contain(key.ModCtrl)).Do() t.Model.AddUnit(e.Modifiers.Contain(key.ModCtrl)).Do()
ie.searchEditor.SetText("") ie.searchEditor.SetText("")
ie.searchEditor.Focus() gtx.Execute(key.FocusCmd{Tag: ie.searchEditor})
l := len(ie.searchEditor.Text()) l := len(ie.searchEditor.Text())
ie.searchEditor.SetCaret(l, l) ie.searchEditor.SetCaret(l, l)
} }

View File

@ -3,14 +3,9 @@ package gioui
import ( import (
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/op"
) )
// globalKeys is a list of keys that are handled globally by the app. var noteMap = map[key.Name]int{
// 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{
"Z": -12, "Z": -12,
"S": -11, "S": -11,
"X": -10, "X": -10,
@ -46,12 +41,12 @@ var noteMap = map[string]int{
} }
// KeyEvent handles incoming key events and returns true if repaint is needed. // 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 { if e.State == key.Press {
switch e.Name { switch e.Name {
case "V": case "V":
if e.Modifiers.Contain(key.ModShortcut) { if e.Modifiers.Contain(key.ModShortcut) {
clipboard.ReadOp{Tag: t}.Add(o) gtx.Execute(clipboard.ReadCmd{Tag: t})
return return
} }
case "Z": case "Z":

View File

@ -4,6 +4,7 @@ import (
"image" "image"
"image/color" "image/color"
"gioui.org/io/event"
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "gioui.org/op"
@ -65,12 +66,19 @@ func (m *MenuStyle) Layout(gtx C, items ...MenuItem) D {
m.Menu.tags = append(m.Menu.tags, false) m.Menu.tags = append(m.Menu.tags, false)
} }
// handle pointer events for this item // 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) e, ok := ev.(pointer.Event)
if !ok { if !ok {
continue continue
} }
switch e.Type { switch e.Kind {
case pointer.Press: case pointer.Press:
item.Doer.Do() item.Doer.Do()
m.Menu.Visible = false m.Menu.Visible = false
@ -130,9 +138,7 @@ func (m *MenuStyle) Layout(gtx C, items ...MenuItem) D {
if item.Doer.Allowed() { if item.Doer.Allowed() {
rect := image.Rect(0, 0, dims.Size.X, dims.Size.Y) rect := image.Rect(0, 0, dims.Size.X, dims.Size.Y)
area := clip.Rect(rect).Push(gtx.Ops) area := clip.Rect(rect).Push(gtx.Ops)
pointer.InputOp{Tag: &m.Menu.tags[i], event.Op(gtx.Ops, &m.Menu.tags[i])
Types: pointer.Press | pointer.Enter | pointer.Leave,
}.Add(gtx.Ops)
area.Pop() area.Pop()
} }
return dims 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 { func (tr *Tracker) layoutMenu(gtx C, title string, clickable *widget.Clickable, menu *Menu, width unit.Dp, items ...MenuItem) layout.Widget {
for clickable.Clicked() { for clickable.Clicked(gtx) {
menu.Visible = true menu.Visible = true
} }
m := PopupMenu(menu, tr.Theme.Shaper) 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 { 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) { switch e := e.(type) {
case key.Event: case key.Event:
if e.State == key.Release { 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 op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
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 Surface{Gray: 24, Focus: te.scrollTable.Focused()}.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, 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 { 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 { 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") addSemitoneBtnStyle := ActionButton(gtx, t.Theme, te.AddSemitoneBtn, "+1")
subtractSemitoneBtnStyle := ActionButton(t.Theme, te.SubtractSemitoneBtn, "-1") subtractSemitoneBtnStyle := ActionButton(gtx, t.Theme, te.SubtractSemitoneBtn, "-1")
addOctaveBtnStyle := ActionButton(t.Theme, te.AddOctaveBtn, "+12") addOctaveBtnStyle := ActionButton(gtx, t.Theme, te.AddOctaveBtn, "+12")
subtractOctaveBtnStyle := ActionButton(t.Theme, te.SubtractOctaveBtn, "-12") subtractOctaveBtnStyle := ActionButton(gtx, t.Theme, te.SubtractOctaveBtn, "-12")
noteOffBtnStyle := ActionButton(t.Theme, te.NoteOffBtn, "Note Off") noteOffBtnStyle := ActionButton(gtx, t.Theme, te.NoteOffBtn, "Note Off")
deleteTrackBtnStyle := ActionIcon(t.Theme, te.DeleteTrackBtn, icons.ActionDelete, "Delete track\n(Ctrl+Shift+T)") deleteTrackBtnStyle := ActionIcon(gtx, 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)") newTrackBtnStyle := ActionIcon(gtx, t.Theme, te.NewTrackBtn, icons.ContentAdd, "Add track\n(Ctrl+T)")
in := layout.UniformInset(unit.Dp(1)) in := layout.UniformInset(unit.Dp(1))
voiceUpDown := func(gtx C) D { voiceUpDown := func(gtx C) D {
numStyle := NumericUpDown(t.Theme, te.TrackVoices, "Number of voices for this track") numStyle := NumericUpDown(t.Theme, te.TrackVoices, "Number of voices for this track")
return in.Layout(gtx, numStyle.Layout) 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, 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(addSemitoneBtnStyle.Layout),
@ -304,7 +346,7 @@ func (te *NoteEditor) command(gtx C, t *Tracker, e key.Event) {
} }
var n byte var n byte
if t.Model.Notes().Effect(te.scrollTable.Table.Cursor().X) { 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()) n = t.Model.Notes().Value(te.scrollTable.Table.Cursor())
t.Model.Notes().FillNibble(byte(nibbleValue), t.Model.Notes().LowNibble()) t.Model.Notes().FillNibble(byte(nibbleValue), t.Model.Notes().LowNibble())
goto validNote goto validNote

View File

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

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "gioui.org/op"
@ -19,9 +20,9 @@ import (
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
) )
const patternCellHeight = 16 const patternCellHeight = unit.Dp(16)
const patternCellWidth = 16 const patternCellWidth = unit.Dp(16)
const patternRowMarkerWidth = 30 const patternRowMarkerWidth = unit.Dp(30)
const orderTitleHeight = unit.Dp(52) const orderTitleHeight = unit.Dp(52)
type OrderEditor struct { type OrderEditor struct {
@ -57,18 +58,11 @@ func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
t.TrackEditor.scrollTable.RowTitleList.CenterOn(cursor.Y) t.TrackEditor.scrollTable.RowTitleList.CenterOn(cursor.Y)
} }
for _, e := range gtx.Events(&oe.tag) { oe.handleEvents(gtx, t)
switch e := e.(type) {
case key.Event:
if e.State != key.Press {
continue
}
oe.command(gtx, t, e)
}
}
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
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 { colTitle := func(gtx C, i int) D {
h := gtx.Dp(orderTitleHeight) 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)) gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6))
title := t.Model.Order().Title(i) title := t.Model.Order().Title(i)
LabelStyle{Alignment: layout.NW, Text: title, FontSize: unit.Sp(12), Color: mediumEmphasisTextColor, Shaper: t.Theme.Shaper}.Layout(gtx) 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 { rowTitle := func(gtx C, j int) D {
w := gtx.Dp(unit.Dp(30)) w := gtx.Dp(unit.Dp(30))
if playPos := t.PlayPosition(); t.SongPanel.PlayingBtn.Bool.Value() && j == playPos.OrderRow { 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 color := rowMarkerPatternTextColor
if l := t.Loop(); j >= l.Start && j < l.Start+l.Length { 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) paint.ColorOp{Color: color}.Add(gtx.Ops)
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j)), op.CallOp{}) widget.Label{}.Layout(gtx, t.Theme.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() 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) paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops)
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop() defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, val, op.CallOp{}) widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.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) 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) 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) { func (oe *OrderEditor) command(gtx C, t *Tracker, e key.Event) {
switch e.Name { switch e.Name {
case key.NameDeleteBackward: 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() 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) t.Model.Order().SetValue(oe.scrollTable.Table.Cursor(), iv)
oe.scrollTable.EnsureCursorVisible() oe.scrollTable.EnsureCursorVisible()
} }

View File

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

View File

@ -1,11 +1,15 @@
package gioui package gioui
import ( import (
"bytes"
"image" "image"
"io"
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/io/transfer"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
@ -20,7 +24,6 @@ type ScrollTable struct {
Table tracker.Table Table tracker.Table
focused bool focused bool
requestFocus bool requestFocus bool
tag bool
colTag bool colTag bool
rowTag bool rowTag bool
cursorMoved bool cursorMoved bool
@ -85,55 +88,10 @@ func (st *ScrollTable) ChildFocused() bool {
func (s ScrollTableStyle) Layout(gtx C) D { func (s ScrollTableStyle) Layout(gtx C) D {
p := image.Pt(gtx.Dp(s.RowTitleWidth), gtx.Dp(s.ColumnTitleHeight)) p := image.Pt(gtx.Dp(s.RowTitleWidth), gtx.Dp(s.ColumnTitleHeight))
s.handleEvents(gtx)
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()
}
}
return Surface{Gray: 24, Focus: s.ScrollTable.Focused() || s.ScrollTable.ChildFocused()}.Layout(gtx, func(gtx C) D { 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() 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 dims := gtx.Constraints.Max
s.layoutColTitles(gtx, p) s.layoutColTitles(gtx, p)
s.layoutRowTitles(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) { func (s ScrollTableStyle) layoutTable(gtx C, p image.Point) {
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
if s.ScrollTable.requestFocus { if s.ScrollTable.requestFocus {
s.ScrollTable.requestFocus = false 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) cellWidth := gtx.Dp(s.CellWidth)
cellHeight := gtx.Dp(s.CellHeight) cellHeight := gtx.Dp(s.CellHeight)
@ -162,14 +211,12 @@ func (s ScrollTableStyle) layoutTable(gtx C, p image.Point) {
colP := s.ColTitleStyle.dragList.List.Position colP := s.ColTitleStyle.dragList.List.Position
rowP := s.RowTitleStyle.dragList.List.Position rowP := s.RowTitleStyle.dragList.List.Position
defer op.Offset(image.Pt(-colP.Offset, -rowP.Offset)).Push(gtx.Ops).Pop() defer op.Offset(image.Pt(-colP.Offset, -rowP.Offset)).Push(gtx.Ops).Pop()
for x := colP.First; x < colP.First+colP.Count; x++ { for x := 0; x < colP.Count; x++ {
offs := op.Offset(image.Point{}).Push(gtx.Ops) for y := 0; y < rowP.Count; y++ {
for y := rowP.First; y < rowP.First+rowP.Count; y++ { o := op.Offset(image.Pt(cellWidth*x, cellHeight*y)).Push(gtx.Ops)
s.element(gtx, x, y) s.element(gtx, x+colP.First, y+rowP.First)
op.Offset(image.Pt(0, cellHeight)).Add(gtx.Ops) 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.Max.Y -= p.Y
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
key.InputOp{Tag: &s.ScrollTable.rowTag, Keys: "→"}.Add(gtx.Ops) event.Op(gtx.Ops, &s.ScrollTable.rowTag)
s.RowTitleStyle.Layout(gtx) 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.Max.X -= p.X
gtx.Constraints.Min.X = gtx.Constraints.Max.X gtx.Constraints.Min.X = gtx.Constraints.Max.X
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop() defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
key.InputOp{Tag: &s.ScrollTable.colTag, Keys: "↓"}.Add(gtx.Ops) event.Op(gtx.Ops, &s.ScrollTable.colTag)
s.ColTitleStyle.Layout(gtx) s.ColTitleStyle.Layout(gtx)
} }
@ -210,7 +257,7 @@ func (s *ScrollTable) command(gtx C, e key.Event) {
if !ok { if !ok {
return 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" { if e.Name == "X" {
s.Table.Clear() s.Table.Clear()
} }
@ -218,7 +265,7 @@ func (s *ScrollTable) command(gtx C, e key.Event) {
} }
case "V": case "V":
if e.Modifiers.Contain(key.ModShortcut) { if e.Modifiers.Contain(key.ModShortcut) {
clipboard.ReadOp{Tag: &s.tag}.Add(gtx.Ops) gtx.Execute(clipboard.ReadCmd{Tag: s})
} }
return return
case key.NameDeleteBackward, key.NameDeleteForward: case key.NameDeleteBackward, key.NameDeleteForward:

View File

@ -4,6 +4,7 @@ import (
"image" "image"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/io/event"
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "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) rect := image.Rect(0, gtx.Constraints.Min.Y-scrWidth, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area = clip.Rect(rect).Push(gtx.Ops) area = clip.Rect(rect).Push(gtx.Ops)
} }
pointer.InputOp{Tag: &s.dragStart, event.Op(gtx.Ops, &s.dragStart)
Types: pointer.Drag | pointer.Press | pointer.Cancel | pointer.Release,
Grab: s.dragging,
}.Add(gtx.Ops)
area.Pop() area.Pop()
stack.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) e, ok := ev.(pointer.Event)
if !ok { if !ok {
continue continue
} }
switch e.Type { switch e.Kind {
case pointer.Press: case pointer.Press:
if s.Axis == layout.Horizontal { if s.Axis == layout.Horizontal {
s.dragStart = e.Position.X 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) rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area2 := clip.Rect(rect).Push(gtx.Ops) area2 := clip.Rect(rect).Push(gtx.Ops)
defer pointer.PassOp{}.Push(gtx.Ops).Pop() defer pointer.PassOp{}.Push(gtx.Ops).Pop()
pointer.InputOp{Tag: &s.tag, event.Op(gtx.Ops, &s.tag)
Types: pointer.Enter | pointer.Leave,
}.Add(gtx.Ops)
area2.Pop() 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) e, ok := ev.(pointer.Event)
if !ok { if !ok {
continue continue
} }
switch e.Type { switch e.Kind {
case pointer.Enter: case pointer.Enter:
s.hovering = true s.hovering = true
case pointer.Leave: 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)) gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(36))
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, 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(gtx, "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, "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)) in := layout.UniformInset(unit.Dp(1))
panicBtnStyle := ToggleButton(tr.Theme, t.PanicBtn, "Panic (F12)") panicBtnStyle := ToggleButton(gtx, tr.Theme, t.PanicBtn, "Panic (F12)")
rewindBtnStyle := ActionIcon(tr.Theme, t.RewindBtn, icons.AVFastRewind, "Rewind\n(F5)") rewindBtnStyle := ActionIcon(gtx, 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)") playBtnStyle := ToggleIcon(gtx, 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)") recordBtnStyle := ToggleIcon(gtx, 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)") noteTrackBtnStyle := ToggleIcon(gtx, 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)") 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, return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {

View File

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

View File

@ -5,14 +5,14 @@ import (
"image" "image"
"io" "io"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
"time" "time"
"gioui.org/app" "gioui.org/app"
"gioui.org/io/clipboard" "gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/io/system" "gioui.org/io/system"
"gioui.org/io/transfer"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
@ -34,7 +34,7 @@ type (
TopHorizontalSplit *Split TopHorizontalSplit *Split
BottomHorizontalSplit *Split BottomHorizontalSplit *Split
VerticalSplit *Split VerticalSplit *Split
KeyPlaying map[string]tracker.NoteID KeyPlaying map[key.Name]tracker.NoteID
PopupAlert *PopupAlert PopupAlert *PopupAlert
SaveChangesDialog *Dialog SaveChangesDialog *Dialog
@ -76,7 +76,7 @@ func NewTracker(model *tracker.Model) *Tracker {
BottomHorizontalSplit: &Split{Ratio: -.6}, BottomHorizontalSplit: &Split{Ratio: -.6},
VerticalSplit: &Split{Axis: layout.Vertical}, 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()), SaveChangesDialog: NewDialog(model.SaveSong(), model.DiscardSong(), model.Cancel()),
WaveTypeDialog: NewDialog(model.ExportInt16(), model.ExportFloat(), model.Cancel()), WaveTypeDialog: NewDialog(model.ExportInt16(), model.ExportFloat(), model.Cancel()),
InstrumentEditor: NewInstrumentEditor(model), InstrumentEditor: NewInstrumentEditor(model),
@ -107,6 +107,11 @@ func (t *Tracker) Main() {
t.InstrumentEditor.Focus() t.InstrumentEditor.Focus()
recoveryTicker := time.NewTicker(time.Second * 30) recoveryTicker := time.NewTicker(time.Second * 30)
t.Explorer = explorer.NewExplorer(w) 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 var ops op.Ops
for { for {
if titleFooter != t.filePathString.Value() { if titleFooter != t.filePathString.Value() {
@ -121,9 +126,10 @@ func (t *Tracker) Main() {
case e := <-t.PlayerMessages: case e := <-t.PlayerMessages:
t.ProcessPlayerMessage(e) t.ProcessPlayerMessage(e)
w.Invalidate() w.Invalidate()
case e := <-w.Events(): case e := <-events:
switch e := e.(type) { switch e := e.(type) {
case system.DestroyEvent: case app.DestroyEvent:
acks <- struct{}{}
if canQuit { if canQuit {
t.Quit().Do() t.Quit().Do()
} }
@ -134,14 +140,18 @@ func (t *Tracker) Main() {
app.Title("Sointu Tracker"), app.Title("Sointu Tracker"),
) )
t.Explorer = explorer.NewExplorer(w) t.Explorer = explorer.NewExplorer(w)
go eventLoop(w, events, acks)
} }
case system.FrameEvent: case app.FrameEvent:
gtx := layout.NewContext(&ops, e) gtx := app.NewContext(&ops, e)
if t.SongPanel.PlayingBtn.Bool.Value() && t.SongPanel.NoteTracking.Bool.Value() { if t.SongPanel.PlayingBtn.Bool.Value() && t.SongPanel.NoteTracking.Bool.Value() {
t.TrackEditor.scrollTable.RowTitleList.CenterOn(t.PlaySongRow()) t.TrackEditor.scrollTable.RowTitleList.CenterOn(t.PlaySongRow())
} }
t.Layout(gtx, w) t.Layout(gtx, w)
e.Frame(gtx.Ops) e.Frame(gtx.Ops)
acks <- struct{}{}
default:
acks <- struct{}{}
} }
case <-recoveryTicker.C: case <-recoveryTicker.C:
t.SaveRecovery() t.SaveRecovery()
@ -158,6 +168,19 @@ func (t *Tracker) Main() {
t.quitWG.Done() 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() { func (t *Tracker) Exec() chan<- func() {
return t.execChan return t.execChan
} }
@ -167,22 +190,6 @@ func (t *Tracker) WaitQuitted() {
} }
func (t *Tracker) Layout(gtx layout.Context, w *app.Window) { 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()) 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() { if t.InstrumentEditor.enlargeBtn.Bool.Value() {
t.layoutTop(gtx) t.layoutTop(gtx)
@ -193,6 +200,27 @@ func (t *Tracker) Layout(gtx layout.Context, w *app.Window) {
} }
t.PopupAlert.Layout(gtx) t.PopupAlert.Layout(gtx)
t.showDialog(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) { func (t *Tracker) showDialog(gtx C) {
@ -201,12 +229,12 @@ func (t *Tracker) showDialog(gtx C) {
} }
switch t.Dialog() { switch t.Dialog() {
case tracker.NewSongChanges, tracker.OpenSongChanges, tracker.QuitChanges: 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.OkStyle.Text = "Save"
dstyle.AltStyle.Text = "Don't save" dstyle.AltStyle.Text = "Don't save"
dstyle.Layout(gtx) dstyle.Layout(gtx)
case tracker.Export: 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.OkStyle.Text = "Int16"
dstyle.AltStyle.Text = "Float32" dstyle.AltStyle.Text = "Float32"
dstyle.Layout(gtx) dstyle.Layout(gtx)

View File

@ -1,11 +1,14 @@
package gioui package gioui
import ( import (
"bytes"
"fmt" "fmt"
"image" "image"
"io"
"math" "math"
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
"gioui.org/io/event"
"gioui.org/io/key" "gioui.org/io/key"
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/layout" "gioui.org/layout"
@ -29,7 +32,6 @@ type UnitEditor struct {
ClearUnitBtn *ActionClickable ClearUnitBtn *ActionClickable
DisableUnitBtn *BoolClickable DisableUnitBtn *BoolClickable
SelectTypeBtn *widget.Clickable SelectTypeBtn *widget.Clickable
tag bool
caser cases.Caser caser cases.Caser
} }
@ -48,7 +50,15 @@ func NewUnitEditor(m *tracker.Model) *UnitEditor {
} }
func (pe *UnitEditor) Layout(gtx C, t *Tracker) D { 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) { switch e := e.(type) {
case key.Event: case key.Event:
if e.State == key.Press { 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 op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop() defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
key.InputOp{Tag: &pe.tag, Keys: "←|Shift-←|→|Shift-→|⎋"}.Add(gtx.Ops)
editorFunc := pe.layoutSliders editorFunc := pe.layoutSliders
if t.UnitSearching().Value() || pe.sliderList.TrackerList.Count() == 0 { 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 { 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 { 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) t.Alerts().Add("Unit copied to clipboard", tracker.Info)
} }
} }
copyUnitBtnStyle := TipIcon(t.Theme, pe.CopyUnitBtn, icons.ContentContentCopy, "Copy unit (Ctrl+C)") copyUnitBtnStyle := TipIcon(t.Theme, pe.CopyUnitBtn, icons.ContentContentCopy, "Copy unit (Ctrl+C)")
deleteUnitBtnStyle := ActionIcon(t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)") deleteUnitBtnStyle := ActionIcon(gtx, 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)") disableUnitBtnStyle := ToggleIcon(gtx, t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, "Disable unit (Ctrl-D)", "Enable unit (Ctrl-D)")
text := t.Units().SelectedType() text := t.Units().SelectedType()
if text == "" { if text == "" {
text = "Choose unit type" text = "Choose unit type"
@ -131,7 +139,7 @@ func (pe *UnitEditor) layoutFooter(gtx C, t *Tracker) D {
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(t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit") clearUnitBtnStyle := ActionIcon(gtx, t.Theme, pe.ClearUnitBtn, icons.ContentClear, "Clear unit")
dims = clearUnitBtnStyle.Layout(gtx) dims = clearUnitBtnStyle.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)}
@ -151,7 +159,7 @@ func (pe *UnitEditor) layoutUnitTypeChooser(gtx C, t *Tracker) D {
element := func(gtx C, i int) D { element := func(gtx C, i int) D {
w := LabelStyle{Text: names[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper} w := LabelStyle{Text: names[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12), Shaper: t.Theme.Shaper}
if i == pe.searchList.TrackerList.Selected() { if i == pe.searchList.TrackerList.Selected() {
for pe.SelectTypeBtn.Clicked() { for pe.SelectTypeBtn.Clicked(gtx) {
t.Units().SetSelectedType(names[i]) t.Units().SetSelectedType(names[i])
} }
return pe.SelectTypeBtn.Layout(gtx, w.Layout) return pe.SelectTypeBtn.Layout(gtx, w.Layout)
@ -232,28 +240,36 @@ func (p ParameterStyle) Layout(gtx C) D {
layout.Rigid(func(gtx C) D { layout.Rigid(func(gtx C) D {
switch p.w.Parameter.Type() { switch p.w.Parameter.Type() {
case tracker.IntegerParameter: case tracker.IntegerParameter:
for _, e := range gtx.Events(&p.w.floatWidget) { for p.Focus {
if ev, ok := e.(pointer.Event); ok && ev.Type == pointer.Scroll { 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) delta := math.Min(math.Max(float64(ev.Scroll.Y), -1), 1)
tracker.Int{IntData: p.w.Parameter}.Add(-int(delta)) tracker.Int{IntData: p.w.Parameter}.Add(-int(delta))
} }
} }
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(200)) gtx.Constraints.Min.X = gtx.Dp(unit.Dp(200))
gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(40)) 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() 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 sliderStyle.Color = p.Theme.Fg
r := image.Rectangle{Max: gtx.Constraints.Min} r := image.Rectangle{Max: gtx.Constraints.Min}
area := clip.Rect(r).Push(gtx.Ops) area := clip.Rect(r).Push(gtx.Ops)
if p.Focus { 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) dims := sliderStyle.Layout(gtx)
area.Pop() 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 return dims
case tracker.BoolParameter: case tracker.BoolParameter:
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(60)) 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, 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..., 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..., unitItems...,
)), )),
) )