Initial implementation of UI anchors, update TestGame.

This commit is contained in:
2025-06-21 22:23:19 +02:00
parent ae1b612524
commit 683656dee8
8 changed files with 148 additions and 20 deletions

View File

@@ -56,7 +56,9 @@ public class TestGame : Game
Input.AddInputMapping("reload", new IInputAction[] { new KeyInputAction(KeyboardKey.R) }); Input.AddInputMapping("reload", new IInputAction[] { new KeyInputAction(KeyboardKey.R) });
_emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect); _emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect);
_uiSystem.AddElement(_container); _frame.AddChild(_container);
_uiSystem.AddElement(_frame);
} }
@@ -78,6 +80,12 @@ public class TestGame : Game
var lastChild = _container.Children.Last(); var lastChild = _container.Children.Last();
_container.RemoveChild(lastChild); _container.RemoveChild(lastChild);
} }
if (Input.IsMouseButtonDown(MouseButton.Left))
{
var mousePos = Input.GetMousePosition();
_frame.Size = new Rect(mousePos.X, mousePos.Y);
}
} }
protected override void Render(double deltaTime) protected override void Render(double deltaTime)
@@ -119,14 +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,
// Size = new Rect(500, 300),
// Direction = FlexDirection.Column,
// Justify = JustifyContent.Start,
// Align = AlignItems.Center,
// Wrap = true,
// Gap = 10f
// };
private Frame _frame = new();
private HorizontalContainer _container = new(new Rect(128.0f, 64.0f), new(), 16)
{ {
ConfineToContents = false, ConfineToContents = true,
Size = new Rect(500, 300), Anchor = Anchor.TopCenter,
Direction = FlexDirection.Row, AnchorOffset = new Vector2(0.5f, 0.0f)
Justify = JustifyContent.Start,
Align = AlignItems.Center,
Wrap = true,
Gap = 10f
}; };
} }

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>

15
Voile/Source/UI/Anchor.cs Normal file
View File

@@ -0,0 +1,15 @@
namespace Voile.UI;
public enum Anchor
{
TopLeft,
TopCenter,
TopRight,
Left,
Center,
Right,
BottomLeft,
BottomCenter,
BottomRight,
Fill
}

View File

@@ -3,10 +3,11 @@ using Voile.Rendering;
namespace Voile.UI.Containers; namespace Voile.UI.Containers;
// TODO: make Container extend Widget, it already implements similar behaviors.
/// <summary> /// <summary>
/// A base class for all UI containers, used to position and rendering child <see cref="IElement">s. /// A base class for all UI containers, used to position and rendering child <see cref="IElement">s.
/// </summary> /// </summary>
public abstract class Container : IElement, IParentableElement, IUpdatableElement, IResizeableElement, IRenderableElement public abstract class Container : IElement, IParentableElement, IUpdatableElement, IResizeableElement, IRenderableElement, IAnchorableElement
{ {
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<IElement> Children => _children; public IReadOnlyList<IElement> Children => _children;
@@ -44,6 +45,8 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen
/// 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.
/// </summary> /// </summary>
public bool ConfineToContents { get; set; } = false; public bool ConfineToContents { get; set; } = false;
public Anchor Anchor { get; set; }
public Vector2 AnchorOffset { get; set; }
public Container() public Container()
{ {
@@ -75,6 +78,11 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen
{ {
if (child is not IUpdatableElement updatable) continue; if (child is not IUpdatableElement updatable) continue;
updatable.Update(); updatable.Update();
if (child is IAnchorableElement anchorable)
{
anchorable.ApplyAnchor(Position, Size);
}
} }
Arrange(); Arrange();
@@ -161,6 +169,38 @@ public abstract class Container : IElement, IParentableElement, IUpdatableElemen
renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f); 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);
break;
case Anchor.Fill:
Position = parentPosition;
Size = parentRect;
break;
default:
throw new NotImplementedException("This anchor type is not implemented!");
}
MarkDirty();
Update();
}
private List<IElement> _children = new(); private List<IElement> _children = new();
private bool _isDirty; private bool _isDirty;
private Rect _size = Rect.Zero; private Rect _size = Rect.Zero;

View File

@@ -0,0 +1,19 @@
namespace Voile.UI.Containers;
public class Frame : Container
{
public Frame()
{
}
public Frame(Rect minimumSize) : base(minimumSize)
{
}
public override void Arrange()
{
}
}

View File

@@ -89,3 +89,10 @@ public interface IInputElement
/// <param name="action">Input action to send.</param> /// <param name="action">Input action to send.</param>
void Input(UIInputContext action); void Input(UIInputContext action);
} }
public interface IAnchorableElement
{
public Anchor Anchor { get; set; }
public Vector2 AnchorOffset { get; set; }
public void ApplyAnchor(Vector2 parentPosition, Rect parentRect);
}

View File

@@ -49,15 +49,15 @@ public class RectangleWidget : Widget
Color = _defaultColor; Color = _defaultColor;
} }
if (mouseInside && inputContext.MouseDown) // if (mouseInside && inputContext.MouseDown)
{ // {
Size = _downSize; // Size = _downSize;
} // }
if (mouseInside && inputContext.MouseReleased) // if (mouseInside && inputContext.MouseReleased)
{ // {
Size = _defaultSize; // Size = _defaultSize;
} // }
if (mouseInside && inputContext.MousePressed) if (mouseInside && inputContext.MousePressed)
{ {

View File

@@ -1,4 +1,5 @@
using System.Numerics; using System.Numerics;
using System.Xml.Serialization;
using Voile.Input; using Voile.Input;
using Voile.Rendering; using Voile.Rendering;
@@ -7,7 +8,7 @@ namespace Voile.UI.Widgets;
/// <summary> /// <summary>
/// A base class for all UI widgets. /// A base class for all UI widgets.
/// </summary> /// </summary>
public abstract class Widget : IElement, IRenderableElement, IInputElement, IResizeableElement, IUpdatableElement public abstract class Widget : IElement, IRenderableElement, IInputElement, IResizeableElement, IUpdatableElement, IAnchorableElement
{ {
public bool Visible { get; set; } = true; public bool Visible { get; set; } = true;
public bool IgnoreInput { get; set; } public bool IgnoreInput { get; set; }
@@ -36,6 +37,36 @@ public abstract class Widget : IElement, IRenderableElement, IInputElement, IRes
public bool Dirty => _isDirty; 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> /// <summary>
/// Called when its time to draw this widget. /// Called when its time to draw this widget.
/// </summary> /// </summary>