Compare commits
2 Commits
15214c9e21
...
806c9cc1d4
| Author | SHA1 | Date | |
|---|---|---|---|
| 806c9cc1d4 | |||
| a450ed9819 |
4
TODO.md
4
TODO.md
@@ -63,7 +63,9 @@
|
|||||||
|
|
||||||
## UI
|
## UI
|
||||||
|
|
||||||
- Immediate mode API.
|
- Basic widgets (button, label, text input)
|
||||||
|
- Layout
|
||||||
|
- Input propagation
|
||||||
- Styling.
|
- Styling.
|
||||||
- Basic input elements (button, text field, toggle).
|
- Basic input elements (button, text field, toggle).
|
||||||
- Containers (vertical and horizontal).
|
- Containers (vertical and horizontal).
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ using System.Numerics;
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Voile.Rendering;
|
using Voile.Rendering;
|
||||||
using Voile.OpenAL;
|
using Voile.OpenAL;
|
||||||
|
using Voile.UI;
|
||||||
|
using Voile.UI.Widgets;
|
||||||
|
using Voile.UI.Containers;
|
||||||
|
|
||||||
public class TestGame : Game
|
public class TestGame : Game
|
||||||
{
|
{
|
||||||
@@ -16,10 +19,11 @@ public class TestGame : Game
|
|||||||
{
|
{
|
||||||
InitializeSystemsDefault();
|
InitializeSystemsDefault();
|
||||||
|
|
||||||
_audioSystem = new OpenALSystem();
|
_uiSystem = new UISystem(new ResourceRef<Style>(Guid.Empty));
|
||||||
|
|
||||||
_particleSystem = new ParticleSystem();
|
_particleSystem = new ParticleSystem();
|
||||||
|
|
||||||
AddSystemToUpdate(_audioSystem);
|
AddSystemToUpdate(_uiSystem);
|
||||||
AddSystemToUpdate(_particleSystem);
|
AddSystemToUpdate(_particleSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +54,13 @@ 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(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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -57,9 +68,8 @@ public class TestGame : Game
|
|||||||
{
|
{
|
||||||
if (Input.IsActionPressed("reload"))
|
if (Input.IsActionPressed("reload"))
|
||||||
{
|
{
|
||||||
// ResourceManager.Reload();
|
ResourceManager.Reload();
|
||||||
// _particleSystem!.RestartEmitter(_emitterId);
|
_particleSystem!.RestartEmitter(_emitterId);
|
||||||
_audioSystem.PlaySound(_sound.Value, 1.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Input.KeyboardKeyJustPressed(KeyboardKey.One))
|
if (Input.KeyboardKeyJustPressed(KeyboardKey.One))
|
||||||
@@ -82,11 +92,7 @@ public class TestGame : Game
|
|||||||
}
|
}
|
||||||
|
|
||||||
Renderer.ResetTransform();
|
Renderer.ResetTransform();
|
||||||
|
_uiSystem.Render(Renderer);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawEmitter(ParticleEmitter emitter)
|
private void DrawEmitter(ParticleEmitter emitter)
|
||||||
@@ -109,7 +115,7 @@ public class TestGame : Game
|
|||||||
}
|
}
|
||||||
|
|
||||||
[NotNull] private ParticleSystem _particleSystem;
|
[NotNull] private ParticleSystem _particleSystem;
|
||||||
private OpenALSystem _audioSystem;
|
[NotNull] private UISystem _uiSystem;
|
||||||
private int _emitterId;
|
private int _emitterId;
|
||||||
private ResourceRef<ParticleEmitterSettingsResource> _fireEffect;
|
private ResourceRef<ParticleEmitterSettingsResource> _fireEffect;
|
||||||
private ResourceRef<Font> _font;
|
private ResourceRef<Font> _font;
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ namespace Voile.Input
|
|||||||
return inputSystem.KeyboardKeyJustReleased(_keyboardKey);
|
return inputSystem.KeyboardKeyJustReleased(_keyboardKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private KeyboardKey _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>
|
/// </summary>
|
||||||
public T Value => ResourceManager.GetResource<T>(Guid);
|
public T Value => ResourceManager.GetResource<T>(Guid);
|
||||||
|
|
||||||
|
public static ResourceRef<T> Empty()
|
||||||
|
{
|
||||||
|
return new ResourceRef<T>(Guid.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
public ResourceRef(Guid guid)
|
public ResourceRef(Guid guid)
|
||||||
{
|
{
|
||||||
Guid = guid;
|
Guid = guid;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Voile.UI;
|
||||||
using Voile.Utils;
|
using Voile.Utils;
|
||||||
using Voile.VFS;
|
using Voile.VFS;
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ namespace Voile.Resources
|
|||||||
/// <param name="resourceGuid">Resource's GUID.</param>
|
/// <param name="resourceGuid">Resource's GUID.</param>
|
||||||
/// <param name="resource">Retrieved resource. Otherwise null if nothing got retrieved.</param>
|
/// <param name="resource">Retrieved resource. Otherwise null if nothing got retrieved.</param>
|
||||||
/// <returns>True if resource got successfully retrieved, otherwise false.</returns>
|
/// <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))
|
if (!TryGetLoader(out ResourceLoader<T>? loader))
|
||||||
{
|
{
|
||||||
@@ -216,6 +216,11 @@ namespace Voile.Resources
|
|||||||
|
|
||||||
if (!GetResource(resourceGuid, out T? loadedResource))
|
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!");
|
throw new Exception($"No resource with GUID \"{resourceGuid}\" found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,11 +345,13 @@ namespace Voile.Resources
|
|||||||
|
|
||||||
private static Logger _logger = new(nameof(ResourceManager));
|
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()
|
private static readonly Dictionary<Type, object> _resourceLoaderAssociations = new()
|
||||||
{
|
{
|
||||||
{typeof(Sound), new SoundLoader()},
|
{ typeof(Sound), new SoundLoader()},
|
||||||
{typeof(Texture2d), new Texture2dLoader()},
|
{typeof(Texture2d), new Texture2dLoader()},
|
||||||
{typeof(Font), new FontLoader()}
|
{typeof(Font), new FontLoader()},
|
||||||
|
{ typeof(Style), new StyleLoader()}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<Type, object> _resourceSaverAssociations = new()
|
private static readonly Dictionary<Type, object> _resourceSaverAssociations = new()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
namespace Voile;
|
namespace Voile;
|
||||||
|
|
||||||
public interface IStartableSystem
|
public interface IStartableSystem
|
||||||
@@ -35,4 +37,12 @@ public interface IUpdatableSystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deltaTime">Time step.</param>
|
/// <param name="deltaTime">Time step.</param>
|
||||||
void Update(double deltaTime);
|
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