diff --git a/TestGame/TestGame.cs b/TestGame/TestGame.cs index 26a7828..1f91408 100644 --- a/TestGame/TestGame.cs +++ b/TestGame/TestGame.cs @@ -125,5 +125,13 @@ public class TestGame : Game private ResourceRef _sound; private ResourceRef _icon; - private GridContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new()); + private FlexContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new()) + { + Size = new Rect(500, 300), + Direction = FlexDirection.Row, + Justify = JustifyContent.Start, + Align = AlignItems.Center, + Wrap = true, + Gap = 10f + }; } \ No newline at end of file diff --git a/Voile/Source/UI/Containers/Container.cs b/Voile/Source/UI/Containers/Container.cs index 1288d55..b0d6eb6 100644 --- a/Voile/Source/UI/Containers/Container.cs +++ b/Voile/Source/UI/Containers/Container.cs @@ -15,7 +15,15 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen /// public Rect MinimumRect { get; set; } = Rect.Zero; /// - public Rect Size { get; set; } = Rect.Zero; + public Rect Size + { + get => _size; + set + { + _size = value; + MarkDirty(); + } + } /// public bool Visible { get; set; } = true; /// @@ -71,7 +79,7 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen { if (Children.Count == 0) { - Size = MinimumRect; + _size = MinimumRect; return; } @@ -99,7 +107,7 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen float finalWidth = MathF.Max(occupiedWidth, MinimumRect.Width); float finalHeight = MathF.Max(occupiedHeight, MinimumRect.Height); - Size = new Rect(finalWidth, finalHeight); + MinimumRect = new Rect(finalWidth, finalHeight); } public void AddChild(IElement child) @@ -135,4 +143,5 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen private List _children = new(); private bool _isDirty; + private Rect _size = Rect.Zero; } \ No newline at end of file diff --git a/Voile/Source/UI/Containers/FlexContainer.cs b/Voile/Source/UI/Containers/FlexContainer.cs new file mode 100644 index 0000000..f706a52 --- /dev/null +++ b/Voile/Source/UI/Containers/FlexContainer.cs @@ -0,0 +1,143 @@ +using System.Numerics; +using Voile.Rendering; + +namespace Voile.UI.Containers; + +public enum FlexDirection +{ + Row, + Column +} + +public enum JustifyContent +{ + Start, + Center, + End, + SpaceBetween +} + +public enum AlignItems +{ + Start, + Center, + End +} + +public class FlexContainer : Container +{ + public FlexDirection Direction { get; set; } = FlexDirection.Row; + public JustifyContent Justify { get; set; } = JustifyContent.Start; + public AlignItems Align { get; set; } = AlignItems.Start; + public bool Wrap { get; set; } = false; + public float Gap { get; set; } = 16f; + + 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 = (Direction == FlexDirection.Row) ? Position.X : Position.Y; + float crossPos = (Direction == FlexDirection.Row) ? Position.Y : Position.X; + + 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.Select(child => GetCrossSize(GetChildSize(child))).Max(); + + 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.Position = childPos; + + currentMain += GetMainSize(childSize) + Gap; + } + + currentCross += maxLineCross + Gap; + } + } + + private Vector2 GetChildSize(IElement child) + { + if (child is IResizeableElement resizeable) + return new Vector2(resizeable.MinimumRect.Width, resizeable.MinimumRect.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 + }; + } +} \ No newline at end of file