mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35: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