~eliasnaur/gio

9b3429d6da910239c8c8979ae1428dd9783d76a3 — Elias Naur 2 years ago 5cc9906
ui: switch to (more) explicit layout

The layout package switched from interfaces to functions for
composing layouts. The switch made sure that no garbage is
generated for transient layouts such as Align, Inset, Stack, Flex.

Unfortunately, that left the stateful widgets and layouts: as soon
as their layout methods are embedded in a transient layout, a
closure is generated that escapes to the heap.

To avoid garbage for both transient as well as stateful widgets,
replace the functional approach with explicit begin/end methods.

A begin method generally starts an op block and returns the adjusted
constraints. An end method takes computed dimensions, ends its op
block and returns adjusted dimensions.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
M ui/layout/flex.go => ui/layout/flex.go +43 -25
@@ 10,13 10,14 @@ import (
)

type Flex struct {
	Constraints Constraints

	Axis               Axis
	MainAxisAlignment  MainAxisAlignment
	CrossAxisAlignment CrossAxisAlignment
	MainAxisSize       MainAxisSize

	constrained bool
	cs          Constraints
	begun       bool
	taken       int
	maxCross    int
	maxBaseline int


@@ 56,29 57,42 @@ const (
	Stretch
)

func (f *Flex) Rigid(ops *ui.Ops, w Widget) FlexChild {
	mainc := axisMainConstraint(f.Axis, f.Constraints)
	mainMax := mainc.Max
	if mainc.Max != ui.Inf {
		mainMax -= f.taken
func (f *Flex) Init(cs Constraints) {
	if f.constrained {
		panic("Constrain must be called exactly once")
	}
	f.constrained = true
	f.cs = cs
	f.taken = 0
	f.maxCross = 0
	f.maxBaseline = 0
}

func (f *Flex) begin(ops *ui.Ops) {
	if !f.constrained {
		panic("must Constrain before adding a child")
	}
	cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.Constraints))
	if f.begun {
		panic("must End before adding a child")
	}
	f.begun = true
	ops.Begin()
	ui.OpLayer{}.Add(ops)
	dims := w(ops, cs)
	block := ops.End()
	f.taken += axisMain(f.Axis, dims.Size)
	if c := axisCross(f.Axis, dims.Size); c > f.maxCross {
		f.maxCross = c
	}
	if b := dims.Baseline; b > f.maxBaseline {
		f.maxBaseline = b
}

func (f *Flex) Rigid(ops *ui.Ops) Constraints {
	f.begin(ops)
	mainc := axisMainConstraint(f.Axis, f.cs)
	mainMax := mainc.Max
	if mainc.Max != ui.Inf {
		mainMax -= f.taken
	}
	return FlexChild{block, dims}
	return axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs))
}

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


@@ 87,10 101,14 @@ func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode, w Widget) Flex
	if mode == Fit {
		submainc.Min = submainc.Max
	}
	cs := axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.Constraints))
	ops.Begin()
	ui.OpLayer{}.Add(ops)
	dims := w(ops, cs)
	return axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs))
}

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


@@ 103,8 121,8 @@ func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode, w Widget) Flex
}

func (f *Flex) Layout(ops *ui.Ops, children ...FlexChild) Dimens {
	mainc := axisMainConstraint(f.Axis, f.Constraints)
	crossSize := axisCrossConstraint(f.Axis, f.Constraints).Constrain(f.maxCross)
	mainc := axisMainConstraint(f.Axis, f.cs)
	crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross)
	var space int
	if mainc.Max != ui.Inf && f.MainAxisSize == Max {
		if mainc.Max > f.taken {

M ui/layout/layout.go => ui/layout/layout.go +25 -19
@@ 9,8 9,6 @@ import (
	"gioui.org/ui"
)

type Widget func(ops *ui.Ops, cs Constraints) Dimens

type Constraints struct {
	Width  Constraint
	Height Constraint


@@ 70,12 68,13 @@ func ExactConstraints(size image.Point) Constraints {
}

type Insets struct {
	C Widget

	Top, Right, Bottom, Left float32

	cs Constraints
}

func (in Insets) W(ops *ui.Ops, cs Constraints) Dimens {
func (in *Insets) Begin(ops *ui.Ops, cs Constraints) Constraints {
	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)))
	if mcs.Width.Max != ui.Inf {


@@ 100,16 99,20 @@ func (in Insets) W(ops *ui.Ops, cs Constraints) Dimens {
	}
	ops.Begin()
	ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t}))}.Add(ops)
	dims := in.C(ops, mcs)
	return mcs
}

func (in *Insets) End(ops *ui.Ops, dims Dimens) Dimens {
	ops.End().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{
		Size:     cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})),
		Size:     in.cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})),
		Baseline: dims.Baseline + t,
	}
}

func EqualInsets(v float32, w Widget) Insets {
	return Insets{C: w, Top: v, Right: v, Bottom: v, Left: v}
func EqualInsets(v float32) Insets {
	return Insets{Top: v, Right: v, Bottom: v, Left: v}
}

func isInf(v ui.Value) bool {


@@ 117,11 120,10 @@ func isInf(v ui.Value) bool {
}

type Sized struct {
	C             Widget
	Width, Height float32
}

func (s Sized) W(ops *ui.Ops, cs Constraints) Dimens {
func (s Sized) Constrain(cs Constraints) Constraints {
	if h := int(s.Height + 0.5); h != 0 {
		if cs.Height.Min < h {
			cs.Height.Min = h


@@ 138,24 140,28 @@ func (s Sized) W(ops *ui.Ops, cs Constraints) Dimens {
			cs.Width.Max = w
		}
	}
	return s.C(ops, cs)
	return cs
}

type Align struct {
	C         Widget
	Alignment Direction
	cs        Constraints
}

func (a Align) W(ops *ui.Ops, cs Constraints) Dimens {
func (a *Align) Begin(ops *ui.Ops, cs Constraints) Constraints {
	a.cs = cs
	ops.Begin()
	dims := a.C(ops, cs.Loose())
	return cs.Loose()
}

func (a *Align) End(ops *ui.Ops, dims Dimens) Dimens {
	block := ops.End()
	sz := dims.Size
	if cs.Width.Max != ui.Inf {
		sz.X = cs.Width.Max
	if a.cs.Width.Max != ui.Inf {
		sz.X = a.cs.Width.Max
	}
	if cs.Height.Max != ui.Inf {
		sz.Y = cs.Height.Max
	if a.cs.Height.Max != ui.Inf {
		sz.Y = a.cs.Height.Max
	}
	var p image.Point
	switch a.Alignment {

M ui/layout/list.go => ui/layout/list.go +46 -44
@@ 31,31 31,31 @@ type List struct {
	offset int
	first  int

	ops *ui.Ops
	cs  Constraints
	len int

	maxSize     int
	children    []scrollChild
	elemForward bool

	size image.Point
	maxSize  int
	children []scrollChild
	dir      iterationDir
}

func (l *List) Init(ops *ui.Ops, cs Constraints, len int) (int, bool) {
type iterationDir uint8

const (
	iterateNone iterationDir = iota
	iterateForward
	iterateBackward
)

func (l *List) Init(cs Constraints, len int) {
	l.dir = iterateNone
	l.maxSize = 0
	l.children = l.children[:0]
	l.ops = ops
	l.cs = cs
	l.len = len
	if l.first > len {
		l.first = len
	}
	if len == 0 {
		return 0, false
	}
	l.scroll.Op(ops, &l.area)
	return l.Index()
}

func (l *List) Dragging() bool {


@@ 70,24 70,28 @@ func (l *List) Scroll(c *ui.Config, q pointer.Events) {
	l.offset += d
}

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

func (l *List) Layout() Dimens {
	l.area.Size = l.size
	return Dimens{Size: l.size}
	return i, cs, ok
}

func (l *List) next() (int, bool) {
	mainc := axisMainConstraint(l.Axis, l.cs)
	if l.offset <= 0 {
		if l.first > 0 {
			l.elemForward = false
			l.dir = iterateBackward
			return l.first - 1, true
		}
		l.offset = 0


@@ 95,7 99,7 @@ func (l *List) next() (int, bool) {
	if l.maxSize-l.offset < mainc.Max {
		i := l.first + len(l.children)
		if i < l.len {
			l.elemForward = true
			l.dir = iterateForward
			return i, true
		}
		missing := mainc.Max - (l.maxSize - l.offset)


@@ 107,31 111,27 @@ func (l *List) next() (int, bool) {
	return 0, false
}

func (l *List) Elem(w Widget) {
	child := l.add(w)
	if l.elemForward {
func (l *List) End(ops *ui.Ops, dims Dimens) {
	block := ops.End()
	child := scrollChild{dims.Size, block}
	switch l.dir {
	case iterateForward:
		mainSize := axisMain(l.Axis, child.size)
		l.maxSize += mainSize
		l.children = append(l.children, child)
	} else {
	case iterateBackward:
		l.first--
		mainSize := axisMain(l.Axis, child.size)
		l.offset += mainSize
		l.maxSize += mainSize
		l.children = append([]scrollChild{child}, l.children...)
	default:
		panic("call Next before End")
	}
	l.dir = iterateNone
}

func (l *List) add(w Widget) scrollChild {
	subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
	l.ops.Begin()
	ui.OpLayer{}.Add(l.ops)
	dims := w(l.ops, subcs)
	block := l.ops.End()
	return scrollChild{dims.Size, block}
}

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


@@ 178,13 178,13 @@ func (l *List) draw() {
			Min: axisPoint(l.Axis, min, -ui.Inf),
			Max: axisPoint(l.Axis, max, ui.Inf),
		}
		l.ops.Begin()
		draw.OpClip{Path: draw.RectPath(r)}.Add(l.ops)
		ops.Begin()
		draw.OpClip{Path: draw.RectPath(r)}.Add(ops)
		ui.OpTransform{
			Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))),
		}.Add(l.ops)
		child.block.Add(l.ops)
		l.ops.End().Add(l.ops)
		}.Add(ops)
		child.block.Add(ops)
		ops.End().Add(ops)
		pos += axisMain(l.Axis, sz)
	}
	atStart := l.first == 0 && l.offset <= 0


@@ 192,7 192,9 @@ func (l *List) draw() {
	if atStart && l.scrollDir < 0 || atEnd && l.scrollDir > 0 {
		l.scroll.Stop()
	}
	l.size = axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
	dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross)
	l.area.Size = dims
	return Dimens{Size: dims}
}

func (l *List) crossConstraintChild(cs Constraints) Constraint {

M ui/layout/stack.go => ui/layout/stack.go +38 -21
@@ 9,11 9,13 @@ import (
)

type Stack struct {
	Alignment   Direction
	Constraints Constraints
	Alignment Direction

	maxSZ    image.Point
	baseline int
	constrained bool
	cs          Constraints
	begun       bool
	maxSZ       image.Point
	baseline    int
}

type StackChild struct {


@@ 34,40 36,55 @@ const (
	W
)

func (s *Stack) Rigid(ops *ui.Ops, w Widget) StackChild {
	ops.Begin()
	ui.OpLayer{}.Add(ops)
	dims := w(ops, s.Constraints)
	b := ops.End()
	if w := dims.Size.X; w > s.maxSZ.X {
		s.maxSZ.X = w
func (s *Stack) Init(cs Constraints) {
	s.cs = cs
	s.constrained = true
	s.maxSZ = image.Point{}
	s.baseline = 0
}

func (s *Stack) begin(ops *ui.Ops) {
	if !s.constrained {
		panic("must Constrain before adding a child")
	}
	if h := dims.Size.Y; h > s.maxSZ.Y {
		s.maxSZ.Y = h
	if s.begun {
		panic("must End before adding a child")
	}
	s.addjustBaseline(dims)
	return StackChild{b, dims}
	s.begun = true
	ops.Begin()
	ui.OpLayer{}.Add(ops)
}

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

func (s *Stack) Expand(ops *ui.Ops, w Widget) StackChild {
func (s *Stack) Expand(ops *ui.Ops) Constraints {
	cs := Constraints{
		Width:  Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
		Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
	}
	ops.Begin()
	ui.OpLayer{}.Add(ops)
	dims := w(ops, cs)
	b := ops.End()
	s.addjustBaseline(dims)
	return StackChild{b, dims}
	return cs
}

func (s *Stack) addjustBaseline(dims Dimens) {
func (s *Stack) End(ops *ui.Ops, dims Dimens) StackChild {
	b := ops.End()
	if w := dims.Size.X; w > s.maxSZ.X {
		s.maxSZ.X = w
	}
	if h := dims.Size.Y; h > s.maxSZ.Y {
		s.maxSZ.Y = h
	}
	if s.baseline == 0 {
		if b := dims.Baseline; b != dims.Size.Y {
			s.baseline = b
		}
	}
	return StackChild{b, dims}
}

func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens {

M ui/text/editor.go => ui/text/editor.go +1 -1
@@ 128,7 128,7 @@ func (e *Editor) caretWidth() fixed.Int26_6 {
	return fixed.Int26_6(oneDp * 64)
}

func (e *Editor) W(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
	twoDp := int(e.cfg.Val(ui.Dp(2)) + 0.5)
	e.padLeft, e.padRight = twoDp, twoDp
	maxWidth := cs.Width.Max

M ui/text/label.go => ui/text/label.go +1 -1
@@ 80,7 80,7 @@ func (l *lineIterator) Next() (String, f32.Point, bool) {
	return String{}, f32.Point{}, false
}

func (l Label) W(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
func (l Label) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
	textLayout := l.Face.Layout(l.Text, false, cs.Width.Max)
	lines := textLayout.Lines
	dims := linesDimens(lines)

M ui/widget/image.go => ui/widget/image.go +1 -1
@@ 16,7 16,7 @@ type Image struct {
	Rect image.Rectangle
}

func (im Image) W(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
func (im Image) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
	d := image.Point{X: cs.Width.Max, Y: cs.Height.Max}
	if d.X == ui.Inf {
		d.X = cs.Width.Min