mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
e436dce0e7
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>
238 lines
4.9 KiB
Go
238 lines
4.9 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package layout
|
|
|
|
import (
|
|
"image"
|
|
|
|
"gioui.org/ui"
|
|
"gioui.org/ui/f32"
|
|
)
|
|
|
|
type Flex struct {
|
|
Constraints Constraints
|
|
|
|
Axis Axis
|
|
MainAxisAlignment MainAxisAlignment
|
|
CrossAxisAlignment CrossAxisAlignment
|
|
MainAxisSize MainAxisSize
|
|
|
|
taken int
|
|
maxCross int
|
|
maxBaseline int
|
|
}
|
|
|
|
type FlexChild struct {
|
|
block ui.OpBlock
|
|
dims Dimens
|
|
}
|
|
|
|
type MainAxisSize uint8
|
|
|
|
type FlexMode uint8
|
|
type MainAxisAlignment uint8
|
|
type CrossAxisAlignment uint8
|
|
|
|
const (
|
|
Loose FlexMode = iota
|
|
Fit
|
|
)
|
|
|
|
const (
|
|
Max MainAxisSize = iota
|
|
Min
|
|
)
|
|
|
|
const (
|
|
Start = 100 + iota
|
|
End
|
|
Center
|
|
|
|
SpaceAround MainAxisAlignment = iota
|
|
SpaceBetween
|
|
SpaceEvenly
|
|
|
|
Baseline CrossAxisAlignment = iota
|
|
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
|
|
}
|
|
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}
|
|
}
|
|
|
|
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
|
|
}
|
|
submainc := Constraint{Max: int(float32(flexSize) * 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)
|
|
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) 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 {
|
|
space = mainc.Max - f.taken
|
|
}
|
|
} else if mainc.Min > f.taken {
|
|
space = mainc.Min - f.taken
|
|
}
|
|
var mainSize int
|
|
var baseline int
|
|
switch f.MainAxisAlignment {
|
|
case Center:
|
|
mainSize += space / 2
|
|
case End:
|
|
mainSize += space
|
|
case SpaceEvenly:
|
|
mainSize += space / (1 + len(children))
|
|
case SpaceAround:
|
|
mainSize += space / (len(children) * 2)
|
|
}
|
|
for _, child := range children {
|
|
dims := child.dims
|
|
b := dims.Baseline
|
|
var cross int
|
|
switch f.CrossAxisAlignment {
|
|
case End:
|
|
cross = crossSize - axisCross(f.Axis, dims.Size)
|
|
case Center:
|
|
cross = (crossSize - axisCross(f.Axis, dims.Size)) / 2
|
|
case Baseline:
|
|
if f.Axis == Horizontal {
|
|
cross = f.maxBaseline - b
|
|
}
|
|
}
|
|
ops.Begin()
|
|
ui.OpTransform{
|
|
Transform: ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))),
|
|
}.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(children))
|
|
case SpaceAround:
|
|
mainSize += space / len(children)
|
|
case SpaceBetween:
|
|
mainSize += space / (len(children) - 1)
|
|
}
|
|
if b != dims.Size.Y {
|
|
baseline = b
|
|
}
|
|
}
|
|
switch f.MainAxisAlignment {
|
|
case Start:
|
|
mainSize += space
|
|
case SpaceEvenly:
|
|
mainSize += space / (1 + len(children))
|
|
case SpaceAround:
|
|
mainSize += space / (len(children) * 2)
|
|
}
|
|
sz := axisPoint(f.Axis, mainSize, crossSize)
|
|
if baseline == 0 {
|
|
baseline = sz.Y
|
|
}
|
|
return Dimens{Size: sz, Baseline: baseline}
|
|
}
|
|
|
|
func axisPoint(a Axis, main, cross int) image.Point {
|
|
if a == Horizontal {
|
|
return image.Point{main, cross}
|
|
} else {
|
|
return image.Point{cross, main}
|
|
}
|
|
}
|
|
|
|
func axisMain(a Axis, sz image.Point) int {
|
|
if a == Horizontal {
|
|
return sz.X
|
|
} else {
|
|
return sz.Y
|
|
}
|
|
}
|
|
|
|
func axisCross(a Axis, sz image.Point) int {
|
|
if a == Horizontal {
|
|
return sz.Y
|
|
} else {
|
|
return sz.X
|
|
}
|
|
}
|
|
|
|
func axisMainConstraint(a Axis, cs Constraints) Constraint {
|
|
if a == Horizontal {
|
|
return cs.Width
|
|
} else {
|
|
return cs.Height
|
|
}
|
|
}
|
|
|
|
func axisCrossConstraint(a Axis, cs Constraints) Constraint {
|
|
if a == Horizontal {
|
|
return cs.Height
|
|
} else {
|
|
return cs.Width
|
|
}
|
|
}
|
|
|
|
func (f *Flex) crossConstraintChild(cs Constraints) Constraint {
|
|
c := axisCrossConstraint(f.Axis, cs)
|
|
switch f.CrossAxisAlignment {
|
|
case Stretch:
|
|
c.Min = c.Max
|
|
default:
|
|
c.Min = 0
|
|
}
|
|
return c
|
|
}
|
|
|
|
func axisConstraints(a Axis, mainc, crossc Constraint) Constraints {
|
|
if a == Horizontal {
|
|
return Constraints{mainc, crossc}
|
|
} else {
|
|
return Constraints{crossc, mainc}
|
|
}
|
|
}
|
|
|
|
func toPointF(p image.Point) f32.Point {
|
|
return f32.Point{X: float32(p.X), Y: float32(p.Y)}
|
|
}
|