diff --git a/go.mod b/go.mod index 1e7ee58..e016f97 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,22 @@ module gioui.org/cmd go 1.17 require ( - gioui.org v0.0.0-20220328154813-a3f147541fd0 + gioui.org v0.0.0-20220628163331-e21c665e70ae github.com/akavel/rsrc v0.10.1 github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 github.com/chromedp/chromedp v0.5.2 golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 - golang.org/x/text v0.3.6 + golang.org/x/text v0.3.7 golang.org/x/tools v0.1.0 ) require ( gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect gioui.org/shader v1.0.6 // indirect - github.com/benoitkugler/textlayout v0.0.10 // indirect + github.com/benoitkugler/textlayout v0.1.1 // indirect github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 // indirect - github.com/go-text/typesetting v0.0.0-20220112121102-58fe93c84506 // indirect + github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b // indirect github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect github.com/gobwas/pool v0.2.0 // indirect github.com/gobwas/ws v1.0.2 // indirect diff --git a/go.sum b/go.sum index adf9991..6688e1d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ -gioui.org v0.0.0-20220328154813-a3f147541fd0 h1:n4FUiCT6P4a2wF6hwX4a5R8TpjAhu/d+3nhwZW16MAI= -gioui.org v0.0.0-20220328154813-a3f147541fd0/go.mod h1:b8vBukexG6eYuXZa14asjLAWJ+JjbZ/ophEnS2FjYUg= +eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q= +gioui.org v0.0.0-20220628163331-e21c665e70ae h1:s8Erm0/zVvi3Fbq0ijjPkRT04XxcGZWTxkxDwUBsxuQ= +gioui.org v0.0.0-20220628163331-e21c665e70ae/go.mod h1:WHoHbUjH91BJS2xkfps2AhKxji+9o3xwfsphGsCBfnM= gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= @@ -9,8 +10,9 @@ github.com/akavel/rsrc v0.10.1 h1:hCCPImjmFKVNGpeLZyTDRHEFC283DzyTXTo0cO0Rq9o= github.com/akavel/rsrc v0.10.1/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= github.com/benoitkugler/textlayout v0.0.5/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8= -github.com/benoitkugler/textlayout v0.0.10 h1:uIaQgH4pBFw1LQ0tPkfjgxo94WYcckzzQaB41L2X84w= -github.com/benoitkugler/textlayout v0.0.10/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8= +github.com/benoitkugler/textlayout v0.1.1 h1:hizE/085xAeY8q7gwV00uHR2Q27KYB2g1HW+UacXl68= +github.com/benoitkugler/textlayout v0.1.1/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= +github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk= github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 h1:QD3KxSJ59L2lxG6MXBjNHxiQO2RmxTQ3XcK+wO44WOg= github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g= github.com/chromedp/chromedp v0.5.2 h1:W8xBXQuUnd2dZK0SN/lyVwsQM7KgW+kY5HGnntms194= @@ -20,8 +22,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 h1:1bjaB/5IIicfKpP4k0s30T2WEw//Kh00zULa8DQ0cxA= github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12/go.mod h1:kDhBRTA/i3H46PVdhqcw26TdGSIj42TOKNWKY+Kipnw= -github.com/go-text/typesetting v0.0.0-20220112121102-58fe93c84506 h1:1TPz/Gn/MsXwJ6bEtI9wdkPcQYr2X3V9I+wz4wPYUdY= -github.com/go-text/typesetting v0.0.0-20220112121102-58fe93c84506/go.mod h1:R0mlTNeyszZ/tKQhbZA7SRGjx+OHsmNzgN2jTV7yZcs= +github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b h1:WINlj3ANt+CVrO2B4NGDHRlPvEWZPxjhb7z+JKypwXI= +github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b/go.mod h1:ZNYu5saGoMOqtkVH5T8onTwhzenDUVszI+5WFHJRaxQ= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= @@ -60,7 +62,6 @@ golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -93,8 +94,9 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= diff --git a/gogio/e2e_test.go b/gogio/e2e_test.go index 0b3ee97..b93a734 100644 --- a/gogio/e2e_test.go +++ b/gogio/e2e_test.go @@ -13,6 +13,7 @@ import ( "io/ioutil" "os" "os/exec" + "runtime" "strings" "testing" "time" @@ -69,27 +70,33 @@ func TestEndToEnd(t *testing.T) { t.Parallel() const ( - testdataWithGoImportPkgPath = "gioui.org/cmd/gogio/testdata" - testdataWithRelativePkgPath = "testdata/testdata.go" + testdataWithGoImportPkgPath = "gioui.org/cmd/gogio/internal/normal" + testdataWithRelativePkgPath = "internal/normal/testdata.go" + customRenderTestdataWithRelativePkgPath = "internal/custom/testdata.go" ) // Keep this list local, to not reuse TestDriver objects. subtests := []struct { - name string - driver TestDriver - pkgPath string + name string + driver TestDriver + pkgPath string + skipGeese string }{ - {"X11 using go import path", &X11TestDriver{}, testdataWithGoImportPkgPath}, - {"X11", &X11TestDriver{}, testdataWithRelativePkgPath}, + {"X11 using go import path", &X11TestDriver{}, testdataWithGoImportPkgPath, ""}, + {"X11", &X11TestDriver{}, testdataWithRelativePkgPath, ""}, + {"X11 with custom rendering", &X11TestDriver{}, customRenderTestdataWithRelativePkgPath, "openbsd"}, // Doesn't work on the builders. //{"Wayland", &WaylandTestDriver{}, testdataWithRelativePkgPath}, - {"JS", &JSTestDriver{}, testdataWithRelativePkgPath}, - {"Android", &AndroidTestDriver{}, testdataWithRelativePkgPath}, - {"Windows", &WineTestDriver{}, testdataWithRelativePkgPath}, + {"JS", &JSTestDriver{}, testdataWithRelativePkgPath, ""}, + {"Android", &AndroidTestDriver{}, testdataWithRelativePkgPath, ""}, + {"Windows", &WineTestDriver{}, testdataWithRelativePkgPath, ""}, } for _, subtest := range subtests { t.Run(subtest.name, func(t *testing.T) { subtest := subtest // copy the changing loop variable + if strings.Contains(subtest.skipGeese, runtime.GOOS) { + t.Skipf("not supported on %s", runtime.GOOS) + } t.Parallel() runEndToEndTest(t, subtest.driver, subtest.pkgPath) }) diff --git a/gogio/internal/custom/testdata.go b/gogio/internal/custom/testdata.go new file mode 100644 index 0000000..52c6207 --- /dev/null +++ b/gogio/internal/custom/testdata.go @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +//go:build linux +// +build linux + +// This program demonstrates the use of a custom OpenGL ES context with +// app.Window. +package main + +import ( + "errors" + "fmt" + "image" + "image/color" + "log" + "os" + "runtime" + "strings" + "unsafe" + + "gioui.org/app" + "gioui.org/gpu" + "gioui.org/io/pointer" + "gioui.org/io/system" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" +) + +/* +#cgo linux pkg-config: egl wayland-egl +#cgo freebsd openbsd CFLAGS: -I/usr/local/include +#cgo openbsd CFLAGS: -I/usr/X11R6/include +#cgo freebsd LDFLAGS: -L/usr/local/lib +#cgo openbsd LDFLAGS: -L/usr/X11R6/lib +#cgo freebsd openbsd LDFLAGS: -lwayland-egl +#cgo CFLAGS: -DEGL_NO_X11 +#cgo LDFLAGS: -lEGL -lGLESv2 + +#include +#include +#include +#include +#define EGL_EGLEXT_PROTOTYPES +#include + +*/ +import "C" + +func getDisplay(ve app.ViewEvent) C.EGLDisplay { + switch ve := ve.(type) { + case app.X11ViewEvent: + return C.eglGetDisplay(C.EGLNativeDisplayType(ve.Display)) + case app.WaylandViewEvent: + return C.eglGetDisplay(C.EGLNativeDisplayType(ve.Display)) + } + panic("no display available") +} + +func nativeViewFor(e app.ViewEvent, size image.Point) (C.EGLNativeWindowType, func()) { + switch e := e.(type) { + case app.X11ViewEvent: + return C.EGLNativeWindowType(uintptr(e.Window)), func() {} + case app.WaylandViewEvent: + eglWin := C.wl_egl_window_create((*C.struct_wl_surface)(e.Surface), C.int(size.X), C.int(size.Y)) + return C.EGLNativeWindowType(uintptr(unsafe.Pointer(eglWin))), func() { + C.wl_egl_window_destroy(eglWin) + } + } + panic("no native view available") +} + +type ( + C = layout.Context + D = layout.Dimensions +) + +type notifyFrame int + +const ( + notifyNone notifyFrame = iota + notifyInvalidate + notifyPrint +) + +// notify keeps track of whether we want to print to stdout to notify the user +// when a frame is ready. Initially we want to notify about the first frame. +var notify = notifyInvalidate + +type eglContext struct { + disp C.EGLDisplay + ctx C.EGLContext + surf C.EGLSurface + cleanup func() +} + +func main() { + go func() { + // Set CustomRenderer so we can provide our own rendering context. + w := app.NewWindow(app.CustomRenderer(true)) + if err := loop(w); err != nil { + log.Fatal(err) + } + os.Exit(0) + }() + app.Main() +} + +func loop(w *app.Window) error { + var ops op.Ops + var ( + ctx *eglContext + gioCtx gpu.GPU + ve app.ViewEvent + init bool + size image.Point + ) + + recreateContext := func() { + w.Run(func() { + if gioCtx != nil { + gioCtx.Release() + gioCtx = nil + } + if ctx != nil { + C.eglMakeCurrent(ctx.disp, nil, nil, nil) + ctx.Release() + ctx = nil + } + c, err := createContext(ve, size) + if err != nil { + log.Fatal(err) + } + ctx = c + }) + if ok := C.eglMakeCurrent(ctx.disp, ctx.surf, ctx.surf, ctx.ctx); ok != C.EGL_TRUE { + err := fmt.Errorf("eglMakeCurrent failed (%#x)", C.eglGetError()) + log.Fatal(err) + } + glGetString := func(e C.GLenum) string { + return C.GoString((*C.char)(unsafe.Pointer(C.glGetString(e)))) + } + fmt.Printf("GL_VERSION: %s\nGL_RENDERER: %s\n", glGetString(C.GL_VERSION), glGetString(C.GL_RENDERER)) + var err error + gioCtx, err = gpu.New(gpu.OpenGL{ES: true, Shared: true}) + if err != nil { + log.Fatal(err) + } + } + + topLeft := quarterWidget{ + color: color.NRGBA{R: 0xde, G: 0xad, B: 0xbe, A: 0xff}, + } + topRight := quarterWidget{ + color: color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}, + } + botLeft := quarterWidget{ + color: color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff}, + } + botRight := quarterWidget{ + color: color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0x80}, + } + + // eglMakeCurrent binds a context to an operating system thread. Prevent Go from switching thread. + runtime.LockOSThread() + for e := range w.Events() { + switch e := e.(type) { + case app.ViewEvent: + ve = e + init = true + if size != (image.Point{}) { + recreateContext() + } + case system.DestroyEvent: + return e.Err + case system.FrameEvent: + if init && size != e.Size { + size = e.Size + recreateContext() + } + if gioCtx == nil || !init { + break + } + // Build ops. + gtx := layout.NewContext(&ops, e) + + // Clear background to white, even on embedded platforms such as webassembly. + paint.Fill(gtx.Ops, color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}) + layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Flexed(1, func(gtx C) D { + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + // r1c1 + layout.Flexed(1, func(gtx C) D { return topLeft.Layout(gtx) }), + // r1c2 + layout.Flexed(1, func(gtx C) D { return topRight.Layout(gtx) }), + ) + }), + layout.Flexed(1, func(gtx C) D { + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + // r2c1 + layout.Flexed(1, func(gtx C) D { return botLeft.Layout(gtx) }), + // r2c2 + layout.Flexed(1, func(gtx C) D { return botRight.Layout(gtx) }), + ) + }), + ) + op.InvalidateOp{}.Add(gtx.Ops) + log.Println("frame") + + // Trigger window resize detection in ANGLE. + C.eglWaitClient() + // Draw custom OpenGL content. + drawGL() + + // Render drawing ops. + if err := gioCtx.Frame(gtx.Ops, gpu.OpenGLRenderTarget{}, e.Size); err != nil { + log.Fatal(fmt.Errorf("render failed: %v", err)) + } + + // Process non-drawing ops. + e.Frame(gtx.Ops) + switch notify { + case notifyInvalidate: + notify = notifyPrint + w.Invalidate() + case notifyPrint: + notify = notifyNone + fmt.Println("gio frame ready") + } + + if ok := C.eglSwapBuffers(ctx.disp, ctx.surf); ok != C.EGL_TRUE { + log.Fatal(fmt.Errorf("swap failed: %v", C.eglGetError())) + } + + } + } + return nil +} + +func drawGL() { + C.glClearColor(0, 0, 0, 1) + C.glClear(C.GL_COLOR_BUFFER_BIT | C.GL_DEPTH_BUFFER_BIT) +} + +func createContext(ve app.ViewEvent, size image.Point) (*eglContext, error) { + view, cleanup := nativeViewFor(ve, size) + var nilv C.EGLNativeWindowType + if view == nilv { + return nil, fmt.Errorf("failed creating native view") + } + disp := getDisplay(ve) + if disp == 0 { + return nil, fmt.Errorf("eglGetPlatformDisplay failed: 0x%x", C.eglGetError()) + } + var major, minor C.EGLint + if ok := C.eglInitialize(disp, &major, &minor); ok != C.EGL_TRUE { + return nil, fmt.Errorf("eglInitialize failed: 0x%x", C.eglGetError()) + } + exts := strings.Split(C.GoString(C.eglQueryString(disp, C.EGL_EXTENSIONS)), " ") + srgb := hasExtension(exts, "EGL_KHR_gl_colorspace") + attribs := []C.EGLint{ + C.EGL_RENDERABLE_TYPE, C.EGL_OPENGL_ES2_BIT, + C.EGL_SURFACE_TYPE, C.EGL_WINDOW_BIT, + C.EGL_BLUE_SIZE, 8, + C.EGL_GREEN_SIZE, 8, + C.EGL_RED_SIZE, 8, + C.EGL_CONFIG_CAVEAT, C.EGL_NONE, + } + if srgb { + // Some drivers need alpha for sRGB framebuffers to work. + attribs = append(attribs, C.EGL_ALPHA_SIZE, 8) + } + attribs = append(attribs, C.EGL_NONE) + var ( + cfg C.EGLConfig + numCfgs C.EGLint + ) + if ok := C.eglChooseConfig(disp, &attribs[0], &cfg, 1, &numCfgs); ok != C.EGL_TRUE { + return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", C.eglGetError()) + } + if numCfgs == 0 { + supportsNoCfg := hasExtension(exts, "EGL_KHR_no_config_context") + if !supportsNoCfg { + return nil, errors.New("eglChooseConfig returned no configs") + } + } + ctxAttribs := []C.EGLint{ + C.EGL_CONTEXT_CLIENT_VERSION, 3, + C.EGL_NONE, + } + ctx := C.eglCreateContext(disp, cfg, nil, &ctxAttribs[0]) + if ctx == nil { + return nil, fmt.Errorf("eglCreateContext failed: 0x%x", C.eglGetError()) + } + var surfAttribs []C.EGLint + if srgb { + surfAttribs = append(surfAttribs, C.EGL_GL_COLORSPACE, C.EGL_GL_COLORSPACE_SRGB) + } + surfAttribs = append(surfAttribs, C.EGL_NONE) + surf := C.eglCreateWindowSurface(disp, cfg, view, &surfAttribs[0]) + if surf == nil { + return nil, fmt.Errorf("eglCreateWindowSurface failed (0x%x)", C.eglGetError()) + } + return &eglContext{disp: disp, ctx: ctx, surf: surf, cleanup: cleanup}, nil +} + +func (c *eglContext) Release() { + if c.ctx != nil { + C.eglDestroyContext(c.disp, c.ctx) + } + if c.surf != nil { + C.eglDestroySurface(c.disp, c.surf) + } + if c.cleanup != nil { + c.cleanup() + } + *c = eglContext{} +} + +func hasExtension(exts []string, ext string) bool { + for _, e := range exts { + if ext == e { + return true + } + } + return false +} + +// quarterWidget paints a quarter of the screen with one color. When clicked, it +// turns red, going back to its normal color when clicked again. +type quarterWidget struct { + color color.NRGBA + + clicked bool +} + +var red = color.NRGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff} + +func (w *quarterWidget) Layout(gtx layout.Context) layout.Dimensions { + var color color.NRGBA + if w.clicked { + color = red + } else { + color = w.color + } + + r := image.Rectangle{Max: gtx.Constraints.Max} + paint.FillShape(gtx.Ops, color, clip.Rect(r).Op()) + + defer clip.Rect(image.Rectangle{ + Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y), + }).Push(gtx.Ops).Pop() + pointer.InputOp{ + Tag: w, + Types: pointer.Press, + }.Add(gtx.Ops) + + for _, e := range gtx.Events(w) { + if e, ok := e.(pointer.Event); ok && e.Type == pointer.Press { + w.clicked = !w.clicked + // notify when we're done updating the frame. + notify = notifyInvalidate + } + } + return layout.Dimensions{Size: gtx.Constraints.Max} +} diff --git a/gogio/testdata/testdata.go b/gogio/internal/normal/testdata.go similarity index 100% rename from gogio/testdata/testdata.go rename to gogio/internal/normal/testdata.go