151 lines
3.6 KiB
C#
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;
|
|
} |