From 9b3429d6da910239c8c8979ae1428dd9783d76a3 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Thu, 30 May 2019 21:39:00 +0200 Subject: [PATCH] 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 --- ui/layout/flex.go | 68 +++++++++++++++++++++------------ ui/layout/layout.go | 44 ++++++++++++---------- ui/layout/list.go | 92 +++++++++++++++++++++++---------------------- ui/layout/stack.go | 65 ++++++++++++++++++++------------ ui/text/editor.go | 2 +- ui/text/label.go | 2 +- ui/widget/image.go | 2 +- 7 files changed, 159 insertions(+), 116 deletions(-) diff --git a/ui/layout/flex.go b/ui/layout/flex.go index ba8ede47..fc39771f 100644 --- a/ui/layout/flex.go +++ b/ui/layout/flex.go @@ -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) +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") + } + 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 if mainc.Max != ui.Inf { mainMax -= f.taken } - cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.Constraints)) - 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} + 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 { diff --git a/ui/layout/layout.go b/ui/layout/layout.go index 96dbaec3..973d43f8 100644 --- a/ui/layout/layout.go +++ b/ui/layout/layout.go @@ -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 { diff --git a/ui/layout/list.go b/ui/layout/list.go index 1093ea7b..d3673271 100644 --- a/ui/layout/list.go +++ b/ui/layout/list.go @@ -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) { - i, ok := l.next() - if !ok { - l.draw() +func (l *List) Next(ops *ui.Ops) (int, Constraints, bool) { + if l.dir != iterateNone { + panic("a previous Next was not finished with Elem") } - return i, ok -} - -func (l *List) Layout() Dimens { - l.area.Size = l.size - return Dimens{Size: l.size} + i, ok := l.next() + 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, 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 { diff --git a/ui/layout/stack.go b/ui/layout/stack.go index 13775e4a..1b6de4a3 100644 --- a/ui/layout/stack.go +++ b/ui/layout/stack.go @@ -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,10 +36,42 @@ const ( 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() 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() if w := dims.Size.X; w > s.maxSZ.X { 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 { 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 b := dims.Baseline; b != dims.Size.Y { s.baseline = b } } + return StackChild{b, dims} } func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens { diff --git a/ui/text/editor.go b/ui/text/editor.go index 1ca18be5..fe9d310d 100644 --- a/ui/text/editor.go +++ b/ui/text/editor.go @@ -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 diff --git a/ui/text/label.go b/ui/text/label.go index a1d16d0f..ac02f4d2 100644 --- a/ui/text/label.go +++ b/ui/text/label.go @@ -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) diff --git a/ui/widget/image.go b/ui/widget/image.go index c133c0e0..3eaef624 100644 --- a/ui/widget/image.go +++ b/ui/widget/image.go @@ -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