diff --git a/Voile/Source/UI/Anchor.cs b/Voile/Source/UI/Anchor.cs
index 36ec04b..b601caf 100644
--- a/Voile/Source/UI/Anchor.cs
+++ b/Voile/Source/UI/Anchor.cs
@@ -1,3 +1,5 @@
+using System.Numerics;
+
namespace Voile.UI;
public enum Anchor
@@ -5,11 +7,35 @@ public enum Anchor
TopLeft,
TopCenter,
TopRight,
- Left,
+ CenterLeft,
Center,
- Right,
+ CenterRight,
BottomLeft,
BottomCenter,
BottomRight,
Fill
+}
+
+
+public static class AnchorExtensions
+{
+ public static Vector2 Calculate(this Anchor anchor, Vector2 parentPosition, Rect parentRect, Rect elementRect)
+ {
+ var size = new Vector2(elementRect.Width, elementRect.Height);
+ var parentSize = new Vector2(parentRect.Width, parentRect.Height);
+
+ return anchor switch
+ {
+ Anchor.TopLeft => Vector2.Zero,
+ Anchor.TopCenter => new Vector2((parentSize.X - size.X) / 2, 0),
+ Anchor.TopRight => new Vector2(parentSize.X - size.X, 0),
+ Anchor.CenterLeft => new Vector2(0, (parentSize.Y - size.Y) / 2),
+ Anchor.Center => (parentSize - size) / 2,
+ Anchor.CenterRight => new Vector2(parentSize.X - size.X, (parentSize.Y - size.Y) / 2),
+ Anchor.BottomLeft => new Vector2(0, parentSize.Y - size.Y),
+ Anchor.BottomCenter => new Vector2((parentSize.X - size.X) / 2, parentSize.Y - size.Y),
+ Anchor.BottomRight => parentSize - size,
+ _ => Vector2.Zero
+ };
+ }
}
\ No newline at end of file
diff --git a/Voile/Source/UI/Containers/Container.cs b/Voile/Source/UI/Containers/Container.cs
index 1b84924..8296057 100644
--- a/Voile/Source/UI/Containers/Container.cs
+++ b/Voile/Source/UI/Containers/Container.cs
@@ -7,46 +7,17 @@ namespace Voile.UI.Containers;
///
/// A base class for all UI containers, used to position and rendering child s.
///
-public abstract class Container : IElement, IParentableElement, IUpdatableElement, IResizeableElement, IRenderableElement, IAnchorableElement
+public abstract class Container : UIElement, IParentableElement
{
///
public IReadOnlyList Children => _children;
- ///
- public Vector2 Position { get; set; }
- ///
- public Rect MinimumRect { get; set; } = Rect.Zero;
- ///
- public Rect Size
- {
- get => _size;
- set
- {
- _size = value;
-
- if (value.Width < MinimumRect.Width)
- {
- _size.Width = MinimumRect.Width;
- }
-
- if (value.Height < MinimumRect.Height)
- {
- _size.Height = MinimumRect.Height;
- }
-
- MarkDirty();
- }
- }
- ///
- public bool Visible { get; set; } = true;
- ///
- public bool Dirty => _isDirty;
///
/// Specifies if this 's minimum size will be confined to contents.
///
public bool ConfineToContents { get; set; } = false;
- public Anchor Anchor { get; set; }
- public Vector2 AnchorOffset { get; set; }
+
+ public override Rect MinimumSize => _minimumSize;
public Container()
{
@@ -55,28 +26,26 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen
public Container(Rect minimumSize)
{
- MinimumRect = minimumSize;
+ _minimumSize = minimumSize;
MarkDirty();
}
public Container(Rect minimumSize, List children)
{
- MinimumRect = minimumSize;
+ _minimumSize = minimumSize;
_children = children;
MarkDirty();
}
- public void Update()
+ protected override void OnUpdate()
{
- if (!_isDirty) return;
- _isDirty = false;
-
-
foreach (var child in _children)
{
if (child is not IUpdatableElement updatable) continue;
+
+ updatable.MarkDirty();
updatable.Update();
if (child is IAnchorableElement anchorable)
@@ -93,8 +62,6 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen
}
}
- public void MarkDirty() => _isDirty = true;
-
///
/// Called when this has to rearrange its children.
///
@@ -127,14 +94,14 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen
float occupiedWidth = (maxX - minX) + padding * 2;
float occupiedHeight = (maxY - minY) + padding * 2;
- float finalWidth = MathF.Max(occupiedWidth, MinimumRect.Width);
- float finalHeight = MathF.Max(occupiedHeight, MinimumRect.Height);
+ float finalWidth = MathF.Max(occupiedWidth, _minimumSize.Width);
+ float finalHeight = MathF.Max(occupiedHeight, _minimumSize.Height);
- MinimumRect = new Rect(finalWidth, finalHeight);
+ Size = new Rect(finalWidth, finalHeight);
- if (MinimumRect > _size)
+ if (_minimumSize > Size)
{
- _size = MinimumRect;
+ Size = _minimumSize;
}
}
@@ -154,7 +121,7 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen
Update();
}
- public void Render(RenderSystem renderer, Style style)
+ public override void Render(RenderSystem renderer, Style style)
{
foreach (var child in Children)
{
@@ -163,45 +130,6 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen
}
}
- public void DrawSize(RenderSystem renderer)
- {
- renderer.SetTransform(Position, Vector2.Zero);
- renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
- }
-
- public void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
- {
- var absoluteOffset = AnchorOffset * new Vector2(Size.Width, Size.Height);
-
- 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) - absoluteOffset;
- break;
- case Anchor.TopRight:
- Position = new Vector2(parentPosition.X + parentRect.Width, parentPosition.Y) - absoluteOffset;
- break;
-
- case Anchor.Fill:
- Position = parentPosition;
- Size = parentRect;
- break;
-
- default:
- throw new NotImplementedException("This anchor type is not implemented!");
- }
-
- MarkDirty();
- Update();
- }
-
private List _children = new();
- private bool _isDirty;
- private Rect _size = Rect.Zero;
+ private Rect _minimumSize = Rect.Zero;
}
\ No newline at end of file
diff --git a/Voile/Source/UI/Containers/FlexContainer.cs b/Voile/Source/UI/Containers/FlexContainer.cs
index d252865..d91a758 100644
--- a/Voile/Source/UI/Containers/FlexContainer.cs
+++ b/Voile/Source/UI/Containers/FlexContainer.cs
@@ -148,7 +148,7 @@ public class FlexContainer : Container
private Vector2 GetChildSize(IElement child)
{
if (child is IResizeableElement resizeable)
- return new Vector2(resizeable.MinimumRect.Width, resizeable.MinimumRect.Height);
+ return new Vector2(resizeable.MinimumSize.Width, resizeable.MinimumSize.Height);
return new Vector2(child.Size.Width, child.Size.Height);
}
diff --git a/Voile/Source/UI/IElement.cs b/Voile/Source/UI/IElement.cs
index 4cdded6..88884f8 100644
--- a/Voile/Source/UI/IElement.cs
+++ b/Voile/Source/UI/IElement.cs
@@ -39,7 +39,7 @@ public interface IResizeableElement
///
/// Get a minimum rectangle size for this element.
///
- public abstract Rect MinimumRect { get; }
+ public abstract Rect MinimumSize { get; }
}
public interface IUpdatableElement
diff --git a/Voile/Source/UI/UIElement.cs b/Voile/Source/UI/UIElement.cs
new file mode 100644
index 0000000..a8cc374
--- /dev/null
+++ b/Voile/Source/UI/UIElement.cs
@@ -0,0 +1,91 @@
+using System.Numerics;
+using Voile.Rendering;
+
+namespace Voile.UI;
+
+public abstract class UIElement : IElement, IRenderableElement, IResizeableElement, IUpdatableElement, IAnchorableElement
+{
+ public bool Visible { get; set; } = true;
+ public bool IgnoreInput { get; set; } = false;
+ public Vector2 Position { get; set; } = Vector2.Zero;
+ public Rect Size
+ {
+ get => _size;
+ set
+ {
+ _size = value;
+
+ if (value.Width < MinimumSize.Width)
+ {
+ _size.Width = MinimumSize.Width;
+ }
+
+ if (value.Height < MinimumSize.Height)
+ {
+ _size.Height = MinimumSize.Height;
+ }
+
+ MarkDirty();
+ }
+ }
+ public Vector2 AnchorOffset { get; set; } = Vector2.Zero;
+ public Anchor Anchor { get; set; } = Anchor.TopLeft;
+
+ public abstract Rect MinimumSize { get; }
+ public bool Dirty => _dirty;
+
+ public virtual void MarkDirty() => _dirty = true;
+
+ public void Update()
+ {
+ if (!_dirty) return;
+ _dirty = false;
+
+ if (Size == Rect.Zero)
+ Size = MinimumSize;
+
+ OnUpdate();
+
+ if (_parentRect != Rect.Zero)
+ {
+ ApplyAnchor(_parentPosition, _parentRect);
+ }
+ }
+
+ public abstract void Render(RenderSystem renderer, Style style);
+ protected abstract void OnUpdate();
+
+ public void DrawSize(RenderSystem renderer)
+ {
+ renderer.SetTransform(Position, 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 >= Position.X && point.Y >= Position.Y &&
+ point.X <= Position.X + Size.Width &&
+ point.Y <= Position.Y + Size.Height;
+ }
+
+ ///
+ /// Applies this anchor.
+ ///
+ public virtual void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
+ {
+ _parentPosition = parentPosition;
+ _parentRect = parentRect;
+ Position = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
+ }
+
+ private bool _dirty = true;
+ private Rect _size = Rect.Zero;
+
+ private Vector2 _parentPosition = Vector2.Zero;
+ private Rect _parentRect = Rect.Zero;
+}
diff --git a/Voile/Source/UI/Widgets/Button.cs b/Voile/Source/UI/Widgets/Button.cs
index 8c69090..d5c4ba3 100644
--- a/Voile/Source/UI/Widgets/Button.cs
+++ b/Voile/Source/UI/Widgets/Button.cs
@@ -18,7 +18,7 @@ public enum ButtonState
public class Button : Widget
{
public string Label { get; set; } = "Button";
- public override Rect MinimumRect => new Rect(Width: 128.0f, Height: 64.0f);
+ public override Rect MinimumSize => new Rect(Width: 128.0f, Height: 64.0f);
public Button(string label, Action pressedAction)
{
@@ -30,7 +30,7 @@ public class Button : Widget
{
// TODO: use a button color from style.
renderer.SetTransform(Position, Vector2.Zero);
- renderer.DrawRectangle(new Vector2(MinimumRect.Width, MinimumRect.Height), new Color(0.25f, 0.25f, 0.25f));
+ renderer.DrawRectangle(new Vector2(MinimumSize.Width, MinimumSize.Height), new Color(0.25f, 0.25f, 0.25f));
}
protected override void OnInput(UIInputContext action)
@@ -38,5 +38,10 @@ public class Button : Widget
}
+ protected override void OnUpdate()
+ {
+ throw new NotImplementedException();
+ }
+
private Action _pressedAction;
}
\ No newline at end of file
diff --git a/Voile/Source/UI/Widgets/Label.cs b/Voile/Source/UI/Widgets/Label.cs
index ba2c06f..a1ec439 100644
--- a/Voile/Source/UI/Widgets/Label.cs
+++ b/Voile/Source/UI/Widgets/Label.cs
@@ -6,7 +6,7 @@ namespace Voile.UI.Widgets;
public class Label : Widget
{
- public override Rect MinimumRect => throw new NotImplementedException();
+ public override Rect MinimumSize => throw new NotImplementedException();
public string Text { get; set; } = "Hello World!";
@@ -34,5 +34,10 @@ public class Label : Widget
renderer.DrawText(_fontOverride, Text, Color.White);
}
+ protected override void OnUpdate()
+ {
+ throw new NotImplementedException();
+ }
+
private ResourceRef _fontOverride = ResourceRef.Empty();
}
\ No newline at end of file
diff --git a/Voile/Source/UI/Widgets/RectangleWidget.cs b/Voile/Source/UI/Widgets/RectangleWidget.cs
index 32d7542..afd7ff6 100644
--- a/Voile/Source/UI/Widgets/RectangleWidget.cs
+++ b/Voile/Source/UI/Widgets/RectangleWidget.cs
@@ -6,11 +6,11 @@ namespace Voile.UI.Widgets;
public class RectangleWidget : Widget
{
- public override Rect MinimumRect { get; }
+ public override Rect MinimumSize { get; }
public Color Color { get; set; } = Color.White;
public RectangleWidget(Rect minimumRect, Color color) : base()
{
- MinimumRect = minimumRect;
+ MinimumSize = minimumRect;
Color = color;
_defaultColor = color;
@@ -65,6 +65,11 @@ public class RectangleWidget : Widget
}
}
+ protected override void OnUpdate()
+ {
+
+ }
+
private Color _defaultColor;
private Color _hoverColor;
diff --git a/Voile/Source/UI/Widgets/Widget.cs b/Voile/Source/UI/Widgets/Widget.cs
index 26f3b82..afeab45 100644
--- a/Voile/Source/UI/Widgets/Widget.cs
+++ b/Voile/Source/UI/Widgets/Widget.cs
@@ -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;
///
/// A base class for all UI widgets.
///
-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();
}
- ///
- 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!");
- }
- }
-
- ///
- /// Called when its time to draw this widget.
- ///
- ///
- 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);
+ }
}
///
/// Called when this widget receives input.
///
/// An input action this widget received.
- 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);
- }
-
- ///
- /// 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 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);
}
\ No newline at end of file