using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace Theriapolis.Game.CodexUI.Core;
///
/// Per-frame input snapshot used by every CodexUI widget.
/// is called once at the top of ; widgets
/// read the resulting edge-detected event flags during their own Update pass.
///
/// Text input piggybacks on MonoGame's
/// event — the screen subscribes during Initialize and pushes characters
/// into . Backspace, Enter and arrow keys
/// route through instead.
///
///
/// Mouse clipping: a parent widget can set before
/// updating a region of the tree, which causes
/// to report off-screen for any cursor outside the clip rect. Children that
/// rely on Bounds.Contains(input.MousePosition) to detect hover/click
/// then ignore events that visually land in some other layer (e.g. a
/// stepper bar painted over a scrolled card). Without this, both the
/// clipped widget and the chrome above it would handle the same click.
///
///
public sealed class CodexInput
{
public Point MousePosition { get; private set; }
public Point PreviousMousePosition { get; private set; }
public Point MouseDelta { get; private set; }
public bool LeftButtonDown { get; private set; }
public bool LeftJustPressed { get; private set; }
public bool LeftJustReleased { get; private set; }
public bool RightJustPressed { get; private set; }
public int ScrollDelta { get; private set; }
private MouseState _prev;
private KeyboardState _prevKb;
private KeyboardState _curKb;
private Rectangle? _mouseClip;
private Point _rawMouse;
/// Characters typed this frame (collected from the window's TextInput event).
public string TextEnteredThisFrame { get; private set; } = "";
private System.Text.StringBuilder _textBuf = new();
public void OnTextInput(char c) => _textBuf.Append(c);
public void Tick()
{
var m = Mouse.GetState();
var kb = Keyboard.GetState();
PreviousMousePosition = _rawMouse;
_rawMouse = new Point(m.X, m.Y);
MouseDelta = _rawMouse - PreviousMousePosition;
ScrollDelta = m.ScrollWheelValue - _prev.ScrollWheelValue;
bool prevLeft = _prev.LeftButton == ButtonState.Pressed;
bool curLeft = m.LeftButton == ButtonState.Pressed;
LeftButtonDown = curLeft;
LeftJustPressed = !prevLeft && curLeft;
LeftJustReleased = prevLeft && !curLeft;
bool prevRight = _prev.RightButton == ButtonState.Pressed;
bool curRight = m.RightButton == ButtonState.Pressed;
RightJustPressed = !prevRight && curRight;
_prev = m;
_prevKb = _curKb;
_curKb = kb;
TextEnteredThisFrame = _textBuf.ToString();
_textBuf.Clear();
ApplyClip();
}
///
/// Restrict reads to .
/// Outside the rect the position is reported far off-screen so widgets
/// that hit-test by Bounds.Contains stop registering hover/click.
/// Pair with after the clipped subtree's
/// Update completes.
///
public void SetMouseClip(Rectangle clipRect)
{
_mouseClip = clipRect;
ApplyClip();
}
public void ClearMouseClip()
{
_mouseClip = null;
ApplyClip();
}
/// Read the current clip rectangle, if any. Used by widgets that
/// nest a tighter clip and need to restore the outer one after their
/// child subtree has finished updating.
public Rectangle? GetMouseClip() => _mouseClip;
private void ApplyClip()
{
if (_mouseClip is Rectangle r && !r.Contains(_rawMouse))
MousePosition = new Point(-99999, -99999);
else
MousePosition = _rawMouse;
}
public bool KeyDown(Keys k) => _curKb.IsKeyDown(k);
public bool KeyJustPressed(Keys k) => _curKb.IsKeyDown(k) && !_prevKb.IsKeyDown(k);
public bool KeyJustReleased(Keys k) => !_curKb.IsKeyDown(k) && _prevKb.IsKeyDown(k);
}