using System.Numerics; using Voile.Rendering; namespace Voile.UI.Containers; public enum FlexDirection { Row, Column } public enum JustifyContent { /// /// Align children to the start of the line. /// Start, /// /// Align children to the center of the line. /// Center, /// /// Align children to the end of the line. /// End, /// /// Equal space between items (no space at edges if multiple). /// SpaceBetween } public enum AlignItems { /// /// Align to the top or left (cross-axis) /// Start, /// /// Center along the cross-axis. /// Center, /// /// Align to the bottom or right. /// End } /// /// A flexible layout container that arranges its child elements in rows or columns, similar to CSS flexbox. Supports wrapping, alignment, spacing, and space distribution. /// public class FlexContainer : Container { /// /// Layout direction to use with this . /// public FlexDirection Direction { get; set; } = FlexDirection.Row; /// /// Controls main-axis alignment of child elements. By default, children will be positioned at the start of a line. /// public JustifyContent Justify { get; set; } = JustifyContent.Start; /// /// Controls cross-axis alignment (start, center, end). /// public AlignItems Align { get; set; } = AlignItems.Start; /// /// If true, children wrap onto multiple lines (rows or columns). /// public bool Wrap { get; set; } = false; /// /// Space between child elements. /// public float Gap { get; set; } = 16f; /// /// (Reserved) If true, distributes extra space among children (not implemented) /// public bool DistributeRemainingSpace { get; set; } = false; public FlexContainer() : base() { } public FlexContainer(Rect minimumSize, List children) : base(minimumSize, children) { } public override void Arrange() { float containerMainSize = (Direction == FlexDirection.Row) ? Size.Width : Size.Height; float mainPos = 0.0f; float crossPos = 0.0f; List> lines = new(); List currentLine = new(); float lineMainSum = 0f; float maxCross = 0f; foreach (var child in Children) { Vector2 childSize = GetChildSize(child); float mainSize = GetMainSize(childSize); float crossSize = GetCrossSize(childSize); bool wrapLine = Wrap && currentLine.Count > 0 && (lineMainSum + mainSize + Gap > containerMainSize); if (wrapLine) { lines.Add(currentLine); currentLine = new(); lineMainSum = 0f; maxCross += crossSize + Gap; } currentLine.Add(child); lineMainSum += mainSize + (currentLine.Count > 1 ? Gap : 0); } if (currentLine.Count > 0) { lines.Add(currentLine); } float currentCross = crossPos; foreach (var line in lines) { float lineMainLength = line.Sum(child => GetMainSize(GetChildSize(child))) + Gap * (line.Count - 1); float justifyOffset = GetJustifyOffset(containerMainSize, lineMainLength, line.Count); float currentMain = mainPos + justifyOffset; float maxLineCross = line.Max(child => GetCrossSize(GetChildSize(child))); foreach (var child in line) { Vector2 childSize = GetChildSize(child); float alignedCross = currentCross + GetAlignOffset(maxLineCross, GetCrossSize(childSize)); Vector2 childPos = (Direction == FlexDirection.Row) ? new Vector2(currentMain, alignedCross) : new Vector2(alignedCross, currentMain); child.LocalPosition = childPos; currentMain += GetMainSize(childSize) + Gap; } currentCross += maxLineCross + Gap; } } private Vector2 GetChildSize(IElement child) { if (child is IResizeableElement resizeable) return new Vector2(resizeable.MinimumSize.Width, resizeable.MinimumSize.Height); return new Vector2(child.Size.Width, child.Size.Height); } private float GetMainSize(Vector2 size) { return Direction == FlexDirection.Row ? size.X : size.Y; } private float GetCrossSize(Vector2 size) { return Direction == FlexDirection.Row ? size.Y : size.X; } private float GetJustifyOffset(float containerSize, float totalMain, int itemCount) { return Justify switch { JustifyContent.Center => (containerSize - totalMain) / 2f, JustifyContent.End => containerSize - totalMain, JustifyContent.SpaceBetween => (itemCount > 1) ? 0 : (containerSize - totalMain) / 2f, _ => 0 }; } private float GetAlignOffset(float maxCross, float childCross) { return Align switch { AlignItems.Center => (maxCross - childCross) / 2f, AlignItems.End => maxCross - childCross, _ => 0f }; } }