953bb985ad
Programmatic Theme builder + reusable popover and stepper widgets, ported from CharacterCreator.zip's :root design tokens. Kitchen-sink scene exercises every primitive for visual eyeballing. CodexPalette.cs: Color tokens lifted verbatim from the React prototype's `:root` block (--bg, --ink, --gild, --seal, etc.). Variable names mirror the CSS so the audit trail stays readable. Spacing locked at the prototype's normal density (--gap=24, --pad=28, --radius=2). Scope cut: only the Dark theme ships. The React prototype designed Parchment, Dark, and Blood as switchable variations — user direction during M5 is that only Dark (leather + candlelight) is wanted for this game. Parchment/Blood code dropped, plan doc updated to match (§1 goal #5, §4.5 UI map, §5 M5 scope, §10 resolved decisions #4). No runtime theme switcher. CodexTheme.Build(): Programmatically constructs a Godot Theme from CodexPalette.Dark plus CodexSpacing/CodexType tokens. Configures Panel, Card, CodexPopover styleboxes; Label variations for H1..H4, CodexTitle, Eyebrow, Meta, ValidationOk/Error, CardName/Body/Meta, StepperNum/ Name; Button + PrimaryButton + GhostButton variants; LineEdit, CheckBox, scrollbar styling. Fonts: looks for CormorantGaramond / CrimsonPro / JetBrainsMono TTFs in res://Fonts/ (or Content/Fonts/) and graceful-falls-back to Godot defaults if missing. M5 ships with no fonts in repo; user can drop them in later for typography parity with the React prototype. CodexPopover.cs: Hoverable text trigger + floating PanelContainer, mirrors src/trait-hint.jsx. Viewport-clamps horizontally and vertically; flips above the trigger if there's no room below; 80 ms grace period when moving cursor from trigger to popover. Detriment variant uses the seal-coloured stylebox. Future TraitName / SkillChip / BonusPill widgets layer className differences on top. CodexStepper.cs: Roman-numeral horizontal stepper with Pending / Active / Complete / Locked states. Active step gets a 2-px gild underline, Complete shows a ✓ in seal-red, Locked shows ✕ + 0.45 modulate. Emits StepClicked(int) for non-locked rows. M5 is decorative — M6 wires the signal to the character-creation state machine. KitchenSink.cs + Main.cs --codex-test: Verification scene rendering every primitive (header, stepper, buttons, inputs, cards, trait popovers). Clicks log to console. Fonts default to Godot's Noto Sans until res://Fonts/ is populated. Closes M5 of theriapolis-rpg-implementation-plan-godot-port.md. Next: M6 (title + character creation). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
230 lines
8.0 KiB
C#
230 lines
8.0 KiB
C#
using Godot;
|
|
using Theriapolis.GodotHost.UI.Widgets;
|
|
|
|
namespace Theriapolis.GodotHost.UI;
|
|
|
|
/// <summary>
|
|
/// M5 kitchen-sink. Renders every primitive from the codex design system
|
|
/// (single Dark theme — Parchment and Blood dropped from scope per port
|
|
/// plan §10) so we can eyeball parity against screenshots of the React
|
|
/// prototype. Launch via:
|
|
/// godot --path Theriapolis.Godot --codex-test
|
|
/// </summary>
|
|
public partial class KitchenSink : Control
|
|
{
|
|
private Panel _root = null!;
|
|
private CodexStepper _stepper = null!;
|
|
|
|
public override void _Ready()
|
|
{
|
|
AnchorRight = 1f;
|
|
AnchorBottom = 1f;
|
|
OffsetRight = 0;
|
|
OffsetBottom = 0;
|
|
|
|
_root = new Panel { ThemeTypeVariation = "Panel" };
|
|
_root.AnchorRight = 1f;
|
|
_root.AnchorBottom = 1f;
|
|
AddChild(_root);
|
|
|
|
ApplyTheme();
|
|
BuildContent();
|
|
}
|
|
|
|
private void ApplyTheme()
|
|
{
|
|
_root.Theme = CodexTheme.Build();
|
|
|
|
// The root Panel's stylebox covers its rect; fill the *outer*
|
|
// viewport (which Godot draws with its default clear colour) so
|
|
// screenshots don't show engine grey along any edge.
|
|
RenderingServer.SetDefaultClearColor(CodexPalette.Dark.BgDeep);
|
|
}
|
|
|
|
private void BuildContent()
|
|
{
|
|
var margin = new MarginContainer();
|
|
margin.AddThemeConstantOverride("margin_left", 36);
|
|
margin.AddThemeConstantOverride("margin_right", 36);
|
|
margin.AddThemeConstantOverride("margin_top", 28);
|
|
margin.AddThemeConstantOverride("margin_bottom", 28);
|
|
margin.AnchorRight = 1f;
|
|
margin.AnchorBottom = 1f;
|
|
_root.AddChild(margin);
|
|
|
|
var scroll = new ScrollContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
|
margin.AddChild(scroll);
|
|
|
|
var col = new VBoxContainer
|
|
{
|
|
SizeFlagsHorizontal = SizeFlags.ExpandFill,
|
|
};
|
|
col.AddThemeConstantOverride("separation", 24);
|
|
scroll.AddChild(col);
|
|
|
|
BuildHeader(col);
|
|
BuildStepperSection(col);
|
|
BuildButtonsSection(col);
|
|
BuildInputsSection(col);
|
|
BuildCardSection(col);
|
|
BuildPopoverSection(col);
|
|
}
|
|
|
|
private void BuildHeader(VBoxContainer col)
|
|
{
|
|
var h = new HBoxContainer();
|
|
col.AddChild(h);
|
|
|
|
var titleCol = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
|
h.AddChild(titleCol);
|
|
|
|
titleCol.AddChild(new Label { Text = "THERIAPOLIS · CODEX OF BECOMING", ThemeTypeVariation = "CodexTitle" });
|
|
titleCol.AddChild(new Label { Text = "Kitchen sink — every primitive, three themes", ThemeTypeVariation = "Eyebrow" });
|
|
|
|
var meta = new Label { Text = "M5 · DESIGN AUDIT", ThemeTypeVariation = "Meta" };
|
|
h.AddChild(meta);
|
|
}
|
|
|
|
private void BuildStepperSection(VBoxContainer col)
|
|
{
|
|
var section = MakeSection(col, "STEPPER");
|
|
_stepper = new CodexStepper();
|
|
_stepper.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
|
section.AddChild(_stepper);
|
|
|
|
_stepper.SetSteps(
|
|
new[] { "Clade", "Species", "Calling", "Subclass", "History", "Abilities", "Skills", "Sign" },
|
|
new[]
|
|
{
|
|
CodexStepper.StepState.Complete,
|
|
CodexStepper.StepState.Complete,
|
|
CodexStepper.StepState.Complete,
|
|
CodexStepper.StepState.Active,
|
|
CodexStepper.StepState.Pending,
|
|
CodexStepper.StepState.Locked,
|
|
CodexStepper.StepState.Locked,
|
|
CodexStepper.StepState.Locked,
|
|
});
|
|
|
|
_stepper.StepClicked += (int idx) => GD.Print($"[kitchen-sink] Stepper clicked index={idx}");
|
|
}
|
|
|
|
private void BuildButtonsSection(VBoxContainer col)
|
|
{
|
|
var section = MakeSection(col, "BUTTONS");
|
|
var row = new HBoxContainer();
|
|
row.AddThemeConstantOverride("separation", 12);
|
|
section.AddChild(row);
|
|
|
|
row.AddChild(new Button { Text = "Default" });
|
|
row.AddChild(new Button { Text = "Primary", ThemeTypeVariation = "PrimaryButton" });
|
|
row.AddChild(new Button { Text = "Ghost", ThemeTypeVariation = "GhostButton" });
|
|
var disabled = new Button { Text = "Disabled", Disabled = true };
|
|
row.AddChild(disabled);
|
|
}
|
|
|
|
private void BuildInputsSection(VBoxContainer col)
|
|
{
|
|
var section = MakeSection(col, "INPUTS");
|
|
var row = new HBoxContainer();
|
|
row.AddThemeConstantOverride("separation", 12);
|
|
section.AddChild(row);
|
|
|
|
var le = new LineEdit
|
|
{
|
|
PlaceholderText = "Enter your name...",
|
|
CustomMinimumSize = new Vector2(280, 0),
|
|
};
|
|
row.AddChild(le);
|
|
|
|
row.AddChild(new CheckBox { Text = "Toggle option" });
|
|
}
|
|
|
|
private void BuildCardSection(VBoxContainer col)
|
|
{
|
|
var section = MakeSection(col, "CARDS");
|
|
var grid = new HBoxContainer();
|
|
grid.AddThemeConstantOverride("separation", CodexSpacing.Gap);
|
|
section.AddChild(grid);
|
|
|
|
AddCard(grid, "Canidae", "Predator · medium", "Strong-jawed pack hunters of the inland forest.", selected: false);
|
|
AddCard(grid, "Felidae", "Predator · medium", "Solitary stalkers, claw-tipped and sharp of ear.", selected: true);
|
|
AddCard(grid, "Mustelidae", "Predator · small", "Burrowers and threadkillers of the river edges.", selected: false);
|
|
}
|
|
|
|
private void AddCard(HBoxContainer parent, string name, string meta, string body, bool selected)
|
|
{
|
|
var panel = new PanelContainer
|
|
{
|
|
ThemeTypeVariation = "Card",
|
|
CustomMinimumSize = new Vector2(240, 0),
|
|
};
|
|
if (selected && _root.Theme is not null && _root.Theme.HasStylebox("panel_selected", "Card"))
|
|
{
|
|
var box = _root.Theme.GetStylebox("panel_selected", "Card");
|
|
panel.AddThemeStyleboxOverride("panel", box);
|
|
}
|
|
var v = new VBoxContainer();
|
|
v.AddThemeConstantOverride("separation", 6);
|
|
panel.AddChild(v);
|
|
v.AddChild(new Label { Text = name, ThemeTypeVariation = "CardName" });
|
|
v.AddChild(new Label { Text = meta.ToUpperInvariant(), ThemeTypeVariation = "CardMeta" });
|
|
v.AddChild(new Label
|
|
{
|
|
Text = body,
|
|
ThemeTypeVariation = "CardBody",
|
|
AutowrapMode = TextServer.AutowrapMode.WordSmart,
|
|
CustomMinimumSize = new Vector2(220, 0),
|
|
});
|
|
parent.AddChild(panel);
|
|
}
|
|
|
|
private void BuildPopoverSection(VBoxContainer col)
|
|
{
|
|
var section = MakeSection(col, "TRAIT POPOVERS · HOVER");
|
|
var row = new HBoxContainer();
|
|
row.AddThemeConstantOverride("separation", 18);
|
|
section.AddChild(row);
|
|
|
|
row.AddChild(new CodexPopover
|
|
{
|
|
TriggerText = "Pack Tactics",
|
|
Title = "Pack Tactics",
|
|
Tag = "active",
|
|
Description = "Once per turn, when an ally is adjacent to your target, gain advantage on the attack.",
|
|
});
|
|
row.AddChild(new CodexPopover
|
|
{
|
|
TriggerText = "Glass Bones",
|
|
Title = "Glass Bones",
|
|
Description = "Critical hits against you deal an extra 1d6 damage.",
|
|
Detriment = true,
|
|
});
|
|
row.AddChild(new CodexPopover
|
|
{
|
|
TriggerText = "Athletics",
|
|
Title = "Athletics",
|
|
Tag = "STR",
|
|
Description = "Climbs, jumps, swims, and brute physical contests.",
|
|
});
|
|
row.AddChild(new CodexPopover
|
|
{
|
|
TriggerText = "+2",
|
|
Title = "STR modifier",
|
|
Description = "+1 from Canidae · +1 from Wolf",
|
|
});
|
|
}
|
|
|
|
private static VBoxContainer MakeSection(VBoxContainer col, string title)
|
|
{
|
|
var box = new VBoxContainer();
|
|
box.AddThemeConstantOverride("separation", 8);
|
|
col.AddChild(box);
|
|
box.AddChild(new Label { Text = title, ThemeTypeVariation = "Eyebrow" });
|
|
var inner = new VBoxContainer();
|
|
inner.AddThemeConstantOverride("separation", 8);
|
|
box.AddChild(inner);
|
|
return inner;
|
|
}
|
|
}
|