Unify Containers and Widgets by creating a base UIElement, add more anchor types, make anchor calculations an extension of Anchor.

This commit is contained in:
2025-06-22 23:28:30 +02:00
parent 95ae2de7ac
commit 61ac079f2b
9 changed files with 163 additions and 181 deletions

View File

@@ -1,5 +1,4 @@
using System.Numerics;
using System.Xml.Serialization;
using Voile.Input;
using Voile.Rendering;
@@ -8,13 +7,8 @@ namespace Voile.UI.Widgets;
/// <summary>
/// A base class for all UI widgets.
/// </summary>
public abstract class Widget : IElement, IRenderableElement, IInputElement, IResizeableElement, IUpdatableElement, IAnchorableElement
public abstract class Widget : UIElement, IInputElement
{
public bool Visible { get; set; } = true;
public bool IgnoreInput { get; set; }
public Vector2 Position { get; set; } = Vector2.Zero;
public Rect Size { get; set; } = new();
public Widget()
{
MarkDirty();
@@ -32,90 +26,18 @@ public abstract class Widget : IElement, IRenderableElement, IInputElement, IRes
MarkDirty();
}
/// </inheritdoc>
public abstract Rect MinimumRect { get; }
public bool Dirty => _isDirty;
public Anchor Anchor { get; set; }
public Vector2 AnchorOffset { get; set; }
public void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
{
switch (Anchor)
{
case Anchor.TopLeft:
Position = parentPosition + AnchorOffset;
break;
case Anchor.TopCenter:
var topCenterX = parentRect.Width / 2;
var topCenterY = parentPosition.Y;
Position = new Vector2(parentPosition.X + topCenterX, topCenterY) + AnchorOffset;
break;
case Anchor.TopRight:
Position = new Vector2(parentPosition.X + parentRect.Width, parentPosition.Y);
break;
case Anchor.Fill:
Position = parentPosition;
Size = parentRect;
break;
default:
throw new NotImplementedException("This anchor type is not implemented!");
}
}
/// <summary>
/// Called when its time to draw this widget.
/// </summary>
/// <param name="renderer"></param>
public abstract void Render(RenderSystem renderer, Style style);
public void Input(UIInputContext context)
{
if (context.Handled) return;
OnInput(context);
if (context.Handled || IgnoreInput) return;
if (ContainsPoint(context.MousePosition))
{
OnInput(context);
}
}
/// <summary>
/// Called when this widget receives input.
/// </summary>
/// <param name="action">An input action this widget received.</param>
protected abstract void OnInput(UIInputContext action);
public void Update()
{
if (!_isDirty) return;
_isDirty = false;
if (Size == Rect.Zero)
{
Size = MinimumRect;
}
}
public void MarkDirty() => _isDirty = true;
public void DrawSize(RenderSystem renderer)
{
renderer.SetTransform(Position, Vector2.Zero);
renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
}
/// <summary>
/// Determines if this <see cref="Widget"/> contains a point within its confines.
/// </summary>
/// <param name="pointPosition">A global position of the point.</param>
/// <returns>True if the point is inside the widget; otherwise, false.</returns>
public bool ContainsPoint(Vector2 pointPosition)
{
return pointPosition.X >= Position.X &&
pointPosition.Y >= Position.Y &&
pointPosition.X <= Position.X + Size.Width &&
pointPosition.Y <= Position.Y + Size.Height;
}
private bool _isDirty = true;
protected abstract void OnInput(UIInputContext context);
}