~eliasnaur/gio-cmd

ecebd405a7288f5cd09f4be326a1f2a4d8dd5c8e — Chris Waldon 2 years ago 35e56c5
gogio: implement custom rendering test

This commit adds an end to end test for the custom rendering
use-case. I confirmed that the new test failed when custom
rendering frame lifecycle was broken, and succeeds now.

However, the old X11 tests started failing when the new
one started passing. I'm not sure how they interfere with
one another, but I'm out of time to investigate.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
5 files changed, 398 insertions(+), 22 deletions(-)

M go.mod
M go.sum
M gogio/e2e_test.go
A gogio/internal/custom/testdata.go
R gogio/{testdata/testdata.go => internal/normal/testdata.go}
M go.mod => go.mod +4 -4
@@ 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

M go.sum => go.sum +10 -8
@@ 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=

M gogio/e2e_test.go => gogio/e2e_test.go +17 -10
@@ 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)
		})

A gogio/internal/custom/testdata.go => gogio/internal/custom/testdata.go +367 -0
@@ 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 <EGL/egl.h>
#include <wayland-client.h>
#include <wayland-egl.h>
#include <GLES3/gl3.h>
#define EGL_EGLEXT_PROTOTYPES
#include <EGL/eglext.h>

*/
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}
}

R gogio/testdata/testdata.go => gogio/internal/normal/testdata.go +0 -0