WIP: UI system, containers and widgets.
This commit is contained in:
@@ -6,6 +6,9 @@ using System.Numerics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Voile.Rendering;
|
||||
using Voile.OpenAL;
|
||||
using Voile.UI;
|
||||
using Voile.UI.Widgets;
|
||||
using Voile.UI.Containers;
|
||||
|
||||
public class TestGame : Game
|
||||
{
|
||||
@@ -16,9 +19,11 @@ public class TestGame : Game
|
||||
{
|
||||
InitializeSystemsDefault();
|
||||
|
||||
_uiSystem = new UISystem(new ResourceRef<Style>(Guid.Empty));
|
||||
|
||||
_particleSystem = new ParticleSystem();
|
||||
|
||||
// AddSystemToUpdate(_audioSystem);
|
||||
AddSystemToUpdate(_uiSystem);
|
||||
AddSystemToUpdate(_particleSystem);
|
||||
}
|
||||
|
||||
@@ -49,6 +54,13 @@ public class TestGame : Game
|
||||
{
|
||||
Input.AddInputMapping("reload", new IInputAction[] { new KeyInputAction(KeyboardKey.R) });
|
||||
_emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect);
|
||||
|
||||
_uiSystem.AddElement(new VerticalContainer(new()
|
||||
{
|
||||
new Label("Hello, I'm a label!", _font),
|
||||
new Label("I'm also a label, except I'm located slightly lower!", _font),
|
||||
new Label("I hope a VerticalContainer works.", _font)
|
||||
}, 64.0f));
|
||||
}
|
||||
|
||||
|
||||
@@ -80,11 +92,7 @@ public class TestGame : Game
|
||||
}
|
||||
|
||||
Renderer.ResetTransform();
|
||||
|
||||
Renderer.DrawText(_font, $"Render: {RenderFrameTime.TotalMilliseconds:F1} ms", Color.White);
|
||||
|
||||
Renderer.SetTransform(new Vector2(0.0f, 16.0f), Vector2.Zero);
|
||||
Renderer.DrawText(_font, $"Update: {UpdateTimeStep * 1000:F1} ms", Color.White);
|
||||
_uiSystem.Render(Renderer);
|
||||
}
|
||||
|
||||
private void DrawEmitter(ParticleEmitter emitter)
|
||||
@@ -107,6 +115,7 @@ public class TestGame : Game
|
||||
}
|
||||
|
||||
[NotNull] private ParticleSystem _particleSystem;
|
||||
[NotNull] private UISystem _uiSystem;
|
||||
private int _emitterId;
|
||||
private ResourceRef<ParticleEmitterSettingsResource> _fireEffect;
|
||||
private ResourceRef<Font> _font;
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace Voile.Input
|
||||
return inputSystem.KeyboardKeyJustReleased(_keyboardKey);
|
||||
}
|
||||
|
||||
|
||||
private KeyboardKey _keyboardKey;
|
||||
}
|
||||
}
|
||||
17
Voile/Source/Resources/Loaders/StyleLoader.cs
Normal file
17
Voile/Source/Resources/Loaders/StyleLoader.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Voile.UI;
|
||||
|
||||
namespace Voile.Resources;
|
||||
|
||||
public class StyleLoader : ResourceLoader<Style>
|
||||
{
|
||||
public override IEnumerable<string> SupportedExtensions =>
|
||||
[
|
||||
".toml"
|
||||
];
|
||||
|
||||
protected override Style LoadResource(string path)
|
||||
{
|
||||
// TODO: implement loading styles.
|
||||
return new Style(string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,11 @@ namespace Voile
|
||||
/// </summary>
|
||||
public T Value => ResourceManager.GetResource<T>(Guid);
|
||||
|
||||
public static ResourceRef<T> Empty()
|
||||
{
|
||||
return new ResourceRef<T>(Guid.Empty);
|
||||
}
|
||||
|
||||
public ResourceRef(Guid guid)
|
||||
{
|
||||
Guid = guid;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Voile.UI;
|
||||
using Voile.Utils;
|
||||
using Voile.VFS;
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace Voile.Resources
|
||||
/// <param name="resourceGuid">Resource's GUID.</param>
|
||||
/// <param name="resource">Retrieved resource. Otherwise null if nothing got retrieved.</param>
|
||||
/// <returns>True if resource got successfully retrieved, otherwise false.</returns>
|
||||
public static T GetResource<T>(Guid resourceGuid) where T : Resource
|
||||
public static T? GetResource<T>(Guid resourceGuid) where T : Resource
|
||||
{
|
||||
if (!TryGetLoader(out ResourceLoader<T>? loader))
|
||||
{
|
||||
@@ -216,6 +216,11 @@ namespace Voile.Resources
|
||||
|
||||
if (!GetResource(resourceGuid, out T? loadedResource))
|
||||
{
|
||||
if (resourceGuid == Guid.Empty)
|
||||
{
|
||||
_logger.Warn("Trying to load a resource with an empty GUID, ignoring.");
|
||||
return null;
|
||||
}
|
||||
throw new Exception($"No resource with GUID \"{resourceGuid}\" found!");
|
||||
}
|
||||
|
||||
@@ -340,11 +345,13 @@ namespace Voile.Resources
|
||||
|
||||
private static Logger _logger = new(nameof(ResourceManager));
|
||||
|
||||
// TODO: don't include types from optional systems. Create a way for third-party systems to register their own loader associations.
|
||||
private static readonly Dictionary<Type, object> _resourceLoaderAssociations = new()
|
||||
{
|
||||
{ typeof(Sound), new SoundLoader()},
|
||||
{typeof(Texture2d), new Texture2dLoader()},
|
||||
{typeof(Font), new FontLoader()}
|
||||
{typeof(Font), new FontLoader()},
|
||||
{ typeof(Style), new StyleLoader()}
|
||||
};
|
||||
|
||||
private static readonly Dictionary<Type, object> _resourceSaverAssociations = new()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile;
|
||||
|
||||
public interface IStartableSystem
|
||||
@@ -36,3 +38,11 @@ public interface IUpdatableSystem
|
||||
/// <param name="deltaTime">Time step.</param>
|
||||
void Update(double deltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A system that renders itself to the screen, ex. UI or particles.
|
||||
/// </summary>
|
||||
public interface IRenderableSystem
|
||||
{
|
||||
void Render(RenderSystem renderer);
|
||||
}
|
||||
27
Voile/Source/UI/Containers/Container.cs
Normal file
27
Voile/Source/UI/Containers/Container.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Numerics;
|
||||
using Voile.UI.Widgets;
|
||||
|
||||
namespace Voile.UI.Containers;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for all UI containers, used to position and rendering child <see cref="Widget">s.
|
||||
/// </summary>
|
||||
public abstract class Container : IElement, IParentableElement
|
||||
{
|
||||
public IReadOnlyList<IElement> Children => _children;
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public Container(List<IElement> children)
|
||||
{
|
||||
_children = children;
|
||||
}
|
||||
|
||||
public abstract void Arrange();
|
||||
|
||||
public void AddChild(IElement child)
|
||||
{
|
||||
_children.Add(child);
|
||||
}
|
||||
|
||||
private List<IElement> _children;
|
||||
}
|
||||
38
Voile/Source/UI/Containers/VerticalContainer.cs
Normal file
38
Voile/Source/UI/Containers/VerticalContainer.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Voile.Rendering;
|
||||
using Voile.UI.Widgets;
|
||||
|
||||
namespace Voile.UI.Containers;
|
||||
|
||||
public class VerticalContainer : Container, IRenderableElement
|
||||
{
|
||||
public float Spacing { get; set; } = 16.0f;
|
||||
public bool Visible { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
public VerticalContainer(List<IElement> children, float spacing) : base(children)
|
||||
{
|
||||
Spacing = spacing;
|
||||
}
|
||||
|
||||
public override void Arrange()
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var pos = Position;
|
||||
pos.Y += i * Spacing;
|
||||
|
||||
child.Position = pos;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(RenderSystem renderer, Style style)
|
||||
{
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child is not IRenderableElement renderable) continue;
|
||||
renderable.Render(renderer, style);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Voile/Source/UI/IElement.cs
Normal file
28
Voile/Source/UI/IElement.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Numerics;
|
||||
using Voile.Input;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
public interface IElement
|
||||
{
|
||||
public Vector2 Position { get; set; }
|
||||
}
|
||||
|
||||
public interface IParentableElement
|
||||
{
|
||||
public IReadOnlyList<IElement> Children { get; }
|
||||
public void AddChild(IElement child);
|
||||
}
|
||||
|
||||
public interface IRenderableElement
|
||||
{
|
||||
public bool Visible { get; set; }
|
||||
public void Render(RenderSystem renderer, Style style);
|
||||
}
|
||||
|
||||
public interface IInputElement
|
||||
{
|
||||
public bool IgnoreInput { get; set; }
|
||||
void Input(IInputAction action);
|
||||
}
|
||||
8
Voile/Source/UI/Rect.cs
Normal file
8
Voile/Source/UI/Rect.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a rectangle. Used to determine widget confines for UI layout.
|
||||
/// </summary>
|
||||
public record Rect(Vector2 Position, float Width, float Height);
|
||||
13
Voile/Source/UI/Style.cs
Normal file
13
Voile/Source/UI/Style.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Voile.Resources;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A resource containing UI style settings.
|
||||
/// </summary>
|
||||
public class Style : TextDataResource
|
||||
{
|
||||
public Style(string path) : base(path)
|
||||
{
|
||||
}
|
||||
}
|
||||
48
Voile/Source/UI/UISystem.cs
Normal file
48
Voile/Source/UI/UISystem.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Voile.Rendering;
|
||||
using Voile.UI.Containers;
|
||||
|
||||
namespace Voile.UI;
|
||||
|
||||
public class UISystem : IUpdatableSystem, IRenderableSystem
|
||||
{
|
||||
public IReadOnlyList<IElement> Elements => _elements;
|
||||
|
||||
public UISystem(ResourceRef<Style> style)
|
||||
{
|
||||
_style = style;
|
||||
}
|
||||
|
||||
public UISystem(ResourceRef<Style> style, List<IElement> elements)
|
||||
{
|
||||
_style = style;
|
||||
_elements = elements;
|
||||
}
|
||||
|
||||
public void AddElement(IElement element) => _elements.Add(element);
|
||||
public void RemoveElement(IElement element) => _elements.Remove(element);
|
||||
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
foreach (var element in _elements)
|
||||
{
|
||||
if (element is Container container)
|
||||
{
|
||||
container.Arrange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(RenderSystem renderer)
|
||||
{
|
||||
foreach (var element in _elements)
|
||||
{
|
||||
if (element is IRenderableElement renderable)
|
||||
{
|
||||
renderable.Render(renderer, _style.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceRef<Style> _style;
|
||||
private List<IElement> _elements = new();
|
||||
}
|
||||
42
Voile/Source/UI/Widgets/Button.cs
Normal file
42
Voile/Source/UI/Widgets/Button.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Numerics;
|
||||
using Voile.Input;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.UI.Widgets;
|
||||
|
||||
public enum ButtonState
|
||||
{
|
||||
Disabled,
|
||||
Hovered,
|
||||
Pressed,
|
||||
Normal
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A clickable button with a label.
|
||||
/// </summary>
|
||||
public class Button : Widget
|
||||
{
|
||||
public string Label { get; set; } = "Button";
|
||||
public override Rect MinimumRect => new Rect(Position, Width: 128.0f, Height: 64.0f);
|
||||
|
||||
public Button(string label, Action pressedAction)
|
||||
{
|
||||
Label = label;
|
||||
_pressedAction = pressedAction;
|
||||
}
|
||||
|
||||
public override void Render(RenderSystem renderer, Style style)
|
||||
{
|
||||
// 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));
|
||||
}
|
||||
|
||||
public override void Input(IInputAction action)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private Action _pressedAction;
|
||||
}
|
||||
38
Voile/Source/UI/Widgets/Label.cs
Normal file
38
Voile/Source/UI/Widgets/Label.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Numerics;
|
||||
using Voile.Input;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.UI.Widgets;
|
||||
|
||||
public class Label : Widget
|
||||
{
|
||||
public override Rect MinimumRect => throw new NotImplementedException();
|
||||
|
||||
public string Text { get; set; } = "Hello World!";
|
||||
|
||||
public Label(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
public Label(string text, ResourceRef<Font> fontOverride)
|
||||
{
|
||||
Text = text;
|
||||
_fontOverride = fontOverride;
|
||||
}
|
||||
|
||||
public override void Input(IInputAction action)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Render(RenderSystem renderer, Style style)
|
||||
{
|
||||
// TODO: use style here.
|
||||
if (!_fontOverride.HasValue) return;
|
||||
renderer.SetTransform(Position, Vector2.Zero);
|
||||
renderer.DrawText(_fontOverride, Text, Color.White);
|
||||
}
|
||||
|
||||
private ResourceRef<Font> _fontOverride = ResourceRef<Font>.Empty();
|
||||
}
|
||||
41
Voile/Source/UI/Widgets/Widget.cs
Normal file
41
Voile/Source/UI/Widgets/Widget.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Numerics;
|
||||
using Voile.Input;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.UI.Widgets;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for all UI widgets.
|
||||
/// </summary>
|
||||
public abstract class Widget : IElement, IRenderableElement, IInputElement
|
||||
{
|
||||
public bool Visible { get; set; } = true;
|
||||
public bool IgnoreInput { get; set; }
|
||||
public Vector2 Position { get; set; } = Vector2.Zero;
|
||||
|
||||
public Widget()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Widget(Vector2 position)
|
||||
{
|
||||
Position = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a minimum rectangle size for this widget.
|
||||
/// </summary>
|
||||
public abstract Rect MinimumRect { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when its time to draw this widget.
|
||||
/// </summary>
|
||||
/// <param name="renderer"></param>
|
||||
public abstract void Render(RenderSystem renderer, Style style);
|
||||
/// <summary>
|
||||
/// Called when this widget receives input.
|
||||
/// </summary>
|
||||
/// <param name="action">An input action this widget received.</param>
|
||||
public abstract void Input(IInputAction action);
|
||||
}
|
||||
Reference in New Issue
Block a user