From e436dce0e761d9b14cebf80868beed8fd1435835 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Thu, 16 May 2019 11:44:17 +0200 Subject: [PATCH] ui/layout: make layout API explicit With layout.Widget a function instead of an interface, the amount of per-frame garbage can be drastically reduced. The layout code ends up slightly more explicit. As a side benefit, the awkward ordering indexing for Flex and Stack fit nicely into their new explicit Layout methods. Signed-off-by: Elias Naur --- ui/layout/flex.go | 91 +++++++----------- ui/layout/layout.go | 222 ++++++++++++++++++-------------------------- ui/layout/list.go | 40 ++++---- ui/layout/stack.go | 65 +++++-------- 4 files changed, 164 insertions(+), 254 deletions(-) diff --git a/ui/layout/flex.go b/ui/layout/flex.go index 9911dc59..ba8ede47 100644 --- a/ui/layout/flex.go +++ b/ui/layout/flex.go @@ -10,23 +10,19 @@ import ( ) type Flex struct { + Constraints Constraints + Axis Axis MainAxisAlignment MainAxisAlignment CrossAxisAlignment CrossAxisAlignment MainAxisSize MainAxisSize - ops *ui.Ops - cs Constraints - - children []flexChild taken int maxCross int maxBaseline int - - ccache [10]flexChild } -type flexChild struct { +type FlexChild struct { block ui.OpBlock dims Dimens } @@ -60,29 +56,17 @@ const ( Stretch ) -func (f *Flex) Init(ops *ui.Ops, cs Constraints) *Flex { - f.ops = ops - f.cs = cs - if f.children == nil { - f.children = f.ccache[:0] - } - f.children = f.children[:0] - f.maxCross = 0 - f.maxBaseline = 0 - return f -} - -func (f *Flex) Rigid(w Widget) *Flex { - mainc := axisMainConstraint(f.Axis, f.cs) +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 } - cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs)) - f.ops.Begin() - ui.OpLayer{}.Add(f.ops) - dims := w.Layout(f.ops, cs) - block := f.ops.End() + 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 @@ -90,12 +74,11 @@ func (f *Flex) Rigid(w Widget) *Flex { if b := dims.Baseline; b > f.maxBaseline { f.maxBaseline = b } - f.children = append(f.children, flexChild{block, dims}) - return f + return FlexChild{block, dims} } -func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex { - mainc := axisMainConstraint(f.Axis, f.cs) +func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode, w Widget) FlexChild { + mainc := axisMainConstraint(f.Axis, f.Constraints) var flexSize int if mainc.Max != ui.Inf && mainc.Max > f.taken { flexSize = mainc.Max - f.taken @@ -104,11 +87,11 @@ func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex { if mode == Fit { submainc.Min = submainc.Max } - cs := axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs)) - f.ops.Begin() - ui.OpLayer{}.Add(f.ops) - dims := w.Layout(f.ops, cs) - block := f.ops.End() + cs := axisConstraints(f.Axis, submainc, 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 @@ -116,18 +99,12 @@ func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex { if b := dims.Baseline; b > f.maxBaseline { f.maxBaseline = b } - if idx < 0 { - idx += len(f.children) + 1 - } - f.children = append(f.children, flexChild{}) - copy(f.children[idx+1:], f.children[idx:]) - f.children[idx] = flexChild{block, dims} - return f + return FlexChild{block, dims} } -func (f *Flex) Layout() Dimens { - mainc := axisMainConstraint(f.Axis, f.cs) - crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross) +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) var space int if mainc.Max != ui.Inf && f.MainAxisSize == Max { if mainc.Max > f.taken { @@ -144,11 +121,11 @@ func (f *Flex) Layout() Dimens { case End: mainSize += space case SpaceEvenly: - mainSize += space / (1 + len(f.children)) + mainSize += space / (1 + len(children)) case SpaceAround: - mainSize += space / (len(f.children) * 2) + mainSize += space / (len(children) * 2) } - for _, child := range f.children { + for _, child := range children { dims := child.dims b := dims.Baseline var cross int @@ -162,20 +139,20 @@ func (f *Flex) Layout() Dimens { cross = f.maxBaseline - b } } - f.ops.Begin() + ops.Begin() ui.OpTransform{ Transform: ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))), - }.Add(f.ops) - child.block.Add(f.ops) - f.ops.End().Add(f.ops) + }.Add(ops) + child.block.Add(ops) + ops.End().Add(ops) mainSize += axisMain(f.Axis, dims.Size) switch f.MainAxisAlignment { case SpaceEvenly: - mainSize += space / (1 + len(f.children)) + mainSize += space / (1 + len(children)) case SpaceAround: - mainSize += space / len(f.children) + mainSize += space / len(children) case SpaceBetween: - mainSize += space / (len(f.children) - 1) + mainSize += space / (len(children) - 1) } if b != dims.Size.Y { baseline = b @@ -185,9 +162,9 @@ func (f *Flex) Layout() Dimens { case Start: mainSize += space case SpaceEvenly: - mainSize += space / (1 + len(f.children)) + mainSize += space / (1 + len(children)) case SpaceAround: - mainSize += space / (len(f.children) * 2) + mainSize += space / (len(children) * 2) } sz := axisPoint(f.Axis, mainSize, crossSize) if baseline == 0 { diff --git a/ui/layout/layout.go b/ui/layout/layout.go index d866e826..ae6a5774 100644 --- a/ui/layout/layout.go +++ b/ui/layout/layout.go @@ -9,9 +9,7 @@ import ( "gioui.org/ui" ) -type Widget interface { - Layout(ops *ui.Ops, cs Constraints) Dimens -} +type Widget func(ops *ui.Ops, cs Constraints) Dimens type Constraints struct { Width Constraint @@ -29,8 +27,6 @@ type Dimens struct { type Axis uint8 -type F func(ops *ui.Ops, cs Constraints) Dimens - const ( Horizontal Axis = iota Vertical @@ -73,147 +69,113 @@ func ExactConstraints(size image.Point) Constraints { } } -func (f F) Layout(ops *ui.Ops, cs Constraints) Dimens { - return f(ops, cs) +type Insets struct { + W Widget + + Top, Right, Bottom, Left float32 } -type Margins struct { - Top, Right, Bottom, Left ui.Value +func (in Insets) Layout(ops *ui.Ops, cs Constraints) Dimens { + 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 { + mcs.Width.Min -= l + r + mcs.Width.Max -= l + r + if mcs.Width.Min < 0 { + mcs.Width.Min = 0 + } + if mcs.Width.Max < mcs.Width.Min { + mcs.Width.Max = mcs.Width.Min + } + } + if mcs.Height.Max != ui.Inf { + mcs.Height.Min -= t + b + mcs.Height.Max -= t + b + if mcs.Height.Min < 0 { + mcs.Height.Min = 0 + } + if mcs.Height.Max < mcs.Height.Min { + mcs.Height.Max = mcs.Height.Min + } + } + ops.Begin() + ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t}))}.Add(ops) + dims := in.W(ops, mcs) + ops.End().Add(ops) + return Dimens{ + Size: cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})), + Baseline: dims.Baseline + t, + } } -func Margin(c *ui.Config, m Margins, w Widget) Widget { - return F(func(ops *ui.Ops, cs Constraints) Dimens { - mcs := cs - t, r, b, l := int(c.Pixels(m.Top)+0.5), int(c.Pixels(m.Right)+0.5), int(c.Pixels(m.Bottom)+0.5), int(c.Pixels(m.Left)+0.5) - if mcs.Width.Max != ui.Inf { - mcs.Width.Min -= l + r - mcs.Width.Max -= l + r - if mcs.Width.Min < 0 { - mcs.Width.Min = 0 - } - if mcs.Width.Max < mcs.Width.Min { - mcs.Width.Max = mcs.Width.Min - } - } - if mcs.Height.Max != ui.Inf { - mcs.Height.Min -= t + b - mcs.Height.Max -= t + b - if mcs.Height.Min < 0 { - mcs.Height.Min = 0 - } - if mcs.Height.Max < mcs.Height.Min { - mcs.Height.Max = mcs.Height.Min - } - } - ops.Begin() - ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t}))}.Add(ops) - dims := w.Layout(ops, mcs) - ops.End().Add(ops) - return Dimens{ - Size: cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})), - Baseline: dims.Baseline + t, - } - }) -} - -func EqualMargins(v ui.Value) Margins { - return Margins{Top: v, Right: v, Bottom: v, Left: v} +func EqualInsets(v float32, w Widget) Insets { + return Insets{W: w, Top: v, Right: v, Bottom: v, Left: v} } func isInf(v ui.Value) bool { return math.IsInf(float64(v.V), 1) } -func Capped(c *ui.Config, maxWidth, maxHeight ui.Value, wt Widget) Widget { - return F(func(ops *ui.Ops, cs Constraints) Dimens { - if !isInf(maxWidth) { - mw := int(c.Pixels(maxWidth) + .5) - if mw < cs.Width.Min { - mw = cs.Width.Min - } - if mw < cs.Width.Max { - cs.Width.Max = mw - } - } - if !isInf(maxHeight) { - mh := int(c.Pixels(maxHeight) + 0.5) - if mh < cs.Height.Min { - mh = cs.Height.Min - } - if mh < cs.Height.Max { - cs.Height.Max = mh - } - } - return wt.Layout(ops, cs) - }) +type Sized struct { + W Widget + Width, Height float32 } -func Sized(c *ui.Config, width, height ui.Value, wt Widget) Widget { - return F(func(ops *ui.Ops, cs Constraints) Dimens { - if h := int(c.Pixels(height) + 0.5); h != 0 { - if cs.Height.Min < h { - cs.Height.Min = h - } - if h < cs.Height.Max { - cs.Height.Max = h - } +func (s Sized) Layout(ops *ui.Ops, cs Constraints) Dimens { + if h := int(s.Height + 0.5); h != 0 { + if cs.Height.Min < h { + cs.Height.Min = h } - if w := int(c.Pixels(width) + .5); w != 0 { - if cs.Width.Min < w { - cs.Width.Min = w - } - if w < cs.Width.Max { - cs.Width.Max = w - } + if h < cs.Height.Max { + cs.Height.Max = h } - return wt.Layout(ops, cs) - }) + } + if w := int(s.Width + .5); w != 0 { + if cs.Width.Min < w { + cs.Width.Min = w + } + if w < cs.Width.Max { + cs.Width.Max = w + } + } + return s.W(ops, cs) } -func Expand(w Widget) Widget { - return F(func(ops *ui.Ops, cs Constraints) Dimens { - if cs.Height.Max != ui.Inf { - cs.Height.Min = cs.Height.Max - } - if cs.Width.Max != ui.Inf { - cs.Width.Min = cs.Width.Max - } - return w.Layout(ops, cs) - }) +type Align struct { + W Widget + Alignment Direction } -func Align(alignment Direction, w Widget) Widget { - return F(func(ops *ui.Ops, cs Constraints) Dimens { - ops.Begin() - dims := w.Layout(ops, cs.Loose()) - block := ops.End() - sz := dims.Size - if cs.Width.Max != ui.Inf { - sz.X = cs.Width.Max - } - if cs.Height.Max != ui.Inf { - sz.Y = cs.Height.Max - } - var p image.Point - switch alignment { - case N, S, Center: - p.X = (sz.X - dims.Size.X) / 2 - case NE, SE, E: - p.X = sz.X - dims.Size.X - } - switch alignment { - case W, Center, E: - p.Y = (sz.Y - dims.Size.Y) / 2 - case SW, S, SE: - p.Y = sz.Y - dims.Size.Y - } - ops.Begin() - ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops) - block.Add(ops) - ops.End().Add(ops) - return Dimens{ - Size: sz, - Baseline: dims.Baseline, - } - }) +func (a Align) Layout(ops *ui.Ops, cs Constraints) Dimens { + ops.Begin() + dims := a.W(ops, cs.Loose()) + block := ops.End() + sz := dims.Size + if cs.Width.Max != ui.Inf { + sz.X = cs.Width.Max + } + if cs.Height.Max != ui.Inf { + sz.Y = cs.Height.Max + } + var p image.Point + switch a.Alignment { + case N, S, Center: + p.X = (sz.X - dims.Size.X) / 2 + case NE, SE, E: + p.X = sz.X - dims.Size.X + } + switch a.Alignment { + case W, Center, E: + p.Y = (sz.Y - dims.Size.Y) / 2 + case SW, S, SE: + p.Y = sz.Y - dims.Size.Y + } + ops.Begin() + ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops) + block.Add(ops) + ops.End().Add(ops) + return Dimens{ + Size: sz, + Baseline: dims.Baseline, + } } diff --git a/ui/layout/list.go b/ui/layout/list.go index 9010479c..1093ea7b 100644 --- a/ui/layout/list.go +++ b/ui/layout/list.go @@ -35,9 +35,9 @@ type List struct { cs Constraints len int - maxSize int - children []scrollChild - elem func(w Widget) + maxSize int + children []scrollChild + elemForward bool size image.Point } @@ -48,7 +48,6 @@ func (l *List) Init(ops *ui.Ops, cs Constraints, len int) (int, bool) { l.ops = ops l.cs = cs l.len = len - l.elem = nil if l.first > len { l.first = len } @@ -88,7 +87,7 @@ func (l *List) next() (int, bool) { mainc := axisMainConstraint(l.Axis, l.cs) if l.offset <= 0 { if l.first > 0 { - l.elem = l.backward + l.elemForward = false return l.first - 1, true } l.offset = 0 @@ -96,7 +95,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.elem = l.forward + l.elemForward = true return i, true } missing := mainc.Max - (l.maxSize - l.offset) @@ -109,30 +108,25 @@ func (l *List) next() (int, bool) { } func (l *List) Elem(w Widget) { - l.elem(w) -} - -func (l *List) backward(w Widget) { - l.first-- child := l.add(w) - mainSize := axisMain(l.Axis, child.size) - l.offset += mainSize - l.maxSize += mainSize - l.children = append([]scrollChild{child}, l.children...) -} - -func (l *List) forward(w Widget) { - child := l.add(w) - mainSize := axisMain(l.Axis, child.size) - l.maxSize += mainSize - l.children = append(l.children, child) + if l.elemForward { + mainSize := axisMain(l.Axis, child.size) + l.maxSize += mainSize + l.children = append(l.children, child) + } else { + l.first-- + mainSize := axisMain(l.Axis, child.size) + l.offset += mainSize + l.maxSize += mainSize + l.children = append([]scrollChild{child}, l.children...) + } } 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.Layout(l.ops, subcs) + dims := w(l.ops, subcs) block := l.ops.End() return scrollChild{dims.Size, block} } diff --git a/ui/layout/stack.go b/ui/layout/stack.go index 71a7dd3c..13775e4a 100644 --- a/ui/layout/stack.go +++ b/ui/layout/stack.go @@ -9,18 +9,14 @@ import ( ) type Stack struct { - Alignment Direction + Alignment Direction + Constraints Constraints - ops *ui.Ops - cs Constraints - children []stackChild maxSZ image.Point baseline int - - ccache [10]stackChild } -type stackChild struct { +type StackChild struct { block ui.OpBlock dims Dimens } @@ -38,23 +34,11 @@ const ( W ) -func (s *Stack) Init(ops *ui.Ops, cs Constraints) *Stack { - if s.children == nil { - s.children = s.ccache[:0] - } - s.children = s.children[:0] - s.maxSZ = image.Point{} - s.baseline = 0 - s.ops = ops - s.cs = cs - return s -} - -func (s *Stack) Rigid(w Widget) *Stack { - s.ops.Begin() - ui.OpLayer{}.Add(s.ops) - dims := w.Layout(s.ops, s.cs) - b := s.ops.End() +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 } @@ -62,27 +46,20 @@ func (s *Stack) Rigid(w Widget) *Stack { s.maxSZ.Y = h } s.addjustBaseline(dims) - s.children = append(s.children, stackChild{b, dims}) - return s + return StackChild{b, dims} } -func (s *Stack) Expand(idx int, w Widget) *Stack { +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}, } - s.ops.Begin() - ui.OpLayer{}.Add(s.ops) - dims := w.Layout(s.ops, cs) - b := s.ops.End() + ops.Begin() + ui.OpLayer{}.Add(ops) + dims := w(ops, cs) + b := ops.End() s.addjustBaseline(dims) - if idx < 0 { - idx += len(s.children) + 1 - } - s.children = append(s.children, stackChild{}) - copy(s.children[idx+1:], s.children[idx:]) - s.children[idx] = stackChild{b, dims} - return s + return StackChild{b, dims} } func (s *Stack) addjustBaseline(dims Dimens) { @@ -93,8 +70,8 @@ func (s *Stack) addjustBaseline(dims Dimens) { } } -func (s *Stack) Layout() Dimens { - for _, ch := range s.children { +func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens { + for _, ch := range children { sz := ch.dims.Size var p image.Point switch s.Alignment { @@ -109,10 +86,10 @@ func (s *Stack) Layout() Dimens { case SW, S, SE: p.Y = s.maxSZ.Y - sz.Y } - s.ops.Begin() - ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(s.ops) - ch.block.Add(s.ops) - s.ops.End().Add(s.ops) + ops.Begin() + ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops) + ch.block.Add(ops) + ops.End().Add(ops) } b := s.baseline if b == 0 {