M6.20: TitleScreen entry point with parchment-themed button stack
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 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
using Godot;
|
||||
using Theriapolis.GodotHost.UI;
|
||||
|
||||
namespace Theriapolis.GodotHost.Scenes;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="CharacterAssembler.PersistedStatePath"/>
|
||||
/// exists; full pickup lands with the M7 play loop.
|
||||
/// Quit — shut down the engine.
|
||||
/// </summary>
|
||||
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<PackedScene>(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();
|
||||
}
|
||||
Reference in New Issue
Block a user