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:
Christopher Wiebe
2026-04-30 20:40:51 -07:00
commit b451f83174
525 changed files with 75786 additions and 0 deletions
+116
View File
@@ -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);
}