Fix any remaining bugs with anchor positioning system, use LocalPosition for UIElement, and make containers use that for arrangement.

This commit is contained in:
2025-06-24 19:45:18 +02:00
parent b228f04670
commit 03668849bc
13 changed files with 74 additions and 72 deletions

View File

@@ -127,22 +127,22 @@ public class TestGame : Game
private ResourceRef<Sound> _sound; private ResourceRef<Sound> _sound;
private ResourceRef<Texture2d> _icon; private ResourceRef<Texture2d> _icon;
// private FlexContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new()) private FlexContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new())
// { {
// ConfineToContents = false, Anchor = Anchor.Center,
// Size = new Rect(500, 300), Size = new Rect(500, 300),
// Direction = FlexDirection.Column, Direction = FlexDirection.Column,
// Justify = JustifyContent.Start, Justify = JustifyContent.Start,
// Align = AlignItems.Center, Align = AlignItems.Center,
// Wrap = true, Wrap = true,
// Gap = 10f Gap = 10f
// }; };
private Frame _frame = new(); private Frame _frame = new();
private HorizontalContainer _container = new(new Rect(128.0f, 64.0f), new(), 16) // private VerticalContainer _container = new(new Rect(128.0f, 64.0f), new(), 16)
{ // {
ConfineToContents = true, // ConfineToContents = true,
Anchor = Anchor.TopRight, // Anchor = Anchor.CenterLeft,
AnchorOffset = new Vector2(1.0f, 0.0f) // AnchorOffset = new Vector2(0.5f, 0.0f)
}; // };
} }

View File

@@ -10,7 +10,7 @@ namespace Voile.UI.Containers;
public abstract class Container : UIElement, IParentableElement public abstract class Container : UIElement, IParentableElement
{ {
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<IElement> Children => _children; public IReadOnlyList<UIElement> Children => _children;
/// <summary> /// <summary>
/// Specifies if this <see cref="Container"/>'s minimum size will be confined to contents. /// Specifies if this <see cref="Container"/>'s minimum size will be confined to contents.
@@ -31,7 +31,7 @@ public abstract class Container : UIElement, IParentableElement
MarkDirty(); MarkDirty();
} }
public Container(Rect minimumSize, List<IElement> children) public Container(Rect minimumSize, List<UIElement> children)
{ {
_minimumSize = minimumSize; _minimumSize = minimumSize;
_children = children; _children = children;
@@ -50,7 +50,7 @@ public abstract class Container : UIElement, IParentableElement
if (child is IAnchorableElement anchorable) if (child is IAnchorableElement anchorable)
{ {
anchorable.ApplyAnchor(Position, Size); anchorable.ApplyAnchor(GlobalPosition, Size);
} }
} }
@@ -80,7 +80,7 @@ public abstract class Container : UIElement, IParentableElement
foreach (var child in Children) foreach (var child in Children)
{ {
var pos = child.Position; var pos = child.GlobalPosition;
var size = child.Size; var size = child.Size;
minX = MathF.Min(minX, pos.X); minX = MathF.Min(minX, pos.X);
@@ -105,15 +105,16 @@ public abstract class Container : UIElement, IParentableElement
} }
} }
public void AddChild(IElement child) public void AddChild(UIElement child)
{ {
_children.Add(child); _children.Add(child);
child.SetParent(this);
MarkDirty(); MarkDirty();
Update(); Update();
} }
public void RemoveChild(IElement child) public void RemoveChild(UIElement child)
{ {
_children.Remove(child); _children.Remove(child);
@@ -130,6 +131,6 @@ public abstract class Container : UIElement, IParentableElement
} }
} }
private List<IElement> _children = new(); private List<UIElement> _children = new();
private Rect _minimumSize = Rect.Zero; private Rect _minimumSize = Rect.Zero;
} }

View File

@@ -78,16 +78,16 @@ public class FlexContainer : Container
public FlexContainer() : base() { } public FlexContainer() : base() { }
public FlexContainer(Rect minimumSize, List<IElement> children) : base(minimumSize, children) { } public FlexContainer(Rect minimumSize, List<UIElement> children) : base(minimumSize, children) { }
public override void Arrange() public override void Arrange()
{ {
float containerMainSize = (Direction == FlexDirection.Row) ? Size.Width : Size.Height; float containerMainSize = (Direction == FlexDirection.Row) ? Size.Width : Size.Height;
float mainPos = (Direction == FlexDirection.Row) ? Position.X : Position.Y; float mainPos = 0.0f;
float crossPos = (Direction == FlexDirection.Row) ? Position.Y : Position.X; float crossPos = 0.0f;
List<List<IElement>> lines = new(); List<List<UIElement>> lines = new();
List<IElement> currentLine = new(); List<UIElement> currentLine = new();
float lineMainSum = 0f; float lineMainSum = 0f;
float maxCross = 0f; float maxCross = 0f;
@@ -136,7 +136,7 @@ public class FlexContainer : Container
? new Vector2(currentMain, alignedCross) ? new Vector2(currentMain, alignedCross)
: new Vector2(alignedCross, currentMain); : new Vector2(alignedCross, currentMain);
child.Position = childPos; child.LocalPosition = childPos;
currentMain += GetMainSize(childSize) + Gap; currentMain += GetMainSize(childSize) + Gap;
} }

View File

@@ -21,7 +21,7 @@ public class GridContainer : Container
/// </summary> /// </summary>
public float RowSpacing { get; set; } = 16.0f; public float RowSpacing { get; set; } = 16.0f;
public GridContainer(Rect minimumSize, List<IElement> children, int columns = 2, float colSpacing = 16.0f, float rowSpacing = 16.0f) public GridContainer(Rect minimumSize, List<UIElement> children, int columns = 2, float colSpacing = 16.0f, float rowSpacing = 16.0f)
: base(minimumSize, children) : base(minimumSize, children)
{ {
Columns = columns; Columns = columns;
@@ -38,16 +38,13 @@ public class GridContainer : Container
public override void Arrange() public override void Arrange()
{ {
float startX = Position.X; float currentX = 0.0f;
float startY = Position.Y; float currentY = 0.0f;
float currentX = startX;
float currentY = startY;
int colIndex = 0; int colIndex = 0;
foreach (var child in Children) foreach (var child in Children)
{ {
child.Position = new Vector2(currentX, currentY); child.LocalPosition = new Vector2(currentX, currentY);
float childWidth = 0.0f; float childWidth = 0.0f;
float childHeight = 0.0f; float childHeight = 0.0f;
@@ -60,7 +57,7 @@ public class GridContainer : Container
if (colIndex >= Columns) if (colIndex >= Columns)
{ {
colIndex = 0; colIndex = 0;
currentX = startX; currentX = 0.0f;
currentY += childHeight + RowSpacing; currentY += childHeight + RowSpacing;
} }
else else

View File

@@ -13,7 +13,7 @@ public class HorizontalContainer : Container
/// </summary> /// </summary>
public float Spacing { get; set; } = 16.0f; public float Spacing { get; set; } = 16.0f;
public HorizontalContainer(Rect minimumSize, List<IElement> children, float spacing = 16.0f) : base(minimumSize, children) public HorizontalContainer(Rect minimumSize, List<UIElement> children, float spacing = 16.0f) : base(minimumSize, children)
{ {
Spacing = spacing; Spacing = spacing;
} }
@@ -25,13 +25,13 @@ public class HorizontalContainer : Container
public override void Arrange() public override void Arrange()
{ {
float currentX = Position.X; float currentX = 0.0f;
for (int i = 0; i < Children.Count; i++) for (int i = 0; i < Children.Count; i++)
{ {
var child = Children[i]; var child = Children[i];
var pos = new Vector2(currentX, Position.Y); var pos = new Vector2(currentX, 0.0f);
child.Position = pos; child.LocalPosition = pos;
currentX += child.Size.Width; currentX += child.Size.Width;

View File

@@ -13,7 +13,7 @@ public class VerticalContainer : Container
/// </summary> /// </summary>
public float Spacing { get; set; } = 16.0f; public float Spacing { get; set; } = 16.0f;
public VerticalContainer(Rect minimumSize, List<IElement> children, float spacing = 16.0f) : base(minimumSize, children) public VerticalContainer(Rect minimumSize, List<UIElement> children, float spacing = 16.0f) : base(minimumSize, children)
{ {
Spacing = spacing; Spacing = spacing;
} }
@@ -25,13 +25,13 @@ public class VerticalContainer : Container
public override void Arrange() public override void Arrange()
{ {
float currentY = Position.Y; float currentY = 0.0f;
for (int i = 0; i < Children.Count; i++) for (int i = 0; i < Children.Count; i++)
{ {
var child = Children[i]; var child = Children[i];
var pos = new Vector2(Position.X, currentY); var pos = new Vector2(0.0f, currentY);
child.Position = pos; child.LocalPosition = pos;
currentY += child.Size.Height; currentY += child.Size.Height;

View File

@@ -9,7 +9,7 @@ public interface IElement
/// <summary> /// <summary>
/// This element's position in pixels relative to the viewport top-left edge. /// This element's position in pixels relative to the viewport top-left edge.
/// </summary> /// </summary>
public Vector2 Position { get; set; } public Vector2 GlobalPosition { get; }
/// <summary> /// <summary>
/// The size of this element. /// The size of this element.
/// </summary> /// </summary>
@@ -21,17 +21,17 @@ public interface IParentableElement
/// <summary> /// <summary>
/// This parentable element's children. /// This parentable element's children.
/// </summary> /// </summary>
public IReadOnlyList<IElement> Children { get; } public IReadOnlyList<UIElement> Children { get; }
/// <summary> /// <summary>
/// Add a child element to this element. /// Add a child element to this element.
/// </summary> /// </summary>
/// <param name="child">Child <see cref="IElement"/>.</param> /// <param name="child">Child <see cref="UIElement"/>.</param>
public void AddChild(IElement child); public void AddChild(UIElement child);
/// <summary> /// <summary>
/// Remove a child element from this element. /// Remove a child element from this element.
/// </summary> /// </summary>
/// <param name="child">Child <see cref="IElement"/> to remove.</param> /// <param name="child">Child <see cref="UIElement"/> to remove.</param>
public void RemoveChild(IElement child); public void RemoveChild(UIElement child);
} }
public interface IResizeableElement public interface IResizeableElement

View File

@@ -7,7 +7,9 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
{ {
public bool Visible { get; set; } = true; public bool Visible { get; set; } = true;
public bool IgnoreInput { get; set; } = false; public bool IgnoreInput { get; set; } = false;
public Vector2 Position { get; set; } = Vector2.Zero; public Vector2 LocalPosition { get; set; } = Vector2.Zero;
public Vector2 GlobalPosition => _parent?.GlobalPosition + LocalPosition ?? LocalPosition;
public Rect Size public Rect Size
{ {
get => _size; get => _size;
@@ -36,6 +38,11 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
public virtual void MarkDirty() => _dirty = true; public virtual void MarkDirty() => _dirty = true;
public void SetParent(UIElement parent)
{
_parent = parent;
}
public void Update() public void Update()
{ {
if (!_dirty) return; if (!_dirty) return;
@@ -46,9 +53,9 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
OnUpdate(); OnUpdate();
if (_parentRect != Rect.Zero) if (_parent is not null && _parent.Size != Rect.Zero)
{ {
ApplyAnchor(_parentPosition, _parentRect); ApplyAnchor(_parent.GlobalPosition, _parent.Size);
} }
} }
@@ -57,7 +64,7 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
public void DrawSize(RenderSystem renderer) public void DrawSize(RenderSystem renderer)
{ {
renderer.SetTransform(Position, Vector2.Zero); renderer.SetTransform(GlobalPosition, Vector2.Zero);
renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f); renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
} }
@@ -68,9 +75,9 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
/// <returns>True if the point is inside the widget; otherwise, false.</returns> /// <returns>True if the point is inside the widget; otherwise, false.</returns>
public bool ContainsPoint(Vector2 point) public bool ContainsPoint(Vector2 point)
{ {
return point.X >= Position.X && point.Y >= Position.Y && return point.X >= GlobalPosition.X && point.Y >= GlobalPosition.Y &&
point.X <= Position.X + Size.Width && point.X <= GlobalPosition.X + Size.Width &&
point.Y <= Position.Y + Size.Height; point.Y <= GlobalPosition.Y + Size.Height;
} }
/// <summary> /// <summary>
@@ -78,14 +85,11 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
/// </summary> /// </summary>
public virtual void ApplyAnchor(Vector2 parentPosition, Rect parentRect) public virtual void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
{ {
_parentPosition = parentPosition; LocalPosition = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
_parentRect = parentRect;
Position = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
} }
private bool _dirty = true; private bool _dirty = true;
private Rect _size = Rect.Zero; private Rect _size = Rect.Zero;
private Vector2 _parentPosition = Vector2.Zero; private UIElement? _parent;
private Rect _parentRect = Rect.Zero;
} }

View File

@@ -22,15 +22,15 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
_style = style; _style = style;
} }
public UISystem(InputSystem inputSystem, ResourceRef<Style> style, List<IElement> elements) public UISystem(InputSystem inputSystem, ResourceRef<Style> style, List<UIElement> elements)
{ {
_input = inputSystem; _input = inputSystem;
_style = style; _style = style;
_elements = elements; _elements = elements;
} }
public void AddElement(IElement element) => _elements.Add(element); public void AddElement(UIElement element) => _elements.Add(element);
public void RemoveElement(IElement element) => _elements.Remove(element); public void RemoveElement(UIElement element) => _elements.Remove(element);
public void Update(double deltaTime) public void Update(double deltaTime)
{ {
@@ -116,7 +116,7 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
} }
} }
private bool PropagateInput(List<IElement> elements, UIInputContext context) private bool PropagateInput(List<UIElement> elements, UIInputContext context)
{ {
for (int i = elements.Count - 1; i >= 0; i--) for (int i = elements.Count - 1; i >= 0; i--)
{ {
@@ -140,7 +140,7 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
} }
private ResourceRef<Style> _style; private ResourceRef<Style> _style;
private List<IElement> _elements = new(); private List<UIElement> _elements = new();
private InputSystem _input; private InputSystem _input;
private Vector2 _lastMousePosition = Vector2.Zero; private Vector2 _lastMousePosition = Vector2.Zero;

View File

@@ -29,7 +29,7 @@ public class Button : Widget
public override void Render(RenderSystem renderer, Style style) public override void Render(RenderSystem renderer, Style style)
{ {
// TODO: use a button color from style. // TODO: use a button color from style.
renderer.SetTransform(Position, Vector2.Zero); renderer.SetTransform(GlobalPosition, Vector2.Zero);
renderer.DrawRectangle(new Vector2(MinimumSize.Width, MinimumSize.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));
} }

View File

@@ -30,7 +30,7 @@ public class Label : Widget
{ {
// TODO: use style here. // TODO: use style here.
if (!_fontOverride.HasValue) return; if (!_fontOverride.HasValue) return;
renderer.SetTransform(Position, Vector2.Zero); renderer.SetTransform(GlobalPosition, Vector2.Zero);
renderer.DrawText(_fontOverride, Text, Color.White); renderer.DrawText(_fontOverride, Text, Color.White);
} }

View File

@@ -22,7 +22,7 @@ public class RectangleWidget : Widget
public override void Render(RenderSystem renderer, Style style) public override void Render(RenderSystem renderer, Style style)
{ {
renderer.SetTransform(Position, Vector2.Zero); renderer.SetTransform(GlobalPosition, Vector2.Zero);
renderer.DrawRectangle(new Vector2(Size.Width, Size.Height), Color); renderer.DrawRectangle(new Vector2(Size.Width, Size.Height), Color);
} }

View File

@@ -22,7 +22,7 @@ public abstract class Widget : UIElement, IInputElement
public Widget(Vector2 position) public Widget(Vector2 position)
{ {
Position = position; LocalPosition = position;
MarkDirty(); MarkDirty();
} }