222 lines
6.4 KiB
C#
222 lines
6.4 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
using Voile.Rendering;
|
|
|
|
namespace Voile.UI;
|
|
|
|
/// <summary>
|
|
/// Base class for all UI elements.
|
|
/// </summary>
|
|
public abstract class UIElement : IElement, IRenderableElement, IResizeableElement, IUpdatableElement, IAnchorableElement
|
|
{
|
|
public bool Visible { get; set; } = true;
|
|
public bool IgnoreInput { get; set; } = false;
|
|
public Vector2 LocalPosition { get; set; } = Vector2.Zero;
|
|
public Vector2 GlobalPosition => _parent?.GlobalPosition + LocalPosition ?? LocalPosition;
|
|
|
|
public string StyleName => $"{StyleElementName ?? "UIElement"}{GetStyleVariantString()}{ConstructStyleModifiers(StyleModifiers)}";
|
|
|
|
/// <summary>
|
|
/// An element name for style.
|
|
/// </summary>
|
|
public virtual string? StyleElementName { get; }
|
|
|
|
public string StyleVariant { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// List of style modifiers for this <see cref="UIElement"/>.
|
|
/// </summary>
|
|
public virtual string[]? StyleModifiers { get; }
|
|
|
|
public ResourceRef<StyleSheet> StyleSheet => Parent?.StyleSheet ?? StyleSheetOverride;
|
|
public ResourceRef<StyleSheet> StyleSheetOverride { get; set; } = ResourceRef<StyleSheet>.Empty();
|
|
|
|
/// <summary>
|
|
/// Parent <see cref="UIElement"/> of this element.
|
|
/// </summary>
|
|
public UIElement? Parent => _parent;
|
|
|
|
public Rect Size
|
|
{
|
|
get => _size;
|
|
set
|
|
{
|
|
if (value.Width < MinimumSize.Width)
|
|
{
|
|
_size.Width = MinimumSize.Width;
|
|
}
|
|
|
|
if (value.Height < MinimumSize.Height)
|
|
{
|
|
_size.Height = MinimumSize.Height;
|
|
}
|
|
|
|
if (_size != value)
|
|
{
|
|
MarkDirty();
|
|
}
|
|
|
|
_size = value;
|
|
}
|
|
}
|
|
public Vector2 AnchorOffset { get; set; } = Vector2.Zero;
|
|
public Anchor Anchor { get; set; } = Anchor.TopLeft;
|
|
|
|
public abstract Rect MinimumSize { get; }
|
|
public bool Dirty => _dirty;
|
|
|
|
public bool TryGetStyle(StyleSheet styleSheet, [NotNullWhen(true)] out Style? style)
|
|
{
|
|
return styleSheet.TryGet(StyleName, out style);
|
|
}
|
|
|
|
public virtual void MarkDirty()
|
|
{
|
|
if (Parent != null && !Parent.Dirty)
|
|
{
|
|
Parent.MarkDirty();
|
|
}
|
|
|
|
_dirty = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a parent element for this <see cref="UIElement"/>.
|
|
/// </summary>
|
|
/// <param name="parent">Element to parent this <see cref="UIElement"/> to.</param>
|
|
public void SetParent(UIElement parent)
|
|
{
|
|
_parent = parent;
|
|
MarkDirty();
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
if (!_dirty) return;
|
|
_dirty = false;
|
|
|
|
if (Size == Rect.Zero)
|
|
Size = MinimumSize;
|
|
|
|
OnUpdate();
|
|
|
|
if (_parent is not null && _parent.Size != Rect.Zero)
|
|
{
|
|
ApplyAnchor(_parent.GlobalPosition, _parent.Size);
|
|
}
|
|
}
|
|
|
|
public abstract void Render(RenderSystem renderer, Style style);
|
|
protected abstract void OnUpdate();
|
|
|
|
/// <summary>
|
|
/// Renders a stylebox from a given style.
|
|
/// </summary>
|
|
/// <param name="renderer"></param>
|
|
/// <param name="style"></param>
|
|
protected void RenderStyleBox(RenderSystem renderer, Style style)
|
|
{
|
|
var backgroundColor = style.BackgroundColor ?? Color.Transparent;
|
|
var borderColor = style.BorderColor ?? Color.Transparent;
|
|
var borderSize = style.BorderSize;
|
|
|
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
|
|
|
renderer.DrawRectangle(new Vector2(Size.Width, Size.Height), backgroundColor);
|
|
|
|
if (borderSize.Left > 0)
|
|
{
|
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
|
renderer.DrawRectangle(
|
|
new Vector2(borderSize.Left, Size.Height),
|
|
borderColor
|
|
);
|
|
}
|
|
|
|
if (borderSize.Top > 0)
|
|
{
|
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
|
renderer.DrawRectangle(
|
|
new Vector2(Size.Width, borderSize.Top),
|
|
borderColor
|
|
);
|
|
}
|
|
|
|
if (borderSize.Right > 0)
|
|
{
|
|
var rightX = GlobalPosition.X + Size.Width - borderSize.Right;
|
|
renderer.SetTransform(new Vector2(rightX, GlobalPosition.Y), Vector2.Zero);
|
|
renderer.DrawRectangle(
|
|
new Vector2(borderSize.Right, Size.Height),
|
|
borderColor
|
|
);
|
|
}
|
|
|
|
if (borderSize.Bottom > 0)
|
|
{
|
|
var bottomY = GlobalPosition.Y + Size.Height - borderSize.Bottom;
|
|
renderer.SetTransform(new Vector2(GlobalPosition.X, bottomY), Vector2.Zero);
|
|
renderer.DrawRectangle(
|
|
new Vector2(Size.Width, borderSize.Bottom),
|
|
borderColor
|
|
);
|
|
}
|
|
}
|
|
|
|
public void DrawSize(RenderSystem renderer)
|
|
{
|
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
|
renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if this <see cref="UIElement"/> 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 point)
|
|
{
|
|
return point.X >= GlobalPosition.X && point.Y >= GlobalPosition.Y &&
|
|
point.X <= GlobalPosition.X + Size.Width &&
|
|
point.Y <= GlobalPosition.Y + Size.Height;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies this <see cref="UIElement"/> anchor.
|
|
/// </summary>
|
|
public virtual void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
|
|
{
|
|
LocalPosition = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
|
|
}
|
|
|
|
private string ConstructStyleModifiers(string[]? modifiers)
|
|
{
|
|
if (modifiers == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var sb = new StringBuilder();
|
|
foreach (var modifier in modifiers)
|
|
{
|
|
sb.Append($".{modifier}");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private string GetStyleVariantString()
|
|
{
|
|
if (string.IsNullOrEmpty(StyleVariant))
|
|
return string.Empty;
|
|
|
|
return $".{StyleVariant}";
|
|
}
|
|
|
|
private bool _dirty = true;
|
|
private Rect _size = Rect.Zero;
|
|
|
|
private UIElement? _parent;
|
|
}
|