package ui import ( "opslag.de/schobers/geom" ) type Spacing struct { Proxy bounds geom.RectangleF32 Margin SideLengths Width *Length Height *Length } func BuildSpacing(content Control, fn func(*Spacing)) *Spacing { s := &Spacing{} s.Content = content if fn != nil { fn(s) } return s } func insetMargins(bounds geom.RectangleF32, margin SideLengths, width, height float32) geom.RectangleF32 { var zero = func(f float32) float32 { if geom.IsNaN32(f) { return 0 } return f } var nan = func(f float32) int { if geom.IsNaN32(f) { return 1 } return 0 } var partial = func(f, p float32) float32 { if geom.IsNaN32(f) { return p } return f } var inset = func(min, max, marginMin, marginMax, length float32) (float32, float32) { var available = max - min var margins = zero(marginMin) + zero(marginMax) if margins > available { return min + zero(marginMin)*available/margins, max - zero(marginMax)*available/margins } var fixed = margins + zero(length) if fixed > available { return min + zero(marginMin), max - zero(marginMax) } var nans = nan(marginMin) + nan(length) + nan(marginMax) var part float32 = 0 if nans > 0 { part = (available - fixed) / float32(nans) } return min + partial(marginMin, part), max - partial(marginMax, part) } bounds.Min.X, bounds.Max.X = inset(bounds.Min.X, bounds.Max.X, margin.Left.Value(), margin.Right.Value(), width) bounds.Min.Y, bounds.Max.Y = inset(bounds.Min.Y, bounds.Max.Y, margin.Top.Value(), margin.Bottom.Value(), height) return bounds } func (s *Spacing) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) { size := s.DesiredSize(ctx, bounds.Size()) content := insetMargins(bounds, s.Margin, s.Width.Zero(size.X), s.Height.Zero(size.Y)) s.bounds = bounds s.Proxy.Arrange(ctx, content, offset, parent) } func (s *Spacing) Bounds() geom.RectangleF32 { return s.bounds } func (s *Spacing) Center() { s.Margin.Left = Infinite() s.Margin.Top = Infinite() s.Margin.Right = Infinite() s.Margin.Bottom = Infinite() } func (s *Spacing) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 { var content = s.Proxy.DesiredSize(ctx, geom.PtF32(s.Width.Zero(size.X), s.Height.Zero(size.Y))) var w, h = s.Width.Zero(content.X), s.Height.Zero(content.Y) var margin = func(l *Length) float32 { var v = l.Value() if geom.IsNaN32(v) { return 0 } return v } return geom.PtF32( margin(s.Margin.Left)+w+margin(s.Margin.Right), margin(s.Margin.Top)+h+margin(s.Margin.Bottom)) } func (s *Spacing) SetSize(w, h float32) { s.Width = Fixed(w) s.Height = Fixed(h) } // FixedSize wraps the Control c to fill exactly the space specified. func FixedSize(c Control, w, h float32) *Spacing { return BuildSpacing(c, func(s *Spacing) { s.Width = Fixed(w) s.Height = Fixed(h) }) } // FixedHeight wraps the Control c to fill exactly the height specified. Width is taken from Control c. func FixedHeight(c Control, h float32) *Spacing { return BuildSpacing(c, func(s *Spacing) { s.Height = Fixed(h) }) } // FixedWidth wraps the Control c to fill exactly the width specified. Height is taken from Control c. func FixedWidth(c Control, w float32) Control { return BuildSpacing(c, func(s *Spacing) { s.Width = Fixed(w) }) } // Margin wraps the Control c to have equal margins on all sides. func Margin(c Control, m float32) *Spacing { return Margins(c, m, m, m, m) } // MarginAxes wraps the Control c to have same margins horizontally (left, right) and vertically (top, left). func MarginAxes(c Control, hor, ver float32) *Spacing { return Margins(c, hor, ver, hor, ver) } // Margins wraps the Control c to have different margins on all sides. func Margins(c Control, left, top, right, bottom float32) *Spacing { return BuildSpacing(c, func(s *Spacing) { s.Margin.Left = Fixed(left) s.Margin.Top = Fixed(top) s.Margin.Right = Fixed(right) s.Margin.Bottom = Fixed(bottom) }) } func stretch(c Control, w, h bool) *Spacing { return BuildSpacing(c, func(s *Spacing) { if w { s.Width = Infinite() } if h { s.Height = Infinite() } }) } // Stretch wraps the Control c to stretch in both directions. func Stretch(c Control) *Spacing { return stretch(c, true, true) } // StretchHeight wraps the Control c to stretch vertically. Width is taken from Control c. func StretchHeight(c Control) *Spacing { return stretch(c, false, true) } // StretchWidth wraps the Control c to stretch horizontally. Height is taken from Control c. func StretchWidth(c Control) *Spacing { return stretch(c, true, false) }