// SPDX-License-Identifier: Unlicense OR MIT package layout import ( "image" "math" "gioui.org/ui" ) type Widget interface { Layout(cs Constraints) (ui.Op, Dimens) } type Constraints struct { Width Constraint Height Constraint } type Constraint struct { Min, Max int } type Dimens struct { Size image.Point Baseline int } type Axis uint8 type F func(cs Constraints) (ui.Op, Dimens) const ( Horizontal Axis = iota Vertical ) func (c Constraint) Constrain(v int) int { if v < c.Min { return c.Min } else if v > c.Max { return c.Max } return v } func (c Constraints) Constrain(p image.Point) image.Point { return image.Point{X: c.Width.Constrain(p.X), Y: c.Height.Constrain(p.Y)} } func (c Constraints) Expand() Constraints { return Constraints{Width: c.Width.Expand(), Height: c.Height.Expand()} } func (c Constraint) Expand() Constraint { return Constraint{Min: c.Max, Max: c.Max} } func (c Constraints) Loose() Constraints { return Constraints{Width: c.Width.Loose(), Height: c.Height.Loose()} } func (c Constraint) Loose() Constraint { return Constraint{Max: c.Max} } // ExactConstraints returns the constraints that exactly represents the // given dimensions. func ExactConstraints(size image.Point) Constraints { return Constraints{ Width: Constraint{Min: size.X, Max: size.X}, Height: Constraint{Min: size.Y, Max: size.Y}, } } func (f F) Layout(cs Constraints) (ui.Op, Dimens) { return f(cs) } type Margins struct { Top, Right, Bottom, Left ui.Value } func Margin(c *ui.Config, m Margins, w Widget) Widget { return F(func(cs Constraints) (ui.Op, Dimens) { 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 } } op, dims := w.Layout(mcs) op = ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t})), Op: op} return op, 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 { return math.IsInf(float64(v.V), 1) } func Capped(c *ui.Config, maxWidth, maxHeight ui.Value, wt Widget) Widget { return F(func(cs Constraints) (ui.Op, Dimens) { if !isInf(maxWidth) { 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(cs) }) } func Sized(c *ui.Config, width, height ui.Value, wt Widget) Widget { return F(func(cs Constraints) (ui.Op, Dimens) { if h := int(c.Pixels(height) + 0.5); h != 0 { if 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 cs.Width.Min < w { cs.Width.Min = w } if w < cs.Width.Max { cs.Width.Max = w } } return wt.Layout(cs) }) } func Expand(w Widget) Widget { return F(func(cs Constraints) (ui.Op, Dimens) { if cs.Height.Max != ui.Inf { cs.Height.Min = cs.Height.Max } if cs.Width.Max != ui.Inf { cs.Width.Min = cs.Width.Max } return w.Layout(cs) }) } func Align(alignment Direction, w Widget) Widget { return F(func(cs Constraints) (ui.Op, Dimens) { op, dims := w.Layout(cs.Loose()) sz := dims.Size if cs.Width.Max != ui.Inf { sz.X = cs.Width.Max } if cs.Height.Max != ui.Inf { sz.Y = cs.Height.Max } var p image.Point switch alignment { case N, S, Center: p.X = (sz.X - dims.Size.X) / 2 case NE, SE, E: p.X = sz.X - dims.Size.X } switch alignment { case W, Center, E: p.Y = (sz.Y - dims.Size.Y) / 2 case SW, S, SE: p.Y = sz.Y - dims.Size.Y } op = ui.OpTransform{Transform: ui.Offset(toPointF(p)), Op: op} return op, Dimens{ Size: sz, Baseline: dims.Baseline, } }) }