forked from joejulian/gio
0f05231c35
Signed-off-by: Elias Naur <mail@eliasnaur.com>
255 lines
5.2 KiB
Go
255 lines
5.2 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package layout
|
|
|
|
import (
|
|
"image"
|
|
|
|
"gioui.org/ui/f32"
|
|
"gioui.org/ui"
|
|
)
|
|
|
|
type Flex struct {
|
|
Axis Axis
|
|
MainAxisAlignment MainAxisAlignment
|
|
CrossAxisAlignment CrossAxisAlignment
|
|
MainAxisSize MainAxisSize
|
|
|
|
cs Constraints
|
|
|
|
children []flexChild
|
|
taken int
|
|
maxCross int
|
|
maxBaseline int
|
|
|
|
ccache [10]flexChild
|
|
opCache [10]ui.Op
|
|
}
|
|
|
|
type flexChild struct {
|
|
op ui.Op
|
|
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) Init(cs Constraints) *Flex {
|
|
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
|
|
if mainc.Max != ui.Inf {
|
|
mainMax -= f.taken
|
|
}
|
|
cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs))
|
|
op, dims := w.Layout(cs)
|
|
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
|
|
}
|
|
f.children = append(f.children, flexChild{op, dims})
|
|
return f
|
|
}
|
|
|
|
func (f *Flex) Flexible(idx int, flex float32, mode FlexMode, w Widget) *Flex {
|
|
mainc := axisMainConstraint(f.Axis, f.cs)
|
|
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.cs))
|
|
op, dims := w.Layout(cs)
|
|
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
|
|
}
|
|
f.children = append(f.children, flexChild{op, dims})
|
|
if idx < 0 {
|
|
idx += len(f.children)
|
|
}
|
|
f.children[idx], f.children[len(f.children)-1] = f.children[len(f.children)-1], f.children[idx]
|
|
return f
|
|
}
|
|
|
|
func (f *Flex) Layout() (ui.Op, Dimens) {
|
|
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 {
|
|
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(f.children))
|
|
case SpaceAround:
|
|
mainSize += space / (len(f.children) * 2)
|
|
}
|
|
var ops ui.Ops
|
|
if len(f.children) > len(f.opCache) {
|
|
ops = make([]ui.Op, len(f.children))
|
|
} else {
|
|
ops = f.opCache[:len(f.children)]
|
|
}
|
|
for i, child := range f.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
|
|
}
|
|
}
|
|
off := ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross)))
|
|
ops[i] = ui.OpLayer{Op: ui.OpTransform{Transform: off, Op: child.op}}
|
|
mainSize += axisMain(f.Axis, dims.Size)
|
|
switch f.MainAxisAlignment {
|
|
case SpaceEvenly:
|
|
mainSize += space / (1 + len(f.children))
|
|
case SpaceAround:
|
|
mainSize += space / len(f.children)
|
|
case SpaceBetween:
|
|
mainSize += space / (len(f.children) - 1)
|
|
}
|
|
if b != dims.Size.Y {
|
|
baseline = b
|
|
}
|
|
}
|
|
switch f.MainAxisAlignment {
|
|
case Start:
|
|
mainSize += space
|
|
case SpaceEvenly:
|
|
mainSize += space / (1 + len(f.children))
|
|
case SpaceAround:
|
|
mainSize += space / (len(f.children) * 2)
|
|
}
|
|
sz := axisPoint(f.Axis, mainSize, crossSize)
|
|
if baseline == 0 {
|
|
baseline = sz.Y
|
|
}
|
|
return ops, 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)}
|
|
}
|