├── 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 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 57 | 62 | 67 | 72 | 77 | 82 | 87 | 89 | 94 | 95 | 102 | 109 | 116 | 123 | 130 | 137 | 144 | 151 | 158 | 165 | 170 | 175 | 180 | 185 | 190 | 195 | 200 | 205 | 210 | 215 | 220 | 225 | 230 | 235 | 240 | 245 | 250 | 255 | 260 | 265 | 270 | 275 | 280 | 285 | 292 | 293 | --------------------------------------------------------------------------------