├── tribaltrouble.jpg
├── go.mod
├── survey_obstacles.svg.png
├── webassembly.go
├── blank.go
├── README.md
├── helloworld.go
├── twolabels1.go
├── editor.go
├── list.go
├── twolabels2.go
├── centering.go
├── animatedcolor.go
├── UNLICENSE
├── flex.go
├── animatedclipping.go
├── stack.go
├── pointer.go
├── go.sum
├── cphgophers-nov-2019.slide
├── gophercon-2019.slide
└── survey_obstacles.svg
/tribaltrouble.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eliasnaur/gophercon-2019-talk/HEAD/tribaltrouble.jpg
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module gophercon2019
2 |
3 | go 1.13
4 |
5 | require gioui.org v0.0.0-20201105153219-94d242d18c92
6 |
--------------------------------------------------------------------------------
/survey_obstacles.svg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eliasnaur/gophercon-2019-talk/HEAD/survey_obstacles.svg.png
--------------------------------------------------------------------------------
/webassembly.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | )
7 |
8 | func main() {
9 | err := http.ListenAndServe(":8080", http.FileServer(http.Dir("www")))
10 | log.Fatal(err)
11 | }
12 |
--------------------------------------------------------------------------------
/blank.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gioui.org/app"
5 | )
6 |
7 | func main() {
8 | go func() { // HLmain
9 | w := app.NewWindow() // HLeventloop
10 | for range w.Events() { // HLeventloop
11 | }
12 | }() // HLmain
13 | app.Main() // HLmain
14 | }
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Slides and demo programs for the Gophercon 2019 talk about [Gio](https://gioui.org/) and
2 | [Scatter](https://scatter.im/), "Simple, Portable and Efficient Graphical Interfaces in Go".
3 |
4 | See the slides online [here](https://go-talks.appspot.com/github.com/eliasnaur/gophercon-2019-talk/gophercon-2019.slide).
5 |
6 | ### Licence
7 |
8 | [The UNLICENCE](https://unlicense.org/)
9 |
--------------------------------------------------------------------------------
/helloworld.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gioui.org/app"
5 | "gioui.org/font/gofont"
6 | "gioui.org/io/system"
7 | "gioui.org/layout"
8 | "gioui.org/op"
9 | "gioui.org/widget/material"
10 | )
11 |
12 | // START OMIT
13 | func main() {
14 | go func() {
15 | w := app.NewWindow()
16 | th := material.NewTheme(gofont.Collection())
17 | var ops op.Ops
18 | for e := range w.Events() {
19 | if e, ok := e.(system.FrameEvent); ok {
20 | gtx := layout.NewContext(&ops, e)
21 |
22 | material.H1(th, "Hello, World!").Layout(gtx) // HLdraw
23 |
24 | e.Frame(gtx.Ops)
25 | }
26 | } // HLeventloop
27 | }()
28 | app.Main()
29 | }
30 |
31 | // END OMIT
32 |
--------------------------------------------------------------------------------
/twolabels1.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gioui.org/app"
5 | "gioui.org/font/gofont"
6 | "gioui.org/io/system"
7 | "gioui.org/layout"
8 | "gioui.org/op"
9 | "gioui.org/widget/material"
10 | )
11 |
12 | func main() {
13 | go func() {
14 | w := app.NewWindow()
15 | th := material.NewTheme(gofont.Collection())
16 | var ops op.Ops
17 | // START OMIT
18 | for e := range w.Events() {
19 | if e, ok := e.(system.FrameEvent); ok {
20 | gtx := layout.NewContext(&ops, e)
21 | drawLabels(gtx, th) // HLdraw
22 | e.Frame(gtx.Ops)
23 | }
24 | }
25 | // END OMIT
26 | }()
27 | app.Main()
28 | }
29 |
30 | // START DRAW OMIT
31 |
32 | func drawLabels(gtx layout.Context, th *material.Theme) {
33 | material.H1(th, "One label").Layout(gtx)
34 | material.H1(th, "Another label").Layout(gtx)
35 | }
36 |
37 | // END DRAW OMIT
38 |
--------------------------------------------------------------------------------
/editor.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gioui.org/app"
5 | "gioui.org/io/system"
6 | "gioui.org/layout"
7 | "gioui.org/op"
8 | "gioui.org/unit"
9 | "gioui.org/widget"
10 | "gioui.org/widget/material"
11 |
12 | "gioui.org/font/gofont"
13 | )
14 |
15 | func main() {
16 | go func() {
17 | w := app.NewWindow()
18 | th := material.NewTheme(gofont.Collection())
19 | // START INIT OMIT
20 | editor := new(widget.Editor)
21 | editor.SetText("Hello, Gophers! Edit me.")
22 | // END INIT OMIT
23 | var ops op.Ops
24 | for e := range w.Events() {
25 | if e, ok := e.(system.FrameEvent); ok {
26 | gtx := layout.NewContext(&ops, e) // HLdraw
27 | // START OMIT
28 | ed := material.Editor(th, editor, "Hint")
29 | ed.TextSize = unit.Sp(52)
30 | ed.Layout(gtx)
31 | // END OMIT
32 | e.Frame(gtx.Ops) // HLdraw
33 | }
34 | } // HLeventloop
35 | }()
36 | app.Main()
37 | }
38 |
--------------------------------------------------------------------------------
/list.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "gioui.org/app"
7 | "gioui.org/font/gofont"
8 | "gioui.org/io/system"
9 | "gioui.org/layout"
10 | "gioui.org/op"
11 | "gioui.org/widget/material"
12 | )
13 |
14 | func main() {
15 | go func() {
16 | w := app.NewWindow()
17 | th := material.NewTheme(gofont.Collection())
18 | // START INIT OMIT
19 | list := &layout.List{
20 | Axis: layout.Vertical,
21 | }
22 | var ops op.Ops
23 | // END INIT OMIT
24 | for e := range w.Events() {
25 | if e, ok := e.(system.FrameEvent); ok {
26 | gtx := layout.NewContext(&ops, e)
27 | drawList(gtx, list, th)
28 | e.Frame(gtx.Ops)
29 | }
30 | }
31 | }()
32 | app.Main()
33 | }
34 |
35 | // START OMIT
36 | func drawList(gtx layout.Context, list *layout.List, th *material.Theme) layout.Dimensions {
37 | const n = 1e6
38 | return list.Layout(gtx, n, func(gtx layout.Context, i int) layout.Dimensions {
39 | txt := fmt.Sprintf("List element #%d", i)
40 |
41 | return material.H3(th, txt).Layout(gtx)
42 | })
43 | }
44 |
45 | // END OMIT
46 |
--------------------------------------------------------------------------------
/twolabels2.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "gioui.org/app"
5 | "gioui.org/f32"
6 | "gioui.org/font/gofont"
7 | "gioui.org/io/system"
8 | "gioui.org/layout"
9 | "gioui.org/op"
10 | "gioui.org/widget/material"
11 | )
12 |
13 | func main() {
14 | go func() {
15 | w := app.NewWindow()
16 | th := material.NewTheme(gofont.Collection())
17 | var ops op.Ops
18 | // START OMIT
19 | for e := range w.Events() {
20 | if e, ok := e.(system.FrameEvent); ok {
21 | gtx := layout.NewContext(&ops, e)
22 | drawLabels(gtx, th) // HLdraw
23 | e.Frame(gtx.Ops)
24 | }
25 | }
26 | // END OMIT
27 | }()
28 | app.Main()
29 | }
30 |
31 | // START DRAW OMIT
32 | func drawLabels(gtx layout.Context, th *material.Theme) {
33 | gtx.Constraints.Min.Y = 0 // HLdraw
34 | dimensions := material.H1(th, "One label").Layout(gtx) // HLdraw
35 | op.Affine(f32.Affine2D{}.Offset(f32.Point{
36 | Y: float32(dimensions.Size.Y), // HLdraw
37 | })).Add(gtx.Ops)
38 | material.H1(th, "Another label").Layout(gtx)
39 | }
40 |
41 | // END DRAW OMIT
42 |
--------------------------------------------------------------------------------
/centering.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "image"
5 |
6 | "gioui.org/app"
7 | "gioui.org/f32"
8 | "gioui.org/font/gofont"
9 | "gioui.org/io/system"
10 | "gioui.org/layout"
11 | "gioui.org/op"
12 | "gioui.org/widget/material"
13 | )
14 |
15 | func main() {
16 | go func() {
17 | w := app.NewWindow()
18 | th := material.NewTheme(gofont.Collection())
19 | var ops op.Ops
20 | for e := range w.Events() {
21 | if e, ok := e.(system.FrameEvent); ok {
22 | gtx := layout.NewContext(&ops, e)
23 | drawLabels(gtx, th)
24 | e.Frame(gtx.Ops)
25 | }
26 | }
27 | }()
28 | app.Main()
29 | }
30 |
31 | // START OMIT
32 | func drawLabels(gtx layout.Context, th *material.Theme) layout.Dimensions {
33 | gtx.Constraints.Min = image.Pt(0, 0)
34 | rec := op.Record(gtx.Ops) // Start recording // HLcenter
35 | dimensions := material.H2(th, "I'm centered!").Layout(gtx)
36 | macro := rec.Stop() // End recording // HLcenter
37 | op.Affine(f32.Affine2D{}.Offset(f32.Point{
38 | X: float32(gtx.Constraints.Max.X-dimensions.Size.X) / 2,
39 | Y: float32(gtx.Constraints.Max.Y-dimensions.Size.Y) / 2,
40 | })).Add(gtx.Ops)
41 | macro.Add(gtx.Ops) // Replay operations // HLcenter
42 | return dimensions
43 | }
44 |
45 | // END OMIT
46 |
--------------------------------------------------------------------------------
/animatedcolor.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "image"
5 | "image/color"
6 | "math"
7 | "time"
8 |
9 | "gioui.org/app"
10 | "gioui.org/io/system"
11 | "gioui.org/op"
12 | "gioui.org/op/clip"
13 | "gioui.org/op/paint"
14 | )
15 |
16 | func main() {
17 | go func() {
18 | w := app.NewWindow()
19 | // START OMIT
20 | ops := new(op.Ops) // HLops
21 | for e := range w.Events() {
22 | if e, ok := e.(system.FrameEvent); ok {
23 | ops.Reset() // HLops
24 |
25 | color := animateColor(e.Now)
26 | square := image.Rectangle{
27 | Min: image.Point{X: 50, Y: 50},
28 | Max: image.Point{X: 500, Y: 500},
29 | }
30 | // Add draw operations.
31 | paint.ColorOp{Color: color}.Add(ops) // HLops
32 | clip.Rect(square).Add(ops) // HLops
33 | paint.PaintOp{}.Add(ops) // HLops
34 | // Request immediate redraw.
35 | op.InvalidateOp{}.Add(ops) // HLops
36 |
37 | // Submit operations.
38 | e.Frame(ops) // HLops
39 | }
40 | }
41 | // END OMIT
42 | }()
43 | app.Main()
44 | }
45 |
46 | var start = time.Now()
47 |
48 | func animateColor(t time.Time) color.RGBA {
49 | dt := t.Sub(start).Seconds()
50 | green := math.Abs(math.Sin(dt))
51 | return color.RGBA{A: 0xff, G: byte(green * 0xff)}
52 | }
53 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 |
2 | This is free and unencumbered software released into the public domain.
3 |
4 | Anyone is free to copy, modify, publish, use, compile, sell, or
5 | distribute this software, either in source code form or as a compiled
6 | binary, for any purpose, commercial or non-commercial, and by any
7 | means.
8 |
9 | In jurisdictions that recognize copyright laws, the author or authors
10 | of this software dedicate any and all copyright interest in the
11 | software to the public domain. We make this dedication for the benefit
12 | of the public at large and to the detriment of our heirs and
13 | successors. We intend this dedication to be an overt act of
14 | relinquishment in perpetuity of all present and future rights to this
15 | software under copyright law.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 |
25 | For more information, please refer to
26 |
--------------------------------------------------------------------------------
/flex.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "image"
5 | "image/color"
6 |
7 | "gioui.org/app"
8 | "gioui.org/io/system"
9 | "gioui.org/layout"
10 | "gioui.org/op"
11 | "gioui.org/op/clip"
12 | "gioui.org/op/paint"
13 | )
14 |
15 | func main() {
16 | go func() {
17 | w := app.NewWindow()
18 | var ops op.Ops
19 | for e := range w.Events() {
20 | if e, ok := e.(system.FrameEvent); ok {
21 | gtx := layout.NewContext(&ops, e)
22 | drawRects(gtx)
23 | e.Frame(gtx.Ops)
24 | }
25 | }
26 | }()
27 | app.Main()
28 | }
29 |
30 | // START OMIT
31 | func drawRects(gtx layout.Context) layout.Dimensions {
32 | return layout.Flex{}.Layout(gtx,
33 | // Red.
34 | layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions {
35 | return drawRect(gtx, color.RGBA{A: 0xff, R: 0xff})
36 | }),
37 |
38 | // Green.
39 | layout.Flexed(0.25, func(gtx layout.Context) layout.Dimensions {
40 | return drawRect(gtx, color.RGBA{A: 0xff, G: 0xff})
41 | }),
42 |
43 | // Blue.
44 | layout.Flexed(0.25, func(gtx layout.Context) layout.Dimensions {
45 | return drawRect(gtx, color.RGBA{A: 0xff, B: 0xff})
46 | }),
47 | )
48 | }
49 |
50 | // END OMIT
51 |
52 | func drawRect(gtx layout.Context, color color.RGBA) layout.Dimensions {
53 | cs := gtx.Constraints
54 | square := image.Rectangle{Max: cs.Max}
55 | paint.FillShape(gtx.Ops, color, clip.Rect(square).Op())
56 | return layout.Dimensions{Size: cs.Max}
57 | }
58 |
--------------------------------------------------------------------------------
/animatedclipping.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "image/color"
5 | "math"
6 | "time"
7 |
8 | "gioui.org/app"
9 | "gioui.org/f32"
10 | "gioui.org/io/system"
11 | "gioui.org/op"
12 | "gioui.org/op/clip"
13 | "gioui.org/op/paint"
14 | )
15 |
16 | func main() {
17 | go func() {
18 | w := app.NewWindow()
19 | ops := new(op.Ops)
20 | for e := range w.Events() {
21 | if e, ok := e.(system.FrameEvent); ok {
22 | ops.Reset()
23 |
24 | // START OMIT
25 | square := f32.Rectangle{Max: f32.Point{X: 500, Y: 500}}
26 | radius := animateRadius(e.Now, 250)
27 |
28 | // Position
29 | op.Affine(f32.Affine2D{}.Offset(f32.Point{ // HLdraw
30 | X: 100, // HLdraw
31 | Y: 100, // HLdraw
32 | })).Add(ops) // HLdraw
33 | // Color
34 | paint.ColorOp{Color: color.RGBA{A: 0xff, G: 0xcc}}.Add(ops) // HLdraw
35 | // Clip corners
36 | clip.RRect{Rect: square,
37 | NE: radius, NW: radius, SE: radius, SW: radius}.Op(ops).Add(ops) // HLdraw
38 | // Draw
39 | paint.PaintOp{}.Add(ops) // HLdraw
40 | // Animate
41 | op.InvalidateOp{}.Add(ops) // HLdraw
42 |
43 | // Submit operations to the window.
44 | e.Frame(ops) // HLdraw
45 | // END OMIT
46 | }
47 | }
48 | }()
49 | app.Main()
50 | }
51 |
52 | // END RR OMIT
53 |
54 | var start = time.Now()
55 |
56 | func animateRadius(t time.Time, max float32) float32 {
57 | dt := t.Sub(start).Seconds()
58 | radius := math.Abs(math.Sin(dt))
59 | return float32(radius) * max
60 | }
61 |
--------------------------------------------------------------------------------
/stack.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "image"
5 | "image/color"
6 |
7 | "gioui.org/app"
8 | "gioui.org/io/system"
9 | "gioui.org/layout"
10 | "gioui.org/op"
11 | "gioui.org/op/clip"
12 | "gioui.org/op/paint"
13 | "gioui.org/unit"
14 | )
15 |
16 | func main() {
17 | go func() {
18 | w := app.NewWindow()
19 | var ops op.Ops
20 | for e := range w.Events() {
21 | if e, ok := e.(system.FrameEvent); ok {
22 | gtx := layout.NewContext(&ops, e)
23 | drawRects(gtx)
24 | e.Frame(gtx.Ops)
25 | }
26 | }
27 | }()
28 | app.Main()
29 | }
30 |
31 | // START OMIT
32 | func drawRects(gtx layout.Context) layout.Dimensions {
33 | return layout.Stack{Alignment: layout.Center}.Layout(gtx,
34 | // Red.
35 | layout.Stacked(func(gtx layout.Context) layout.Dimensions {
36 | return drawRect(gtx, color.RGBA{A: 0xff, R: 0xff}, unit.Dp(50))
37 | }),
38 |
39 | // Green.
40 | layout.Stacked(func(gtx layout.Context) layout.Dimensions {
41 | return drawRect(gtx, color.RGBA{A: 0xff, G: 0xff}, unit.Dp(100))
42 | }),
43 |
44 | // Blue.
45 | layout.Stacked(func(gtx layout.Context) layout.Dimensions {
46 | return drawRect(gtx, color.RGBA{A: 0xff, B: 0xff}, unit.Dp(150))
47 | }),
48 | )
49 | }
50 |
51 | // END OMIT
52 |
53 | func drawRect(gtx layout.Context, color color.RGBA, inset unit.Value) layout.Dimensions {
54 | in := layout.UniformInset(inset)
55 | return in.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
56 | cs := gtx.Constraints
57 | square := image.Rectangle{Max: cs.Max}
58 | paint.FillShape(gtx.Ops, color, clip.Rect(square).Op())
59 | return layout.Dimensions{Size: cs.Max}
60 | })
61 | }
62 |
--------------------------------------------------------------------------------
/pointer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "image"
5 | "image/color"
6 |
7 | "gioui.org/app"
8 | "gioui.org/io/pointer"
9 | "gioui.org/io/system"
10 | "gioui.org/layout"
11 | "gioui.org/op"
12 | "gioui.org/op/clip"
13 | "gioui.org/op/paint"
14 | )
15 |
16 | // START QUEUE OMIT
17 | func main() {
18 | go func() {
19 | w := app.NewWindow()
20 | button := new(Button)
21 | var ops op.Ops
22 | for e := range w.Events() {
23 | if e, ok := e.(system.FrameEvent); ok {
24 | gtx := layout.NewContext(&ops, e)
25 | button.Layout(gtx)
26 | e.Frame(gtx.Ops)
27 | }
28 | }
29 | }()
30 | app.Main()
31 | }
32 |
33 | // END QUEUE OMIT
34 |
35 | type Button struct {
36 | pressed bool
37 | }
38 |
39 | // START OMIT
40 | func (b *Button) Layout(gtx layout.Context) layout.Dimensions {
41 | for _, e := range gtx.Events(b) { // HLevent
42 | if e, ok := e.(pointer.Event); ok { // HLevent
43 | switch e.Type { // HLevent
44 | case pointer.Press: // HLevent
45 | b.pressed = true // HLevent
46 | case pointer.Release: // HLevent
47 | b.pressed = false // HLevent
48 | }
49 | }
50 | }
51 |
52 | col := color.RGBA{A: 0xff, R: 0xff}
53 | if b.pressed {
54 | col = color.RGBA{A: 0xff, G: 0xff}
55 | }
56 | pointer.Rect( // HLevent
57 | image.Rectangle{Max: image.Point{X: 500, Y: 500}}, // HLevent
58 | ).Add(gtx.Ops) // HLevent
59 | pointer.InputOp{Tag: b}.Add(gtx.Ops) // HLevent
60 | return drawSquare(gtx.Ops, col)
61 | }
62 |
63 | // END OMIT
64 |
65 | func drawSquare(ops *op.Ops, color color.RGBA) layout.Dimensions {
66 | square := image.Rectangle{
67 | Max: image.Point{X: 500, Y: 500},
68 | }
69 | paint.FillShape(ops, color, clip.Rect(square).Op())
70 | return layout.Dimensions{Size: image.Pt(500, 500)}
71 | }
72 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
2 | gioui.org v0.0.0-20201105153219-94d242d18c92 h1:1GHIbWom8jXI3wzPOq5JOT40uEaPUFH9eki/ViecVR8=
3 | gioui.org v0.0.0-20201105153219-94d242d18c92/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
4 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
5 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
6 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
7 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
8 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
9 | golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs=
10 | golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
11 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
12 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
13 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
14 | golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
15 | golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
16 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
17 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
18 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
19 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
20 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
21 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
22 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
23 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
24 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
25 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
26 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
27 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
28 | golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
29 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
30 |
--------------------------------------------------------------------------------
/cphgophers-nov-2019.slide:
--------------------------------------------------------------------------------
1 | Gio: Portable Immediate Mode GUI
2 | Cph Gophers Meetup, November 2019
3 |
4 | Elias Naur
5 | mail@eliasnaur.com
6 | @eliasnaur
7 | https://gioui.org
8 | https://scatter.im
9 | https://eliasnaur.com/sponsor
10 |
11 | * Introduction
12 |
13 | Gio - [[https://gioui.org][gioui.org]]
14 |
15 | - Immediate mode design.
16 | - Cross platform (macOS, Linux, FreeBSD, Windows, Android, iOS, tvOS, WebAssembly).
17 | - GPU accelerated vector and text rendering.
18 | - No garbage during drawing and layout.
19 | - Core is 100% Go. OS-specific native interfaces are optional.
20 |
21 |
22 | * Demo - Scatter
23 |
24 |
25 |
26 | * Immediate mode UI
27 |
28 | - UI state is owned by the program. Even your layout and widget tree.
29 | - No callbacks. Events are handled while drawing.
30 |
31 |
32 |
33 | * Blank window
34 |
35 | .play blank.go
36 |
37 |
38 |
39 | * Hello, World
40 |
41 | .play helloworld.go /START OMIT/,/END OMIT/ HLdraw
42 |
43 | : This the proverbial Hello, world program written with Gio. It's little bigger but still fit on a slide after leaving out the package statement and package imports.
44 |
45 | : Compared to the blank window from earlier, hello world loads a font, initializes a few support variables and draws a label.
46 |
47 | : There are no shortcuts in the program, perhaps except from the type assertion that in a larger program will be a type switch for the various event types.
48 |
49 |
50 | * Running Gio programs
51 |
52 | * Linux, macOS, Windows, FreeBSD
53 |
54 | Enable modules
55 |
56 | export GO111MODULE=on
57 |
58 | Build, install or run the program
59 |
60 | go build gioui.org/example/hello
61 | go install scatter.im/cmd/scatter
62 | go run helloworld.go
63 |
64 | : Running Gio programs is as straightforward as any other program, at least on desktop systems. Linux require a C compiler and a few development libraries, while macOS requires Xcode. There are no build dependencies for Windows, but Gio currently need the ANGLE OpenGL ES emulator to run.
65 |
66 | : I recommend enabling module mode so Gio is automatically downloaded for you and to shield yourself from the still frequent API changes.
67 |
68 |
69 |
70 | * Android
71 |
72 | Install the gogio tool
73 |
74 | go install gioui.org/cmd/gogio
75 | $GOBIN/gogio -target android -o hello.apk helloworld.go
76 |
77 | Install on a connected device or emulator with adb
78 |
79 | adb install hello.apk
80 |
81 | : The mobile platforms are of course more troublesome. The Gio project include a tool that can package a Gio program suitable for installation to a mobile device, an emulator or for including in an existing project.
82 |
83 | : To build for Android you need the Android SDK and NDK installed. The
84 | gogio tool can produce an apk file that can be installed with the Android adb tool.
85 |
86 |
87 |
88 | * iOS/tvOS
89 |
90 | For iOS/tvOS devices:
91 |
92 | $GOBIN/gogio -target -o hello.ipa -appid helloworld.go
93 |
94 | Use the .app file extension for simulators:
95 |
96 | $GOBIN/gogio -target -o hello.app helloworld.go
97 |
98 | Install on a running simulator
99 |
100 | xcrun simctl install booted hello.app
101 |
102 | : To build for iOS and tvOS you need Xcode and the bundle id from a valid provisioning profile. A good way to acquire that is to set up a sample project in Xcode and run it once on your device.
103 |
104 | : The iSimulators don't accept ipa packages, but rather raw .app
105 | directories. The gogio tool use the output extension to distinguish.
106 |
107 | : Please note that tvOS support is very early. For example, there is currenctly no API exposed for the remote control. I added support because it was easy and as a proof of concept.
108 |
109 |
110 |
111 | * Browsers (Go 1.14+)
112 |
113 | To output a directory ready to serve:
114 |
115 | $GOBIN/gogio -target js -o www helloworld.go
116 |
117 | Use a webserver or goexec to serve it:
118 |
119 | go run github.com/shurcooL/goexec 'http.ListenAndServe(":8080", http.FileServer(http.Dir("www")))'
120 |
121 | : The gogio tool also build a webassembly version of your program. It includes the required support files and a basic HTML host page so the program is ready to serve.
122 |
123 | : You can use any webserver that serves static files; I use Dmitri's goexec tool for demos and tests. Goexec can run a complete webserver in a single line if you have Go modules enabled.
124 |
125 | : Note that running in the browser works but is slow. Partly because Gio invokes JavaScript functions in a straightforward but suboptimal way, partly because the Go runtime is not yet a great match for webassembly. I hope that future webassembly improvements will drastically speed up Go in the browser.
126 |
127 | : Also note that Gio use a WebGL Canvas element for display and input. You don't get the convenience of HTML DOM elements. At least not yet.
128 |
129 | : [go run helloworld.go]
130 | : [gogio -target android -o android.apk helloworld.go]
131 | : [gogio -target ios -o hello.app helloworld.go]
132 | : [xcrun simctl install booted hello.app]
133 | : [adb install hello.apk]
134 | : [go run webassembly.go]
135 |
136 |
137 |
138 | * Operations
139 |
140 | * Operations
141 |
142 | Serializing operations
143 |
144 | import "gioui.org/op" // Pure Go
145 |
146 | var ops op.Ops
147 | // Add operations to ops
148 | op.InvalidateOp{}.Add(ops)
149 | ...
150 |
151 | Applying operations.
152 |
153 | import "gioui.org/io/system"
154 |
155 | var e system.FrameEvent
156 | e.Frame(&ops)
157 |
158 |
159 | : In Gio, the basic building block is the operation. There are operations for clipping, transforming and drawing as well as ops for controlling input flow and requesting a redraw for animation.
160 |
161 | : This is a list of drawing operations.
162 |
163 | : Immediate mode UI libraries redraw everything and Gio is no different; if your program state changes, you simply request a Redraw and redraw everything.
164 |
165 | : This is actually more efficient than it might sound, both through the sheer power of modern GPUs but also through designing operations to be efficient.
166 |
167 | : Operations are carefully designed such that they generate no garbage when drawing. The underlying Ops buffer is reused and the Add method of every op is written so the op itself never escapes to the heap.
168 |
169 | : For example, the label from the helloworld example is drawn garbage free, as long as your string is constant and re-use the font cache.
170 |
171 | * Operations
172 |
173 | Position other operations
174 |
175 | import "gioui.org/op"
176 | import "gioui.org/f32"
177 |
178 | op.TransformOp{}.Offset(f32.Point{...}).Add(ops)
179 |
180 | Request a redraw
181 |
182 | ui.InvalidateOp{}.Add(ops) // Immediate
183 | ui.InvalidateOp{At: ...}.Add(ops) // Delayed
184 |
185 | * Drawing operations
186 |
187 | Set current color or image
188 |
189 | import "gioui.org/op/paint"
190 |
191 | paint.ColorOp{Color: color.RGBA{...}}.Add(ops)
192 | paint.ImageOp{Src: ..., Rect: ...}.Add(ops)
193 |
194 | Draw with the current color or image
195 |
196 | paint.PaintOp{Rect: ...}.Add(ops)
197 |
198 |
199 | * Clip operations
200 |
201 | Clip drawing to a rectangle
202 |
203 | import "gioui.org/op/clip"
204 |
205 | var ops *op.Ops
206 |
207 | clip.Rect{Rect: f32.Rectangle{...}}.Op(ops).Add(ops)
208 |
209 | Rounded corners
210 |
211 | clip.Rect{Rect: ..., NE: ..., NW: ..., SE: ..., SW:...}.Op(ops).Add(ops)
212 |
213 | General (even-odd) outline
214 |
215 | var b clip.Path
216 | b.Begin(ops)
217 | b.Line(...)
218 | b.Quad(...) // Quadratic Beziér curve
219 | b.Cube(...) // Cubic Beziér curve
220 | b.End().Add(ops)
221 |
222 | : The major parts of any UI program are drawing, layout and input handling.
223 |
224 | : We'll tackle drawing first.
225 |
226 |
227 | * Input operations
228 |
229 | Keyboard and text input
230 |
231 | import "gioui.org/io/key"
232 |
233 | // Declare key handler.
234 | key.InputOp{Key: handler, Focus: true/false}.Add(ops)
235 |
236 | // Hide soft keyboard.
237 | key.HideInputOp{}.Add(ops)
238 |
239 | Mouse and touch input
240 |
241 | import "gioui.org/io/pointer"
242 |
243 | // Define hit area.
244 | pointer.Rect(image.Rectangle{...}).Add(ops)
245 | pointer.Ellipse(image.Rectangle{...}).Add(ops)
246 |
247 | // Declare pointer handler.
248 | pointer.InputOp{Key: c, Grab: true/false}
249 |
250 |
251 |
252 | * Drawing
253 |
254 |
255 | * Drawing (and animating)
256 |
257 | .play animatedclipping.go /START OMIT/,/END OMIT/
258 |
259 |
260 | * Layout
261 |
262 | : Layout is the positioning, sizing and layering of widgets.
263 |
264 | : Some framework add properties or special widgets for laying out other widgets.
265 |
266 | : Layouts in Gio is done through transient helper objects, except for the scrollable list which is user controlled.
267 |
268 |
269 | * Constraints and dimensions
270 |
271 | Constraints are input
272 |
273 | package layout // import gioui.org/layout
274 |
275 | type Constraints struct {
276 | Width Constraint
277 | Height Constraint
278 | }
279 |
280 | type Constraint struct {
281 | Min, Max int
282 | }
283 |
284 | Dimensions are output
285 |
286 | type Dimensions struct {
287 | Size image.Point
288 | Baseline int
289 | }
290 |
291 | * Constraints and dimensions
292 |
293 | Widgets accept constraints, output dimensions.
294 |
295 | package material // import gioui.org/widget/material
296 |
297 | func (l Label) Layout(gtx *layout.Context)
298 |
299 |
300 | Context tracks the current constraints and dimensions.
301 |
302 | package layout // import "gioui.org/layout"
303 |
304 | type Context struct {
305 | Constraints Constraints
306 | Dimensions Dimensions
307 |
308 | ...
309 | }
310 |
311 |
312 |
313 | * Example - two labels
314 |
315 | .play twolabels2.go /START DRAW OMIT/,/END DRAW OMIT/ HLdraw
316 |
317 |
318 |
319 | * Layout helpers
320 |
321 | Aligning
322 |
323 | var gtx *layout.Context
324 |
325 | layout.Center.Layout(gtx, func() {
326 | someWidget.Layout(gtx, ...) // Draw widget
327 | })
328 |
329 | Insetting
330 |
331 | inset := layout.Inset{Top: ui.Dp(8), ...} // 8dp top inset
332 | inset.Layout(gtx, func() {
333 | anotherWidget.Layout(gtx, ...) // Draw widget
334 | })
335 |
336 | : Centering is a special case of aligning widgets. Aligning and insetting widgets are so common that Gio provides two helper
337 | : types in the layout package, Align and Inset.
338 |
339 | : Both Align and Inset are designed to be garbage free. Go is clever enough to allocate them on the stack even though their Begin methods mutate them.
340 |
341 | : Note that the layout package represent distances with device independent points instead of pixels to ensure your program looks the
342 | : same regardless of your monitor's pixel density.
343 |
344 |
345 |
346 | * Flex layout
347 |
348 | Lay out widgets on an axis.
349 |
350 | .play flex.go /START OMIT/,/END OMIT/ HLflex
351 |
352 | : A more complex layout in Gio is the Flex. It mimics the flex layout from Flutter and is used for laying out widgets along an axis.
353 |
354 | : The program shown on this slide demonstrates a weighted layout, where the first rectangle takes up half the available space, while the two last
355 | : share the other half.
356 |
357 | : [go run flex.go]
358 |
359 |
360 |
361 | * Stack layout
362 |
363 | .play stack.go /START OMIT/,/END OMIT/ HLstack
364 |
365 | : Stack is another layout in Gio which as the name implies is used for layering widget on top of each other.
366 |
367 | : In this example, stack is used to stack a list of rectangles with increasing insets.
368 |
369 | : [go run stack.go]
370 |
371 |
372 |
373 | * List layout
374 |
375 | .code list.go /START INIT OMIT/,/END INIT OMIT/ HLlist
376 |
377 | .play list.go /START OMIT/,/END OMIT/ HLlist
378 |
379 | : The most complex layout in Gio is the scrollable List. It is also the first stateful widget you've seen, in that it can accept user input and scroll its content to match.
380 |
381 | : Even though the list is declared to be a million elements long, the list only lays out the currently visible elements.
382 |
383 | : List does that by asking your program to lay out a particular index, returned by the Index method.
384 |
385 | : [go run list.go]
386 |
387 |
388 |
389 | * Input
390 |
391 | : I came up with the name Gio as an acronym for "graphical input/output". So far we've only seen output: drawing and positioning drawings.
392 |
393 | : Let's move on to the other side, input.
394 |
395 |
396 |
397 | * Input queue and handler keys
398 |
399 | // Queue maps an event handler key to the events
400 | // available to the handler.
401 | type Queue interface {
402 | Events(k Key) []Event
403 | }
404 |
405 | // Key is the stable identifier for an event handler.
406 | // For a handler h, the key is typically &h.
407 | type Key interface{}
408 |
409 | : One of my favorite features of immediate mode UI programs is the absence of callbacks.
410 |
411 | : In Gio all events from input sources such your mouse, finger, keyboard are distributed to handlers through the input.Queue.
412 |
413 | : Queue is an interface with just a single method, Next, returning all events available in this frame for a given
414 | : handler.
415 |
416 | : A Key is the identifier for a handler. Any value will do, as long as it is stable across frames,
417 | : and can be used as a map key.
418 |
419 | : Typically the address of the handler is used as its Key.
420 |
421 |
422 |
423 | * Pointer event handling
424 |
425 | .play pointer.go /START OMIT/,/END OMIT/ HLevent
426 |
427 | : Let's say you have a Button widget. It has only one field, the pressed boolean.
428 |
429 | : The first part of the button layout method updates the pressed state. It runs through the available events and set the pressed to true for press events and false for release events.
430 |
431 | : Then, it registers the handler by specifying a hit area, in this case a rectangle, and a InputOp for specifying its own key.
432 |
433 | : Note how there are no callbacks involved and that there is no way or need to unregister a handler; all handler registrations are cleared
434 |
435 | : at the beginning of the next frame.
436 |
437 | : Registration and handling of events are separate and can be done in any order, because event handling is for the current set of events, while registration is for delivering events between frames.
438 |
439 | : [go run pointer.go]
440 |
441 |
442 |
443 |
444 | * Gestures
445 |
446 | import "gioui.org/op"
447 | import "gioui.org/gesture"
448 |
449 | Detect clicks
450 |
451 | var queue op.Queue
452 | var c gesture.Click
453 | for _, event := range c.Events(queue) {
454 | // event is a gesture.ClickEvent, not a raw pointer.Event.
455 | }
456 |
457 | Determine scroll distance from mouse wheel or touch drag/fling
458 |
459 | var cfg ui.Config
460 | var s gesture.Scroll
461 |
462 | distance := s.Scroll(cfg, queue, gesture.Vertical)
463 |
464 | : The gesture package help your program recognize higher level gestures from low level pointer events. Examples shown are click and scroll.
465 |
466 | : In a sense, gestures are state machines that are updated by raw pointers events.
467 |
468 | : The Click state machine record the current pressed state, which the Scroll state machine track touch velocity, while animating it on touch release.
469 |
470 |
471 | * Widgets and themes
472 |
473 | * Material theme
474 |
475 | import "gioui.org/widget/material"
476 |
477 | th := material.NewTheme()
478 |
479 | Labels (stateless)
480 |
481 | th.Label(unit.Sp(14), "14sp text").Layout(gtx)
482 | th.H3("H3 text").Layout(gtx)
483 |
484 | Customization
485 |
486 | // Per theme
487 | th.TextSize = unit.Sp(20)
488 | th.Color.Primary = color.RGBA{...}
489 |
490 | // Per widget
491 | lbl := th.H3("Custom text") // Use theme properties.
492 | lbl.Color = color.RGBA{...} // Set widget property.
493 | lbl.Layout(gtx)
494 |
495 |
496 | * Stateful widgets
497 |
498 | import "gioui.org/widget" // State
499 | import "gioui.org/widget/material" // Theme
500 |
501 | // Initialize and store button state.
502 | var button = new(widget.Button)
503 |
504 | Stateless appearance
505 |
506 | var th *material.Theme
507 |
508 | th.Button("Click me").Layout(gtx, button)
509 |
510 | Events
511 |
512 | for button.Clicked(gtx) {
513 | fmt.Println("Clicked!")
514 | }
515 |
516 | * Widgets - the Editor
517 |
518 | Initialize the editor
519 |
520 | import "gioui.org/widget"
521 |
522 | .code editor.go /START INIT OMIT/,/END INIT OMIT/
523 |
524 | Draw, layout and handle input in one call, Layout.
525 |
526 | .play editor.go /START OMIT/,/END OMIT/
527 |
528 |
529 | * Why Gio?
530 |
531 |
532 |
533 | * Why Gio?
534 |
535 | Gio is
536 |
537 | - Simple. Immediate mode design, no hidden state.
538 | - Portable. The core of Gio is all Go.
539 | - Fast. GPU accelerated, very little per-frame garbage.
540 | - Convenient. Develop on desktop, deploy on mobile.
541 | - Public domain source (UNLICENCE). Dual licenced MIT to please your lawyers.
542 |
--------------------------------------------------------------------------------
/gophercon-2019.slide:
--------------------------------------------------------------------------------
1 | Simple, Portable and Efficient Graphical Interfaces in Go
2 | Gophercon 2019
3 |
4 | Elias Naur
5 | mail@eliasnaur.com
6 | @eliasnaur
7 | https://gioui.org
8 | https://scatter.im
9 |
10 | * Go 2018 Survey Results
11 |
12 | .image survey_obstacles.svg.png 900 _
13 |
14 | : According to the Go survey, writing GUI programs is one of the top 5 challenges for Go programmers.
15 |
16 | : This talk is about that challenge, and about making Go a good choice for writing GUI programs.
17 |
18 | : I want to make a serious dent in fixing that and I want to make Go a natural and even obvious choice for writing GUI programs with.
19 |
20 | : Some of you have probably seen this figure before. It's from the Go 2018 survey results, and shows the top responses to the question "What is the biggest obstacle you personally face using Go today?"
21 |
22 | : As you may know, the top complaint, packaging and modules, is being taken care of.
23 |
24 | : The second, familarity; I suppose that's going to work itself out over time.
25 |
26 | : Number 3, generics, is also well covered; there's even a talk about it tomorrow by Ian Taylor.
27 |
28 | : So that's the top 3. And it seems momentum is building for doing something about the fifth entry, error handling.
29 |
30 | : That leaves #4 open, GUI development. This talk is about making a serious dent in that.
31 |
32 |
33 |
34 | * Introduction
35 |
36 | Gio - [[https://gioui.org][gioui.org]]
37 |
38 | Gio is a simple Go module for writing portable and fast graphical interfaces.
39 |
40 | Scatter - [[https://scatter.im][scatter.im]]
41 |
42 | Scatter is a Gio program for end-to-end encrypted messaging over email.
43 |
44 | : So what's in it for you?
45 |
46 | : In this talk I'm going to introduce Gio, a set of packages for writing simple and portable graphical user interfaces in Go.
47 |
48 | : It is written from scratch in Go and only depend on a few system libraries.
49 |
50 | : I've spent almost 2 years designing and developing it, and it's been my dog food for the last month or so while developing Scatter.
51 |
52 | : It really does feel much simpler and faster to write user interfaces with Gio compared to my years of experience with other libraries and frameworks.
53 |
54 |
55 |
56 |
57 |
58 | * Demo - Scatter
59 |
60 | : Gio is a comprehensive module and I have a lot to cover in my time slot. However, as a motivating example of what can be achieved today, here's Scatter which is a small program for sending encrypted messages over email.
61 |
62 | : Scatter runs on the desktop as well as on the two mobile platforms.
63 |
64 | : It asks for login details to a SMTP and IMAP host, after which you can send or receive invitations to messaging using the Signal protocol.
65 |
66 | : I've filled a few demo contacts and message threads to give you a feel of the program.
67 |
68 | : [demo scatter]
69 |
70 |
71 |
72 | * Features
73 |
74 | - Immediate mode design.
75 | - Only depends on lowest-level platform libraries.
76 | - GPU accelerated vector and text rendering.
77 | - No garbage generated in drawing or layout code.
78 | - Cross platform (macOS, Linux, Windows, Android, iOS, tvOS, Webassembly).
79 | - Core is 100% Go. OS-specific native interfaces are optional.
80 |
81 | : At the time I decided to write Gio I simply wanted to write mobile apps in pure Go.
82 |
83 | : Then, as I considered the various designs, Google's at that time up and coming Flutter framework nudged me to take the clean slate approach. Rather than interfacing with some existing toolkit and suffering the maintenance load of keeping the two worlds cooperating, Flutter does everything internally: drawing, input handling, layout, animation and so on. It takes much longer to build, but the result is much more maintainable.
84 |
85 | : The popularity of Flutter is a great boon to Gio; side-stepping a platform's native UI toolkit is risky, but Flutter is proof that the approach is feasible and reasonable.
86 |
87 | : Finally, for the efficient drawing of vector graphics, text in particular, Gio uses the same approach as Pathfinder, a Rust library for drawing vector graphics on the GPU.
88 |
89 | : With Pathfinder, there is no need to pre-bake vector outlines into images before drawing. For example, all text in Gio is rendered from the truetype font character outlines every frame.
90 |
91 |
92 | * Immediate mode UI
93 |
94 | - UI state is owned by the program. Even your layout and widget tree.
95 | - No callbacks. Events are handled while drawing.
96 |
97 | : .image tribaltrouble.jpg 370 _
98 |
99 | : I haven't written a line of Dear ImGui code, but reading about it and watching a presentation by fellow game developer Casey Muratori, I went further that even Flutter and abandoned the traditional implicit widget hierarchy and state tracking of other frameworks. Even React doesn't go as far as Dear ImGui and Gio.
100 |
101 | : What is an immediate mode UI?
102 |
103 | : When I wrote the custom GUI code for the 3D multiplayer game Tribal Trouble 15 years ago, I instinctively chose a design similar to Dear ImGui.
104 |
105 |
106 | * Blank window
107 |
108 | .play blank.go
109 |
110 | * Blank window
111 |
112 | .play blank.go HLmain
113 |
114 | * Blank window
115 |
116 | .play blank.go HLeventloop
117 |
118 | : I tried to fit a complete hello world example on a slide, but failed. Here's a minimal program for a blank window instead.
119 |
120 | : This is a minimal complete Gio program ready to run; it displays an empty window, and is otherwise fully functional.
121 |
122 | : * Blank window
123 |
124 | : .code blank.go HLinitfunc
125 |
126 | : A few surprising details arise from the listing. The first is that the window is created and run from a goroutine started in an init function, not from main. That's because on Android, all non-Java code must be loaded from a C library. And in Go, libraries don't run main functions.
127 |
128 |
129 |
130 | * Hello, World
131 |
132 | .play helloworld.go /START OMIT/,/END OMIT/ HLdraw
133 |
134 | : This the proverbial Hello, world program written with Gio. It's little bigger but still fit on a slide after leaving out the package statement and package imports.
135 |
136 | : Compared to the blank window from earlier, hello world loads a font, initializes a few support variables and draws a label.
137 |
138 | : There are no shortcuts in the program, perhaps except from the type assertion that in a larger program will be a type switch for the various event types.
139 |
140 |
141 | * Running Gio programs
142 |
143 | * Linux, macOS, Windows
144 |
145 | Enable modules
146 |
147 | export GO111MODULE=on
148 |
149 | Build, install or run the program
150 |
151 | go build gioui.org/example/hello
152 | go install scatter.im/cmd/scatter
153 | go run helloworld.go
154 |
155 | : Running Gio programs is as straightforward as any other program, at least on desktop systems. Linux require a C compiler and a few development libraries, while macOS requires Xcode. There are no build dependencies for Windows, but Gio currently need the ANGLE OpenGL ES emulator to run.
156 |
157 | : I recommend enabling module mode so Gio is automatically downloaded for you and to shield yourself from the still frequent API changes.
158 |
159 |
160 |
161 | * Android
162 |
163 | Install the gogio tool
164 |
165 | go install gioui.org/cmd/gogio
166 | $GOBIN/gogio -target android -o hello.apk helloworld.go
167 |
168 | Install on a connected device or emulator with adb
169 |
170 | adb install hello.apk
171 |
172 | : The mobile platforms are of course more troublesome. The Gio project include a tool that can package a Gio program suitable for installation to a mobile device, an emulator or for including in an existing project.
173 |
174 | : To build for Android you need the Android SDK and NDK installed. The
175 | gogio tool can produce an apk file that can be installed with the Android adb tool.
176 |
177 |
178 |
179 | * iOS/tvOS
180 |
181 | For iOS/tvOS devices:
182 |
183 | $GOBIN/gogio -target -o hello.ipa -appid helloworld.go
184 |
185 | Use the .app file extension for simulators:
186 |
187 | $GOBIN/gogio -target -o hello.app helloworld.go
188 |
189 | Install on a running simulator
190 |
191 | xcrun simctl install booted hello.app
192 |
193 | : To build for iOS and tvOS you need Xcode and the bundle id from a valid provisioning profile. A good way to acquire that is to set up a sample project in Xcode and run it once on your device.
194 |
195 | : The iSimulators don't accept ipa packages, but rather raw .app
196 | directories. The gogio tool use the output extension to distinguish.
197 |
198 | : Please note that tvOS support is very early. For example, there is currenctly no API exposed for the remote control. I added support because it was easy and as a proof of concept.
199 |
200 |
201 |
202 | * Browsers
203 |
204 | To output a directory ready to serve:
205 |
206 | $GOBIN/gogio -target js -o www helloworld.go
207 |
208 | Use a webserver or goexec to serve it:
209 |
210 | go run github.com/shurcooL/goexec 'http.ListenAndServe(":8080", http.FileServer(http.Dir("www")))'
211 |
212 | : The gogio tool also build a webassembly version of your program. It includes the required support files and a basic HTML host page so the program is ready to serve.
213 |
214 | : You can use any webserver that serves static files; I use Dmitri's goexec tool for demos and tests. Goexec can run a complete webserver in a single line if you have Go modules enabled.
215 |
216 | : Note that running in the browser works but is slow. Partly because Gio invokes JavaScript functions in a straightforward but suboptimal way, partly because the Go runtime is not yet a great match for webassembly. I hope that future webassembly improvements will drastically speed up Go in the browser.
217 |
218 | : Also note that Gio use a WebGL Canvas element for display and input. You don't get the convenience of HTML DOM elements. At least not yet.
219 |
220 | : [go run helloworld.go]
221 | : [gogio -target android -o android.apk helloworld.go]
222 | : [gogio -target ios -o hello.app helloworld.go]
223 | : [xcrun simctl install booted hello.app]
224 | : [adb install hello.apk]
225 | : [go run webassembly.go]
226 |
227 |
228 |
229 | * Operations
230 |
231 | * Operations
232 |
233 | Serializing operations
234 |
235 | import "gioui.org/op" // Pure Go
236 |
237 | var ops op.Ops
238 | // Add operations to ops
239 | op.InvalidateOp{}.Add(ops)
240 | ...
241 |
242 | Only the app package depends on platform libraries
243 |
244 | import "gioui.org/io/system"
245 |
246 | var e system.FrameEvent
247 | e.Frame(&ops)
248 |
249 |
250 | : In Gio, the basic building block is the operation. There are operations for clipping, transforming and drawing as well as ops for controlling input flow and requesting a redraw for animation.
251 |
252 | : This is a list of drawing operations.
253 |
254 | : Immediate mode UI libraries redraw everything and Gio is no different; if your program state changes, you simply request a Redraw and redraw everything.
255 |
256 | : This is actually more efficient than it might sound, both through the sheer power of modern GPUs but also through designing operations to be efficient.
257 |
258 | : Operations are carefully designed such that they generate no garbage when drawing. The underlying Ops buffer is reused and the Add method of every op is written so the op itself never escapes to the heap.
259 |
260 | : For example, the label from the helloworld example is drawn garbage free, as long as your string is constant and re-use the font cache.
261 |
262 | * Operations
263 |
264 | Position other operations
265 |
266 | import "gioui.org/op"
267 | import "gioui.org/f32"
268 |
269 | op.TransformOp{}.Offset(f32.Point{...}).Add(ops)
270 |
271 | Request a redraw
272 |
273 | ui.InvalidateOp{}.Add(ops) // Immediate
274 | ui.InvalidateOp{At: ...}.Add(ops) // Delayed
275 |
276 | * Drawing operations
277 |
278 | Set current color or image
279 |
280 | import "gioui.org/op/paint"
281 |
282 | paint.ColorOp{Color: color.RGBA{...}}.Add(ops)
283 | paint.ImageOp{Src: ..., Rect: ...}.Add(ops)
284 |
285 | Draw with the current color or image
286 |
287 | paint.PaintOp{Rect: ...}.Add(ops)
288 |
289 |
290 | * Clip operations
291 |
292 | Clip drawing to a rectangle
293 |
294 | import "gioui.org/op/clip"
295 |
296 | clip.Rect{Rect: image.Rectangle{...}}.Op(ops).Add(ops)
297 |
298 | Or to an outline
299 |
300 | var b paint.Path
301 | b.Begin(ops)
302 | b.Line(...)
303 | b.Quad(...) // Quadratic Beziér curve
304 | b.Cube(...) // Cubic Beziér curve
305 | b.End().Add(ops)
306 |
307 | : The major parts of any UI program are drawing, layout and input handling.
308 |
309 | : We'll tackle drawing first.
310 |
311 |
312 | * Input operations
313 |
314 | Keyboard and text input
315 |
316 | import "gioui.org/io/key"
317 |
318 | // Declare key handler.
319 | key.InputOp{Key: handler, Focus: true/false}.Add(ops)
320 |
321 | // Hide soft keyboard.
322 | key.HideInputOp{}.Add(ops)
323 |
324 | Mouse and touch input
325 |
326 | import "gioui.org/io/pointer"
327 |
328 | // Define hit area.
329 | pointer.Rect(...).Add(ops)
330 | pointer.Ellipse().Add(ops)
331 |
332 | // Declare pointer handler.
333 | pointer.InputOp{Key: c, Grab true/false}
334 |
335 |
336 |
337 | * Drawing
338 |
339 |
340 | * Drawing (and animating)
341 |
342 | Drawing and animating a clipped square
343 |
344 | .play animatedclipping.go /START OMIT/,/END OMIT/
345 |
346 | : This is another animated example, this time with a clip that changes over time.
347 |
348 | : [go run animatedpaths.go]
349 |
350 |
351 |
352 | * Layout
353 |
354 | : Layout is the positioning, sizing and layering of widgets.
355 |
356 | : Some framework add properties or special widgets for laying out other widgets.
357 |
358 | : Layouts in Gio is done through transient helper objects, except for the scrollable list which is user controlled.
359 |
360 |
361 | * Constraints and dimensions
362 |
363 | Constraints are input
364 |
365 | package layout // import gioui.org/layout
366 |
367 | type Constraints struct {
368 | Width Constraint
369 | Height Constraint
370 | }
371 |
372 | type Constraint struct {
373 | Min, Max int
374 | }
375 |
376 | Dimensions are output
377 |
378 | type Dimensions struct {
379 | Size image.Point
380 | Baseline int
381 | }
382 |
383 | * Constraints and dimensions
384 |
385 | Widgets accept constraints, output dimensions, stored in
386 | layout.Context.
387 |
388 | package text // import gioui.org/text
389 |
390 | func (l Label) Layout(gtx *layout.Context)
391 | func (e *Editor) Layout(gtx *layout.Context)
392 |
393 | package widget // import gioui.org/widget
394 |
395 | func (im Image) Layout(gtx *layout.Context)
396 |
397 |
398 | : Gio borrows Constraints and Dimensions from the Flutter framework.
399 |
400 | : The idea is that a widgets is given a range of accepted sizes, and return its actual "layout" size.
401 |
402 | : Note that some widgets may choose to draw outside its layout rectangle.
403 |
404 | : By convention, widgets have a Layout method that take the constraints as input and return the dimensions.
405 |
406 | : Label is no different
407 |
408 |
409 |
410 |
411 | * Example - two labels
412 |
413 | .play twolabels2.go /START DRAW OMIT/,/END DRAW OMIT/ HLdraw
414 |
415 | : The constraints given to us in drawLabel are rigid, and forces the dimensions of the labels to take up the whole window.
416 |
417 | : So first we loosen the constraints by setting the minimum height to 0.
418 |
419 | : Then, we use the dimensions of the first label to offset the second.
420 |
421 | : [go run twolabels2.go]
422 |
423 |
424 |
425 | * Layout helpers
426 |
427 | Aligning
428 |
429 | var gtx *layout.Context
430 |
431 | layout.Center.Layout(gtx, func() {
432 | someWidget.Layout(gtx, ...) // Draw widget
433 | })
434 | dimensions := gtx.Dimensions
435 |
436 | Insetting
437 |
438 | inset := layout.Inset{Top: ui.Dp(8), ...} // 8dp top inset
439 | inset.Layout(gtx, func() {
440 | anotherWidget.Layout(gtx, ...) // Draw widget
441 | })
442 | dimensions := gtx.Dimensions
443 |
444 | : Centering is a special case of aligning widgets. Aligning and insetting widgets are so common that Gio provides two helper
445 | : types in the layout package, Align and Inset.
446 |
447 | : Both Align and Inset are designed to be garbage free. Go is clever enough to allocate them on the stack even though their Begin methods mutate them.
448 |
449 | : Note that the layout package represent distances with device independent points instead of pixels to ensure your program looks the
450 | : same regardless of your monitor's pixel density.
451 |
452 |
453 |
454 | * Flex layout
455 |
456 | Lay out widgets on an axis.
457 |
458 | .play flex.go /START OMIT/,/END OMIT/ HLflex
459 |
460 | : A more complex layout in Gio is the Flex. It mimics the flex layout from Flutter and is used for laying out widgets along an axis.
461 |
462 | : The program shown on this slide demonstrates a weighted layout, where the first rectangle takes up half the available space, while the two last
463 | : share the other half.
464 |
465 | : [go run flex.go]
466 |
467 |
468 |
469 | * Stack layout
470 |
471 | .play stack.go /START OMIT/,/END OMIT/ HLstack
472 |
473 | : Stack is another layout in Gio which as the name implies is used for layering widget on top of each other.
474 |
475 | : In this example, stack is used to stack a list of rectangles with increasing insets.
476 |
477 | : [go run stack.go]
478 |
479 |
480 |
481 | * List layout
482 |
483 | .code list.go /START INIT OMIT/,/END INIT OMIT/ HLlist
484 |
485 | .play list.go /START OMIT/,/END OMIT/ HLlist
486 |
487 | : The most complex layout in Gio is the scrollable List. It is also the first stateful widget you've seen, in that it can accept user input and scroll its content to match.
488 |
489 | : Even though the list is declared to be a million elements long, the list only lays out the currently visible elements.
490 |
491 | : List does that by asking your program to lay out a particular index, returned by the Index method.
492 |
493 | : [go run list.go]
494 |
495 |
496 |
497 | * Input
498 |
499 | : I came up with the name Gio as an acronym for "graphical input/output". So far we've only seen output: drawing and positioning drawings.
500 |
501 | : Let's move on to the other side, input.
502 |
503 |
504 |
505 | * Input queue and handler keys
506 |
507 | // Queue maps an event handler key to the events
508 | // available to the handler.
509 | type Queue interface {
510 | Events(k Key) []Event
511 | }
512 |
513 | // Key is the stable identifier for an event handler.
514 | // For a handler h, the key is typically &h.
515 | type Key interface{}
516 |
517 | : One of my favorite features of immediate mode UI programs is the absence of callbacks.
518 |
519 | : In Gio all events from input sources such your mouse, finger, keyboard are distributed to handlers through the input.Queue.
520 |
521 | : Queue is an interface with just a single method, Next, returning all events available in this frame for a given
522 | : handler.
523 |
524 | : A Key is the identifier for a handler. Any value will do, as long as it is stable across frames,
525 | : and can be used as a map key.
526 |
527 | : Typically the address of the handler is used as its Key.
528 |
529 |
530 |
531 | * Pointer event handling
532 |
533 | .play pointer.go /START OMIT/,/END OMIT/ HLevent
534 |
535 | : Let's say you have a Button widget. It has only one field, the pressed boolean.
536 |
537 | : The first part of the button layout method updates the pressed state. It runs through the available events and set the pressed to true for press events and false for release events.
538 |
539 | : Then, it registers the handler by specifying a hit area, in this case a rectangle, and a InputOp for specifying its own key.
540 |
541 | : Note how there are no callbacks involved and that there is no way or need to unregister a handler; all handler registrations are cleared
542 |
543 | : at the beginning of the next frame.
544 |
545 | : Registration and handling of events are separate and can be done in any order, because event handling is for the current set of events, while registration is for delivering events between frames.
546 |
547 | : [go run pointer.go]
548 |
549 |
550 |
551 | * Window event queue
552 |
553 | The Window's Queue method returns an ui.Queue for OS events.
554 |
555 | package app // import gioui.org/app
556 |
557 | func (w *Window) Queue() *Queue
558 |
559 |
560 |
561 | * Gestures
562 |
563 | import "gioui.org/op"
564 | import "gioui.org/gesture"
565 |
566 | Detect clicks
567 |
568 | var queue op.Queue
569 | var c gesture.Click
570 | for _, event := range c.Events(queue) {
571 | // event is a gesture.ClickEvent, not a raw pointer.Event.
572 | }
573 |
574 | Determine scroll distance from mouse wheel or touch drag/fling
575 |
576 | var cfg ui.Config
577 | var s gesture.Scroll
578 |
579 | distance := s.Scroll(cfg, queue, gesture.Vertical)
580 |
581 | : The gesture package help your program recognize higher level gestures from low level pointer events. Examples shown are click and scroll.
582 |
583 | : In a sense, gestures are state machines that are updated by raw pointers events.
584 |
585 | : The Click state machine record the current pressed state, which the Scroll state machine track touch velocity, while animating it on touch release.
586 |
587 |
588 | * Widgets
589 |
590 | * Widgets - the Editor
591 |
592 | Initialize the editor
593 |
594 | import "gioui.org/text"
595 |
596 | .code editor.go /START INIT OMIT/,/END INIT OMIT/
597 |
598 | Draw, layout and handle input in one call.
599 |
600 | .play editor.go /START OMIT/,/END OMIT/
601 |
602 | * Why Gio?
603 |
604 |
605 |
606 | * Why Gio?
607 |
608 | Gio is
609 |
610 | - Simple. Immediate mode design, no hidden state.
611 | - Portable. The core of Gio is all Go.
612 | - Fast. GPU accelerated, very little per-frame garbage.
613 | - Convenient. Develop on desktop, deploy on mobile.
614 | - Public domain source (UNLICENCE). Dual licenced MIT to please your lawyers.
615 |
616 | Most importantly, Gio needs your help to succeed!
617 |
618 | : So if you want to write your user interface in Go,
619 |
620 | : and care about as simple and straightforward programming interface.
621 |
622 | : Or if you like myself, loathe to repeat yourself, Gio runs on all the popular platforms.
623 |
624 | : Portability is also very convenient during development. I usually test on my desktop system while only occasionally deploying to an emulator or device for verification or for mobile specific features.
625 |
626 | : That's it for a short introduction to the major parts of the Gio library as it looks like today.
627 |
628 | : Perhaps you're wondering whether you should spend time on Gio.
629 |
630 | : I put a lot of effort into ensuring Gio's design follows that of Go: simple, orthogonal and scalable. Not just for speed but for programmer productivity.
631 |
632 | : Simplicity. The immediate mode design makes it much less frustrating to debug and develop UI programs.
633 |
634 | : Portability, because it's written in Go and because its only non-Go dependencies are low level system libraries for window management, input handling, and access to the GPU.
635 |
636 | : Gio is fast because Go is fast and because it's designed not to generate garbage during a frame. And the heavy lifting is done by your GPU.
637 |
638 | : Convenient. Gio enjoys the very low compilation times and because it runs on your desktop you don't need a device or emulator for much of your mobile development.
639 |
640 | : And finally, Gio is unlicenced so you can take whatever you like from Gio and use it in your own project if you choose. You don't need to pollute your code with attribution or licence.
641 |
642 | : Let's make Go a natural and even an obvious choice for writing GUI programs!
643 |
644 |
645 | : What about the future of Gio?
646 |
647 | : Well, I've already spent over a year on it, so according to the Lindy effect I'm likely to spend at least another year on it.
648 |
649 | : I'm going to spend that time bringing Gio closer to a 1.0 version and on Gio programs that interest me, such as Scatter.
650 |
651 | : I very much hope you will join me in the future development of Gio. It most likely won't succeed without your help.
652 |
653 | : If you have any questions or comments I'm available during the entire Gophercon.
654 |
655 | : Thank you for listening.
656 |
--------------------------------------------------------------------------------
/survey_obstacles.svg:
--------------------------------------------------------------------------------
1 |
2 |
293 |
--------------------------------------------------------------------------------