123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- // SPDX-License-Identifier: Unlicense OR MIT
- package layout
- import (
- "image"
- "gioui.org/op"
- )
- // Flex lays out child elements along an axis,
- // according to alignment and weights.
- type Flex struct {
- // Axis is the main axis, either Horizontal or Vertical.
- Axis Axis
- // Spacing controls the distribution of space left after
- // layout.
- Spacing Spacing
- // Alignment is the alignment in the cross axis.
- Alignment Alignment
- // WeightSum is the sum of weights used for the weighted
- // size of Flexed children. If WeightSum is zero, the sum
- // of all Flexed weights is used.
- WeightSum float32
- }
- // FlexChild is the descriptor for a Flex child.
- type FlexChild struct {
- flex bool
- weight float32
- widget Widget
- // Scratch space.
- call op.CallOp
- dims Dimensions
- }
- // Spacing determine the spacing mode for a Flex.
- type Spacing uint8
- const (
- // SpaceEnd leaves space at the end.
- SpaceEnd Spacing = iota
- // SpaceStart leaves space at the start.
- SpaceStart
- // SpaceSides shares space between the start and end.
- SpaceSides
- // SpaceAround distributes space evenly between children,
- // with half as much space at the start and end.
- SpaceAround
- // SpaceBetween distributes space evenly between children,
- // leaving no space at the start and end.
- SpaceBetween
- // SpaceEvenly distributes space evenly between children and
- // at the start and end.
- SpaceEvenly
- )
- // Rigid returns a Flex child with a maximal constraint of the
- // remaining space.
- func Rigid(widget Widget) FlexChild {
- return FlexChild{
- widget: widget,
- }
- }
- // Flexed returns a Flex child forced to take up weight fraction of the
- // space left over from Rigid children. The fraction is weight
- // divided by either the weight sum of all Flexed children or the Flex
- // WeightSum if non zero.
- func Flexed(weight float32, widget Widget) FlexChild {
- return FlexChild{
- flex: true,
- weight: weight,
- widget: widget,
- }
- }
- // Layout a list of children. The position of the children are
- // determined by the specified order, but Rigid children are laid out
- // before Flexed children.
- func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
- size := 0
- cs := gtx.Constraints
- mainMin, mainMax := f.Axis.mainConstraint(cs)
- crossMin, crossMax := f.Axis.crossConstraint(cs)
- remaining := mainMax
- var totalWeight float32
- cgtx := gtx
- // Lay out Rigid children.
- for i, child := range children {
- if child.flex {
- totalWeight += child.weight
- continue
- }
- macro := op.Record(gtx.Ops)
- cgtx.Constraints = f.Axis.constraints(0, remaining, crossMin, crossMax)
- dims := child.widget(cgtx)
- c := macro.Stop()
- sz := f.Axis.Convert(dims.Size).X
- size += sz
- remaining -= sz
- if remaining < 0 {
- remaining = 0
- }
- children[i].call = c
- children[i].dims = dims
- }
- if w := f.WeightSum; w != 0 {
- totalWeight = w
- }
- // fraction is the rounding error from a Flex weighting.
- var fraction float32
- flexTotal := remaining
- // Lay out Flexed children.
- for i, child := range children {
- if !child.flex {
- continue
- }
- var flexSize int
- if remaining > 0 && totalWeight > 0 {
- // Apply weight and add any leftover fraction from a
- // previous Flexed.
- childSize := float32(flexTotal) * child.weight / totalWeight
- flexSize = int(childSize + fraction + .5)
- fraction = childSize - float32(flexSize)
- if flexSize > remaining {
- flexSize = remaining
- }
- }
- macro := op.Record(gtx.Ops)
- cgtx.Constraints = f.Axis.constraints(flexSize, flexSize, crossMin, crossMax)
- dims := child.widget(cgtx)
- c := macro.Stop()
- sz := f.Axis.Convert(dims.Size).X
- size += sz
- remaining -= sz
- if remaining < 0 {
- remaining = 0
- }
- children[i].call = c
- children[i].dims = dims
- }
- maxCross := crossMin
- var maxBaseline int
- for _, child := range children {
- if c := f.Axis.Convert(child.dims.Size).Y; c > maxCross {
- maxCross = c
- }
- if b := child.dims.Size.Y - child.dims.Baseline; b > maxBaseline {
- maxBaseline = b
- }
- }
- var space int
- if mainMin > size {
- space = mainMin - size
- }
- var mainSize int
- switch f.Spacing {
- case SpaceSides:
- mainSize += space / 2
- case SpaceStart:
- mainSize += space
- case SpaceEvenly:
- mainSize += space / (1 + len(children))
- case SpaceAround:
- if len(children) > 0 {
- mainSize += space / (len(children) * 2)
- }
- }
- for i, child := range children {
- dims := child.dims
- b := dims.Size.Y - dims.Baseline
- var cross int
- switch f.Alignment {
- case End:
- cross = maxCross - f.Axis.Convert(dims.Size).Y
- case Middle:
- cross = (maxCross - f.Axis.Convert(dims.Size).Y) / 2
- case Baseline:
- if f.Axis == Horizontal {
- cross = maxBaseline - b
- }
- }
- pt := f.Axis.Convert(image.Pt(mainSize, cross))
- trans := op.Offset(pt).Push(gtx.Ops)
- child.call.Add(gtx.Ops)
- trans.Pop()
- mainSize += f.Axis.Convert(dims.Size).X
- if i < len(children)-1 {
- switch f.Spacing {
- case SpaceEvenly:
- mainSize += space / (1 + len(children))
- case SpaceAround:
- if len(children) > 0 {
- mainSize += space / len(children)
- }
- case SpaceBetween:
- if len(children) > 1 {
- mainSize += space / (len(children) - 1)
- }
- }
- }
- }
- switch f.Spacing {
- case SpaceSides:
- mainSize += space / 2
- case SpaceEnd:
- mainSize += space
- case SpaceEvenly:
- mainSize += space / (1 + len(children))
- case SpaceAround:
- if len(children) > 0 {
- mainSize += space / (len(children) * 2)
- }
- }
- sz := f.Axis.Convert(image.Pt(mainSize, maxCross))
- sz = cs.Constrain(sz)
- return Dimensions{Size: sz, Baseline: sz.Y - maxBaseline}
- }
- func (s Spacing) String() string {
- switch s {
- case SpaceEnd:
- return "SpaceEnd"
- case SpaceStart:
- return "SpaceStart"
- case SpaceSides:
- return "SpaceSides"
- case SpaceAround:
- return "SpaceAround"
- case SpaceBetween:
- return "SpaceAround"
- case SpaceEvenly:
- return "SpaceEvenly"
- default:
- panic("unreachable")
- }
- }
|