Files
Christopher Wiebe b451f83174 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>
2026-04-30 20:40:51 -07:00

110 lines
3.8 KiB
C#

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Theriapolis.Game.CodexUI.Drag;
using Theriapolis.Game.Screens;
namespace Theriapolis.Game.CodexUI.Core;
/// <summary>
/// Base class for screens implemented in CodexUI. Owns the input snapshot,
/// the root widget, the parchment background tiling, the drag-drop
/// controller, and the popover layer. Concrete screens override
/// <see cref="BuildRoot"/> to populate the widget tree.
///
/// Implements <see cref="IScreen"/> so the existing <c>ScreenManager</c>
/// can treat CodexUI screens identically to Myra-based ones.
/// </summary>
public abstract class CodexScreen : IScreen
{
protected Game1 Game = null!;
protected CodexInput Input { get; } = new();
protected CodexWidget? Root;
protected CodexAtlas Atlas { get; private set; } = null!;
protected DragDropController DragDrop { get; } = new();
/// <summary>The popover layer is painted last so floating panels stay above the page.</summary>
protected Widgets.CodexHoverPopover? Popover { get; set; }
private System.EventHandler<TextInputEventArgs>? _textInputHandler;
private bool _layoutDirty = true;
public virtual void Initialize(Game1 game)
{
Game = game;
Atlas = game.CodexAtlas;
if (CodexFonts.DisplayLarge is null)
throw new System.InvalidOperationException("CodexFonts.LoadAll must be called before any CodexScreen is initialized.");
_textInputHandler = (_, e) =>
{
// Filter out non-printable controls so backspace etc. routes via
// KeyJustPressed instead of pushing into the text buffer.
if (e.Character >= 32 && e.Character != 127) Input.OnTextInput(e.Character);
};
game.Window.TextInput += _textInputHandler;
Root = BuildRoot();
_layoutDirty = true;
}
public virtual void Deactivate()
{
if (_textInputHandler is not null) Game.Window.TextInput -= _textInputHandler;
}
public virtual void Reactivate() { _layoutDirty = true; }
/// <summary>Concrete screens build the entire widget tree here. Called once after Initialize.</summary>
protected abstract CodexWidget BuildRoot();
/// <summary>Force a re-measure on next frame (e.g. step changed).</summary>
public void InvalidateLayout()
{
Root = BuildRoot();
_layoutDirty = true;
}
public virtual void Update(GameTime gameTime)
{
Input.Tick();
if (Root is null) return;
if (_layoutDirty)
{
var vp = Game.GraphicsDevice.Viewport;
Root.Measure(new Point(vp.Width, vp.Height));
Root.Arrange(new Rectangle(0, 0, vp.Width, vp.Height));
_layoutDirty = false;
}
Root.Update(gameTime, Input);
Popover?.Update(gameTime, Input);
DragDrop.Update(gameTime, Input);
}
public virtual void Draw(GameTime gameTime, SpriteBatch sb)
{
Game.GraphicsDevice.Clear(CodexColors.BgDeep);
sb.Begin();
DrawBackground(sb);
Root?.Draw(sb, gameTime);
Popover?.Draw(sb, gameTime);
DragDrop.Draw(sb);
sb.End();
}
/// <summary>
/// Paint a flat <see cref="CodexColors.BgDeep"/> across the full viewport.
/// The body widget paints its own lighter <see cref="CodexColors.Bg"/>
/// fill on top so cards (which use the slightly-darker
/// <see cref="CodexColors.Bg2"/>) read clearly against their immediate
/// background. We don't tile a parchment-grain texture: a 256-px tile
/// produces visible seams, and the procedural radial gradients made
/// some areas brighter than the cards on top of them.
/// </summary>
private void DrawBackground(SpriteBatch sb)
{
var vp = Game.GraphicsDevice.Viewport;
sb.Draw(Atlas.Pixel, vp.Bounds, CodexColors.BgDeep);
}
}