Initial implementation of UI anchors, update TestGame.
This commit is contained in:
@@ -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
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -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
15
Voile/Source/UI/Anchor.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
public enum Anchor
|
||||||
|
{
|
||||||
|
TopLeft,
|
||||||
|
TopCenter,
|
||||||
|
TopRight,
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
BottomLeft,
|
||||||
|
BottomCenter,
|
||||||
|
BottomRight,
|
||||||
|
Fill
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
19
Voile/Source/UI/Containers/Frame.cs
Normal file
19
Voile/Source/UI/Containers/Frame.cs
Normal 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()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,4 +88,11 @@ public interface IInputElement
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <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);
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user