using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Text; using Voile.Rendering; namespace Voile.UI; /// /// Base class for all UI elements. /// 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)}"; /// /// An element name for style. /// public virtual string? StyleElementName { get; } public string StyleVariant { get; set; } = string.Empty; /// /// List of style modifiers for this . /// public virtual string[]? StyleModifiers { get; } public ResourceRef StyleSheet => Parent?.StyleSheet ?? StyleSheetOverride; public ResourceRef StyleSheetOverride { get; set; } = ResourceRef.Empty(); /// /// Parent of this element. /// 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; } /// /// Sets a parent element for this . /// /// Element to parent this to. 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(); /// /// Renders a stylebox from a given style. /// /// /// 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); } /// /// Determines if this contains a point within its confines. /// /// A global position of the point. /// True if the point is inside the widget; otherwise, false. 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; } /// /// Applies this anchor. /// 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; }