mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 09:55:40 +00:00
example: delete
Examples now live in the https://git.sr.ht/~eliasnaur/gio-example repository. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -1,169 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
// GLFW doesn't build on OpenBSD and FreeBSD.
|
|
||||||
// +build !openbsd,!freebsd,!windows,!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"
|
|
||||||
giogl "gioui.org/gpu/gl"
|
|
||||||
"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.3-core/gl"
|
|
||||||
"github.com/go-gl/glfw/v3.3/glfw"
|
|
||||||
)
|
|
||||||
|
|
||||||
type glfwConfig struct {
|
|
||||||
Scale float32
|
|
||||||
}
|
|
||||||
|
|
||||||
type goglFunctions struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
window, err := glfw.CreateWindow(800, 600, "Gio + GLFW", nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
window.MakeContextCurrent()
|
|
||||||
|
|
||||||
if err := gl.Init(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// Enable sRGB.
|
|
||||||
gl.Enable(gl.FRAMEBUFFER_SRGB)
|
|
||||||
|
|
||||||
var queue router.Router
|
|
||||||
var ops op.Ops
|
|
||||||
th := material.NewTheme(gofont.Collection())
|
|
||||||
backend, err := giogl.NewBackend(nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
gpu, err := gpu.New(backend)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCallbacks(window, &queue)
|
|
||||||
for !window.ShouldClose() {
|
|
||||||
glfw.PollEvents()
|
|
||||||
scale := float32(1.0)
|
|
||||||
if monitor := window.GetMonitor(); monitor != nil {
|
|
||||||
scalex, _ := window.GetMonitor().GetContentScale()
|
|
||||||
scale = scalex
|
|
||||||
}
|
|
||||||
width, height := window.GetSize()
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
draw(gtx, th)
|
|
||||||
gpu.Collect(sz, gtx.Ops)
|
|
||||||
gpu.BeginFrame()
|
|
||||||
queue.Frame(gtx.Ops)
|
|
||||||
gpu.EndFrame()
|
|
||||||
window.SwapBuffers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var button widget.Clickable
|
|
||||||
|
|
||||||
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) {
|
|
||||||
lastPos = f32.Point{X: float32(xpos), Y: float32(ypos)}
|
|
||||||
q.Add(pointer.Event{
|
|
||||||
Type: pointer.Move,
|
|
||||||
Position: lastPos,
|
|
||||||
Source: pointer.Mouse,
|
|
||||||
Time: time.Since(beginning),
|
|
||||||
Buttons: btns,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
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.ButtonLeft
|
|
||||||
case glfw.MouseButton2:
|
|
||||||
btn = pointer.ButtonRight
|
|
||||||
case glfw.MouseButton3:
|
|
||||||
btn = pointer.ButtonMiddle
|
|
||||||
}
|
|
||||||
var typ pointer.Type
|
|
||||||
switch action {
|
|
||||||
case glfw.Release:
|
|
||||||
typ = pointer.Release
|
|
||||||
btns &^= btn
|
|
||||||
case glfw.Press:
|
|
||||||
typ = pointer.Press
|
|
||||||
btns |= btn
|
|
||||||
}
|
|
||||||
q.Add(pointer.Event{
|
|
||||||
Type: typ,
|
|
||||||
Source: pointer.Mouse,
|
|
||||||
Time: time.Since(beginning),
|
|
||||||
Position: lastPos,
|
|
||||||
Buttons: btns,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *glfwConfig) Px(v unit.Value) int {
|
|
||||||
scale := s.Scale
|
|
||||||
if v.U == unit.UnitPx {
|
|
||||||
scale = 1
|
|
||||||
}
|
|
||||||
return int(math.Round(float64(scale * v.V)))
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
module gioui.org/example
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
||||||
require (
|
|
||||||
gioui.org v0.0.0-20201206220230-a87a520ae825
|
|
||||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72
|
|
||||||
github.com/google/go-github/v24 v24.0.1
|
|
||||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3
|
|
||||||
golang.org/x/image v0.0.0-20200618115811-c13761719519
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
|
||||||
)
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
gioui.org v0.0.0-20201206220230-a87a520ae825 h1:8eQeFlQ0IL5sOX74YcwEBk3OtGNTRCqIU3Rz0z0U6vE=
|
|
||||||
gioui.org v0.0.0-20201206220230-a87a520ae825/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
|
|
||||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/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 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
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=
|
|
||||||
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs=
|
|
||||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
|
|
||||||
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-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
|
|
||||||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/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-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
|
|
||||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
// 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
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,424 +0,0 @@
|
|||||||
// 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/pointer"
|
|
||||||
"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 *layout.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 *layout.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 = &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: &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)
|
|
||||||
}
|
|
||||||
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).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 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)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
pointer.Rect(image.Rectangle{Max: dims.Size}).Add(gtx.Ops)
|
|
||||||
click := &u.userClicks[index]
|
|
||||||
click.Add(gtx.Ops)
|
|
||||||
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 op.Push(gtx.Ops).Pop()
|
|
||||||
clip.RRect{
|
|
||||||
Rect: f32.Rectangle{Max: f32.Point{X: szf, Y: szf}},
|
|
||||||
NE: rr, NW: rr, SE: rr, SW: rr,
|
|
||||||
}.Add(gtx.Ops)
|
|
||||||
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.`
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,431 +0,0 @@
|
|||||||
// 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/app/headless"
|
|
||||||
"gioui.org/f32"
|
|
||||||
"gioui.org/font/gofont"
|
|
||||||
"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 int)
|
|
||||||
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 <- 10
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
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, err := w.Screenshot()
|
|
||||||
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 > 100 {
|
|
||||||
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)
|
|
||||||
defer op.Push(gtx.Ops).Pop()
|
|
||||||
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 = &layout.List{
|
|
||||||
Axis: layout.Vertical,
|
|
||||||
}
|
|
||||||
progress = 0
|
|
||||||
progressIncrementer chan int
|
|
||||||
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 icon != nil {
|
|
||||||
size := gtx.Px(unit.Dp(56)) - 2*gtx.Px(unit.Dp(16))
|
|
||||||
b.icon.Layout(gtx, unit.Px(float32(size)))
|
|
||||||
d = layout.Dimensions{
|
|
||||||
Size: image.Point{X: size, Y: size},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
defer op.Push(gtx.Ops).Pop()
|
|
||||||
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)
|
|
||||||
clip.Rect(dr).Add(gtx.Ops)
|
|
||||||
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).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
|
|
||||||
}
|
|
||||||
return material.Button(th, button, "Click me!").Layout(gtx)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
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).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 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.`
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gioui.org/app/headless"
|
|
||||||
"gioui.org/f32"
|
|
||||||
"gioui.org/font/gofont"
|
|
||||||
"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)
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
defer op.Push(gtx.Ops).Pop()
|
|
||||||
|
|
||||||
offset := smooth(s.offset)
|
|
||||||
|
|
||||||
if s.offset > 0 {
|
|
||||||
op.Offset(f32.Point{
|
|
||||||
X: float32(dims.Size.X) * (offset - 1),
|
|
||||||
}).Add(gtx.Ops)
|
|
||||||
s.lastCall.Add(gtx.Ops)
|
|
||||||
|
|
||||||
op.Offset(f32.Point{
|
|
||||||
X: float32(dims.Size.X),
|
|
||||||
}).Add(gtx.Ops)
|
|
||||||
s.nextCall.Add(gtx.Ops)
|
|
||||||
} else {
|
|
||||||
op.Offset(f32.Point{
|
|
||||||
X: float32(dims.Size.X) * (offset + 1),
|
|
||||||
}).Add(gtx.Ops)
|
|
||||||
s.lastCall.Add(gtx.Ops)
|
|
||||||
|
|
||||||
op.Offset(f32.Point{
|
|
||||||
X: float32(-dims.Size.X),
|
|
||||||
}).Add(gtx.Ops)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
// 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 op.Push(gtx.Ops).Pop()
|
|
||||||
clip.Rect(dr).Add(gtx.Ops)
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
// Multiple windows in Gio.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"gioui.org/app"
|
|
||||||
"gioui.org/io/event"
|
|
||||||
"gioui.org/io/system"
|
|
||||||
"gioui.org/layout"
|
|
||||||
"gioui.org/op"
|
|
||||||
"gioui.org/unit"
|
|
||||||
"gioui.org/widget"
|
|
||||||
"gioui.org/widget/material"
|
|
||||||
|
|
||||||
"gioui.org/font/gofont"
|
|
||||||
)
|
|
||||||
|
|
||||||
type window struct {
|
|
||||||
*app.Window
|
|
||||||
|
|
||||||
more widget.Clickable
|
|
||||||
close widget.Clickable
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
newWindow()
|
|
||||||
app.Main()
|
|
||||||
}
|
|
||||||
|
|
||||||
var windowCount int32
|
|
||||||
|
|
||||||
func newWindow() {
|
|
||||||
atomic.AddInt32(&windowCount, +1)
|
|
||||||
go func() {
|
|
||||||
w := new(window)
|
|
||||||
w.Window = app.NewWindow()
|
|
||||||
if err := w.loop(w.Events()); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if c := atomic.AddInt32(&windowCount, -1); c == 0 {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) loop(events <-chan event.Event) error {
|
|
||||||
th := material.NewTheme(gofont.Collection())
|
|
||||||
var ops op.Ops
|
|
||||||
for {
|
|
||||||
e := <-events
|
|
||||||
switch e := e.(type) {
|
|
||||||
case system.DestroyEvent:
|
|
||||||
return e.Err
|
|
||||||
case system.FrameEvent:
|
|
||||||
for w.more.Clicked() {
|
|
||||||
newWindow()
|
|
||||||
}
|
|
||||||
for w.close.Clicked() {
|
|
||||||
w.Close()
|
|
||||||
}
|
|
||||||
gtx := layout.NewContext(&ops, e)
|
|
||||||
|
|
||||||
layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.Flex{
|
|
||||||
Alignment: layout.Middle,
|
|
||||||
}.Layout(gtx,
|
|
||||||
RigidInset(material.Button(th, &w.more, "More!").Layout),
|
|
||||||
RigidInset(material.Button(th, &w.close, "Close").Layout),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
e.Frame(gtx.Ops)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RigidInset(w layout.Widget) layout.FlexChild {
|
|
||||||
return layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.UniformInset(unit.Sp(5)).Layout(gtx, w)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user