Browse Source

学习 goi 项目代码

master
VIVIMAN 3 years ago
parent
commit
d21d9e9b57
  1. 10
      golang_learn/data_studygolang/周刊127.go
  2. 34
      gui_gio_learn/7gui/README.md
  3. 137
      gui_gio_learn/7gui/counter/main.go
  4. 211
      gui_gio_learn/7gui/temperature/main.go
  5. 155
      gui_gio_learn/7gui/timer/main.go
  6. 142
      gui_gio_learn/7gui/timer/timer.go
  7. 63
      gui_gio_learn/LICENSE
  8. 21
      gui_gio_learn/README.md
  9. 98
      gui_gio_learn/colorpicker/main.go
  10. 43
      gui_gio_learn/component/applayout/applayout.go
  11. 66
      gui_gio_learn/component/icon/icons.go
  12. 65
      gui_gio_learn/component/main.go
  13. 103
      gui_gio_learn/component/pages/about/about.go
  14. 181
      gui_gio_learn/component/pages/appbar/appbar.go
  15. 136
      gui_gio_learn/component/pages/discloser/discloser.go
  16. 185
      gui_gio_learn/component/pages/menu/menu.go
  17. 95
      gui_gio_learn/component/pages/navdrawer/navdrawer.go
  18. 118
      gui_gio_learn/component/pages/page.go
  19. 194
      gui_gio_learn/component/pages/textfield/textfield.go
  20. 158
      gui_gio_learn/explorer/main.go
  21. 88
      gui_gio_learn/galaxy/galaxy.go
  22. 378
      gui_gio_learn/galaxy/main.go
  23. 216
      gui_gio_learn/glfw/main.go
  24. 17
      gui_gio_learn/go.mod
  25. 725
      gui_gio_learn/go.sum
  26. 248
      gui_gio_learn/gophers/main.go
  27. 24
      gui_gio_learn/gophers/main_test.go
  28. 424
      gui_gio_learn/gophers/ui.go
  29. 60
      gui_gio_learn/haptic/example.go
  30. 17
      gui_gio_learn/haptic/example_android.go
  31. 10
      gui_gio_learn/haptic/example_nonandroid.go
  32. 51
      gui_gio_learn/hello/hello.go
  33. 433
      gui_gio_learn/kitchen/kitchen.go
  34. 93
      gui_gio_learn/kitchen/main_test.go
  35. 116
      gui_gio_learn/life/board.go
  36. 118
      gui_gio_learn/life/main.go
  37. 68
      gui_gio_learn/life/style.go
  38. 168
      gui_gio_learn/markdown/main.go
  39. 64
      gui_gio_learn/multiwindow/letters.go
  40. 86
      gui_gio_learn/multiwindow/log.go
  41. 126
      gui_gio_learn/multiwindow/main.go
  42. 8
      gui_gio_learn/notify/build_macos.go
  43. 50
      gui_gio_learn/notify/example.app/Contents/Info.plist
  44. 1
      gui_gio_learn/notify/example.app/Contents/PkgInfo
  45. 88
      gui_gio_learn/notify/main.go
  46. 40
      gui_gio_learn/opengl/display_angle.go
  47. 24
      gui_gio_learn/opengl/egl_linux.go
  48. 288
      gui_gio_learn/opengl/main.go
  49. 16
      gui_gio_learn/opengl/view_darwin.go
  50. 18
      gui_gio_learn/opengl/view_windows.go
  51. 25
      gui_gio_learn/outlay/fan/cribbage/cmd/crib.go
  52. 171
      gui_gio_learn/outlay/fan/cribbage/cribbage.go
  53. 163
      gui_gio_learn/outlay/fan/main.go
  54. 116
      gui_gio_learn/outlay/fan/playing/card.go
  55. 142
      gui_gio_learn/outlay/fan/widget/boring/cards.go
  56. 34
      gui_gio_learn/outlay/fan/widget/boring/rect.go
  57. 49
      gui_gio_learn/outlay/fan/widget/state.go
  58. 223
      gui_gio_learn/outlay/grid/main.go
  59. 166
      gui_gio_learn/outlay/table/main.go
  60. 132
      gui_gio_learn/tabs/slider.go
  61. 158
      gui_gio_learn/tabs/tabs.go

10
golang_learn/data_studygolang/周刊127.go

@ -0,0 +1,10 @@
package main
import (
"fmt"
)
func main() {
var a, b float64 = 1.0, 4.0
fmt.Println(a , b)
}

34
gui_gio_learn/7gui/README.md

@ -0,0 +1,34 @@
# 7 GUIs
This demonstrates several classic GUI problems based on [7GUIs](https://eugenkiss.github.io/7guis/). They show different ways of using Gio framework.
The examples show one way of implementing of things, of course, there are many more.
The examples are over-commented to help understand the structure better, in practice, you don't need that many comments.
## Counter
Counter shows basic usage of Gio and how to write interactions.
It displays a count value that increases when you press a button.
[UI](./counter/main.go)
## Temperature Converter
Temperature conversion shows bidirectional data flow between two editable fields.
It implements a bordered field that can be used to propagate values back to another field without causing update loops.
[UI](./temperature/main.go)
## Timer
Timer shows how to react to external signals.
It implements a timer that is running in a separate goroutine and the UI interacts with it. The same effect can be implemented in shorter ways without goroutines, however it nicely demonstrates how you would interact with information that comes in asynchronously.
The UI shows a slider to change the duration of the timer and there is a button to reset the counter.
[UI](./timer/main.go), [Timer](./timer/timer.go)

137
gui_gio_learn/7gui/counter/main.go

@ -0,0 +1,137 @@
package main
import (
"log"
"os"
"strconv"
"gioui.org/app" // app contains Window handling.
"gioui.org/font/gofont" // gofont is used for loading the default font.
"gioui.org/io/key" // key is used for keyboard events.
"gioui.org/io/system" // system is used for system events (e.g. closing the window).
"gioui.org/layout" // layout is used for layouting widgets.
"gioui.org/op" // op is used for recording different operations.
"gioui.org/unit" // unit is used to define pixel-independent sizes
"gioui.org/widget" // widget contains state handling for widgets.
"gioui.org/widget/material" // material contains material design widgets.
)
func main() {
// The ui loop is separated from the application window creation
// such that it can be used for testing.
ui := NewUI()
// This creates a new application window and starts the UI.
go func() {
w := app.NewWindow(
app.Title("Counter"),
app.Size(unit.Dp(240), unit.Dp(70)),
)
if err := ui.Run(w); err != nil {
log.Println(err)
os.Exit(1)
}
os.Exit(0)
}()
// This starts Gio main.
app.Main()
}
// defaultMargin is a margin applied in multiple places to give
// widgets room to breathe.
var defaultMargin = unit.Dp(10)
// UI holds all of the application state.
type UI struct {
// Theme is used to hold the fonts used throughout the application.
Theme *material.Theme
// Counter displays and keeps the state of the counter.
Counter Counter
}
// NewUI creates a new UI using the Go Fonts.
func NewUI() *UI {
ui := &UI{}
ui.Theme = material.NewTheme(gofont.Collection())
return ui
}
// Run handles window events and renders the application.
func (ui *UI) Run(w *app.Window) error {
var ops op.Ops
// listen for events happening on the window.
for e := range w.Events() {
// detect the type of the event.
switch e := e.(type) {
// this is sent when the application should re-render.
case system.FrameEvent:
// gtx is used to pass around rendering and event information.
gtx := layout.NewContext(&ops, e)
// render and handle UI.
ui.Layout(gtx)
// render and handle the operations from the UI.
e.Frame(gtx.Ops)
// handle a global key press.
case key.Event:
switch e.Name {
// when we click escape, let's close the window.
case key.NameEscape:
return nil
}
// this is sent when the application is closed.
case system.DestroyEvent:
return e.Err
}
}
return nil
}
// Layout displays the main program layout.
func (ui *UI) Layout(gtx layout.Context) layout.Dimensions {
// inset is used to add padding around the window border.
inset := layout.UniformInset(defaultMargin)
return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return ui.Counter.Layout(ui.Theme, gtx)
})
}
// Counter is a component that keeps track of it's state and
// displays itself as a label and a button.
type Counter struct {
// Count is the current value.
Count int
// increase is used to track button clicks.
increase widget.Clickable
}
// Layout lays out the counter and handles input.
func (counter *Counter) Layout(th *material.Theme, gtx layout.Context) layout.Dimensions {
// Flex layout lays out widgets from left to right by default.
return layout.Flex{}.Layout(gtx,
// We use weight 1 for both text and count to make them the same size.
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
// We center align the text to the area available.
return layout.Center.Layout(gtx,
// Body1 is the default text size for reading.
material.Body1(th, strconv.Itoa(counter.Count)).Layout)
}),
// We use an empty widget to add spacing between the text
// and the button.
layout.Rigid(layout.Spacer{Height: defaultMargin}.Layout),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
// For every click on the button increment the count.
for range counter.increase.Clicks() {
counter.Count++
}
// Finally display the button.
return material.Button(th, &counter.increase, "Count").Layout(gtx)
}),
)
}

211
gui_gio_learn/7gui/temperature/main.go

@ -0,0 +1,211 @@
package main
import (
"image/color"
"log"
"os"
"strconv"
"gioui.org/app" // app contains Window handling.
"gioui.org/font/gofont" // gofont is used for loading the default font.
"gioui.org/io/key" // key is used for keyboard events.
"gioui.org/io/system" // system is used for system events (e.g. closing the window).
"gioui.org/layout" // layout is used for layouting widgets.
"gioui.org/op" // op is used for recording different operations.
"gioui.org/unit" // unit is used to define pixel-independent sizes
"gioui.org/widget" // widget contains state handling for widgets.
"gioui.org/widget/material" // material contains material design widgets.
)
func main() {
// The ui loop is separated from the application window creation
// such that it can be used for testing.
ui := NewUI()
// This creates a new application window and starts the UI.
go func() {
w := app.NewWindow(
app.Title("Temperature Converter"),
app.Size(unit.Dp(360), unit.Dp(47)),
)
if err := ui.Run(w); err != nil {
log.Println(err)
os.Exit(1)
}
os.Exit(0)
}()
// This starts Gio main.
app.Main()
}
// defaultMargin is a margin applied in multiple places to give
// widgets room to breathe.
var defaultMargin = unit.Dp(10)
// UI holds all of the application state.
type UI struct {
// Theme is used to hold the fonts used throughout the application.
Theme *material.Theme
// Converter displays and modifies the state.
Converter Converter
}
// NewUI creates a new UI using the Go Fonts.
func NewUI() *UI {
ui := &UI{}
ui.Theme = material.NewTheme(gofont.Collection())
ui.Converter.Init()
return ui
}
// Run handles window events and renders the application.
func (ui *UI) Run(w *app.Window) error {
var ops op.Ops
// listen for events happening on the window.
for e := range w.Events() {
// detect the type of the event.
switch e := e.(type) {
// this is sent when the application should re-render.
case system.FrameEvent:
// gtx is used to pass around rendering and event information.
gtx := layout.NewContext(&ops, e)
// render and handle UI.
ui.Layout(gtx)
// render and handle the operations from the UI.
e.Frame(gtx.Ops)
// handle a global key press.
case key.Event:
switch e.Name {
// when we click escape, let's close the window.
case key.NameEscape:
return nil
}
// this is sent when the application is closed.
case system.DestroyEvent:
return e.Err
}
}
return nil
}
// Layout displays the main program layout.
func (ui *UI) Layout(gtx layout.Context) layout.Dimensions {
// inset is used to add padding around the window border.
inset := layout.UniformInset(defaultMargin)
return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return ui.Converter.Layout(ui.Theme, gtx)
})
}
// Converter is a component that keeps track of it's state and
// displays itself as two editors.
type Converter struct {
Celsius Field
Fahrenheit Field
}
// Init is used to set the inital state.
func (conv *Converter) Init() {
conv.Celsius.SingleLine = true
conv.Fahrenheit.SingleLine = true
}
// Layout lays out the editors.
func (conv *Converter) Layout(th *material.Theme, gtx layout.Context) layout.Dimensions {
// We use an empty widget to add spacing between widgets.
spacer := layout.Rigid(layout.Spacer{Width: defaultMargin}.Layout)
// check whether the celsius value has changed.
if conv.Celsius.Changed() {
// try to convert the value to an integer
newValue, err := strconv.Atoi(conv.Celsius.Text())
// update whether the editor is displaying a valid value
conv.Celsius.Invalid = err != nil
if !conv.Celsius.Invalid {
// update the other editor when it's valid
conv.Fahrenheit.Invalid = false
conv.Fahrenheit.SetText(strconv.Itoa(newValue*9/5 + 32))
}
}
// check whether the fahrenheit value has changed.
if conv.Fahrenheit.Changed() {
newValue, err := strconv.Atoi(conv.Fahrenheit.Text())
conv.Fahrenheit.Invalid = err != nil
if !conv.Fahrenheit.Invalid {
conv.Celsius.Invalid = false
conv.Celsius.SetText(strconv.Itoa((newValue - 32) * 5 / 9))
}
}
// TODO: use proper baseline alignment.
return layout.Flex{}.Layout(gtx,
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return conv.Celsius.Layout(th, gtx)
}),
spacer,
layout.Rigid(material.Body1(th, "Celsius").Layout),
spacer,
layout.Rigid(material.Body1(th, "=").Layout),
spacer,
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return conv.Fahrenheit.Layout(th, gtx)
}),
spacer,
layout.Rigid(material.Body1(th, "Fahrenheit").Layout),
)
}
// Field implements an editor that allows updating the state and detect
// changes to the field from other sources.
type Field struct {
widget.Editor
Invalid bool
old string
}
// Changed checks once whether the editor context has changed.
func (ed *Field) Changed() bool {
newText := ed.Editor.Text()
changed := newText != ed.old
ed.old = newText
return changed
}
// SetText sets editor content without marking the editor changed.
func (ed *Field) SetText(s string) {
ed.old = s
ed.Editor.SetText(s)
}
// Layout handles the editor with the appropriate color and border.
func (ed *Field) Layout(th *material.Theme, gtx layout.Context) layout.Dimensions {
// Determine colors based on the state of the editor.
borderWidth := float32(0.5)
borderColor := color.NRGBA{A: 107}
switch {
case ed.Editor.Focused():
borderColor = th.Palette.ContrastBg
borderWidth = 2
case ed.Invalid:
borderColor = color.NRGBA{R: 200, A: 0xFF}
}
// draw an editor with a border.
return widget.Border{
Color: borderColor,
CornerRadius: unit.Dp(4),
Width: unit.Dp(borderWidth),
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(4)).Layout(gtx,
material.Editor(th, &ed.Editor, "").Layout)
})
}

155
gui_gio_learn/7gui/timer/main.go

@ -0,0 +1,155 @@
package main
import (
"log"
"os"
"time"
"gioui.org/app" // app contains Window handling.
"gioui.org/font/gofont" // gofont is used for loading the default font.
"gioui.org/io/key" // key is used for keyboard events.
"gioui.org/io/system" // system is used for system events (e.g. closing the window).
"gioui.org/layout" // layout is used for layouting widgets.
"gioui.org/op" // op is used for recording different operations.
"gioui.org/unit" // unit is used to define pixel-independent sizes
"gioui.org/widget" // widget contains state handling for widgets.
"gioui.org/widget/material" // material contains material design widgets.
)
func main() {
// The ui loop is separated from the application window creation
// such that it can be used for testing.
ui := NewUI()
// This creates a new application window and starts the UI.
go func() {
w := app.NewWindow(
app.Title("Timer"),
app.Size(unit.Dp(360), unit.Dp(360)),
)
if err := ui.Run(w); err != nil {
log.Println(err)
os.Exit(1)
}
os.Exit(0)
}()
// This starts Gio main.
app.Main()
}
// defaultMargin is a margin applied in multiple places to give
// widgets room to breathe.
var defaultMargin = unit.Dp(10)
// UI holds all of the application state.
type UI struct {
// Theme is used to hold the fonts used throughout the application.
Theme *material.Theme
Timer *Timer
duration widget.Float
reset widget.Clickable
}
// NewUI creates a new UI using the Go Fonts.
func NewUI() *UI {
ui := &UI{}
ui.Theme = material.NewTheme(gofont.Collection())
// start with reasonable defaults.
ui.Timer = NewTimer(5 * time.Second)
ui.duration.Value = 5
return ui
}
// Run handles window events and renders the application.
func (ui *UI) Run(w *app.Window) error {
// start the timer goroutine and ensure it's closed
// when the application closes.
closeTimer := ui.Timer.Start()
defer closeTimer()
var ops op.Ops
for {
select {
// when the timer is updated we should update the screen.
case <-ui.Timer.Updated:
w.Invalidate()
case e := <-w.Events():
// detect the type of the event.
switch e := e.(type) {
// this is sent when the application should re-render.
case system.FrameEvent:
// gtx is used to pass around rendering and event information.
gtx := layout.NewContext(&ops, e)
// render and handle UI.
ui.Layout(gtx)
// render and handle the operations from the UI.
e.Frame(gtx.Ops)
// handle a global key press.
case key.Event:
switch e.Name {
// when we click escape, let's close the window.
case key.NameEscape:
return nil
}
// this is sent when the application is closed.
case system.DestroyEvent:
return e.Err
}
}
}
return nil
}
// Layout displays the main program layout.
func (ui *UI) Layout(gtx layout.Context) layout.Dimensions {
th := ui.Theme
// check whether the reset button was clicked.
if ui.reset.Clicked() {
ui.Timer.Reset()
}
// check whether the slider value has changed.
if ui.duration.Changed() {
ui.Timer.SetDuration(secondsToDuration(float64(ui.duration.Value)))
}
// get the latest information about the timer.
info := ui.Timer.Info()
progress := float32(0)
if info.Duration == 0 {
progress = 1
} else {
progress = float32(info.Progress.Seconds() / info.Duration.Seconds())
}
// inset is used to add padding around the window border.
inset := layout.UniformInset(defaultMargin)
return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(material.Body1(th, "Elapsed Time").Layout),
layout.Rigid(material.ProgressBar(th, progress).Layout),
layout.Rigid(material.Body1(th, info.ProgressString()).Layout),
layout.Rigid(layout.Spacer{Height: th.TextSize}.Layout),
layout.Rigid(material.Body1(th, "Duration").Layout),
layout.Rigid(material.Slider(th, &ui.duration, 0, 15).Layout),
layout.Rigid(layout.Spacer{Height: th.TextSize}.Layout),
layout.Rigid(material.Button(th, &ui.reset, "Reset").Layout),
)
})
}
func secondsToDuration(s float64) time.Duration {
return time.Duration(s * float64(time.Second))
}

142
gui_gio_learn/7gui/timer/timer.go

@ -0,0 +1,142 @@
package main
import (
"context"
"fmt"
"sync"
"time"
)
// Timer implements an
type Timer struct {
// Updated is used to notify UI about changes in the timer.
Updated chan struct{}
// mu locks the state such that it can be modified and accessed
// from multiple goroutines.
mu sync.Mutex
start time.Time // start corresponds to when the timer was started.
now time.Time // now corresponds to the last updated time.
duration time.Duration // duration is the maximum progress.
}
// NewTimer creates a new timer with the specified timer.
func NewTimer(initialDuration time.Duration) *Timer {
return &Timer{
Updated: make(chan struct{}),
duration: initialDuration,
}
}
// Start the timer goroutine and return a cancel func that
// that can be used to stop it.
func (t *Timer) Start() context.CancelFunc {
// initialize the timer state.
now := time.Now()
t.now = now
t.start = now
// we use done to signal stopping the goroutine.
// a context.Context could be also used.
done := make(chan struct{})
go t.run(done)
return func() { close(done) }
}
// run is the main loop for the timer.
func (t *Timer) run(done chan struct{}) {
// we use a time.Ticker to update the state,
// in many cases, this could be a network access instead.
tick := time.NewTicker(50 * time.Millisecond)
defer tick.Stop()
for {
select {
case now := <-tick.C:
t.update(now)
case <-done:
return
}
}
}
// invalidate sends a signal to the UI that
// the internal state has changed.
func (t *Timer) invalidate() {
// we use a non-blocking send, that way the Timer
// can continue updating internally.
select {
case t.Updated <- struct{}{}:
default:
}
}
func (t *Timer) update(now time.Time) {
t.mu.Lock()
defer t.mu.Unlock()
previousNow := t.now
t.now = now
// first check whether we have not exceeded the duration.
// in that case the progress advanced and we need to notify
// about a change.
progressAfter := t.now.Sub(t.start)
if progressAfter <= t.duration {
t.invalidate()
return
}
// when we had progressed beyond the duration we also
// need to update the first time it happens.
progressBefore := previousNow.Sub(t.start)
if progressBefore <= t.duration {
t.invalidate()
return
}
}
// Reset resets timer to the last know time.
func (t *Timer) Reset() {
t.mu.Lock()
defer t.mu.Unlock()
t.start = t.now
t.invalidate()
}
// SetDuration changes the duration of the timer.
func (t *Timer) SetDuration(duration time.Duration) {
t.mu.Lock()
defer t.mu.Unlock()
if t.duration == duration {
return
}
t.duration = duration
t.invalidate()
}
// Info returns the latest know info about the timer.
func (t *Timer) Info() (info Info) {
t.mu.Lock()
defer t.mu.Unlock()
info.Progress = t.now.Sub(t.start)
info.Duration = t.duration
if info.Progress > info.Duration {
info.Progress = info.Duration
}
return info
}
// Info is the information about the timer.
type Info struct {
Progress time.Duration
Duration time.Duration
}
// ProgressString returns the progress formatted as seconds.
func (info *Info) ProgressString() string {
return fmt.Sprintf("%.1fs", info.Progress.Seconds())
}

63
gui_gio_learn/LICENSE

@ -0,0 +1,63 @@
This project is provided under the terms of the UNLICENSE or
the MIT license denoted by the following SPDX identifier:
SPDX-License-Identifier: Unlicense OR MIT
You may use the project under the terms of either license.
Both licenses are reproduced below.
----
The MIT License (MIT)
Copyright (c) 2019 The Gio authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
---
The UNLICENSE
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>
---

21
gui_gio_learn/README.md

@ -0,0 +1,21 @@
# Gio Examples
Example programs for the [Gio project](https://gioui.org).
[![builds.sr.ht status](https://builds.sr.ht/~eliasnaur/gio-example.svg)](https://builds.sr.ht/~eliasnaur/gio-example)
## Issues
File bugs and TODOs through the [issue tracker](https://todo.sr.ht/~eliasnaur/gio) or send an email
to [~eliasnaur/gio@todo.sr.ht](mailto:~eliasnaur/gio@todo.sr.ht). For general discussion, use the
mailing list: [~eliasnaur/gio@lists.sr.ht](mailto:~eliasnaur/gio@lists.sr.ht).
## Contributing
Post discussion to the [mailing list](https://lists.sr.ht/~eliasnaur/gio) and patches to
[gio-patches](https://lists.sr.ht/~eliasnaur/gio-patches). No Sourcehut
account is required and you can post without being subscribed.
See the [contribution guide](https://gioui.org/doc/contribute) for more details.
An [official GitHub mirror](https://github.com/gioui/gio-example) is available.

98
gui_gio_learn/colorpicker/main.go

@ -0,0 +1,98 @@
package main
import (
"image"
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/font/gofont"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/widget/material"
"gioui.org/x/colorpicker"
)
func main() {
go func() {
w := app.NewWindow()
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
type (
C = layout.Context
D = layout.Dimensions
)
var white = color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
func loop(w *app.Window) error {
th := material.NewTheme(gofont.Collection())
background := white
current := color.NRGBA{R: 255, G: 128, B: 75, A: 255}
picker := colorpicker.State{}
picker.SetColor(current)
muxState := colorpicker.NewMuxState(
[]colorpicker.MuxOption{
{
Label: "current",
Value: &current,
},
{
Label: "background",
Value: &th.Palette.Bg,
},
{
Label: "foreground",
Value: &th.Palette.Fg,
},
}...)
background = *muxState.Color()
var ops op.Ops
for {
e := <-w.Events()
switch e := e.(type) {
case system.DestroyEvent:
log.Println("destroyed")
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
if muxState.Changed() {
background = *muxState.Color()
log.Printf("mux changed")
}
if picker.Changed() {
current = picker.Color()
background = *muxState.Color()
log.Printf("picker changed")
}
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return colorpicker.PickerStyle{Label: "Current", Theme: th, State: &picker}.Layout(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return colorpicker.Mux(th, &muxState, "Display Right:").Layout(gtx)
}),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
size := gtx.Constraints.Max
paint.FillShape(gtx.Ops, background, clip.Rect(image.Rectangle{Max: size}).Op())
return D{Size: size}
}),
)
}),
)
e.Frame(gtx.Ops)
}
}
}

43
gui_gio_learn/component/applayout/applayout.go

@ -0,0 +1,43 @@
package applayout
import (
"gioui.org/layout"
"gioui.org/unit"
)
type (
C = layout.Context
D = layout.Dimensions
)
// DetailRow lays out two widgets in a horizontal row, with the left
// widget considered the "Primary" widget.
type DetailRow struct {
// PrimaryWidth is the fraction of the available width that should
// be allocated to the primary widget. It should be in the range
// (0,1.0]. Defaults to 0.3 if not set.
PrimaryWidth float32
// Inset is automatically applied to both widgets. This inset is
// required, and will default to a uniform 8DP inset if not set.
layout.Inset
}
var DefaultInset = layout.UniformInset(unit.Dp(8))
// Layout the DetailRow with the provided widgets.
func (d DetailRow) Layout(gtx C, primary, detail layout.Widget) D {
if d.PrimaryWidth == 0 {
d.PrimaryWidth = 0.3
}
if d.Inset == (layout.Inset{}) {
d.Inset = DefaultInset
}
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Flexed(d.PrimaryWidth, func(gtx C) D {
return d.Inset.Layout(gtx, primary)
}),
layout.Flexed(1-d.PrimaryWidth, func(gtx C) D {
return d.Inset.Layout(gtx, detail)
}),
)
}

66
gui_gio_learn/component/icon/icons.go

@ -0,0 +1,66 @@
package icon
import (
"gioui.org/widget"
"golang.org/x/exp/shiny/materialdesign/icons"
)
var MenuIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.NavigationMenu)
return icon
}()
var RestaurantMenuIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.MapsRestaurantMenu)
return icon
}()
var AccountBalanceIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ActionAccountBalance)
return icon
}()
var AccountBoxIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ActionAccountBox)
return icon
}()
var CartIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ActionAddShoppingCart)
return icon
}()
var HomeIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ActionHome)
return icon
}()
var SettingsIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ActionSettings)
return icon
}()
var OtherIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ActionHelp)
return icon
}()
var HeartIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ActionFavorite)
return icon
}()
var PlusIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ContentAdd)
return icon
}()
var EditIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ContentCreate)
return icon
}()
var VisibilityIcon *widget.Icon = func() *widget.Icon {
icon, _ := widget.NewIcon(icons.ActionVisibility)
return icon
}()

65
gui_gio_learn/component/main.go

@ -0,0 +1,65 @@
package main
import (
"flag"
"log"
"os"
"gioui.org/app"
page "gioui.org/example/component/pages"
"gioui.org/example/component/pages/about"
"gioui.org/example/component/pages/appbar"
"gioui.org/example/component/pages/discloser"
"gioui.org/example/component/pages/menu"
"gioui.org/example/component/pages/navdrawer"
"gioui.org/example/component/pages/textfield"
"gioui.org/font/gofont"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/widget/material"
)
type (
C = layout.Context
D = layout.Dimensions
)
func main() {
flag.Parse()
go func() {
w := app.NewWindow()
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func loop(w *app.Window) error {
th := material.NewTheme(gofont.Collection())
var ops op.Ops
router := page.NewRouter()
router.Register(0, appbar.New(&router))
router.Register(1, navdrawer.New(&router))
router.Register(2, textfield.New(&router))
router.Register(3, menu.New(&router))
router.Register(4, discloser.New(&router))
router.Register(5, about.New(&router))
for {
select {
case e := <-w.Events():
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
router.Layout(gtx, th)
e.Frame(gtx.Ops)
}
}
}
}

103
gui_gio_learn/component/pages/about/about.go

@ -0,0 +1,103 @@
package about
import (
"gioui.org/io/clipboard"
"gioui.org/layout"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
alo "gioui.org/example/component/applayout"
"gioui.org/example/component/icon"
page "gioui.org/example/component/pages"
)
type (
C = layout.Context
D = layout.Dimensions
)
// Page holds the state for a page demonstrating the features of
// the AppBar component.
type Page struct {
eliasCopyButton, chrisCopyButtonGH, chrisCopyButtonLP widget.Clickable
widget.List
*page.Router
}
// New constructs a Page with the provided router.
func New(router *page.Router) *Page {
return &Page{
Router: router,
}
}
var _ page.Page = &Page{}
func (p *Page) Actions() []component.AppBarAction {
return []component.AppBarAction{}
}
func (p *Page) Overflow() []component.OverflowAction {
return []component.OverflowAction{}
}
func (p *Page) NavItem() component.NavItem {
return component.NavItem{
Name: "About this library",
Icon: icon.OtherIcon,
}
}
const (
sponsorEliasURL = "https://github.com/sponsors/eliasnaur"
sponsorChrisURLGitHub = "https://github.com/sponsors/whereswaldon"
sponsorChrisURLLiberapay = "https://liberapay.com/whereswaldon/"
)
func (p *Page) Layout(gtx C, th *material.Theme) D {
p.List.Axis = layout.Vertical
return material.List(th, &p.List).Layout(gtx, 1, func(gtx C, _ int) D {
return layout.Flex{
Alignment: layout.Middle,
Axis: layout.Vertical,
}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return alo.DefaultInset.Layout(gtx, material.Body1(th, `This library implements material design components from https://material.io using https://gioui.org.
If you like this library and work like it, please consider sponsoring Elias and/or Chris!`).Layout)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Elias Naur can be sponsored on GitHub at "+sponsorEliasURL).Layout,
func(gtx C) D {
if p.eliasCopyButton.Clicked() {
clipboard.WriteOp{
Text: sponsorEliasURL,
}.Add(gtx.Ops)
}
return material.Button(th, &p.eliasCopyButton, "Copy Sponsorship URL").Layout(gtx)
})
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Chris Waldon can be sponsored on GitHub at "+sponsorChrisURLGitHub+" and on Liberapay at "+sponsorChrisURLLiberapay).Layout,
func(gtx C) D {
if p.chrisCopyButtonGH.Clicked() {
clipboard.WriteOp{Text: sponsorChrisURLGitHub}.Add(gtx.Ops)
}
if p.chrisCopyButtonLP.Clicked() {
clipboard.WriteOp{Text: sponsorChrisURLLiberapay}.Add(gtx.Ops)
}
return alo.DefaultInset.Layout(gtx, func(gtx C) D {
return layout.Flex{}.Layout(gtx,
layout.Flexed(.5, material.Button(th, &p.chrisCopyButtonGH, "Copy GitHub URL").Layout),
layout.Flexed(.5, material.Button(th, &p.chrisCopyButtonLP, "Copy Liberapay URL").Layout),
)
})
})
}),
)
})
}

181
gui_gio_learn/component/pages/appbar/appbar.go

@ -0,0 +1,181 @@
package appbar
import (
"image/color"
"gioui.org/layout"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
alo "gioui.org/example/component/applayout"
"gioui.org/example/component/icon"
page "gioui.org/example/component/pages"
)
type (
C = layout.Context
D = layout.Dimensions
)
// Page holds the state for a page demonstrating the features of
// the AppBar component.
type Page struct {
heartBtn, plusBtn, contextBtn widget.Clickable
exampleOverflowState, red, green, blue widget.Clickable
bottomBar, customNavIcon widget.Bool
favorited bool
widget.List
*page.Router
}
// New constructs a Page with the provided router.
func New(router *page.Router) *Page {
return &Page{
Router: router,
}
}
var _ page.Page = &Page{}
func (p *Page) Actions() []component.AppBarAction {
return []component.AppBarAction{
{
OverflowAction: component.OverflowAction{
Name: "Favorite",
Tag: &p.heartBtn,
},
Layout: func(gtx layout.Context, bg, fg color.NRGBA) layout.Dimensions {
if p.heartBtn.Clicked() {
p.favorited = !p.favorited
}
btn := component.SimpleIconButton(bg, fg, &p.heartBtn, icon.HeartIcon)
btn.Background = bg
if p.favorited {
btn.Color = color.NRGBA{R: 200, A: 255}
} else {
btn.Color = fg
}
return btn.Layout(gtx)
},
},
component.SimpleIconAction(&p.plusBtn, icon.PlusIcon,
component.OverflowAction{
Name: "Create",
Tag: &p.plusBtn,
},
),
}
}
func (p *Page) Overflow() []component.OverflowAction {
return []component.OverflowAction{
{
Name: "Example 1",
Tag: &p.exampleOverflowState,
},
{
Name: "Example 2",
Tag: &p.exampleOverflowState,
},
}
}
func (p *Page) NavItem() component.NavItem {
return component.NavItem{
Name: "App Bar Features",
Icon: icon.HomeIcon,
}
}
const (
settingNameColumnWidth = .3
settingDetailsColumnWidth = 1 - settingNameColumnWidth
)
func (p *Page) Layout(gtx C, th *material.Theme) D {
p.List.Axis = layout.Vertical
return material.List(th, &p.List).Layout(gtx, 1, func(gtx C, _ int) D {
return layout.Flex{
Alignment: layout.Middle,
Axis: layout.Vertical,
}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return alo.DefaultInset.Layout(gtx, material.Body1(th, `The app bar widget provides a consistent interface element for triggering navigation and page-specific actions.
The controls below allow you to see the various features available in our App Bar implementation.`).Layout)
}),
layout.Rigid(func(gtx C) D {
return alo.DetailRow{}.Layout(gtx, material.Body1(th, "Contextual App Bar").Layout, func(gtx C) D {
if p.contextBtn.Clicked() {
p.Router.AppBar.SetContextualActions(
[]component.AppBarAction{
component.SimpleIconAction(&p.red, icon.HeartIcon,
component.OverflowAction{
Name: "House",
Tag: &p.red,
},
),
},
[]component.OverflowAction{
{
Name: "foo",
Tag: &p.blue,
},
{
Name: "bar",
Tag: &p.green,
},
},
)
p.Router.AppBar.ToggleContextual(gtx.Now, "Contextual Title")
}
return material.Button(th, &p.contextBtn, "Trigger").Layout(gtx)
})
}),
layout.Rigid(func(gtx C) D {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Bottom App Bar").Layout,
func(gtx C) D {
if p.bottomBar.Changed() {
if p.bottomBar.Value {
p.Router.ModalNavDrawer.Anchor = component.Bottom
p.Router.AppBar.Anchor = component.Bottom
} else {
p.Router.ModalNavDrawer.Anchor = component.Top
p.Router.AppBar.Anchor = component.Top
}
p.Router.BottomBar = p.bottomBar.Value
}
return material.Switch(th, &p.bottomBar, "Use Bottom App Bar").Layout(gtx)
})
}),
layout.Rigid(func(gtx C) D {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Custom Navigation Icon").Layout,
func(gtx C) D {
if p.customNavIcon.Changed() {
if p.customNavIcon.Value {
p.Router.AppBar.NavigationIcon = icon.HomeIcon
} else {
p.Router.AppBar.NavigationIcon = icon.MenuIcon
}
}
return material.Switch(th, &p.customNavIcon, "Use Custom Navigation Icon").Layout(gtx)
})
}),
layout.Rigid(func(gtx C) D {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Animated Resize").Layout,
material.Body2(th, "Resize the width of your screen to see app bar actions collapse into or emerge from the overflow menu (as size permits).").Layout,
)
}),
layout.Rigid(func(gtx C) D {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Custom Action Buttons").Layout,
material.Body2(th, "Click the heart action to see custom button behavior.").Layout)
}),
)
})
}

136
gui_gio_learn/component/pages/discloser/discloser.go

@ -0,0 +1,136 @@
package discloser
import (
"gioui.org/layout"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
"gioui.org/example/component/icon"
page "gioui.org/example/component/pages"
)
// TreeNode is a simple tree implementation that holds both
// display data and the state for Discloser widgets. In
// practice, you'll often want to separate the state from
// the data being presented.
type TreeNode struct {
Text string
Children []TreeNode
component.DiscloserState
}
type (
C = layout.Context
D = layout.Dimensions
)
// Page holds the state for a page demonstrating the features of
// the AppBar component.
type Page struct {
TreeNode
widget.List
*page.Router
CustomDiscloserState component.DiscloserState
}
// New constructs a Page with the provided router.
func New(router *page.Router) *Page {
return &Page{
Router: router,
TreeNode: TreeNode{
Text: "Expand Me",
Children: []TreeNode{
{
Text: "Disclosers can be (expand me)...",
Children: []TreeNode{
{
Text: "...nested to arbitrary depths.",
},
{
Text: "There are also types available to customize the look and feel of the discloser:",
Children: []TreeNode{
{
Text: "• DiscloserStyle lets you provide your own control instead of the default triangle used here.",
},
{
Text: "• DiscloserArrowStyle lets you alter the presentation of the triangle used here, like changing its color, size, left/right anchoring, or margin.",
},
},
},
},
},
},
},
}
}
var _ page.Page = &Page{}
func (p *Page) Actions() []component.AppBarAction {
return []component.AppBarAction{}
}
func (p *Page) Overflow() []component.OverflowAction {
return []component.OverflowAction{}
}
func (p *Page) NavItem() component.NavItem {
return component.NavItem{
Name: "Disclosers",
Icon: icon.VisibilityIcon,
}
}
// LayoutTreeNode recursively lays out a tree of widgets described by
// TreeNodes.
func (p *Page) LayoutTreeNode(gtx C, th *material.Theme, tn *TreeNode) D {
if len(tn.Children) == 0 {
return layout.UniformInset(unit.Dp(2)).Layout(gtx,
material.Body1(th, tn.Text).Layout)
}
children := make([]layout.FlexChild, 0, len(tn.Children))
for i := range tn.Children {
child := &tn.Children[i]
children = append(children, layout.Rigid(
func(gtx C) D {
return p.LayoutTreeNode(gtx, th, child)
}))
}
return component.SimpleDiscloser(th, &tn.DiscloserState).Layout(gtx,
material.Body1(th, tn.Text).Layout,
func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
})
}
// LayoutCustomDiscloser demonstrates how to create a custom control for
// a discloser.
func (p *Page) LayoutCustomDiscloser(gtx C, th *material.Theme) D {
return component.Discloser(th, &p.CustomDiscloserState).Layout(gtx,
func(gtx C) D {
var l material.LabelStyle
l = material.Body1(th, "+")
if p.CustomDiscloserState.Visible() {
l.Text = "-"
}
l.Font.Variant = "Mono"
return layout.UniformInset(unit.Dp(2)).Layout(gtx, l.Layout)
},
material.Body1(th, "Custom Control").Layout,
material.Body2(th, "This control only took 9 lines of code.").Layout,
)
}
func (p *Page) Layout(gtx C, th *material.Theme) D {
p.List.Axis = layout.Vertical
return material.List(th, &p.List).Layout(gtx, 2, func(gtx C, index int) D {
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
if index == 0 {
return p.LayoutTreeNode(gtx, th, &p.TreeNode)
}
return p.LayoutCustomDiscloser(gtx, th)
})
})
}

185
gui_gio_learn/component/pages/menu/menu.go

@ -0,0 +1,185 @@
package menu
import (
"fmt"
"image"
"image/color"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
"gioui.org/example/component/icon"
page "gioui.org/example/component/pages"
)
type (
C = layout.Context
D = layout.Dimensions
)
// Page holds the state for a page demonstrating the features of
// the Menu component.
type Page struct {
redButton, greenButton, blueButton widget.Clickable
balanceButton, accountButton, cartButton widget.Clickable
leftFillColor color.NRGBA
leftContextArea component.ContextArea
leftMenu, rightMenu component.MenuState
menuInit bool
menuDemoList layout.List
menuDemoListStates []component.ContextArea
widget.List
*page.Router
}
// New constructs a Page with the provided router.
func New(router *page.Router) *Page {
return &Page{
Router: router,
}
}
var _ page.Page = &Page{}
func (p *Page) Actions() []component.AppBarAction {
return []component.AppBarAction{}
}
func (p *Page) Overflow() []component.OverflowAction {
return []component.OverflowAction{}
}
func (p *Page) NavItem() component.NavItem {
return component.NavItem{
Name: "Menu Features",
Icon: icon.RestaurantMenuIcon,
}
}
func (p *Page) Layout(gtx C, th *material.Theme) D {
p.List.Axis = layout.Vertical
return material.List(th, &p.List).Layout(gtx, 1, func(gtx C, _ int) D {
if !p.menuInit {
p.leftMenu = component.MenuState{
Options: []func(gtx C) D{
func(gtx C) D {
return layout.Inset{
Left: unit.Dp(16),
Right: unit.Dp(16),
}.Layout(gtx, material.Body1(th, "Menus support arbitrary widgets.\nThis is just a label!\nHere's a loader:").Layout)
},
component.Divider(th).Layout,
func(gtx C) D {
return layout.Inset{
Top: unit.Dp(4),
Bottom: unit.Dp(4),
Left: unit.Dp(16),
Right: unit.Dp(16),
}.Layout(gtx, func(gtx C) D {
gtx.Constraints.Max.X = gtx.Px(unit.Dp(24))
gtx.Constraints.Max.Y = gtx.Px(unit.Dp(24))
return material.Loader(th).Layout(gtx)
})
},
component.SubheadingDivider(th, "Colors").Layout,
component.MenuItem(th, &p.redButton, "Red").Layout,
component.MenuItem(th, &p.greenButton, "Green").Layout,
component.MenuItem(th, &p.blueButton, "Blue").Layout,
},
}
p.rightMenu = component.MenuState{
Options: []func(gtx C) D{
func(gtx C) D {
item := component.MenuItem(th, &p.balanceButton, "Balance")
item.Icon = icon.AccountBalanceIcon
item.Hint = component.MenuHintText(th, "Hint")
return item.Layout(gtx)
},
func(gtx C) D {
item := component.MenuItem(th, &p.accountButton, "Account")
item.Icon = icon.AccountBoxIcon
item.Hint = component.MenuHintText(th, "Hint")
return item.Layout(gtx)
},
func(gtx C) D {
item := component.MenuItem(th, &p.cartButton, "Cart")
item.Icon = icon.CartIcon
item.Hint = component.MenuHintText(th, "Hint")
return item.Layout(gtx)
},
},
}
}
if p.redButton.Clicked() {
p.leftFillColor = color.NRGBA{R: 200, A: 255}
}
if p.greenButton.Clicked() {
p.leftFillColor = color.NRGBA{G: 200, A: 255}
}
if p.blueButton.Clicked() {
p.leftFillColor = color.NRGBA{B: 200, A: 255}
}
return layout.Flex{}.Layout(gtx,
layout.Flexed(.5, func(gtx C) D {
return widget.Border{
Color: color.NRGBA{A: 255},
Width: unit.Dp(2),
}.Layout(gtx, func(gtx C) D {
return layout.Stack{}.Layout(gtx,
layout.Stacked(func(gtx C) D {
max := image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.X)
rect := image.Rectangle{
Max: max,
}
paint.FillShape(gtx.Ops, p.leftFillColor, clip.Rect(rect).Op())
return D{Size: max}
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(12)).Layout(gtx, func(gtx C) D {
return component.Surface(th).Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Dp(12)).Layout(gtx, material.Body1(th, "Right-click anywhere in this region").Layout)
})
})
}),
layout.Expanded(func(gtx C) D {
return p.leftContextArea.Layout(gtx, func(gtx C) D {
gtx.Constraints.Min = image.Point{}
return component.Menu(th, &p.leftMenu).Layout(gtx)
})
}),
)
})
}),
layout.Flexed(.5, func(gtx C) D {
gtx.Constraints.Max.Y = gtx.Constraints.Max.X
return layout.UniformInset(unit.Dp(12)).Layout(gtx, func(gtx C) D {
p.menuDemoList.Axis = layout.Vertical
return p.menuDemoList.Layout(gtx, 30, func(gtx C, index int) D {
if len(p.menuDemoListStates) < index+1 {
p.menuDemoListStates = append(p.menuDemoListStates, component.ContextArea{})
}
state := &p.menuDemoListStates[index]
return layout.Stack{}.Layout(gtx,
layout.Stacked(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return layout.UniformInset(unit.Dp(8)).Layout(gtx, material.Body1(th, fmt.Sprintf("Item %d", index)).Layout)
}),
layout.Expanded(func(gtx C) D {
return state.Layout(gtx, func(gtx C) D {
gtx.Constraints.Min.X = 0
return component.Menu(th, &p.rightMenu).Layout(gtx)
})
}),
)
})
})
}),
)
})
}

95
gui_gio_learn/component/pages/navdrawer/navdrawer.go

@ -0,0 +1,95 @@
package navdrawer
import (
"gioui.org/layout"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
alo "gioui.org/example/component/applayout"
"gioui.org/example/component/icon"
page "gioui.org/example/component/pages"
)
type (
C = layout.Context
D = layout.Dimensions
)
// Page holds the state for a page demonstrating the features of
// the NavDrawer component.
type Page struct {
nonModalDrawer widget.Bool
widget.List
*page.Router
}
// New constructs a Page with the provided router.
func New(router *page.Router) *Page {
return &Page{
Router: router,
}
}
var _ page.Page = &Page{}
func (p *Page) Actions() []component.AppBarAction {
return []component.AppBarAction{}
}
func (p *Page) Overflow() []component.OverflowAction {
return []component.OverflowAction{}
}
func (p *Page) NavItem() component.NavItem {
return component.NavItem{
Name: "Nav Drawer Features",
Icon: icon.SettingsIcon,
}
}
func (p *Page) Layout(gtx C, th *material.Theme) D {
p.List.Axis = layout.Vertical
return material.List(th, &p.List).Layout(gtx, 1, func(gtx C, _ int) D {
return layout.Flex{
Alignment: layout.Middle,
Axis: layout.Vertical,
}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return alo.DefaultInset.Layout(gtx, material.Body1(th, `The nav drawer widget provides a consistent interface element for navigation.
The controls below allow you to see the various features available in our Navigation Drawer implementation.`).Layout)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Use non-modal drawer").Layout,
func(gtx C) D {
if p.nonModalDrawer.Changed() {
p.Router.NonModalDrawer = p.nonModalDrawer.Value
if p.nonModalDrawer.Value {
p.Router.NavAnim.Appear(gtx.Now)
} else {
p.Router.NavAnim.Disappear(gtx.Now)
}
}
return material.Switch(th, &p.nonModalDrawer, "Use Non-Modal Navigation Drawer").Layout(gtx)
})
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Drag to Close").Layout,
material.Body2(th, "You can close the modal nav drawer by dragging it to the left.").Layout)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Touch Scrim to Close").Layout,
material.Body2(th, "You can close the modal nav drawer touching anywhere in the translucent scrim to the right.").Layout)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return alo.DetailRow{}.Layout(gtx,
material.Body1(th, "Bottom content anchoring").Layout,
material.Body2(th, "If you toggle support for the bottom app bar in the App Bar settings, nav drawer content will anchor to the bottom of the drawer area instead of the top.").Layout)
}),
)
})
}

118
gui_gio_learn/component/pages/page.go

@ -0,0 +1,118 @@
package page
import (
"log"
"time"
"gioui.org/example/component/icon"
"gioui.org/layout"
"gioui.org/op/paint"
"gioui.org/widget/material"
"gioui.org/x/component"
)
type Page interface {
Actions() []component.AppBarAction
Overflow() []component.OverflowAction
Layout(gtx layout.Context, th *material.Theme) layout.Dimensions
NavItem() component.NavItem
}
type Router struct {
pages map[interface{}]Page
current interface{}
*component.ModalNavDrawer
NavAnim component.VisibilityAnimation
*component.AppBar
*component.ModalLayer
NonModalDrawer, BottomBar bool
}
func NewRouter() Router {
modal := component.NewModal()
nav := component.NewNav("Navigation Drawer", "This is an example.")
modalNav := component.ModalNavFrom(&nav, modal)
bar := component.NewAppBar(modal)
bar.NavigationIcon = icon.MenuIcon
na := component.VisibilityAnimation{
State: component.Invisible,
Duration: time.Millisecond * 250,
}
return Router{
pages: make(map[interface{}]Page),
ModalLayer: modal,
ModalNavDrawer: modalNav,
AppBar: bar,
NavAnim: na,
}
}
func (r *Router) Register(tag interface{}, p Page) {
r.pages[tag] = p
navItem := p.NavItem()
navItem.Tag = tag
if r.current == interface{}(nil) {
r.current = tag
r.AppBar.Title = navItem.Name
r.AppBar.SetActions(p.Actions(), p.Overflow())
}
r.ModalNavDrawer.AddNavItem(navItem)
}
func (r *Router) SwitchTo(tag interface{}) {
p, ok := r.pages[tag]
if !ok {
return
}
navItem := p.NavItem()
r.current = tag
r.AppBar.Title = navItem.Name
r.AppBar.SetActions(p.Actions(), p.Overflow())
}
func (r *Router) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
for _, event := range r.AppBar.Events(gtx) {
switch event := event.(type) {
case component.AppBarNavigationClicked:
if r.NonModalDrawer {
r.NavAnim.ToggleVisibility(gtx.Now)
} else {
r.ModalNavDrawer.Appear(gtx.Now)
r.NavAnim.Disappear(gtx.Now)
}
case component.AppBarContextMenuDismissed:
log.Printf("Context menu dismissed: %v", event)
case component.AppBarOverflowActionClicked:
log.Printf("Overflow action selected: %v", event)
}
}
if r.ModalNavDrawer.NavDestinationChanged() {
r.SwitchTo(r.ModalNavDrawer.CurrentNavDestination())
}
paint.Fill(gtx.Ops, th.Palette.Bg)
content := layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Max.X /= 3
return r.NavDrawer.Layout(gtx, th, &r.NavAnim)
}),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return r.pages[r.current].Layout(gtx, th)
}),
)
})
bar := layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return r.AppBar.Layout(gtx, th, "Menu", "Actions")
})
flex := layout.Flex{Axis: layout.Vertical}
if r.BottomBar {
flex.Layout(gtx, content, bar)
} else {
flex.Layout(gtx, bar, content)
}
r.ModalLayer.Layout(gtx, th)
return layout.Dimensions{Size: gtx.Constraints.Max}
}

194
gui_gio_learn/component/pages/textfield/textfield.go

@ -0,0 +1,194 @@
package textfield
import (
"image/color"
"unicode"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
alo "gioui.org/example/component/applayout"
"gioui.org/example/component/icon"
page "gioui.org/example/component/pages"
)
type (
C = layout.Context
D = layout.Dimensions
)
// Page holds the state for a page demonstrating the features of
// the TextField component.
type Page struct {
inputAlignment layout.Alignment
inputAlignmentEnum widget.Enum
nameInput, addressInput, priceInput, tweetInput, numberInput component.TextField
widget.List
*page.Router
}
// New constructs a Page with the provided router.
func New(router *page.Router) *Page {
return &Page{
Router: router,
}
}
var _ page.Page = &Page{}
func (p *Page) Actions() []component.AppBarAction {
return []component.AppBarAction{}
}
func (p *Page) Overflow() []component.OverflowAction {
return []component.OverflowAction{}
}
func (p *Page) NavItem() component.NavItem {
return component.NavItem{
Name: "Text Field Features",
Icon: icon.EditIcon,
}
}
func (p *Page) Layout(gtx C, th *material.Theme) D {
p.List.Axis = layout.Vertical
return material.List(th, &p.List).Layout(gtx, 1, func(gtx C, _ int) D {
return layout.Flex{
Axis: layout.Vertical,
}.Layout(
gtx,
layout.Rigid(func(gtx C) D {
p.nameInput.Alignment = p.inputAlignment
return p.nameInput.Layout(gtx, th, "Name")
}),
layout.Rigid(func(gtx C) D {
return alo.DefaultInset.Layout(gtx, material.Body2(th, "Responds to hover events.").Layout)
}),
layout.Rigid(func(gtx C) D {
p.addressInput.Alignment = p.inputAlignment
return p.addressInput.Layout(gtx, th, "Address")
}),
layout.Rigid(func(gtx C) D {
return alo.DefaultInset.Layout(gtx, material.Body2(th, "Label animates properly when you click to select the text field.").Layout)
}),
layout.Rigid(func(gtx C) D {
p.priceInput.Prefix = func(gtx C) D {
th := *th
th.Palette.Fg = color.NRGBA{R: 100, G: 100, B: 100, A: 255}
return material.Label(&th, th.TextSize, "$").Layout(gtx)
}
p.priceInput.Suffix = func(gtx C) D {
th := *th
th.Palette.Fg = color.NRGBA{R: 100, G: 100, B: 100, A: 255}
return material.Label(&th, th.TextSize, ".00").Layout(gtx)
}
p.priceInput.SingleLine = true
p.priceInput.Alignment = p.inputAlignment
return p.priceInput.Layout(gtx, th, "Price")
}),
layout.Rigid(func(gtx C) D {
return alo.DefaultInset.Layout(gtx, material.Body2(th, "Can have prefix and suffix elements.").Layout)
}),
layout.Rigid(func(gtx C) D {
if err := func() string {
for _, r := range p.numberInput.Text() {
if !unicode.IsDigit(r) {
return "Must contain only digits"
}
}
return ""
}(); err != "" {
p.numberInput.SetError(err)
} else {
p.numberInput.ClearError()
}
p.numberInput.SingleLine = true
p.numberInput.Alignment = p.inputAlignment
return p.numberInput.Layout(gtx, th, "Number")
}),
layout.Rigid(func(gtx C) D {
return alo.DefaultInset.Layout(gtx, material.Body2(th, "Can be validated.").Layout)
}),
layout.Rigid(func(gtx C) D {
if p.tweetInput.TextTooLong() {
p.tweetInput.SetError("Too many characters")
} else {
p.tweetInput.ClearError()
}
p.tweetInput.CharLimit = 128
p.tweetInput.Helper = "Tweets have a limited character count"
p.tweetInput.Alignment = p.inputAlignment
return p.tweetInput.Layout(gtx, th, "Tweet")
}),
layout.Rigid(func(gtx C) D {
return alo.DefaultInset.Layout(gtx, material.Body2(th, "Can have a character counter and help text.").Layout)
}),
layout.Rigid(func(gtx C) D {
if p.inputAlignmentEnum.Changed() {
switch p.inputAlignmentEnum.Value {
case layout.Start.String():
p.inputAlignment = layout.Start
case layout.Middle.String():
p.inputAlignment = layout.Middle
case layout.End.String():
p.inputAlignment = layout.End
default:
p.inputAlignment = layout.Start
}
op.InvalidateOp{}.Add(gtx.Ops)
}
return alo.DefaultInset.Layout(
gtx,
func(gtx C) D {
return layout.Flex{
Axis: layout.Vertical,
}.Layout(
gtx,
layout.Rigid(func(gtx C) D {
return material.Body2(th, "Text Alignment").Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
return layout.Flex{
Axis: layout.Vertical,
}.Layout(
gtx,
layout.Rigid(func(gtx C) D {
return material.RadioButton(
th,
&p.inputAlignmentEnum,
layout.Start.String(),
"Start",
).Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
return material.RadioButton(
th,
&p.inputAlignmentEnum,
layout.Middle.String(),
"Middle",
).Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
return material.RadioButton(
th,
&p.inputAlignmentEnum,
layout.End.String(),
"End",
).Layout(gtx)
}),
)
}),
)
},
)
}),
layout.Rigid(func(gtx C) D {
return alo.DefaultInset.Layout(gtx, material.Body2(th, "This text field implementation was contributed by Jack Mordaunt. Thanks Jack!").Layout)
}),
)
})
}

158
gui_gio_learn/explorer/main.go

@ -0,0 +1,158 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
// A simple Gio program. See https://gioui.org for more information.
import (
"fmt"
"image"
"image/jpeg"
_ "image/jpeg"
"image/png"
_ "image/png"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/paint"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/font/gofont"
"gioui.org/x/explorer"
)
func main() {
go func() {
w := app.NewWindow()
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
type (
C = layout.Context
D = layout.Dimensions
)
// ImageResult is the results of trying to open an image. It may
// contain either an error or an image, but not both. The error
// should always be checked first.
type ImageResult struct {
Error error
Format string
Image image.Image
}
func loop(w *app.Window) error {
expl := explorer.NewExplorer(w)
var openBtn, saveBtn widget.Clickable
th := material.NewTheme(gofont.Collection())
imgChan := make(chan ImageResult)
saveChan := make(chan error)
var img ImageResult
var saveErr error
var ops op.Ops
for {
select {
case img = <-imgChan:
w.Invalidate()
case saveErr = <-saveChan:
w.Invalidate()
case e := <-w.Events():
expl.ListenEvents(e)
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
if openBtn.Clicked() {
go func() {
file, err := expl.ChooseFile("png", "jpeg", "jpg")
if err != nil {
err = fmt.Errorf("failed opening image file: %w", err)
imgChan <- ImageResult{Error: err}
return
}
defer file.Close()
imgData, format, err := image.Decode(file)
if err != nil {
err = fmt.Errorf("failed decoding image data: %w", err)
imgChan <- ImageResult{Error: err}
return
}
imgChan <- ImageResult{Image: imgData, Format: format}
}()
}
if saveBtn.Clicked() {
go func(img ImageResult) {
if img.Error != nil {
saveChan <- fmt.Errorf("no image loaded, cannot save")
return
}
extension := "jpg"
switch img.Format {
case "png":
extension = "png"
}
file, err := expl.CreateFile("file." + extension)
if err != nil {
saveChan <- fmt.Errorf("failed exporting image file: %w", err)
return
}
defer func() {
saveChan <- file.Close()
}()
switch extension {
case "jpg":
if err := jpeg.Encode(file, img.Image, nil); err != nil {
saveChan <- fmt.Errorf("failed encoding image file: %w", err)
return
}
case "png":
if err := png.Encode(file, img.Image); err != nil {
saveChan <- fmt.Errorf("failed encoding image file: %w", err)
return
}
}
}(img)
}
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(material.Button(th, &openBtn, "Open Image").Layout),
layout.Flexed(1, func(gtx C) D {
if img.Error == nil && img.Image == nil {
return D{}
} else if img.Error != nil {
return material.H6(th, img.Error.Error()).Layout(gtx)
}
return widget.Image{
Src: paint.NewImageOp(img.Image),
Fit: widget.Contain,
}.Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
if img.Image == nil {
gtx.Queue = nil
}
return material.Button(th, &saveBtn, "Save Image").Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
if saveErr == nil {
return D{}
}
return material.H6(th, saveErr.Error()).Layout(gtx)
}),
)
e.Frame(gtx.Ops)
}
}
}
}

88
gui_gio_learn/galaxy/galaxy.go

@ -0,0 +1,88 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"log"
"golang.org/x/exp/rand"
"gonum.org/v1/gonum/spatial/barneshut"
"gonum.org/v1/gonum/spatial/r2"
)
type mass struct {
d r2.Vec // position
v r2.Vec // velocity
m float64 // mass
}
func (m *mass) Coord2() r2.Vec { return m.d }
func (m *mass) Mass() float64 { return m.m }
func (m *mass) move(f r2.Vec) {
// F = ma
f.X /= m.m
f.Y /= m.m
m.v = m.v.Add(f)
// Update position.
m.d = m.d.Add(m.v)
}
func galaxy(numStars int, rnd *rand.Rand) ([]*mass, barneshut.Plane) {
// Make 50 stars in random locations and velocities.
stars := make([]*mass, numStars)
p := make([]barneshut.Particle2, len(stars))
for i := range stars {
s := &mass{
d: r2.Vec{
X: 100*rnd.Float64() - 50,
Y: 100*rnd.Float64() - 50,
},
m: rnd.Float64(),
}
// Aim at the ground and miss.
s.d = s.d.Scale(-1).Add(r2.Vec{
X: 10 * rnd.NormFloat64(),
Y: 10 * rnd.NormFloat64(),
})
stars[i] = s
p[i] = s
}
// Make a plane to calculate approximate forces
plane := barneshut.Plane{Particles: p}
return stars, plane
}
func simulate(stars []*mass, plane barneshut.Plane, dist *distribution) {
vectors := make([]r2.Vec, len(stars))
// Build the data structure. For small systems
// this step may be omitted and ForceOn will
// perform the naive quadratic calculation
// without building the data structure.
err := plane.Reset()
if err != nil {
log.Fatal(err)
}
// Calculate the force vectors using the theta
// parameter.
const theta = 0.1
// and an imaginary gravitational constant.
const G = 10
for j, s := range stars {
vectors[j] = plane.ForceOn(s, theta, barneshut.Gravity2).Scale(G)
}
// Update positions.
for j, s := range stars {
s.move(vectors[j])
}
// Recompute the distribution of stars
dist.Update(stars)
dist.EnsureSquare()
}

378
gui_gio_learn/galaxy/main.go

@ -0,0 +1,378 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"fmt"
"image"
"image/color"
"log"
"math"
"strconv"
"time"
"golang.org/x/exp/rand"
"golang.org/x/exp/shiny/materialdesign/icons"
"gonum.org/v1/gonum/spatial/r2"
"gioui.org/app"
"gioui.org/f32"
"gioui.org/font/gofont"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
)
// distribution tracks useful minimum and maximum information about
// the stars.
type distribution struct {
min, max r2.Vec
maxSpeed float64
meanSpeed float64
minMass, maxMass float64
speedSum float64
speedSamples int
}
// Update ensures that the distribution contains accurate min/max
// data for the slice of stars provided.
func (d *distribution) Update(stars []*mass) {
var (
speedSum float64
speedSamples int
)
for i, s := range stars {
speed := distance(s.v, s.d)
if i == 0 {
d.minMass = s.m
}
if s.d.X < d.min.X {
d.min.X = s.d.X
}
if s.d.Y < d.min.Y {
d.min.Y = s.d.Y
}
if s.d.X > d.max.X {
d.max.X = s.d.X
}
if s.d.Y > d.max.Y {
d.max.Y = s.d.Y
}
if s.m > d.maxMass {
d.maxMass = s.m
}
if s.m < d.minMass {
d.minMass = s.m
}
if speed > d.maxSpeed {
d.maxSpeed = speed
}
speedSamples++
speedSum += speed
}
d.meanSpeed = speedSum / float64(speedSamples)
}
// EnsureSquare adjusts the distribution so that the min and max
// coordinates are the corners of a square (by padding one axis
// equally across the top and bottom). This helps to prevent visual
// distortion during the visualization, though it does not stop it
// completely.
func (d *distribution) EnsureSquare() {
diff := d.max.Sub(d.min)
if diff.X > diff.Y {
padding := (diff.X - diff.Y) / 2
d.max.Y += padding
d.min.Y -= padding
} else if diff.Y > diff.X {
padding := (diff.Y - diff.X) / 2
d.max.X += padding
d.min.X -= padding
}
}
// String describes the distribution in text form.
func (d distribution) String() string {
return fmt.Sprintf("distance: (min: %v max: %v), mass: (min: %v, max: %v)", d.min, d.max, d.minMass, d.maxMass)
}
// Scale uses the min/max data within the distribution to compute the
// position, speed, and size of the star.
func (d distribution) Scale(star *mass) Star {
s := Star{}
s.X = float32((star.d.X - d.min.X) / (d.max.X - d.min.X))
s.Y = float32((star.d.Y - d.min.Y) / (d.max.Y - d.min.Y))
speed := math.Log(distance(star.v, star.d)) / math.Log(d.maxSpeed)
s.Speed = float32(speed)
s.Size = unit.Dp(float32(1 + ((star.m / (d.maxMass - d.minMass)) * 10)))
return s
}
// distance implements the simple two-dimensional euclidean distance function.
func distance(a, b r2.Vec) float64 {
return math.Sqrt((b.X-a.X)*(b.X-a.X) + (b.Y-a.Y)*(b.Y-a.Y))
}
var PlayIcon = func() *widget.Icon {
ic, _ := widget.NewIcon(icons.AVPlayArrow)
return ic
}()
var PauseIcon = func() *widget.Icon {
ic, _ := widget.NewIcon(icons.AVPause)
return ic
}()
var ClearIcon = func() *widget.Icon {
ic, _ := widget.NewIcon(icons.ContentClear)
return ic
}()
// viewport models a region of a larger space. Offset is the location
// of the upper-left corner of the view within the larger space. size
// is the dimensions of the viewport within the larger space.
type viewport struct {
offset f32.Point
size f32.Point
}
// subview modifies v to describe a smaller region by zooming into the
// space described by v using other.
func (v *viewport) subview(other *viewport) {
v.offset.X += other.offset.X * v.size.X
v.offset.Y += other.offset.Y * v.size.Y
v.size.X *= other.size.X
v.size.Y *= other.size.Y
}
// ensureSquare returns a copy of the rectangle that has been padded to
// be square by increasing the maximum coordinate.
func ensureSquare(r image.Rectangle) image.Rectangle {
dx := r.Dx()
dy := r.Dy()
if dx > dy {
r.Max.Y = r.Min.Y + dx
} else if dy > dx {
r.Max.X = r.Min.X + dy
}
return r
}
var (
ops op.Ops
play, clear widget.Clickable
playing = false
th = material.NewTheme(gofont.Collection())
selected image.Rectangle
selecting = false
view *viewport
)
func main() {
th.Palette.Fg, th.Palette.Bg = th.Palette.Bg, th.Palette.Fg
dist := distribution{}
seed := time.Now().UnixNano()
rnd := rand.New(rand.NewSource(uint64(seed)))
// Make 1000 stars in random locations.
stars, plane := galaxy(1000, rnd)
dist.Update(stars)
desiredSize := unit.Dp(800)
window := app.NewWindow(
app.Size(desiredSize, desiredSize),
app.Title("Seed: "+strconv.Itoa(int(seed))),
)
iterateSim := func() {
if !playing {
return
}
simulate(stars, plane, &dist)
window.Invalidate()
}
for {
select {
case ev := <-window.Events():
switch ev := ev.(type) {
case system.DestroyEvent:
if ev.Err != nil {
log.Fatal(ev.Err)
}
return
case system.FrameEvent:
gtx := layout.NewContext(&ops, ev)
paint.Fill(gtx.Ops, th.Palette.Bg)
layout.Center.Layout(gtx, func(gtx C) D {
return widget.Border{
Color: th.Fg,
Width: unit.Dp(1),
}.Layout(gtx, func(gtx C) D {
if gtx.Constraints.Max.X > gtx.Constraints.Max.Y {
gtx.Constraints.Max.X = gtx.Constraints.Max.Y
} else {
gtx.Constraints.Max.Y = gtx.Constraints.Max.X
}
gtx.Constraints.Min = gtx.Constraints.Max
if clear.Clicked() {
view = nil
}
if play.Clicked() {
playing = !playing
}
layoutSelectionLayer(gtx)
for _, s := range stars {
dist.Scale(s).Layout(gtx, view)
}
layoutControls(gtx)
return D{Size: gtx.Constraints.Max}
})
})
ev.Frame(gtx.Ops)
iterateSim()
}
}
}
}
func layoutControls(gtx C) D {
layout.N.Layout(gtx, func(gtx C) D {
return material.Body1(th, "Click and drag to zoom in on a region").Layout(gtx)
})
layout.S.Layout(gtx, func(gtx C) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
return layout.Flex{
Spacing: layout.SpaceEvenly,
}.Layout(gtx,
layout.Rigid(func(gtx C) D {
var btn material.IconButtonStyle
if playing {
btn = material.IconButton(th, &play, PauseIcon, "Pause Simulation")
} else {
btn = material.IconButton(th, &play, PlayIcon, "Play Simulation")
}
return btn.Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
if view == nil {
gtx = gtx.Disabled()
}
return material.IconButton(th, &clear, ClearIcon, "Reset Viewport").Layout(gtx)
}),
)
})
})
return D{}
}
func layoutSelectionLayer(gtx C) D {
for _, event := range gtx.Events(&selected) {
switch event := event.(type) {
case pointer.Event:
var intPt image.Point
intPt.X = int(event.Position.X)
intPt.Y = int(event.Position.Y)
switch event.Type {
case pointer.Press:
selecting = true
selected.Min = intPt
selected.Max = intPt
case pointer.Drag:
if intPt.X >= selected.Min.X && intPt.Y >= selected.Min.Y {
selected.Max = intPt
} else {
selected.Min = intPt
}
selected = ensureSquare(selected)
case pointer.Release:
selecting = false
newView := &viewport{
offset: f32.Point{
X: float32(selected.Min.X) / float32(gtx.Constraints.Max.X),
Y: float32(selected.Min.Y) / float32(gtx.Constraints.Max.Y),
},
size: f32.Point{
X: float32(selected.Dx()) / float32(gtx.Constraints.Max.X),
Y: float32(selected.Dy()) / float32(gtx.Constraints.Max.Y),
},
}
if view == nil {
view = newView
} else {
view.subview(newView)
}
case pointer.Cancel:
selecting = false
selected = image.Rectangle{}
}
}
}
if selecting {
paint.FillShape(gtx.Ops, color.NRGBA{R: 255, A: 100}, clip.Rect(selected).Op())
}
pr := clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops)
pointer.CursorNameOp{Name: pointer.CursorCrossHair}.Add(gtx.Ops)
pointer.InputOp{
Tag: &selected,
Types: pointer.Press | pointer.Release | pointer.Drag,
}.Add(gtx.Ops)
pr.Pop()
return D{Size: gtx.Constraints.Max}
}
// Star represents a point of mass rendered within a specific region of a canvas.
type Star struct {
X, Y float32
Speed float32
Size unit.Value
}
type (
C = layout.Context
D = layout.Dimensions
)
// Layout renders the star into the gtx assuming that it is visible within the
// provided viewport. Stars outside of the viewport will be skipped.
func (s Star) Layout(gtx layout.Context, view *viewport) layout.Dimensions {
px := gtx.Px(s.Size)
if view != nil {
if s.X < view.offset.X || s.X > view.offset.X+view.size.X {
return D{}
}
if s.Y < view.offset.Y || s.Y > view.offset.Y+view.size.Y {
return D{}
}
s.X = (s.X - view.offset.X) / view.size.X
s.Y = (s.Y - view.offset.Y) / view.size.Y
}
rr := float32(px / 2)
x := s.X*float32(gtx.Constraints.Max.X) - rr
y := s.Y*float32(gtx.Constraints.Max.Y) - rr
defer op.Offset(f32.Pt(x, y)).Push(gtx.Ops).Pop()
rect := f32.Rectangle{
Max: f32.Pt(float32(px), float32(px)),
}
fill := color.NRGBA{R: 0xff, G: 128, B: 0xff, A: 50}
fill.R = 255 - uint8(255*s.Speed)
fill.B = uint8(255 * s.Speed)
paint.FillShape(gtx.Ops, fill, clip.UniformRRect(rect, rr).Op(gtx.Ops))
return D{}
}

216
gui_gio_learn/glfw/main.go

@ -0,0 +1,216 @@
// SPDX-License-Identifier: Unlicense OR MIT
// GLFW doesn't build on OpenBSD and FreeBSD.
//go:build !openbsd && !freebsd && !android && !ios && !js
// +build !openbsd,!freebsd,!android,!ios,!js
// The glfw example demonstrates integration of Gio into a foreign
// windowing and rendering library, in this case GLFW
// (https://www.glfw.org).
//
// See the go-glfw package for installation of the native
// dependencies:
//
// https://github.com/go-gl/glfw
package main
import (
"image"
"log"
"math"
"runtime"
"time"
"gioui.org/f32"
"gioui.org/font/gofont"
"gioui.org/gpu"
"gioui.org/io/pointer"
"gioui.org/io/router"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"github.com/go-gl/gl/v3.1/gles2"
"github.com/go-gl/gl/v3.3-core/gl"
"github.com/go-gl/glfw/v3.3/glfw"
)
// desktopGL is true when the (core, desktop) OpenGL should
// be used, false for OpenGL ES.
const desktopGL = runtime.GOOS == "darwin"
func main() {
// Required by the OpenGL threading model.
runtime.LockOSThread()
err := glfw.Init()
if err != nil {
log.Fatal(err)
}
defer glfw.Terminate()
// Gio assumes a sRGB backbuffer.
glfw.WindowHint(glfw.SRGBCapable, glfw.True)
glfw.WindowHint(glfw.ScaleToMonitor, glfw.True)
glfw.WindowHint(glfw.CocoaRetinaFramebuffer, glfw.True)
if desktopGL {
glfw.WindowHint(glfw.ContextVersionMajor, 3)
glfw.WindowHint(glfw.ContextVersionMinor, 3)
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
} else {
glfw.WindowHint(glfw.ContextCreationAPI, glfw.EGLContextAPI)
glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLESAPI)
glfw.WindowHint(glfw.ContextVersionMajor, 3)
glfw.WindowHint(glfw.ContextVersionMinor, 0)
}
window, err := glfw.CreateWindow(800, 600, "Gio + GLFW", nil, nil)
if err != nil {
log.Fatal(err)
}
window.MakeContextCurrent()
if desktopGL {
err = gl.Init()
} else {
err = gles2.Init()
}
if err != nil {
log.Fatalf("gl.Init failed: %v", err)
}
if desktopGL {
// Enable sRGB.
gl.Enable(gl.FRAMEBUFFER_SRGB)
// Set up default VBA, required for the forward-compatible core profile.
var defVBA uint32
gl.GenVertexArrays(1, &defVBA)
gl.BindVertexArray(defVBA)
}
var queue router.Router
var ops op.Ops
th := material.NewTheme(gofont.Collection())
gpuCtx, err := gpu.New(gpu.OpenGL{ES: !desktopGL, Shared: true})
if err != nil {
log.Fatal(err)
}
defer gpuCtx.Release()
registerCallbacks(window, &queue)
for !window.ShouldClose() {
glfw.PollEvents()
scale, _ := window.GetContentScale()
width, height := window.GetFramebufferSize()
sz := image.Point{X: width, Y: height}
ops.Reset()
gtx := layout.Context{
Ops: &ops,
Now: time.Now(),
Queue: &queue,
Metric: unit.Metric{
PxPerDp: scale,
PxPerSp: scale,
},
Constraints: layout.Exact(sz),
}
drawOpenGL()
draw(gtx, th)
gpuCtx.Frame(gtx.Ops, gpu.OpenGLRenderTarget{}, sz)
queue.Frame(gtx.Ops)
window.SwapBuffers()
}
}
var (
button widget.Clickable
green float64 = 0.2
)
// drawOpenGL demonstrates the direct use of OpenGL commands
// to draw non-Gio content below the Gio UI.
func drawOpenGL() {
if desktopGL {
gl.ClearColor(0, float32(green), 0, 1)
gl.Clear(gl.COLOR_BUFFER_BIT)
} else {
gles2.ClearColor(0, float32(green), 0, 1)
gles2.Clear(gl.COLOR_BUFFER_BIT)
}
}
// handleCursorEvent handles cursor events not processed by Gio.
func handleCursorEvent(xpos, ypos float64) {
log.Printf("mouse cursor: (%f,%f)", xpos, ypos)
}
// handleMouseButtonEvent handles mouse button events not processed by Gio.
func handleMouseButtonEvent(button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
if action == glfw.Press {
green += 0.1
green, _ = math.Frexp(green)
}
log.Printf("mouse button: %v action %v mods %v", button, action, mods)
}
func draw(gtx layout.Context, th *material.Theme) layout.Dimensions {
return layout.Center.Layout(gtx,
material.Button(th, &button, "Button").Layout,
)
}
func registerCallbacks(window *glfw.Window, q *router.Router) {
var btns pointer.Buttons
beginning := time.Now()
var lastPos f32.Point
window.SetCursorPosCallback(func(w *glfw.Window, xpos float64, ypos float64) {
scale := float32(1)
if runtime.GOOS == "darwin" {
// macOS cursor positions are not scaled to the underlying framebuffer
// size when CocoaRetinaFramebuffer is true.
scale, _ = w.GetContentScale()
}
lastPos = f32.Point{X: float32(xpos) * scale, Y: float32(ypos) * scale}
e := pointer.Event{
Type: pointer.Move,
Position: lastPos,
Source: pointer.Mouse,
Time: time.Since(beginning),
Buttons: btns,
}
if !q.Queue(e) {
handleCursorEvent(xpos, ypos)
}
})
window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
var btn pointer.Buttons
switch button {
case glfw.MouseButton1:
btn = pointer.ButtonPrimary
case glfw.MouseButton2:
btn = pointer.ButtonSecondary
case glfw.MouseButton3:
btn = pointer.ButtonTertiary
}
var typ pointer.Type
switch action {
case glfw.Release:
typ = pointer.Release
btns &^= btn
case glfw.Press:
typ = pointer.Press
btns |= btn
}
e := pointer.Event{
Type: typ,
Source: pointer.Mouse,
Time: time.Since(beginning),
Position: lastPos,
Buttons: btns,
}
if !q.Queue(e) {
handleMouseButtonEvent(button, action, mods)
}
})
}

17
gui_gio_learn/go.mod

@ -0,0 +1,17 @@
module gioui.org/example
go 1.16
require (
gioui.org v0.0.0-20220105104929-8d8aeef66bef
gioui.org/x v0.0.0-20220105211426-ffad81642756
github.com/go-gl/gl v0.0.0-20210315015930-ae072cafe09d
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48
github.com/google/go-github/v24 v24.0.1
github.com/inkeliz/giohyperlink v0.0.0-20211125170138-7c983051483f
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013
golang.org/x/text v0.3.7 // indirect
gonum.org/v1/gonum v0.8.2
)

725
gui_gio_learn/go.sum

@ -0,0 +1,725 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gioui.org v0.0.0-20210910062418-d5d0a75a9bcb/go.mod h1:BTldRXnY5mrUrYZCdWyDwyMzyUzpfZN1cF4MMRrOt9w=
gioui.org v0.0.0-20211103131231-1d0769ae89a5/go.mod h1:yoWOxPng6WkDpsud+NRmkoftmyWn3rkKsYGEcWHpjTI=
gioui.org v0.0.0-20220105104929-8d8aeef66bef h1:vjBKFl76dewHkdOnlEt09daV4/E/oHHnMA4rV7eGH+E=
gioui.org v0.0.0-20220105104929-8d8aeef66bef/go.mod h1:yoWOxPng6WkDpsud+NRmkoftmyWn3rkKsYGEcWHpjTI=
gioui.org/cmd v0.0.0-20211103131231-1d0769ae89a5/go.mod h1:qrH3h4nt/PyIqx/XabL/eJ5cXQnQ0ERHqC3VEXx/Rmg=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/shader v1.0.2/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
gioui.org/x v0.0.0-20220105211426-ffad81642756 h1:zLf4bSur15cxl3NpqsRMoefKlCP0SkVGrViUYMMdXC0=
gioui.org/x v0.0.0-20220105211426-ffad81642756/go.mod h1:4TBvhIO9Pn30hcg3iCz8RtVemMn08T/QG2lZ534XAR8=
git.wow.st/gmp/jni v0.0.0-20200827154156-014cd5c7c4c0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/akavel/rsrc v0.10.1/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/chromedp v0.5.2/go.mod h1:rsTo/xRo23KZZwFmWk2Ui79rBaVRRATCjLzNQlOFSiA=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/esiqveland/notify v0.11.0 h1:0WJ/xW+3Ln8uRBYntG7f0XihXxnlOaQTdha1yyzXz30=
github.com/esiqveland/notify v0.11.0/go.mod h1:63UbVSaeJwF0LVJARHFuPgUAoM7o1BEvCZyknsuonBc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/gl v0.0.0-20210315015930-ae072cafe09d h1:o81yRlBATU4PRn97lydmsq8hTRNXI4wlR/VvUQhFRVY=
github.com/go-gl/gl v0.0.0-20210315015930-ae072cafe09d/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v24 v24.0.1 h1:KCt1LjMJEey1qvPXxa9SjaWxwTsCWSq6p2Ju57UR4Q4=
github.com/google/go-github/v24 v24.0.1/go.mod h1:CRqaW1Uns1TCkP0wqTpxYyRxRjxwvKU/XSS44u6X74M=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/inkeliz/giohyperlink v0.0.0-20211125170138-7c983051483f h1:9YZNpMBphjme3V+Cg+2lxi0l0fib13NOBQCnCIaeXdc=
github.com/inkeliz/giohyperlink v0.0.0-20211125170138-7c983051483f/go.mod h1:54IEkqZQZniLpn6VUGxA4hlt+GXZgFL4Nk7widXiVqE=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3 h1:IlrJD2AM5p8JhN/wVny9jt6gJ9hut2VALhSeZ3SYluk=
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013 h1:55H5j7lotzuFCEOKDsMch+fRNUQ9DgtyHOUP31FNqKc=
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

248
gui_gio_learn/gophers/main.go

@ -0,0 +1,248 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
// A Gio program that displays Go contributors from GitHub. See https://gioui.org for more information.
import (
"context"
"flag"
"fmt"
"image"
"log"
"net/http"
"os"
"golang.org/x/oauth2"
"gioui.org/app"
"gioui.org/gesture"
"gioui.org/io/key"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
"github.com/google/go-github/v24/github"
_ "image/jpeg"
_ "image/png"
_ "net/http/pprof"
)
type App struct {
w *app.Window
ui *UI
updateUsers chan []*user
commitsResult chan []*github.Commit
ctx context.Context
ctxCancel context.CancelFunc
}
var (
prof = flag.Bool("profile", false, "serve profiling data at http://localhost:6060")
stats = flag.Bool("stats", false, "show rendering statistics")
token = flag.String("token", "", "Github authentication token")
)
func main() {
flag.Parse()
initProfiling()
if *token == "" {
fmt.Println("The quota for anonymous GitHub API access is very low. Specify a token with -token to avoid quota errors.")
fmt.Println("See https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line.")
}
go func() {
w := app.NewWindow(
app.Size(unit.Dp(400), unit.Dp(800)),
app.Title("Gophers"),
)
if err := newApp(w).run(); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func initProfiling() {
if !*prof {
return
}
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}
func (a *App) run() error {
a.ui.profiling = *stats
var ops op.Ops
for {
select {
case users := <-a.updateUsers:
a.ui.users = users
a.ui.userClicks = make([]gesture.Click, len(users))
a.w.Invalidate()
case commits := <-a.commitsResult:
a.ui.selectedUser.commits = commits
a.w.Invalidate()
case e := <-a.w.Events():
switch e := e.(type) {
case key.Event:
switch e.Name {
case key.NameEscape:
os.Exit(0)
case "P":
if e.Modifiers.Contain(key.ModShortcut) {
a.ui.profiling = !a.ui.profiling
a.w.Invalidate()
}
}
case system.DestroyEvent:
return e.Err
case system.StageEvent:
if e.Stage >= system.StageRunning {
if a.ctxCancel == nil {
a.ctx, a.ctxCancel = context.WithCancel(context.Background())
}
if a.ui.users == nil {
go a.fetchContributors()
}
} else {
if a.ctxCancel != nil {
a.ctxCancel()
a.ctxCancel = nil
}
}
case *system.CommandEvent:
switch e.Type {
case system.CommandBack:
if a.ui.selectedUser != nil {
a.ui.selectedUser = nil
e.Cancel = true
a.w.Invalidate()
}
}
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
a.ui.Layout(gtx)
e.Frame(gtx.Ops)
}
}
}
}
func newApp(w *app.Window) *App {
a := &App{
w: w,
updateUsers: make(chan []*user),
commitsResult: make(chan []*github.Commit, 1),
}
fetch := func(u string) {
a.fetchCommits(a.ctx, u)
}
a.ui = newUI(fetch)
return a
}
func githubClient(ctx context.Context) *github.Client {
var tc *http.Client
if *token != "" {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: *token},
)
tc = oauth2.NewClient(ctx, ts)
}
return github.NewClient(tc)
}
func (a *App) fetchContributors() {
client := githubClient(a.ctx)
cons, _, err := client.Repositories.ListContributors(a.ctx, "golang", "go", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "github: failed to fetch contributors: %v\n", err)
return
}
var users []*user
userErrs := make(chan error, len(cons))
avatarErrs := make(chan error, len(cons))
for _, con := range cons {
con := con
avatar := con.GetAvatarURL()
if avatar == "" {
continue
}
u := &user{
login: con.GetLogin(),
}
users = append(users, u)
go func() {
guser, _, err := client.Users.Get(a.ctx, u.login)
if err != nil {
avatarErrs <- err
return
}
u.name = guser.GetName()
u.company = guser.GetCompany()
avatarErrs <- nil
}()
go func() {
a, err := fetchImage(avatar)
if a != nil {
u.avatar = a
}
userErrs <- err
}()
}
for i := 0; i < len(cons); i++ {
if err := <-userErrs; err != nil {
fmt.Fprintf(os.Stderr, "github: failed to fetch user: %v\n", err)
}
if err := <-avatarErrs; err != nil {
fmt.Fprintf(os.Stderr, "github: failed to fetch avatar: %v\n", err)
}
}
// Drop users with no avatar or name.
for i := len(users) - 1; i >= 0; i-- {
if u := users[i]; u.name == "" || u.avatar.Bounds().Size() == (image.Point{}) {
users = append(users[:i], users[i+1:]...)
}
}
a.updateUsers <- users
}
func fetchImage(url string) (image.Image, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("fetchImage: http.Get(%q): %v", url, err)
}
defer resp.Body.Close()
img, _, err := image.Decode(resp.Body)
if err != nil {
return nil, fmt.Errorf("fetchImage: image decode failed: %v", err)
}
return img, nil
}
func (a *App) fetchCommits(ctx context.Context, user string) {
go func() {
gh := githubClient(ctx)
repoCommits, _, err := gh.Repositories.ListCommits(ctx, "golang", "go", &github.CommitsListOptions{
Author: user,
})
if err != nil {
log.Printf("failed to fetch commits: %v", err)
return
}
var commits []*github.Commit
for _, commit := range repoCommits {
if c := commit.GetCommit(); c != nil {
commits = append(commits, c)
}
}
a.commitsResult <- commits
}()
}

24
gui_gio_learn/gophers/main_test.go

@ -0,0 +1,24 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"image"
"testing"
"gioui.org/layout"
"gioui.org/op"
)
func BenchmarkUI(b *testing.B) {
fetch := func(_ string) {}
u := newUI(fetch)
var ops op.Ops
for i := 0; i < b.N; i++ {
gtx := layout.Context{
Ops: &ops,
Constraints: layout.Exact(image.Pt(800, 600)),
}
u.Layout(gtx)
}
}

424
gui_gio_learn/gophers/ui.go

@ -0,0 +1,424 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
// A Gio program that displays Go contributors from GitHub. See https://gioui.org for more information.
import (
"fmt"
"image"
"image/color"
"log"
"runtime"
"gioui.org/f32"
"gioui.org/font/gofont"
"gioui.org/gesture"
"gioui.org/io/key"
"gioui.org/io/profile"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"github.com/google/go-github/v24/github"
"golang.org/x/exp/shiny/materialdesign/icons"
"golang.org/x/image/draw"
)
type UI struct {
fab *widget.Clickable
fabIcon *widget.Icon
usersList *widget.List
users []*user
userClicks []gesture.Click
selectedUser *userPage
edit, edit2 *widget.Editor
fetchCommits func(u string)
// Profiling.
profiling bool
profile profile.Event
lastMallocs uint64
}
type userPage struct {
user *user
commitsList *widget.List
commits []*github.Commit
}
type user struct {
name string
login string
company string
avatar image.Image
avatarOp paint.ImageOp
}
var theme *material.Theme
type (
C = layout.Context
D = layout.Dimensions
)
func init() {
theme = material.NewTheme(gofont.Collection())
theme.Palette.Fg = rgb(0x333333)
}
func newUI(fetchCommits func(string)) *UI {
u := &UI{
fetchCommits: fetchCommits,
}
u.usersList = &widget.List{
List: layout.List{
Axis: layout.Vertical,
},
}
u.fab = new(widget.Clickable)
u.edit2 = &widget.Editor{
// Alignment: text.End,
SingleLine: true,
}
var err error
u.fabIcon, err = widget.NewIcon(icons.ContentSend)
if err != nil {
log.Fatal(err)
}
u.edit2.SetText("Single line editor. Edit me!")
u.edit = &widget.Editor{
// Alignment: text.End,
// SingleLine: true,
}
u.edit.SetText(longTextSample)
return u
}
func rgb(c uint32) color.NRGBA {
return argb((0xff << 24) | c)
}
func argb(c uint32) color.NRGBA {
return color.NRGBA{A: uint8(c >> 24), R: uint8(c >> 16), G: uint8(c >> 8), B: uint8(c)}
}
func (u *UI) layoutTimings(gtx layout.Context) {
if !u.profiling {
return
}
for _, e := range gtx.Events(u) {
if e, ok := e.(profile.Event); ok {
u.profile = e
}
}
profile.Op{Tag: u}.Add(gtx.Ops)
var mstats runtime.MemStats
runtime.ReadMemStats(&mstats)
mallocs := mstats.Mallocs - u.lastMallocs
u.lastMallocs = mstats.Mallocs
layout.NE.Layout(gtx, func(gtx C) D {
return layout.Inset{Top: unit.Dp(16)}.Layout(gtx, func(gtx C) D {
txt := fmt.Sprintf("m: %d %s", mallocs, u.profile.Timings)
lbl := material.Caption(theme, txt)
lbl.Font.Variant = "Mono"
return lbl.Layout(gtx)
})
})
}
func (u *UI) Layout(gtx layout.Context) {
for i := range u.userClicks {
click := &u.userClicks[i]
for _, e := range click.Events(gtx) {
if e.Type == gesture.TypeClick {
u.selectedUser = u.newUserPage(u.users[i])
}
}
}
if u.selectedUser == nil {
u.layoutUsers(gtx)
} else {
u.selectedUser.Layout(gtx)
}
u.layoutTimings(gtx)
}
func (u *UI) newUserPage(user *user) *userPage {
up := &userPage{
user: user,
commitsList: &widget.List{List: layout.List{Axis: layout.Vertical}},
}
u.fetchCommits(user.login)
return up
}
func (up *userPage) Layout(gtx layout.Context) {
l := up.commitsList
if l.Dragging() {
key.SoftKeyboardOp{Show: false}.Add(gtx.Ops)
}
material.List(theme, l).Layout(gtx, len(up.commits), func(gtx C, i int) D {
return up.commit(gtx, i)
})
}
func (up *userPage) commit(gtx layout.Context, index int) layout.Dimensions {
u := up.user
msg := up.commits[index].GetMessage()
label := material.Caption(theme, msg)
in := layout.Inset{Top: unit.Dp(16), Right: unit.Dp(8), Left: unit.Dp(8)}
return in.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(func(gtx C) D {
sz := gtx.Px(unit.Dp(48))
cc := clipCircle{}
return cc.Layout(gtx, func(gtx C) D {
gtx.Constraints = layout.Exact(gtx.Constraints.Constrain(image.Point{X: sz, Y: sz}))
return u.layoutAvatar(gtx)
})
}),
layout.Flexed(1, func(gtx C) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return layout.Inset{Left: unit.Dp(8)}.Layout(gtx, label.Layout)
}),
)
})
}
func (u *UI) layoutUsers(gtx layout.Context) {
layout.Stack{Alignment: layout.SE}.Layout(gtx,
layout.Expanded(func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return layout.UniformInset(unit.Dp(16)).Layout(gtx, func(gtx C) D {
sz := gtx.Px(unit.Dp(200))
cs := gtx.Constraints
gtx.Constraints = layout.Exact(cs.Constrain(image.Point{X: sz, Y: sz}))
return material.Editor(theme, u.edit, "Hint").Layout(gtx)
})
}),
layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
in := layout.Inset{Bottom: unit.Dp(16), Left: unit.Dp(16), Right: unit.Dp(16)}
return in.Layout(gtx, func(gtx C) D {
e := material.Editor(theme, u.edit2, "Hint")
e.TextSize = unit.Sp(14)
e.Font.Style = text.Italic
return e.Layout(gtx)
})
}),
layout.Rigid(func(gtx C) D {
return layout.Stack{}.Layout(gtx,
layout.Expanded(func(gtx C) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return fill{rgb(0xf2f2f2)}.Layout(gtx)
}),
layout.Stacked(func(gtx C) D {
in := layout.Inset{Top: unit.Dp(16), Right: unit.Dp(8), Bottom: unit.Dp(8), Left: unit.Dp(8)}
return in.Layout(gtx, func(gtx C) D {
lbl := material.Caption(theme, "GOPHERS")
lbl.Color = rgb(0x888888)
return lbl.Layout(gtx)
})
}),
)
}),
layout.Flexed(1, func(gtx C) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return u.layoutContributors(gtx)
}),
)
}),
layout.Stacked(func(gtx C) D {
in := layout.UniformInset(unit.Dp(16))
return in.Layout(gtx, func(gtx C) D {
for u.fab.Clicked() {
}
return material.IconButton(theme, u.fab, u.fabIcon, "Floating Action Button Example").Layout(gtx)
})
}),
)
}
func (u *UI) layoutContributors(gtx layout.Context) layout.Dimensions {
l := u.usersList
if l.Dragging() {
key.SoftKeyboardOp{Show: false}.Add(gtx.Ops)
}
return material.List(theme, l).Layout(gtx, len(u.users), func(gtx C, i int) D {
return u.user(gtx, i)
})
}
func (u *UI) user(gtx layout.Context, index int) layout.Dimensions {
user := u.users[index]
in := layout.UniformInset(unit.Dp(8))
dims := in.Layout(gtx, func(gtx C) D {
return centerRowOpts().Layout(gtx,
layout.Rigid(func(gtx C) D {
in := layout.Inset{Right: unit.Dp(8)}
cc := clipCircle{}
return in.Layout(gtx, func(gtx C) D {
return cc.Layout(gtx, func(gtx C) D {
dim := gtx.Px(unit.Dp(48))
sz := image.Point{X: dim, Y: dim}
gtx.Constraints = layout.Exact(gtx.Constraints.Constrain(sz))
return user.layoutAvatar(gtx)
})
})
}),
layout.Rigid(func(gtx C) D {
return column().Layout(gtx,
layout.Rigid(func(gtx C) D {
return baseline().Layout(gtx,
layout.Rigid(material.Body1(theme, user.name).Layout),
layout.Flexed(1, func(gtx C) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return layout.E.Layout(gtx, func(gtx C) D {
return layout.Inset{Left: unit.Dp(2)}.Layout(gtx,
material.Caption(theme, "3 hours ago").Layout)
})
}),
)
}),
layout.Rigid(func(gtx C) D {
in := layout.Inset{Top: unit.Dp(4)}
return in.Layout(gtx, func(gtx C) D {
lbl := material.Caption(theme, user.company)
lbl.Color = rgb(0xbbbbbb)
return lbl.Layout(gtx)
})
}),
)
}),
)
})
pr := clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops)
click := &u.userClicks[index]
click.Add(gtx.Ops)
pr.Pop()
return dims
}
func (u *user) layoutAvatar(gtx layout.Context) layout.Dimensions {
sz := gtx.Constraints.Min.X
if u.avatarOp.Size().X != sz {
img := image.NewRGBA(image.Rectangle{Max: image.Point{X: sz, Y: sz}})
draw.ApproxBiLinear.Scale(img, img.Bounds(), u.avatar, u.avatar.Bounds(), draw.Src, nil)
u.avatarOp = paint.NewImageOp(img)
}
img := widget.Image{Src: u.avatarOp}
img.Scale = float32(sz) / float32(gtx.Px(unit.Dp(float32(sz))))
return img.Layout(gtx)
}
type fill struct {
col color.NRGBA
}
func (f fill) Layout(gtx layout.Context) layout.Dimensions {
cs := gtx.Constraints
d := cs.Min
dr := image.Rectangle{
Max: image.Point{X: d.X, Y: d.Y},
}
paint.FillShape(gtx.Ops, f.col, clip.Rect(dr).Op())
return layout.Dimensions{Size: d}
}
func column() layout.Flex {
return layout.Flex{Axis: layout.Vertical}
}
func centerRowOpts() layout.Flex {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}
}
func baseline() layout.Flex {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Baseline}
}
type clipCircle struct{}
func (c *clipCircle) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
m := op.Record(gtx.Ops)
dims := w(gtx)
call := m.Stop()
max := dims.Size.X
if dy := dims.Size.Y; dy > max {
max = dy
}
szf := float32(max)
rr := szf * .5
defer clip.RRect{
Rect: f32.Rectangle{Max: f32.Point{X: szf, Y: szf}},
NE: rr, NW: rr, SE: rr, SW: rr,
}.Push(gtx.Ops).Pop()
call.Add(gtx.Ops)
return dims
}
const longTextSample = `1. I learned from my grandfather, Verus, to use good manners, and to
put restraint on anger. 2. In the famous memory of my father I had a
pattern of modesty and manliness. 3. Of my mother I learned to be
pious and generous; to keep myself not only from evil deeds, but even
from evil thoughts; and to live with a simplicity which is far from
customary among the rich. 4. I owe it to my great-grandfather that I
did not attend public lectures and discussions, but had good and able
teachers at home; and I owe him also the knowledge that for things of
this nature a man should count no expense too great.
5. My tutor taught me not to favour either green or blue at the
chariot races, nor, in the contests of gladiators, to be a supporter
either of light or heavy armed. He taught me also to endure labour;
not to need many things; to serve myself without troubling others; not
to intermeddle in the affairs of others, and not easily to listen to
slanders against them.
6. Of Diognetus I had the lesson not to busy myself about vain things;
not to credit the great professions of such as pretend to work
wonders, or of sorcerers about their charms, and their expelling of
Demons and the like; not to keep quails (for fighting or divination),
nor to run after such things; to suffer freedom of speech in others,
and to apply myself heartily to philosophy. Him also I must thank for
my hearing first Bacchius, then Tandasis and Marcianus; that I wrote
dialogues in my youth, and took a liking to the philosopher's pallet
and skins, and to the other things which, by the Grecian discipline,
belong to that profession.
7. To Rusticus I owe my first apprehensions that my nature needed
reform and cure; and that I did not fall into the ambition of the
common Sophists, either by composing speculative writings or by
declaiming harangues of exhortation in public; further, that I never
strove to be admired by ostentation of great patience in an ascetic
life, or by display of activity and application; that I gave over the
study of rhetoric, poetry, and the graces of language; and that I did
not pace my house in my senatorial robes, or practise any similar
affectation. I observed also the simplicity of style in his letters,
particularly in that which he wrote to my mother from Sinuessa. I
learned from him to be easily appeased, and to be readily reconciled
with those who had displeased me or given cause of offence, so soon as
they inclined to make their peace; to read with care; not to rest
satisfied with a slight and superficial knowledge; nor quickly to
assent to great talkers. I have him to thank that I met with the
discourses of Epictetus, which he furnished me from his own library.
8. From Apollonius I learned true liberty, and tenacity of purpose; to
regard nothing else, even in the smallest degree, but reason always;
and always to remain unaltered in the agonies of pain, in the losses
of children, or in long diseases. He afforded me a living example of
how the same man can, upon occasion, be most yielding and most
inflexible. He was patient in exposition; and, as might well be seen,
esteemed his fine skill and ability in teaching others the principles
of philosophy as the least of his endowments. It was from him that I
learned how to receive from friends what are thought favours without
seeming humbled by the giver or insensible to the gift.`

60
gui_gio_learn/haptic/example.go

@ -0,0 +1,60 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
// A simple Gio program. See https://gioui.org for more information.
import (
"log"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/font/gofont"
"gioui.org/x/haptic"
)
var buzzer *haptic.Buzzer
func main() {
go func() {
w := app.NewWindow()
if err := loop(w); err != nil {
log.Fatal(err)
}
}()
app.Main()
}
func loop(w *app.Window) error {
th := material.NewTheme(gofont.Collection())
btn := widget.Clickable{}
buzzer = haptic.NewBuzzer(w)
var ops op.Ops
for {
select {
case e := <-w.Events():
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
if btn.Clicked() {
buzzer.Buzz()
}
gtx := layout.NewContext(&ops, e)
layout.Center.Layout(gtx, material.Button(th, &btn, "buzz").Layout)
e.Frame(gtx.Ops)
default:
ProcessPlatformEvent(e)
}
case err := <-buzzer.Errors():
if err != nil {
log.Printf("buzzer error: %v", err)
}
}
}
}

17
gui_gio_learn/haptic/example_android.go

@ -0,0 +1,17 @@
package main
import (
"gioui.org/app"
"gioui.org/io/event"
)
// ProcessPlatformEvent handles platform-specific event processing. If it
// consumed the provided event, it returns true. In this case, no further
// event processing should occur.
func ProcessPlatformEvent(event event.Event) bool {
if ve, ok := event.(app.ViewEvent); ok {
buzzer.SetView(ve.View)
return true
}
return false
}

10
gui_gio_learn/haptic/example_nonandroid.go

@ -0,0 +1,10 @@
//go:build !android
// +build !android
package main
import "gioui.org/io/event"
func ProcessPlatformEvent(event event.Event) bool {
return false
}

51
gui_gio_learn/hello/hello.go

@ -0,0 +1,51 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
// A simple Gio program. See https://gioui.org for more information.
import (
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/text"
"gioui.org/widget/material"
"gioui.org/font/gofont"
)
func main() {
go func() {
w := app.NewWindow()
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func loop(w *app.Window) error {
th := material.NewTheme(gofont.Collection())
var ops op.Ops
for {
e := <-w.Events()
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
l := material.H1(th, "Hello, Gio")
maroon := color.NRGBA{R: 127, G: 0, B: 0, A: 255}
l.Color = maroon
l.Alignment = text.Middle
l.Layout(gtx)
e.Frame(gtx.Ops)
}
}
}

433
gui_gio_learn/kitchen/kitchen.go

@ -0,0 +1,433 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
// A Gio program that demonstrates Gio widgets. See https://gioui.org for more information.
import (
"bytes"
"flag"
"fmt"
"image"
"image/color"
"image/png"
"io/ioutil"
"log"
"math"
"os"
"time"
"gioui.org/app"
"gioui.org/f32"
"gioui.org/font/gofont"
"gioui.org/gpu/headless"
"gioui.org/io/pointer"
"gioui.org/io/router"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"golang.org/x/exp/shiny/materialdesign/icons"
)
var screenshot = flag.String("screenshot", "", "save a screenshot to a file and exit")
var disable = flag.Bool("disable", false, "disable all widgets")
type iconAndTextButton struct {
theme *material.Theme
button *widget.Clickable
icon *widget.Icon
word string
}
func main() {
flag.Parse()
editor.SetText(longText)
ic, err := widget.NewIcon(icons.ContentAdd)
if err != nil {
log.Fatal(err)
}
icon = ic
progressIncrementer = make(chan float32)
if *screenshot != "" {
if err := saveScreenshot(*screenshot); err != nil {
fmt.Fprintf(os.Stderr, "failed to save screenshot: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}
go func() {
for {
time.Sleep(time.Second)
progressIncrementer <- 0.1
}
}()
go func() {
w := app.NewWindow(app.Size(unit.Dp(800), unit.Dp(700)))
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func saveScreenshot(f string) error {
const scale = 1.5
sz := image.Point{X: 800 * scale, Y: 600 * scale}
w, err := headless.NewWindow(sz.X, sz.Y)
if err != nil {
return err
}
gtx := layout.Context{
Ops: new(op.Ops),
Metric: unit.Metric{
PxPerDp: scale,
PxPerSp: scale,
},
Constraints: layout.Exact(sz),
Queue: new(router.Router),
}
th := material.NewTheme(gofont.Collection())
kitchen(gtx, th)
w.Frame(gtx.Ops)
img := image.NewRGBA(image.Rectangle{Max: sz})
err = w.Screenshot(img)
if err != nil {
return err
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return err
}
return ioutil.WriteFile(f, buf.Bytes(), 0666)
}
func loop(w *app.Window) error {
th := material.NewTheme(gofont.Collection())
var ops op.Ops
for {
select {
case e := <-w.Events():
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
if *disable {
gtx = gtx.Disabled()
}
if checkbox.Changed() {
if checkbox.Value {
transformTime = e.Now
} else {
transformTime = time.Time{}
}
}
transformedKitchen(gtx, th)
e.Frame(gtx.Ops)
}
case p := <-progressIncrementer:
progress += p
if progress > 1 {
progress = 0
}
w.Invalidate()
}
}
}
func transformedKitchen(gtx layout.Context, th *material.Theme) layout.Dimensions {
if !transformTime.IsZero() {
dt := float32(gtx.Now.Sub(transformTime).Seconds())
angle := dt * .1
op.InvalidateOp{}.Add(gtx.Ops)
tr := f32.Affine2D{}
tr = tr.Rotate(f32.Pt(300, 20), -angle)
scale := 1.0 - dt*.5
if scale < 0.5 {
scale = 0.5
}
tr = tr.Scale(f32.Pt(300, 20), f32.Pt(scale, scale))
offset := dt * 50
if offset > 200 {
offset = 200
}
tr = tr.Offset(f32.Pt(0, offset))
op.Affine(tr).Add(gtx.Ops)
}
return kitchen(gtx, th)
}
var (
editor = new(widget.Editor)
lineEditor = &widget.Editor{
SingleLine: true,
Submit: true,
}
button = new(widget.Clickable)
greenButton = new(widget.Clickable)
iconTextButton = new(widget.Clickable)
iconButton = new(widget.Clickable)
flatBtn = new(widget.Clickable)
disableBtn = new(widget.Clickable)
radioButtonsGroup = new(widget.Enum)
list = &widget.List{
List: layout.List{
Axis: layout.Vertical,
},
}
progress = float32(0)
progressIncrementer chan float32
green = true
topLabel = "Hello, Gio"
icon *widget.Icon
checkbox = new(widget.Bool)
swtch = new(widget.Bool)
transformTime time.Time
float = new(widget.Float)
)
type (
D = layout.Dimensions
C = layout.Context
)
func (b iconAndTextButton) Layout(gtx layout.Context) layout.Dimensions {
return material.ButtonLayout(b.theme, b.button).Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Dp(12)).Layout(gtx, func(gtx C) D {
iconAndLabel := layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}
textIconSpacer := unit.Dp(5)
layIcon := layout.Rigid(func(gtx C) D {
return layout.Inset{Right: textIconSpacer}.Layout(gtx, func(gtx C) D {
var d D
if b.icon != nil {
size := gtx.Px(unit.Dp(56)) - 2*gtx.Px(unit.Dp(16))
gtx.Constraints = layout.Exact(image.Pt(size, size))
d = b.icon.Layout(gtx, b.theme.ContrastFg)
}
return d
})
})
layLabel := layout.Rigid(func(gtx C) D {
return layout.Inset{Left: textIconSpacer}.Layout(gtx, func(gtx C) D {
l := material.Body1(b.theme, b.word)
l.Color = b.theme.Palette.ContrastFg
return l.Layout(gtx)
})
})
return iconAndLabel.Layout(gtx, layIcon, layLabel)
})
})
}
func kitchen(gtx layout.Context, th *material.Theme) layout.Dimensions {
for _, e := range lineEditor.Events() {
if e, ok := e.(widget.SubmitEvent); ok {
topLabel = e.Text
lineEditor.SetText("")
}
}
widgets := []layout.Widget{
material.H3(th, topLabel).Layout,
func(gtx C) D {
gtx.Constraints.Max.Y = gtx.Px(unit.Dp(200))
return material.Editor(th, editor, "Hint").Layout(gtx)
},
func(gtx C) D {
e := material.Editor(th, lineEditor, "Hint")
e.Font.Style = text.Italic
border := widget.Border{Color: color.NRGBA{A: 0xff}, CornerRadius: unit.Dp(8), Width: unit.Px(2)}
return border.Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Dp(8)).Layout(gtx, e.Layout)
})
},
func(gtx C) D {
gtx.Constraints.Min.Y = gtx.Px(unit.Dp(50))
gtx.Constraints.Max.Y = gtx.Constraints.Min.Y
dr := image.Rectangle{Max: gtx.Constraints.Min}
paint.LinearGradientOp{
Stop1: layout.FPt(dr.Min),
Stop2: layout.FPt(dr.Max),
Color1: color.NRGBA{R: 0x10, G: 0xff, B: 0x10, A: 0xFF},
Color2: color.NRGBA{R: 0x10, G: 0x10, B: 0xff, A: 0xFF},
}.Add(gtx.Ops)
defer clip.Rect(dr).Push(gtx.Ops).Pop()
paint.PaintOp{}.Add(gtx.Ops)
return layout.Dimensions{
Size: gtx.Constraints.Max,
}
},
func(gtx C) D {
in := layout.UniformInset(unit.Dp(8))
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return in.Layout(gtx, material.IconButton(th, iconButton, icon, "Add Icon Button").Layout)
}),
layout.Rigid(func(gtx C) D {
return in.Layout(gtx, iconAndTextButton{theme: th, icon: icon, word: "Icon", button: iconTextButton}.Layout)
}),
layout.Rigid(func(gtx C) D {
return in.Layout(gtx, func(gtx C) D {
for button.Clicked() {
green = !green
}
dims := material.Button(th, button, "Click me!").Layout(gtx)
pointer.CursorNameOp{Name: pointer.CursorPointer}.Add(gtx.Ops)
return dims
})
}),
layout.Rigid(func(gtx C) D {
return in.Layout(gtx, func(gtx C) D {
l := "Green"
if !green {
l = "Blue"
}
btn := material.Button(th, greenButton, l)
if green {
btn.Background = color.NRGBA{A: 0xff, R: 0x9e, G: 0x9d, B: 0x24}
}
return btn.Layout(gtx)
})
}),
layout.Rigid(func(gtx C) D {
return in.Layout(gtx, func(gtx C) D {
return material.Clickable(gtx, flatBtn, func(gtx C) D {
return layout.UniformInset(unit.Dp(12)).Layout(gtx, func(gtx C) D {
flatBtnText := material.Body1(th, "Flat")
if gtx.Queue == nil {
flatBtnText.Color.A = 150
}
return layout.Center.Layout(gtx, flatBtnText.Layout)
})
})
})
}),
layout.Rigid(material.ProgressCircle(th, progress).Layout),
)
},
material.ProgressBar(th, progress).Layout,
func(gtx C) D {
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(
material.CheckBox(th, checkbox, "Transform").Layout,
),
layout.Rigid(func(gtx C) D {
return layout.Inset{Left: unit.Dp(16)}.Layout(gtx,
material.Switch(th, swtch, "Example Switch").Layout,
)
}),
layout.Rigid(func(gtx C) D {
return layout.Inset{Left: unit.Dp(16)}.Layout(gtx, func(gtx C) D {
text := "enabled"
if !swtch.Value {
text = "disabled"
gtx = gtx.Disabled()
}
btn := material.Button(th, disableBtn, text)
return btn.Layout(gtx)
})
}),
layout.Rigid(func(gtx C) D {
return layout.Inset{Left: unit.Dp(16)}.Layout(gtx, func(gtx C) D {
if !swtch.Value {
return D{}
}
return material.Loader(th).Layout(gtx)
})
}),
)
},
func(gtx C) D {
return layout.Flex{}.Layout(gtx,
layout.Rigid(material.RadioButton(th, radioButtonsGroup, "r1", "RadioButton1").Layout),
layout.Rigid(material.RadioButton(th, radioButtonsGroup, "r2", "RadioButton2").Layout),
layout.Rigid(material.RadioButton(th, radioButtonsGroup, "r3", "RadioButton3").Layout),
)
},
func(gtx C) D {
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Flexed(1, material.Slider(th, float, 0, 2*math.Pi).Layout),
layout.Rigid(func(gtx C) D {
return layout.UniformInset(unit.Dp(8)).Layout(gtx,
material.Body1(th, fmt.Sprintf("%.2f", float.Value)).Layout,
)
}),
)
},
}
return material.List(th, list).Layout(gtx, len(widgets), func(gtx C, i int) D {
return layout.UniformInset(unit.Dp(16)).Layout(gtx, widgets[i])
})
}
const longText = `1. I learned from my grandfather, Verus, to use good manners, and to
put restraint on anger. 2. In the famous memory of my father I had a
pattern of modesty and manliness. 3. Of my mother I learned to be
pious and generous; to keep myself not only from evil deeds, but even
from evil thoughts; and to live with a simplicity which is far from
customary among the rich. 4. I owe it to my great-grandfather that I
did not attend public lectures and discussions, but had good and able
teachers at home; and I owe him also the knowledge that for things of
this nature a man should count no expense too great.
5. My tutor taught me not to favour either green or blue at the
chariot races, nor, in the contests of gladiators, to be a supporter
either of light or heavy armed. He taught me also to endure labour;
not to need many things; to serve myself without troubling others; not
to intermeddle in the affairs of others, and not easily to listen to
slanders against them.
6. Of Diognetus I had the lesson not to busy myself about vain things;
not to credit the great professions of such as pretend to work
wonders, or of sorcerers about their charms, and their expelling of
Demons and the like; not to keep quails (for fighting or divination),
nor to run after such things; to suffer freedom of speech in others,
and to apply myself heartily to philosophy. Him also I must thank for
my hearing first Bacchius, then Tandasis and Marcianus; that I wrote
dialogues in my youth, and took a liking to the philosopher's pallet
and skins, and to the other things which, by the Grecian discipline,
belong to that profession.
7. To Rusticus I owe my first apprehensions that my nature needed
reform and cure; and that I did not fall into the ambition of the
common Sophists, either by composing speculative writings or by
declaiming harangues of exhortation in public; further, that I never
strove to be admired by ostentation of great patience in an ascetic
life, or by display of activity and application; that I gave over the
study of rhetoric, poetry, and the graces of language; and that I did
not pace my house in my senatorial robes, or practise any similar
affectation. I observed also the simplicity of style in his letters,
particularly in that which he wrote to my mother from Sinuessa. I
learned from him to be easily appeased, and to be readily reconciled
with those who had displeased me or given cause of offence, so soon as
they inclined to make their peace; to read with care; not to rest
satisfied with a slight and superficial knowledge; nor quickly to
assent to great talkers. I have him to thank that I met with the
discourses of Epictetus, which he furnished me from his own library.
8. From Apollonius I learned true liberty, and tenacity of purpose; to
regard nothing else, even in the smallest degree, but reason always;
and always to remain unaltered in the agonies of pain, in the losses
of children, or in long diseases. He afforded me a living example of
how the same man can, upon occasion, be most yielding and most
inflexible. He was patient in exposition; and, as might well be seen,
esteemed his fine skill and ability in teaching others the principles
of philosophy as the least of his endowments. It was from him that I
learned how to receive from friends what are thought favours without
seeming humbled by the giver or insensible to the gift.`

93
gui_gio_learn/kitchen/main_test.go

@ -0,0 +1,93 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"image"
"testing"
"time"
"gioui.org/f32"
"gioui.org/font/gofont"
"gioui.org/gpu/headless"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/widget/material"
)
func BenchmarkUI(b *testing.B) { benchmarkUI(b, transformation{}) }
func BenchmarkUI_Offset(b *testing.B) { benchmarkUI(b, transformation{offset: true}) }
func BenchmarkUI_Scale(b *testing.B) { benchmarkUI(b, transformation{scale: true}) }
func BenchmarkUI_Rotate(b *testing.B) { benchmarkUI(b, transformation{rotate: true}) }
func BenchmarkUI_All(b *testing.B) {
benchmarkUI(b, transformation{offset: true, rotate: true, scale: true})
}
func benchmarkUI(b *testing.B, transform transformation) {
th := material.NewTheme(gofont.Collection())
w, err := headless.NewWindow(800, 600)
if err != nil {
b.Fatal(err)
}
defer w.Release()
var layoutTime time.Duration
var frameTime time.Duration
b.ResetTimer()
var ops op.Ops
for i := 0; i < b.N; i++ {
ops.Reset()
gtx := layout.Context{
Ops: &ops,
Constraints: layout.Exact(image.Pt(800, 600)),
}
addTransform(i, transform, gtx.Ops)
layoutTime += measure(func() { kitchen(gtx, th) })
frameTime += measure(func() { w.Frame(&ops) })
}
b.StopTimer()
b.ReportMetric(float64(layoutTime.Nanoseconds())/float64(b.N), "ns/layout")
b.ReportMetric(float64(frameTime.Nanoseconds())/float64(b.N), "ns/frame")
}
type transformation struct {
offset bool
rotate bool
scale bool
}
func addTransform(i int, transform transformation, ops *op.Ops) {
if !(transform.offset || transform.rotate || transform.scale) {
return
}
dt := float32(i)
tr := f32.Affine2D{}
if transform.rotate {
angle := dt * .1
tr = tr.Rotate(f32.Pt(300, 20), -angle)
}
if transform.scale {
scale := 1.0 - dt*.5
if scale < 0.5 {
scale = 0.5
}
tr = tr.Scale(f32.Pt(300, 20), f32.Pt(scale, scale))
}
if transform.offset {
offset := dt * 50
if offset > 200 {
offset = 200
}
tr = tr.Offset(f32.Pt(0, offset))
}
op.Affine(tr).Add(ops)
}
func measure(fn func()) time.Duration {
start := time.Now()
fn()
return time.Since(start)
}

116
gui_gio_learn/life/board.go

@ -0,0 +1,116 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"image"
"math/rand"
)
// Board implements game of life logic.
type Board struct {
// Size is the count of cells in a particular dimension.
Size image.Point
// Cells contains the alive or dead cells.
Cells []byte
// buffer is used to avoid reallocating a new cells
// slice for every update.
buffer []byte
}
// NewBoard returns a new game of life with the defined size.
func NewBoard(size image.Point) *Board {
return &Board{
Size: size,
Cells: make([]byte, size.X*size.Y),
buffer: make([]byte, size.X*size.Y),
}
}
// Randomize randomizes each cell state.
func (b *Board) Randomize() {
rand.Read(b.Cells)
for i, v := range b.Cells {
if v < 0x30 {
b.Cells[i] = 1
} else {
b.Cells[i] = 0
}
}
}
// Pt returns the coordinate given a index in b.Cells.
func (b *Board) Pt(i int) image.Point {
x, y := i%b.Size.X, i/b.Size.Y
return image.Point{X: x, Y: y}
}
// At returns the b.Cells index, given a wrapped coordinate.
func (b *Board) At(c image.Point) int {
if c.X < 0 {
c.X += b.Size.X
}
if c.X >= b.Size.X {
c.X -= b.Size.X
}
if c.Y < 0 {
c.Y += b.Size.Y
}
if c.Y >= b.Size.Y {
c.Y -= b.Size.Y
}
return b.Size.Y*c.Y + c.X
}
// SetWithoutWrap sets a cell to alive.
func (b *Board) SetWithoutWrap(c image.Point) {
if !c.In(image.Rectangle{Max: b.Size}) {
return
}
b.Cells[b.At(c)] = 1
}
// Advance advances the board state by 1.
func (b *Board) Advance() {
next, cur := b.buffer, b.Cells
defer func() { b.Cells, b.buffer = next, cur }()
for i := range next {
next[i] = 0
}
for y := 0; y < b.Size.Y; y++ {
for x := 0; x < b.Size.X; x++ {
var t byte
t += cur[b.At(image.Pt(x-1, y-1))]
t += cur[b.At(image.Pt(x+0, y-1))]
t += cur[b.At(image.Pt(x+1, y-1))]
t += cur[b.At(image.Pt(x-1, y+0))]
t += cur[b.At(image.Pt(x+1, y+0))]
t += cur[b.At(image.Pt(x-1, y+1))]
t += cur[b.At(image.Pt(x+0, y+1))]
t += cur[b.At(image.Pt(x+1, y+1))]
// Any live cell with fewer than two live neighbours dies, as if by underpopulation.
// Any live cell with two or three live neighbours lives on to the next generation.
// Any live cell with more than three live neighbours dies, as if by overpopulation.
// Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
p := b.At(image.Pt(x, y))
switch {
case t < 2:
t = 0
case t == 2:
t = cur[p]
case t == 3:
t = 1
case t > 3:
t = 0
}
next[p] = t
}
}
}

118
gui_gio_learn/life/main.go

@ -0,0 +1,118 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"image"
"log"
"os"
"time"
"gioui.org/app" // app contains Window handling.
"gioui.org/io/key" // key is used for keyboard events.
"gioui.org/io/system" // system is used for system events (e.g. closing the window).
"gioui.org/layout" // layout is used for layouting widgets.
"gioui.org/op" // op is used for recording different operations.
"gioui.org/unit" // unit is used to define pixel-independent sizes
)
var (
// cellSizePx is the cell size in pixels.
cellSize = unit.Dp(5)
// boardSize is the count of cells in a particular dimension.
boardSize = image.Pt(50, 50)
)
func main() {
// The ui loop is separated from the application window creation
// such that it can be used for testing.
ui := NewUI()
windowWidth := cellSize.Scale(float32(boardSize.X + 2))
windowHeight := cellSize.Scale(float32(boardSize.Y + 2))
// This creates a new application window and starts the UI.
go func() {
w := app.NewWindow(
app.Title("Game of Life"),
app.Size(windowWidth, windowHeight),
)
if err := ui.Run(w); err != nil {
log.Println(err)
os.Exit(1)
}
os.Exit(0)
}()
// This starts Gio main.
app.Main()
}
// UI holds all of the application state.
type UI struct {
// Board handles all game-of-life logic.
Board *Board
}
// NewUI creates a new UI using the Go Fonts.
func NewUI() *UI {
// We start with a new random board.
board := NewBoard(boardSize)
board.Randomize()
return &UI{
Board: board,
}
}
// Run handles window events and renders the application.
func (ui *UI) Run(w *app.Window) error {
var ops op.Ops
// Update the board 3 times per second.
advanceBoard := time.NewTicker(time.Second / 3)
defer advanceBoard.Stop()
// listen for events happening on the window.
for {
select {
case e := <-w.Events():
// detect the type of the event.
switch e := e.(type) {
// this is sent when the application should re-render.
case system.FrameEvent:
// gtx is used to pass around rendering and event information.
gtx := layout.NewContext(&ops, e)
// render and handle UI.
ui.Layout(gtx)
// render and handle the operations from the UI.
e.Frame(gtx.Ops)
// handle a global key press.
case key.Event:
switch e.Name {
// when we click escape, let's close the window.
case key.NameEscape:
return nil
}
// this is sent when the application is closed.
case system.DestroyEvent:
return e.Err
}
case <-advanceBoard.C:
ui.Board.Advance()
w.Invalidate()
}
}
}
// Layout displays the main program layout.
func (ui *UI) Layout(gtx layout.Context) layout.Dimensions {
return layout.Center.Layout(gtx,
BoardStyle{
CellSizePx: gtx.Px(cellSize),
Board: ui.Board,
}.Layout,
)
}

68
gui_gio_learn/life/style.go

@ -0,0 +1,68 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"image"
"image/color"
"gioui.org/f32" // f32 is used for shape calculations.
"gioui.org/io/pointer" // system is used for system events (e.g. closing the window).
"gioui.org/layout" // layout is used for layouting widgets.
// op is used for recording different operations.
"gioui.org/op/clip" // clip is used to draw the cell shape.
"gioui.org/op/paint" // paint is used to paint the cells.
)
// BoardStyle draws Board with rectangles.
type BoardStyle struct {
CellSizePx int
*Board
}
// Layout draws the Board and accepts input for adding alive cells.
func (board BoardStyle) Layout(gtx layout.Context) layout.Dimensions {
// Calculate the board size based on the cell size in pixels.
size := board.Size.Mul(board.CellSizePx)
gtx.Constraints = layout.Exact(size)
// Handle any input from a pointer.
for _, ev := range gtx.Events(board.Board) {
if ev, ok := ev.(pointer.Event); ok {
p := image.Pt(int(ev.Position.X), int(ev.Position.Y))
// Calculate the board coordinate given a cursor position.
p = p.Div(board.CellSizePx)
board.SetWithoutWrap(p)
}
}
// Register to listen for pointer Drag events.
pr := clip.Rect(image.Rectangle{Max: size}).Push(gtx.Ops)
pointer.InputOp{Tag: board.Board, Types: pointer.Drag}.Add(gtx.Ops)
pr.Pop()
cellSize := float32(board.CellSizePx)
// Draw a shape for each alive cell.
var p clip.Path
p.Begin(gtx.Ops)
for i, v := range board.Cells {
if v == 0 {
continue
}
c := layout.FPt(board.Pt(i).Mul(board.CellSizePx))
p.MoveTo(f32.Pt(c.X, c.Y))
p.LineTo(f32.Pt(c.X+cellSize, c.Y))
p.LineTo(f32.Pt(c.X+cellSize, c.Y+cellSize))
p.LineTo(f32.Pt(c.X, c.Y+cellSize))
p.Close()
}
defer clip.Outline{Path: p.End()}.Op().Push(gtx.Ops).Pop()
// Paint the shape with a black color.
paint.ColorOp{Color: color.NRGBA{A: 0xFF}}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
return layout.Dimensions{Size: size}
}

168
gui_gio_learn/markdown/main.go

@ -0,0 +1,168 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
// A simple Gio program. See https://gioui.org for more information.
//
// This program showcases markdown rendering.
// The left pane contains a text editor for inputing raw text.
// The right pane renders the resulting markdown document using richtext.
//
// Richtext is fully interactive, links can be clicked, hovered, and longpressed.
import (
"image"
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
"gioui.org/x/markdown"
"gioui.org/x/richtext"
"gioui.org/font/gofont"
"github.com/inkeliz/giohyperlink"
)
func main() {
ui := UI{
Window: app.NewWindow(),
Renderer: markdown.NewRenderer(),
Shaper: text.NewCache(gofont.Collection()),
Theme: NewTheme(gofont.Collection()),
Resize: component.Resize{Ratio: 0.5},
}
go func() {
if err := ui.Loop(); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
type (
C = layout.Context
D = layout.Dimensions
)
// UI specifies the user interface.
type UI struct {
// External systems.
// Window provides access to the OS window.
Window *app.Window
// Theme contains semantic style data. Extends `material.Theme`.
Theme *Theme
// Shaper cache of registered fonts.
Shaper *text.Cache
// Renderer tranforms raw text containing markdown into richtext.
Renderer *markdown.Renderer
// Core state.
// Editor retains raw text in an edit buffer.
Editor widget.Editor
// TextState retains rich text interactions: clicks, hovers and longpresses.
TextState richtext.InteractiveText
// Resize state retains the split between the editor and the rendered text.
component.Resize
}
// Theme contains semantic style data.
type Theme struct {
// Base theme to extend.
Base *material.Theme
// cache of processed markdown.
cache []richtext.SpanStyle
}
// NewTheme instantiates a theme, extending material theme.
func NewTheme(font []text.FontFace) *Theme {
return &Theme{
Base: material.NewTheme(font),
}
}
// Loop drives the UI until the window is destroyed.
func (ui UI) Loop() error {
var ops op.Ops
for {
e := <-ui.Window.Events()
giohyperlink.ListenEvents(e)
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
ui.Layout(gtx)
e.Frame(gtx.Ops)
}
}
}
// Update processes events from the previous frame, updating state accordingly.
func (ui *UI) Update(gtx C) {
for o, events := ui.TextState.Events(); o != nil; o, events = ui.TextState.Events() {
for _, e := range events {
switch e.Type {
case richtext.Click:
if url := o.Get(markdown.MetadataURL); url != "" {
if err := giohyperlink.Open(url); err != nil {
// TODO(jfm): display UI element explaining the error to the user.
log.Printf("error: opening hyperlink: %v", err)
}
}
case richtext.Hover:
case richtext.LongPress:
log.Println("longpress")
ui.Window.Option(app.Title(o.Get(markdown.MetadataURL)))
}
}
}
for _, event := range ui.Editor.Events() {
if _, ok := event.(widget.ChangeEvent); ok {
var err error
ui.Theme.cache, err = ui.Renderer.Render(ui.Theme.Base, []byte(ui.Editor.Text()))
if err != nil {
// TODO(jfm): display UI element explaining the error to the user.
log.Printf("error: rendering markdown: %v", err)
}
}
}
}
// Layout renders the current frame.
func (ui *UI) Layout(gtx C) D {
ui.Update(gtx)
return ui.Resize.Layout(gtx,
func(gtx C) D {
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
return material.Editor(ui.Theme.Base, &ui.Editor, "markdown").Layout(gtx)
})
},
func(gtx C) D {
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
return richtext.Text(&ui.TextState, ui.Shaper, ui.Theme.cache...).Layout(gtx)
})
},
func(gtx C) D {
rect := image.Rectangle{
Max: image.Point{
X: (gtx.Px(unit.Dp(4))),
Y: (gtx.Constraints.Max.Y),
},
}
paint.FillShape(gtx.Ops, color.NRGBA{A: 200}, clip.Rect(rect).Op())
return D{Size: rect.Max}
},
)
}

64
gui_gio_learn/multiwindow/letters.go

@ -0,0 +1,64 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"gioui.org/app"
"gioui.org/layout"
"gioui.org/widget"
"gioui.org/widget/material"
)
// Letters displays a clickable list of text items that open a new window.
type Letters struct {
win *Window
log *Log
items []*LetterListItem
list widget.List
}
// NewLetters creates a new letters view with the provided log.
func NewLetters(log *Log) *Letters {
view := &Letters{
log: log,
list: widget.List{List: layout.List{Axis: layout.Vertical}},
}
for text := 'a'; text <= 'z'; text++ {
view.items = append(view.items, &LetterListItem{Text: string(text)})
}
return view
}
// Run implements Window.Run method.
func (v *Letters) Run(w *Window) error {
v.win = w
return WidgetView(v.Layout).Run(w)
}
// Layout handles drawing the letters view.
func (v *Letters) Layout(gtx layout.Context) layout.Dimensions {
th := v.win.App.Theme
return material.List(th, &v.list).Layout(gtx, len(v.items), func(gtx layout.Context, index int) layout.Dimensions {
item := v.items[index]
for item.Click.Clicked() {
v.log.Printf("opening %s view", item.Text)
bigText := material.H1(th, item.Text)
size := bigText.TextSize
size.V *= 2
v.win.App.NewWindow(item.Text,
WidgetView(func(gtx layout.Context) layout.Dimensions {
return layout.Center.Layout(gtx, bigText.Layout)
}),
app.Size(size, size),
)
}
return material.Button(th, &item.Click, item.Text).Layout(gtx)
})
}
type LetterListItem struct {
Text string
Click widget.Clickable
}

86
gui_gio_learn/multiwindow/log.go

@ -0,0 +1,86 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"fmt"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/widget"
"gioui.org/widget/material"
)
// Log shows a list of strings.
type Log struct {
addLine chan string
lines []string
close widget.Clickable
list widget.List
}
// NewLog crates a new log view.
func NewLog() *Log {
return &Log{
addLine: make(chan string, 100),
list: widget.List{List: layout.List{Axis: layout.Vertical}},
}
}
// Printf adds a new line to the log.
func (log *Log) Printf(format string, args ...interface{}) {
s := fmt.Sprintf(format, args...)
// ensure that this logging does not block.
select {
case log.addLine <- s:
default:
}
}
// Run handles window loop for the log.
func (log *Log) Run(w *Window) error {
var ops op.Ops
applicationClose := w.App.Context.Done()
for {
select {
case <-applicationClose:
return nil
// listen to new lines from Printf and add them to our lines.
case line := <-log.addLine:
log.lines = append(log.lines, line)
w.Invalidate()
case e := <-w.Events():
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
log.Layout(w, w.App.Theme, gtx)
e.Frame(gtx.Ops)
}
}
}
}
// Layout displays the log with a close button.
func (log *Log) Layout(w *Window, th *material.Theme, gtx layout.Context) {
// This is here to demonstrate programmatic closing of a window,
// however it's probably better to use OS close button instead.
for log.close.Clicked() {
w.Window.Close()
}
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(material.Button(th, &log.close, "Close").Layout),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return material.List(th, &log.list).Layout(gtx, len(log.lines), func(gtx layout.Context, i int) layout.Dimensions {
return material.Body1(th, log.lines[i]).Layout(gtx)
})
}),
)
}

126
gui_gio_learn/multiwindow/main.go

@ -0,0 +1,126 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
// This projects demonstrates one way to manage and use multiple windows.
//
// It shows:
// * how to track multiple windows,
// * how to communicate between windows,
// * how to create new windows.
import (
"context"
"os"
"os/signal"
"sync"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/widget/material"
"gioui.org/font/gofont"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
go func() {
a := NewApplication(ctx)
log := NewLog()
log.Printf("[Application Started]")
letters := NewLetters(log)
a.NewWindow("Log", log)
a.NewWindow("Letters", letters)
a.Wait()
os.Exit(0)
}()
app.Main()
}
// Application keeps track of all the windows and global state.
type Application struct {
// Context is used to broadcast application shutdown.
Context context.Context
// Shutdown shuts down all windows.
Shutdown func()
// Theme is the application wide theme.
Theme *material.Theme
// active keeps track the open windows, such that application
// can shut down, when all of them are closed.
active sync.WaitGroup
}
func NewApplication(ctx context.Context) *Application {
ctx, cancel := context.WithCancel(ctx)
return &Application{
Context: ctx,
Shutdown: cancel,
Theme: material.NewTheme(gofont.Collection()),
}
}
// Wait waits for all windows to close.
func (a *Application) Wait() {
a.active.Wait()
}
// NewWindow creates a new tracked window.
func (a *Application) NewWindow(title string, view View, opts ...app.Option) {
opts = append(opts, app.Title(title))
w := &Window{
App: a,
Window: app.NewWindow(opts...),
}
a.active.Add(1)
go func() {
defer a.active.Done()
view.Run(w)
}()
}
// Window holds window state.
type Window struct {
App *Application
*app.Window
}
// View describes .
type View interface {
// Run handles the window event loop.
Run(w *Window) error
}
// WidgetView allows to use layout.Widget as a view.
type WidgetView func(gtx layout.Context) layout.Dimensions
// Run displays the widget with default handling.
func (view WidgetView) Run(w *Window) error {
var ops op.Ops
applicationClose := w.App.Context.Done()
for {
select {
case <-applicationClose:
return nil
case e := <-w.Events():
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
view(gtx)
e.Frame(gtx.Ops)
}
}
}
}

8
gui_gio_learn/notify/build_macos.go

@ -0,0 +1,8 @@
//go:build darwin
// +build darwin
//go:generate mkdir -p example.app/Contents/MacOS
//go:generate go build -o example.app/Contents/MacOS/example
//go:generate codesign -s - example.app
package main

50
gui_gio_learn/notify/example.app/Contents/Info.plist

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>18G103</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>example</string>
<key>CFBundleIdentifier</key>
<string>org.gioui</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>11C505</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>19B90</string>
<key>DTSDKName</key>
<string>macosx10.15</string>
<key>DTXcode</key>
<string>1130</string>
<key>DTXcodeBuild</key>
<string>11C505</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticTermination</key>
<true/>
<key>NSSupportsSuddenTermination</key>
<true/>
</dict>
</plist>

1
gui_gio_learn/notify/example.app/Contents/PkgInfo

@ -0,0 +1 @@
APPL????

88
gui_gio_learn/notify/main.go

@ -0,0 +1,88 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
// A simple Gio program. See https://gioui.org for more information.
import (
"fmt"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
"gioui.org/x/notify"
"gioui.org/font/gofont"
)
func main() {
go func() {
w := app.NewWindow(
app.Title("notify"),
app.Size(unit.Dp(800), unit.Dp(600)))
var ops op.Ops
for event := range w.Events() {
switch event := event.(type) {
case system.DestroyEvent:
os.Exit(0)
case system.FrameEvent:
event.Frame(frame(layout.NewContext(&ops, event)))
}
}
}()
app.Main()
}
type (
// C quick alias for Context.
C = layout.Context
// D quick alias for Dimensions.
D = layout.Dimensions
)
var (
th = material.NewTheme(gofont.Collection())
notifier = func() notify.Notifier {
n, err := notify.NewNotifier()
if err != nil {
panic(fmt.Errorf("init notification manager: %w", err))
}
return n
}()
editor component.TextField
notifyBtn widget.Clickable
)
// frame lays out the entire frame and returns the reusltant ops buffer.
func frame(gtx C) *op.Ops {
if notifyBtn.Clicked() {
msg := "This is a notification send from gio."
if txt := editor.Text(); txt != "" {
msg = txt
}
go notifier.CreateNotification("Hello Gio!", msg)
}
layout.Center.Layout(gtx, func(gtx C) D {
gtx.Constraints.Max.X = gtx.Px(unit.Dp(300))
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return editor.Layout(gtx, th, "enter a notification message")
}),
layout.Rigid(func(gtx C) D {
return layout.Spacer{Height: unit.Dp(10)}.Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
return material.Button(th, &notifyBtn, "notify").Layout(gtx)
}),
)
})
return gtx.Ops
}

40
gui_gio_learn/opengl/display_angle.go

@ -0,0 +1,40 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build windows || darwin
// +build windows darwin
package main
import (
"strings"
"gioui.org/app"
)
/*
#cgo CFLAGS: -DEGL_NO_X11
#cgo LDFLAGS: -lEGL -lGLESv2
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#define EGL_EGLEXT_PROTOTYPES
#include <EGL/eglext.h>
*/
import "C"
func getDisplay(_ app.ViewEvent) C.EGLDisplay {
var EGL_NO_DISPLAY C.EGLDisplay
platformExts := strings.Split(C.GoString(C.eglQueryString(EGL_NO_DISPLAY, C.EGL_EXTENSIONS)), " ")
platformType := C.EGLint(C.EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE)
if hasExtension(platformExts, "EGL_ANGLE_platform_angle_metal") {
// The Metal backend works better than the OpenGL backend.
platformType = C.EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE
}
attrs := []C.EGLint{
C.EGL_PLATFORM_ANGLE_TYPE_ANGLE,
platformType,
C.EGL_NONE,
}
return C.eglGetPlatformDisplayEXT(C.EGL_PLATFORM_ANGLE_ANGLE, nil, &attrs[0])
}

24
gui_gio_learn/opengl/egl_linux.go

@ -0,0 +1,24 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import "gioui.org/app"
/*
#cgo CFLAGS: -DEGL_NO_X11
#cgo LDFLAGS: -lEGL -lGLESv2
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#define EGL_EGLEXT_PROTOTYPES
#include <EGL/eglext.h>
*/
import "C"
func getDisplay(ve app.ViewEvent) C.EGLDisplay {
return C.eglGetDisplay(C.EGLNativeDisplayType(ve.Display))
}
func nativeViewFor(e app.ViewEvent) C.EGLNativeWindowType {
return C.EGLNativeWindowType(uintptr(e.Window))
}

288
gui_gio_learn/opengl/main.go

@ -0,0 +1,288 @@
// SPDX-License-Identifier: Unlicense OR MIT
//go:build darwin || windows || linux
// +build darwin windows linux
// This program demonstrates the use of a custom OpenGL ES context with
// app.Window. It is similar to the GLFW example, but uses Gio's window
// implementation instead of the one in GLFW.
//
// The example runs on Linux using the normal EGL and X11 libraries, so
// no additional libraries need to be installed.
//
// The example runs on macOS and Windows using ANGLE:
//
// $ CGO_CFLAGS=-I<path-to-ANGLE>/include CGO_LDFLAGS=-L<path-to-angle-libraries> go build -o opengl.exe ./opengl
//
// You'll need the ANGLE libraries (EGL and GLESv2) in your library search path. On macOS:
//
// $ DYLD_LIBRARY_PATH=<path-to-ANGLE-libraries> ./opengl.exe
//
package main
import (
"bytes"
"errors"
"fmt"
"image"
"image/png"
"log"
"os"
"runtime"
"strings"
"unsafe"
"gioui.org/app"
"gioui.org/gpu"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/font/gofont"
)
/*
#cgo CFLAGS: -DEGL_NO_X11
#cgo LDFLAGS: -lEGL -lGLESv2
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#define EGL_EGLEXT_PROTOTYPES
#include <EGL/eglext.h>
*/
import "C"
type eglContext struct {
disp C.EGLDisplay
ctx C.EGLContext
surf C.EGLSurface
}
func main() {
go func() {
// Set CustomRenderer so we can provide our own rendering context.
w := app.NewWindow(app.CustomRenderer(true))
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
var btnScreenshot widget.Clickable
func loop(w *app.Window) error {
th := material.NewTheme(gofont.Collection())
var ops op.Ops
var (
ctx *eglContext
gioCtx gpu.GPU
)
for e := range w.Events() {
switch e := e.(type) {
case app.ViewEvent:
w.Run(func() {
if gioCtx != nil {
gioCtx.Release()
gioCtx = nil
}
if ctx != nil {
C.eglMakeCurrent(ctx.disp, nil, nil, nil)
ctx.Release()
ctx = nil
}
view := nativeViewFor(e)
var nilv C.EGLNativeWindowType
if view == nilv {
return
}
c, err := createContext(e)
if err != nil {
log.Fatal(err)
}
ctx = c
// eglMakeCurrent binds a context to an operating system thread. Prevent Go from switching thread.
runtime.LockOSThread()
if ok := C.eglMakeCurrent(ctx.disp, ctx.surf, ctx.surf, ctx.ctx); ok != C.EGL_TRUE {
err := fmt.Errorf("eglMakeCurrent failed (%#x)", C.eglGetError())
log.Fatal(err)
}
glGetString := func(e C.GLenum) string {
return C.GoString((*C.char)(unsafe.Pointer(C.glGetString(e))))
}
fmt.Printf("GL_VERSION: %s\nGL_RENDERER: %s\n", glGetString(C.GL_VERSION), glGetString(C.GL_RENDERER))
gioCtx, err = gpu.New(gpu.OpenGL{ES: true, Shared: true})
if err != nil {
log.Fatal(err)
}
})
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
if gioCtx == nil {
break
}
// Build ops.
gtx := layout.NewContext(&ops, e)
// Catch pointer events not hitting UI.
types := pointer.Move | pointer.Press | pointer.Release
pointer.InputOp{Tag: w, Types: types}.Add(gtx.Ops)
for _, e := range gtx.Events(w) {
log.Println("Event:", e)
}
drawUI(th, gtx)
w.Run(func() {
// Trigger window resize detection in ANGLE.
C.eglWaitClient()
// Draw custom OpenGL content.
drawGL()
// Render drawing ops.
if err := gioCtx.Frame(gtx.Ops, gpu.OpenGLRenderTarget{}, e.Size); err != nil {
log.Fatal(fmt.Errorf("render failed: %v", err))
}
if ok := C.eglSwapBuffers(ctx.disp, ctx.surf); ok != C.EGL_TRUE {
log.Fatal(fmt.Errorf("swap failed: %v", C.eglGetError()))
}
if btnScreenshot.Clicked() {
if err := screenshot(gioCtx, e.Size, gtx.Ops); err != nil {
log.Fatal(err)
}
}
})
// Process non-drawing ops.
e.Frame(gtx.Ops)
}
}
return nil
}
func screenshot(ctx gpu.GPU, size image.Point, ops *op.Ops) error {
var tex C.GLuint
C.glGenTextures(1, &tex)
defer C.glDeleteTextures(1, &tex)
C.glBindTexture(C.GL_TEXTURE_2D, tex)
C.glTexImage2D(C.GL_TEXTURE_2D, 0, C.GL_RGBA, C.GLint(size.X), C.GLint(size.Y), 0, C.GL_RGBA, C.GL_UNSIGNED_BYTE, nil)
var fbo C.GLuint
C.glGenFramebuffers(1, &fbo)
defer C.glDeleteFramebuffers(1, &fbo)
C.glBindFramebuffer(C.GL_FRAMEBUFFER, fbo)
defer C.glBindFramebuffer(C.GL_FRAMEBUFFER, 0)
C.glFramebufferTexture2D(C.GL_FRAMEBUFFER, C.GL_COLOR_ATTACHMENT0, C.GL_TEXTURE_2D, tex, 0)
if st := C.glCheckFramebufferStatus(C.GL_FRAMEBUFFER); st != C.GL_FRAMEBUFFER_COMPLETE {
return fmt.Errorf("screenshot: framebuffer incomplete (%#x)", st)
}
drawGL()
if err := ctx.Frame(ops, gpu.OpenGLRenderTarget{V: uint(fbo)}, size); err != nil {
return fmt.Errorf("screenshot: %w", err)
}
r := image.Rectangle{Max: size}
ss := image.NewRGBA(r)
C.glReadPixels(C.GLint(r.Min.X), C.GLint(r.Min.Y), C.GLint(r.Dx()), C.GLint(r.Dy()), C.GL_RGBA, C.GL_UNSIGNED_BYTE, unsafe.Pointer(&ss.Pix[0]))
var buf bytes.Buffer
if err := png.Encode(&buf, ss); err != nil {
return fmt.Errorf("screenshot: %w", err)
}
const file = "screenshot.png"
if err := os.WriteFile(file, buf.Bytes(), 0644); err != nil {
return fmt.Errorf("screenshot: %w", err)
}
fmt.Printf("wrote %q\n", file)
return nil
}
func drawGL() {
C.glClearColor(.5, .5, 0, 1)
C.glClear(C.GL_COLOR_BUFFER_BIT | C.GL_DEPTH_BUFFER_BIT)
}
func drawUI(th *material.Theme, gtx layout.Context) layout.Dimensions {
return layout.Center.Layout(gtx,
material.Button(th, &btnScreenshot, "Screenshot").Layout,
)
}
func createContext(ve app.ViewEvent) (*eglContext, error) {
view := nativeViewFor(ve)
disp := getDisplay(ve)
if disp == 0 {
return nil, fmt.Errorf("eglGetPlatformDisplay failed: 0x%x", C.eglGetError())
}
var major, minor C.EGLint
if ok := C.eglInitialize(disp, &major, &minor); ok != C.EGL_TRUE {
return nil, fmt.Errorf("eglInitialize failed: 0x%x", C.eglGetError())
}
exts := strings.Split(C.GoString(C.eglQueryString(disp, C.EGL_EXTENSIONS)), " ")
srgb := hasExtension(exts, "EGL_KHR_gl_colorspace")
attribs := []C.EGLint{
C.EGL_RENDERABLE_TYPE, C.EGL_OPENGL_ES2_BIT,
C.EGL_SURFACE_TYPE, C.EGL_WINDOW_BIT,
C.EGL_BLUE_SIZE, 8,
C.EGL_GREEN_SIZE, 8,
C.EGL_RED_SIZE, 8,
C.EGL_CONFIG_CAVEAT, C.EGL_NONE,
}
if srgb {
// Some drivers need alpha for sRGB framebuffers to work.
attribs = append(attribs, C.EGL_ALPHA_SIZE, 8)
}
attribs = append(attribs, C.EGL_NONE)
var (
cfg C.EGLConfig
numCfgs C.EGLint
)
if ok := C.eglChooseConfig(disp, &attribs[0], &cfg, 1, &numCfgs); ok != C.EGL_TRUE {
return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", C.eglGetError())
}
if numCfgs == 0 {
supportsNoCfg := hasExtension(exts, "EGL_KHR_no_config_context")
if !supportsNoCfg {
return nil, errors.New("eglChooseConfig returned no configs")
}
}
ctxAttribs := []C.EGLint{
C.EGL_CONTEXT_CLIENT_VERSION, 3,
C.EGL_NONE,
}
ctx := C.eglCreateContext(disp, cfg, nil, &ctxAttribs[0])
if ctx == nil {
return nil, fmt.Errorf("eglCreateContext failed: 0x%x", C.eglGetError())
}
var surfAttribs []C.EGLint
if srgb {
surfAttribs = append(surfAttribs, C.EGL_GL_COLORSPACE, C.EGL_GL_COLORSPACE_SRGB)
}
surfAttribs = append(surfAttribs, C.EGL_NONE)
surf := C.eglCreateWindowSurface(disp, cfg, view, &surfAttribs[0])
if surf == nil {
return nil, fmt.Errorf("eglCreateWindowSurface failed (0x%x)", C.eglGetError())
}
return &eglContext{disp: disp, ctx: ctx, surf: surf}, nil
}
func (c *eglContext) Release() {
if c.ctx != nil {
C.eglDestroyContext(c.disp, c.ctx)
}
if c.surf != nil {
C.eglDestroySurface(c.disp, c.surf)
}
*c = eglContext{}
}
func hasExtension(exts []string, ext string) bool {
for _, e := range exts {
if ext == e {
return true
}
}
return false
}

16
gui_gio_learn/opengl/view_darwin.go

@ -0,0 +1,16 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"gioui.org/app"
)
/*
#include <EGL/egl.h>
*/
import "C"
func nativeViewFor(e app.ViewEvent) C.EGLNativeWindowType {
return C.EGLNativeWindowType(e.Layer)
}

18
gui_gio_learn/opengl/view_windows.go

@ -0,0 +1,18 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"unsafe"
"gioui.org/app"
)
/*
#include <EGL/egl.h>
*/
import "C"
func nativeViewFor(e app.ViewEvent) C.EGLNativeWindowType {
return C.EGLNativeWindowType(unsafe.Pointer(e.HWND))
}

25
gui_gio_learn/outlay/fan/cribbage/cmd/crib.go

@ -0,0 +1,25 @@
package main
import (
"fmt"
"gioui.org/example/outlay/fan/cribbage"
)
func main() {
g := cribbage.NewGame(2)
fmt.Println(g)
g.DealRound()
fmt.Println(g)
g.Sacrifice(0, 0)
g.Sacrifice(0, 4)
g.Sacrifice(1, 0)
g.Sacrifice(1, 4)
fmt.Println(g)
g.CutAt(10)
fmt.Println(g)
g.Reset()
fmt.Println(g)
g.DealRound()
fmt.Println(g)
}

171
gui_gio_learn/outlay/fan/cribbage/cribbage.go

@ -0,0 +1,171 @@
package cribbage
import (
"fmt"
"math/rand"
"gioui.org/example/outlay/fan/playing"
)
type Phase uint8
const (
BetweenHands Phase = iota
Dealing
Sacrifice
Cut
CircularCount
CountHands
CountCrib
)
func (p Phase) String() string {
switch p {
case BetweenHands:
return "between"
case Dealing:
return "dealing"
case Sacrifice:
return "sacrifice"
case Cut:
return "cut"
case CircularCount:
return "circular count"
case CountHands:
return "count hands"
case CountCrib:
return "count crib"
default:
return "unknown"
}
}
type Game struct {
Phase
Deck []playing.Card
CutCard *playing.Card
Dealer int
Crib []playing.Card
Players []Player
}
type Player struct {
Hand, Table []playing.Card
}
func (p Player) String() string {
return fmt.Sprintf("[Hand: %s, Table: %s]", p.Hand, p.Table)
}
func (g Game) String() string {
return fmt.Sprintf("[Phase: %v\nDealer: %v\nCrib: %v\nCut: %v\nPlayers: %v\nDeck: %v]\n", g.Phase, g.Dealer, g.Crib, g.CutCard, g.Players, g.Deck)
}
const MinHand = 4
func NewGame(players int) Game {
var g Game
g.Players = make([]Player, players)
g.Dealer = g.NumPlayers() - 1
for i := 0; i < 4; i++ {
for j := 0; j < 13; j++ {
g.Deck = append(g.Deck, playing.Card{
Suit: playing.Suit(i),
Rank: playing.Rank(j),
})
}
}
g.Phase = Dealing
return g
}
func (g Game) NumPlayers() int {
return len(g.Players)
}
func (g Game) Right(player int) int {
return (player + g.NumPlayers() - 1) % g.NumPlayers()
}
func (g Game) Left(player int) int {
return (player + 1) % g.NumPlayers()
}
func (g *Game) CutAt(depth int) {
g.CutCard = &g.Deck[depth]
g.Phase = CircularCount
}
func DrainInto(src, dest *[]playing.Card) {
for _, c := range *src {
*dest = append(*dest, c)
}
*src = (*src)[:0]
}
func (g *Game) Reset() {
for i := range g.Players {
DrainInto(&(g.Players[i].Hand), &g.Deck)
DrainInto(&(g.Players[i].Table), &g.Deck)
}
DrainInto(&(g.Crib), &g.Deck)
g.Phase = Dealing
g.CutCard = nil
}
func (g *Game) DealCardTo(dest *[]playing.Card) {
card := g.Deck[0]
g.Deck = g.Deck[1:]
*dest = append(*dest, card)
}
func (g *Game) DealRound() {
g.Dealer = g.Left(g.Dealer)
g.Reset()
g.Shuffle()
for i := 0; i < g.CardsToDealPerPlayer(); i++ {
for i := range g.Players {
g.DealCardTo(&(g.Players[i].Hand))
}
}
for i := 0; i < g.CardsDealtToCrib(); i++ {
g.DealCardTo(&g.Crib)
}
g.Phase = Sacrifice
}
func (g Game) CardsToDealPerPlayer() int {
switch g.NumPlayers() {
case 2:
return 6
case 3:
return 5
case 4:
return 5
default:
return 0
}
}
func (g Game) CardsDealtToCrib() int {
if g.NumPlayers() == 3 {
return 1
}
return 0
}
func (g *Game) Shuffle() {
rand.Shuffle(len(g.Deck), func(i, j int) {
g.Deck[i], g.Deck[j] = g.Deck[j], g.Deck[i]
})
}
func (g *Game) Sacrifice(player, card int) {
hand := g.Players[player].Hand
if len(hand) <= MinHand {
return
}
c := hand[card]
g.Players[player].Hand = append(hand[:card], hand[card+1:]...)
g.Crib = append(g.Crib, c)
}

163
gui_gio_learn/outlay/fan/main.go

@ -0,0 +1,163 @@
package main
import (
"log"
"math"
"math/rand"
"os"
"time"
"gioui.org/app"
"gioui.org/example/outlay/fan/playing"
xwidget "gioui.org/example/outlay/fan/widget"
"gioui.org/example/outlay/fan/widget/boring"
"gioui.org/font/gofont"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/outlay"
)
type (
C = layout.Context
D = layout.Dimensions
)
func main() {
go func() {
w := app.NewWindow()
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func genCards(th *material.Theme) []boring.HoverCard {
cards := []boring.HoverCard{}
max := 30
deck := playing.Deck()
rand.Shuffle(len(deck), func(i, j int) {
deck[i], deck[j] = deck[j], deck[i]
})
for i := 0; i < max; i++ {
cards = append(cards, boring.HoverCard{
CardStyle: boring.CardStyle{
Card: deck[i],
Theme: th,
Height: unit.Dp(200),
},
HoverState: &xwidget.HoverState{},
})
}
return cards
}
func loop(w *app.Window) error {
th := material.NewTheme(gofont.Collection())
fan := outlay.Fan{
Animation: outlay.Animation{
Duration: time.Second / 4,
},
WidthRadians: math.Pi,
OffsetRadians: 2 * math.Pi,
}
numCards := widget.Float{}
numCards.Value = 1.0
var width, offset, radius widget.Float
var useRadius widget.Bool
cardChildren := []outlay.FanItem{}
cards := genCards(th)
for i := range cards {
cardChildren = append(cardChildren, outlay.Item(i == 5, cards[i].Layout))
}
var ops op.Ops
for {
e := <-w.Events()
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
for i := range cards {
cardChildren[i].Elevate = cards[i].Hovering(gtx)
}
visibleCards := int(math.Round(float64(numCards.Value*float32(len(cardChildren)-1)))) + 1
fan.OffsetRadians = offset.Value * 2 * math.Pi
fan.WidthRadians = width.Value * 2 * math.Pi
if useRadius.Changed() || radius.Changed() {
if useRadius.Value {
r := cards[0].Height.Scale(radius.Value * 2)
fan.HollowRadius = &r
} else {
fan.HollowRadius = nil
}
}
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return material.Body1(th, "1").Layout(gtx)
}),
layout.Flexed(1, func(gtx C) D {
return material.Slider(th, &numCards, 0.0, 1.0).Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
return material.Body1(th, "10").Layout(gtx)
}),
)
}),
layout.Rigid(func(gtx C) D {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return material.Body1(th, "width 0").Layout(gtx)
}),
layout.Flexed(1, func(gtx C) D {
return material.Slider(th, &width, 0.0, 1.0).Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
return material.Body1(th, "2pi").Layout(gtx)
}),
)
}),
layout.Rigid(func(gtx C) D {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return material.Body1(th, "offset 0").Layout(gtx)
}),
layout.Flexed(1, func(gtx C) D {
return material.Slider(th, &offset, 0.0, 1.0).Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
return material.Body1(th, "2pi").Layout(gtx)
}),
)
}),
layout.Rigid(func(gtx C) D {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return material.CheckBox(th, &useRadius, "use").Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
return material.Body1(th, "radius 0%").Layout(gtx)
}),
layout.Flexed(1, func(gtx C) D {
return material.Slider(th, &radius, 0.0, 1.0).Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
return material.Body1(th, "200%").Layout(gtx)
}),
)
}),
layout.Flexed(1, func(gtx C) D {
return fan.Layout(gtx, cardChildren[:visibleCards]...)
}),
)
e.Frame(gtx.Ops)
}
}
}

116
gui_gio_learn/outlay/fan/playing/card.go

@ -0,0 +1,116 @@
/*
Package playing provides types for modeling a deck of conventional
playing cards.
*/
package playing
type Suit uint8
type Rank uint8
type Color bool
const (
Spades Suit = iota
Clubs
Hearts
Diamonds
UnknownSuit
)
const (
Ace Rank = iota
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Jack
Queen
King
UnknownRank
)
const (
Red Color = true
Black Color = false
)
type Card struct {
Suit
Rank
}
func Deck() []Card {
d := make([]Card, 0, 52)
for i := 0; i < 4; i++ {
for k := 0; k < 13; k++ {
d = append(d, Card{
Suit: Suit(i),
Rank: Rank(k),
})
}
}
return d
}
func (r Rank) String() string {
switch r {
case Ace:
return "A"
case Two:
return "2"
case Three:
return "3"
case Four:
return "4"
case Five:
return "5"
case Six:
return "6"
case Seven:
return "7"
case Eight:
return "8"
case Nine:
return "9"
case Ten:
return "10"
case Jack:
return "J"
case Queen:
return "Q"
case King:
return "K"
default:
return "?"
}
}
func (s Suit) String() string {
switch s {
case Spades:
return "♠"
case Hearts:
return "♥"
case Diamonds:
return "♦"
case Clubs:
return "♣"
default:
return "?"
}
}
func (s Suit) Color() Color {
switch s {
case Spades, Clubs:
return Black
case Hearts, Diamonds:
return Red
default:
return Black
}
}

142
gui_gio_learn/outlay/fan/widget/boring/cards.go

@ -0,0 +1,142 @@
package boring
import (
"image/color"
"math"
"gioui.org/example/outlay/fan/playing"
xwidget "gioui.org/example/outlay/fan/widget"
"gioui.org/f32"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
"gioui.org/widget/material"
)
type (
C = layout.Context
D = layout.Dimensions
)
type CardPalette struct {
RedSuit, BlackSuit color.NRGBA
Border, Background color.NRGBA
}
func (p CardPalette) ColorFor(s playing.Suit) color.NRGBA {
if s.Color() == playing.Red {
return p.RedSuit
}
return p.BlackSuit
}
var DefaultPalette = &CardPalette{
RedSuit: color.NRGBA{R: 0xa0, B: 0x20, A: 0xff},
BlackSuit: color.NRGBA{A: 0xff},
Border: color.NRGBA{R: 0x80, G: 0x80, B: 0x80, A: 0xff},
Background: color.NRGBA{R: 0xf0, G: 0xf0, B: 0xf0, A: 0xff},
}
type CardStyle struct {
*material.Theme
playing.Card
Height unit.Value
*CardPalette
}
const cardHeightToWidth = 14.0 / 9.0
const cardRadiusToWidth = 1.0 / 16.0
const borderWidth = 0.005
func (c *CardStyle) Palette() *CardPalette {
if c.CardPalette == nil {
return DefaultPalette
}
return c.CardPalette
}
func (c *CardStyle) Layout(gtx C) D {
gtx.Constraints.Max.Y = gtx.Px(c.Height)
gtx.Constraints.Max.X = int(float32(gtx.Constraints.Max.Y) / cardHeightToWidth)
outerRadius := float32(gtx.Constraints.Max.X) * cardRadiusToWidth
innerRadius := (1 - borderWidth) * outerRadius
borderWidth := c.Height.Scale(borderWidth)
return layout.Stack{}.Layout(gtx,
layout.Expanded(func(gtx C) D {
return Rect{
Color: c.Palette().Border,
Size: layout.FPt(gtx.Constraints.Max),
Radii: outerRadius,
}.Layout(gtx)
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(borderWidth).Layout(gtx, func(gtx C) D {
return layout.Stack{}.Layout(gtx,
layout.Expanded(func(gtx C) D {
return Rect{
Color: c.Palette().Background,
Size: layout.FPt(gtx.Constraints.Max),
Radii: innerRadius,
}.Layout(gtx)
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx C) D {
gtx.Constraints.Min = gtx.Constraints.Max
origin := f32.Point{
X: float32(gtx.Constraints.Max.X / 2),
Y: float32(gtx.Constraints.Max.Y / 2),
}
layout.Center.Layout(gtx, func(gtx C) D {
face := material.H1(c.Theme, c.Rank.String())
face.Color = c.Palette().ColorFor(c.Suit)
return face.Layout(gtx)
})
c.layoutCorner(gtx)
defer op.Affine(f32.Affine2D{}.Rotate(origin, math.Pi)).Push(gtx.Ops).Pop()
c.layoutCorner(gtx)
return D{Size: gtx.Constraints.Max}
})
}),
)
})
}),
)
}
func (c *CardStyle) layoutCorner(gtx layout.Context) layout.Dimensions {
col := c.Palette().ColorFor(c.Suit)
return layout.NW.Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
return layout.Flex{
Axis: layout.Vertical,
Alignment: layout.Middle,
}.Layout(gtx,
layout.Rigid(func(gtx C) D {
label := material.H6(c.Theme, c.Rank.String())
label.Color = col
return label.Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
label := material.H6(c.Theme, c.Suit.String())
label.Color = col
return label.Layout(gtx)
}),
)
})
})
}
type HoverCard struct {
CardStyle
*xwidget.HoverState
}
func (h HoverCard) Layout(gtx C) D {
dims := h.CardStyle.Layout(gtx)
gtx.Constraints.Max = dims.Size
gtx.Constraints.Min = dims.Size
h.HoverState.Layout(gtx)
return dims
}

34
gui_gio_learn/outlay/fan/widget/boring/rect.go

@ -0,0 +1,34 @@
package boring
import (
"image"
"image/color"
"gioui.org/f32"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/op/paint"
)
// Rect creates a rectangle of the provided background color with
// Dimensions specified by size and a corner radius (on all corners)
// specified by radii.
type Rect struct {
Color color.NRGBA
Size f32.Point
Radii float32
}
// Layout renders the Rect into the provided context
func (r Rect) Layout(gtx C) D {
return DrawRect(gtx, r.Color, r.Size, r.Radii)
}
// DrawRect creates a rectangle of the provided background color with
// Dimensions specified by size and a corner radius (on all corners)
// specified by radii.
func DrawRect(gtx C, background color.NRGBA, size f32.Point, radii float32) D {
bounds := f32.Rectangle{Max: size}
paint.FillShape(gtx.Ops, background, clip.UniformRRect(bounds, radii).Op(gtx.Ops))
return layout.Dimensions{Size: image.Pt(int(size.X), int(size.Y))}
}

49
gui_gio_learn/outlay/fan/widget/state.go

@ -0,0 +1,49 @@
package widget
import (
"image"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
)
type (
C = layout.Context
D = layout.Dimensions
)
type HoverState struct {
hovering bool
}
func (c *HoverState) Hovering(gtx C) bool {
start := c.hovering
for _, ev := range gtx.Events(c) {
switch ev := ev.(type) {
case pointer.Event:
switch ev.Type {
case pointer.Enter:
c.hovering = true
case pointer.Leave:
c.hovering = false
case pointer.Cancel:
c.hovering = false
}
}
}
if c.hovering != start {
op.InvalidateOp{}.Add(gtx.Ops)
}
return c.hovering
}
func (c *HoverState) Layout(gtx C) D {
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
pointer.InputOp{
Tag: c,
Types: pointer.Enter | pointer.Leave,
}.Add(gtx.Ops)
return D{Size: gtx.Constraints.Max}
}

223
gui_gio_learn/outlay/grid/main.go

@ -0,0 +1,223 @@
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"strconv"
"gioui.org/app"
"gioui.org/f32"
"gioui.org/font/gofont"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/outlay"
)
func main() {
go func() {
w := app.NewWindow(
app.Size(unit.Dp(800), unit.Dp(400)),
app.Title("Gio layouts"),
)
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func loop(w *app.Window) error {
ui := newUI()
var ops op.Ops
for e := range w.Events() {
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
ui.Layout(gtx)
e.Frame(gtx.Ops)
}
}
return nil
}
type UI struct {
theme *material.Theme
active int
tabs []uiTab
list layout.List
}
type uiTab struct {
name string
click widget.Clickable
text string
w func(tab *uiTab, gtx layout.Context) layout.Dimensions
num int
ed widget.Editor
}
var (
vWrap = outlay.GridWrap{
Axis: layout.Vertical,
Alignment: layout.End,
}
hWrap = outlay.GridWrap{
Axis: layout.Horizontal,
Alignment: layout.End,
}
vGrid = outlay.Grid{
Num: 11,
Axis: layout.Vertical,
}
hGrid = outlay.Grid{
Num: 11,
Axis: layout.Horizontal,
}
)
func newUI() *UI {
ui := &UI{
theme: material.NewTheme(gofont.Collection()),
list: layout.List{
Axis: layout.Horizontal,
Alignment: layout.Baseline,
},
}
ui.tabs = append(ui.tabs,
uiTab{
name: "V wrap",
text: "Lay out items vertically before wrapping to the next column.",
w: func(tab *uiTab, gtx layout.Context) layout.Dimensions {
return vWrap.Layout(gtx, tab.num, func(gtx layout.Context, i int) layout.Dimensions {
s := fmt.Sprintf("item %d", i)
return material.Body1(ui.theme, s).Layout(gtx)
})
},
},
uiTab{
name: "H wrap",
text: "Lay out items horizontally before wrapping to the next row.",
w: func(tab *uiTab, gtx layout.Context) layout.Dimensions {
return hWrap.Layout(gtx, tab.num, func(gtx layout.Context, i int) layout.Dimensions {
s := fmt.Sprintf("item %d", i)
return material.Body1(ui.theme, s).Layout(gtx)
})
},
},
uiTab{
name: "V grid",
text: fmt.Sprintf("Lay out %d items vertically before going to the next column.", vGrid.Num),
w: func(tab *uiTab, gtx layout.Context) layout.Dimensions {
return vGrid.Layout(gtx, tab.num, func(gtx layout.Context, i int) layout.Dimensions {
s := fmt.Sprintf("item %d", i)
return material.Body1(ui.theme, s).Layout(gtx)
})
},
},
uiTab{
name: "H grid",
text: fmt.Sprintf("Lay out %d items horizontally before going to the next row.", hGrid.Num),
w: func(tab *uiTab, gtx layout.Context) layout.Dimensions {
return hGrid.Layout(gtx, tab.num, func(gtx layout.Context, i int) layout.Dimensions {
s := fmt.Sprintf("item %d", i)
return material.Body1(ui.theme, s).Layout(gtx)
})
},
},
)
for i := range ui.tabs {
tab := &ui.tabs[i]
tab.ed = widget.Editor{
SingleLine: true,
Submit: true,
}
tab.num = 99
tab.ed.SetText(strconv.Itoa(tab.num))
}
return ui
}
func (ui *UI) Layout(gtx layout.Context) layout.Dimensions {
for i := range ui.tabs {
for ui.tabs[i].click.Clicked() {
ui.active = i
}
}
activeTab := &ui.tabs[ui.active]
return layout.Flex{
Axis: layout.Vertical,
}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return ui.list.Layout(gtx, len(ui.tabs), func(gtx layout.Context, idx int) layout.Dimensions {
tab := &ui.tabs[idx]
title := func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(6)).Layout(gtx, material.H6(ui.theme, tab.name).Layout)
}
if idx != ui.active {
return material.Clickable(gtx, &tab.click, title)
}
return layout.Stack{}.Layout(gtx,
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
defer clip.UniformRRect(f32.Rectangle{
Max: layout.FPt(gtx.Constraints.Min),
}, 0).Push(gtx.Ops).Pop()
paint.Fill(gtx.Ops, color.NRGBA{A: 64})
return layout.Dimensions{}
}),
layout.Stacked(title),
)
})
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
pt := image.Point{X: gtx.Constraints.Max.X, Y: 4}
defer clip.UniformRRect(f32.Rectangle{
Max: layout.FPt(pt),
}, 0).Push(gtx.Ops).Pop()
paint.Fill(gtx.Ops, ui.theme.Palette.ContrastBg)
return layout.Dimensions{Size: pt}
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Stack{}.Layout(gtx,
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
defer clip.UniformRRect(f32.Rectangle{
Max: layout.FPt(image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Min.Y)),
}, 0).Push(gtx.Ops).Pop()
paint.Fill(gtx.Ops, color.NRGBA{A: 24})
return layout.Dimensions{}
}),
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
if x, _ := strconv.Atoi(activeTab.ed.Text()); x != activeTab.num {
activeTab.num = x
}
return layout.Flex{
Alignment: layout.Baseline,
}.Layout(gtx,
layout.Rigid(material.Body1(ui.theme, activeTab.text).Layout),
layout.Rigid(material.Body1(ui.theme, " Num = ").Layout),
layout.Rigid(material.Editor(ui.theme, &activeTab.ed, "").Layout),
)
})
}),
)
}),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return activeTab.w(activeTab, gtx)
}),
)
}

166
gui_gio_learn/outlay/table/main.go

@ -0,0 +1,166 @@
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"strconv"
"gioui.org/app"
"gioui.org/font/gofont"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/outlay"
)
func main() {
go func() {
w := app.NewWindow(
app.Size(unit.Dp(800), unit.Dp(400)),
app.Title("Gio layouts"),
)
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func loop(w *app.Window) error {
var table = uiTable{
theme: material.NewTheme(gofont.Collection()),
Table: outlay.Table{
CellSize: func(m unit.Metric, x, y int) image.Point {
return image.Pt(m.Px(unit.Dp(50)), m.Px(unit.Dp(30)))
},
},
}
var ops op.Ops
for e := range w.Events() {
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
table.Layout(gtx)
e.Frame(gtx.Ops)
}
}
return nil
}
type uiTable struct {
theme *material.Theme
xed, yed widget.Editor
cells []cell
outlay.Table
}
type cell struct {
click widget.Clickable
clicked bool
}
func (t *uiTable) Layout(gtx layout.Context) layout.Dimensions {
th := t.theme
return layout.Flex{
Axis: layout.Vertical,
}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{
Axis: layout.Horizontal,
Spacing: layout.SpaceSides,
}.Layout(gtx,
layout.Rigid(material.Body1(th, "Number of columns: ").Layout),
layout.Rigid(material.Editor(th, &t.xed, "columns").Layout),
)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{
Axis: layout.Horizontal,
Spacing: layout.SpaceSides,
}.Layout(gtx,
layout.Rigid(material.Body1(th, "Number of rows: ").Layout),
layout.Rigid(material.Editor(th, &t.yed, "rows").Layout),
)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{
Axis: layout.Horizontal,
Spacing: layout.SpaceSides,
}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
var selected int
for i := range t.cells {
if t.cells[i].clicked {
selected++
}
}
var txt string
switch selected {
case 0:
txt = "Click cells to select them."
case 1:
txt = fmt.Sprint("1 cell selected. Click again to unselect.")
default:
txt = fmt.Sprintf("%d cells selected. Click again to unselect.", selected)
}
return material.Body1(th, txt).Layout(gtx)
}),
)
}),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
xn, _ := strconv.Atoi(t.xed.Text())
yn, _ := strconv.Atoi(t.yed.Text())
t.cells = growCells(t.cells, xn*yn)
return t.Table.Layout(gtx, xn, yn, func(gtx layout.Context, x, y int) layout.Dimensions {
c := &t.cells[x+y*xn]
return c.Layout(gtx, th, x, y)
})
}),
)
}
func (c *cell) Layout(gtx layout.Context, th *material.Theme, x, y int) layout.Dimensions {
var txt string
if y < 0 {
txt = fmt.Sprintf("item %d", x)
} else {
txt = fmt.Sprintf("%dx%d", x, y)
}
macro := op.Record(gtx.Ops)
dims := material.Clickable(gtx, &c.click, func(gtx layout.Context) layout.Dimensions {
return layout.Center.Layout(gtx, material.Body1(th, txt).Layout)
})
call := macro.Stop()
if c.click.Clicked() {
c.clicked = !c.clicked
}
defer clip.Rect{Max: dims.Size}.Push(gtx.Ops).Pop()
col := color.NRGBA{R: 255, G: 255, B: 255, A: 255}
if c.clicked {
col = color.NRGBA{R: 128, G: 128, B: 128, A: 255}
}
paint.Fill(gtx.Ops, col)
call.Add(gtx.Ops)
return dims
}
func growCells(cells []cell, n int) []cell {
if len(cells) < n {
cells = append(cells, make([]cell, n-len(cells))...)
}
return cells[:n]
}

132
gui_gio_learn/tabs/slider.go

@ -0,0 +1,132 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"time"
"gioui.org/f32"
"gioui.org/layout"
"gioui.org/op"
)
const defaultDuration = 300 * time.Millisecond
// Slider implements sliding between old/new widget values.
type Slider struct {
Duration time.Duration
push int
next *op.Ops
nextCall op.CallOp
lastCall op.CallOp
t0 time.Time
offset float32
}
// PushLeft pushes the existing widget to the left.
func (s *Slider) PushLeft() { s.push = 1 }
// PushRight pushes the existing widget to the right.
func (s *Slider) PushRight() { s.push = -1 }
// Layout lays out widget that can be pushed.
func (s *Slider) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
if s.push != 0 {
s.next = nil
s.lastCall = s.nextCall
s.offset = float32(s.push)
s.t0 = gtx.Now
s.push = 0
}
var delta time.Duration
if !s.t0.IsZero() {
now := gtx.Now
delta = now.Sub(s.t0)
s.t0 = now
}
if s.offset != 0 {
duration := s.Duration
if duration == 0 {
duration = defaultDuration
}
movement := float32(delta.Seconds()) / float32(duration.Seconds())
if s.offset < 0 {
s.offset += movement
if s.offset >= 0 {
s.offset = 0
}
} else {
s.offset -= movement
if s.offset <= 0 {
s.offset = 0
}
}
op.InvalidateOp{}.Add(gtx.Ops)
}
var dims layout.Dimensions
{
if s.next == nil {
s.next = new(op.Ops)
}
gtx := gtx
gtx.Ops = s.next
gtx.Ops.Reset()
m := op.Record(gtx.Ops)
dims = w(gtx)
s.nextCall = m.Stop()
}
if s.offset == 0 {
s.nextCall.Add(gtx.Ops)
return dims
}
offset := smooth(s.offset)
if s.offset > 0 {
defer op.Offset(f32.Point{
X: float32(dims.Size.X) * (offset - 1),
}).Push(gtx.Ops).Pop()
s.lastCall.Add(gtx.Ops)
defer op.Offset(f32.Point{
X: float32(dims.Size.X),
}).Push(gtx.Ops).Pop()
s.nextCall.Add(gtx.Ops)
} else {
defer op.Offset(f32.Point{
X: float32(dims.Size.X) * (offset + 1),
}).Push(gtx.Ops).Pop()
s.lastCall.Add(gtx.Ops)
defer op.Offset(f32.Point{
X: float32(-dims.Size.X),
}).Push(gtx.Ops).Pop()
s.nextCall.Add(gtx.Ops)
}
return dims
}
// smooth handles -1 to 1 with ease-in-out cubic easing func.
func smooth(t float32) float32 {
if t < 0 {
return -easeInOutCubic(-t)
}
return easeInOutCubic(t)
}
// easeInOutCubic maps a linear value to a ease-in-out-cubic easing function.
func easeInOutCubic(t float32) float32 {
if t < 0.5 {
return 4 * t * t * t
}
return (t-1)*(2*t-2)*(2*t-2) + 1
}

158
gui_gio_learn/tabs/tabs.go

@ -0,0 +1,158 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"fmt"
"image"
"image/color"
"log"
"math"
"os"
"gioui.org/app"
"gioui.org/f32"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/font/gofont"
)
func main() {
go func() {
defer os.Exit(0)
w := app.NewWindow()
if err := loop(w); err != nil {
log.Fatal(err)
}
}()
app.Main()
}
func loop(w *app.Window) error {
th := material.NewTheme(gofont.Collection())
var ops op.Ops
for {
e := <-w.Events()
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
drawTabs(gtx, th)
e.Frame(gtx.Ops)
}
}
}
var tabs Tabs
var slider Slider
type Tabs struct {
list layout.List
tabs []Tab
selected int
}
type Tab struct {
btn widget.Clickable
Title string
}
func init() {
for i := 1; i <= 100; i++ {
tabs.tabs = append(tabs.tabs,
Tab{Title: fmt.Sprintf("Tab %d", i)},
)
}
}
type (
C = layout.Context
D = layout.Dimensions
)
func drawTabs(gtx layout.Context, th *material.Theme) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return tabs.list.Layout(gtx, len(tabs.tabs), func(gtx C, tabIdx int) D {
t := &tabs.tabs[tabIdx]
if t.btn.Clicked() {
if tabs.selected < tabIdx {
slider.PushLeft()
} else if tabs.selected > tabIdx {
slider.PushRight()
}
tabs.selected = tabIdx
}
var tabWidth int
return layout.Stack{Alignment: layout.S}.Layout(gtx,
layout.Stacked(func(gtx C) D {
dims := material.Clickable(gtx, &t.btn, func(gtx C) D {
return layout.UniformInset(unit.Sp(12)).Layout(gtx,
material.H6(th, t.Title).Layout,
)
})
tabWidth = dims.Size.X
return dims
}),
layout.Stacked(func(gtx C) D {
if tabs.selected != tabIdx {
return layout.Dimensions{}
}
tabHeight := gtx.Px(unit.Dp(4))
tabRect := image.Rect(0, 0, tabWidth, tabHeight)
paint.FillShape(gtx.Ops, th.Palette.ContrastBg, clip.Rect(tabRect).Op())
return layout.Dimensions{
Size: image.Point{X: tabWidth, Y: tabHeight},
}
}),
)
})
}),
layout.Flexed(1, func(gtx C) D {
return slider.Layout(gtx, func(gtx C) D {
fill(gtx, dynamicColor(tabs.selected), dynamicColor(tabs.selected+1))
return layout.Center.Layout(gtx,
material.H1(th, fmt.Sprintf("Tab content #%d", tabs.selected+1)).Layout,
)
})
}),
)
}
func fill(gtx layout.Context, col1, col2 color.NRGBA) {
dr := image.Rectangle{Max: gtx.Constraints.Min}
paint.FillShape(gtx.Ops,
color.NRGBA{R: 0, G: 0, B: 0, A: 0xFF},
clip.Rect(dr).Op(),
)
col2.R = byte(float32(col2.R))
col2.G = byte(float32(col2.G))
col2.B = byte(float32(col2.B))
paint.LinearGradientOp{
Stop1: f32.Pt(float32(dr.Min.X), 0),
Stop2: f32.Pt(float32(dr.Max.X), 0),
Color1: col1,
Color2: col2,
}.Add(gtx.Ops)
defer clip.Rect(dr).Push(gtx.Ops).Pop()
paint.PaintOp{}.Add(gtx.Ops)
}
func dynamicColor(i int) color.NRGBA {
sn, cs := math.Sincos(float64(i) * math.Phi)
return color.NRGBA{
R: 0xA0 + byte(0x30*sn),
G: 0xA0 + byte(0x30*cs),
B: 0xD0,
A: 0xFF,
}
}
Loading…
Cancel
Save