Compare commits
5 Commits
255dea138b
...
64d3dba42d
| Author | SHA1 | Date | |
|---|---|---|---|
| 64d3dba42d | |||
| 5bf052db96 | |||
| 389a73cf24 | |||
| d44341974f | |||
| b2f3e1c351 |
@@ -56,9 +56,10 @@ 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);
|
||||||
|
|
||||||
_frame.AddChild(_container);
|
_fillContainer.AddChild(_marginContainer);
|
||||||
|
_marginContainer.AddChild(_container);
|
||||||
|
|
||||||
_uiSystem.AddElement(_frame);
|
_uiSystem.AddElement(_fillContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ public class TestGame : Game
|
|||||||
if (Input.IsMouseButtonDown(MouseButton.Left))
|
if (Input.IsMouseButtonDown(MouseButton.Left))
|
||||||
{
|
{
|
||||||
var mousePos = Input.GetMousePosition();
|
var mousePos = Input.GetMousePosition();
|
||||||
_frame.Size = new Rect(mousePos.X, mousePos.Y);
|
_fillContainer.Size = new Rect(mousePos.X, mousePos.Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,19 +131,19 @@ public class TestGame : Game
|
|||||||
private FlexContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new())
|
private FlexContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Center,
|
Anchor = Anchor.Center,
|
||||||
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 = 8.0f
|
||||||
};
|
};
|
||||||
|
|
||||||
private Frame _frame = new();
|
private FillContainer _fillContainer = new();
|
||||||
// private VerticalContainer _container = new(new Rect(128.0f, 64.0f), new(), 16)
|
private MarginContainer _marginContainer = new(new Margin(32.0f))
|
||||||
|
{
|
||||||
|
};
|
||||||
|
// private HorizontalContainer _container = new(new Rect(128.0f, 64.0f), new(), 16)
|
||||||
// {
|
// {
|
||||||
// ConfineToContents = true,
|
// ConfineToContents = true,
|
||||||
// Anchor = Anchor.CenterLeft,
|
|
||||||
// AnchorOffset = new Vector2(0.5f, 0.0f)
|
|
||||||
// };
|
// };
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Voile.Resources;
|
using Voile.Resources;
|
||||||
|
|
||||||
namespace Voile
|
namespace Voile
|
||||||
@@ -32,7 +33,7 @@ namespace Voile
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">An instance of a retrieved <see cref="Resource"/>.</param>
|
/// <param name="value">An instance of a retrieved <see cref="Resource"/>.</param>
|
||||||
/// <returns><c>true</c> if the resource was successfully retrieved, otherwise <c>false</c>.</returns>
|
/// <returns><c>true</c> if the resource was successfully retrieved, otherwise <c>false</c>.</returns>
|
||||||
public bool TryGetValue(out T? value)
|
public bool TryGetValue([NotNullWhen(true)] out T? value)
|
||||||
{
|
{
|
||||||
value = ResourceManager.GetResource<T>(Guid);
|
value = ResourceManager.GetResource<T>(Guid);
|
||||||
return value != null;
|
return value != null;
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ public abstract class Container : UIElement, IParentableElement
|
|||||||
{
|
{
|
||||||
if (child is not IUpdatableElement updatable) continue;
|
if (child is not IUpdatableElement updatable) continue;
|
||||||
|
|
||||||
updatable.MarkDirty();
|
|
||||||
updatable.Update();
|
updatable.Update();
|
||||||
|
|
||||||
if (child is IAnchorableElement anchorable)
|
if (child is IAnchorableElement anchorable)
|
||||||
@@ -61,6 +60,18 @@ public abstract class Container : UIElement, IParentableElement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void MarkDirty()
|
||||||
|
{
|
||||||
|
base.MarkDirty();
|
||||||
|
|
||||||
|
foreach (var child in _children)
|
||||||
|
{
|
||||||
|
if (child is not IUpdatableElement updatable) continue;
|
||||||
|
|
||||||
|
updatable.MarkDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when this <see cref="Container"/> has to rearrange its children.
|
/// Called when this <see cref="Container"/> has to rearrange its children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -96,7 +107,12 @@ public abstract class Container : UIElement, IParentableElement
|
|||||||
float finalWidth = MathF.Max(occupiedWidth, _minimumSize.Width);
|
float finalWidth = MathF.Max(occupiedWidth, _minimumSize.Width);
|
||||||
float finalHeight = MathF.Max(occupiedHeight, _minimumSize.Height);
|
float finalHeight = MathF.Max(occupiedHeight, _minimumSize.Height);
|
||||||
|
|
||||||
Size = new Rect(finalWidth, finalHeight);
|
var finalSize = new Rect(finalWidth, finalHeight);
|
||||||
|
|
||||||
|
if (finalSize != Size)
|
||||||
|
{
|
||||||
|
Size = finalSize;
|
||||||
|
}
|
||||||
|
|
||||||
if (_minimumSize > Size)
|
if (_minimumSize > Size)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ using Voile.Rendering;
|
|||||||
namespace Voile.UI.Containers;
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A frame is a special container that occupies the entire available size of the parent.
|
/// A special container that occupies the entire available size of the parent. <br />
|
||||||
|
/// Usually used as a root element for the UI system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Frame : Container
|
public class FillContainer : Container
|
||||||
{
|
{
|
||||||
public Frame()
|
public FillContainer()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Frame(Rect minimumSize) : base(minimumSize)
|
public FillContainer(Rect minimumSize) : base(minimumSize)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
78
Voile/Source/UI/Containers/MarginContainer.cs
Normal file
78
Voile/Source/UI/Containers/MarginContainer.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the margin offsets applied around an element.
|
||||||
|
/// </summary>
|
||||||
|
public struct Margin
|
||||||
|
{
|
||||||
|
public float Left;
|
||||||
|
public float Right;
|
||||||
|
public float Top;
|
||||||
|
public float Bottom;
|
||||||
|
|
||||||
|
public Margin(float uniform)
|
||||||
|
{
|
||||||
|
Left = Right = Top = Bottom = uniform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Margin(float horizontal, float vertical)
|
||||||
|
{
|
||||||
|
Left = Right = horizontal;
|
||||||
|
Top = Bottom = vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Margin(float left, float right, float top, float bottom)
|
||||||
|
{
|
||||||
|
Left = left;
|
||||||
|
Right = right;
|
||||||
|
Top = top;
|
||||||
|
Bottom = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Margin Zero => new Margin(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MarginContainer : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The margin to apply around the contents of this container.
|
||||||
|
/// </summary>
|
||||||
|
public Margin Margin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies if this <see cref="MarginContainer"/> will fill to parent size.
|
||||||
|
/// </summary>
|
||||||
|
public bool Fill { get; set; } = true;
|
||||||
|
|
||||||
|
public MarginContainer() : this(new Margin()) { }
|
||||||
|
|
||||||
|
public MarginContainer(Margin margin)
|
||||||
|
{
|
||||||
|
Margin = margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
base.OnUpdate();
|
||||||
|
if (Parent == null) return;
|
||||||
|
|
||||||
|
Size = Parent.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Arrange()
|
||||||
|
{
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
var newPosition = new Vector2(Margin.Left, Margin.Top);
|
||||||
|
var newSize = new Rect(
|
||||||
|
Size.Width - Margin.Left - Margin.Right,
|
||||||
|
Size.Height - Margin.Top - Margin.Bottom
|
||||||
|
);
|
||||||
|
|
||||||
|
child.Size = newSize;
|
||||||
|
child.LocalPosition = newPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,6 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
|||||||
get => _size;
|
get => _size;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_size = value;
|
|
||||||
|
|
||||||
if (value.Width < MinimumSize.Width)
|
if (value.Width < MinimumSize.Width)
|
||||||
{
|
{
|
||||||
_size.Width = MinimumSize.Width;
|
_size.Width = MinimumSize.Width;
|
||||||
@@ -35,8 +33,13 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
|||||||
_size.Height = MinimumSize.Height;
|
_size.Height = MinimumSize.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_size != value)
|
||||||
|
{
|
||||||
MarkDirty();
|
MarkDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_size = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public Vector2 AnchorOffset { get; set; } = Vector2.Zero;
|
public Vector2 AnchorOffset { get; set; } = Vector2.Zero;
|
||||||
public Anchor Anchor { get; set; } = Anchor.TopLeft;
|
public Anchor Anchor { get; set; } = Anchor.TopLeft;
|
||||||
@@ -44,7 +47,15 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
|||||||
public abstract Rect MinimumSize { get; }
|
public abstract Rect MinimumSize { get; }
|
||||||
public bool Dirty => _dirty;
|
public bool Dirty => _dirty;
|
||||||
|
|
||||||
public virtual void MarkDirty() => _dirty = true;
|
public virtual void MarkDirty()
|
||||||
|
{
|
||||||
|
if (Parent != null && !Parent.Dirty)
|
||||||
|
{
|
||||||
|
Parent.MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a parent element for this <see cref="UIElement"/>.
|
/// Sets a parent element for this <see cref="UIElement"/>.
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
public IReadOnlyList<IElement> Elements => _elements;
|
public IReadOnlyList<IElement> Elements => _elements;
|
||||||
|
|
||||||
public bool RenderDebugRects { get; set; }
|
public bool RenderDebugRects { get; set; }
|
||||||
|
public Color DebugSizeRectColor { get; set; } = Color.Red;
|
||||||
|
public Color DebugDirtyRectColor { get; set; } = new Color(1.0f, 1.0f, 0.0f, 0.5f);
|
||||||
|
|
||||||
public UISystem(InputSystem inputSystem)
|
public UISystem(InputSystem inputSystem)
|
||||||
{
|
{
|
||||||
@@ -50,22 +52,44 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
{
|
{
|
||||||
if (element is IRenderableElement renderable)
|
if (element is IRenderableElement renderable)
|
||||||
{
|
{
|
||||||
renderable.Render(renderer, _style.Value);
|
// TODO: normally you'd load a default style if the one supplied is empty,
|
||||||
|
// but for now this will do.
|
||||||
if (!RenderDebugRects) return;
|
if (!_style.TryGetValue(out var value))
|
||||||
renderable.DrawSize(renderer);
|
{
|
||||||
|
value = new Style(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element is IParentableElement parentable)
|
renderable.Render(renderer, value);
|
||||||
{
|
}
|
||||||
foreach (var child in parentable.Children)
|
}
|
||||||
{
|
|
||||||
if (child is not IRenderableElement renderableChild) continue;
|
|
||||||
|
|
||||||
if (!RenderDebugRects) return;
|
if (!RenderDebugRects) return;
|
||||||
renderableChild.DrawSize(renderer);
|
|
||||||
|
foreach (var element in _elements)
|
||||||
|
{
|
||||||
|
if (element is not UIElement uiElement) continue;
|
||||||
|
DrawDebugForElement(renderer, uiElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawDebugForElement(RenderSystem renderer, UIElement element)
|
||||||
|
{
|
||||||
|
var size = new Vector2(element.Size.Width, element.Size.Height);
|
||||||
|
renderer.SetTransform(element.GlobalPosition, Vector2.Zero);
|
||||||
|
renderer.DrawRectangleOutline(size, DebugSizeRectColor);
|
||||||
|
|
||||||
|
if (element.Dirty)
|
||||||
|
{
|
||||||
|
renderer.DrawRectangle(size, DebugDirtyRectColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element is IParentableElement parentableElement)
|
||||||
|
{
|
||||||
|
foreach (var child in parentableElement.Children)
|
||||||
|
{
|
||||||
|
if (child is not UIElement childElement) continue;
|
||||||
|
DrawDebugForElement(renderer, childElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,11 +147,28 @@ namespace Voile
|
|||||||
public static readonly Color Yellow = new(0xFFFF00);
|
public static readonly Color Yellow = new(0xFFFF00);
|
||||||
public static readonly Color YellowGreen = new(0x9ACD32);
|
public static readonly Color YellowGreen = new(0x9ACD32);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Red component of this <see cref="Color"/>.
|
||||||
|
/// </summary>
|
||||||
public byte R { get; set; }
|
public byte R { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Green component of this <see cref="Color"/>.
|
||||||
|
/// </summary>
|
||||||
public byte G { get; set; }
|
public byte G { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Blue component of this <see cref="Color"/>.
|
||||||
|
/// </summary>
|
||||||
public byte B { get; set; }
|
public byte B { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Alpha component of this <see cref="Color"/>.
|
||||||
|
/// </summary>/// <summary>
|
||||||
|
/// Gets the color as a 32-bit ARGB integer in the format 0xAARRGGBB.
|
||||||
|
/// </summary>
|
||||||
public byte A { get; set; } = 255;
|
public byte A { get; set; } = 255;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the color as a 32-bit ARGB integer in the format 0xAARRGGBB.
|
||||||
|
/// </summary>
|
||||||
public int Argb
|
public int Argb
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -165,6 +182,13 @@ namespace Voile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Color"/> struct using float RGB(A) values between 0 and 1.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="r">The red component (0.0 to 1.0).</param>
|
||||||
|
/// <param name="g">The green component (0.0 to 1.0).</param>
|
||||||
|
/// <param name="b">The blue component (0.0 to 1.0).</param>
|
||||||
|
/// <param name="a">The alpha component (0.0 to 1.0), default is 1.0 (fully opaque).</param>
|
||||||
public Color(float r, float g, float b, float a = 1.0f)
|
public Color(float r, float g, float b, float a = 1.0f)
|
||||||
{
|
{
|
||||||
R = (byte)Math.Clamp(r * 255, 0, 255);
|
R = (byte)Math.Clamp(r * 255, 0, 255);
|
||||||
@@ -173,6 +197,13 @@ namespace Voile
|
|||||||
A = (byte)Math.Clamp(a * 255, 0, 255);
|
A = (byte)Math.Clamp(a * 255, 0, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Color"/> struct using byte RGB(A) values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="r">The red component (0 to 255).</param>
|
||||||
|
/// <param name="g">The green component (0 to 255).</param>
|
||||||
|
/// <param name="b">The blue component (0 to 255).</param>
|
||||||
|
/// <param name="a">The alpha component (0 to 255), default is 255 (fully opaque).</param>
|
||||||
public Color(byte r, byte g, byte b, byte a = 255)
|
public Color(byte r, byte g, byte b, byte a = 255)
|
||||||
{
|
{
|
||||||
R = r;
|
R = r;
|
||||||
@@ -181,6 +212,13 @@ namespace Voile
|
|||||||
A = a;
|
A = a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Color"/> struct using a hexadecimal value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hex">
|
||||||
|
/// A 24-bit (RRGGBB) or 32-bit (AARRGGBB) integer representing the color.
|
||||||
|
/// Alpha is assumed to be 255 if not included.
|
||||||
|
/// </param>
|
||||||
public Color(int hex)
|
public Color(int hex)
|
||||||
{
|
{
|
||||||
A = 255; // Default alpha to 255 if not provided
|
A = 255; // Default alpha to 255 if not provided
|
||||||
@@ -193,7 +231,13 @@ namespace Voile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly Color FromHexString(string hex)
|
/// <summary>
|
||||||
|
/// Parses a color from a hexadecimal string in the format "#RRGGBB" or "#AARRGGBB".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hex">The hex string representing the color.</param>
|
||||||
|
/// <returns>A <see cref="Color"/> instance parsed from the string.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if the format is invalid.</exception>
|
||||||
|
public static Color FromHexString(string hex)
|
||||||
{
|
{
|
||||||
if (hex.StartsWith("#"))
|
if (hex.StartsWith("#"))
|
||||||
{
|
{
|
||||||
@@ -216,6 +260,11 @@ namespace Voile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a lightened version of the color by interpolating toward white.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amount">A value from 0.0 (no change) to 1.0 (fully white).</param>
|
||||||
|
/// <returns>A lighter <see cref="Color"/>.</returns>
|
||||||
public Color Lightened(float amount)
|
public Color Lightened(float amount)
|
||||||
{
|
{
|
||||||
var result = this;
|
var result = this;
|
||||||
@@ -225,6 +274,11 @@ namespace Voile
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a darkened version of the color by interpolating toward black.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amount">A value from 0.0 (no change) to 1.0 (fully black).</param>
|
||||||
|
/// <returns>A darker <see cref="Color"/>.</returns>
|
||||||
public Color Darkened(float amount)
|
public Color Darkened(float amount)
|
||||||
{
|
{
|
||||||
var result = this;
|
var result = this;
|
||||||
@@ -234,6 +288,10 @@ namespace Voile
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts this color to a <see cref="System.Drawing.Color"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="System.Drawing.Color"/> with equivalent ARGB values.</returns>
|
||||||
public System.Drawing.Color ToSystemColor()
|
public System.Drawing.Color ToSystemColor()
|
||||||
{
|
{
|
||||||
var result = System.Drawing.Color.FromArgb(Argb);
|
var result = System.Drawing.Color.FromArgb(Argb);
|
||||||
|
|||||||
Reference in New Issue
Block a user