Compare commits
7 Commits
9a3512702a
...
standard-r
| Author | SHA1 | Date | |
|---|---|---|---|
| dc7122ed26 | |||
| 78b46cb38e | |||
| 6c3576891e | |||
| 03668849bc | |||
| b228f04670 | |||
| a5d2668c18 | |||
| 30c438c407 |
11
TODO.md
11
TODO.md
@@ -4,8 +4,8 @@
|
||||
|
||||
- ActionJustPressed and KeyboardKeyJustPressed don't detect inputs consistently.
|
||||
- **Solution**: This is a problem related to custom frame pacing for fixed timestep. Raylib polls input in BeginFrame, which means certain functions related to JustPressed* may work incorrectly when polled in a separate timestep. This can be fixed if the entire input system will be moved to SDL or with a custom render + input backend altogether.
|
||||
- Fix any remaining bugs with anchor positioning system.
|
||||
- Containers don't position their chilren correctly when using anchors and adding/removing them.
|
||||
- ~~Fix any remaining bugs with anchor positioning system.~~
|
||||
- ~~Containers don't position their chilren correctly when using anchors and adding/removing them.~~
|
||||
|
||||
## Core
|
||||
|
||||
@@ -78,7 +78,10 @@
|
||||
- ~~GridContainer~~
|
||||
- ~~FlexContainer~~
|
||||
- ~~Positioning (anchors)~~
|
||||
- Move layouting to Render instead of Update, use Update for input.
|
||||
- ~~Move layouting to Render instead of Update, use Update for input.~~
|
||||
- Input propagation
|
||||
- Basic input elements (button, text field, toggle).
|
||||
- Styling.
|
||||
- Styling
|
||||
- Add style settings for UI panels (for buttons, labels, etc.).
|
||||
- Find a way to reference external assets in the style (fonts, textures).
|
||||
- Create a default style for widgets.
|
||||
@@ -90,14 +90,14 @@ public class TestGame : Game
|
||||
|
||||
protected override void Render(double deltaTime)
|
||||
{
|
||||
Renderer.ClearBackground(Color.Black);
|
||||
foreach (var emitter in _particleSystem!.Emitters)
|
||||
{
|
||||
DrawEmitter(emitter);
|
||||
}
|
||||
Renderer.ClearBackground(Color.CadetBlue);
|
||||
// foreach (var emitter in _particleSystem!.Emitters)
|
||||
// {
|
||||
// DrawEmitter(emitter);
|
||||
// }
|
||||
|
||||
Renderer.ResetTransform();
|
||||
_uiSystem.Render(Renderer);
|
||||
// Renderer.ResetTransform();
|
||||
// _uiSystem.Render(Renderer);
|
||||
}
|
||||
|
||||
private void DrawEmitter(ParticleEmitter emitter)
|
||||
@@ -127,22 +127,22 @@ public class TestGame : Game
|
||||
private ResourceRef<Sound> _sound;
|
||||
private ResourceRef<Texture2d> _icon;
|
||||
|
||||
// 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 FlexContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new())
|
||||
{
|
||||
Anchor = Anchor.Center,
|
||||
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 = true,
|
||||
Anchor = Anchor.TopRight,
|
||||
AnchorOffset = new Vector2(1.0f, 0.0f)
|
||||
};
|
||||
// private VerticalContainer _container = new(new Rect(128.0f, 64.0f), new(), 16)
|
||||
// {
|
||||
// ConfineToContents = true,
|
||||
// Anchor = Anchor.CenterLeft,
|
||||
// AnchorOffset = new Vector2(0.5f, 0.0f)
|
||||
// };
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Voile
|
||||
|
||||
if (Renderer is null)
|
||||
{
|
||||
Renderer = new RaylibRenderSystem();
|
||||
Renderer = new StandardRenderSystem();
|
||||
}
|
||||
|
||||
if (Input is null)
|
||||
|
||||
@@ -363,7 +363,7 @@ namespace Voile.Rendering
|
||||
|
||||
private Silk.NET.WebGPU.Color VoileColorToWebGPUColor(Color color)
|
||||
{
|
||||
return new Silk.NET.WebGPU.Color(color.R, color.G, color.B, color.A);
|
||||
return new Silk.NET.WebGPU.Color((double)color.R / 255, (double)color.G / 255, (double)color.B / 255, (double)color.A / 255);
|
||||
}
|
||||
|
||||
private unsafe RenderPassColorAttachment CreateClearColorAttachment(TextureView* view, Color clearColor)
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Voile.UI.Containers;
|
||||
public abstract class Container : UIElement, IParentableElement
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<IElement> Children => _children;
|
||||
public IReadOnlyList<UIElement> Children => _children;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies if this <see cref="Container"/>'s minimum size will be confined to contents.
|
||||
@@ -31,7 +31,7 @@ public abstract class Container : UIElement, IParentableElement
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
public Container(Rect minimumSize, List<IElement> children)
|
||||
public Container(Rect minimumSize, List<UIElement> children)
|
||||
{
|
||||
_minimumSize = minimumSize;
|
||||
_children = children;
|
||||
@@ -50,7 +50,7 @@ public abstract class Container : UIElement, IParentableElement
|
||||
|
||||
if (child is IAnchorableElement anchorable)
|
||||
{
|
||||
anchorable.ApplyAnchor(Position, Size);
|
||||
anchorable.ApplyAnchor(GlobalPosition, Size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public abstract class Container : UIElement, IParentableElement
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var pos = child.Position;
|
||||
var pos = child.GlobalPosition;
|
||||
var size = child.Size;
|
||||
|
||||
minX = MathF.Min(minX, pos.X);
|
||||
@@ -105,15 +105,16 @@ public abstract class Container : UIElement, IParentableElement
|
||||
}
|
||||
}
|
||||
|
||||
public void AddChild(IElement child)
|
||||
public void AddChild(UIElement child)
|
||||
{
|
||||
_children.Add(child);
|
||||
child.SetParent(this);
|
||||
|
||||
MarkDirty();
|
||||
Update();
|
||||
}
|
||||
|
||||
public void RemoveChild(IElement child)
|
||||
public void RemoveChild(UIElement child)
|
||||
{
|
||||
_children.Remove(child);
|
||||
|
||||
@@ -130,6 +131,6 @@ public abstract class Container : UIElement, IParentableElement
|
||||
}
|
||||
}
|
||||
|
||||
private List<IElement> _children = new();
|
||||
private List<UIElement> _children = new();
|
||||
private Rect _minimumSize = Rect.Zero;
|
||||
}
|
||||
@@ -78,16 +78,16 @@ public class FlexContainer : Container
|
||||
|
||||
public FlexContainer() : base() { }
|
||||
|
||||
public FlexContainer(Rect minimumSize, List<IElement> children) : base(minimumSize, children) { }
|
||||
public FlexContainer(Rect minimumSize, List<UIElement> children) : base(minimumSize, children) { }
|
||||
|
||||
public override void Arrange()
|
||||
{
|
||||
float containerMainSize = (Direction == FlexDirection.Row) ? Size.Width : Size.Height;
|
||||
float mainPos = (Direction == FlexDirection.Row) ? Position.X : Position.Y;
|
||||
float crossPos = (Direction == FlexDirection.Row) ? Position.Y : Position.X;
|
||||
float mainPos = 0.0f;
|
||||
float crossPos = 0.0f;
|
||||
|
||||
List<List<IElement>> lines = new();
|
||||
List<IElement> currentLine = new();
|
||||
List<List<UIElement>> lines = new();
|
||||
List<UIElement> currentLine = new();
|
||||
float lineMainSum = 0f;
|
||||
float maxCross = 0f;
|
||||
|
||||
@@ -136,7 +136,7 @@ public class FlexContainer : Container
|
||||
? new Vector2(currentMain, alignedCross)
|
||||
: new Vector2(alignedCross, currentMain);
|
||||
|
||||
child.Position = childPos;
|
||||
child.LocalPosition = childPos;
|
||||
|
||||
currentMain += GetMainSize(childSize) + Gap;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class GridContainer : Container
|
||||
/// </summary>
|
||||
public float RowSpacing { get; set; } = 16.0f;
|
||||
|
||||
public GridContainer(Rect minimumSize, List<IElement> children, int columns = 2, float colSpacing = 16.0f, float rowSpacing = 16.0f)
|
||||
public GridContainer(Rect minimumSize, List<UIElement> children, int columns = 2, float colSpacing = 16.0f, float rowSpacing = 16.0f)
|
||||
: base(minimumSize, children)
|
||||
{
|
||||
Columns = columns;
|
||||
@@ -38,16 +38,13 @@ public class GridContainer : Container
|
||||
|
||||
public override void Arrange()
|
||||
{
|
||||
float startX = Position.X;
|
||||
float startY = Position.Y;
|
||||
|
||||
float currentX = startX;
|
||||
float currentY = startY;
|
||||
float currentX = 0.0f;
|
||||
float currentY = 0.0f;
|
||||
int colIndex = 0;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Position = new Vector2(currentX, currentY);
|
||||
child.LocalPosition = new Vector2(currentX, currentY);
|
||||
|
||||
float childWidth = 0.0f;
|
||||
float childHeight = 0.0f;
|
||||
@@ -60,7 +57,7 @@ public class GridContainer : Container
|
||||
if (colIndex >= Columns)
|
||||
{
|
||||
colIndex = 0;
|
||||
currentX = startX;
|
||||
currentX = 0.0f;
|
||||
currentY += childHeight + RowSpacing;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -13,7 +13,7 @@ public class HorizontalContainer : Container
|
||||
/// </summary>
|
||||
public float Spacing { get; set; } = 16.0f;
|
||||
|
||||
public HorizontalContainer(Rect minimumSize, List<IElement> children, float spacing = 16.0f) : base(minimumSize, children)
|
||||
public HorizontalContainer(Rect minimumSize, List<UIElement> children, float spacing = 16.0f) : base(minimumSize, children)
|
||||
{
|
||||
Spacing = spacing;
|
||||
}
|
||||
@@ -25,13 +25,13 @@ public class HorizontalContainer : Container
|
||||
|
||||
public override void Arrange()
|
||||
{
|
||||
float currentX = Position.X;
|
||||
float currentX = 0.0f;
|
||||
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
var child = Children[i];
|
||||
var pos = new Vector2(currentX, Position.Y);
|
||||
child.Position = pos;
|
||||
var pos = new Vector2(currentX, 0.0f);
|
||||
child.LocalPosition = pos;
|
||||
|
||||
currentX += child.Size.Width;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ public class VerticalContainer : Container
|
||||
/// </summary>
|
||||
public float Spacing { get; set; } = 16.0f;
|
||||
|
||||
public VerticalContainer(Rect minimumSize, List<IElement> children, float spacing = 16.0f) : base(minimumSize, children)
|
||||
public VerticalContainer(Rect minimumSize, List<UIElement> children, float spacing = 16.0f) : base(minimumSize, children)
|
||||
{
|
||||
Spacing = spacing;
|
||||
}
|
||||
@@ -25,13 +25,13 @@ public class VerticalContainer : Container
|
||||
|
||||
public override void Arrange()
|
||||
{
|
||||
float currentY = Position.Y;
|
||||
float currentY = 0.0f;
|
||||
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
var child = Children[i];
|
||||
var pos = new Vector2(Position.X, currentY);
|
||||
child.Position = pos;
|
||||
var pos = new Vector2(0.0f, currentY);
|
||||
child.LocalPosition = pos;
|
||||
|
||||
currentY += child.Size.Height;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ public interface IElement
|
||||
/// <summary>
|
||||
/// This element's position in pixels relative to the viewport top-left edge.
|
||||
/// </summary>
|
||||
public Vector2 Position { get; set; }
|
||||
public Vector2 GlobalPosition { get; }
|
||||
/// <summary>
|
||||
/// The size of this element.
|
||||
/// </summary>
|
||||
@@ -21,17 +21,17 @@ public interface IParentableElement
|
||||
/// <summary>
|
||||
/// This parentable element's children.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IElement> Children { get; }
|
||||
public IReadOnlyList<UIElement> Children { get; }
|
||||
/// <summary>
|
||||
/// Add a child element to this element.
|
||||
/// </summary>
|
||||
/// <param name="child">Child <see cref="IElement"/>.</param>
|
||||
public void AddChild(IElement child);
|
||||
/// <param name="child">Child <see cref="UIElement"/>.</param>
|
||||
public void AddChild(UIElement child);
|
||||
/// <summary>
|
||||
/// Remove a child element from this element.
|
||||
/// </summary>
|
||||
/// <param name="child">Child <see cref="IElement"/> to remove.</param>
|
||||
public void RemoveChild(IElement child);
|
||||
/// <param name="child">Child <see cref="UIElement"/> to remove.</param>
|
||||
public void RemoveChild(UIElement child);
|
||||
}
|
||||
|
||||
public interface IResizeableElement
|
||||
|
||||
@@ -7,7 +7,9 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
||||
{
|
||||
public bool Visible { get; set; } = true;
|
||||
public bool IgnoreInput { get; set; } = false;
|
||||
public Vector2 Position { get; set; } = Vector2.Zero;
|
||||
public Vector2 LocalPosition { get; set; } = Vector2.Zero;
|
||||
public Vector2 GlobalPosition => _parent?.GlobalPosition + LocalPosition ?? LocalPosition;
|
||||
|
||||
public Rect Size
|
||||
{
|
||||
get => _size;
|
||||
@@ -36,6 +38,11 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
||||
|
||||
public virtual void MarkDirty() => _dirty = true;
|
||||
|
||||
public void SetParent(UIElement parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!_dirty) return;
|
||||
@@ -46,9 +53,9 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
||||
|
||||
OnUpdate();
|
||||
|
||||
if (_parentRect != Rect.Zero)
|
||||
if (_parent is not null && _parent.Size != Rect.Zero)
|
||||
{
|
||||
ApplyAnchor(_parentPosition, _parentRect);
|
||||
ApplyAnchor(_parent.GlobalPosition, _parent.Size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +64,7 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
||||
|
||||
public void DrawSize(RenderSystem renderer)
|
||||
{
|
||||
renderer.SetTransform(Position, Vector2.Zero);
|
||||
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||
renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
|
||||
}
|
||||
|
||||
@@ -68,9 +75,9 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
||||
/// <returns>True if the point is inside the widget; otherwise, false.</returns>
|
||||
public bool ContainsPoint(Vector2 point)
|
||||
{
|
||||
return point.X >= Position.X && point.Y >= Position.Y &&
|
||||
point.X <= Position.X + Size.Width &&
|
||||
point.Y <= Position.Y + Size.Height;
|
||||
return point.X >= GlobalPosition.X && point.Y >= GlobalPosition.Y &&
|
||||
point.X <= GlobalPosition.X + Size.Width &&
|
||||
point.Y <= GlobalPosition.Y + Size.Height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -78,14 +85,11 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
||||
/// </summary>
|
||||
public virtual void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
|
||||
{
|
||||
_parentPosition = parentPosition;
|
||||
_parentRect = parentRect;
|
||||
Position = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
|
||||
LocalPosition = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
|
||||
}
|
||||
|
||||
private bool _dirty = true;
|
||||
private Rect _size = Rect.Zero;
|
||||
|
||||
private Vector2 _parentPosition = Vector2.Zero;
|
||||
private Rect _parentRect = Rect.Zero;
|
||||
private UIElement? _parent;
|
||||
}
|
||||
|
||||
@@ -22,29 +22,30 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
||||
_style = style;
|
||||
}
|
||||
|
||||
public UISystem(InputSystem inputSystem, ResourceRef<Style> style, List<IElement> elements)
|
||||
public UISystem(InputSystem inputSystem, ResourceRef<Style> style, List<UIElement> elements)
|
||||
{
|
||||
_input = inputSystem;
|
||||
_style = style;
|
||||
_elements = elements;
|
||||
}
|
||||
|
||||
public void AddElement(IElement element) => _elements.Add(element);
|
||||
public void RemoveElement(IElement element) => _elements.Remove(element);
|
||||
public void AddElement(UIElement element) => _elements.Add(element);
|
||||
public void RemoveElement(UIElement element) => _elements.Remove(element);
|
||||
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
HandleInput();
|
||||
}
|
||||
|
||||
public void Render(RenderSystem renderer)
|
||||
{
|
||||
// Update elements each time UI system is rendered.
|
||||
foreach (var element in _elements)
|
||||
{
|
||||
if (element is not IUpdatableElement updatable) continue;
|
||||
updatable.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(RenderSystem renderer)
|
||||
{
|
||||
foreach (var element in _elements)
|
||||
{
|
||||
if (element is IRenderableElement renderable)
|
||||
@@ -115,7 +116,7 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
||||
}
|
||||
}
|
||||
|
||||
private bool PropagateInput(List<IElement> elements, UIInputContext context)
|
||||
private bool PropagateInput(List<UIElement> elements, UIInputContext context)
|
||||
{
|
||||
for (int i = elements.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -139,7 +140,7 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
||||
}
|
||||
|
||||
private ResourceRef<Style> _style;
|
||||
private List<IElement> _elements = new();
|
||||
private List<UIElement> _elements = new();
|
||||
private InputSystem _input;
|
||||
|
||||
private Vector2 _lastMousePosition = Vector2.Zero;
|
||||
|
||||
@@ -29,7 +29,7 @@ public class Button : Widget
|
||||
public override void Render(RenderSystem renderer, Style style)
|
||||
{
|
||||
// TODO: use a button color from style.
|
||||
renderer.SetTransform(Position, Vector2.Zero);
|
||||
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||
renderer.DrawRectangle(new Vector2(MinimumSize.Width, MinimumSize.Height), new Color(0.25f, 0.25f, 0.25f));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public class Label : Widget
|
||||
{
|
||||
// TODO: use style here.
|
||||
if (!_fontOverride.HasValue) return;
|
||||
renderer.SetTransform(Position, Vector2.Zero);
|
||||
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||
renderer.DrawText(_fontOverride, Text, Color.White);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public class RectangleWidget : Widget
|
||||
|
||||
public override void Render(RenderSystem renderer, Style style)
|
||||
{
|
||||
renderer.SetTransform(Position, Vector2.Zero);
|
||||
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||
renderer.DrawRectangle(new Vector2(Size.Width, Size.Height), Color);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public abstract class Widget : UIElement, IInputElement
|
||||
|
||||
public Widget(Vector2 position)
|
||||
{
|
||||
Position = position;
|
||||
LocalPosition = position;
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
Reference in New Issue
Block a user