Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user