From 2db442be7e7f42000b651c6dba25a23d1a4986b9 Mon Sep 17 00:00:00 2001 From: Christopher Wiebe Date: Sat, 9 May 2026 22:01:51 -0700 Subject: [PATCH] M6.20: TitleScreen entry point with parchment-themed button stack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default boot now lands on a centered title screen instead of the M0 hello-world label. Vertical button stack — New Character, Continue, Quit — over the codex parchment field, with the H1 codex title and a PORT / GODOT · M6.20 version chip in the bottom-right. Continue is disabled until user://character.json exists; clicking it prints a placeholder until the M7 play loop can pick the persisted state up. New Character swaps the title for the wizard scene under the Main parent. The wizard's existing "← Title" back-button on Step 0 now actually does something — TitleScreen wires its BackToTitle signal to a parent-side swap that reinstates the title screen when the player backs out. --wizard command-line flag still skips straight to the wizard for fast-path development. Layout uses SetAnchorsAndOffsetsPreset (LayoutPreset.FullRect) on the backing panel and CenterContainer — manual AnchorRight = 1 doesn't fill in code because Godot's anchor setters preserve visual position by adjusting offsets, leaving the control at 0×0. Co-Authored-By: Claude Opus 4.7 --- Theriapolis.Godot/Main.cs | 8 +- Theriapolis.Godot/Main.tscn | 14 --- Theriapolis.Godot/Scenes/TitleScreen.cs | 147 ++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 Theriapolis.Godot/Scenes/TitleScreen.cs diff --git a/Theriapolis.Godot/Main.cs b/Theriapolis.Godot/Main.cs index da97fff..ff9f30f 100644 --- a/Theriapolis.Godot/Main.cs +++ b/Theriapolis.Godot/Main.cs @@ -1,6 +1,7 @@ using Godot; using Theriapolis.GodotHost.Platform; using Theriapolis.GodotHost.Rendering; +using Theriapolis.GodotHost.Scenes; using Theriapolis.GodotHost.UI; namespace Theriapolis.GodotHost; @@ -128,7 +129,12 @@ public partial class Main : Control return; } - GD.Print("Theriapolis.Godot host ready (M0 hello-world)."); + // Default entry point — TitleScreen. M0's hello-world Label is no + // longer the boot UI; the title swaps itself for the wizard when + // "New Character" is clicked, or shuts the engine down on Quit. + foreach (Node child in GetChildren()) + child.QueueFree(); + AddChild(new TitleScreen()); } public override void _UnhandledInput(InputEvent @event) diff --git a/Theriapolis.Godot/Main.tscn b/Theriapolis.Godot/Main.tscn index 63067b1..39db321 100644 --- a/Theriapolis.Godot/Main.tscn +++ b/Theriapolis.Godot/Main.tscn @@ -7,17 +7,3 @@ anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 script = ExtResource("1_main") - -[node name="Label" type="Label" parent="."] -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -200.0 -offset_top = -16.0 -offset_right = 200.0 -offset_bottom = 16.0 -horizontal_alignment = 1 -vertical_alignment = 1 -text = "Theriapolis · Godot port · M0 · F11 toggles fullscreen" diff --git a/Theriapolis.Godot/Scenes/TitleScreen.cs b/Theriapolis.Godot/Scenes/TitleScreen.cs new file mode 100644 index 0000000..48c46d8 --- /dev/null +++ b/Theriapolis.Godot/Scenes/TitleScreen.cs @@ -0,0 +1,147 @@ +using Godot; +using Theriapolis.GodotHost.UI; + +namespace Theriapolis.GodotHost.Scenes; + +/// +/// Entry-point screen — vertical button stack on a parchment field with the +/// codex title and a version label. Per port-plan §M6, exists primarily to +/// validate the design system in a non-trivial composition before the player +/// reaches character creation. +/// +/// Button actions: +/// New Character — swap self for the Wizard scene under the Main parent +/// (siblings cleared so the wizard fills the viewport). +/// Continue — disabled until +/// exists; full pickup lands with the M7 play loop. +/// Quit — shut down the engine. +/// +public partial class TitleScreen : Control +{ + private const string VersionLabel = "PORT / GODOT · M6.20"; + private const string WizardScenePath = "res://Scenes/Wizard.tscn"; + + public override void _Ready() + { + SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + Theme = CodexTheme.Build(); + + // Backing panel so the parchment Bg fills the viewport (the Control + // itself paints nothing). Same pattern as Wizard.cs. + // Note: SetAnchorsAndOffsetsPreset is required (not just AnchorRight = + // 1) because Godot's anchor setters preserve visual position by + // adjusting offsets — manual anchor edits leave the control at 0×0. + var bg = new Panel { MouseFilter = MouseFilterEnum.Ignore }; + AddChild(bg); + bg.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + MoveChild(bg, 0); + + // Centered title + button stack column. + var center = new CenterContainer { MouseFilter = MouseFilterEnum.Ignore }; + AddChild(center); + center.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + + var col = new VBoxContainer { CustomMinimumSize = new Vector2(360, 0) }; + col.AddThemeConstantOverride("separation", 28); + center.AddChild(col); + + var titleBlock = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ShrinkCenter }; + titleBlock.AddThemeConstantOverride("separation", 4); + col.AddChild(titleBlock); + titleBlock.AddChild(new Label + { + Text = "THERIAPOLIS", + ThemeTypeVariation = "CodexTitle", + HorizontalAlignment = HorizontalAlignment.Center, + }); + titleBlock.AddChild(new Label + { + Text = "CODEX OF BECOMING", + ThemeTypeVariation = "Eyebrow", + HorizontalAlignment = HorizontalAlignment.Center, + }); + + var buttonStack = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; + buttonStack.AddThemeConstantOverride("separation", 12); + col.AddChild(buttonStack); + + var newBtn = MakeMenuButton("New Character", primary: true); + newBtn.Pressed += OnNewCharacter; + buttonStack.AddChild(newBtn); + + var continueBtn = MakeMenuButton("Continue", primary: false); + continueBtn.Disabled = !FileAccess.FileExists(CharacterAssembler.PersistedStatePath); + continueBtn.Pressed += OnContinue; + buttonStack.AddChild(continueBtn); + + var quitBtn = MakeMenuButton("Quit", primary: false); + quitBtn.Pressed += OnQuit; + buttonStack.AddChild(quitBtn); + + // Version chip in the bottom-right corner — small mono Eyebrow tag, + // sits over the parchment field at a comfortable margin. + var versionLabel = new Label + { + Text = VersionLabel, + ThemeTypeVariation = "Eyebrow", + AnchorLeft = 1, AnchorRight = 1, + AnchorTop = 1, AnchorBottom = 1, + OffsetLeft = -180, OffsetTop = -28, + OffsetRight = -16, OffsetBottom = -10, + HorizontalAlignment = HorizontalAlignment.Right, + }; + AddChild(versionLabel); + } + + private static Button MakeMenuButton(string text, bool primary) + { + var btn = new Button + { + Text = text, + FocusMode = FocusModeEnum.None, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + CustomMinimumSize = new Vector2(0, 44), + }; + if (primary) btn.ThemeTypeVariation = "PrimaryButton"; + return btn; + } + + private void OnNewCharacter() + { + var packed = ResourceLoader.Load(WizardScenePath); + if (packed is null) + { + GD.PushError($"[title] Failed to load {WizardScenePath}"); + return; + } + var parent = GetParent(); + if (parent is null) return; + // Clear siblings so the wizard fills the viewport, then swap in. + foreach (Node sibling in parent.GetChildren()) + if (sibling != this) sibling.QueueFree(); + var wizardNode = packed.Instantiate(); + parent.AddChild(wizardNode); + // The wizard's "← Title" back-button (visible on step 0) emits + // BackToTitle; reinstate this title screen when that fires. + if (wizardNode is Wizard wizard) + wizard.BackToTitle += () => SwapBackToTitle(parent); + QueueFree(); + } + + private static void SwapBackToTitle(Node parent) + { + foreach (Node child in parent.GetChildren()) child.QueueFree(); + parent.AddChild(new TitleScreen()); + } + + private void OnContinue() + { + // M7 territory — the play-loop screens that consume the persisted + // character don't exist yet. For now, surface a print so the click + // does something visible and the button isn't dead UI. + GD.Print($"[title] Continue: {CharacterAssembler.PersistedStatePath} exists. " + + "Play-loop pickup lands with M7."); + } + + private void OnQuit() => GetTree().Quit(); +}