├── .gitignore ├── 0-quick-start ├── 0-immediate-mode │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go ├── 1-hello-world │ └── main.go ├── 2-hello-prod │ └── main.go └── 3-hello-qapp │ └── main.go ├── 1-drawing ├── 0-paint │ └── main.go ├── 1-clip │ └── main.go ├── 2-outline │ └── main.go ├── 3-stroke │ └── main.go ├── 4-offset │ └── main.go ├── 5-save │ └── main.go ├── 6-image │ ├── gamer.png │ └── main.go └── 7-animation │ └── main.go ├── 2-interaction ├── 0-key │ └── main.go ├── 1-pointer │ └── main.go ├── 2-drag │ └── main.go ├── 3-click │ └── main.go └── 4-button │ └── main.go ├── 3-widget ├── 0-context │ └── main.go ├── 1-theme │ └── main.go ├── 2-button │ └── main.go ├── 3-label │ └── main.go ├── 4-editor │ └── editor.go ├── 5-slider │ └── main.go └── 6-image │ └── main.go ├── 4-layout ├── 0-direction │ └── main.go ├── 1-inset │ └── main.go ├── 2-stack │ └── main.go ├── 3-flex │ └── main.go └── 4-list │ └── main.go ├── 5-async └── 0-select │ └── main.go ├── 6-case-study ├── 0-counter │ └── main.go ├── 1-life │ ├── board.go │ ├── main.go │ └── style.go └── 2-timer │ ├── main.go │ └── timer.go ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── qapp └── loop.go └── qasset ├── asset.go ├── gamer.png └── neutral.png /.gitignore: -------------------------------------------------------------------------------- 1 | *_ignore 2 | 3 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 4 | *.o 5 | *.a 6 | *.so 7 | 8 | *~* 9 | *.huge.* 10 | 11 | # Folders 12 | _obj 13 | _test 14 | 15 | # Architecture specific extensions/prefixes 16 | *.[568vq] 17 | [568vq].out 18 | 19 | *.cgo1.go 20 | *.cgo2.c 21 | _cgo_defun.c 22 | _cgo_gotypes.go 23 | _cgo_export.* 24 | 25 | _testmain.go 26 | 27 | *.exe 28 | *.test 29 | *.prof 30 | 31 | *.svg -------------------------------------------------------------------------------- /0-quick-start/0-immediate-mode/README.md: -------------------------------------------------------------------------------- 1 | # Immediate Mode UI 2 | 3 | ``` 4 | ┌──────────────────────────────────────────────┐ 5 | │ INPUT / EVENTS │ 6 | │ ▼ 7 | │ ┌────────────────────────────┐ 8 | │ │APPLICATION │ 9 | ┌────────────────────────────────────────┐ │ ┌LAYOUT─────────┐ │ 10 | │OS │ │ │ │ │ 11 | │ ┌─────────┐ ┌──────────┐ ┌───────────┐ │ │ │ ┌LAYOUT────┐│ │ 12 | │ │ GPU │ │ Keyboard │ │ Windowing │ │ │ │ │ ││ │ 13 | │ └─────────┘ ├──────────┤ └───────────┘ │ │ STATE───┼──▶│ ││ │ 14 | │ │ Mouse │ │ │ │ └──────────┘│ │ 15 | │ ├──────────┤ │ │ │ ┌LAYOUT────┐│ │ 16 | │ │ Touch │ │ │ │ │ ││ │ 17 | │ └──────────┘ │ │ STATE───┼──▶│ ││ │ 18 | │ │ │ │ └──────────┘│ │ 19 | └────────────────────────────────────────┘ │ │ │ │ 20 | ▲ │ └───────────────┘ │ 21 | │ └────────────────────────────┘ 22 | │ │ 23 | │ │ 24 | │ OPERATIONS │ 25 | └──────────────────────────────────────────────┘ 26 | ``` -------------------------------------------------------------------------------- /0-quick-start/0-immediate-mode/go.mod: -------------------------------------------------------------------------------- 1 | module termui 2 | 3 | go 1.19 4 | 5 | require github.com/nsf/termbox-go v1.1.1 6 | 7 | require github.com/mattn/go-runewidth v0.0.9 // indirect 8 | -------------------------------------------------------------------------------- /0-quick-start/0-immediate-mode/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 2 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 3 | github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= 4 | github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= 5 | -------------------------------------------------------------------------------- /0-quick-start/0-immediate-mode/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | 9 | "github.com/nsf/termbox-go" 10 | ) 11 | 12 | func main() { 13 | var alpha bool 14 | var beta bool 15 | var gamma bool 16 | var delta bool 17 | 18 | Main(func(frame *Frame) { 19 | Checkbox{Label: "Alpha", Value: &alpha}.Layout(frame) 20 | Checkbox{Label: "Beta", Value: &beta}.Layout(frame) 21 | Checkbox{Label: "Gamma", Value: &gamma}.Layout(frame) 22 | Checkbox{Label: "Delta", Value: &delta}.Layout(frame) 23 | }) 24 | } 25 | 26 | /* Example Component */ 27 | 28 | type Checkbox struct { 29 | Label string 30 | Value *bool 31 | } 32 | 33 | func (box Checkbox) Layout(frame *Frame) { 34 | frame.Focus.Input(func() { 35 | focused := frame.Focus.Active() 36 | if focused { 37 | switch frame.Key { 38 | case termbox.KeyArrowUp: 39 | frame.Focus.Prev() 40 | case termbox.KeyArrowDown: 41 | frame.Focus.Next() 42 | case termbox.KeyEnter, termbox.KeySpace: 43 | *box.Value = !*box.Value 44 | } 45 | } 46 | 47 | var check = "[ ] " 48 | if *box.Value { 49 | check = "[x] " 50 | } 51 | 52 | if focused { 53 | frame.Draw.Highlight(check, box.Label) 54 | } else { 55 | frame.Draw.Default(check, box.Label) 56 | } 57 | }) 58 | } 59 | 60 | /* Immediate Mode TUI */ 61 | 62 | // Main starts the main loop of a immediate mode loop. 63 | func Main(fn func(frame *Frame)) { 64 | if err := termbox.Init(); err != nil { 65 | log.Fatal(err) 66 | } 67 | defer termbox.Close() 68 | 69 | var frame Frame 70 | termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse) 71 | 72 | loop: 73 | for { 74 | switch ev := termbox.PollEvent(); ev.Type { 75 | case termbox.EventResize: 76 | ev.Key = 0 77 | fallthrough 78 | case termbox.EventKey: 79 | if ev.Key == termbox.KeyCtrlC { 80 | break loop 81 | } 82 | 83 | frame.Key = ev.Key 84 | 85 | frame.Draw.Invalidate = true 86 | for frame.Draw.Invalidate { 87 | frame.Draw.BeforeFrame() 88 | 89 | termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) 90 | fn(&frame) 91 | termbox.Flush() 92 | 93 | if frame.Focus.AfterFrame() { 94 | frame.Draw.Invalidate = true 95 | } 96 | 97 | frame.Key = 0 98 | } 99 | } 100 | } 101 | } 102 | 103 | // Frame contains all information to update and draw the UI. 104 | type Frame struct { 105 | Input 106 | Draw Draw 107 | Focus Focus 108 | } 109 | 110 | // Input contains information about the input key. 111 | type Input struct { 112 | Key termbox.Key 113 | } 114 | 115 | // Focus tracks currently focused items and next focus state. 116 | type Focus struct { 117 | current int 118 | active int 119 | next int 120 | } 121 | 122 | // Input creates a widget that can be focused. 123 | func (focus *Focus) Input(fn func()) { 124 | focus.current++ 125 | if focus.active < 0 { 126 | focus.active = 0 127 | focus.next = 0 128 | } 129 | fn() 130 | } 131 | 132 | // Set sets the focus to the current context. 133 | func (focus *Focus) Set() { focus.next = focus.current } 134 | 135 | // Prev moves focus backwards. 136 | func (focus *Focus) Prev() { focus.next = focus.active - 1 } 137 | 138 | // Next moves focus forward. 139 | func (focus *Focus) Next() { focus.next = focus.active + 1 } 140 | 141 | // AfterFrame updates the focus state. 142 | func (focus *Focus) AfterFrame() (updated bool) { 143 | last := focus.active 144 | 145 | focus.active = focus.next 146 | if focus.current > 0 { 147 | if focus.active > focus.current { 148 | focus.active = focus.active % (focus.current + 1) 149 | } 150 | for focus.active < 0 { 151 | focus.active += (focus.current + 1) 152 | } 153 | } else { 154 | focus.active = -1 155 | } 156 | focus.current = -1 157 | focus.next = focus.active 158 | 159 | return last != focus.active 160 | } 161 | 162 | // Active checks whether the current input is in focus. 163 | func (focus *Focus) Active() bool { 164 | return focus.current == focus.active 165 | } 166 | 167 | // Draw contains operations to updating the screen. 168 | type Draw struct { 169 | Line int 170 | Invalidate bool 171 | } 172 | 173 | // BeforeFrame resets the draw state. 174 | func (draw *Draw) BeforeFrame() { 175 | draw.Line = 0 176 | draw.Invalidate = false 177 | } 178 | 179 | // Default draws text with default styling. 180 | func (draw *Draw) Default(args ...interface{}) { 181 | for x, c := range fmt.Sprint(args...) { 182 | termbox.SetCell(x, draw.Line, c, termbox.ColorWhite, termbox.ColorBlack) 183 | } 184 | draw.Line++ 185 | } 186 | 187 | // Highlight draws text with inverted styling. 188 | func (draw *Draw) Highlight(args ...interface{}) { 189 | for x, c := range fmt.Sprint(args...) { 190 | termbox.SetCell(x, draw.Line, c, termbox.ColorBlack, termbox.ColorWhite) 191 | } 192 | draw.Line++ 193 | } 194 | -------------------------------------------------------------------------------- /0-quick-start/1-hello-world/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image/color" 7 | "log" 8 | "os" 9 | 10 | "gioui.org/app" // app contains Window handling. 11 | "gioui.org/font/gofont" // gofont is used for loading the default font. 12 | "gioui.org/io/system" // system is used for system events (e.g. closing the window). 13 | "gioui.org/layout" // layout is used for layouting widgets. 14 | "gioui.org/op" // op is used for recording different operations. 15 | "gioui.org/text" // text contains constants for text layouting. 16 | "gioui.org/widget/material" // material contains material design widgets. 17 | ) 18 | 19 | func main() { 20 | go func() { 21 | w := app.NewWindow() 22 | if err := loop(w); err != nil { 23 | log.Println(err) 24 | os.Exit(1) 25 | } 26 | os.Exit(0) 27 | }() 28 | app.Main() 29 | } 30 | 31 | func loop(w *app.Window) error { 32 | // th contains constants for theming. 33 | th := material.NewTheme(gofont.Collection()) 34 | 35 | // ops will be used to encode different operations. 36 | var ops op.Ops 37 | 38 | // listen for events happening on the window. 39 | for e := range w.Events() { 40 | // detect the type of the event. 41 | switch e := e.(type) { 42 | // this is sent when the application should re-render. 43 | case system.FrameEvent: 44 | // gtx is used to pass around rendering and event information. 45 | gtx := layout.NewContext(&ops, e) 46 | 47 | // handle all UI logic 48 | l := material.H1(th, "Hello, Gio") 49 | maroon := color.NRGBA{R: 127, G: 0, B: 0, A: 255} 50 | l.Color = maroon 51 | l.Alignment = text.Middle 52 | l.Layout(gtx) 53 | 54 | // render and handle the operations from the UI. 55 | e.Frame(gtx.Ops) 56 | 57 | // this is sent when the application is closed. 58 | case system.DestroyEvent: 59 | return e.Err 60 | } 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /0-quick-start/2-hello-prod/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image/color" 7 | "log" 8 | "os" 9 | 10 | "gioui.org/app" // app contains Window handling. 11 | "gioui.org/font/gofont" // gofont is used for loading the default font. 12 | "gioui.org/io/system" // system is used for system events (e.g. closing the window). 13 | "gioui.org/layout" // layout is used for layouting widgets. 14 | "gioui.org/op" // op is used for recording different operations. 15 | "gioui.org/text" // text contains constants for text layouting. 16 | "gioui.org/unit" // unit is used to define pixel-independent sizes 17 | "gioui.org/widget/material" // material contains material design widgets. 18 | ) 19 | 20 | var ( 21 | TitleColor = color.NRGBA{R: 127, G: 0, B: 0, A: 255} 22 | ) 23 | 24 | func main() { 25 | // The ui loop is separated from the application window creation 26 | // such that it can be used for testing. 27 | ui := NewUI() 28 | 29 | go func() { 30 | w := app.NewWindow( 31 | // Set the window title. 32 | app.Title("Hello, Prod!"), 33 | // Set the size for the window. 34 | app.Size(unit.Dp(800), unit.Dp(400)), 35 | ) 36 | if err := ui.Run(w); err != nil { 37 | log.Println(err) 38 | os.Exit(1) 39 | } 40 | os.Exit(0) 41 | }() 42 | 43 | app.Main() 44 | } 45 | 46 | // UI holds all of the application state. 47 | type UI struct { 48 | // Theme is used to hold the fonts used throughout the application. 49 | Theme *material.Theme 50 | } 51 | 52 | // NewUI creates a new UI using the Go Fonts. 53 | func NewUI() *UI { 54 | ui := &UI{} 55 | // Load the theme and fonts. 56 | ui.Theme = material.NewTheme(gofont.Collection()) 57 | return ui 58 | } 59 | 60 | // Run handles window events and renders the application. 61 | func (ui *UI) Run(w *app.Window) error { 62 | // ops will be used to encode different operations. 63 | var ops op.Ops 64 | 65 | // listen for events happening on the window. 66 | for e := range w.Events() { 67 | // detect the type of the event. 68 | switch e := e.(type) { 69 | // this is sent when the application should re-render. 70 | case system.FrameEvent: 71 | // gtx is used to pass around rendering and event information. 72 | gtx := layout.NewContext(&ops, e) 73 | // handle all UI logic. 74 | ui.Layout(gtx) 75 | // render and handle the operations from the UI. 76 | e.Frame(gtx.Ops) 77 | 78 | // this is sent when the application is closed. 79 | case system.DestroyEvent: 80 | return e.Err 81 | } 82 | } 83 | 84 | return nil 85 | } 86 | 87 | // Layout handles rendering and input. 88 | func (ui *UI) Layout(gtx layout.Context) layout.Dimensions { 89 | return Title(ui.Theme, "Hello, Prod!").Layout(gtx) 90 | } 91 | 92 | // Title creates a center aligned H1. 93 | func Title(th *material.Theme, caption string) material.LabelStyle { 94 | l := material.H1(th, caption) 95 | l.Color = TitleColor 96 | l.Alignment = text.Middle 97 | return l 98 | } 99 | -------------------------------------------------------------------------------- /0-quick-start/3-hello-qapp/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image/color" 7 | 8 | "gioui.org/font/gofont" // gofont is used for loading the default font. 9 | "gioui.org/layout" // layout is used for layouting widgets. 10 | "gioui.org/text" // text contains constants for text layouting. 11 | "gioui.org/widget/material" // material contains material design widgets. 12 | 13 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 14 | ) 15 | 16 | var Theme = material.NewTheme(gofont.Collection()) 17 | 18 | func main() { qapp.Layout(Layout) } 19 | 20 | // Layout handles rendering and input. 21 | func Layout(gtx layout.Context) layout.Dimensions { 22 | return Title(Theme, "Hello, Quick!").Layout(gtx) 23 | } 24 | 25 | // Title creates a center aligned H1. 26 | func Title(th *material.Theme, caption string) material.LabelStyle { 27 | l := material.H1(th, caption) 28 | l.Color = color.NRGBA{R: 127, G: 0, B: 0, A: 255} 29 | l.Alignment = text.Middle 30 | return l 31 | } 32 | -------------------------------------------------------------------------------- /1-drawing/0-paint/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image/color" 7 | 8 | "gioui.org/op" // op is used for recording different operations. 9 | "gioui.org/op/paint" // paint contains operations for coloring. 10 | 11 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 12 | ) 13 | 14 | func main() { 15 | qapp.Render(func(ops *op.Ops) { 16 | red := color.NRGBA{R: 0xFF, A: 0xFF} 17 | // ColorOp sets the brush for painting. 18 | paint.ColorOp{Color: red}.Add(ops) 19 | // PaintOp paints the configured. 20 | paint.PaintOp{}.Add(ops) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /1-drawing/1-clip/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "image/color" 8 | 9 | "gioui.org/op" // op is used for recording different operations. 10 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 11 | "gioui.org/op/paint" // paint contains operations for coloring. 12 | 13 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 14 | ) 15 | 16 | func main() { 17 | qapp.Render(func(ops *op.Ops) { 18 | // It's possible to restrict the area where to draw. 19 | clipping := clip.Rect{Max: image.Pt(100, 100)}.Push(ops) 20 | defer clipping.Pop() 21 | // defer clip.Rect{Min: image.Pt(40, 50), Max: image.Pt(60, 200)}.Push(ops).Pop() 22 | 23 | // color the clip area: 24 | red := color.NRGBA{R: 0xFF, A: 0xFF} 25 | paint.ColorOp{Color: red}.Add(ops) 26 | paint.PaintOp{}.Add(ops) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /1-drawing/2-outline/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image/color" 7 | 8 | "gioui.org/f32" // f32 contains float32 points. 9 | "gioui.org/op" // op is used for recording different operations. 10 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 11 | "gioui.org/op/paint" // paint contains operations for coloring. 12 | 13 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 14 | ) 15 | 16 | func main() { 17 | qapp.Render(func(ops *op.Ops) { 18 | // create a custom clip shape 19 | var p clip.Path 20 | p.Begin(ops) 21 | p.MoveTo(f32.Pt(30, 30)) 22 | p.LineTo(f32.Pt(170, 170)) 23 | p.LineTo(f32.Pt(80, 170)) 24 | // the path must be closed 25 | p.Close() 26 | 27 | // set the clip to the outline 28 | defer clip.Outline{ 29 | Path: p.End(), 30 | }.Op().Push(ops).Pop() 31 | 32 | // color the clip area: 33 | red := color.NRGBA{R: 0xFF, A: 0xFF} 34 | paint.ColorOp{Color: red}.Add(ops) 35 | paint.PaintOp{}.Add(ops) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /1-drawing/3-stroke/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image/color" 7 | 8 | "gioui.org/f32" // f32 contains float32 points. 9 | "gioui.org/op" // op is used for recording different operations. 10 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 11 | "gioui.org/op/paint" // paint contains operations for coloring. 12 | 13 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 14 | ) 15 | 16 | func main() { 17 | qapp.Render(func(ops *op.Ops) { 18 | var p clip.Path 19 | p.Begin(ops) 20 | p.MoveTo(f32.Pt(30, 30)) 21 | p.LineTo(f32.Pt(170, 170)) 22 | p.LineTo(f32.Pt(80, 170)) 23 | // p.Close() 24 | 25 | // set the clip to the stroke of the path 26 | defer clip.Stroke{ 27 | Path: p.End(), 28 | Width: 20, 29 | // package gioui.org/x/stroke provides additional styling options for a line. 30 | }.Op().Push(ops).Pop() 31 | 32 | // color the clip area: 33 | red := color.NRGBA{R: 0xFF, A: 0xFF} 34 | paint.ColorOp{Color: red}.Add(ops) 35 | paint.PaintOp{}.Add(ops) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /1-drawing/4-offset/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "image/color" 8 | 9 | "gioui.org/op" // op is used for recording different operations. 10 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 11 | "gioui.org/op/paint" // paint contains operations for coloring. 12 | 13 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 14 | ) 15 | 16 | func main() { 17 | qapp.Render(func(ops *op.Ops) { 18 | defer op.Offset(image.Pt(100, 100)).Push(ops).Pop() 19 | defer clip.Rect{Max: image.Pt(100, 100)}.Push(ops).Pop() 20 | 21 | // color the clip area: 22 | red := color.NRGBA{R: 0xFF, A: 0xFF} 23 | paint.ColorOp{Color: red}.Add(ops) 24 | paint.PaintOp{}.Add(ops) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /1-drawing/5-save/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "image/color" 8 | 9 | "gioui.org/op" // op is used for recording different operations. 10 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 11 | "gioui.org/op/paint" // paint contains operations for coloring. 12 | 13 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 14 | ) 15 | 16 | func main() { 17 | qapp.Render(func(ops *op.Ops) { 18 | func() { 19 | area := clip.Rect{Max: image.Pt(100, 100)}.Push(ops) 20 | 21 | red := color.NRGBA{R: 0x80, A: 0xFF} 22 | paint.ColorOp{Color: red}.Add(ops) 23 | paint.PaintOp{}.Add(ops) 24 | 25 | area.Pop() 26 | }() 27 | 28 | func() { 29 | defer clip.Rect{Min: image.Pt(40, 50), Max: image.Pt(60, 200)}.Push(ops).Pop() 30 | 31 | green := color.NRGBA{G: 0xFF, A: 0xFF} 32 | paint.ColorOp{Color: green}.Add(ops) 33 | paint.PaintOp{}.Add(ops) 34 | }() 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /1-drawing/6-image/gamer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golangestonia/learn-gio/26d662ca5c8aff8515c446aca2e7d63ad4a33c30/1-drawing/6-image/gamer.png -------------------------------------------------------------------------------- /1-drawing/6-image/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | _ "embed" 8 | "image/png" 9 | 10 | "gioui.org/op" // op is used for recording different operations. 11 | "gioui.org/op/paint" // paint contains operations for coloring. 12 | 13 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 14 | ) 15 | 16 | //go:embed gamer.png 17 | var imageData []byte 18 | 19 | var imageOp = func() paint.ImageOp { 20 | m, err := png.Decode(bytes.NewReader(imageData)) 21 | if err != nil { 22 | panic(err) 23 | } 24 | return paint.NewImageOp(m) 25 | }() 26 | 27 | func main() { 28 | qapp.Render(func(ops *op.Ops) { 29 | imageOp.Add(ops) 30 | paint.PaintOp{}.Add(ops) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /1-drawing/7-animation/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | 8 | "gioui.org/f32" // f32 contains float32 points. 9 | "gioui.org/op" // op is used for recording different operations. 10 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 11 | "gioui.org/op/paint" // paint contains operations for coloring. 12 | 13 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 14 | "github.com/golangestonia/learn-gio/qasset" // qasset contains convenience assets for this tutorial 15 | ) 16 | 17 | var imageOp = paint.NewImageOp(qasset.Neutral) 18 | 19 | var position float32 20 | 21 | func main() { 22 | qapp.Render(func(ops *op.Ops) { 23 | position += 0.5 24 | // Note, if we use op.Offset, the image will be pixel-snapped. 25 | // The behavior we want, depends on the context. 26 | defer op.Affine(f32.Affine2D{}.Offset(f32.Pt(position, 0))).Push(ops).Pop() 27 | 28 | // the render needs to be called immediately again 29 | op.InvalidateOp{}.Add(ops) 30 | 31 | defer clip.Rect{Max: image.Pt(200, 200)}.Push(ops).Pop() 32 | imageOp.Add(ops) 33 | paint.PaintOp{}.Add(ops) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /2-interaction/0-key/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "strings" 7 | 8 | "gioui.org/f32" // f32 contains float32 points. 9 | "gioui.org/io/event" // event contains general event information. 10 | "gioui.org/io/key" // key contains input/output for keyboards. 11 | "gioui.org/op" // op is used for recording different operations. 12 | "gioui.org/op/paint" // paint contains operations for coloring. 13 | 14 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 15 | "github.com/golangestonia/learn-gio/qasset" // qasset contains convenience assets for this tutorial 16 | ) 17 | 18 | var imageOp = paint.NewImageOp(qasset.Neutral) 19 | 20 | const speed = 50 21 | 22 | var location = f32.Pt(300, 300) 23 | var arrowKeys = key.Set(strings.Join([]string{key.NameLeftArrow, key.NameUpArrow, key.NameRightArrow, key.NameDownArrow}, "|")) 24 | 25 | func main() { 26 | qapp.Input(func(ops *op.Ops, queue event.Queue) { 27 | // keep the focus, since only one thing can 28 | key.FocusOp{Tag: &location}.Add(ops) 29 | // register tag &location as reading input 30 | key.InputOp{ 31 | Tag: &location, 32 | Keys: arrowKeys, 33 | }.Add(ops) 34 | 35 | // read events from input event queue 36 | for _, ev := range queue.Events(&location) { 37 | // figure out, which event it was 38 | switch ev := ev.(type) { 39 | case key.Event: 40 | if ev.State == key.Press { 41 | switch ev.Name { 42 | case key.NameLeftArrow: 43 | location.X -= speed 44 | case key.NameUpArrow: 45 | location.Y -= speed 46 | case key.NameRightArrow: 47 | location.X += speed 48 | case key.NameDownArrow: 49 | location.Y += speed 50 | } 51 | } 52 | } 53 | } 54 | op.Affine(f32.Affine2D{}.Offset(location)).Add(ops) 55 | imageOp.Add(ops) 56 | paint.PaintOp{}.Add(ops) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /2-interaction/1-pointer/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | 8 | "gioui.org/f32" // f32 contains float32 points. 9 | "gioui.org/io/event" // event contains general event information. 10 | "gioui.org/io/pointer" // pointer contains input/output for mouse and touch screens. 11 | "gioui.org/op" // op is used for recording different operations. 12 | "gioui.org/op/clip" 13 | "gioui.org/op/paint" // paint contains operations for coloring. 14 | 15 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 16 | "github.com/golangestonia/learn-gio/qasset" // qasset contains convenience assets for this tutorial 17 | ) 18 | 19 | var imageOp = paint.NewImageOp(qasset.Neutral) 20 | 21 | var location = f32.Pt(300, 300) 22 | var targetLocation = location 23 | 24 | func main() { 25 | qapp.InputSize(func(ops *op.Ops, queue event.Queue, windowSize image.Point) { 26 | // register area for input events 27 | defer clip.Rect(image.Rectangle{Max: windowSize}).Push(ops).Pop() 28 | 29 | // register the area for pointer events 30 | pointer.InputOp{ 31 | Tag: &location, 32 | Types: pointer.Press, 33 | }.Add(ops) 34 | 35 | // read events from input event queue 36 | for _, ev := range queue.Events(&location) { 37 | // figure out, which event it was 38 | switch ev := ev.(type) { 39 | case pointer.Event: 40 | if ev.Type == pointer.Press { 41 | targetLocation = ev.Position 42 | } 43 | } 44 | } 45 | 46 | // move slightly towards the target location 47 | if delta := targetLocation.Sub(location); abs(delta.X) > 1 || abs(delta.Y) > 1 { 48 | delta.X *= 0.1 49 | delta.Y *= 0.1 50 | location = location.Add(delta) 51 | 52 | // ensure we animate this 53 | op.InvalidateOp{}.Add(ops) 54 | } 55 | 56 | defer op.Affine(f32.Affine2D{}.Offset(location)).Push(ops).Pop() 57 | 58 | imageSize := imageOp.Size().Div(-2) 59 | defer op.Offset(imageSize).Push(ops).Pop() 60 | 61 | imageOp.Add(ops) 62 | paint.PaintOp{}.Add(ops) 63 | }) 64 | } 65 | 66 | func abs(s float32) float32 { 67 | if s < 0 { 68 | return -s 69 | } 70 | return s 71 | } 72 | -------------------------------------------------------------------------------- /2-interaction/2-drag/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "gioui.org/f32" // f32 contains float32 points. 7 | "gioui.org/gesture" // gesture contains different gesture events 8 | "gioui.org/io/event" // event contains general event information. 9 | "gioui.org/io/pointer" // pointer contains input/output for mouse and touch screens. 10 | "gioui.org/op" // op is used for recording different operations. 11 | "gioui.org/op/clip" 12 | "gioui.org/op/paint" // paint contains operations for coloring. 13 | "gioui.org/unit" // unit contains metric conversion 14 | 15 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 16 | "github.com/golangestonia/learn-gio/qasset" // qasset contains convenience assets for this tutorial 17 | ) 18 | 19 | var imageOp = paint.NewImageOp(qasset.Neutral) 20 | 21 | var location = f32.Pt(300, 300) 22 | var drag gesture.Drag 23 | 24 | func main() { 25 | qapp.Metric(func(ops *op.Ops, queue event.Queue, metric unit.Metric) { 26 | // handle drag events 27 | var dragOffset f32.Point 28 | for _, ev := range drag.Events(metric, queue, gesture.Both) { 29 | if ev.Type == pointer.Drag { 30 | dragOffset = ev.Position 31 | } 32 | } 33 | location = location.Add(dragOffset) 34 | 35 | // update the offset, must be after drag.Events 36 | defer op.Affine(f32.Affine2D{}.Offset(location)).Push(ops).Pop() 37 | 38 | // register image area for input events 39 | defer clip.Rect{Max: imageOp.Size()}.Push(ops).Pop() 40 | drag.Add(ops) 41 | 42 | // draw the image 43 | imageOp.Add(ops) 44 | paint.PaintOp{}.Add(ops) 45 | }) 46 | } 47 | 48 | func abs(s float32) float32 { 49 | if s < 0 { 50 | return -s 51 | } 52 | return s 53 | } 54 | -------------------------------------------------------------------------------- /2-interaction/3-click/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "image/color" 8 | "log" 9 | 10 | "gioui.org/gesture" // gesture contains different gesture event handling. 11 | "gioui.org/io/event" // event contains general event information. 12 | "gioui.org/op" // op is used for recording different operations. 13 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 14 | "gioui.org/op/paint" // paint contains operations for coloring. 15 | 16 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 17 | ) 18 | 19 | var buttonClick gesture.Click 20 | var buttonArea = image.Rect(50, 50, 150, 150) 21 | var buttonColor = color.NRGBA{R: 0x40, G: 0x40, B: 0x40, A: 0xFF} 22 | 23 | func main() { 24 | qapp.Input(func(ops *op.Ops, queue event.Queue) { 25 | // set the area where we want to listen to clicks 26 | defer clip.Rect(buttonArea).Push(ops).Pop() 27 | // register click gesture 28 | buttonClick.Add(ops) 29 | 30 | // calculate the color of a rectangle based on the click status 31 | targetColor := color.NRGBA{R: 0x40, G: 0x40, B: 0x40, A: 0xFF} 32 | if buttonClick.Hovered() { 33 | targetColor = color.NRGBA{R: 0x80, G: 0x80, B: 0xA0, A: 0xFF} 34 | } 35 | if buttonClick.Pressed() { 36 | targetColor = color.NRGBA{R: 0x80, G: 0xA0, B: 0x80, A: 0xFF} 37 | } 38 | 39 | // animate the color change 40 | if buttonColor != targetColor { 41 | buttonColor = transition(buttonColor, targetColor) 42 | op.InvalidateOp{}.Add(ops) 43 | } 44 | 45 | // see whether we had a click event 46 | for _, ev := range buttonClick.Events(queue) { 47 | switch ev.Type { 48 | case gesture.TypeClick: 49 | buttonColor = color.NRGBA{R: 0x80, G: 0xFF, B: 0x80, A: 0xFF} 50 | log.Println("clicked") 51 | } 52 | } 53 | 54 | // draw the button 55 | clip.Rect(buttonArea).Push(ops).Pop() 56 | paint.ColorOp{Color: buttonColor}.Add(ops) 57 | paint.PaintOp{}.Add(ops) 58 | }) 59 | } 60 | 61 | func transition(from, to color.NRGBA) color.NRGBA { 62 | return color.NRGBA{ 63 | R: transitionByte(from.R, to.R), 64 | G: transitionByte(from.G, to.G), 65 | B: transitionByte(from.B, to.B), 66 | A: transitionByte(from.A, to.A), 67 | } 68 | } 69 | 70 | func transitionByte(a, b byte) byte { 71 | const speed = 2 72 | delta := int(b) - int(a) 73 | if delta < -speed { 74 | delta = -speed 75 | } else if delta > speed { 76 | delta = speed 77 | } 78 | return byte(int(a) + delta) 79 | } 80 | -------------------------------------------------------------------------------- /2-interaction/4-button/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "image/color" 8 | "log" 9 | 10 | "gioui.org/gesture" // gesture contains different gesture event handling. 11 | "gioui.org/io/event" // event contains general event information. 12 | "gioui.org/op" // op is used for recording different operations. 13 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 14 | "gioui.org/op/paint" // paint contains operations for coloring. 15 | 16 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 17 | ) 18 | 19 | var button = Button{ 20 | Area: image.Rect(50, 50, 150, 150), 21 | } 22 | 23 | func main() { 24 | qapp.Input(func(ops *op.Ops, queue event.Queue) { 25 | if button.Do(ops, queue) { 26 | log.Println("clicked") 27 | } 28 | }) 29 | } 30 | 31 | type Button struct { 32 | Area image.Rectangle 33 | click gesture.Click 34 | color color.NRGBA 35 | } 36 | 37 | func (button *Button) Do(ops *op.Ops, queue event.Queue) (clicked bool) { 38 | if button.color == (color.NRGBA{}) { 39 | button.color = color.NRGBA{R: 0x40, G: 0x40, B: 0x40, A: 0xFF} 40 | } 41 | 42 | // set the area where we want to listen to clicks 43 | defer clip.Rect(button.Area).Push(ops).Pop() 44 | // register click gesture 45 | button.click.Add(ops) 46 | 47 | // calculate the color of a rectangle based on the click status 48 | targetColor := color.NRGBA{R: 0x40, G: 0x40, B: 0x40, A: 0xFF} 49 | if button.click.Hovered() { 50 | targetColor = color.NRGBA{R: 0x80, G: 0x80, B: 0xA0, A: 0xFF} 51 | } 52 | if button.click.Pressed() { 53 | targetColor = color.NRGBA{R: 0x80, G: 0xA0, B: 0x80, A: 0xFF} 54 | } 55 | 56 | // animate the color change 57 | if button.color != targetColor { 58 | button.color = transition(button.color, targetColor) 59 | op.InvalidateOp{}.Add(ops) 60 | } 61 | 62 | // see whether we had a click event 63 | for _, ev := range button.click.Events(queue) { 64 | switch ev.Type { 65 | case gesture.TypeClick: 66 | button.color = color.NRGBA{R: 0x80, G: 0xFF, B: 0x80, A: 0xFF} 67 | clicked = true 68 | } 69 | } 70 | 71 | // draw the button 72 | defer clip.Rect(button.Area).Push(ops).Pop() 73 | paint.ColorOp{Color: button.color}.Add(ops) 74 | paint.PaintOp{}.Add(ops) 75 | 76 | return clicked 77 | } 78 | 79 | func transition(from, to color.NRGBA) color.NRGBA { 80 | return color.NRGBA{ 81 | R: transitionByte(from.R, to.R), 82 | G: transitionByte(from.G, to.G), 83 | B: transitionByte(from.B, to.B), 84 | A: transitionByte(from.A, to.A), 85 | } 86 | } 87 | 88 | func transitionByte(a, b byte) byte { 89 | const speed = 2 90 | delta := int(b) - int(a) 91 | if delta < -speed { 92 | delta = -speed 93 | } else if delta > speed { 94 | delta = speed 95 | } 96 | return byte(int(a) + delta) 97 | } 98 | -------------------------------------------------------------------------------- /3-widget/0-context/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "image/color" 8 | "log" 9 | 10 | "gioui.org/gesture" // gesture contains different gesture event handling. 11 | "gioui.org/layout" // layout is used for layouting widgets. 12 | "gioui.org/op" // op is used for recording different operations. 13 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 14 | "gioui.org/op/paint" // paint contains operations for coloring. 15 | 16 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 17 | ) 18 | 19 | /* 20 | // Context carries the state needed by almost all layouts and widgets. 21 | type Context struct { 22 | // Constraints track the constraints for the active widget or layout. 23 | Constraints Constraints 24 | 25 | Metric unit.Metric 26 | // By convention, a nil Queue is a signal to widgets to draw themselves 27 | // in a disabled state. 28 | Queue event.Queue 29 | // Now is the animation time. 30 | Now time.Time 31 | 32 | *op.Ops 33 | } 34 | */ 35 | 36 | var button = Button{ 37 | Area: image.Rect(50, 50, 150, 150), 38 | } 39 | 40 | func main() { 41 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 42 | if button.Layout(gtx) { 43 | log.Println("clicked") 44 | } 45 | 46 | return layout.Dimensions{} 47 | }) 48 | } 49 | 50 | type Button struct { 51 | Area image.Rectangle 52 | click gesture.Click 53 | color color.NRGBA 54 | } 55 | 56 | // Layout is a way to implement a button. 57 | func (button *Button) Layout(gtx layout.Context) (clicked bool) { 58 | if button.color == (color.NRGBA{}) { 59 | button.color = color.NRGBA{R: 0x40, G: 0x40, B: 0x40, A: 0xFF} 60 | } 61 | 62 | // set the area where we want to listen to clicks 63 | defer clip.Rect(button.Area).Push(gtx.Ops).Pop() 64 | // register click gesture 65 | button.click.Add(gtx.Ops) 66 | 67 | // calculate the color of a rectangle based on the click status 68 | targetColor := color.NRGBA{R: 0x40, G: 0x40, B: 0x40, A: 0xFF} 69 | if button.click.Hovered() { 70 | targetColor = color.NRGBA{R: 0x80, G: 0x80, B: 0xA0, A: 0xFF} 71 | } 72 | if button.click.Pressed() { 73 | targetColor = color.NRGBA{R: 0x80, G: 0xA0, B: 0x80, A: 0xFF} 74 | } 75 | 76 | // animate the color change 77 | if button.color != targetColor { 78 | // TODO: this should use gtx.Now for color changes 79 | button.color = transition(button.color, targetColor) 80 | op.InvalidateOp{}.Add(gtx.Ops) 81 | } 82 | 83 | // see whether we had a click event 84 | for _, ev := range button.click.Events(gtx.Queue) { 85 | switch ev.Type { 86 | case gesture.TypeClick: 87 | button.color = color.NRGBA{R: 0x80, G: 0xFF, B: 0x80, A: 0xFF} 88 | clicked = true 89 | } 90 | } 91 | 92 | // draw the button 93 | defer clip.Rect(button.Area).Push(gtx.Ops).Pop() 94 | paint.ColorOp{Color: button.color}.Add(gtx.Ops) 95 | paint.PaintOp{}.Add(gtx.Ops) 96 | 97 | return clicked 98 | } 99 | 100 | func transition(from, to color.NRGBA) color.NRGBA { 101 | return color.NRGBA{ 102 | R: transitionByte(from.R, to.R), 103 | G: transitionByte(from.G, to.G), 104 | B: transitionByte(from.B, to.B), 105 | A: transitionByte(from.A, to.A), 106 | } 107 | } 108 | 109 | func transitionByte(a, b byte) byte { 110 | const speed = 2 111 | delta := int(b) - int(a) 112 | if delta < -speed { 113 | delta = -speed 114 | } else if delta > speed { 115 | delta = speed 116 | } 117 | return byte(int(a) + delta) 118 | } 119 | -------------------------------------------------------------------------------- /3-widget/1-theme/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "gioui.org/font/gofont" // gofont is used for loading the default font. 7 | "gioui.org/layout" // layout is used for layouting widgets. 8 | "gioui.org/op/paint" // paint contains operations for coloring. 9 | "gioui.org/widget/material" // material contains material design widgets. 10 | 11 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 12 | ) 13 | 14 | var Theme = func() *material.Theme { 15 | theme := material.NewTheme(gofont.Collection()) 16 | theme.Palette = Invert(theme.Palette) 17 | return theme 18 | }() 19 | 20 | func Invert(pal material.Palette) material.Palette { 21 | pal.Fg, pal.Bg = pal.Bg, pal.Fg 22 | pal.ContrastFg, pal.ContrastBg = pal.ContrastBg, pal.ContrastFg 23 | return pal 24 | } 25 | 26 | func main() { 27 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 28 | paint.ColorOp{Color: Theme.Bg}.Add(gtx.Ops) 29 | paint.PaintOp{}.Add(gtx.Ops) 30 | 31 | return material.H1(Theme, "Hello, Quick!").Layout(gtx) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /3-widget/2-button/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | 8 | "gioui.org/font/gofont" // gofont is used for loading the default font. 9 | "gioui.org/layout" // layout is used for layouting widgets. 10 | "gioui.org/widget" // widget contains state for different widgets 11 | "gioui.org/widget/material" // material contains material design widgets. 12 | 13 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 14 | ) 15 | 16 | var Theme = material.NewTheme(gofont.Collection()) 17 | 18 | var click widget.Clickable 19 | 20 | func main() { 21 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 22 | for click.Clicked() { 23 | log.Println("Click") 24 | } 25 | return material.Button(Theme, &click, "Click").Layout(gtx) 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /3-widget/3-label/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "gioui.org/font/gofont" // gofont is used for loading the default font. 7 | "gioui.org/layout" // layout is used for layouting widgets. 8 | "gioui.org/widget/material" // material contains material design widgets. 9 | 10 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 11 | ) 12 | 13 | var Theme = material.NewTheme(gofont.Collection()) 14 | 15 | func main() { 16 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 17 | title := material.H1(Theme, "Hello!") 18 | 19 | return title.Layout(gtx) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /3-widget/4-editor/editor.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "gioui.org/font/gofont" // gofont is used for loading the default font. 7 | "gioui.org/layout" // layout is used for layouting widgets. 8 | "gioui.org/widget" // widget contains state for different widgets 9 | "gioui.org/widget/material" // material contains material design widgets. 10 | 11 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 12 | ) 13 | 14 | var Theme = material.NewTheme(gofont.Collection()) 15 | 16 | var editor widget.Editor 17 | 18 | func main() { 19 | editor.SetText("hello world") 20 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 21 | return material.Editor(Theme, &editor, "").Layout(gtx) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /3-widget/5-slider/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "gioui.org/font/gofont" // gofont is used for loading the default font. 7 | "gioui.org/layout" // layout is used for layouting widgets. 8 | "gioui.org/widget" // widget contains state for different widgets 9 | "gioui.org/widget/material" // material contains material design widgets. 10 | 11 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 12 | ) 13 | 14 | var Theme = material.NewTheme(gofont.Collection()) 15 | 16 | var value widget.Float 17 | 18 | func main() { 19 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 20 | return material.Slider(Theme, &value, 0, 10).Layout(gtx) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /3-widget/6-image/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "gioui.org/font/gofont" // gofont is used for loading the default font. 7 | "gioui.org/layout" // layout is used for layouting widgets. 8 | "gioui.org/op/paint" 9 | "gioui.org/widget" // widget contains state for different widgets 10 | "gioui.org/widget/material" // material contains material design widgets. 11 | 12 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 13 | "github.com/golangestonia/learn-gio/qasset" 14 | ) 15 | 16 | var Theme = material.NewTheme(gofont.Collection()) 17 | 18 | var imageOp = paint.NewImageOp(qasset.Neutral) 19 | 20 | func main() { 21 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 22 | return widget.Image{ 23 | Src: imageOp, 24 | Fit: widget.Cover, 25 | Position: layout.Center, 26 | }.Layout(gtx) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /4-layout/0-direction/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "gioui.org/font/gofont" // gofont is used for loading the default font. 7 | "gioui.org/layout" // layout is used for layouting widgets. 8 | "gioui.org/widget/material" // material contains material design widgets. 9 | 10 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 11 | ) 12 | 13 | var Theme = material.NewTheme(gofont.Collection()) 14 | 15 | func main() { 16 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 17 | title := material.H1(Theme, "Hello!") 18 | return layout.Center.Layout(gtx, title.Layout) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /4-layout/1-inset/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | 8 | "gioui.org/font/gofont" // gofont is used for loading the default font. 9 | "gioui.org/layout" // layout is used for layouting widgets. 10 | "gioui.org/unit" // unit contains metric conversion 11 | "gioui.org/widget" // widget contains state for different widgets 12 | "gioui.org/widget/material" // material contains material design widgets. 13 | 14 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 15 | ) 16 | 17 | var Theme = material.NewTheme(gofont.Collection()) 18 | 19 | var click widget.Clickable 20 | 21 | func main() { 22 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 23 | for click.Clicked() { 24 | log.Println("Click") 25 | } 26 | return layout.UniformInset(unit.Dp(32)).Layout(gtx, 27 | material.Button(Theme, &click, "Click").Layout, 28 | ) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /4-layout/2-stack/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "gioui.org/font/gofont" // gofont is used for loading the default font. 7 | "gioui.org/layout" // layout is used for layouting widgets. 8 | "gioui.org/op/clip" // clip contains operations for clipping painting area. 9 | "gioui.org/op/paint" // paint contains operations for coloring. 10 | "gioui.org/widget/material" // material contains material design widgets. 11 | 12 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 13 | ) 14 | 15 | var Theme = material.NewTheme(gofont.Collection()) 16 | 17 | func main() { 18 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 19 | return layout.Stack{ 20 | Alignment: layout.Center, 21 | }.Layout(gtx, 22 | layout.Expanded(func(gtx layout.Context) layout.Dimensions { 23 | size := gtx.Constraints.Max 24 | smallest := min(size.X, size.Y) 25 | 26 | size.X, size.Y = smallest, smallest 27 | shape := clip.Ellipse{Max: size} 28 | paint.FillShape(gtx.Ops, Theme.ContrastBg, shape.Op(gtx.Ops)) 29 | 30 | return layout.Dimensions{ 31 | Size: size, 32 | } 33 | }), 34 | layout.Stacked(func(gtx layout.Context) layout.Dimensions { 35 | size := gtx.Constraints.Max 36 | textSize := min(size.X, size.Y) * 3 / 4 37 | if textSize > 1023 { 38 | // There's a limit how large we can make the font. 39 | textSize = 1023 40 | } 41 | 42 | label := material.H1(Theme, "P") 43 | label.Color = Theme.ContrastFg 44 | label.TextSize = gtx.Metric.PxToSp(textSize) 45 | 46 | return label.Layout(gtx) 47 | }), 48 | ) 49 | }) 50 | } 51 | 52 | func min(x, y int) int { 53 | if x < y { 54 | return x 55 | } 56 | return y 57 | } 58 | -------------------------------------------------------------------------------- /4-layout/3-flex/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "image/color" 8 | 9 | "gioui.org/font/gofont" // gofont is used for loading the default font. 10 | "gioui.org/layout" // layout is used for layouting widgets. 11 | "gioui.org/text" // text contains constants for text layouting. 12 | "gioui.org/widget" // widget contains state for different widgets 13 | "gioui.org/widget/material" // material contains material design widgets. 14 | 15 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 16 | ) 17 | 18 | var Theme = material.NewTheme(gofont.Collection()) 19 | 20 | var editor widget.Editor 21 | 22 | var inset = layout.UniformInset(8) 23 | var border = widget.Border{ 24 | Color: color.NRGBA{R: 0x88, G: 0x88, B: 0x88, A: 0xFF}, 25 | Width: 1, 26 | } 27 | 28 | func main() { 29 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 30 | return inset.Layout(gtx, 31 | func(gtx layout.Context) layout.Dimensions { 32 | return layout.Flex{ 33 | Axis: layout.Vertical, 34 | Alignment: layout.Middle, 35 | }.Layout(gtx, 36 | layout.Rigid(Center(material.H2(Theme, "Header")).Layout), 37 | layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { 38 | return border.Layout(gtx, func(gtx layout.Context) layout.Dimensions { 39 | return inset.Layout(gtx, material.Editor(Theme, &editor, "").Layout) 40 | }) 41 | }), 42 | layout.Rigid(func(gtx layout.Context) layout.Dimensions { 43 | line, col := editor.CaretPos() 44 | s := fmt.Sprintf("line:%d col:%d", line, col) 45 | return Center(material.Body1(Theme, s)).Layout(gtx) 46 | }), 47 | ) 48 | }) 49 | }) 50 | } 51 | 52 | func Center(label material.LabelStyle) material.LabelStyle { 53 | label.Alignment = text.Middle 54 | return label 55 | } 56 | -------------------------------------------------------------------------------- /4-layout/4-list/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | 8 | "gioui.org/font/gofont" // gofont is used for loading the default font. 9 | "gioui.org/layout" // layout is used for layouting widgets. 10 | "gioui.org/widget/material" // material contains material design widgets. 11 | 12 | "github.com/golangestonia/learn-gio/qapp" // qapp contains convenience funcs for this tutorial 13 | ) 14 | 15 | var Theme = material.NewTheme(gofont.Collection()) 16 | 17 | var list = layout.List{ 18 | Axis: layout.Vertical, 19 | } 20 | 21 | var items = createItems(1000) 22 | 23 | func main() { 24 | qapp.Layout(func(gtx layout.Context) layout.Dimensions { 25 | return list.Layout(gtx, len(items), 26 | func(gtx layout.Context, index int) layout.Dimensions { 27 | return material.Body1(Theme, items[index]).Layout(gtx) 28 | }) 29 | }) 30 | } 31 | 32 | func createItems(n int) []string { 33 | xs := []string{} 34 | for i := 0; i < n; i++ { 35 | xs = append(xs, fmt.Sprintf("%08x", i)) 36 | } 37 | return xs 38 | } 39 | -------------------------------------------------------------------------------- /5-async/0-select/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | "os" 8 | "strconv" 9 | "time" 10 | 11 | "gioui.org/app" // app contains Window handling. 12 | "gioui.org/font/gofont" // gofont is used for loading the default font. 13 | "gioui.org/io/system" // system is used for system events (e.g. closing the window). 14 | "gioui.org/layout" // layout is used for layouting widgets. 15 | "gioui.org/op" // op is used for recording different operations. 16 | "gioui.org/widget/material" // material contains material design widgets. 17 | ) 18 | 19 | func main() { 20 | go func() { 21 | w := app.NewWindow() 22 | if err := loop(w); err != nil { 23 | log.Println(err) 24 | os.Exit(1) 25 | } 26 | os.Exit(0) 27 | }() 28 | app.Main() 29 | } 30 | 31 | func loop(w *app.Window) error { 32 | // th contains constants for theming. 33 | th := material.NewTheme(gofont.Collection()) 34 | // ops will be used to encode different operations. 35 | var ops op.Ops 36 | 37 | counter := 0 38 | ticker := time.NewTicker(time.Second) 39 | 40 | // listen for events happening on the window. 41 | for { 42 | select { 43 | case <-ticker.C: 44 | counter++ 45 | w.Invalidate() 46 | 47 | case e := <-w.Events(): 48 | // detect the type of the event. 49 | switch e := e.(type) { 50 | // this is sent when the application should re-render. 51 | case system.FrameEvent: 52 | // gtx is used to pass around rendering and event information. 53 | gtx := layout.NewContext(&ops, e) 54 | 55 | layout.Center.Layout(gtx, 56 | material.H1(th, strconv.Itoa(counter)).Layout, 57 | ) 58 | 59 | // render and handle the operations from the UI. 60 | e.Frame(gtx.Ops) 61 | 62 | // this is sent when the application is closed. 63 | case system.DestroyEvent: 64 | return e.Err 65 | } 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /6-case-study/0-counter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strconv" 7 | 8 | "gioui.org/app" // app contains Window handling. 9 | "gioui.org/font/gofont" // gofont is used for loading the default font. 10 | "gioui.org/io/key" // key is used for keyboard events. 11 | "gioui.org/io/system" // system is used for system events (e.g. closing the window). 12 | "gioui.org/layout" // layout is used for layouting widgets. 13 | "gioui.org/op" // op is used for recording different operations. 14 | "gioui.org/unit" // unit is used to define pixel-independent sizes 15 | "gioui.org/widget" // widget contains state handling for widgets. 16 | "gioui.org/widget/material" // material contains material design widgets. 17 | ) 18 | 19 | func main() { 20 | // The ui loop is separated from the application window creation 21 | // such that it can be used for testing. 22 | ui := NewUI() 23 | 24 | // This creates a new application window and starts the UI. 25 | go func() { 26 | w := app.NewWindow( 27 | app.Title("Counter"), 28 | app.Size(unit.Dp(240), unit.Dp(70)), 29 | ) 30 | if err := ui.Run(w); err != nil { 31 | log.Println(err) 32 | os.Exit(1) 33 | } 34 | os.Exit(0) 35 | }() 36 | 37 | // This starts Gio main. 38 | app.Main() 39 | } 40 | 41 | // defaultMargin is a margin applied in multiple places to give 42 | // widgets room to breathe. 43 | var defaultMargin = unit.Dp(10) 44 | 45 | // UI holds all of the application state. 46 | type UI struct { 47 | // Theme is used to hold the fonts used throughout the application. 48 | Theme *material.Theme 49 | 50 | // Counter displays and keeps the state of the counter. 51 | Counter Counter 52 | } 53 | 54 | // NewUI creates a new UI using the Go Fonts. 55 | func NewUI() *UI { 56 | ui := &UI{} 57 | ui.Theme = material.NewTheme(gofont.Collection()) 58 | return ui 59 | } 60 | 61 | // Run handles window events and renders the application. 62 | func (ui *UI) Run(w *app.Window) error { 63 | var ops op.Ops 64 | 65 | // listen for events happening on the window. 66 | for e := range w.Events() { 67 | // detect the type of the event. 68 | switch e := e.(type) { 69 | // this is sent when the application should re-render. 70 | case system.FrameEvent: 71 | // gtx is used to pass around rendering and event information. 72 | gtx := layout.NewContext(&ops, e) 73 | // render and handle UI. 74 | ui.Layout(gtx) 75 | // render and handle the operations from the UI. 76 | e.Frame(gtx.Ops) 77 | 78 | // handle a global key press. 79 | case key.Event: 80 | switch e.Name { 81 | // when we click escape, let's close the window. 82 | case key.NameEscape: 83 | return nil 84 | } 85 | 86 | // this is sent when the application is closed. 87 | case system.DestroyEvent: 88 | return e.Err 89 | } 90 | } 91 | 92 | return nil 93 | } 94 | 95 | // Layout displays the main program layout. 96 | func (ui *UI) Layout(gtx layout.Context) layout.Dimensions { 97 | // inset is used to add padding around the window border. 98 | inset := layout.UniformInset(defaultMargin) 99 | return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { 100 | return ui.Counter.Layout(ui.Theme, gtx) 101 | }) 102 | } 103 | 104 | // Counter is a component that keeps track of it's state and 105 | // displays itself as a label and a button. 106 | type Counter struct { 107 | // Count is the current value. 108 | Count int 109 | 110 | // increase is used to track button clicks. 111 | increase widget.Clickable 112 | } 113 | 114 | // Layout lays out the counter and handles input. 115 | func (counter *Counter) Layout(th *material.Theme, gtx layout.Context) layout.Dimensions { 116 | // Flex layout lays out widgets from left to right by default. 117 | return layout.Flex{}.Layout(gtx, 118 | // We use weight 1 for both text and count to make them the same size. 119 | layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { 120 | // We center align the text to the area available. 121 | return layout.Center.Layout(gtx, 122 | // Body1 is the default text size for reading. 123 | material.Body1(th, strconv.Itoa(counter.Count)).Layout) 124 | }), 125 | // We use an empty widget to add spacing between the text 126 | // and the button. 127 | layout.Rigid(layout.Spacer{Height: defaultMargin}.Layout), 128 | layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { 129 | // For every click on the button increment the count. 130 | for range counter.increase.Clicks() { 131 | counter.Count++ 132 | } 133 | // Finally display the button. 134 | return material.Button(th, &counter.increase, "Count").Layout(gtx) 135 | }), 136 | ) 137 | } 138 | -------------------------------------------------------------------------------- /6-case-study/1-life/board.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "math/rand" 8 | ) 9 | 10 | // Board implements game of life logic. 11 | type Board struct { 12 | // Size is the count of cells in a particular dimension. 13 | Size image.Point 14 | // Cells contains the alive or dead cells. 15 | Cells []byte 16 | 17 | // buffer is used to avoid reallocating a new cells 18 | // slice for every update. 19 | buffer []byte 20 | } 21 | 22 | // NewBoard returns a new game of life with the defined size. 23 | func NewBoard(size image.Point) *Board { 24 | return &Board{ 25 | Size: size, 26 | Cells: make([]byte, size.X*size.Y), 27 | buffer: make([]byte, size.X*size.Y), 28 | } 29 | } 30 | 31 | // Randomize randomizes each cell state. 32 | func (b *Board) Randomize() { 33 | rand.Read(b.Cells) 34 | for i, v := range b.Cells { 35 | if v < 0x30 { 36 | b.Cells[i] = 1 37 | } else { 38 | b.Cells[i] = 0 39 | } 40 | } 41 | } 42 | 43 | // Pt returns the coordinate given a index in b.Cells. 44 | func (b *Board) Pt(i int) image.Point { 45 | x, y := i%b.Size.X, i/b.Size.Y 46 | return image.Point{X: x, Y: y} 47 | } 48 | 49 | // At returns the b.Cells index, given a wrapped coordinate. 50 | func (b *Board) At(c image.Point) int { 51 | if c.X < 0 { 52 | c.X += b.Size.X 53 | } 54 | if c.X >= b.Size.X { 55 | c.X -= b.Size.X 56 | } 57 | if c.Y < 0 { 58 | c.Y += b.Size.Y 59 | } 60 | if c.Y >= b.Size.Y { 61 | c.Y -= b.Size.Y 62 | } 63 | return b.Size.Y*c.Y + c.X 64 | } 65 | 66 | // SetWithoutWrap sets a cell to alive. 67 | func (b *Board) SetWithoutWrap(c image.Point) { 68 | if !c.In(image.Rectangle{Max: b.Size}) { 69 | return 70 | } 71 | 72 | b.Cells[b.At(c)] = 1 73 | } 74 | 75 | // Advance advances the board state by 1. 76 | func (b *Board) Advance() { 77 | next, cur := b.buffer, b.Cells 78 | defer func() { b.Cells, b.buffer = next, cur }() 79 | 80 | for i := range next { 81 | next[i] = 0 82 | } 83 | 84 | for y := 0; y < b.Size.Y; y++ { 85 | for x := 0; x < b.Size.X; x++ { 86 | var t byte 87 | t += cur[b.At(image.Pt(x-1, y-1))] 88 | t += cur[b.At(image.Pt(x+0, y-1))] 89 | t += cur[b.At(image.Pt(x+1, y-1))] 90 | t += cur[b.At(image.Pt(x-1, y+0))] 91 | t += cur[b.At(image.Pt(x+1, y+0))] 92 | t += cur[b.At(image.Pt(x-1, y+1))] 93 | t += cur[b.At(image.Pt(x+0, y+1))] 94 | t += cur[b.At(image.Pt(x+1, y+1))] 95 | 96 | // Any live cell with fewer than two live neighbours dies, as if by underpopulation. 97 | // Any live cell with two or three live neighbours lives on to the next generation. 98 | // Any live cell with more than three live neighbours dies, as if by overpopulation. 99 | // Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. 100 | 101 | p := b.At(image.Pt(x, y)) 102 | switch { 103 | case t < 2: 104 | t = 0 105 | case t == 2: 106 | t = cur[p] 107 | case t == 3: 108 | t = 1 109 | case t > 3: 110 | t = 0 111 | } 112 | 113 | next[p] = t 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /6-case-study/1-life/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "log" 8 | "os" 9 | "time" 10 | 11 | "gioui.org/app" // app contains Window handling. 12 | "gioui.org/io/key" // key is used for keyboard events. 13 | "gioui.org/io/system" // system is used for system events (e.g. closing the window). 14 | "gioui.org/layout" // layout is used for layouting widgets. 15 | "gioui.org/op" // op is used for recording different operations. 16 | "gioui.org/unit" // unit is used to define pixel-independent sizes 17 | ) 18 | 19 | var ( 20 | // cellSizePx is the cell size in pixels. 21 | cellSize = unit.Dp(5) 22 | // boardSize is the count of cells in a particular dimension. 23 | boardSize = image.Pt(50, 50) 24 | ) 25 | 26 | func main() { 27 | // The ui loop is separated from the application window creation 28 | // such that it can be used for testing. 29 | ui := NewUI() 30 | 31 | windowWidth := cellSize * unit.Dp(boardSize.X+2) 32 | windowHeight := cellSize * unit.Dp(boardSize.Y+2) 33 | // This creates a new application window and starts the UI. 34 | go func() { 35 | w := app.NewWindow( 36 | app.Title("Game of Life"), 37 | app.Size(windowWidth, windowHeight), 38 | ) 39 | if err := ui.Run(w); err != nil { 40 | log.Println(err) 41 | os.Exit(1) 42 | } 43 | os.Exit(0) 44 | }() 45 | 46 | // This starts Gio main. 47 | app.Main() 48 | } 49 | 50 | // UI holds all of the application state. 51 | type UI struct { 52 | // Board handles all game-of-life logic. 53 | Board *Board 54 | } 55 | 56 | // NewUI creates a new UI using the Go Fonts. 57 | func NewUI() *UI { 58 | // We start with a new random board. 59 | board := NewBoard(boardSize) 60 | board.Randomize() 61 | 62 | return &UI{ 63 | Board: board, 64 | } 65 | } 66 | 67 | // Run handles window events and renders the application. 68 | func (ui *UI) Run(w *app.Window) error { 69 | var ops op.Ops 70 | 71 | // Update the board 3 times per second. 72 | advanceBoard := time.NewTicker(time.Second / 3) 73 | defer advanceBoard.Stop() 74 | 75 | // listen for events happening on the window. 76 | for { 77 | select { 78 | case e := <-w.Events(): 79 | // detect the type of the event. 80 | switch e := e.(type) { 81 | // this is sent when the application should re-render. 82 | case system.FrameEvent: 83 | // gtx is used to pass around rendering and event information. 84 | gtx := layout.NewContext(&ops, e) 85 | // render and handle UI. 86 | ui.Layout(gtx) 87 | // render and handle the operations from the UI. 88 | e.Frame(gtx.Ops) 89 | 90 | // handle a global key press. 91 | case key.Event: 92 | switch e.Name { 93 | // when we click escape, let's close the window. 94 | case key.NameEscape: 95 | return nil 96 | } 97 | 98 | // this is sent when the application is closed. 99 | case system.DestroyEvent: 100 | return e.Err 101 | } 102 | 103 | case <-advanceBoard.C: 104 | ui.Board.Advance() 105 | w.Invalidate() 106 | } 107 | } 108 | } 109 | 110 | // Layout displays the main program layout. 111 | func (ui *UI) Layout(gtx layout.Context) layout.Dimensions { 112 | return layout.Center.Layout(gtx, 113 | BoardStyle{ 114 | CellSize: cellSize, 115 | Board: ui.Board, 116 | }.Layout, 117 | ) 118 | } 119 | -------------------------------------------------------------------------------- /6-case-study/1-life/style.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "image/color" 8 | 9 | "gioui.org/f32" // f32 is used for shape calculations. 10 | "gioui.org/io/pointer" // system is used for system events (e.g. closing the window). 11 | "gioui.org/layout" // layout is used for layouting widgets. 12 | "gioui.org/op/clip" // clip is used to draw the cell shape. 13 | "gioui.org/op/paint" // paint is used to paint the cells. 14 | "gioui.org/unit" // unit is used to define pixel-independent sizes 15 | ) 16 | 17 | // BoardStyle draws Board with rectangles. 18 | type BoardStyle struct { 19 | CellSize unit.Dp 20 | *Board 21 | } 22 | 23 | // Layout draws the Board and accepts input for adding alive cells. 24 | func (board BoardStyle) Layout(gtx layout.Context) layout.Dimensions { 25 | cellSizePx := gtx.Dp(board.CellSize) 26 | 27 | // Calculate the board size based on the cell size in pixels. 28 | size := board.Size.Mul(cellSizePx) 29 | gtx.Constraints = layout.Exact(size) 30 | 31 | // Handle any input from a pointer. 32 | for _, ev := range gtx.Events(board.Board) { 33 | if ev, ok := ev.(pointer.Event); ok { 34 | p := image.Pt(int(ev.Position.X), int(ev.Position.Y)) 35 | // Calculate the board coordinate given a cursor position. 36 | p = p.Div(cellSizePx) 37 | board.SetWithoutWrap(p) 38 | } 39 | } 40 | // Register to listen for pointer Drag events. 41 | defer clip.Rect(image.Rectangle{Max: size}).Push(gtx.Ops).Pop() 42 | pointer.InputOp{Tag: board.Board, Types: pointer.Drag}.Add(gtx.Ops) 43 | 44 | cellSize := float32(cellSize) 45 | 46 | // Draw a shape for each alive cell. 47 | var p clip.Path 48 | p.Begin(gtx.Ops) 49 | for i, v := range board.Cells { 50 | if v == 0 { 51 | continue 52 | } 53 | 54 | c := layout.FPt(board.Pt(i).Mul(cellSizePx)) 55 | p.MoveTo(f32.Pt(c.X, c.Y)) 56 | p.LineTo(f32.Pt(c.X+cellSize, c.Y)) 57 | p.LineTo(f32.Pt(c.X+cellSize, c.Y+cellSize)) 58 | p.LineTo(f32.Pt(c.X, c.Y+cellSize)) 59 | p.Close() 60 | } 61 | defer clip.Outline{Path: p.End()}.Op().Push(gtx.Ops).Pop() 62 | 63 | // Paint the shape with a black color. 64 | paint.ColorOp{Color: color.NRGBA{A: 0xFF}}.Add(gtx.Ops) 65 | paint.PaintOp{}.Add(gtx.Ops) 66 | 67 | return layout.Dimensions{Size: size} 68 | } 69 | -------------------------------------------------------------------------------- /6-case-study/2-timer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | 8 | "gioui.org/app" // app contains Window handling. 9 | "gioui.org/font/gofont" // gofont is used for loading the default font. 10 | "gioui.org/io/key" // key is used for keyboard events. 11 | "gioui.org/io/system" // system is used for system events (e.g. closing the window). 12 | "gioui.org/layout" // layout is used for layouting widgets. 13 | "gioui.org/op" // op is used for recording different operations. 14 | "gioui.org/unit" // unit is used to define pixel-independent sizes 15 | "gioui.org/widget" // widget contains state handling for widgets. 16 | "gioui.org/widget/material" // material contains material design widgets. 17 | ) 18 | 19 | func main() { 20 | // The ui loop is separated from the application window creation 21 | // such that it can be used for testing. 22 | ui := NewUI() 23 | 24 | // This creates a new application window and starts the UI. 25 | go func() { 26 | w := app.NewWindow( 27 | app.Title("Timer"), 28 | app.Size(unit.Dp(360), unit.Dp(360)), 29 | ) 30 | if err := ui.Run(w); err != nil { 31 | log.Println(err) 32 | os.Exit(1) 33 | } 34 | os.Exit(0) 35 | }() 36 | 37 | // This starts Gio main. 38 | app.Main() 39 | } 40 | 41 | // defaultMargin is a margin applied in multiple places to give 42 | // widgets room to breathe. 43 | var defaultMargin = unit.Dp(10) 44 | 45 | // UI holds all of the application state. 46 | type UI struct { 47 | // Theme is used to hold the fonts used throughout the application. 48 | Theme *material.Theme 49 | 50 | Timer *Timer 51 | 52 | duration widget.Float 53 | reset widget.Clickable 54 | } 55 | 56 | // NewUI creates a new UI using the Go Fonts. 57 | func NewUI() *UI { 58 | ui := &UI{} 59 | ui.Theme = material.NewTheme(gofont.Collection()) 60 | 61 | // start with reasonable defaults. 62 | ui.Timer = NewTimer(5 * time.Second) 63 | ui.duration.Value = 5 64 | 65 | return ui 66 | } 67 | 68 | // Run handles window events and renders the application. 69 | func (ui *UI) Run(w *app.Window) error { 70 | 71 | // start the timer goroutine and ensure it's closed 72 | // when the application closes. 73 | closeTimer := ui.Timer.Start() 74 | defer closeTimer() 75 | 76 | var ops op.Ops 77 | for { 78 | select { 79 | // when the timer is updated we should update the screen. 80 | case <-ui.Timer.Updated: 81 | w.Invalidate() 82 | 83 | case e := <-w.Events(): 84 | // detect the type of the event. 85 | switch e := e.(type) { 86 | // this is sent when the application should re-render. 87 | case system.FrameEvent: 88 | // gtx is used to pass around rendering and event information. 89 | gtx := layout.NewContext(&ops, e) 90 | // render and handle UI. 91 | ui.Layout(gtx) 92 | // render and handle the operations from the UI. 93 | e.Frame(gtx.Ops) 94 | 95 | // handle a global key press. 96 | case key.Event: 97 | switch e.Name { 98 | // when we click escape, let's close the window. 99 | case key.NameEscape: 100 | return nil 101 | } 102 | 103 | // this is sent when the application is closed. 104 | case system.DestroyEvent: 105 | return e.Err 106 | } 107 | } 108 | } 109 | } 110 | 111 | // Layout displays the main program layout. 112 | func (ui *UI) Layout(gtx layout.Context) layout.Dimensions { 113 | th := ui.Theme 114 | 115 | // check whether the reset button was clicked. 116 | if ui.reset.Clicked() { 117 | ui.Timer.Reset() 118 | } 119 | // check whether the slider value has changed. 120 | if ui.duration.Changed() { 121 | ui.Timer.SetDuration(secondsToDuration(float64(ui.duration.Value))) 122 | } 123 | 124 | // get the latest information about the timer. 125 | info := ui.Timer.Info() 126 | progress := float32(0) 127 | if info.Duration == 0 { 128 | progress = 1 129 | } else { 130 | progress = float32(info.Progress.Seconds() / info.Duration.Seconds()) 131 | } 132 | 133 | // inset is used to add padding around the window border. 134 | inset := layout.UniformInset(defaultMargin) 135 | return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { 136 | return layout.Flex{Axis: layout.Vertical}.Layout(gtx, 137 | layout.Rigid(material.Body1(th, "Elapsed Time").Layout), 138 | layout.Rigid(material.ProgressBar(th, progress).Layout), 139 | layout.Rigid(material.Body1(th, info.ProgressString()).Layout), 140 | 141 | layout.Rigid(layout.Spacer{Height: gtx.Metric.SpToDp(th.TextSize)}.Layout), 142 | layout.Rigid(material.Body1(th, "Duration").Layout), 143 | layout.Rigid(material.Slider(th, &ui.duration, 0, 15).Layout), 144 | 145 | layout.Rigid(layout.Spacer{Height: gtx.Metric.SpToDp(th.TextSize)}.Layout), 146 | layout.Rigid(material.Button(th, &ui.reset, "Reset").Layout), 147 | ) 148 | }) 149 | } 150 | 151 | func secondsToDuration(s float64) time.Duration { 152 | return time.Duration(s * float64(time.Second)) 153 | } 154 | -------------------------------------------------------------------------------- /6-case-study/2-timer/timer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | // Timer implements an 11 | type Timer struct { 12 | // Updated is used to notify UI about changes in the timer. 13 | Updated chan struct{} 14 | 15 | // mu locks the state such that it can be modified and accessed 16 | // from multiple goroutines. 17 | mu sync.Mutex 18 | start time.Time // start corresponds to when the timer was started. 19 | now time.Time // now corresponds to the last updated time. 20 | duration time.Duration // duration is the maximum progress. 21 | } 22 | 23 | // NewTimer creates a new timer with the specified timer. 24 | func NewTimer(initialDuration time.Duration) *Timer { 25 | return &Timer{ 26 | Updated: make(chan struct{}), 27 | duration: initialDuration, 28 | } 29 | } 30 | 31 | // Start the timer goroutine and return a cancel func that 32 | // that can be used to stop it. 33 | func (t *Timer) Start() context.CancelFunc { 34 | // initialize the timer state. 35 | now := time.Now() 36 | t.now = now 37 | t.start = now 38 | 39 | // we use done to signal stopping the goroutine. 40 | // a context.Context could be also used. 41 | done := make(chan struct{}) 42 | go t.run(done) 43 | return func() { close(done) } 44 | } 45 | 46 | // run is the main loop for the timer. 47 | func (t *Timer) run(done chan struct{}) { 48 | // we use a time.Ticker to update the state, 49 | // in many cases, this could be a network access instead. 50 | tick := time.NewTicker(50 * time.Millisecond) 51 | defer tick.Stop() 52 | 53 | for { 54 | select { 55 | case now := <-tick.C: 56 | t.update(now) 57 | case <-done: 58 | return 59 | } 60 | } 61 | } 62 | 63 | // invalidate sends a signal to the UI that 64 | // the internal state has changed. 65 | func (t *Timer) invalidate() { 66 | // we use a non-blocking send, that way the Timer 67 | // can continue updating internally. 68 | select { 69 | case t.Updated <- struct{}{}: 70 | default: 71 | } 72 | } 73 | 74 | func (t *Timer) update(now time.Time) { 75 | t.mu.Lock() 76 | defer t.mu.Unlock() 77 | 78 | previousNow := t.now 79 | t.now = now 80 | 81 | // first check whether we have not exceeded the duration. 82 | // in that case the progress advanced and we need to notify 83 | // about a change. 84 | progressAfter := t.now.Sub(t.start) 85 | if progressAfter <= t.duration { 86 | t.invalidate() 87 | return 88 | } 89 | 90 | // when we had progressed beyond the duration we also 91 | // need to update the first time it happens. 92 | progressBefore := previousNow.Sub(t.start) 93 | if progressBefore <= t.duration { 94 | t.invalidate() 95 | return 96 | } 97 | } 98 | 99 | // Reset resets timer to the last know time. 100 | func (t *Timer) Reset() { 101 | t.mu.Lock() 102 | defer t.mu.Unlock() 103 | 104 | t.start = t.now 105 | t.invalidate() 106 | } 107 | 108 | // SetDuration changes the duration of the timer. 109 | func (t *Timer) SetDuration(duration time.Duration) { 110 | t.mu.Lock() 111 | defer t.mu.Unlock() 112 | 113 | if t.duration == duration { 114 | return 115 | } 116 | t.duration = duration 117 | t.invalidate() 118 | } 119 | 120 | // Info returns the latest know info about the timer. 121 | func (t *Timer) Info() (info Info) { 122 | t.mu.Lock() 123 | defer t.mu.Unlock() 124 | 125 | info.Progress = t.now.Sub(t.start) 126 | info.Duration = t.duration 127 | if info.Progress > info.Duration { 128 | info.Progress = info.Duration 129 | } 130 | return info 131 | } 132 | 133 | // Info is the information about the timer. 134 | type Info struct { 135 | Progress time.Duration 136 | Duration time.Duration 137 | } 138 | 139 | // ProgressString returns the progress formatted as seconds. 140 | func (info *Info) ProgressString() string { 141 | return fmt.Sprintf("%.1fs", info.Progress.Seconds()) 142 | } 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is provided under the terms of the UNLICENSE or 2 | the MIT license denoted by the following SPDX identifier: 3 | 4 | SPDX-License-Identifier: Unlicense OR MIT 5 | 6 | You may use the project under the terms of either license. 7 | 8 | Both licenses are reproduced below. 9 | 10 | ---- 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2022 Golang Estonia 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | --- 33 | 34 | 35 | 36 | --- 37 | The UNLICENSE 38 | 39 | This is free and unencumbered software released into the public domain. 40 | 41 | Anyone is free to copy, modify, publish, use, compile, sell, or 42 | distribute this software, either in source code form or as a compiled 43 | binary, for any purpose, commercial or non-commercial, and by any 44 | means. 45 | 46 | In jurisdictions that recognize copyright laws, the author or authors 47 | of this software dedicate any and all copyright interest in the 48 | software to the public domain. We make this dedication for the benefit 49 | of the public at large and to the detriment of our heirs and 50 | successors. We intend this dedication to be an overt act of 51 | relinquishment in perpetuity of all present and future rights to this 52 | software under copyright law. 53 | 54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 55 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 56 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 57 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 58 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 59 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 60 | OTHER DEALINGS IN THE SOFTWARE. 61 | 62 | For more information, please refer to 63 | --- 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn Gio 2 | 3 | This repository contains small examples for learning Gio UI Framework. -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/golangestonia/learn-gio 2 | 3 | go 1.19 4 | 5 | require gioui.org v0.0.0-20230101161950-e9bce02b24f0 6 | 7 | require ( 8 | gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect 9 | gioui.org/shader v1.0.6 // indirect 10 | github.com/benoitkugler/textlayout v0.3.0 // indirect 11 | github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 // indirect 12 | golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 // indirect 13 | golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect 14 | golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect 15 | golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect 16 | golang.org/x/text v0.3.7 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q= 2 | gioui.org v0.0.0-20230101161950-e9bce02b24f0 h1:/3chuQ/TLkZ6SubTxxxOfO5FM1PUWVo0o/epk6PNe1o= 3 | gioui.org v0.0.0-20230101161950-e9bce02b24f0/go.mod h1:3lLo7xMHYnnHTrgKNNctBjEKKH3wQCO2Sn7ti5Jy8mU= 4 | gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= 5 | gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc= 6 | gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= 7 | gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y= 8 | gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= 9 | github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= 10 | github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk= 11 | github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= 12 | github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk= 13 | github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= 14 | github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 h1:iOA0HmtpANn48hX2nlDNMu0VVaNza35HJG0WeetBVzQ= 15 | github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI= 16 | golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg= 17 | golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 18 | golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0= 19 | golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= 20 | golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 21 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 22 | golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU= 23 | golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= 24 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 25 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 26 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= 28 | golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 30 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 31 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 32 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 33 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 34 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 35 | -------------------------------------------------------------------------------- /qapp/loop.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package qapp 4 | 5 | import ( 6 | "image" 7 | "log" 8 | "os" 9 | 10 | "gioui.org/app" // app contains Window handling. 11 | "gioui.org/io/event" // event contains general event information. 12 | "gioui.org/io/system" // system is used for system events (e.g. closing the window). 13 | "gioui.org/layout" // layout is used for layouting widgets. 14 | "gioui.org/op" // op is used for recording different operations. 15 | "gioui.org/unit" // unit contains metric conversion 16 | ) 17 | 18 | // Render is a utility to start a rendering gio app. 19 | func Render(fn func(ops *op.Ops)) { 20 | go func() { 21 | w := app.NewWindow() 22 | // ops will be used to encode different operations. 23 | var ops op.Ops 24 | 25 | // listen for events happening on the window. 26 | for e := range w.Events() { 27 | // detect the type of the event. 28 | switch e := e.(type) { 29 | // this is sent when the application should re-render. 30 | case system.FrameEvent: 31 | // gtx is used to pass around rendering and event information. 32 | gtx := layout.NewContext(&ops, e) 33 | // render content 34 | fn(gtx.Ops) 35 | // render and handle the operations from the UI. 36 | e.Frame(gtx.Ops) 37 | 38 | // this is sent when the application is closed. 39 | case system.DestroyEvent: 40 | if e.Err != nil { 41 | log.Println(e.Err) 42 | os.Exit(1) 43 | } 44 | os.Exit(0) 45 | } 46 | } 47 | }() 48 | app.Main() 49 | } 50 | 51 | // Input is a utility to start a rendering and input gio app. 52 | func Input(fn func(ops *op.Ops, queue event.Queue)) { 53 | go func() { 54 | w := app.NewWindow() 55 | // ops will be used to encode different operations. 56 | var ops op.Ops 57 | 58 | // listen for events happening on the window. 59 | for e := range w.Events() { 60 | // detect the type of the event. 61 | switch e := e.(type) { 62 | // this is sent when the application should re-render. 63 | case system.FrameEvent: 64 | // gtx is used to pass around rendering and event information. 65 | gtx := layout.NewContext(&ops, e) 66 | // render content 67 | fn(gtx.Ops, gtx.Queue) 68 | // render and handle the operations from the UI. 69 | e.Frame(gtx.Ops) 70 | 71 | // this is sent when the application is closed. 72 | case system.DestroyEvent: 73 | if e.Err != nil { 74 | log.Println(e.Err) 75 | os.Exit(1) 76 | } 77 | os.Exit(0) 78 | } 79 | } 80 | }() 81 | app.Main() 82 | } 83 | 84 | // InputSize is a utility to start a rendering and input gio app. 85 | func InputSize(fn func(ops *op.Ops, queue event.Queue, windowSize image.Point)) { 86 | go func() { 87 | w := app.NewWindow() 88 | // ops will be used to encode different operations. 89 | var ops op.Ops 90 | 91 | // listen for events happening on the window. 92 | for e := range w.Events() { 93 | // detect the type of the event. 94 | switch e := e.(type) { 95 | // this is sent when the application should re-render. 96 | case system.FrameEvent: 97 | // gtx is used to pass around rendering and event information. 98 | gtx := layout.NewContext(&ops, e) 99 | // render content 100 | fn(gtx.Ops, gtx.Queue, gtx.Constraints.Max) 101 | // render and handle the operations from the UI. 102 | e.Frame(gtx.Ops) 103 | 104 | // this is sent when the application is closed. 105 | case system.DestroyEvent: 106 | if e.Err != nil { 107 | log.Println(e.Err) 108 | os.Exit(1) 109 | } 110 | os.Exit(0) 111 | } 112 | } 113 | }() 114 | app.Main() 115 | } 116 | 117 | // Metric is a utility to start a rendering, input and metrics gio app. 118 | func Metric(fn func(ops *op.Ops, queue event.Queue, metric unit.Metric)) { 119 | go func() { 120 | w := app.NewWindow() 121 | // ops will be used to encode different operations. 122 | var ops op.Ops 123 | 124 | // listen for events happening on the window. 125 | for e := range w.Events() { 126 | // detect the type of the event. 127 | switch e := e.(type) { 128 | // this is sent when the application should re-render. 129 | case system.FrameEvent: 130 | // gtx is used to pass around rendering and event information. 131 | gtx := layout.NewContext(&ops, e) 132 | // render content 133 | fn(gtx.Ops, gtx.Queue, gtx.Metric) 134 | // render and handle the operations from the UI. 135 | e.Frame(gtx.Ops) 136 | 137 | // this is sent when the application is closed. 138 | case system.DestroyEvent: 139 | if e.Err != nil { 140 | log.Println(e.Err) 141 | os.Exit(1) 142 | } 143 | os.Exit(0) 144 | } 145 | } 146 | }() 147 | app.Main() 148 | } 149 | 150 | // Layout is a utility to start a layouting gio app. 151 | func Layout(lay func(gtx layout.Context) layout.Dimensions) { 152 | go func() { 153 | w := app.NewWindow() 154 | // ops will be used to encode different operations. 155 | var ops op.Ops 156 | 157 | // listen for events happening on the window. 158 | for e := range w.Events() { 159 | // detect the type of the event. 160 | switch e := e.(type) { 161 | // this is sent when the application should re-render. 162 | case system.FrameEvent: 163 | // gtx is used to pass around rendering and event information. 164 | gtx := layout.NewContext(&ops, e) 165 | // render content 166 | lay(gtx) 167 | // render and handle the operations from the UI. 168 | e.Frame(gtx.Ops) 169 | 170 | // this is sent when the application is closed. 171 | case system.DestroyEvent: 172 | if e.Err != nil { 173 | log.Println(e.Err) 174 | os.Exit(1) 175 | } 176 | os.Exit(0) 177 | } 178 | } 179 | }() 180 | app.Main() 181 | } 182 | -------------------------------------------------------------------------------- /qasset/asset.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense OR MIT 2 | 3 | package qasset 4 | 5 | import ( 6 | "bytes" 7 | _ "embed" 8 | "image" 9 | "image/png" 10 | ) 11 | 12 | //go:embed neutral.png 13 | var neutralData []byte 14 | 15 | var Neutral = func() image.Image { 16 | m, err := png.Decode(bytes.NewReader(neutralData)) 17 | if err != nil { 18 | panic(err) 19 | } 20 | return m 21 | }() 22 | 23 | //go:embed gamer.png 24 | var gamerData []byte 25 | 26 | var Gamer = func() image.Image { 27 | m, err := png.Decode(bytes.NewReader(gamerData)) 28 | if err != nil { 29 | panic(err) 30 | } 31 | return m 32 | }() 33 | -------------------------------------------------------------------------------- /qasset/gamer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golangestonia/learn-gio/26d662ca5c8aff8515c446aca2e7d63ad4a33c30/qasset/gamer.png -------------------------------------------------------------------------------- /qasset/neutral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golangestonia/learn-gio/26d662ca5c8aff8515c446aca2e7d63ad4a33c30/qasset/neutral.png --------------------------------------------------------------------------------