~eliasnaur/gio

df791f2e9b17979b4e6f94417aee5a0f2f440e19 — Elias Naur 2 years ago 60fd129
ui: remove ui.Ops parameters from layouts and path builder structs

Layouts and path builders are transient and need an ops list for
operation. However, instead of passing the ops list to every method,
pass the list in an init method and store it for subsequent methods.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
M ui/draw/path.go => ui/draw/path.go +36 -31
@@ 14,6 14,7 @@ import (
)

type PathBuilder struct {
	ops       *ui.Ops
	firstVert int
	nverts    int
	maxy      float32


@@ 38,17 39,21 @@ func (p opClip) Add(o *ui.Ops) {
	o.Write(data)
}

func (p *PathBuilder) Init(ops *ui.Ops) {
	p.ops = ops
}

// MoveTo moves the pen to the given position.
func (p *PathBuilder) Move(ops *ui.Ops, to f32.Point) {
	p.end(ops)
func (p *PathBuilder) Move(to f32.Point) {
	p.end()
	to = to.Add(p.pen)
	p.maxy = to.Y
	p.pen = to
}

// end completes the current contour.
func (p *PathBuilder) end(ops *ui.Ops) {
	aux := ops.Aux()
func (p *PathBuilder) end() {
	aux := p.ops.Aux()
	bo := binary.LittleEndian
	// Fill in maximal Y coordinates of the NW and NE corners.
	for i := p.firstVert; i < p.nverts; i++ {


@@ 59,25 64,25 @@ func (p *PathBuilder) end(ops *ui.Ops) {
}

// Line records a line from the pen to end.
func (p *PathBuilder) Line(ops *ui.Ops, to f32.Point) {
func (p *PathBuilder) Line(to f32.Point) {
	to = to.Add(p.pen)
	p.lineTo(ops, to)
	p.lineTo(to)
}

func (p *PathBuilder) lineTo(ops *ui.Ops, to f32.Point) {
func (p *PathBuilder) lineTo(to f32.Point) {
	// Model lines as degenerate quadratic beziers.
	p.quadTo(ops, to.Add(p.pen).Mul(.5), to)
	p.quadTo(to.Add(p.pen).Mul(.5), to)
}

// Quad records a quadratic bezier from the pen to end
// with the control point ctrl.
func (p *PathBuilder) Quad(ops *ui.Ops, ctrl, to f32.Point) {
func (p *PathBuilder) Quad(ctrl, to f32.Point) {
	ctrl = ctrl.Add(p.pen)
	to = to.Add(p.pen)
	p.quadTo(ops, ctrl, to)
	p.quadTo(ctrl, to)
}

func (p *PathBuilder) quadTo(ops *ui.Ops, ctrl, to f32.Point) {
func (p *PathBuilder) quadTo(ctrl, to f32.Point) {
	// Zero width curves don't contribute to stenciling.
	if p.pen.X == to.X && p.pen.X == ctrl.X {
		p.pen = to


@@ 104,8 109,8 @@ func (p *PathBuilder) quadTo(ops *ui.Ops, ctrl, to f32.Point) {
		ctrl0 := p.pen.Mul(1 - t).Add(ctrl.Mul(t))
		ctrl1 := ctrl.Mul(1 - t).Add(to.Mul(t))
		mid := ctrl0.Mul(1 - t).Add(ctrl1.Mul(t))
		p.simpleQuadTo(ops, ctrl0, mid)
		p.simpleQuadTo(ops, ctrl1, to)
		p.simpleQuadTo(ctrl0, mid)
		p.simpleQuadTo(ctrl1, to)
		if mid.X > bounds.Max.X {
			bounds.Max.X = mid.X
		}


@@ 113,7 118,7 @@ func (p *PathBuilder) quadTo(ops *ui.Ops, ctrl, to f32.Point) {
			bounds.Min.X = mid.X
		}
	} else {
		p.simpleQuadTo(ops, ctrl, to)
		p.simpleQuadTo(ctrl, to)
	}
	// Find the y extremum, if any.
	d = v0.Y - v1.Y


@@ 132,7 137,7 @@ func (p *PathBuilder) quadTo(ops *ui.Ops, ctrl, to f32.Point) {

// Cube records a cubic bezier from the pen through
// two control points ending in to.
func (p *PathBuilder) Cube(ops *ui.Ops, ctrl0, ctrl1, to f32.Point) {
func (p *PathBuilder) Cube(ctrl0, ctrl1, to f32.Point) {
	ctrl0 = ctrl0.Add(p.pen)
	ctrl1 = ctrl1.Add(p.pen)
	to = to.Add(p.pen)


@@ 146,12 151,12 @@ func (p *PathBuilder) Cube(ops *ui.Ops, ctrl0, ctrl1, to f32.Point) {
	if h := hull.Dy(); h > l {
		l = h
	}
	p.approxCubeTo(ops, 0, l*0.001, ctrl0, ctrl1, to)
	p.approxCubeTo(0, l*0.001, ctrl0, ctrl1, to)
}

// approxCube approximates a cubic beziér by a series of quadratic
// curves.
func (p *PathBuilder) approxCubeTo(ops *ui.Ops, splits int, maxDist float32, ctrl0, ctrl1, to f32.Point) int {
func (p *PathBuilder) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Point) int {
	// The idea is from
	// https://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
	// where a quadratic approximates a cubic by eliminating its t³ term


@@ 179,7 184,7 @@ func (p *PathBuilder) approxCubeTo(ops *ui.Ops, splits int, maxDist float32, ctr
	c := ctrl0.Mul(3).Sub(p.pen).Add(ctrl1.Mul(3)).Sub(to).Mul(1.0 / 4.0)
	const maxSplits = 32
	if splits >= maxSplits {
		p.quadTo(ops, c, to)
		p.quadTo(c, to)
		return splits
	}
	// The maximum distance between the cubic P and its approximation Q given t


@@ 191,7 196,7 @@ func (p *PathBuilder) approxCubeTo(ops *ui.Ops, splits int, maxDist float32, ctr
	v := to.Sub(ctrl1.Mul(3)).Add(ctrl0.Mul(3)).Sub(p.pen)
	d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36)
	if d2 <= maxDist*maxDist {
		p.quadTo(ops, c, to)
		p.quadTo(c, to)
		return splits
	}
	// De Casteljau split the curve and approximate the halves.


@@ 203,8 208,8 @@ func (p *PathBuilder) approxCubeTo(ops *ui.Ops, splits int, maxDist float32, ctr
	c12 := c1.Add(c2.Sub(c1).Mul(t))
	c0112 := c01.Add(c12.Sub(c01).Mul(t))
	splits++
	splits = p.approxCubeTo(ops, splits, maxDist, c0, c01, c0112)
	splits = p.approxCubeTo(ops, splits, maxDist, c12, c2, to)
	splits = p.approxCubeTo(splits, maxDist, c0, c01, c0112)
	splits = p.approxCubeTo(splits, maxDist, c12, c2, to)
	return splits
}



@@ 220,7 225,7 @@ func (p *PathBuilder) expand(b f32.Rectangle) {
	p.bounds = p.bounds.Union(b)
}

func (p *PathBuilder) vertex(o *ui.Ops, cornerx, cornery int16, ctrl, to f32.Point) {
func (p *PathBuilder) vertex(cornerx, cornery int16, ctrl, to f32.Point) {
	p.nverts++
	v := path.Vertex{
		CornerX: cornerx,


@@ 246,10 251,10 @@ func (p *PathBuilder) vertex(o *ui.Ops, cornerx, cornery int16, ctrl, to f32.Poi
	bo.PutUint32(data[21:], math.Float32bits(v.CtrlY))
	bo.PutUint32(data[25:], math.Float32bits(v.ToX))
	bo.PutUint32(data[29:], math.Float32bits(v.ToY))
	o.Write(data)
	p.ops.Write(data)
}

func (p *PathBuilder) simpleQuadTo(ops *ui.Ops, ctrl, to f32.Point) {
func (p *PathBuilder) simpleQuadTo(ctrl, to f32.Point) {
	if p.pen.Y > p.maxy {
		p.maxy = p.pen.Y
	}


@@ 260,19 265,19 @@ func (p *PathBuilder) simpleQuadTo(ops *ui.Ops, ctrl, to f32.Point) {
		p.maxy = to.Y
	}
	// NW.
	p.vertex(ops, -1, 1, ctrl, to)
	p.vertex(-1, 1, ctrl, to)
	// NE.
	p.vertex(ops, 1, 1, ctrl, to)
	p.vertex(1, 1, ctrl, to)
	// SW.
	p.vertex(ops, -1, -1, ctrl, to)
	p.vertex(-1, -1, ctrl, to)
	// SE.
	p.vertex(ops, 1, -1, ctrl, to)
	p.vertex(1, -1, ctrl, to)
	p.pen = to
}

func (p *PathBuilder) End(ops *ui.Ops) {
	p.end(ops)
func (p *PathBuilder) End() {
	p.end()
	opClip{
		bounds: p.bounds,
	}.Add(ops)
	}.Add(p.ops)
}

M ui/layout/flex.go => ui/layout/flex.go +17 -15
@@ 15,6 15,7 @@ type Flex struct {
	CrossAxisAlignment CrossAxisAlignment
	MainAxisSize       MainAxisSize

	ops         *ui.Ops
	constrained bool
	cs          Constraints
	begun       bool


@@ 57,10 58,11 @@ const (
	Stretch
)

func (f *Flex) Init(cs Constraints) {
func (f *Flex) Init(ops *ui.Ops, cs Constraints) {
	if f.constrained {
		panic("Constrain must be called exactly once")
	}
	f.ops = ops
	f.constrained = true
	f.cs = cs
	f.taken = 0


@@ 68,7 70,7 @@ func (f *Flex) Init(cs Constraints) {
	f.maxBaseline = 0
}

func (f *Flex) begin(ops *ui.Ops) {
func (f *Flex) begin() {
	if !f.constrained {
		panic("must Constrain before adding a child")
	}


@@ 76,12 78,12 @@ func (f *Flex) begin(ops *ui.Ops) {
		panic("must End before adding a child")
	}
	f.begun = true
	ops.Begin()
	ui.OpLayer{}.Add(ops)
	f.ops.Begin()
	ui.OpLayer{}.Add(f.ops)
}

func (f *Flex) Rigid(ops *ui.Ops) Constraints {
	f.begin(ops)
func (f *Flex) Rigid() Constraints {
	f.begin()
	mainc := axisMainConstraint(f.Axis, f.cs)
	mainMax := mainc.Max
	if mainc.Max != ui.Inf {


@@ 90,8 92,8 @@ func (f *Flex) Rigid(ops *ui.Ops) Constraints {
	return axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs))
}

func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode) Constraints {
	f.begin(ops)
func (f *Flex) Flexible(flex float32, mode FlexMode) Constraints {
	f.begin()
	mainc := axisMainConstraint(f.Axis, f.cs)
	var flexSize int
	if mainc.Max != ui.Inf && mainc.Max > f.taken {


@@ 104,12 106,12 @@ func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode) Constraints {
	return axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs))
}

func (f *Flex) End(ops *ui.Ops, dims Dimens) FlexChild {
func (f *Flex) End(dims Dimens) FlexChild {
	if !f.begun {
		panic("End called without an active child")
	}
	f.begun = false
	block := ops.End()
	block := f.ops.End()
	f.taken += axisMain(f.Axis, dims.Size)
	if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
		f.maxCross = c


@@ 120,7 122,7 @@ func (f *Flex) End(ops *ui.Ops, dims Dimens) FlexChild {
	return FlexChild{block, dims}
}

func (f *Flex) Layout(ops *ui.Ops, children ...FlexChild) Dimens {
func (f *Flex) Layout(children ...FlexChild) Dimens {
	mainc := axisMainConstraint(f.Axis, f.cs)
	crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross)
	var space int


@@ 157,12 159,12 @@ func (f *Flex) Layout(ops *ui.Ops, children ...FlexChild) Dimens {
				cross = f.maxBaseline - b
			}
		}
		ui.OpPush{}.Add(ops)
		ui.OpPush{}.Add(f.ops)
		ui.OpTransform{
			Transform: ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))),
		}.Add(ops)
		child.block.Add(ops)
		ui.OpPop{}.Add(ops)
		}.Add(f.ops)
		child.block.Add(f.ops)
		ui.OpPop{}.Add(f.ops)
		mainSize += axisMain(f.Axis, dims.Size)
		switch f.MainAxisAlignment {
		case SpaceEvenly:

M ui/layout/layout.go => ui/layout/layout.go +9 -3
@@ 70,10 70,12 @@ func ExactConstraints(size image.Point) Constraints {
type Insets struct {
	Top, Right, Bottom, Left float32

	cs Constraints
	ops *ui.Ops
	cs  Constraints
}

func (in *Insets) Begin(ops *ui.Ops, cs Constraints) Constraints {
	in.ops = ops
	in.cs = cs
	mcs := cs
	t, r, b, l := int(math.Round(float64(in.Top))), int(math.Round(float64(in.Right))), int(math.Round(float64(in.Bottom))), int(math.Round(float64(in.Left)))


@@ 102,7 104,8 @@ func (in *Insets) Begin(ops *ui.Ops, cs Constraints) Constraints {
	return mcs
}

func (in *Insets) End(ops *ui.Ops, dims Dimens) Dimens {
func (in *Insets) End(dims Dimens) Dimens {
	ops := in.ops
	ui.OpPop{}.Add(ops)
	t, r, b, l := int(math.Round(float64(in.Top))), int(math.Round(float64(in.Right))), int(math.Round(float64(in.Bottom))), int(math.Round(float64(in.Left)))
	return Dimens{


@@ 144,17 147,20 @@ func (s Sized) Constrain(cs Constraints) Constraints {
}

type Align struct {
	ops       *ui.Ops
	Alignment Direction
	cs        Constraints
}

func (a *Align) Begin(ops *ui.Ops, cs Constraints) Constraints {
	a.ops = ops
	a.cs = cs
	ops.Begin()
	return cs.Loose()
}

func (a *Align) End(ops *ui.Ops, dims Dimens) Dimens {
func (a *Align) End(dims Dimens) Dimens {
	ops := a.ops
	block := ops.End()
	sz := dims.Size
	if a.cs.Width.Max != ui.Inf {

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

	ops       *ui.Ops
	scroll    gesture.Scroll
	scrollDir int



@@ 46,7 47,8 @@ const (
	iterateBackward
)

func (l *List) Init(cs Constraints, len int) {
func (l *List) Init(ops *ui.Ops, cs Constraints, len int) {
	l.ops = ops
	l.dir = iterateNone
	l.maxSize = 0
	l.children = l.children[:0]


@@ 55,6 57,7 @@ func (l *List) Init(cs Constraints, len int) {
	if l.first > len {
		l.first = len
	}
	ops.Begin()
}

func (l *List) Dragging() bool {


@@ 69,19 72,16 @@ func (l *List) Update(c *ui.Config, q pointer.Events) {
	l.offset += d
}

func (l *List) Next(ops *ui.Ops) (int, Constraints, bool) {
func (l *List) Next() (int, Constraints, bool) {
	if l.dir != iterateNone {
		panic("a previous Next was not finished with Elem")
	}
	i, ok := l.next()
	var cs Constraints
	if ok {
		if len(l.children) == 0 {
			ops.Begin()
		}
		cs = axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
		ops.Begin()
		ui.OpLayer{}.Add(ops)
		l.ops.Begin()
		ui.OpLayer{}.Add(l.ops)
	}
	return i, cs, ok
}


@@ 110,8 110,8 @@ func (l *List) next() (int, bool) {
	return 0, false
}

func (l *List) End(ops *ui.Ops, dims Dimens) {
	block := ops.End()
func (l *List) End(dims Dimens) {
	block := l.ops.End()
	child := scrollChild{dims.Size, block}
	switch l.dir {
	case iterateForward:


@@ 130,7 130,7 @@ func (l *List) End(ops *ui.Ops, dims Dimens) {
	l.dir = iterateNone
}

func (l *List) Layout(ops *ui.Ops) Dimens {
func (l *List) Layout() Dimens {
	mainc := axisMainConstraint(l.Axis, l.cs)
	for len(l.children) > 0 {
		sz := l.children[0].size


@@ 155,6 155,7 @@ func (l *List) Layout(ops *ui.Ops) Dimens {
			break
		}
	}
	ops := l.ops
	pos := -l.offset
	for _, child := range l.children {
		sz := child.size


@@ 192,12 193,10 @@ func (l *List) Layout(ops *ui.Ops) Dimens {
		l.scroll.Stop()
	}
	dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
	if len(l.children) > 0 {
		block := ops.End()
		pointer.AreaRect(dims).Add(ops)
		l.scroll.Add(ops)
		block.Add(ops)
	}
	block := ops.End()
	pointer.AreaRect(dims).Add(ops)
	l.scroll.Add(ops)
	block.Add(ops)
	return Dimens{Size: dims}
}


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

	ops         *ui.Ops
	constrained bool
	cs          Constraints
	begun       bool


@@ 36,14 37,15 @@ const (
	W
)

func (s *Stack) Init(cs Constraints) {
func (s *Stack) Init(ops *ui.Ops, cs Constraints) {
	s.ops = ops
	s.cs = cs
	s.constrained = true
	s.maxSZ = image.Point{}
	s.baseline = 0
}

func (s *Stack) begin(ops *ui.Ops) {
func (s *Stack) begin() {
	if !s.constrained {
		panic("must Constrain before adding a child")
	}


@@ 51,25 53,25 @@ func (s *Stack) begin(ops *ui.Ops) {
		panic("must End before adding a child")
	}
	s.begun = true
	ops.Begin()
	ui.OpLayer{}.Add(ops)
	s.ops.Begin()
	ui.OpLayer{}.Add(s.ops)
}

func (s *Stack) Rigid(ops *ui.Ops) Constraints {
	s.begin(ops)
func (s *Stack) Rigid() Constraints {
	s.begin()
	return s.cs
}

func (s *Stack) Expand(ops *ui.Ops) Constraints {
	s.begin(ops)
func (s *Stack) Expand() Constraints {
	s.begin()
	return Constraints{
		Width:  Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
		Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
	}
}

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


@@ 85,7 87,7 @@ func (s *Stack) End(ops *ui.Ops, dims Dimens) StackChild {
	return StackChild{b, dims}
}

func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens {
func (s *Stack) Layout(children ...StackChild) Dimens {
	for _, ch := range children {
		sz := ch.dims.Size
		var p image.Point


@@ 101,10 103,10 @@ func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens {
		case SW, S, SE:
			p.Y = s.maxSZ.Y - sz.Y
		}
		ui.OpPush{}.Add(ops)
		ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops)
		ch.block.Add(ops)
		ui.OpPop{}.Add(ops)
		ui.OpPush{}.Add(s.ops)
		ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(s.ops)
		ch.block.Add(s.ops)
		ui.OpPop{}.Add(s.ops)
	}
	b := s.baseline
	if b == 0 {

M ui/measure/measure.go => ui/measure/measure.go +7 -6
@@ 233,6 233,7 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.OpBlock {
	var lastPos f32.Point
	var builder draw.PathBuilder
	ops := new(ui.Ops)
	builder.Init(ops)
	var x fixed.Int26_6
	var advIdx int
	ops.Begin()


@@ 246,7 247,7 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.OpBlock {
			pos := f32.Point{
				X: float32(x) / 64,
			}
			builder.Move(ops, pos.Sub(lastPos))
			builder.Move(pos.Sub(lastPos))
			lastPos = pos
			var lastArg f32.Point
			// Convert sfnt.Segments to relative segments.


@@ 271,13 272,13 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.OpBlock {
				}
				switch fseg.Op {
				case sfnt.SegmentOpMoveTo:
					builder.Move(ops, args[0])
					builder.Move(args[0])
				case sfnt.SegmentOpLineTo:
					builder.Line(ops, args[0])
					builder.Line(args[0])
				case sfnt.SegmentOpQuadTo:
					builder.Quad(ops, args[0], args[1])
					builder.Quad(args[0], args[1])
				case sfnt.SegmentOpCubeTo:
					builder.Cube(ops, args[0], args[1], args[2])
					builder.Cube(args[0], args[1], args[2])
				default:
					panic("unsupported segment op")
				}


@@ 287,6 288,6 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.OpBlock {
		x += str.Advances[advIdx]
		advIdx++
	}
	builder.End(ops)
	builder.End()
	return ops.End()
}