Complete the Gio v0.10.2 GUI migration (working on Android).
- Event loop: call w.Event() and FrameEvent.Frame() on the same goroutine; no channel bridge (the bridge broke frame rendering). - clip: use clip.UniformRRect(...).Push(gtx.Ops) / Pop() for real clipping in layoutRRect (old .Add(ops) painted unclipped). - Window clear color is white in v0.10.2. - Fonts: always provide gofont; on Android use WithCollection(gofont.Collection()). - Button labels: explicitly set Color (zero-value NRGBA has A=0, i.e. transparent, so labels were invisible). - Filter: detect changes by comparing the editor text, since material.Editor.Layout consumes the editor's input events internally and a later FilterEd.Update(gtx) never sees them. - idPage: persist the manually entered ID with store.SetID (on Android nativeIdentities is unimplemented, so there are no id buttons to click). - Use U+2026 (gofont) instead of U+22EE (not in gofont) for the menu button. - Split getConfDir/noidLabelText out into impl_linux.go for the Linux build.
This commit is contained in:
parent
af1b878308
commit
e7218b1efd
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,3 +2,5 @@ cmd/passgo/passgo
|
||||||
cmd/passgo-gui/passgo-gui
|
cmd/passgo-gui/passgo-gui
|
||||||
nohup.out
|
nohup.out
|
||||||
*.apk
|
*.apk
|
||||||
|
*.idsig
|
||||||
|
/passgo-gui
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
//+build android
|
//go:build android
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.wow.st/gmp/passgo"
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
"gioui.org/io/event"
|
"gioui.org/io/event"
|
||||||
|
"git.wow.st/gmp/passgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -22,8 +22,14 @@ func init() {
|
||||||
|
|
||||||
func handleEvent(e event.Event) {
|
func handleEvent(e event.Event) {
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
case app.ViewEvent:
|
case app.AndroidViewEvent:
|
||||||
initPgp(e.View)
|
// A zero View means the view is detached (e.g. the activity going
|
||||||
|
// away on a recents-wipe); there is nothing to register for a
|
||||||
|
// detached view. A re-attach arrives as a fresh event with a live
|
||||||
|
// view.
|
||||||
|
if e.View != 0 {
|
||||||
|
initPgp(e.View)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
//+build darwin
|
//go:build darwin
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"gioui.org/font/opentype"
|
"gioui.org/font/opentype"
|
||||||
"gioui.org/text"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -22,22 +22,18 @@ func setFont() error {
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
fnts, err := opentype.ParseCollectionReaderAt(f)
|
defer f.Close()
|
||||||
|
data, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
log(Info, "Cannot read system font collection")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fnts, err := opentype.ParseCollection(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log(Info, "Cannot parse font collection")
|
log(Info, "Cannot parse font collection")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fnt, err := fnts.Font(0)
|
collection = append(collection, fnts...)
|
||||||
if err != nil {
|
|
||||||
log(Info, "Cannot get font from collection")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
collection = append(collection, text.FontFace{Font: text.Font{}, Face: fnt})
|
|
||||||
if err != nil {
|
|
||||||
log(Info, "Cannot access font from font collection")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//font.Register(text.Font{}, face)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
29
cmd/passgo-gui/impl_linux.go
Normal file
29
cmd/passgo-gui/impl_linux.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
//go:build linux && !android
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gioui.org/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
noidLabelText = "No GPG ids available"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getConfDir() (string, error) {
|
||||||
|
ret, err := app.DataDir()
|
||||||
|
if err != nil {
|
||||||
|
log(Error, "Cannot get data directory:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(ret); os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(ret, 0700)
|
||||||
|
if err != nil {
|
||||||
|
log(Error, "Cannot create configuration directory ", ret)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//+build !android
|
//go:build !android
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
// +build darwin linux
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -16,8 +14,6 @@ import (
|
||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
"gioui.org/font/gofont"
|
"gioui.org/font/gofont"
|
||||||
"gioui.org/io/key"
|
|
||||||
"gioui.org/io/system"
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
|
|
@ -43,24 +39,26 @@ type conf struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if false { go func() {
|
if false {
|
||||||
f, err := os.Create("cpuprofile")
|
go func() {
|
||||||
if err != nil {
|
f, err := os.Create("cpuprofile")
|
||||||
fmt.Printf("Can't create CPU profile\n")
|
if err != nil {
|
||||||
os.Exit(-1)
|
fmt.Printf("Can't create CPU profile\n")
|
||||||
}
|
os.Exit(-1)
|
||||||
fmt.Printf("Starting CPU profile\n")
|
}
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
fmt.Printf("Starting CPU profile\n")
|
||||||
fmt.Printf("Can't start CPU profile\n")
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
|
fmt.Printf("Can't start CPU profile\n")
|
||||||
|
f.Close()
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 10)
|
||||||
|
fmt.Printf("Stopping CPU profile\n")
|
||||||
|
pprof.StopCPUProfile()
|
||||||
f.Close()
|
f.Close()
|
||||||
os.Exit(-1)
|
fmt.Printf("CPU profile written\n")
|
||||||
}
|
}()
|
||||||
time.Sleep(time.Second * 10)
|
}
|
||||||
fmt.Printf("Stopping CPU profile\n")
|
|
||||||
pprof.StopCPUProfile()
|
|
||||||
f.Close()
|
|
||||||
fmt.Printf("CPU profile written\n")
|
|
||||||
}() }
|
|
||||||
var fd *os.File
|
var fd *os.File
|
||||||
var err error
|
var err error
|
||||||
confDir, err = getConfDir()
|
confDir, err = getConfDir()
|
||||||
|
|
@ -106,7 +104,7 @@ func main() {
|
||||||
passch = make(chan []byte)
|
passch = make(chan []byte)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log(Info,"passgo.Identities()")
|
log(Info, "passgo.Identities()")
|
||||||
passgo.Identities()
|
passgo.Identities()
|
||||||
}()
|
}()
|
||||||
go Updater()
|
go Updater()
|
||||||
|
|
@ -117,18 +115,18 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fontSize float32
|
fontSize float32
|
||||||
collection []text.FontFace
|
collection []text.FontFace
|
||||||
confDir string
|
confDir string
|
||||||
Config conf
|
Config conf
|
||||||
l []passgo.Pass
|
l []passgo.Pass
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
store passgo.Store
|
store passgo.Store
|
||||||
reload chan struct{}
|
reload chan struct{}
|
||||||
updated chan struct{}
|
updated chan struct{}
|
||||||
chdir chan struct{}
|
chdir chan struct{}
|
||||||
passch chan []byte
|
passch chan []byte
|
||||||
th *material.Theme
|
th *material.Theme
|
||||||
)
|
)
|
||||||
|
|
||||||
func Updater() {
|
func Updater() {
|
||||||
|
|
@ -196,14 +194,22 @@ func saveConf(fds ...*os.File) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func eventLoop() {
|
func eventLoop() {
|
||||||
if collection == nil {
|
|
||||||
collection = gofont.Collection()
|
|
||||||
}
|
|
||||||
|
|
||||||
var ops op.Ops
|
var ops op.Ops
|
||||||
th = material.NewTheme(collection)
|
th = material.NewTheme()
|
||||||
th.TextSize = unit.Sp(fontSize)
|
th.TextSize = unit.Sp(fontSize)
|
||||||
w := app.NewWindow(
|
if len(collection) > 0 {
|
||||||
|
// Use the embedded Go font plus the platform fonts (e.g. the
|
||||||
|
// Unicode font installed on Darwin) instead of the system fonts.
|
||||||
|
th.Shaper = text.NewShaper(text.NoSystemFonts(),
|
||||||
|
text.WithCollection(append(gofont.Collection(), collection...)))
|
||||||
|
} else {
|
||||||
|
// Always provide the embedded Go font: the system font scan
|
||||||
|
// finds no fonts on Android, and a fontless shaper renders a
|
||||||
|
// blank screen.
|
||||||
|
th.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||||
|
}
|
||||||
|
w := new(app.Window)
|
||||||
|
w.Option(
|
||||||
app.Size(unit.Dp(250), unit.Dp(500)),
|
app.Size(unit.Dp(250), unit.Dp(500)),
|
||||||
app.Title("passgo"))
|
app.Title("passgo"))
|
||||||
|
|
||||||
|
|
@ -211,13 +217,12 @@ func eventLoop() {
|
||||||
|
|
||||||
var c1 layout.FlexChild // flex child for title bar
|
var c1 layout.FlexChild // flex child for title bar
|
||||||
|
|
||||||
sysinset := &layout.Inset{}
|
|
||||||
margin := layout.UniformInset(unit.Dp(10))
|
margin := layout.UniformInset(unit.Dp(10))
|
||||||
|
|
||||||
title := material.Body1(th, "passgo")
|
title := material.Body1(th, "passgo")
|
||||||
dotsBtn := &Button{
|
dotsBtn := &Button{
|
||||||
Size: unit.Sp(fontSize),
|
Size: unit.Sp(fontSize),
|
||||||
Label: "\xe2\x8b\xae",
|
Label: "\u2026",
|
||||||
Alignment: text.Middle,
|
Alignment: text.Middle,
|
||||||
Color: black,
|
Color: black,
|
||||||
Background: gray,
|
Background: gray,
|
||||||
|
|
@ -243,6 +248,7 @@ func eventLoop() {
|
||||||
var overlayStart time.Time
|
var overlayStart time.Time
|
||||||
|
|
||||||
FilterEd := &widget.Editor{SingleLine: true}
|
FilterEd := &widget.Editor{SingleLine: true}
|
||||||
|
var lastFilter string
|
||||||
updateBtns := func() {
|
updateBtns := func() {
|
||||||
passBtns = passBtns[:0]
|
passBtns = passBtns[:0]
|
||||||
pathnames = pathnames[:0]
|
pathnames = pathnames[:0]
|
||||||
|
|
@ -256,10 +262,10 @@ func eventLoop() {
|
||||||
d, n := path.Split(x.Pathname)
|
d, n := path.Split(x.Pathname)
|
||||||
x = passgo.Pass{
|
x = passgo.Pass{
|
||||||
Pathname: d,
|
Pathname: d,
|
||||||
Level: x.Level - 1,
|
Level: x.Level - 1,
|
||||||
Dir: true,
|
Dir: true,
|
||||||
}
|
}
|
||||||
fmt.Printf("addd(): d/n = '%s'/'%s'\n", d,n)
|
fmt.Printf("addd(): d/n = '%s'/'%s'\n", d, n)
|
||||||
if dirs[d] != true {
|
if dirs[d] != true {
|
||||||
addd(x)
|
addd(x)
|
||||||
}
|
}
|
||||||
|
|
@ -267,8 +273,9 @@ func eventLoop() {
|
||||||
s := strings.Repeat(" /", x.Level)
|
s := strings.Repeat(" /", x.Level)
|
||||||
lbl := strings.Join([]string{s, d}, "")
|
lbl := strings.Join([]string{s, d}, "")
|
||||||
passBtns = append(passBtns, &Button{
|
passBtns = append(passBtns, &Button{
|
||||||
Size: unit.Sp(fontSize),
|
Size: unit.Sp(fontSize),
|
||||||
Label: lbl,
|
Label: lbl,
|
||||||
|
Color: black,
|
||||||
Background: gray,
|
Background: gray,
|
||||||
})
|
})
|
||||||
pathnames = append(pathnames, x.Pathname)
|
pathnames = append(pathnames, x.Pathname)
|
||||||
|
|
@ -288,6 +295,7 @@ func eventLoop() {
|
||||||
passBtns = append(passBtns, &Button{
|
passBtns = append(passBtns, &Button{
|
||||||
Size: unit.Sp(fontSize),
|
Size: unit.Sp(fontSize),
|
||||||
Label: lbl,
|
Label: lbl,
|
||||||
|
Color: black,
|
||||||
Background: gray,
|
Background: gray,
|
||||||
})
|
})
|
||||||
pathnames = append(pathnames, x.Pathname)
|
pathnames = append(pathnames, x.Pathname)
|
||||||
|
|
@ -304,7 +312,7 @@ func eventLoop() {
|
||||||
idBtns := make([]*Button, 0)
|
idBtns := make([]*Button, 0)
|
||||||
|
|
||||||
updateIdBtns := func() {
|
updateIdBtns := func() {
|
||||||
log(Info,"passgo.Identities()")
|
log(Info, "passgo.Identities()")
|
||||||
ids, err := passgo.Identities()
|
ids, err := passgo.Identities()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log(Info, err)
|
log(Info, err)
|
||||||
|
|
@ -335,7 +343,7 @@ func eventLoop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
storeDirLabel := material.Label(th, unit.Sp(fontSize), "Store directory")
|
storeDirLabel := material.Label(th, unit.Sp(fontSize), "Store directory")
|
||||||
storeDirEd := &widget.Editor{ SingleLine: true}
|
storeDirEd := &widget.Editor{SingleLine: true}
|
||||||
storeDirEd.SetText(store.Dir)
|
storeDirEd.SetText(store.Dir)
|
||||||
saveBtn := &Button{
|
saveBtn := &Button{
|
||||||
Size: unit.Sp(fontSize),
|
Size: unit.Sp(fontSize),
|
||||||
|
|
@ -361,7 +369,7 @@ func eventLoop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
promptLabel := material.Label(th, unit.Sp(fontSize), "passphrase")
|
promptLabel := material.Label(th, unit.Sp(fontSize), "passphrase")
|
||||||
promptEd := &widget.Editor{ SingleLine: true, Submit: true }
|
promptEd := &widget.Editor{SingleLine: true, Submit: true}
|
||||||
okBtn := &Button{
|
okBtn := &Button{
|
||||||
Size: unit.Sp(fontSize),
|
Size: unit.Sp(fontSize),
|
||||||
Label: "ok",
|
Label: "ok",
|
||||||
|
|
@ -386,9 +394,9 @@ func eventLoop() {
|
||||||
|
|
||||||
insertLabel := material.Label(th, unit.Sp(fontSize), "Insert")
|
insertLabel := material.Label(th, unit.Sp(fontSize), "Insert")
|
||||||
passnameLabel := material.Label(th, unit.Sp(fontSize), "password name:")
|
passnameLabel := material.Label(th, unit.Sp(fontSize), "password name:")
|
||||||
passnameEd := &widget.Editor{ SingleLine: true }
|
passnameEd := &widget.Editor{SingleLine: true}
|
||||||
passvalLabel := material.Label(th, unit.Sp(fontSize), "password value:")
|
passvalLabel := material.Label(th, unit.Sp(fontSize), "password value:")
|
||||||
passvalEd := &widget.Editor{ SingleLine: true, Submit: true }
|
passvalEd := &widget.Editor{SingleLine: true, Submit: true}
|
||||||
|
|
||||||
noidLabel := material.Label(th, unit.Sp(fontSize), noidLabelText)
|
noidLabel := material.Label(th, unit.Sp(fontSize), noidLabelText)
|
||||||
idLabel := material.Label(th, unit.Sp(fontSize), "Select ID")
|
idLabel := material.Label(th, unit.Sp(fontSize), "Select ID")
|
||||||
|
|
@ -433,6 +441,7 @@ func eventLoop() {
|
||||||
})
|
})
|
||||||
if xBtn.Clicked() {
|
if xBtn.Clicked() {
|
||||||
FilterEd.SetText("")
|
FilterEd.SetText("")
|
||||||
|
lastFilter = ""
|
||||||
updateBtns()
|
updateBtns()
|
||||||
}
|
}
|
||||||
return lpf1.Layout(gtx, c21, c22)
|
return lpf1.Layout(gtx, c21, c22)
|
||||||
|
|
@ -440,9 +449,6 @@ func eventLoop() {
|
||||||
c3 := layout.Flexed(1.0, func(gtx C) D {
|
c3 := layout.Flexed(1.0, func(gtx C) D {
|
||||||
var ret D
|
var ret D
|
||||||
mux.Lock()
|
mux.Lock()
|
||||||
if lst.Dragging() {
|
|
||||||
key.HideInputOp{}.Add(gtx.Ops)
|
|
||||||
}
|
|
||||||
switch {
|
switch {
|
||||||
case store.Empty:
|
case store.Empty:
|
||||||
confBtn.Layout(gtx)
|
confBtn.Layout(gtx)
|
||||||
|
|
@ -543,7 +549,11 @@ func eventLoop() {
|
||||||
if animating && x > end {
|
if animating && x > end {
|
||||||
animOff()
|
animOff()
|
||||||
}
|
}
|
||||||
if len(FilterEd.Events()) > 0 {
|
// material.Editor.Layout (above) already consumes the editor's
|
||||||
|
// input events, so we detect a filter change by comparing the
|
||||||
|
// current text against the previous frame's.
|
||||||
|
if t := FilterEd.Text(); t != lastFilter {
|
||||||
|
lastFilter = t
|
||||||
updateBtns()
|
updateBtns()
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -588,19 +598,14 @@ func eventLoop() {
|
||||||
c4 = layout.Rigid(func(gtx C) D {
|
c4 = layout.Rigid(func(gtx C) D {
|
||||||
return material.Editor(th, idEd, "id").Layout(gtx)
|
return material.Editor(th, idEd, "id").Layout(gtx)
|
||||||
})
|
})
|
||||||
for _, e := range idEd.Events() {
|
|
||||||
switch e.(type) {
|
|
||||||
case widget.SubmitEvent:
|
|
||||||
log(Info, "Submit")
|
|
||||||
store.Id = idEd.Text()
|
|
||||||
page = listPage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c5 = layout.Rigid(func(gtx C) D {
|
c5 = layout.Rigid(func(gtx C) D {
|
||||||
return idSubmitBtn.Layout(gtx)
|
return idSubmitBtn.Layout(gtx)
|
||||||
})
|
})
|
||||||
if idSubmitBtn.Clicked() {
|
if idSubmitBtn.Clicked() {
|
||||||
store.Id = idEd.Text()
|
store.Id = idEd.Text()
|
||||||
|
// Persist the ID so it survives a restart (the manual entry
|
||||||
|
// path has no keyring to re-derive it from).
|
||||||
|
store.SetID(store.Id)
|
||||||
page = listPage
|
page = listPage
|
||||||
}
|
}
|
||||||
c6 = layout.Rigid(func(gtx C) D {
|
c6 = layout.Rigid(func(gtx C) D {
|
||||||
|
|
@ -632,6 +637,15 @@ func eventLoop() {
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
})
|
})
|
||||||
|
if ev, ok := idEd.Update(gtx); ok {
|
||||||
|
if _, isSubmit := ev.(widget.SubmitEvent); isSubmit {
|
||||||
|
log(Info, "Submit")
|
||||||
|
store.Id = idEd.Text()
|
||||||
|
store.SetID(store.Id)
|
||||||
|
w.Invalidate()
|
||||||
|
page = listPage
|
||||||
|
}
|
||||||
|
}
|
||||||
return flex.Layout(gtx, c1, c2, c3, c4, c5, c6, c7)
|
return flex.Layout(gtx, c1, c2, c3, c4, c5, c6, c7)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -644,7 +658,7 @@ func eventLoop() {
|
||||||
numBtn.Button = Button{Size: unit.Sp(fontSize), Label: "#"}
|
numBtn.Button = Button{Size: unit.Sp(fontSize), Label: "#"}
|
||||||
symBtn.Select()
|
symBtn.Select()
|
||||||
numBtn.Select()
|
numBtn.Select()
|
||||||
lenEd := &widget.Editor{ SingleLine: true, Alignment: text.End }
|
lenEd := &widget.Editor{SingleLine: true, Alignment: text.End}
|
||||||
lenEd.SetText("15")
|
lenEd.SetText("15")
|
||||||
lBtn := &Button{Size: unit.Sp(fontSize), Label: "<", Background: gray}
|
lBtn := &Button{Size: unit.Sp(fontSize), Label: "<", Background: gray}
|
||||||
rBtn := &Button{Size: unit.Sp(fontSize), Label: ">", Background: gray}
|
rBtn := &Button{Size: unit.Sp(fontSize), Label: ">", Background: gray}
|
||||||
|
|
@ -835,9 +849,8 @@ func eventLoop() {
|
||||||
|
|
||||||
promptPage = func(gtx C) D {
|
promptPage = func(gtx C) D {
|
||||||
submit := false
|
submit := false
|
||||||
for _, e := range promptEd.Events() {
|
if ev, ok := promptEd.Update(gtx); ok {
|
||||||
switch e.(type) {
|
if _, isSubmit := ev.(widget.SubmitEvent); isSubmit {
|
||||||
case widget.SubmitEvent:
|
|
||||||
log(Info, "Submit")
|
log(Info, "Submit")
|
||||||
submit = true
|
submit = true
|
||||||
}
|
}
|
||||||
|
|
@ -882,78 +895,73 @@ func eventLoop() {
|
||||||
ms := &runtime.MemStats{}
|
ms := &runtime.MemStats{}
|
||||||
x := 0
|
x := 0
|
||||||
var mallocs uint64
|
var mallocs uint64
|
||||||
|
var idsInit bool
|
||||||
for {
|
for {
|
||||||
|
// Window.Event and FrameEvent.Frame must be called from the same
|
||||||
|
// goroutine; drain the auxiliary channels non-blocking instead of
|
||||||
|
// bridging the window events through a channel.
|
||||||
select {
|
select {
|
||||||
case <-updated:
|
case <-updated:
|
||||||
log(Info, "UPDATE")
|
log(Info, "UPDATE")
|
||||||
updateBtns()
|
updateBtns()
|
||||||
w.Invalidate()
|
w.Invalidate()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
select {
|
||||||
case <-anim.C:
|
case <-anim.C:
|
||||||
w.Invalidate()
|
w.Invalidate()
|
||||||
case e := <-w.Events():
|
default:
|
||||||
|
}
|
||||||
|
switch e := w.Event().(type) {
|
||||||
|
case app.DestroyEvent:
|
||||||
|
os.Exit(0)
|
||||||
|
case app.FrameEvent:
|
||||||
x++
|
x++
|
||||||
if x == 100 {
|
if !idsInit { // the app is running; refresh the ID list
|
||||||
runtime.ReadMemStats(ms)
|
idsInit = true
|
||||||
mallocs = ms.Mallocs
|
go updateIdBtns()
|
||||||
}
|
}
|
||||||
switch e := e.(type) {
|
// NewContext resets ops and adjusts for e.Insets.
|
||||||
case system.DestroyEvent:
|
gtx := app.NewContext(&ops, e)
|
||||||
os.Exit(0)
|
|
||||||
case system.StageEvent:
|
|
||||||
if e.Stage == system.StageRunning {
|
|
||||||
go func() {
|
|
||||||
updateIdBtns()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
case system.FrameEvent:
|
|
||||||
gtx := layout.NewContext(&ops, e)
|
|
||||||
|
|
||||||
sysinset.Top = e.Insets.Top
|
margin.Layout(gtx, func(gtx C) D {
|
||||||
sysinset.Bottom = e.Insets.Bottom
|
margincs = gtx.Constraints
|
||||||
sysinset.Left = e.Insets.Left
|
c1 = layout.Rigid(func(gtx C) D {
|
||||||
sysinset.Right = e.Insets.Right
|
ct2 := layout.Rigid(func(gtx C) D {
|
||||||
|
return plusBtn.Layout(gtx)
|
||||||
sysinset.Layout(gtx, func(gtx C) D {
|
|
||||||
return margin.Layout(gtx, func(gtx C) D {
|
|
||||||
margincs = gtx.Constraints
|
|
||||||
c1 = layout.Rigid(func(gtx C) D {
|
|
||||||
ct2 := layout.Rigid(func(gtx C) D {
|
|
||||||
return plusBtn.Layout(gtx)
|
|
||||||
})
|
|
||||||
ct3 := layout.Rigid(func(gtx C) D {
|
|
||||||
return dotsBtn.Layout(gtx)
|
|
||||||
})
|
|
||||||
ct1 := layout.Flexed(1.0, func(gtx C) D {
|
|
||||||
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
|
||||||
return title.Layout(gtx)
|
|
||||||
})
|
|
||||||
return titleflex.Layout(gtx, ct1, ct2, ct3)
|
|
||||||
})
|
|
||||||
|
|
||||||
return page(gtx)
|
|
||||||
})
|
})
|
||||||
|
ct3 := layout.Rigid(func(gtx C) D {
|
||||||
|
return dotsBtn.Layout(gtx)
|
||||||
|
})
|
||||||
|
ct1 := layout.Flexed(1.0, func(gtx C) D {
|
||||||
|
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
||||||
|
return title.Layout(gtx)
|
||||||
|
})
|
||||||
|
return titleflex.Layout(gtx, ct1, ct2, ct3)
|
||||||
})
|
})
|
||||||
|
|
||||||
if dotsBtn.Clicked() {
|
return page(gtx)
|
||||||
log(Info, "Configure")
|
})
|
||||||
w.Invalidate()
|
|
||||||
page = confPage
|
if dotsBtn.Clicked() {
|
||||||
}
|
log(Info, "Configure")
|
||||||
if plusBtn.Clicked() {
|
w.Invalidate()
|
||||||
log(Info, "Plus")
|
page = confPage
|
||||||
w.Invalidate()
|
|
||||||
insName, insValue = "", ""
|
|
||||||
page = insertPage
|
|
||||||
}
|
|
||||||
e.Frame(gtx.Ops)
|
|
||||||
default:
|
|
||||||
handleEvent(e)
|
|
||||||
}
|
}
|
||||||
if x == 100 {
|
if plusBtn.Clicked() {
|
||||||
x = 0
|
log(Info, "Plus")
|
||||||
runtime.ReadMemStats(ms)
|
w.Invalidate()
|
||||||
fmt.Printf("mallocs: %d\n", ms.Mallocs-mallocs)
|
insName, insValue = "", ""
|
||||||
|
page = insertPage
|
||||||
}
|
}
|
||||||
|
e.Frame(&ops)
|
||||||
|
default:
|
||||||
|
handleEvent(e)
|
||||||
|
}
|
||||||
|
if x == 100 {
|
||||||
|
x = 0
|
||||||
|
runtime.ReadMemStats(ms)
|
||||||
|
fmt.Printf("mallocs: %d\n", ms.Mallocs-mallocs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"gioui.org/f32"
|
|
||||||
"gioui.org/gesture"
|
"gioui.org/gesture"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/event"
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
"gioui.org/op/paint"
|
"gioui.org/op/paint"
|
||||||
|
|
@ -16,18 +15,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
black = color.RGBA{A: 0xff, R: 0, G: 0, B: 0}
|
black = color.NRGBA{A: 0xff, R: 0, G: 0, B: 0}
|
||||||
white = color.RGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}
|
white = color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}
|
||||||
gray = color.RGBA{A: 0xff, R: 0xf0, G: 0xf0, B: 0xf0}
|
gray = color.NRGBA{A: 0xff, R: 0xf0, G: 0xf0, B: 0xf0}
|
||||||
darkgray = color.RGBA{A: 0xff, R: 0xa0, G: 0xa0, B: 0xa0}
|
darkgray = color.NRGBA{A: 0xff, R: 0xa0, G: 0xa0, B: 0xa0}
|
||||||
)
|
)
|
||||||
|
|
||||||
type Overlay struct {
|
type Overlay struct {
|
||||||
Size unit.Value
|
Size unit.Sp
|
||||||
Text string
|
Text string
|
||||||
Click gesture.Click
|
Click gesture.Click
|
||||||
Color color.RGBA
|
Color color.NRGBA
|
||||||
Background color.RGBA
|
Background color.NRGBA
|
||||||
Alignment text.Alignment
|
Alignment text.Alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,11 +38,9 @@ func (b *Overlay) Layout(gtx C) D {
|
||||||
l := material.Label(th, b.Size, b.Text)
|
l := material.Label(th, b.Size, b.Text)
|
||||||
ins := layout.UniformInset(unit.Dp(4))
|
ins := layout.UniformInset(unit.Dp(4))
|
||||||
l.Color = b.Color
|
l.Color = b.Color
|
||||||
ret := ins.Layout(gtx, func(gtx C) D {
|
return ins.Layout(gtx, func(gtx C) D {
|
||||||
return l.Layout(gtx)
|
return l.Layout(gtx)
|
||||||
})
|
})
|
||||||
pointer.Rect(image.Rect(0, 0, ret.Size.X, ret.Size.Y)).Add(gtx.Ops)
|
|
||||||
return ret
|
|
||||||
})
|
})
|
||||||
c1 := layout.Expanded(func(gtx C) D {
|
c1 := layout.Expanded(func(gtx C) D {
|
||||||
return layoutRRect(b.Background, gtx)
|
return layoutRRect(b.Background, gtx)
|
||||||
|
|
@ -54,55 +51,58 @@ func (b *Overlay) Layout(gtx C) D {
|
||||||
|
|
||||||
type SelButton struct {
|
type SelButton struct {
|
||||||
Button
|
Button
|
||||||
SelColor color.RGBA
|
SelColor color.NRGBA
|
||||||
Selected bool
|
Selected bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Button struct {
|
type Button struct {
|
||||||
Size unit.Value
|
Size unit.Sp
|
||||||
Label string
|
Label string
|
||||||
Click gesture.Click
|
Click gesture.Click
|
||||||
Color color.RGBA
|
Color color.NRGBA
|
||||||
Background color.RGBA
|
Background color.NRGBA
|
||||||
Alignment text.Alignment
|
Alignment text.Alignment
|
||||||
clicked bool
|
clicked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func layoutRRect(col color.RGBA, gtx C) D {
|
func layoutRRect(col color.NRGBA, gtx C) D {
|
||||||
r := float32(gtx.Px(unit.Dp(4)))
|
r := gtx.Dp(4)
|
||||||
sz := image.Point{X: gtx.Constraints.Min.X, Y: gtx.Constraints.Min.Y}
|
sz := image.Point{X: gtx.Constraints.Min.X, Y: gtx.Constraints.Min.Y}
|
||||||
w, h := float32(sz.X), float32(sz.Y)
|
// Push the rounded-rect clip, paint, then restore the previous clip.
|
||||||
rect := f32.Rectangle{
|
stk := clip.UniformRRect(image.Rectangle{Max: sz}, r).Push(gtx.Ops)
|
||||||
f32.Point{0, 0},
|
|
||||||
f32.Point{w, h},
|
|
||||||
}
|
|
||||||
//clip.RoundRect(gtx.Ops, rect, r, r, r, r)
|
|
||||||
clip.RRect{Rect: rect, NE: r, NW: r, SE: r, SW: r}.Add(gtx.Ops)
|
|
||||||
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
||||||
paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(gtx.Ops)
|
paint.PaintOp{}.Add(gtx.Ops)
|
||||||
|
stk.Pop()
|
||||||
return layout.Dimensions{Size: sz}
|
return layout.Dimensions{Size: sz}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Button) Layout(gtx C) D {
|
func (b *Button) Layout(gtx C) D {
|
||||||
mwidth := gtx.Constraints.Min.X
|
mwidth := gtx.Constraints.Min.X
|
||||||
b.clicked = false
|
b.clicked = false
|
||||||
for _, ev := range b.Click.Events(gtx) {
|
|
||||||
if ev.Type == gesture.TypeClick {
|
|
||||||
b.clicked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ins := layout.UniformInset(unit.Dp(1))
|
ins := layout.UniformInset(unit.Dp(1))
|
||||||
return ins.Layout(gtx, func(gtx C) D {
|
return ins.Layout(gtx, func(gtx C) D {
|
||||||
st := layout.Stack{}
|
st := layout.Stack{}
|
||||||
c2 := layout.Stacked(func(gtx C) D {
|
c2 := layout.Stacked(func(gtx C) D {
|
||||||
l := material.Label(th, b.Size, b.Label)
|
l := material.Label(th, b.Size, b.Label)
|
||||||
|
l.Color = b.Color
|
||||||
ins := layout.UniformInset(unit.Dp(4))
|
ins := layout.UniformInset(unit.Dp(4))
|
||||||
//paint.ColorOp{Color: b.Color}.Add(ops)
|
|
||||||
ret := ins.Layout(gtx, func(gtx C) D {
|
ret := ins.Layout(gtx, func(gtx C) D {
|
||||||
return l.Layout(gtx)
|
return l.Layout(gtx)
|
||||||
})
|
})
|
||||||
pointer.Rect(image.Rect(0, 0, ret.Size.X, ret.Size.Y)).Add(gtx.Ops)
|
// Register the click handler for the label area.
|
||||||
|
stk := clip.Rect(image.Rectangle{Max: ret.Size}).Push(gtx.Ops)
|
||||||
|
event.Op(gtx.Ops, b)
|
||||||
b.Click.Add(gtx.Ops)
|
b.Click.Add(gtx.Ops)
|
||||||
|
stk.Pop()
|
||||||
|
for {
|
||||||
|
ev, ok := b.Click.Update(gtx.Source)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ev.Kind == gesture.KindClick {
|
||||||
|
b.clicked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
return ret
|
return ret
|
||||||
})
|
})
|
||||||
c1 := layout.Expanded(func(gtx C) D {
|
c1 := layout.Expanded(func(gtx C) D {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user