~eliasnaur/gio

b3517a365e8c1e6b81aa9164657b2dde84e08f0c — Elias Naur 8 months ago fe20cde
ui,internal/ops,internal/opconst: move OpsReader to internal ops package

To avoid import cycles, move the op constants to its own package,
internal/opconst.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
M ui/app/internal/gpu/caches.go => ui/app/internal/gpu/caches.go +8 -8
@@ 3,7 3,7 @@ package gpu
import (
	"fmt"

	"gioui.org/ui"
	"gioui.org/ui/internal/ops"
)

type resourceCache struct {


@@ 11,11 11,11 @@ type resourceCache struct {
	newRes map[interface{}]resource
}

// opCache is like a resourceCache using the concrete OpKey
// opCache is like a resourceCache using the concrete Key
// key type to avoid allocations.
type opCache struct {
	res    map[ui.OpKey]resource
	newRes map[ui.OpKey]resource
	res    map[ops.Key]resource
	newRes map[ops.Key]resource
}

func newResourceCache() *resourceCache {


@@ 64,12 64,12 @@ func (r *resourceCache) release(ctx *context) {

func newOpCache() *opCache {
	return &opCache{
		res:    make(map[ui.OpKey]resource),
		newRes: make(map[ui.OpKey]resource),
		res:    make(map[ops.Key]resource),
		newRes: make(map[ops.Key]resource),
	}
}

func (r *opCache) get(key ui.OpKey) (resource, bool) {
func (r *opCache) get(key ops.Key) (resource, bool) {
	v, exists := r.res[key]
	if exists {
		r.newRes[key] = v


@@ 77,7 77,7 @@ func (r *opCache) get(key ui.OpKey) (resource, bool) {
	return v, exists
}

func (r *opCache) put(key ui.OpKey, val resource) {
func (r *opCache) put(key ops.Key, val resource) {
	if _, exists := r.newRes[key]; exists {
		panic(fmt.Errorf("key exists, %p", key))
	}

M ui/app/internal/gpu/gpu.go => ui/app/internal/gpu/gpu.go +17 -16
@@ 16,6 16,7 @@ import (
	"gioui.org/ui/app/internal/gl"
	gdraw "gioui.org/ui/draw"
	"gioui.org/ui/f32"
	"gioui.org/ui/internal/opconst"
	"gioui.org/ui/internal/ops"
	"golang.org/x/image/draw"
)


@@ 58,7 59,7 @@ type renderer struct {
}

type drawOps struct {
	reader     ui.OpsReader
	reader     ops.Reader
	cache      *resourceCache
	viewport   image.Point
	clearColor [3]float32


@@ 90,7 91,7 @@ type pathOp struct {
	// clip is the union of all
	// later clip rectangles.
	clip      image.Rectangle
	pathKey   ui.OpKey
	pathKey   ops.Key
	path      bool
	pathVerts []byte
	parent    *pathOp


@@ 124,7 125,7 @@ type opClip struct {
}

func (op *opClip) decode(data []byte) {
	if ops.OpType(data[0]) != ops.TypeClip {
	if opconst.OpType(data[0]) != opconst.TypeClip {
		panic("invalid op")
	}
	bo := binary.LittleEndian


@@ 641,20 642,20 @@ func (d *drawOps) newPathOp() *pathOp {
	return &d.pathOpCache[len(d.pathOpCache)-1]
}

func (d *drawOps) collectOps(r *ui.OpsReader, state drawState) int {
func (d *drawOps) collectOps(r *ops.Reader, state drawState) int {
	var aux []byte
	var auxKey ui.OpKey
	var auxKey ops.Key
loop:
	for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
		switch ops.OpType(encOp.Data[0]) {
		case ops.TypeTransform:
		switch opconst.OpType(encOp.Data[0]) {
		case opconst.TypeTransform:
			var op ui.TransformOp
			op.Decode(encOp.Data)
			state.t = state.t.Multiply(op)
		case ops.TypeAux:
			aux = encOp.Data[ops.TypeAuxLen:]
		case opconst.TypeAux:
			aux = encOp.Data[opconst.TypeAuxLen:]
			auxKey = encOp.Key
		case ops.TypeClip:
		case opconst.TypeClip:
			var op opClip
			op.decode(encOp.Data)
			off := state.t.Transform(f32.Point{})


@@ 676,18 677,18 @@ loop:
				d.pathOps = append(d.pathOps, state.cpath)
			}
			aux = nil
			auxKey = ui.OpKey{}
		case ops.TypeColor:
			auxKey = ops.Key{}
		case opconst.TypeColor:
			var op gdraw.ColorOp
			op.Decode(encOp.Data, encOp.Refs)
			state.img = nil
			state.color = op.Color
		case ops.TypeImage:
		case opconst.TypeImage:
			var op gdraw.ImageOp
			op.Decode(encOp.Data, encOp.Refs)
			state.img = op.Src
			state.imgRect = op.Rect
		case ops.TypeDraw:
		case opconst.TypeDraw:
			var op gdraw.DrawOp
			op.Decode(encOp.Data, encOp.Refs)
			off := state.t.Transform(f32.Point{})


@@ 723,9 724,9 @@ loop:
			} else {
				d.imageOps = append(d.imageOps, img)
			}
		case ops.TypePush:
		case opconst.TypePush:
			state.z = d.collectOps(r, state)
		case ops.TypePop:
		case opconst.TypePop:
			break loop
		}
	}

M ui/app/internal/input/key.go => ui/app/internal/input/key.go +7 -6
@@ 5,6 5,7 @@ package input
import (
	"gioui.org/ui"
	"gioui.org/ui/input"
	"gioui.org/ui/internal/opconst"
	"gioui.org/ui/internal/ops"
	"gioui.org/ui/key"
)


@@ 14,7 15,7 @@ type TextInputState uint8
type keyQueue struct {
	focus    input.Key
	handlers map[input.Key]*keyHandler
	reader   ui.OpsReader
	reader   ops.Reader
	state    TextInputState
}



@@ 94,8 95,8 @@ func (q *keyQueue) resolveFocus(events *handlerEvents) (input.Key, listenerPrior
	var hide bool
loop:
	for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
		switch ops.OpType(encOp.Data[0]) {
		case ops.TypeKeyHandler:
		switch opconst.OpType(encOp.Data[0]) {
		case opconst.TypeKeyHandler:
			var op key.HandlerOp
			op.Decode(encOp.Data, encOp.Refs)
			var newPri listenerPriority


@@ 119,15 120,15 @@ loop:
				events.Set(op.Key, []input.Event{key.FocusEvent{Focus: false}})
			}
			h.active = true
		case ops.TypeHideInput:
		case opconst.TypeHideInput:
			hide = true
		case ops.TypePush:
		case opconst.TypePush:
			newK, newPri, h := q.resolveFocus(events)
			hide = hide || h
			if newPri.replaces(pri) {
				k, pri = newK, newPri
			}
		case ops.TypePop:
		case opconst.TypePop:
			break loop
		}
	}

M ui/app/internal/input/pointer.go => ui/app/internal/input/pointer.go +11 -10
@@ 9,6 9,7 @@ import (
	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/input"
	"gioui.org/ui/internal/opconst"
	"gioui.org/ui/internal/ops"
	"gioui.org/ui/pointer"
)


@@ 18,7 19,7 @@ type pointerQueue struct {
	areas    []areaNode
	handlers map[input.Key]*pointerHandler
	pointers []pointerInfo
	reader   ui.OpsReader
	reader   ops.Reader
	scratch  []input.Key
}



@@ 63,18 64,18 @@ const (
	areaEllipse
)

func (q *pointerQueue) collectHandlers(r *ui.OpsReader, events *handlerEvents, t ui.TransformOp, area, node int, pass bool) {
func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t ui.TransformOp, area, node int, pass bool) {
	for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
		switch ops.OpType(encOp.Data[0]) {
		case ops.TypePush:
		switch opconst.OpType(encOp.Data[0]) {
		case opconst.TypePush:
			q.collectHandlers(r, events, t, area, node, pass)
		case ops.TypePop:
		case opconst.TypePop:
			return
		case ops.TypePass:
		case opconst.TypePass:
			var op pointer.PassOp
			op.Decode(encOp.Data)
			pass = op.Pass
		case ops.TypeArea:
		case opconst.TypeArea:
			var op areaOp
			op.Decode(encOp.Data)
			q.areas = append(q.areas, areaNode{trans: t, next: area, area: op})


@@ 85,11 86,11 @@ func (q *pointerQueue) collectHandlers(r *ui.OpsReader, events *handlerEvents, t
				pass: pass,
			})
			node = len(q.hitTree) - 1
		case ops.TypeTransform:
		case opconst.TypeTransform:
			var op ui.TransformOp
			op.Decode(encOp.Data)
			t = t.Multiply(op)
		case ops.TypePointerHandler:
		case opconst.TypePointerHandler:
			var op pointer.HandlerOp
			op.Decode(encOp.Data, encOp.Refs)
			q.hitTree = append(q.hitTree, hitNode{


@@ 264,7 265,7 @@ func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
}

func (op *areaOp) Decode(d []byte) {
	if ops.OpType(d[0]) != ops.TypeArea {
	if opconst.OpType(d[0]) != opconst.TypeArea {
		panic("invalid op")
	}
	bo := binary.LittleEndian

M ui/app/internal/input/router.go => ui/app/internal/input/router.go +5 -4
@@ 7,6 7,7 @@ import (

	"gioui.org/ui"
	"gioui.org/ui/input"
	"gioui.org/ui/internal/opconst"
	"gioui.org/ui/internal/ops"
	"gioui.org/ui/key"
	"gioui.org/ui/pointer"


@@ 21,7 22,7 @@ type Router struct {

	handlers handlerEvents

	reader ui.OpsReader
	reader ops.Reader

	// InvalidateOp summary.
	wakeup     bool


@@ 71,15 72,15 @@ func (q *Router) TextInputState() TextInputState {

func (q *Router) collect() {
	for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() {
		switch ops.OpType(encOp.Data[0]) {
		case ops.TypeInvalidate:
		switch opconst.OpType(encOp.Data[0]) {
		case opconst.TypeInvalidate:
			var op ui.InvalidateOp
			op.Decode(encOp.Data)
			if !q.wakeup || op.At.Before(q.wakeupTime) {
				q.wakeup = true
				q.wakeupTime = op.At
			}
		case ops.TypeProfile:
		case opconst.TypeProfile:
			var op system.ProfileOp
			op.Decode(encOp.Data, encOp.Refs)
			q.profHandlers = append(q.profHandlers, op.Key)

M ui/draw/draw.go => ui/draw/draw.go +10 -10
@@ 10,7 10,7 @@ import (

	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/internal/ops"
	"gioui.org/ui/internal/opconst"
)

type ImageOp struct {


@@ 27,8 27,8 @@ type DrawOp struct {
}

func (i ImageOp) Add(o *ui.Ops) {
	data := make([]byte, ops.TypeImageLen)
	data[0] = byte(ops.TypeImage)
	data := make([]byte, opconst.TypeImageLen)
	data[0] = byte(opconst.TypeImage)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], uint32(i.Rect.Min.X))
	bo.PutUint32(data[5:], uint32(i.Rect.Min.Y))


@@ 39,7 39,7 @@ func (i ImageOp) Add(o *ui.Ops) {

func (i *ImageOp) Decode(data []byte, refs []interface{}) {
	bo := binary.LittleEndian
	if ops.OpType(data[0]) != ops.TypeImage {
	if opconst.OpType(data[0]) != opconst.TypeImage {
		panic("invalid op")
	}
	sr := image.Rectangle{


@@ 59,8 59,8 @@ func (i *ImageOp) Decode(data []byte, refs []interface{}) {
}

func (c ColorOp) Add(o *ui.Ops) {
	data := make([]byte, ops.TypeColorLen)
	data[0] = byte(ops.TypeColor)
	data := make([]byte, opconst.TypeColorLen)
	data[0] = byte(opconst.TypeColor)
	data[1] = c.Color.R
	data[2] = c.Color.G
	data[3] = c.Color.B


@@ 69,7 69,7 @@ func (c ColorOp) Add(o *ui.Ops) {
}

func (c *ColorOp) Decode(data []byte, refs []interface{}) {
	if ops.OpType(data[0]) != ops.TypeColor {
	if opconst.OpType(data[0]) != opconst.TypeColor {
		panic("invalid op")
	}
	*c = ColorOp{


@@ 83,8 83,8 @@ func (c *ColorOp) Decode(data []byte, refs []interface{}) {
}

func (d DrawOp) Add(o *ui.Ops) {
	data := make([]byte, ops.TypeDrawLen)
	data[0] = byte(ops.TypeDraw)
	data := make([]byte, opconst.TypeDrawLen)
	data[0] = byte(opconst.TypeDraw)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], math.Float32bits(d.Rect.Min.X))
	bo.PutUint32(data[5:], math.Float32bits(d.Rect.Min.Y))


@@ 95,7 95,7 @@ func (d DrawOp) Add(o *ui.Ops) {

func (d *DrawOp) Decode(data []byte, refs []interface{}) {
	bo := binary.LittleEndian
	if ops.OpType(data[0]) != ops.TypeDraw {
	if opconst.OpType(data[0]) != opconst.TypeDraw {
		panic("invalid op")
	}
	r := f32.Rectangle{

M ui/draw/path.go => ui/draw/path.go +4 -4
@@ 9,7 9,7 @@ import (

	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/internal/ops"
	"gioui.org/ui/internal/opconst"
	"gioui.org/ui/internal/path"
)



@@ 30,8 30,8 @@ type ClipOp struct {
}

func (p ClipOp) Add(o *ui.Ops) {
	data := make([]byte, ops.TypeClipLen)
	data[0] = byte(ops.TypeClip)
	data := make([]byte, opconst.TypeClipLen)
	data[0] = byte(opconst.TypeClip)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], math.Float32bits(p.bounds.Min.X))
	bo.PutUint32(data[5:], math.Float32bits(p.bounds.Min.Y))


@@ 239,7 239,7 @@ func (p *PathBuilder) vertex(cornerx, cornery int16, ctrl, to f32.Point) {
		ToY:     to.Y,
	}
	data := make([]byte, path.VertStride+1)
	data[0] = byte(ops.TypeAux)
	data[0] = byte(opconst.TypeAux)
	bo := binary.LittleEndian
	data[1] = byte(uint16(v.CornerX))
	data[2] = byte(uint16(v.CornerX) >> 8)

R ui/internal/ops/ops.go => ui/internal/opconst/ops.go +1 -1
@@ 1,4 1,4 @@
package ops
package opconst

type OpType byte


A ui/internal/ops/reader.go => ui/internal/ops/reader.go +177 -0
@@ 0,0 1,177 @@
package ops

import (
	"encoding/binary"

	"gioui.org/ui"
	"gioui.org/ui/internal/opconst"
)

// Reader parses an ops list.
type Reader struct {
	pc    pc
	stack []macro
	ops   *ui.Ops
}

// EncodedOp represents an encoded op returned by
// Reader.
type EncodedOp struct {
	Key  Key
	Data []byte
	Refs []interface{}
}

// Key is a unique key for a given op.
type Key struct {
	ops     *ui.Ops
	pc      int
	version int
}

// Shadow of ui.MacroOp.
type macroOp struct {
	recording bool
	ops       *ui.Ops
	version   int
	pc        pc
}

type pc struct {
	data int
	refs int
}

type macro struct {
	ops   *ui.Ops
	retPC pc
	endPC pc
}

type opMacroDef struct {
	endpc pc
}

type opAux struct {
	len int
}

// Reset start reading from the op list.
func (r *Reader) Reset(ops *ui.Ops) {
	r.stack = r.stack[:0]
	r.pc = pc{}
	r.ops = nil
	if ops == nil {
		return
	}
	r.ops = ops
}

func (r *Reader) Decode() (EncodedOp, bool) {
	if r.ops == nil {
		return EncodedOp{}, false
	}
	for {
		if len(r.stack) > 0 {
			b := r.stack[len(r.stack)-1]
			if r.pc == b.endPC {
				r.ops = b.ops
				r.pc = b.retPC
				r.stack = r.stack[:len(r.stack)-1]
				continue
			}
		}
		if r.pc.data == len(r.ops.Data) {
			return EncodedOp{}, false
		}
		key := Key{ops: r.ops, pc: r.pc.data, version: r.ops.Version}
		t := opconst.OpType(r.ops.Data[r.pc.data])
		n := t.Size()
		nrefs := t.NumRefs()
		data := r.ops.Data[r.pc.data : r.pc.data+n]
		refs := r.ops.Refs[r.pc.refs : r.pc.refs+nrefs]
		switch t {
		case opconst.TypeAux:
			var op opAux
			op.decode(data)
			n += op.len
			data = r.ops.Data[r.pc.data : r.pc.data+n]
		case opconst.TypeMacro:
			var op macroOp
			op.decode(data, refs)
			macroOps := op.ops
			if opconst.OpType(macroOps.Data[op.pc.data]) != opconst.TypeMacroDef {
				panic("invalid macro reference")
			}
			if op.version != op.ops.Version {
				panic("invalid MacroOp reference to reset Ops")
			}
			var opDef opMacroDef
			opDef.decode(macroOps.Data[op.pc.data : op.pc.data+opconst.TypeMacroDef.Size()])
			retPC := r.pc
			retPC.data += n
			retPC.refs += nrefs
			r.stack = append(r.stack, macro{
				ops:   r.ops,
				retPC: retPC,
				endPC: opDef.endpc,
			})
			r.ops = macroOps
			r.pc = op.pc
			r.pc.data += opconst.TypeMacroDef.Size()
			r.pc.refs += opconst.TypeMacroDef.NumRefs()
			continue
		case opconst.TypeMacroDef:
			var op opMacroDef
			op.decode(data)
			r.pc = op.endpc
			continue
		}
		r.pc.data += n
		r.pc.refs += nrefs
		return EncodedOp{Key: key, Data: data, Refs: refs}, true
	}
}

func (op *opMacroDef) decode(data []byte) {
	if opconst.OpType(data[0]) != opconst.TypeMacroDef {
		panic("invalid op")
	}
	bo := binary.LittleEndian
	dataIdx := int(int32(bo.Uint32(data[1:])))
	refsIdx := int(int32(bo.Uint32(data[5:])))
	*op = opMacroDef{
		endpc: pc{
			data: dataIdx,
			refs: refsIdx,
		},
	}
}

func (op *opAux) decode(data []byte) {
	if opconst.OpType(data[0]) != opconst.TypeAux {
		panic("invalid op")
	}
	bo := binary.LittleEndian
	*op = opAux{
		len: int(int32(bo.Uint32(data[1:]))),
	}
}

func (m *macroOp) decode(data []byte, refs []interface{}) {
	if opconst.OpType(data[0]) != opconst.TypeMacro {
		panic("invalid op")
	}
	bo := binary.LittleEndian
	dataIdx := int(int32(bo.Uint32(data[1:])))
	refsIdx := int(int32(bo.Uint32(data[5:])))
	version := int(int32(bo.Uint32(data[9:])))
	*m = macroOp{
		ops: refs[0].(*ui.Ops),
		pc: pc{
			data: dataIdx,
			refs: refsIdx,
		},
		version: version,
	}
}

M ui/key/key.go => ui/key/key.go +6 -6
@@ 5,7 5,7 @@ package key
import (
	"gioui.org/ui"
	"gioui.org/ui/input"
	"gioui.org/ui/internal/ops"
	"gioui.org/ui/internal/opconst"
)

type HandlerOp struct {


@@ 56,8 56,8 @@ func (m Modifiers) Contain(m2 Modifiers) bool {
}

func (h HandlerOp) Add(o *ui.Ops) {
	data := make([]byte, ops.TypeKeyHandlerLen)
	data[0] = byte(ops.TypeKeyHandler)
	data := make([]byte, opconst.TypeKeyHandlerLen)
	data[0] = byte(opconst.TypeKeyHandler)
	if h.Focus {
		data[1] = 1
	}


@@ 65,7 65,7 @@ func (h HandlerOp) Add(o *ui.Ops) {
}

func (h *HandlerOp) Decode(d []byte, refs []interface{}) {
	if ops.OpType(d[0]) != ops.TypeKeyHandler {
	if opconst.OpType(d[0]) != opconst.TypeKeyHandler {
		panic("invalid op")
	}
	*h = HandlerOp{


@@ 75,8 75,8 @@ func (h *HandlerOp) Decode(d []byte, refs []interface{}) {
}

func (h HideInputOp) Add(o *ui.Ops) {
	data := make([]byte, ops.TypeHideInputLen)
	data[0] = byte(ops.TypeHideInput)
	data := make([]byte, opconst.TypeHideInputLen)
	data[0] = byte(opconst.TypeHideInput)
	o.Write(data)
}


M ui/ops.go => ui/ops.go +29 -185
@@ 3,16 3,16 @@ package ui
import (
	"encoding/binary"

	"gioui.org/ui/internal/ops"
	"gioui.org/ui/internal/opconst"
)

// Ops holds a list of serialized Ops.
type Ops struct {
	version int
	Version int
	// Serialized ops.
	data []byte
	Data []byte
	// Op references.
	refs []interface{}
	Refs []interface{}

	stackDepth int



@@ 21,39 21,6 @@ type Ops struct {
	auxLen int
}

// OpsReader parses an ops list. Internal use only.
type OpsReader struct {
	pc    pc
	stack []macro
	ops   *Ops
}

// EncodedOp represents an encoded op returned by
// OpsReader. Internal use only.
type EncodedOp struct {
	Key  OpKey
	Data []byte
	Refs []interface{}
}

// OpKey is a unique key for a given op. Internal use only.
type OpKey struct {
	ops     *Ops
	pc      int
	version int
}

type macro struct {
	ops   *Ops
	retPC pc
	endPC pc
}

type pc struct {
	data int
	refs int
}

type StackOp struct {
	depth  int
	active bool


@@ 67,12 34,9 @@ type MacroOp struct {
	pc        pc
}

type opMacroDef struct {
	endpc pc
}

type opAux struct {
	len int
type pc struct {
	data int
	refs int
}

func (s *StackOp) Push(o *Ops) {


@@ 83,7 47,7 @@ func (s *StackOp) Push(o *Ops) {
	s.ops = o
	o.stackDepth++
	s.depth = o.stackDepth
	o.Write([]byte{byte(ops.TypePush)})
	o.Write([]byte{byte(opconst.TypePush)})
}

func (s *StackOp) Pop() {


@@ 96,32 60,7 @@ func (s *StackOp) Pop() {
	}
	s.active = false
	s.ops.stackDepth--
	s.ops.Write([]byte{byte(ops.TypePop)})
}

func (op *opAux) decode(data []byte) {
	if ops.OpType(data[0]) != ops.TypeAux {
		panic("invalid op")
	}
	bo := binary.LittleEndian
	*op = opAux{
		len: int(int32(bo.Uint32(data[1:]))),
	}
}

func (op *opMacroDef) decode(data []byte) {
	if ops.OpType(data[0]) != ops.TypeMacroDef {
		panic("invalid op")
	}
	bo := binary.LittleEndian
	dataIdx := int(int32(bo.Uint32(data[1:])))
	refsIdx := int(int32(bo.Uint32(data[5:])))
	*op = opMacroDef{
		endpc: pc{
			data: dataIdx,
			refs: refsIdx,
		},
	}
	s.ops.Write([]byte{byte(opconst.TypePop)})
}

// Reset the Ops, preparing it for re-use.


@@ 129,12 68,12 @@ func (o *Ops) Reset() {
	o.inAux = false
	o.stackDepth = 0
	// Leave references to the GC.
	for i := range o.refs {
		o.refs[i] = nil
	for i := range o.Refs {
		o.Refs[i] = nil
	}
	o.data = o.data[:0]
	o.refs = o.refs[:0]
	o.version++
	o.Data = o.Data[:0]
	o.Refs = o.Refs[:0]
	o.Version++
}

// Internal use only.


@@ 142,29 81,29 @@ func (o *Ops) Aux() []byte {
	if !o.inAux {
		return nil
	}
	return o.data[o.auxOff+ops.TypeAuxLen : o.auxOff+ops.TypeAuxLen+o.auxLen]
	return o.Data[o.auxOff+opconst.TypeAuxLen : o.auxOff+opconst.TypeAuxLen+o.auxLen]
}

func (d *Ops) write(op []byte, refs ...interface{}) {
	d.data = append(d.data, op...)
	d.refs = append(d.refs, refs...)
	d.Data = append(d.Data, op...)
	d.Refs = append(d.Refs, refs...)
}

func (o *Ops) Write(op []byte, refs ...interface{}) {
	t := ops.OpType(op[0])
	t := opconst.OpType(op[0])
	if len(refs) != t.NumRefs() {
		panic("invalid ref count")
	}
	switch t {
	case ops.TypeAux:
	case opconst.TypeAux:
		// Write only the data.
		op = op[1:]
		if !o.inAux {
			o.inAux = true
			o.auxOff = o.pc().data
			o.auxLen = 0
			header := make([]byte, ops.TypeAuxLen)
			header[0] = byte(ops.TypeAux)
			header := make([]byte, opconst.TypeAuxLen)
			header[0] = byte(opconst.TypeAux)
			o.write(header)
		}
		o.auxLen += len(op)


@@ 172,14 111,14 @@ func (o *Ops) Write(op []byte, refs ...interface{}) {
		if o.inAux {
			o.inAux = false
			bo := binary.LittleEndian
			bo.PutUint32(o.data[o.auxOff+1:], uint32(o.auxLen))
			bo.PutUint32(o.Data[o.auxOff+1:], uint32(o.auxLen))
		}
	}
	o.write(op, refs...)
}

func (d *Ops) pc() pc {
	return pc{data: len(d.data), refs: len(d.refs)}
	return pc{data: len(d.Data), refs: len(d.Refs)}
}

// Record a macro of operations.


@@ 191,7 130,7 @@ func (m *MacroOp) Record(o *Ops) {
	m.ops = o
	m.pc = o.pc()
	// Make room for a macro definition. Filled out in Stop.
	m.ops.Write(make([]byte, ops.TypeMacroDefLen))
	m.ops.Write(make([]byte, opconst.TypeMacroDefLen))
}

// Stop recording the macro.


@@ 202,30 141,12 @@ func (m *MacroOp) Stop() {
	m.recording = false
	pc := m.ops.pc()
	// Fill out the macro definition reserved in Record.
	data := m.ops.data[m.pc.data : m.pc.data+ops.TypeMacroDefLen]
	data[0] = byte(ops.TypeMacroDef)
	data := m.ops.Data[m.pc.data : m.pc.data+opconst.TypeMacroDefLen]
	data[0] = byte(opconst.TypeMacroDef)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], uint32(pc.data))
	bo.PutUint32(data[5:], uint32(pc.refs))
	m.version = m.ops.version
}

func (m *MacroOp) decode(data []byte, refs []interface{}) {
	if ops.OpType(data[0]) != ops.TypeMacro {
		panic("invalid op")
	}
	bo := binary.LittleEndian
	dataIdx := int(int32(bo.Uint32(data[1:])))
	refsIdx := int(int32(bo.Uint32(data[5:])))
	version := int(int32(bo.Uint32(data[9:])))
	*m = MacroOp{
		ops: refs[0].(*Ops),
		pc: pc{
			data: dataIdx,
			refs: refsIdx,
		},
		version: version,
	}
	m.version = m.ops.Version
}

func (m MacroOp) Add(o *Ops) {


@@ 235,88 156,11 @@ func (m MacroOp) Add(o *Ops) {
	if m.ops == nil {
		return
	}
	data := make([]byte, ops.TypeMacroLen)
	data[0] = byte(ops.TypeMacro)
	data := make([]byte, opconst.TypeMacroLen)
	data[0] = byte(opconst.TypeMacro)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], uint32(m.pc.data))
	bo.PutUint32(data[5:], uint32(m.pc.refs))
	bo.PutUint32(data[9:], uint32(m.version))
	o.Write(data, m.ops)
}

// Reset start reading from the op list.
func (r *OpsReader) Reset(ops *Ops) {
	r.stack = r.stack[:0]
	r.pc = pc{}
	r.ops = nil
	if ops == nil {
		return
	}
	r.ops = ops
}

func (r *OpsReader) Decode() (EncodedOp, bool) {
	if r.ops == nil {
		return EncodedOp{}, false
	}
	for {
		if len(r.stack) > 0 {
			b := r.stack[len(r.stack)-1]
			if r.pc == b.endPC {
				r.ops = b.ops
				r.pc = b.retPC
				r.stack = r.stack[:len(r.stack)-1]
				continue
			}
		}
		if r.pc.data == len(r.ops.data) {
			return EncodedOp{}, false
		}
		key := OpKey{ops: r.ops, pc: r.pc.data, version: r.ops.version}
		t := ops.OpType(r.ops.data[r.pc.data])
		n := t.Size()
		nrefs := t.NumRefs()
		data := r.ops.data[r.pc.data : r.pc.data+n]
		refs := r.ops.refs[r.pc.refs : r.pc.refs+nrefs]
		switch t {
		case ops.TypeAux:
			var op opAux
			op.decode(data)
			n += op.len
			data = r.ops.data[r.pc.data : r.pc.data+n]
		case ops.TypeMacro:
			var op MacroOp
			op.decode(data, refs)
			macroOps := op.ops
			if ops.OpType(macroOps.data[op.pc.data]) != ops.TypeMacroDef {
				panic("invalid macro reference")
			}
			if op.version != op.ops.version {
				panic("invalid MacroOp reference to reset Ops")
			}
			var opDef opMacroDef
			opDef.decode(macroOps.data[op.pc.data : op.pc.data+ops.TypeMacroDef.Size()])
			retPC := r.pc
			retPC.data += n
			retPC.refs += nrefs
			r.stack = append(r.stack, macro{
				ops:   r.ops,
				retPC: retPC,
				endPC: opDef.endpc,
			})
			r.ops = macroOps
			r.pc = op.pc
			r.pc.data += ops.TypeMacroDef.Size()
			r.pc.refs += ops.TypeMacroDef.NumRefs()
			continue
		case ops.TypeMacroDef:
			var op opMacroDef
			op.decode(data)
			r.pc = op.endpc
			continue
		}
		r.pc.data += n
		r.pc.refs += nrefs
		return EncodedOp{Key: key, Data: data, Refs: refs}, true
	}
}

M ui/pointer/pointer.go => ui/pointer/pointer.go +9 -9
@@ 10,7 10,7 @@ import (
	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/input"
	"gioui.org/ui/internal/ops"
	"gioui.org/ui/internal/opconst"
)

type Event struct {


@@ 95,8 95,8 @@ func (op EllipseAreaOp) Add(ops *ui.Ops) {
}

func (op areaOp) add(o *ui.Ops) {
	data := make([]byte, ops.TypeAreaLen)
	data[0] = byte(ops.TypeArea)
	data := make([]byte, opconst.TypeAreaLen)
	data[0] = byte(opconst.TypeArea)
	data[1] = byte(op.kind)
	bo := binary.LittleEndian
	bo.PutUint32(data[2:], uint32(op.rect.Min.X))


@@ 107,8 107,8 @@ func (op areaOp) add(o *ui.Ops) {
}

func (h HandlerOp) Add(o *ui.Ops) {
	data := make([]byte, ops.TypePointerHandlerLen)
	data[0] = byte(ops.TypePointerHandler)
	data := make([]byte, opconst.TypePointerHandlerLen)
	data[0] = byte(opconst.TypePointerHandler)
	if h.Grab {
		data[1] = 1
	}


@@ 116,7 116,7 @@ func (h HandlerOp) Add(o *ui.Ops) {
}

func (h *HandlerOp) Decode(d []byte, refs []interface{}) {
	if ops.OpType(d[0]) != ops.TypePointerHandler {
	if opconst.OpType(d[0]) != opconst.TypePointerHandler {
		panic("invalid op")
	}
	*h = HandlerOp{


@@ 126,8 126,8 @@ func (h *HandlerOp) Decode(d []byte, refs []interface{}) {
}

func (op PassOp) Add(o *ui.Ops) {
	data := make([]byte, ops.TypePassLen)
	data[0] = byte(ops.TypePass)
	data := make([]byte, opconst.TypePassLen)
	data[0] = byte(opconst.TypePass)
	if op.Pass {
		data[1] = 1
	}


@@ 135,7 135,7 @@ func (op PassOp) Add(o *ui.Ops) {
}

func (op *PassOp) Decode(d []byte) {
	if ops.OpType(d[0]) != ops.TypePass {
	if opconst.OpType(d[0]) != opconst.TypePass {
		panic("invalid op")
	}
	*op = PassOp{

M ui/system/system.go => ui/system/system.go +4 -4
@@ 7,7 7,7 @@ package system
import (
	"gioui.org/ui"
	"gioui.org/ui/input"
	"gioui.org/ui/internal/ops"
	"gioui.org/ui/internal/opconst"
)

// ProfileOp registers a handler for receiving


@@ 24,13 24,13 @@ type ProfileEvent struct {
}

func (p ProfileOp) Add(o *ui.Ops) {
	data := make([]byte, ops.TypeProfileLen)
	data[0] = byte(ops.TypeProfile)
	data := make([]byte, opconst.TypeProfileLen)
	data[0] = byte(opconst.TypeProfile)
	o.Write(data, p.Key)
}

func (p *ProfileOp) Decode(d []byte, refs []interface{}) {
	if ops.OpType(d[0]) != ops.TypeProfile {
	if opconst.OpType(d[0]) != opconst.TypeProfile {
		panic("invalid op")
	}
	*p = ProfileOp{

M ui/ui.go => ui/ui.go +7 -7
@@ 8,7 8,7 @@ import (
	"time"

	"gioui.org/ui/f32"
	"gioui.org/ui/internal/ops"
	"gioui.org/ui/internal/opconst"
)

// Config represents the essential configuration for


@@ 33,8 33,8 @@ type TransformOp struct {
}

func (r InvalidateOp) Add(o *Ops) {
	data := make([]byte, ops.TypeRedrawLen)
	data[0] = byte(ops.TypeInvalidate)
	data := make([]byte, opconst.TypeRedrawLen)
	data[0] = byte(opconst.TypeInvalidate)
	bo := binary.LittleEndian
	// UnixNano cannot represent the zero time.
	if t := r.At; !t.IsZero() {


@@ 48,7 48,7 @@ func (r InvalidateOp) Add(o *Ops) {

func (r *InvalidateOp) Decode(d []byte) {
	bo := binary.LittleEndian
	if ops.OpType(d[0]) != ops.TypeInvalidate {
	if opconst.OpType(d[0]) != opconst.TypeInvalidate {
		panic("invalid op")
	}
	if nanos := bo.Uint64(d[1:]); nanos > 0 {


@@ 79,8 79,8 @@ func (t TransformOp) Multiply(t2 TransformOp) TransformOp {
}

func (t TransformOp) Add(o *Ops) {
	data := make([]byte, ops.TypeTransformLen)
	data[0] = byte(ops.TypeTransform)
	data := make([]byte, opconst.TypeTransformLen)
	data[0] = byte(opconst.TypeTransform)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], math.Float32bits(t.offset.X))
	bo.PutUint32(data[5:], math.Float32bits(t.offset.Y))


@@ 89,7 89,7 @@ func (t TransformOp) Add(o *Ops) {

func (t *TransformOp) Decode(d []byte) {
	bo := binary.LittleEndian
	if ops.OpType(d[0]) != ops.TypeTransform {
	if opconst.OpType(d[0]) != opconst.TypeTransform {
		panic("invalid op")
	}
	*t = TransformOp{f32.Point{