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>
This commit is contained in:
Elias Naur
2019-05-30 21:39:00 +02:00
parent 5cc9906eb8
commit 9b3429d6da
7 changed files with 159 additions and 116 deletions
+43 -25
View File
@@ -10,13 +10,14 @@ import (
) )
type Flex struct { type Flex struct {
Constraints Constraints
Axis Axis Axis Axis
MainAxisAlignment MainAxisAlignment MainAxisAlignment MainAxisAlignment
CrossAxisAlignment CrossAxisAlignment CrossAxisAlignment CrossAxisAlignment
MainAxisSize MainAxisSize MainAxisSize MainAxisSize
constrained bool
cs Constraints
begun bool
taken int taken int
maxCross int maxCross int
maxBaseline int maxBaseline int
@@ -56,29 +57,42 @@ const (
Stretch Stretch
) )
func (f *Flex) Rigid(ops *ui.Ops, w Widget) FlexChild { func (f *Flex) Init(cs Constraints) {
mainc := axisMainConstraint(f.Axis, f.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")
}
if f.begun {
panic("must End before adding a child")
}
f.begun = true
ops.Begin()
ui.OpLayer{}.Add(ops)
}
func (f *Flex) Rigid(ops *ui.Ops) Constraints {
f.begin(ops)
mainc := axisMainConstraint(f.Axis, f.cs)
mainMax := mainc.Max mainMax := mainc.Max
if mainc.Max != ui.Inf { if mainc.Max != ui.Inf {
mainMax -= f.taken mainMax -= f.taken
} }
cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.Constraints)) return axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs))
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
}
return FlexChild{block, dims}
} }
func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode, w Widget) FlexChild { func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode) Constraints {
mainc := axisMainConstraint(f.Axis, f.Constraints) f.begin(ops)
mainc := axisMainConstraint(f.Axis, f.cs)
var flexSize int var flexSize int
if mainc.Max != ui.Inf && mainc.Max > f.taken { if mainc.Max != ui.Inf && mainc.Max > f.taken {
flexSize = 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 { if mode == Fit {
submainc.Min = submainc.Max submainc.Min = submainc.Max
} }
cs := axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.Constraints)) return axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs))
ops.Begin() }
ui.OpLayer{}.Add(ops)
dims := w(ops, 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() block := ops.End()
f.taken += axisMain(f.Axis, dims.Size) f.taken += axisMain(f.Axis, dims.Size)
if c := axisCross(f.Axis, dims.Size); c > f.maxCross { 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 { func (f *Flex) Layout(ops *ui.Ops, children ...FlexChild) Dimens {
mainc := axisMainConstraint(f.Axis, f.Constraints) mainc := axisMainConstraint(f.Axis, f.cs)
crossSize := axisCrossConstraint(f.Axis, f.Constraints).Constrain(f.maxCross) crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross)
var space int var space int
if mainc.Max != ui.Inf && f.MainAxisSize == Max { if mainc.Max != ui.Inf && f.MainAxisSize == Max {
if mainc.Max > f.taken { if mainc.Max > f.taken {
+25 -19
View File
@@ -9,8 +9,6 @@ import (
"gioui.org/ui" "gioui.org/ui"
) )
type Widget func(ops *ui.Ops, cs Constraints) Dimens
type Constraints struct { type Constraints struct {
Width Constraint Width Constraint
Height Constraint Height Constraint
@@ -70,12 +68,13 @@ func ExactConstraints(size image.Point) Constraints {
} }
type Insets struct { type Insets struct {
C Widget
Top, Right, Bottom, Left float32 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 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))) 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 { if mcs.Width.Max != ui.Inf {
@@ -100,16 +99,20 @@ func (in Insets) W(ops *ui.Ops, cs Constraints) Dimens {
} }
ops.Begin() ops.Begin()
ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t}))}.Add(ops) 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) 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{ 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, Baseline: dims.Baseline + t,
} }
} }
func EqualInsets(v float32, w Widget) Insets { func EqualInsets(v float32) Insets {
return Insets{C: w, Top: v, Right: v, Bottom: v, Left: v} return Insets{Top: v, Right: v, Bottom: v, Left: v}
} }
func isInf(v ui.Value) bool { func isInf(v ui.Value) bool {
@@ -117,11 +120,10 @@ func isInf(v ui.Value) bool {
} }
type Sized struct { type Sized struct {
C Widget
Width, Height float32 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 h := int(s.Height + 0.5); h != 0 {
if cs.Height.Min < h { if cs.Height.Min < h {
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 cs.Width.Max = w
} }
} }
return s.C(ops, cs) return cs
} }
type Align struct { type Align struct {
C Widget
Alignment Direction 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() ops.Begin()
dims := a.C(ops, cs.Loose()) return cs.Loose()
}
func (a *Align) End(ops *ui.Ops, dims Dimens) Dimens {
block := ops.End() block := ops.End()
sz := dims.Size sz := dims.Size
if cs.Width.Max != ui.Inf { if a.cs.Width.Max != ui.Inf {
sz.X = cs.Width.Max sz.X = a.cs.Width.Max
} }
if cs.Height.Max != ui.Inf { if a.cs.Height.Max != ui.Inf {
sz.Y = cs.Height.Max sz.Y = a.cs.Height.Max
} }
var p image.Point var p image.Point
switch a.Alignment { switch a.Alignment {
+47 -45
View File
@@ -31,31 +31,31 @@ type List struct {
offset int offset int
first int first int
ops *ui.Ops
cs Constraints cs Constraints
len int len int
maxSize int maxSize int
children []scrollChild children []scrollChild
elemForward bool dir iterationDir
size image.Point
} }
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.maxSize = 0
l.children = l.children[:0] l.children = l.children[:0]
l.ops = ops
l.cs = cs l.cs = cs
l.len = len l.len = len
if l.first > len { if l.first > len {
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 { func (l *List) Dragging() bool {
@@ -70,24 +70,28 @@ func (l *List) Scroll(c *ui.Config, q pointer.Events) {
l.offset += d l.offset += d
} }
func (l *List) Index() (int, bool) { func (l *List) Next(ops *ui.Ops) (int, Constraints, bool) {
i, ok := l.next() if l.dir != iterateNone {
if !ok { panic("a previous Next was not finished with Elem")
l.draw()
} }
return i, ok i, ok := l.next()
} var cs Constraints
if ok {
func (l *List) Layout() Dimens { if len(l.children) == 0 {
l.area.Size = l.size l.scroll.Op(ops, &l.area)
return Dimens{Size: l.size} }
cs = axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
ops.Begin()
ui.OpLayer{}.Add(ops)
}
return i, cs, ok
} }
func (l *List) next() (int, bool) { func (l *List) next() (int, bool) {
mainc := axisMainConstraint(l.Axis, l.cs) mainc := axisMainConstraint(l.Axis, l.cs)
if l.offset <= 0 { if l.offset <= 0 {
if l.first > 0 { if l.first > 0 {
l.elemForward = false l.dir = iterateBackward
return l.first - 1, true return l.first - 1, true
} }
l.offset = 0 l.offset = 0
@@ -95,7 +99,7 @@ func (l *List) next() (int, bool) {
if l.maxSize-l.offset < mainc.Max { if l.maxSize-l.offset < mainc.Max {
i := l.first + len(l.children) i := l.first + len(l.children)
if i < l.len { if i < l.len {
l.elemForward = true l.dir = iterateForward
return i, true return i, true
} }
missing := mainc.Max - (l.maxSize - l.offset) missing := mainc.Max - (l.maxSize - l.offset)
@@ -107,31 +111,27 @@ func (l *List) next() (int, bool) {
return 0, false return 0, false
} }
func (l *List) Elem(w Widget) { func (l *List) End(ops *ui.Ops, dims Dimens) {
child := l.add(w) block := ops.End()
if l.elemForward { child := scrollChild{dims.Size, block}
switch l.dir {
case iterateForward:
mainSize := axisMain(l.Axis, child.size) mainSize := axisMain(l.Axis, child.size)
l.maxSize += mainSize l.maxSize += mainSize
l.children = append(l.children, child) l.children = append(l.children, child)
} else { case iterateBackward:
l.first-- l.first--
mainSize := axisMain(l.Axis, child.size) mainSize := axisMain(l.Axis, child.size)
l.offset += mainSize l.offset += mainSize
l.maxSize += mainSize l.maxSize += mainSize
l.children = append([]scrollChild{child}, l.children...) l.children = append([]scrollChild{child}, l.children...)
default:
panic("call Next before End")
} }
l.dir = iterateNone
} }
func (l *List) add(w Widget) scrollChild { func (l *List) Layout(ops *ui.Ops) Dimens {
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() {
mainc := axisMainConstraint(l.Axis, l.cs) mainc := axisMainConstraint(l.Axis, l.cs)
for len(l.children) > 0 { for len(l.children) > 0 {
sz := l.children[0].size sz := l.children[0].size
@@ -178,13 +178,13 @@ func (l *List) draw() {
Min: axisPoint(l.Axis, min, -ui.Inf), Min: axisPoint(l.Axis, min, -ui.Inf),
Max: axisPoint(l.Axis, max, ui.Inf), Max: axisPoint(l.Axis, max, ui.Inf),
} }
l.ops.Begin() ops.Begin()
draw.OpClip{Path: draw.RectPath(r)}.Add(l.ops) draw.OpClip{Path: draw.RectPath(r)}.Add(ops)
ui.OpTransform{ ui.OpTransform{
Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))), Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))),
}.Add(l.ops) }.Add(ops)
child.block.Add(l.ops) child.block.Add(ops)
l.ops.End().Add(l.ops) ops.End().Add(ops)
pos += axisMain(l.Axis, sz) pos += axisMain(l.Axis, sz)
} }
atStart := l.first == 0 && l.offset <= 0 atStart := l.first == 0 && l.offset <= 0
@@ -192,7 +192,9 @@ func (l *List) draw() {
if atStart && l.scrollDir < 0 || atEnd && l.scrollDir > 0 { if atStart && l.scrollDir < 0 || atEnd && l.scrollDir > 0 {
l.scroll.Stop() 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 { func (l *List) crossConstraintChild(cs Constraints) Constraint {
+41 -24
View File
@@ -9,11 +9,13 @@ import (
) )
type Stack struct { type Stack struct {
Alignment Direction Alignment Direction
Constraints Constraints
maxSZ image.Point constrained bool
baseline int cs Constraints
begun bool
maxSZ image.Point
baseline int
} }
type StackChild struct { type StackChild struct {
@@ -34,10 +36,42 @@ const (
W W
) )
func (s *Stack) Rigid(ops *ui.Ops, w Widget) StackChild { 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 s.begun {
panic("must End before adding a child")
}
s.begun = true
ops.Begin() ops.Begin()
ui.OpLayer{}.Add(ops) ui.OpLayer{}.Add(ops)
dims := w(ops, s.Constraints) }
func (s *Stack) Rigid(ops *ui.Ops) Constraints {
ops.Begin()
ui.OpLayer{}.Add(ops)
return s.cs
}
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)
return cs
}
func (s *Stack) End(ops *ui.Ops, dims Dimens) StackChild {
b := ops.End() b := ops.End()
if w := dims.Size.X; w > s.maxSZ.X { if w := dims.Size.X; w > s.maxSZ.X {
s.maxSZ.X = w s.maxSZ.X = w
@@ -45,29 +79,12 @@ func (s *Stack) Rigid(ops *ui.Ops, w Widget) StackChild {
if h := dims.Size.Y; h > s.maxSZ.Y { if h := dims.Size.Y; h > s.maxSZ.Y {
s.maxSZ.Y = h s.maxSZ.Y = h
} }
s.addjustBaseline(dims)
return StackChild{b, dims}
}
func (s *Stack) Expand(ops *ui.Ops, w Widget) StackChild {
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}
}
func (s *Stack) addjustBaseline(dims Dimens) {
if s.baseline == 0 { if s.baseline == 0 {
if b := dims.Baseline; b != dims.Size.Y { if b := dims.Baseline; b != dims.Size.Y {
s.baseline = b s.baseline = b
} }
} }
return StackChild{b, dims}
} }
func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens { func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens {
+1 -1
View File
@@ -128,7 +128,7 @@ func (e *Editor) caretWidth() fixed.Int26_6 {
return fixed.Int26_6(oneDp * 64) 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) twoDp := int(e.cfg.Val(ui.Dp(2)) + 0.5)
e.padLeft, e.padRight = twoDp, twoDp e.padLeft, e.padRight = twoDp, twoDp
maxWidth := cs.Width.Max maxWidth := cs.Width.Max
+1 -1
View File
@@ -80,7 +80,7 @@ func (l *lineIterator) Next() (String, f32.Point, bool) {
return String{}, f32.Point{}, false 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) textLayout := l.Face.Layout(l.Text, false, cs.Width.Max)
lines := textLayout.Lines lines := textLayout.Lines
dims := linesDimens(lines) dims := linesDimens(lines)
+1 -1
View File
@@ -16,7 +16,7 @@ type Image struct {
Rect image.Rectangle 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} d := image.Point{X: cs.Width.Max, Y: cs.Height.Max}
if d.X == ui.Inf { if d.X == ui.Inf {
d.X = cs.Width.Min d.X = cs.Width.Min