b451f83174
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>
112 lines
4.1 KiB
C#
112 lines
4.1 KiB
C#
using FontStashSharp;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using Theriapolis.Game.CodexUI.Core;
|
|
|
|
namespace Theriapolis.Game.CodexUI.Widgets;
|
|
|
|
public enum CodexButtonVariant { Primary, Ghost, Small }
|
|
|
|
/// <summary>
|
|
/// Codex-styled push button. The three variants match the React design's
|
|
/// <c>.btn.primary / .btn.ghost / .btn.small</c>:
|
|
/// - Primary: gilded fill on parchment, used for Confirm + Next-style actions.
|
|
/// - Ghost: ink border, transparent fill, used for Back + secondary actions.
|
|
/// - Small: smaller padding/font, used inline (Reroll / Auto-assign / Clear).
|
|
/// </summary>
|
|
public sealed class CodexButton : CodexWidget
|
|
{
|
|
public string Text { get; set; }
|
|
public CodexButtonVariant Variant { get; set; }
|
|
public System.Action? OnClick { get; set; }
|
|
public int? FixedWidth { get; set; }
|
|
|
|
private readonly CodexAtlas _atlas;
|
|
private readonly SpriteFontBase _font;
|
|
private bool _hovered;
|
|
private bool _pressed;
|
|
|
|
public CodexButton(string text, CodexAtlas atlas, CodexButtonVariant variant = CodexButtonVariant.Ghost,
|
|
System.Action? onClick = null, int? fixedWidth = null)
|
|
{
|
|
Text = text;
|
|
_atlas = atlas;
|
|
Variant = variant;
|
|
OnClick = onClick;
|
|
FixedWidth = fixedWidth;
|
|
_font = variant == CodexButtonVariant.Small ? CodexFonts.MonoTag : CodexFonts.DisplaySmall;
|
|
}
|
|
|
|
protected override Point MeasureCore(Point available)
|
|
{
|
|
var s = _font.MeasureString(Text);
|
|
int padX = Variant == CodexButtonVariant.Small ? 12 : 22;
|
|
int padY = Variant == CodexButtonVariant.Small ? 6 : 10;
|
|
int w = FixedWidth ?? ((int)s.X + padX * 2);
|
|
int h = (int)System.MathF.Ceiling(_font.LineHeight) + padY * 2;
|
|
return new Point(System.Math.Min(w, available.X), h);
|
|
}
|
|
|
|
protected override void ArrangeCore(Rectangle bounds) { }
|
|
|
|
public override void Update(GameTime gt, CodexInput input)
|
|
{
|
|
bool wasHovered = _hovered;
|
|
_hovered = ContainsPoint(input.MousePosition);
|
|
if (_hovered && input.LeftJustPressed) _pressed = true;
|
|
if (input.LeftJustReleased)
|
|
{
|
|
if (_pressed && _hovered && Enabled) OnClick?.Invoke();
|
|
_pressed = false;
|
|
}
|
|
}
|
|
|
|
public override void Draw(SpriteBatch sb, GameTime gt)
|
|
{
|
|
Color fill, border, textColor;
|
|
switch (Variant)
|
|
{
|
|
case CodexButtonVariant.Primary:
|
|
fill = _hovered ? CodexColors.Seal2 : CodexColors.Seal;
|
|
border = CodexColors.Seal2;
|
|
textColor = CodexColors.Bg;
|
|
break;
|
|
case CodexButtonVariant.Ghost:
|
|
fill = _hovered ? CodexColors.Ink : CodexColors.Bg;
|
|
border = CodexColors.Ink;
|
|
textColor = _hovered ? CodexColors.Bg : CodexColors.Ink;
|
|
break;
|
|
default:
|
|
fill = _hovered ? CodexColors.Bg2 : CodexColors.Bg;
|
|
border = CodexColors.Rule;
|
|
textColor = CodexColors.InkSoft;
|
|
break;
|
|
}
|
|
|
|
if (!Enabled)
|
|
{
|
|
// 40% opacity per .btn[disabled]
|
|
fill = new Color(fill.R, fill.G, fill.B, (byte)(fill.A * 0.4f));
|
|
textColor = new Color(textColor.R, textColor.G, textColor.B, (byte)(textColor.A * 0.6f));
|
|
}
|
|
|
|
// Body fill
|
|
sb.Draw(_atlas.Pixel, Bounds, fill);
|
|
// 1-px border outline
|
|
DrawBorder(sb, Bounds, border, 1);
|
|
|
|
var s = _font.MeasureString(Text);
|
|
float tx = Bounds.X + (Bounds.Width - s.X) / 2f;
|
|
float ty = Bounds.Y + (Bounds.Height - _font.LineHeight) / 2f;
|
|
_font.DrawText(sb, Text, new Vector2(tx, ty), textColor);
|
|
}
|
|
|
|
private void DrawBorder(SpriteBatch sb, Rectangle r, Color c, int t)
|
|
{
|
|
sb.Draw(_atlas.Pixel, new Rectangle(r.X, r.Y, r.Width, t), c);
|
|
sb.Draw(_atlas.Pixel, new Rectangle(r.X, r.Bottom - t, r.Width, t), c);
|
|
sb.Draw(_atlas.Pixel, new Rectangle(r.X, r.Y, t, r.Height), c);
|
|
sb.Draw(_atlas.Pixel, new Rectangle(r.Right - t, r.Y, t, r.Height), c);
|
|
}
|
|
}
|