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 <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-05-16 11:44:17 +02:00
parent 25ba53ea51
commit e436dce0e7
4 changed files with 164 additions and 254 deletions
+34 -57
View File
@@ -10,23 +10,19 @@ 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
ops *ui.Ops
cs Constraints
children []flexChild
taken int taken int
maxCross int maxCross int
maxBaseline int maxBaseline int
ccache [10]flexChild
} }
type flexChild struct { type FlexChild struct {
block ui.OpBlock block ui.OpBlock
dims Dimens dims Dimens
} }
@@ -60,29 +56,17 @@ const (
Stretch Stretch
) )
func (f *Flex) Init(ops *ui.Ops, cs Constraints) *Flex { func (f *Flex) Rigid(ops *ui.Ops, w Widget) FlexChild {
f.ops = ops mainc := axisMainConstraint(f.Axis, f.Constraints)
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)
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.cs)) cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.Constraints))
f.ops.Begin() ops.Begin()
ui.OpLayer{}.Add(f.ops) ui.OpLayer{}.Add(ops)
dims := w.Layout(f.ops, cs) dims := w(ops, cs)
block := f.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 {
f.maxCross = c f.maxCross = c
@@ -90,12 +74,11 @@ func (f *Flex) Rigid(w Widget) *Flex {
if b := dims.Baseline; b > f.maxBaseline { if b := dims.Baseline; b > f.maxBaseline {
f.maxBaseline = b f.maxBaseline = b
} }
f.children = append(f.children, flexChild{block, dims}) return FlexChild{block, dims}
return f
} }
func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex { func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode, w Widget) FlexChild {
mainc := axisMainConstraint(f.Axis, f.cs) mainc := axisMainConstraint(f.Axis, f.Constraints)
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
@@ -104,11 +87,11 @@ func (f *Flex) Flexible(idx int, 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.cs)) cs := axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.Constraints))
f.ops.Begin() ops.Begin()
ui.OpLayer{}.Add(f.ops) ui.OpLayer{}.Add(ops)
dims := w.Layout(f.ops, cs) dims := w(ops, cs)
block := f.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 {
f.maxCross = c 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 { if b := dims.Baseline; b > f.maxBaseline {
f.maxBaseline = b f.maxBaseline = b
} }
if idx < 0 { return FlexChild{block, dims}
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
} }
func (f *Flex) Layout() Dimens { func (f *Flex) Layout(ops *ui.Ops, children ...FlexChild) Dimens {
mainc := axisMainConstraint(f.Axis, f.cs) mainc := axisMainConstraint(f.Axis, f.Constraints)
crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross) crossSize := axisCrossConstraint(f.Axis, f.Constraints).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 {
@@ -144,11 +121,11 @@ func (f *Flex) Layout() Dimens {
case End: case End:
mainSize += space mainSize += space
case SpaceEvenly: case SpaceEvenly:
mainSize += space / (1 + len(f.children)) mainSize += space / (1 + len(children))
case SpaceAround: 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 dims := child.dims
b := dims.Baseline b := dims.Baseline
var cross int var cross int
@@ -162,20 +139,20 @@ func (f *Flex) Layout() Dimens {
cross = f.maxBaseline - b cross = f.maxBaseline - b
} }
} }
f.ops.Begin() ops.Begin()
ui.OpTransform{ ui.OpTransform{
Transform: ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))), Transform: ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))),
}.Add(f.ops) }.Add(ops)
child.block.Add(f.ops) child.block.Add(ops)
f.ops.End().Add(f.ops) ops.End().Add(ops)
mainSize += axisMain(f.Axis, dims.Size) mainSize += axisMain(f.Axis, dims.Size)
switch f.MainAxisAlignment { switch f.MainAxisAlignment {
case SpaceEvenly: case SpaceEvenly:
mainSize += space / (1 + len(f.children)) mainSize += space / (1 + len(children))
case SpaceAround: case SpaceAround:
mainSize += space / len(f.children) mainSize += space / len(children)
case SpaceBetween: case SpaceBetween:
mainSize += space / (len(f.children) - 1) mainSize += space / (len(children) - 1)
} }
if b != dims.Size.Y { if b != dims.Size.Y {
baseline = b baseline = b
@@ -185,9 +162,9 @@ func (f *Flex) Layout() Dimens {
case Start: case Start:
mainSize += space mainSize += space
case SpaceEvenly: case SpaceEvenly:
mainSize += space / (1 + len(f.children)) mainSize += space / (1 + len(children))
case SpaceAround: case SpaceAround:
mainSize += space / (len(f.children) * 2) mainSize += space / (len(children) * 2)
} }
sz := axisPoint(f.Axis, mainSize, crossSize) sz := axisPoint(f.Axis, mainSize, crossSize)
if baseline == 0 { if baseline == 0 {
+92 -130
View File
@@ -9,9 +9,7 @@ import (
"gioui.org/ui" "gioui.org/ui"
) )
type Widget interface { type Widget func(ops *ui.Ops, cs Constraints) Dimens
Layout(ops *ui.Ops, cs Constraints) Dimens
}
type Constraints struct { type Constraints struct {
Width Constraint Width Constraint
@@ -29,8 +27,6 @@ type Dimens struct {
type Axis uint8 type Axis uint8
type F func(ops *ui.Ops, cs Constraints) Dimens
const ( const (
Horizontal Axis = iota Horizontal Axis = iota
Vertical Vertical
@@ -73,147 +69,113 @@ func ExactConstraints(size image.Point) Constraints {
} }
} }
func (f F) Layout(ops *ui.Ops, cs Constraints) Dimens { type Insets struct {
return f(ops, cs) W Widget
Top, Right, Bottom, Left float32
} }
type Margins struct { func (in Insets) Layout(ops *ui.Ops, cs Constraints) Dimens {
Top, Right, Bottom, Left ui.Value 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 { func EqualInsets(v float32, w Widget) Insets {
return F(func(ops *ui.Ops, cs Constraints) Dimens { return Insets{W: w, Top: v, Right: v, Bottom: v, Left: v}
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 isInf(v ui.Value) bool { func isInf(v ui.Value) bool {
return math.IsInf(float64(v.V), 1) return math.IsInf(float64(v.V), 1)
} }
func Capped(c *ui.Config, maxWidth, maxHeight ui.Value, wt Widget) Widget { type Sized struct {
return F(func(ops *ui.Ops, cs Constraints) Dimens { W Widget
if !isInf(maxWidth) { Width, Height float32
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)
})
} }
func Sized(c *ui.Config, width, height ui.Value, wt Widget) Widget { func (s Sized) Layout(ops *ui.Ops, cs Constraints) Dimens {
return F(func(ops *ui.Ops, cs Constraints) Dimens { if h := int(s.Height + 0.5); h != 0 {
if h := int(c.Pixels(height) + 0.5); h != 0 { if cs.Height.Min < h {
if cs.Height.Min < h { cs.Height.Min = h
cs.Height.Min = h
}
if h < cs.Height.Max {
cs.Height.Max = h
}
} }
if w := int(c.Pixels(width) + .5); w != 0 { if h < cs.Height.Max {
if cs.Width.Min < w { cs.Height.Max = h
cs.Width.Min = w
}
if w < cs.Width.Max {
cs.Width.Max = w
}
} }
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 { type Align struct {
return F(func(ops *ui.Ops, cs Constraints) Dimens { W Widget
if cs.Height.Max != ui.Inf { Alignment Direction
cs.Height.Min = cs.Height.Max
}
if cs.Width.Max != ui.Inf {
cs.Width.Min = cs.Width.Max
}
return w.Layout(ops, cs)
})
} }
func Align(alignment Direction, w Widget) Widget { func (a Align) Layout(ops *ui.Ops, cs Constraints) Dimens {
return F(func(ops *ui.Ops, cs Constraints) Dimens { ops.Begin()
ops.Begin() dims := a.W(ops, cs.Loose())
dims := w.Layout(ops, cs.Loose()) block := ops.End()
block := ops.End() sz := dims.Size
sz := dims.Size if cs.Width.Max != ui.Inf {
if cs.Width.Max != ui.Inf { sz.X = cs.Width.Max
sz.X = cs.Width.Max }
} if cs.Height.Max != ui.Inf {
if cs.Height.Max != ui.Inf { sz.Y = cs.Height.Max
sz.Y = cs.Height.Max }
} var p image.Point
var p image.Point switch a.Alignment {
switch alignment { case N, S, Center:
case N, S, Center: p.X = (sz.X - dims.Size.X) / 2
p.X = (sz.X - dims.Size.X) / 2 case NE, SE, E:
case NE, SE, E: p.X = sz.X - dims.Size.X
p.X = sz.X - dims.Size.X }
} switch a.Alignment {
switch alignment { case W, Center, E:
case W, Center, E: p.Y = (sz.Y - dims.Size.Y) / 2
p.Y = (sz.Y - dims.Size.Y) / 2 case SW, S, SE:
case SW, S, SE: p.Y = sz.Y - dims.Size.Y
p.Y = sz.Y - dims.Size.Y }
} ops.Begin()
ops.Begin() ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops)
ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops) block.Add(ops)
block.Add(ops) ops.End().Add(ops)
ops.End().Add(ops) return Dimens{
return Dimens{ Size: sz,
Size: sz, Baseline: dims.Baseline,
Baseline: dims.Baseline, }
}
})
} }
+17 -23
View File
@@ -35,9 +35,9 @@ type List struct {
cs Constraints cs Constraints
len int len int
maxSize int maxSize int
children []scrollChild children []scrollChild
elem func(w Widget) elemForward bool
size image.Point size image.Point
} }
@@ -48,7 +48,6 @@ func (l *List) Init(ops *ui.Ops, cs Constraints, len int) (int, bool) {
l.ops = ops l.ops = ops
l.cs = cs l.cs = cs
l.len = len l.len = len
l.elem = nil
if l.first > len { if l.first > len {
l.first = len l.first = len
} }
@@ -88,7 +87,7 @@ 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.elem = l.backward l.elemForward = false
return l.first - 1, true return l.first - 1, true
} }
l.offset = 0 l.offset = 0
@@ -96,7 +95,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.elem = l.forward l.elemForward = true
return i, true return i, true
} }
missing := mainc.Max - (l.maxSize - l.offset) missing := mainc.Max - (l.maxSize - l.offset)
@@ -109,30 +108,25 @@ func (l *List) next() (int, bool) {
} }
func (l *List) Elem(w Widget) { func (l *List) Elem(w Widget) {
l.elem(w)
}
func (l *List) backward(w Widget) {
l.first--
child := l.add(w) child := l.add(w)
mainSize := axisMain(l.Axis, child.size) if l.elemForward {
l.offset += mainSize mainSize := axisMain(l.Axis, child.size)
l.maxSize += mainSize l.maxSize += mainSize
l.children = append([]scrollChild{child}, l.children...) l.children = append(l.children, child)
} } else {
l.first--
func (l *List) forward(w Widget) { mainSize := axisMain(l.Axis, child.size)
child := l.add(w) l.offset += mainSize
mainSize := axisMain(l.Axis, child.size) l.maxSize += mainSize
l.maxSize += mainSize l.children = append([]scrollChild{child}, l.children...)
l.children = append(l.children, child) }
} }
func (l *List) add(w Widget) scrollChild { func (l *List) add(w Widget) scrollChild {
subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs)) subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs))
l.ops.Begin() l.ops.Begin()
ui.OpLayer{}.Add(l.ops) ui.OpLayer{}.Add(l.ops)
dims := w.Layout(l.ops, subcs) dims := w(l.ops, subcs)
block := l.ops.End() block := l.ops.End()
return scrollChild{dims.Size, block} return scrollChild{dims.Size, block}
} }
+21 -44
View File
@@ -9,18 +9,14 @@ import (
) )
type Stack struct { type Stack struct {
Alignment Direction Alignment Direction
Constraints Constraints
ops *ui.Ops
cs Constraints
children []stackChild
maxSZ image.Point maxSZ image.Point
baseline int baseline int
ccache [10]stackChild
} }
type stackChild struct { type StackChild struct {
block ui.OpBlock block ui.OpBlock
dims Dimens dims Dimens
} }
@@ -38,23 +34,11 @@ const (
W W
) )
func (s *Stack) Init(ops *ui.Ops, cs Constraints) *Stack { func (s *Stack) Rigid(ops *ui.Ops, w Widget) StackChild {
if s.children == nil { ops.Begin()
s.children = s.ccache[:0] ui.OpLayer{}.Add(ops)
} dims := w(ops, s.Constraints)
s.children = s.children[:0] b := ops.End()
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()
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
} }
@@ -62,27 +46,20 @@ func (s *Stack) Rigid(w Widget) *Stack {
s.maxSZ.Y = h s.maxSZ.Y = h
} }
s.addjustBaseline(dims) s.addjustBaseline(dims)
s.children = append(s.children, stackChild{b, dims}) return StackChild{b, dims}
return s
} }
func (s *Stack) Expand(idx int, w Widget) *Stack { func (s *Stack) Expand(ops *ui.Ops, w Widget) StackChild {
cs := Constraints{ cs := Constraints{
Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X}, Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X},
Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y}, Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y},
} }
s.ops.Begin() ops.Begin()
ui.OpLayer{}.Add(s.ops) ui.OpLayer{}.Add(ops)
dims := w.Layout(s.ops, cs) dims := w(ops, cs)
b := s.ops.End() b := ops.End()
s.addjustBaseline(dims) s.addjustBaseline(dims)
if idx < 0 { return StackChild{b, dims}
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
} }
func (s *Stack) addjustBaseline(dims Dimens) { func (s *Stack) addjustBaseline(dims Dimens) {
@@ -93,8 +70,8 @@ func (s *Stack) addjustBaseline(dims Dimens) {
} }
} }
func (s *Stack) Layout() Dimens { func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens {
for _, ch := range s.children { for _, ch := range children {
sz := ch.dims.Size sz := ch.dims.Size
var p image.Point var p image.Point
switch s.Alignment { switch s.Alignment {
@@ -109,10 +86,10 @@ func (s *Stack) Layout() Dimens {
case SW, S, SE: case SW, S, SE:
p.Y = s.maxSZ.Y - sz.Y p.Y = s.maxSZ.Y - sz.Y
} }
s.ops.Begin() ops.Begin()
ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(s.ops) ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops)
ch.block.Add(s.ops) ch.block.Add(ops)
s.ops.End().Add(s.ops) ops.End().Add(ops)
} }
b := s.baseline b := s.baseline
if b == 0 { if b == 0 {