~eliasnaur/gio

94a913a371c43b5df8ac688f7e8b58661fafb1cd — Elias Naur 1 year, 4 months ago ba9ffe8
ui: move macro recording from Ops to MacroOp

Move the Record and Stop methods from Ops to MacroOp itself.

Before this change, Ops.Stop stopped the recording of the most
recent macro, which could be a different macro than intended.
After this change, there is no such confusion.

As a bonus, the Ops API becomes less cluttered.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
6 files changed, 67 insertions(+), 62 deletions(-)

M ui/layout/flex.go
M ui/layout/layout.go
M ui/layout/list.go
M ui/layout/stack.go
M ui/measure/measure.go
M ui/ops.go
M ui/layout/flex.go => ui/layout/flex.go +4 -3
@@ 14,6 14,7 @@ type Flex struct {
	MainAxisAlignment  MainAxisAlignment
	CrossAxisAlignment CrossAxisAlignment

	macro       ui.MacroOp
	ops         *ui.Ops
	cs          Constraints
	mode        flexMode


@@ 74,7 75,7 @@ func (f *Flex) begin(mode flexMode) {
		panic("must End before adding a child")
	}
	f.mode = mode
	f.ops.Record()
	f.macro.Record(f.ops)
}

func (f *Flex) Rigid() Constraints {


@@ 107,7 108,7 @@ func (f *Flex) End(dims Dimens) FlexChild {
	if f.mode <= modeBegun {
		panic("End called without an active child")
	}
	macro := f.ops.Stop()
	f.macro.Stop()
	sz := axisMain(f.Axis, dims.Size)
	f.size += sz
	if f.mode == modeRigid {


@@ 120,7 121,7 @@ func (f *Flex) End(dims Dimens) FlexChild {
	if b := dims.Baseline; b > f.maxBaseline {
		f.maxBaseline = b
	}
	return FlexChild{macro, dims}
	return FlexChild{f.macro, dims}
}

func (f *Flex) Layout(children ...FlexChild) Dimens {

M ui/layout/layout.go => ui/layout/layout.go +4 -3
@@ 119,6 119,7 @@ func UniformInset(v ui.Value) Inset {
type Align struct {
	Alignment Direction

	macro ui.MacroOp
	ops   *ui.Ops
	begun bool
	cs    Constraints


@@ 131,7 132,7 @@ func (a *Align) Begin(ops *ui.Ops, cs Constraints) Constraints {
	a.begun = true
	a.ops = ops
	a.cs = cs
	ops.Record()
	a.macro.Record(ops)
	cs.Width.Min = 0
	cs.Height.Min = 0
	return cs


@@ 143,7 144,7 @@ func (a *Align) End(dims Dimens) Dimens {
	}
	a.begun = false
	ops := a.ops
	macro := ops.Stop()
	a.macro.Stop()
	sz := dims.Size
	if sz.X < a.cs.Width.Min {
		sz.X = a.cs.Width.Min


@@ 166,7 167,7 @@ func (a *Align) End(dims Dimens) Dimens {
	}
	ui.PushOp{}.Add(ops)
	ui.TransformOp{Transform: ui.Offset(toPointF(p))}.Add(ops)
	macro.Add(ops)
	a.macro.Add(ops)
	ui.PopOp{}.Add(ops)
	return Dimens{
		Size:     sz,

M ui/layout/list.go => ui/layout/list.go +8 -6
@@ 27,6 27,8 @@ type List struct {
	// The distance scrolled since last call to Init.
	Distance int

	macro     ui.MacroOp
	child     ui.MacroOp
	ops       *ui.Ops
	scroll    gesture.Scroll
	scrollDir int


@@ 70,7 72,7 @@ func (l *List) Init(ops *ui.Ops, cs Constraints, len int) {
	if l.first > len {
		l.first = len
	}
	ops.Record()
	l.macro.Record(ops)
	l.Next()
}



@@ 103,7 105,7 @@ func (l *List) Next() {
		i = l.len - 1 - i
	}
	l.index = i
	l.ops.Record()
	l.child.Record(l.ops)
}

// Index is the current element index.


@@ 146,8 148,8 @@ func (l *List) next() (int, bool) {

// Elem completes an element.
func (l *List) Elem(dims Dimens) {
	macro := l.ops.Stop()
	child := scrollChild{dims.Size, macro}
	l.child.Stop()
	child := scrollChild{dims.Size, l.child}
	switch l.dir {
	case iterateForward:
		mainSize := axisMain(l.Axis, child.size)


@@ 237,9 239,9 @@ func (l *List) Layout() Dimens {
		l.scroll.Stop()
	}
	dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
	macro := ops.Stop()
	l.macro.Stop()
	pointer.RectAreaOp{Size: dims}.Add(ops)
	l.scroll.Add(ops)
	macro.Add(ops)
	l.macro.Add(ops)
	return Dimens{Size: dims}
}

M ui/layout/stack.go => ui/layout/stack.go +4 -3
@@ 11,6 11,7 @@ import (
type Stack struct {
	Alignment Direction

	macro       ui.MacroOp
	ops         *ui.Ops
	constrained bool
	cs          Constraints


@@ 54,7 55,7 @@ func (s *Stack) begin() {
		panic("must End before adding a child")
	}
	s.begun = true
	s.ops.Record()
	s.macro.Record(s.ops)
}

func (s *Stack) Rigid() Constraints {


@@ 71,7 72,7 @@ func (s *Stack) Expand() Constraints {
}

func (s *Stack) End(dims Dimens) StackChild {
	b := s.ops.Stop()
	s.macro.Stop()
	s.begun = false
	if w := dims.Size.X; w > s.maxSZ.X {
		s.maxSZ.X = w


@@ 84,7 85,7 @@ func (s *Stack) End(dims Dimens) StackChild {
			s.baseline = b
		}
	}
	return StackChild{b, dims}
	return StackChild{s.macro, dims}
}

func (s *Stack) Layout(children ...StackChild) Dimens {

M ui/measure/measure.go => ui/measure/measure.go +4 -2
@@ 234,7 234,8 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.MacroOp {
	builder.Init(ops)
	var x fixed.Int26_6
	var advIdx int
	ops.Record()
	var m ui.MacroOp
	m.Record(ops)
	for _, r := range str.String {
		if !unicode.IsSpace(r) {
			segs, ok := f.LoadGlyph(ppem, r)


@@ 287,5 288,6 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.MacroOp {
		advIdx++
	}
	builder.End()
	return ops.Stop()
	m.Stop()
	return m
}

M ui/ops.go => ui/ops.go +43 -45
@@ 2,16 2,12 @@ package ui

import (
	"encoding/binary"
	"errors"
	"fmt"

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

// Ops holds a list of serialized Ops.
type Ops struct {
	// Stack of macro start indices.
	stack   []pc
	version int
	// Serialized ops.
	data []byte


@@ 61,9 57,10 @@ type PushOp struct{}
type PopOp struct{}

type MacroOp struct {
	ops     *Ops
	version int
	pc      pc
	recording bool
	ops       *Ops
	version   int
	pc        pc
}

type opMacroDef struct {


@@ 82,14 79,6 @@ func (p PopOp) Add(o *Ops) {
	o.Write([]byte{byte(ops.TypePop)})
}

// Record starts recording a macro. Multiple simultaneous
// recordings are supported. Stop ends the most recent.
func (o *Ops) Record() {
	o.stack = append(o.stack, o.pc())
	// Make room for a macro definition. Filled out in Stop.
	o.Write(make([]byte, ops.TypeMacroDefLen))
}

func (op *opAux) decode(data []byte) {
	if ops.OpType(data[0]) != ops.TypeAux {
		panic("invalid op")


@@ 115,28 104,9 @@ func (op *opMacroDef) decode(data []byte) {
	}
}

// Stop the most recent recording and return the macro for later
// use.
func (o *Ops) Stop() MacroOp {
	if len(o.stack) == 0 {
		panic(errors.New("not recording a macro"))
	}
	start := o.stack[len(o.stack)-1]
	o.stack = o.stack[:len(o.stack)-1]
	pc := o.pc()
	// Write the macro header reserved in Begin.
	data := o.data[start.data : start.data+ops.TypeMacroDefLen]
	data[0] = byte(ops.TypeMacroDef)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], uint32(pc.data))
	bo.PutUint32(data[5:], uint32(pc.refs))
	return MacroOp{ops: o, pc: start, version: o.version}
}

// Reset the Ops, preparing it for re-use.
func (o *Ops) Reset() {
	o.inAux = false
	o.stack = o.stack[:0]
	// Leave references to the GC.
	for i := range o.refs {
		o.refs[i] = nil


@@ 191,7 161,35 @@ func (d *Ops) pc() pc {
	return pc{data: len(d.data), refs: len(d.refs)}
}

func (b *MacroOp) decode(data []byte, refs []interface{}) {
// Record a macro of operations.
func (m *MacroOp) Record(o *Ops) {
	if m.recording {
		panic("already recording")
	}
	m.recording = true
	m.ops = o
	m.pc = o.pc()
	// Make room for a macro definition. Filled out in Stop.
	m.ops.Write(make([]byte, ops.TypeMacroDefLen))
}

// Stop recording the macro.
func (m *MacroOp) Stop() {
	if !m.recording {
		panic("not recording")
	}
	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)
	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")
	}


@@ 199,7 197,7 @@ func (b *MacroOp) decode(data []byte, refs []interface{}) {
	dataIdx := int(bo.Uint32(data[1:]))
	refsIdx := int(bo.Uint32(data[5:]))
	version := int(bo.Uint32(data[9:]))
	*b = MacroOp{
	*m = MacroOp{
		ops: refs[0].(*Ops),
		pc: pc{
			data: dataIdx,


@@ 209,17 207,20 @@ func (b *MacroOp) decode(data []byte, refs []interface{}) {
	}
}

func (b MacroOp) Add(o *Ops) {
	if b.ops == nil {
func (m MacroOp) Add(o *Ops) {
	if m.recording {
		panic("a recording is in progress")
	}
	if m.ops == nil {
		return
	}
	data := make([]byte, ops.TypeMacroLen)
	data[0] = byte(ops.TypeMacro)
	bo := binary.LittleEndian
	bo.PutUint32(data[1:], uint32(b.pc.data))
	bo.PutUint32(data[5:], uint32(b.pc.refs))
	bo.PutUint32(data[9:], uint32(b.version))
	o.Write(data, b.ops)
	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.


@@ 230,9 231,6 @@ func (r *OpsReader) Reset(ops *Ops) {
	if ops == nil {
		return
	}
	if n := len(ops.stack); n > 0 {
		panic(fmt.Errorf("%d Begin(s) not matched with End", n))
	}
	r.ops = ops
}