Compare commits
3 Commits
5b29ea012e
...
7e86898e1a
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e86898e1a | |||
| a1f56f49fb | |||
| 84efb2a3d1 |
2
TODO.md
2
TODO.md
@@ -73,7 +73,7 @@
|
|||||||
- ~~VerticalContainer~~
|
- ~~VerticalContainer~~
|
||||||
- ~~HorizontalContainer~~
|
- ~~HorizontalContainer~~
|
||||||
- ~~GridContainer~~
|
- ~~GridContainer~~
|
||||||
- FlexContainer
|
- ~~FlexContainer~~
|
||||||
- Positioning (anchors)
|
- Positioning (anchors)
|
||||||
- Input propagation
|
- Input propagation
|
||||||
- Basic input elements (button, text field, toggle).
|
- Basic input elements (button, text field, toggle).
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class TestGame : Game
|
|||||||
{
|
{
|
||||||
InitializeSystemsDefault();
|
InitializeSystemsDefault();
|
||||||
|
|
||||||
_uiSystem = new UISystem(new ResourceRef<Style>(Guid.Empty));
|
_uiSystem = new UISystem(Input, ResourceRef<Style>.Empty());
|
||||||
_uiSystem.RenderDebugRects = true;
|
_uiSystem.RenderDebugRects = true;
|
||||||
|
|
||||||
_particleSystem = new ParticleSystem();
|
_particleSystem = new ParticleSystem();
|
||||||
@@ -78,12 +78,6 @@ 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();
|
|
||||||
_container.Size = new Rect(mousePos.X, mousePos.Y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Render(double deltaTime)
|
protected override void Render(double deltaTime)
|
||||||
|
|||||||
@@ -33,4 +33,29 @@ namespace Voile.Input
|
|||||||
|
|
||||||
private KeyboardKey _keyboardKey;
|
private KeyboardKey _keyboardKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct MouseInputAction : IInputAction
|
||||||
|
{
|
||||||
|
public MouseButton MouseButton { get; private set; }
|
||||||
|
|
||||||
|
public MouseInputAction(MouseButton button)
|
||||||
|
{
|
||||||
|
MouseButton = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPressed(InputSystem inputSystem)
|
||||||
|
{
|
||||||
|
return inputSystem.IsMousePressed(MouseButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDown(InputSystem inputSystem)
|
||||||
|
{
|
||||||
|
return inputSystem.IsMouseButtonDown(MouseButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReleased(InputSystem inputSystem)
|
||||||
|
{
|
||||||
|
return inputSystem.IsMouseButtonReleased(MouseButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -101,8 +101,12 @@ namespace Voile.Input
|
|||||||
public abstract bool KeyboardKeyJustReleased(KeyboardKey key);
|
public abstract bool KeyboardKeyJustReleased(KeyboardKey key);
|
||||||
|
|
||||||
public abstract int GetCharPressed();
|
public abstract int GetCharPressed();
|
||||||
|
|
||||||
|
public abstract bool IsMousePressed(MouseButton button);
|
||||||
public abstract bool IsMouseButtonDown(MouseButton button);
|
public abstract bool IsMouseButtonDown(MouseButton button);
|
||||||
|
public abstract bool IsMouseButtonReleased(MouseButton button);
|
||||||
public abstract float GetMouseWheelMovement();
|
public abstract float GetMouseWheelMovement();
|
||||||
|
|
||||||
public abstract void SetMousePosition(Vector2 position);
|
public abstract void SetMousePosition(Vector2 position);
|
||||||
public abstract Vector2 GetMousePosition();
|
public abstract Vector2 GetMousePosition();
|
||||||
public abstract void HideCursor();
|
public abstract void HideCursor();
|
||||||
|
|||||||
@@ -34,6 +34,16 @@ namespace Voile.Input
|
|||||||
return Raylib.IsKeyDown(rayKey);
|
return Raylib.IsKeyDown(rayKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool IsMousePressed(MouseButton button)
|
||||||
|
{
|
||||||
|
return Raylib.IsMouseButtonPressed((Raylib_cs.MouseButton)button);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsMouseButtonReleased(MouseButton button)
|
||||||
|
{
|
||||||
|
return Raylib.IsMouseButtonReleased((Raylib_cs.MouseButton)button);
|
||||||
|
}
|
||||||
|
|
||||||
public override bool IsMouseButtonDown(MouseButton button)
|
public override bool IsMouseButtonDown(MouseButton button)
|
||||||
{
|
{
|
||||||
return Raylib.IsMouseButtonDown((Raylib_cs.MouseButton)button);
|
return Raylib.IsMouseButtonDown((Raylib_cs.MouseButton)button);
|
||||||
|
|||||||
@@ -87,5 +87,5 @@ public interface IInputElement
|
|||||||
/// Send an input action to this element.
|
/// Send an input action to this element.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">Input action to send.</param>
|
/// <param name="action">Input action to send.</param>
|
||||||
void Input(IInputAction action);
|
void Input(UIInputContext action);
|
||||||
}
|
}
|
||||||
35
Voile/Source/UI/UIInputContext.cs
Normal file
35
Voile/Source/UI/UIInputContext.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Input;
|
||||||
|
|
||||||
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
public class UIInputContext
|
||||||
|
{
|
||||||
|
public IInputAction Action { get; }
|
||||||
|
public Vector2 MousePosition { get; }
|
||||||
|
public bool MousePressed { get; set; }
|
||||||
|
public bool MouseReleased { get; set; }
|
||||||
|
public bool MouseDown { get; set; }
|
||||||
|
|
||||||
|
public string ActionName { get; }
|
||||||
|
|
||||||
|
public int CharPressed { get; }
|
||||||
|
public bool HasCharInput => CharPressed != 0;
|
||||||
|
|
||||||
|
public bool Handled => _handled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks this context as handled, meaning next elements that will receive it should discard it.
|
||||||
|
/// </summary>
|
||||||
|
public void SetHandled() => _handled = true;
|
||||||
|
|
||||||
|
public UIInputContext(IInputAction action, Vector2 mousePosition, string actionName, int charPressed = 0)
|
||||||
|
{
|
||||||
|
Action = action;
|
||||||
|
MousePosition = mousePosition;
|
||||||
|
ActionName = actionName;
|
||||||
|
CharPressed = charPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _handled;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Voile.Input;
|
||||||
using Voile.Rendering;
|
using Voile.Rendering;
|
||||||
using Voile.UI.Containers;
|
|
||||||
|
|
||||||
namespace Voile.UI;
|
namespace Voile.UI;
|
||||||
|
|
||||||
@@ -10,13 +10,21 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
|
|
||||||
public bool RenderDebugRects { get; set; }
|
public bool RenderDebugRects { get; set; }
|
||||||
|
|
||||||
public UISystem(ResourceRef<Style> style)
|
public UISystem(InputSystem inputSystem)
|
||||||
{
|
{
|
||||||
|
_style = ResourceRef<Style>.Empty();
|
||||||
|
_input = inputSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UISystem(InputSystem inputSystem, ResourceRef<Style> style)
|
||||||
|
{
|
||||||
|
_input = inputSystem;
|
||||||
_style = style;
|
_style = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UISystem(ResourceRef<Style> style, List<IElement> elements)
|
public UISystem(InputSystem inputSystem, ResourceRef<Style> style, List<IElement> elements)
|
||||||
{
|
{
|
||||||
|
_input = inputSystem;
|
||||||
_style = style;
|
_style = style;
|
||||||
_elements = elements;
|
_elements = elements;
|
||||||
}
|
}
|
||||||
@@ -26,6 +34,8 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
|
|
||||||
public void Update(double deltaTime)
|
public void Update(double deltaTime)
|
||||||
{
|
{
|
||||||
|
HandleInput();
|
||||||
|
|
||||||
foreach (var element in _elements)
|
foreach (var element in _elements)
|
||||||
{
|
{
|
||||||
if (element is not IUpdatableElement updatable) continue;
|
if (element is not IUpdatableElement updatable) continue;
|
||||||
@@ -58,6 +68,79 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleInput()
|
||||||
|
{
|
||||||
|
int charPressed = _input.GetCharPressed();
|
||||||
|
Vector2 mousePos = _input.GetMousePosition();
|
||||||
|
|
||||||
|
Vector2 currentMousePosition = _input.GetMousePosition();
|
||||||
|
|
||||||
|
foreach (var (actionName, mappings) in InputSystem.InputMappings)
|
||||||
|
{
|
||||||
|
foreach (var action in mappings)
|
||||||
|
{
|
||||||
|
if (action.IsPressed(_input))
|
||||||
|
{
|
||||||
|
// TODO: specify which mouse button is used in the context.
|
||||||
|
var context = new UIInputContext(action, mousePos, actionName, charPressed)
|
||||||
|
{
|
||||||
|
MouseDown = _input.IsMouseButtonDown(MouseButton.Left),
|
||||||
|
MouseReleased = _input.IsMouseButtonReleased(MouseButton.Left),
|
||||||
|
MousePressed = _input.IsMouseButtonReleased(MouseButton.Left),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (PropagateInput(_elements, context))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charPressed != 0)
|
||||||
|
{
|
||||||
|
var context = new UIInputContext(new KeyInputAction(KeyboardKey.Null), mousePos, "", charPressed);
|
||||||
|
PropagateInput(_elements, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (currentMousePosition != _lastMousePosition)
|
||||||
|
{
|
||||||
|
// TODO: specify which mouse button is used in the context.
|
||||||
|
var context = new UIInputContext(new MouseInputAction(MouseButton.Left), mousePos, "", charPressed)
|
||||||
|
{
|
||||||
|
MouseDown = _input.IsMouseButtonDown(MouseButton.Left),
|
||||||
|
MouseReleased = _input.IsMouseButtonReleased(MouseButton.Left),
|
||||||
|
MousePressed = _input.IsMouseButtonReleased(MouseButton.Left),
|
||||||
|
};
|
||||||
|
PropagateInput(_elements, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool PropagateInput(List<IElement> elements, UIInputContext context)
|
||||||
|
{
|
||||||
|
for (int i = elements.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var element = elements[i];
|
||||||
|
|
||||||
|
if (element is IInputElement inputElement && !inputElement.IgnoreInput)
|
||||||
|
{
|
||||||
|
inputElement.Input(context);
|
||||||
|
_input.SetAsHandled();
|
||||||
|
// return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element is IParentableElement parent)
|
||||||
|
{
|
||||||
|
if (PropagateInput(parent.Children.ToList(), context))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private ResourceRef<Style> _style;
|
private ResourceRef<Style> _style;
|
||||||
private List<IElement> _elements = new();
|
private List<IElement> _elements = new();
|
||||||
|
private InputSystem _input;
|
||||||
|
|
||||||
|
private Vector2 _lastMousePosition = Vector2.Zero;
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ public class Button : Widget
|
|||||||
renderer.DrawRectangle(new Vector2(MinimumRect.Width, MinimumRect.Height), new Color(0.25f, 0.25f, 0.25f));
|
renderer.DrawRectangle(new Vector2(MinimumRect.Width, MinimumRect.Height), new Color(0.25f, 0.25f, 0.25f));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Input(IInputAction action)
|
protected override void OnInput(UIInputContext action)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class Label : Widget
|
|||||||
_fontOverride = fontOverride;
|
_fontOverride = fontOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Input(IInputAction action)
|
protected override void OnInput(UIInputContext action)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,66 @@ public class RectangleWidget : Widget
|
|||||||
{
|
{
|
||||||
public override Rect MinimumRect { get; }
|
public override Rect MinimumRect { get; }
|
||||||
public Color Color { get; set; } = Color.White;
|
public Color Color { get; set; } = Color.White;
|
||||||
public RectangleWidget(Rect minimumRect, Color color)
|
public RectangleWidget(Rect minimumRect, Color color) : base()
|
||||||
{
|
{
|
||||||
MinimumRect = minimumRect;
|
MinimumRect = minimumRect;
|
||||||
Color = color;
|
Color = color;
|
||||||
}
|
|
||||||
|
|
||||||
public override void Input(IInputAction action)
|
_defaultColor = color;
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
_defaultSize = Size;
|
||||||
|
|
||||||
|
_hoverColor = color.Lightened(0.25f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Render(RenderSystem renderer, Style style)
|
public override void Render(RenderSystem renderer, Style style)
|
||||||
{
|
{
|
||||||
renderer.SetTransform(Position, Vector2.Zero);
|
renderer.SetTransform(Position, Vector2.Zero);
|
||||||
renderer.DrawRectangle(new Vector2(MinimumRect.Width, MinimumRect.Height), Color);
|
renderer.DrawRectangle(new Vector2(Size.Width, Size.Height), Color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnInput(UIInputContext inputContext)
|
||||||
|
{
|
||||||
|
if (_defaultSize == Rect.Zero)
|
||||||
|
{
|
||||||
|
_defaultSize = Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_downSize == Rect.Zero)
|
||||||
|
{
|
||||||
|
_downSize = new Rect(Size.Width * 0.75f, Size.Height * 0.75f);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mouseInside = ContainsPoint(inputContext.MousePosition);
|
||||||
|
|
||||||
|
if (mouseInside)
|
||||||
|
{
|
||||||
|
Color = _hoverColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Color = _defaultColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseInside && inputContext.MouseDown)
|
||||||
|
{
|
||||||
|
Size = _downSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseInside && inputContext.MouseReleased)
|
||||||
|
{
|
||||||
|
Size = _defaultSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseInside && inputContext.MousePressed)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Hello, I was clicked!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color _defaultColor;
|
||||||
|
private Color _hoverColor;
|
||||||
|
|
||||||
|
private Rect _defaultSize = Rect.Zero;
|
||||||
|
private Rect _downSize = Rect.Zero;
|
||||||
}
|
}
|
||||||
@@ -16,13 +16,19 @@ public abstract class Widget : IElement, IRenderableElement, IInputElement, IRes
|
|||||||
|
|
||||||
public Widget()
|
public Widget()
|
||||||
{
|
{
|
||||||
|
MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Widget(Rect size)
|
||||||
|
{
|
||||||
|
Size = size;
|
||||||
|
MarkDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Widget(Vector2 position)
|
public Widget(Vector2 position)
|
||||||
{
|
{
|
||||||
Position = position;
|
Position = position;
|
||||||
Size = MinimumRect;
|
MarkDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// </inheritdoc>
|
/// </inheritdoc>
|
||||||
@@ -36,11 +42,17 @@ public abstract class Widget : IElement, IRenderableElement, IInputElement, IRes
|
|||||||
/// <param name="renderer"></param>
|
/// <param name="renderer"></param>
|
||||||
public abstract void Render(RenderSystem renderer, Style style);
|
public abstract void Render(RenderSystem renderer, Style style);
|
||||||
|
|
||||||
|
public void Input(UIInputContext context)
|
||||||
|
{
|
||||||
|
if (context.Handled) return;
|
||||||
|
OnInput(context);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when this widget receives input.
|
/// Called when this widget receives input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">An input action this widget received.</param>
|
/// <param name="action">An input action this widget received.</param>
|
||||||
public abstract void Input(IInputAction action);
|
protected abstract void OnInput(UIInputContext action);
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
@@ -61,5 +73,18 @@ public abstract class Widget : IElement, IRenderableElement, IInputElement, IRes
|
|||||||
renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
|
renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if this <see cref="Widget"/> contains a point within its confines.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pointPosition">A global position of the point.</param>
|
||||||
|
/// <returns>True if the point is inside the widget; otherwise, false.</returns>
|
||||||
|
public bool ContainsPoint(Vector2 pointPosition)
|
||||||
|
{
|
||||||
|
return pointPosition.X >= Position.X &&
|
||||||
|
pointPosition.Y >= Position.Y &&
|
||||||
|
pointPosition.X <= Position.X + Size.Width &&
|
||||||
|
pointPosition.Y <= Position.Y + Size.Height;
|
||||||
|
}
|
||||||
|
|
||||||
private bool _isDirty = true;
|
private bool _isDirty = true;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user