-
Notifications
You must be signed in to change notification settings - Fork 1
/
cphgophers-nov-2019.slide
541 lines (289 loc) · 13.9 KB
/
cphgophers-nov-2019.slide
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
Gio: Portable Immediate Mode GUI
Cph Gophers Meetup, November 2019
Elias Naur
mail@eliasnaur.com
@eliasnaur
https://gioui.org
https://scatter.im
https://eliasnaur.com/sponsor
* Introduction
Gio - [[https://gioui.org][gioui.org]]
- Immediate mode design.
- Cross platform (macOS, Linux, FreeBSD, Windows, Android, iOS, tvOS, WebAssembly).
- GPU accelerated vector and text rendering.
- No garbage during drawing and layout.
- Core is 100% Go. OS-specific native interfaces are optional.
* Demo - Scatter
* Immediate mode UI
- UI state is owned by the program. Even your layout and widget tree.
- No callbacks. Events are handled while drawing.
* Blank window
.play blank.go
* Hello, World
.play helloworld.go /START OMIT/,/END OMIT/ HLdraw
: This the proverbial Hello, world program written with Gio. It's little bigger but still fit on a slide after leaving out the package statement and package imports.
: Compared to the blank window from earlier, hello world loads a font, initializes a few support variables and draws a label.
: There are no shortcuts in the program, perhaps except from the type assertion that in a larger program will be a type switch for the various event types.
* Running Gio programs
* Linux, macOS, Windows, FreeBSD
Enable modules
export GO111MODULE=on
Build, install or run the program
go build gioui.org/example/hello
go install scatter.im/cmd/scatter
go run helloworld.go
: Running Gio programs is as straightforward as any other program, at least on desktop systems. Linux require a C compiler and a few development libraries, while macOS requires Xcode. There are no build dependencies for Windows, but Gio currently need the ANGLE OpenGL ES emulator to run.
: I recommend enabling module mode so Gio is automatically downloaded for you and to shield yourself from the still frequent API changes.
* Android
Install the gogio tool
go install gioui.org/cmd/gogio
$GOBIN/gogio -target android -o hello.apk helloworld.go
Install on a connected device or emulator with adb
adb install hello.apk
: The mobile platforms are of course more troublesome. The Gio project include a tool that can package a Gio program suitable for installation to a mobile device, an emulator or for including in an existing project.
: To build for Android you need the Android SDK and NDK installed. The
gogio tool can produce an apk file that can be installed with the Android adb tool.
* iOS/tvOS
For iOS/tvOS devices:
$GOBIN/gogio -target <ios|tvos> -o hello.ipa -appid <bundle id> helloworld.go
Use the .app file extension for simulators:
$GOBIN/gogio -target <ios|tvos> -o hello.app helloworld.go
Install on a running simulator
xcrun simctl install booted hello.app
: To build for iOS and tvOS you need Xcode and the bundle id from a valid provisioning profile. A good way to acquire that is to set up a sample project in Xcode and run it once on your device.
: The iSimulators don't accept ipa packages, but rather raw .app
directories. The gogio tool use the output extension to distinguish.
: Please note that tvOS support is very early. For example, there is currenctly no API exposed for the remote control. I added support because it was easy and as a proof of concept.
* Browsers (Go 1.14+)
To output a directory ready to serve:
$GOBIN/gogio -target js -o www helloworld.go
Use a webserver or goexec to serve it:
go run github.com/shurcooL/goexec 'http.ListenAndServe(":8080", http.FileServer(http.Dir("www")))'
: The gogio tool also build a webassembly version of your program. It includes the required support files and a basic HTML host page so the program is ready to serve.
: You can use any webserver that serves static files; I use Dmitri's goexec tool for demos and tests. Goexec can run a complete webserver in a single line if you have Go modules enabled.
: Note that running in the browser works but is slow. Partly because Gio invokes JavaScript functions in a straightforward but suboptimal way, partly because the Go runtime is not yet a great match for webassembly. I hope that future webassembly improvements will drastically speed up Go in the browser.
: Also note that Gio use a WebGL Canvas element for display and input. You don't get the convenience of HTML DOM elements. At least not yet.
: [go run helloworld.go]
: [gogio -target android -o android.apk helloworld.go]
: [gogio -target ios -o hello.app helloworld.go]
: [xcrun simctl install booted hello.app]
: [adb install hello.apk]
: [go run webassembly.go]
* Operations
* Operations
Serializing operations
import "gioui.org/op" // Pure Go
var ops op.Ops
// Add operations to ops
op.InvalidateOp{}.Add(ops)
...
Applying operations.
import "gioui.org/io/system"
var e system.FrameEvent
e.Frame(&ops)
: In Gio, the basic building block is the operation. There are operations for clipping, transforming and drawing as well as ops for controlling input flow and requesting a redraw for animation.
: This is a list of drawing operations.
: Immediate mode UI libraries redraw everything and Gio is no different; if your program state changes, you simply request a Redraw and redraw everything.
: This is actually more efficient than it might sound, both through the sheer power of modern GPUs but also through designing operations to be efficient.
: Operations are carefully designed such that they generate no garbage when drawing. The underlying Ops buffer is reused and the Add method of every op is written so the op itself never escapes to the heap.
: For example, the label from the helloworld example is drawn garbage free, as long as your string is constant and re-use the font cache.
* Operations
Position other operations
import "gioui.org/op"
import "gioui.org/f32"
op.TransformOp{}.Offset(f32.Point{...}).Add(ops)
Request a redraw
ui.InvalidateOp{}.Add(ops) // Immediate
ui.InvalidateOp{At: ...}.Add(ops) // Delayed
* Drawing operations
Set current color or image
import "gioui.org/op/paint"
paint.ColorOp{Color: color.RGBA{...}}.Add(ops)
paint.ImageOp{Src: ..., Rect: ...}.Add(ops)
Draw with the current color or image
paint.PaintOp{Rect: ...}.Add(ops)
* Clip operations
Clip drawing to a rectangle
import "gioui.org/op/clip"
var ops *op.Ops
clip.Rect{Rect: f32.Rectangle{...}}.Op(ops).Add(ops)
Rounded corners
clip.Rect{Rect: ..., NE: ..., NW: ..., SE: ..., SW:...}.Op(ops).Add(ops)
General (even-odd) outline
var b clip.Path
b.Begin(ops)
b.Line(...)
b.Quad(...) // Quadratic Beziér curve
b.Cube(...) // Cubic Beziér curve
b.End().Add(ops)
: The major parts of any UI program are drawing, layout and input handling.
: We'll tackle drawing first.
* Input operations
Keyboard and text input
import "gioui.org/io/key"
// Declare key handler.
key.InputOp{Key: handler, Focus: true/false}.Add(ops)
// Hide soft keyboard.
key.HideInputOp{}.Add(ops)
Mouse and touch input
import "gioui.org/io/pointer"
// Define hit area.
pointer.Rect(image.Rectangle{...}).Add(ops)
pointer.Ellipse(image.Rectangle{...}).Add(ops)
// Declare pointer handler.
pointer.InputOp{Key: c, Grab: true/false}
* Drawing
* Drawing (and animating)
.play animatedclipping.go /START OMIT/,/END OMIT/
* Layout
: Layout is the positioning, sizing and layering of widgets.
: Some framework add properties or special widgets for laying out other widgets.
: Layouts in Gio is done through transient helper objects, except for the scrollable list which is user controlled.
* Constraints and dimensions
Constraints are input
package layout // import gioui.org/layout
type Constraints struct {
Width Constraint
Height Constraint
}
type Constraint struct {
Min, Max int
}
Dimensions are output
type Dimensions struct {
Size image.Point
Baseline int
}
* Constraints and dimensions
Widgets accept constraints, output dimensions.
package material // import gioui.org/widget/material
func (l Label) Layout(gtx *layout.Context)
Context tracks the current constraints and dimensions.
package layout // import "gioui.org/layout"
type Context struct {
Constraints Constraints
Dimensions Dimensions
...
}
* Example - two labels
.play twolabels2.go /START DRAW OMIT/,/END DRAW OMIT/ HLdraw
* Layout helpers
Aligning
var gtx *layout.Context
layout.Center.Layout(gtx, func() {
someWidget.Layout(gtx, ...) // Draw widget
})
Insetting
inset := layout.Inset{Top: ui.Dp(8), ...} // 8dp top inset
inset.Layout(gtx, func() {
anotherWidget.Layout(gtx, ...) // Draw widget
})
: Centering is a special case of aligning widgets. Aligning and insetting widgets are so common that Gio provides two helper
: types in the layout package, Align and Inset.
: Both Align and Inset are designed to be garbage free. Go is clever enough to allocate them on the stack even though their Begin methods mutate them.
: Note that the layout package represent distances with device independent points instead of pixels to ensure your program looks the
: same regardless of your monitor's pixel density.
* Flex layout
Lay out widgets on an axis.
.play flex.go /START OMIT/,/END OMIT/ HLflex
: A more complex layout in Gio is the Flex. It mimics the flex layout from Flutter and is used for laying out widgets along an axis.
: The program shown on this slide demonstrates a weighted layout, where the first rectangle takes up half the available space, while the two last
: share the other half.
: [go run flex.go]
* Stack layout
.play stack.go /START OMIT/,/END OMIT/ HLstack
: Stack is another layout in Gio which as the name implies is used for layering widget on top of each other.
: In this example, stack is used to stack a list of rectangles with increasing insets.
: [go run stack.go]
* List layout
.code list.go /START INIT OMIT/,/END INIT OMIT/ HLlist
.play list.go /START OMIT/,/END OMIT/ HLlist
: The most complex layout in Gio is the scrollable List. It is also the first stateful widget you've seen, in that it can accept user input and scroll its content to match.
: Even though the list is declared to be a million elements long, the list only lays out the currently visible elements.
: List does that by asking your program to lay out a particular index, returned by the Index method.
: [go run list.go]
* Input
: I came up with the name Gio as an acronym for "graphical input/output". So far we've only seen output: drawing and positioning drawings.
: Let's move on to the other side, input.
* Input queue and handler keys
// Queue maps an event handler key to the events
// available to the handler.
type Queue interface {
Events(k Key) []Event
}
// Key is the stable identifier for an event handler.
// For a handler h, the key is typically &h.
type Key interface{}
: One of my favorite features of immediate mode UI programs is the absence of callbacks.
: In Gio all events from input sources such your mouse, finger, keyboard are distributed to handlers through the input.Queue.
: Queue is an interface with just a single method, Next, returning all events available in this frame for a given
: handler.
: A Key is the identifier for a handler. Any value will do, as long as it is stable across frames,
: and can be used as a map key.
: Typically the address of the handler is used as its Key.
* Pointer event handling
.play pointer.go /START OMIT/,/END OMIT/ HLevent
: Let's say you have a Button widget. It has only one field, the pressed boolean.
: The first part of the button layout method updates the pressed state. It runs through the available events and set the pressed to true for press events and false for release events.
: Then, it registers the handler by specifying a hit area, in this case a rectangle, and a InputOp for specifying its own key.
: Note how there are no callbacks involved and that there is no way or need to unregister a handler; all handler registrations are cleared
: at the beginning of the next frame.
: Registration and handling of events are separate and can be done in any order, because event handling is for the current set of events, while registration is for delivering events between frames.
: [go run pointer.go]
* Gestures
import "gioui.org/op"
import "gioui.org/gesture"
Detect clicks
var queue op.Queue
var c gesture.Click
for _, event := range c.Events(queue) {
// event is a gesture.ClickEvent, not a raw pointer.Event.
}
Determine scroll distance from mouse wheel or touch drag/fling
var cfg ui.Config
var s gesture.Scroll
distance := s.Scroll(cfg, queue, gesture.Vertical)
: The gesture package help your program recognize higher level gestures from low level pointer events. Examples shown are click and scroll.
: In a sense, gestures are state machines that are updated by raw pointers events.
: The Click state machine record the current pressed state, which the Scroll state machine track touch velocity, while animating it on touch release.
* Widgets and themes
* Material theme
import "gioui.org/widget/material"
th := material.NewTheme()
Labels (stateless)
th.Label(unit.Sp(14), "14sp text").Layout(gtx)
th.H3("H3 text").Layout(gtx)
Customization
// Per theme
th.TextSize = unit.Sp(20)
th.Color.Primary = color.RGBA{...}
// Per widget
lbl := th.H3("Custom text") // Use theme properties.
lbl.Color = color.RGBA{...} // Set widget property.
lbl.Layout(gtx)
* Stateful widgets
import "gioui.org/widget" // State
import "gioui.org/widget/material" // Theme
// Initialize and store button state.
var button = new(widget.Button)
Stateless appearance
var th *material.Theme
th.Button("Click me").Layout(gtx, button)
Events
for button.Clicked(gtx) {
fmt.Println("Clicked!")
}
* Widgets - the Editor
Initialize the editor
import "gioui.org/widget"
.code editor.go /START INIT OMIT/,/END INIT OMIT/
Draw, layout and handle input in one call, Layout.
.play editor.go /START OMIT/,/END OMIT/
* Why Gio?
* Why Gio?
Gio is
- Simple. Immediate mode design, no hidden state.
- Portable. The core of Gio is all Go.
- Fast. GPU accelerated, very little per-frame garbage.
- Convenient. Develop on desktop, deploy on mobile.
- Public domain source (UNLICENCE). Dual licenced MIT to please your lawyers.