Backspace and direction key echoing in InputField.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Voile.Input;
|
||||
using Voile.Rendering;
|
||||
|
||||
namespace Voile.UI;
|
||||
@@ -18,6 +19,19 @@ public interface IElement
|
||||
public Rect Size { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a UI element that requires a frame-independent update tick loop.
|
||||
/// </summary>
|
||||
public interface ITickableElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Excutes unconditionally on every frame engine loop step.
|
||||
/// </summary>
|
||||
/// <param name="dt">Elapsed delta frame time in seconds.</param>
|
||||
/// <param name="input">InputSystem that this tickable element should use to poll input events.</param>
|
||||
void Tick(float dt, InputSystem input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a UI element that can contain child elements.
|
||||
/// </summary>
|
||||
|
||||
@@ -55,6 +55,8 @@ public class UIInputContext
|
||||
public bool IsUp => ActionName == "up";
|
||||
public bool IsDown => ActionName == "down";
|
||||
|
||||
public float DeltaTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if control key is pressed.
|
||||
/// </summary>
|
||||
|
||||
@@ -39,14 +39,13 @@ public class UISystem : IUpdatableSystem, IRenderableSystem, IReloadableSystem
|
||||
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
// HandleInput();
|
||||
float dt = (float)deltaTime;
|
||||
PropagateTick(_elements, dt);
|
||||
HandleInput((float)deltaTime);
|
||||
}
|
||||
|
||||
public void Render(RenderSystem renderer)
|
||||
{
|
||||
// Update elements each time UI system is rendered.
|
||||
HandleInput();
|
||||
|
||||
foreach (var element in _elements)
|
||||
{
|
||||
if (element is not IUpdatableElement updatable) continue;
|
||||
@@ -104,54 +103,90 @@ public class UISystem : IUpdatableSystem, IRenderableSystem, IReloadableSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleInput()
|
||||
private void HandleInput(float deltaTime)
|
||||
{
|
||||
int charPressed = _input.GetCharPressed();
|
||||
Vector2 mousePos = _input.GetMousePosition();
|
||||
|
||||
Vector2 currentMousePosition = _input.GetMousePosition();
|
||||
bool inputHandled = false;
|
||||
|
||||
// 1. Process Actions (Pressed or Held)
|
||||
foreach (var (actionName, mappings) in InputSystem.InputMappings)
|
||||
{
|
||||
foreach (var action in mappings)
|
||||
{
|
||||
if (action.IsPressed(_input))
|
||||
bool isPressed = action.IsPressed(_input);
|
||||
bool isDown = action.IsDown(_input);
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
// 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.IsMouseButtonPressed(MouseButton.Left),
|
||||
DeltaTime = deltaTime
|
||||
};
|
||||
|
||||
if (PropagateInput(_elements, context))
|
||||
{
|
||||
inputHandled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inputHandled) break;
|
||||
}
|
||||
|
||||
if (inputHandled)
|
||||
{
|
||||
_lastMousePosition = currentMousePosition;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (charPressed != 0)
|
||||
{
|
||||
var context = new UIInputContext(new KeyInputAction(KeyboardKey.Null), mousePos, "", charPressed);
|
||||
PropagateInput(_elements, context);
|
||||
var context = new UIInputContext(new KeyInputAction(KeyboardKey.Null), mousePos, "", charPressed)
|
||||
{
|
||||
DeltaTime = deltaTime
|
||||
};
|
||||
if (PropagateInput(_elements, context))
|
||||
{
|
||||
_lastMousePosition = currentMousePosition;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (currentMousePosition != _lastMousePosition)
|
||||
{
|
||||
// TODO: specify which mouse button is used in the context.
|
||||
var context = new UIInputContext(new MouseInputAction(MouseButton.Left), mousePos, "", charPressed)
|
||||
var frameContext = new UIInputContext(new MouseInputAction(MouseButton.Left), mousePos, "", charPressed)
|
||||
{
|
||||
MouseDown = _input.IsMouseButtonDown(MouseButton.Left),
|
||||
MouseReleased = _input.IsMouseButtonReleased(MouseButton.Left),
|
||||
MousePressed = _input.IsMouseButtonPressed(MouseButton.Left),
|
||||
DeltaTime = deltaTime
|
||||
};
|
||||
|
||||
PropagateInput(_elements, context);
|
||||
PropagateInput(_elements, frameContext);
|
||||
|
||||
return;
|
||||
_lastMousePosition = currentMousePosition;
|
||||
}
|
||||
|
||||
private void PropagateTick(List<UIElement> elements, float dt)
|
||||
{
|
||||
for (int i = 0; i < elements.Count; i++)
|
||||
{
|
||||
var element = elements[i];
|
||||
|
||||
if (!element.Visible) continue;
|
||||
|
||||
if (element is ITickableElement tickable)
|
||||
{
|
||||
tickable.Tick(dt, _input);
|
||||
}
|
||||
|
||||
if (element is IParentableElement parentable)
|
||||
{
|
||||
PropagateTick(parentable.Children.ToList(), dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Numerics;
|
||||
using Voile.Input;
|
||||
using Voile.Rendering;
|
||||
using Voile.Resources;
|
||||
|
||||
namespace Voile.UI.Widgets;
|
||||
|
||||
public class InputField : Widget
|
||||
public class InputField : Widget, ITickableElement
|
||||
{
|
||||
public override Rect MinimumSize => _placeholderSize + _padding;
|
||||
public override string? StyleElementName => nameof(InputField);
|
||||
@@ -43,20 +44,30 @@ public class InputField : Widget
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Tick(float dt, InputSystem input)
|
||||
{
|
||||
if (!_isFocused)
|
||||
return;
|
||||
|
||||
HandleRepeatingActions(dt, input);
|
||||
}
|
||||
|
||||
protected override void OnInput(UIInputContext context)
|
||||
{
|
||||
HandleFocus(context);
|
||||
|
||||
if (!_isFocused)
|
||||
{
|
||||
_lastActiveAction = string.Empty;
|
||||
_repeatTimer = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
HandleTextInput(context);
|
||||
HandleBackspace(context);
|
||||
HandleCursorMovement(context);
|
||||
|
||||
ClampCursor();
|
||||
}
|
||||
|
||||
|
||||
private void HandleFocus(UIInputContext context)
|
||||
{
|
||||
if (!context.MousePressed)
|
||||
@@ -87,35 +98,74 @@ public class InputField : Widget
|
||||
context.SetHandled();
|
||||
}
|
||||
|
||||
private void HandleBackspace(UIInputContext context)
|
||||
private void HandleRepeatingActions(float dt, InputSystem input)
|
||||
{
|
||||
if (!context.IsBackspace)
|
||||
return;
|
||||
string currentAction = string.Empty;
|
||||
|
||||
if (_cursor <= 0 || _input.Length == 0)
|
||||
return;
|
||||
if (input.IsKeyboardKeyDown(KeyboardKey.Backspace)) currentAction = "backspace";
|
||||
else if (input.IsKeyboardKeyDown(KeyboardKey.Left)) currentAction = "left";
|
||||
else if (input.IsKeyboardKeyDown(KeyboardKey.Right)) currentAction = "right";
|
||||
|
||||
if (string.IsNullOrEmpty(currentAction))
|
||||
{
|
||||
_lastActiveAction = string.Empty;
|
||||
_repeatTimer = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
bool shouldExecute = false;
|
||||
|
||||
if (_lastActiveAction != currentAction)
|
||||
{
|
||||
shouldExecute = true;
|
||||
_repeatTimer = 0.0f;
|
||||
_lastActiveAction = currentAction;
|
||||
}
|
||||
else
|
||||
{
|
||||
_repeatTimer += dt;
|
||||
|
||||
if (_repeatTimer >= INITIAL_DELAY)
|
||||
{
|
||||
shouldExecute = true;
|
||||
_repeatTimer = INITIAL_DELAY - REPEAT_RATE;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldExecute)
|
||||
{
|
||||
ExecuteAction(currentAction);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteAction(string actionName)
|
||||
{
|
||||
switch (actionName)
|
||||
{
|
||||
case "backspace":
|
||||
if (_cursor > 0 && _input.Length > 0)
|
||||
{
|
||||
_input = _input.Remove(_cursor - 1, 1);
|
||||
_cursor--;
|
||||
MarkDirty();
|
||||
|
||||
context.SetHandled();
|
||||
}
|
||||
break;
|
||||
|
||||
private void HandleCursorMovement(UIInputContext context)
|
||||
{
|
||||
if (context.IsLeft && _cursor > 0)
|
||||
case "left":
|
||||
if (_cursor > 0)
|
||||
{
|
||||
_cursor--;
|
||||
MarkDirty();
|
||||
context.SetHandled();
|
||||
}
|
||||
break;
|
||||
|
||||
if (context.IsRight && _cursor < _input.Length)
|
||||
case "right":
|
||||
if (_cursor < _input.Length)
|
||||
{
|
||||
_cursor++;
|
||||
MarkDirty();
|
||||
context.SetHandled();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +265,12 @@ public class InputField : Widget
|
||||
return _suitableFont.Value.Measure(text).Width;
|
||||
}
|
||||
|
||||
private string _input = "";
|
||||
private float _repeatTimer = 0.0f;
|
||||
private string _lastActiveAction = string.Empty;
|
||||
private const float INITIAL_DELAY = 0.5f;
|
||||
private const float REPEAT_RATE = 0.05f;
|
||||
|
||||
private string _input = string.Empty;
|
||||
private bool _isFocused;
|
||||
|
||||
private int _cursor;
|
||||
|
||||
Reference in New Issue
Block a user