Merge FlexContainer, WIP dynamic container resizing.

This commit is contained in:
2025-06-20 19:46:42 +02:00
parent 76dafe9996
commit bc95fff4a3
3 changed files with 164 additions and 4 deletions

View File

@@ -125,5 +125,13 @@ public class TestGame : Game
private ResourceRef<Sound> _sound;
private ResourceRef<Texture2d> _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
};
}

View File

@@ -15,7 +15,15 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen
/// <inheritdoc />
public Rect MinimumRect { get; set; } = Rect.Zero;
/// <inheritdoc />
public Rect Size { get; set; } = Rect.Zero;
public Rect Size
{
get => _size;
set
{
_size = value;
MarkDirty();
}
}
/// <inheritdoc />
public bool Visible { get; set; } = true;
/// <inheritdoc />
@@ -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<IElement> _children = new();
private bool _isDirty;
private Rect _size = Rect.Zero;
}

View File

@@ -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<IElement> 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<List<IElement>> lines = new();
List<IElement> 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
};
}
}