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
};
}
}