Files
Voile/Voile/Source/UI/Containers/Container.cs

151 lines
3.6 KiB
C#

using System.Numerics;
using Voile.Rendering;
namespace Voile.UI.Containers;
/// <summary>
/// A base class for all UI containers, used to position and rendering child <see cref="IElement">s.
/// </summary>
public abstract class Container : UIElement, IParentableElement
{
/// <inheritdoc />
public IReadOnlyList<UIElement> Children => _children;
/// <summary>
/// Specifies if this <see cref="Container"/>'s minimum size will be confined to contents.
/// </summary>
public bool ConfineToContents { get; set; } = false;
public override Rect MinimumSize => _minimumSize;
public Container()
{
MarkDirty();
}
public Container(Rect minimumSize)
{
_minimumSize = minimumSize;
MarkDirty();
}
public Container(Rect minimumSize, List<UIElement> children)
{
_minimumSize = minimumSize;
_children = children;
MarkDirty();
}
protected override void OnUpdate()
{
foreach (var child in _children)
{
if (child is not IUpdatableElement updatable) continue;
updatable.Update();
if (child is IAnchorableElement anchorable)
{
anchorable.ApplyAnchor(GlobalPosition, Size);
}
}
Arrange();
if (ConfineToContents)
{
RecalculateSizes();
}
}
public override void MarkDirty()
{
base.MarkDirty();
foreach (var child in _children)
{
if (child is not IUpdatableElement updatable) continue;
updatable.MarkDirty();
}
}
/// <summary>
/// Called when this <see cref="Container"/> has to rearrange its children.
/// </summary>
public abstract void Arrange();
/// <summary>
/// Recalculates sizes for this <see cref="Container"/>.
/// This is done automatically if <see cref="ConfineToContents"/> is true.
/// </summary>
public void RecalculateSizes()
{
float minX = float.MaxValue;
float minY = float.MaxValue;
float maxX = float.MinValue;
float maxY = float.MinValue;
foreach (var child in Children)
{
var pos = child.GlobalPosition;
var size = child.Size;
minX = MathF.Min(minX, pos.X);
minY = MathF.Min(minY, pos.Y);
maxX = MathF.Max(maxX, pos.X + size.Width);
maxY = MathF.Max(maxY, pos.Y + size.Height);
}
float padding = 0f;
float occupiedWidth = (maxX - minX) + padding * 2;
float occupiedHeight = (maxY - minY) + padding * 2;
float finalWidth = MathF.Max(occupiedWidth, _minimumSize.Width);
float finalHeight = MathF.Max(occupiedHeight, _minimumSize.Height);
var finalSize = new Rect(finalWidth, finalHeight);
if (finalSize != Size)
{
Size = finalSize;
}
if (_minimumSize > Size)
{
Size = _minimumSize;
}
}
public void AddChild(UIElement child)
{
_children.Add(child);
child.SetParent(this);
MarkDirty();
Update();
}
public void RemoveChild(UIElement child)
{
_children.Remove(child);
MarkDirty();
Update();
}
public override void Render(RenderSystem renderer, Style style)
{
foreach (var child in Children)
{
if (child is not IRenderableElement renderable) continue;
renderable.Render(renderer, style);
}
}
private List<UIElement> _children = new();
private Rect _minimumSize = Rect.Zero;
}