117 lines
4.3 KiB
C#
117 lines
4.3 KiB
C#
|
|
using Microsoft.Xna.Framework;
|
||
|
|
using Microsoft.Xna.Framework.Input;
|
||
|
|
|
||
|
|
namespace Theriapolis.Game.CodexUI.Core;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Per-frame input snapshot used by every CodexUI widget. <see cref="Tick"/>
|
||
|
|
/// is called once at the top of <see cref="CodexScreen.Update"/>; widgets
|
||
|
|
/// read the resulting edge-detected event flags during their own Update pass.
|
||
|
|
///
|
||
|
|
/// Text input piggybacks on MonoGame's <see cref="GameWindow.TextInput"/>
|
||
|
|
/// event — the screen subscribes during Initialize and pushes characters
|
||
|
|
/// into <see cref="TextEnteredThisFrame"/>. Backspace, Enter and arrow keys
|
||
|
|
/// route through <see cref="KeyJustPressed"/> instead.
|
||
|
|
///
|
||
|
|
/// <para>
|
||
|
|
/// Mouse clipping: a parent widget can set <see cref="SetMouseClip"/> before
|
||
|
|
/// updating a region of the tree, which causes <see cref="MousePosition"/>
|
||
|
|
/// to report off-screen for any cursor outside the clip rect. Children that
|
||
|
|
/// rely on <c>Bounds.Contains(input.MousePosition)</c> 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.
|
||
|
|
/// </para>
|
||
|
|
/// </summary>
|
||
|
|
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;
|
||
|
|
|
||
|
|
/// <summary>Characters typed this frame (collected from the window's TextInput event).</summary>
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Restrict <see cref="MousePosition"/> reads to <paramref name="clipRect"/>.
|
||
|
|
/// Outside the rect the position is reported far off-screen so widgets
|
||
|
|
/// that hit-test by <c>Bounds.Contains</c> stop registering hover/click.
|
||
|
|
/// Pair with <see cref="ClearMouseClip"/> after the clipped subtree's
|
||
|
|
/// Update completes.
|
||
|
|
/// </summary>
|
||
|
|
public void SetMouseClip(Rectangle clipRect)
|
||
|
|
{
|
||
|
|
_mouseClip = clipRect;
|
||
|
|
ApplyClip();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void ClearMouseClip()
|
||
|
|
{
|
||
|
|
_mouseClip = null;
|
||
|
|
ApplyClip();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>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.</summary>
|
||
|
|
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);
|
||
|
|
}
|