123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- // SPDX-License-Identifier: Unlicense OR MIT
- package layout
- import (
- "image"
- "gioui.org/f32"
- "gioui.org/op"
- "gioui.org/unit"
- )
- // Constraints represent the minimum and maximum size of a widget.
- //
- // A widget does not have to treat its constraints as "hard". For
- // example, if it's passed a constraint with a minimum size that's
- // smaller than its actual minimum size, it should return its minimum
- // size dimensions instead. Parent widgets should deal appropriately
- // with child widgets that return dimensions that do not fit their
- // constraints (for example, by clipping).
- type Constraints struct {
- Min, Max image.Point
- }
- // Dimensions are the resolved size and baseline for a widget.
- //
- // Baseline is the distance from the bottom of a widget to the baseline of
- // any text it contains (or 0). The purpose is to be able to align text
- // that span multiple widgets.
- type Dimensions struct {
- Size image.Point
- Baseline int
- }
- // Axis is the Horizontal or Vertical direction.
- type Axis uint8
- // Alignment is the mutual alignment of a list of widgets.
- type Alignment uint8
- // Direction is the alignment of widgets relative to a containing
- // space.
- type Direction uint8
- // Widget is a function scope for drawing, processing events and
- // computing dimensions for a user interface element.
- type Widget func(gtx Context) Dimensions
- const (
- Start Alignment = iota
- End
- Middle
- Baseline
- )
- const (
- NW Direction = iota
- N
- NE
- E
- SE
- S
- SW
- W
- Center
- )
- const (
- Horizontal Axis = iota
- Vertical
- )
- // Exact returns the Constraints with the minimum and maximum size
- // set to size.
- func Exact(size image.Point) Constraints {
- return Constraints{
- Min: size, Max: size,
- }
- }
- // FPt converts an point to a f32.Point.
- func FPt(p image.Point) f32.Point {
- return f32.Point{
- X: float32(p.X), Y: float32(p.Y),
- }
- }
- // Constrain a size so each dimension is in the range [min;max].
- func (c Constraints) Constrain(size image.Point) image.Point {
- if min := c.Min.X; size.X < min {
- size.X = min
- }
- if min := c.Min.Y; size.Y < min {
- size.Y = min
- }
- if max := c.Max.X; size.X > max {
- size.X = max
- }
- if max := c.Max.Y; size.Y > max {
- size.Y = max
- }
- return size
- }
- // AddMin returns a copy of Constraints with the Min constraint enlarged by up to delta
- // while still fitting within the Max constraint. The Max is unchanged, and the Min constraint
- // will not go negative.
- func (c Constraints) AddMin(delta image.Point) Constraints {
- c.Min = c.Min.Add(delta)
- if c.Min.X < 0 {
- c.Min.X = 0
- }
- if c.Min.Y < 0 {
- c.Min.Y = 0
- }
- c.Min = c.Constrain(c.Min)
- return c
- }
- // SubMax returns a copy of Constraints with the Max constraint shrunk by up to delta
- // while not going negative. The values of delta are expected to be positive.
- // The Min constraint is adjusted to fit within the new Max constraint.
- func (c Constraints) SubMax(delta image.Point) Constraints {
- c.Max = c.Max.Sub(delta)
- if c.Max.X < 0 {
- c.Max.X = 0
- }
- if c.Max.Y < 0 {
- c.Max.Y = 0
- }
- c.Min = c.Constrain(c.Min)
- return c
- }
- // Inset adds space around a widget by decreasing its maximum
- // constraints. The minimum constraints will be adjusted to ensure
- // they do not exceed the maximum.
- type Inset struct {
- Top, Bottom, Left, Right unit.Dp
- }
- // Layout a widget.
- func (in Inset) Layout(gtx Context, w Widget) Dimensions {
- top := gtx.Dp(in.Top)
- right := gtx.Dp(in.Right)
- bottom := gtx.Dp(in.Bottom)
- left := gtx.Dp(in.Left)
- mcs := gtx.Constraints
- mcs.Max.X -= left + right
- if mcs.Max.X < 0 {
- left = 0
- right = 0
- mcs.Max.X = 0
- }
- if mcs.Min.X > mcs.Max.X {
- mcs.Min.X = mcs.Max.X
- }
- mcs.Max.Y -= top + bottom
- if mcs.Max.Y < 0 {
- bottom = 0
- top = 0
- mcs.Max.Y = 0
- }
- if mcs.Min.Y > mcs.Max.Y {
- mcs.Min.Y = mcs.Max.Y
- }
- gtx.Constraints = mcs
- trans := op.Offset(image.Pt(left, top)).Push(gtx.Ops)
- dims := w(gtx)
- trans.Pop()
- return Dimensions{
- Size: dims.Size.Add(image.Point{X: right + left, Y: top + bottom}),
- Baseline: dims.Baseline + bottom,
- }
- }
- // UniformInset returns an Inset with a single inset applied to all
- // edges.
- func UniformInset(v unit.Dp) Inset {
- return Inset{Top: v, Right: v, Bottom: v, Left: v}
- }
- // Layout a widget according to the direction.
- // The widget is called with the context constraints minimum cleared.
- func (d Direction) Layout(gtx Context, w Widget) Dimensions {
- macro := op.Record(gtx.Ops)
- csn := gtx.Constraints.Min
- switch d {
- case N, S:
- gtx.Constraints.Min.Y = 0
- case E, W:
- gtx.Constraints.Min.X = 0
- default:
- gtx.Constraints.Min = image.Point{}
- }
- dims := w(gtx)
- call := macro.Stop()
- sz := dims.Size
- if sz.X < csn.X {
- sz.X = csn.X
- }
- if sz.Y < csn.Y {
- sz.Y = csn.Y
- }
- p := d.Position(dims.Size, sz)
- defer op.Offset(p).Push(gtx.Ops).Pop()
- call.Add(gtx.Ops)
- return Dimensions{
- Size: sz,
- Baseline: dims.Baseline + sz.Y - dims.Size.Y - p.Y,
- }
- }
- // Position calculates widget position according to the direction.
- func (d Direction) Position(widget, bounds image.Point) image.Point {
- var p image.Point
- switch d {
- case N, S, Center:
- p.X = (bounds.X - widget.X) / 2
- case NE, SE, E:
- p.X = bounds.X - widget.X
- }
- switch d {
- case W, Center, E:
- p.Y = (bounds.Y - widget.Y) / 2
- case SW, S, SE:
- p.Y = bounds.Y - widget.Y
- }
- return p
- }
- // Spacer adds space between widgets.
- type Spacer struct {
- Width, Height unit.Dp
- }
- func (s Spacer) Layout(gtx Context) Dimensions {
- return Dimensions{
- Size: gtx.Constraints.Constrain(image.Point{
- X: gtx.Dp(s.Width),
- Y: gtx.Dp(s.Height),
- }),
- }
- }
- func (a Alignment) String() string {
- switch a {
- case Start:
- return "Start"
- case End:
- return "End"
- case Middle:
- return "Middle"
- case Baseline:
- return "Baseline"
- default:
- panic("unreachable")
- }
- }
- // Convert a point in (x, y) coordinates to (main, cross) coordinates,
- // or vice versa. Specifically, Convert((x, y)) returns (x, y) unchanged
- // for the horizontal axis, or (y, x) for the vertical axis.
- func (a Axis) Convert(pt image.Point) image.Point {
- if a == Horizontal {
- return pt
- }
- return image.Pt(pt.Y, pt.X)
- }
- // FConvert a point in (x, y) coordinates to (main, cross) coordinates,
- // or vice versa. Specifically, FConvert((x, y)) returns (x, y) unchanged
- // for the horizontal axis, or (y, x) for the vertical axis.
- func (a Axis) FConvert(pt f32.Point) f32.Point {
- if a == Horizontal {
- return pt
- }
- return f32.Pt(pt.Y, pt.X)
- }
- // mainConstraint returns the min and max main constraints for axis a.
- func (a Axis) mainConstraint(cs Constraints) (int, int) {
- if a == Horizontal {
- return cs.Min.X, cs.Max.X
- }
- return cs.Min.Y, cs.Max.Y
- }
- // crossConstraint returns the min and max cross constraints for axis a.
- func (a Axis) crossConstraint(cs Constraints) (int, int) {
- if a == Horizontal {
- return cs.Min.Y, cs.Max.Y
- }
- return cs.Min.X, cs.Max.X
- }
- // constraints returns the constraints for axis a.
- func (a Axis) constraints(mainMin, mainMax, crossMin, crossMax int) Constraints {
- if a == Horizontal {
- return Constraints{Min: image.Pt(mainMin, crossMin), Max: image.Pt(mainMax, crossMax)}
- }
- return Constraints{Min: image.Pt(crossMin, mainMin), Max: image.Pt(crossMax, mainMax)}
- }
- func (a Axis) String() string {
- switch a {
- case Horizontal:
- return "Horizontal"
- case Vertical:
- return "Vertical"
- default:
- panic("unreachable")
- }
- }
- func (d Direction) String() string {
- switch d {
- case NW:
- return "NW"
- case N:
- return "N"
- case NE:
- return "NE"
- case E:
- return "E"
- case SE:
- return "SE"
- case S:
- return "S"
- case SW:
- return "SW"
- case W:
- return "W"
- case Center:
- return "Center"
- default:
- panic("unreachable")
- }
- }
|