83c6343783
--dark command-line flag swaps CodexTheme.DefaultPalette to Dark before any UI mounts; both TitleScreen and Wizard pick it up via the no-arg Build() overload. Stepper colours track the active palette. ApplyStateColors and the Active step's gild underline previously read from a stub that hardcoded parchment values, so the Active label rendered as brown-black ink against the dark bg (invisible). Both sites now read CodexTheme.DefaultPalette directly. Card hover stays applied while the cursor is over an inner Button. PanelContainer.MouseExited fires when the cursor crosses onto a child that captures input (Sire/Dam toggles, Sheep/Goat toggles, trait pickers); the recheck defers and uses GetGlobalRect.HasPoint on the cursor position so hover only drops when the cursor truly leaves the card area. Selection stylebox lands on first refresh. SetSelected was previously called inside BuildCard before AddChild, so HasThemeStylebox returned false (theme cascade unreachable) and the override silently dropped — it only re-attached when MouseEntered later re-ran Apply. Refactored SetSelected/SetHover through a new ApplyOrDefer helper that uses CallDeferred when the card isn't in tree yet, so the seal border + drop shadow appear immediately on selection rather than only after the first hover. Selection drop shadow refined. Was a 14px shadow at offset (0,14) which overlapped the next card by 16px in the v_separation:12 grid. Now offset (4,4) + size 6 — diagonal "light from upper-left" direction, total reach 10px, leaves a 2px clearance before the next card so the shadow reads as a shadow on the surface below. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
165 lines
5.5 KiB
C#
165 lines
5.5 KiB
C#
using Godot;
|
|
using Theriapolis.GodotHost.Platform;
|
|
using Theriapolis.GodotHost.Rendering;
|
|
using Theriapolis.GodotHost.Scenes;
|
|
using Theriapolis.GodotHost.UI;
|
|
|
|
namespace Theriapolis.GodotHost;
|
|
|
|
// Control (not Node) so child Control scenes (Wizard, KitchenSink, etc.)
|
|
// can anchor to a real rect that fills the viewport. With a plain Node
|
|
// parent, anchors are ignored and Controls sit at (0,0) at intrinsic min
|
|
// size, which causes wide content to overflow off the right edge.
|
|
public partial class Main : Control
|
|
{
|
|
public override void _Ready()
|
|
{
|
|
// GetCmdlineArgs returns every arg (Godot's own flags + ours);
|
|
// GetCmdlineUserArgs only returns args after a "--" separator.
|
|
// Use the union so users don't have to remember the separator.
|
|
var userArgs = OS.GetCmdlineUserArgs();
|
|
var allArgs = OS.GetCmdlineArgs();
|
|
var args = new string[userArgs.Length + allArgs.Length];
|
|
userArgs.CopyTo(args, 0);
|
|
allArgs.CopyTo(args, userArgs.Length);
|
|
|
|
ulong? smokeTestSeed = null;
|
|
ulong? worldMapSeed = null;
|
|
bool runAssetTest = false;
|
|
bool runCodexTest = false;
|
|
bool runWizard = false;
|
|
(ulong seed, int tx, int ty)? tacticalArgs = null;
|
|
|
|
// --dark is independent of the entry-point flags: it sets the codex
|
|
// palette default before any UI mounts so both TitleScreen and the
|
|
// wizard pick it up via CodexTheme.Build()'s no-arg overload.
|
|
for (int i = 0; i < args.Length; i++)
|
|
{
|
|
if (args[i] == "--dark")
|
|
{
|
|
CodexTheme.DefaultPalette = CodexPalette.Dark;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < args.Length; i++)
|
|
{
|
|
if (args[i] == "--codex-test")
|
|
{
|
|
runCodexTest = true;
|
|
break;
|
|
}
|
|
if (args[i] == "--wizard")
|
|
{
|
|
runWizard = true;
|
|
break;
|
|
}
|
|
if (args[i] == "--smoke-test")
|
|
{
|
|
ulong seed = 12345UL;
|
|
if (i + 1 < args.Length && ulong.TryParse(args[i + 1], out var parsed))
|
|
seed = parsed;
|
|
smokeTestSeed = seed;
|
|
break;
|
|
}
|
|
if (args[i] == "--asset-test")
|
|
{
|
|
runAssetTest = true;
|
|
break;
|
|
}
|
|
if (args[i] == "--world-map")
|
|
{
|
|
ulong seed = 12345UL;
|
|
if (i + 1 < args.Length && ulong.TryParse(args[i + 1], out var parsed))
|
|
seed = parsed;
|
|
worldMapSeed = seed;
|
|
break;
|
|
}
|
|
if (args[i] == "--tactical")
|
|
{
|
|
ulong seed = 12345UL;
|
|
int tx = 128, ty = 128;
|
|
if (i + 1 < args.Length && ulong.TryParse(args[i + 1], out var s))
|
|
seed = s;
|
|
if (i + 2 < args.Length && int.TryParse(args[i + 2], out var x))
|
|
tx = x;
|
|
if (i + 3 < args.Length && int.TryParse(args[i + 3], out var y))
|
|
ty = y;
|
|
tacticalArgs = (seed, tx, ty);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (smokeTestSeed.HasValue)
|
|
{
|
|
int code = SmokeTest.Run(smokeTestSeed.Value);
|
|
GetTree().Quit(code);
|
|
return;
|
|
}
|
|
|
|
if (runAssetTest)
|
|
{
|
|
int code = AssetTest.Run();
|
|
GetTree().Quit(code);
|
|
return;
|
|
}
|
|
|
|
if (worldMapSeed.HasValue)
|
|
{
|
|
// M4: unified seamless-zoom view. --world-map starts zoomed out
|
|
// (fit-to-viewport, initialZoom=0 = compute fit), --tactical
|
|
// starts at native sprite zoom 32 with the player at the given
|
|
// tile. Wheel between them seamlessly.
|
|
foreach (Node child in GetChildren())
|
|
child.QueueFree();
|
|
AddChild(new WorldView(worldMapSeed.Value));
|
|
return;
|
|
}
|
|
|
|
if (tacticalArgs.HasValue)
|
|
{
|
|
foreach (Node child in GetChildren())
|
|
child.QueueFree();
|
|
var (seed, tx, ty) = tacticalArgs.Value;
|
|
AddChild(new WorldView(seed, tx, ty, initialZoom: 32f));
|
|
return;
|
|
}
|
|
|
|
if (runCodexTest)
|
|
{
|
|
foreach (Node child in GetChildren())
|
|
child.QueueFree();
|
|
AddChild(new KitchenSink());
|
|
return;
|
|
}
|
|
|
|
if (runWizard)
|
|
{
|
|
foreach (Node child in GetChildren())
|
|
child.QueueFree();
|
|
var packed = ResourceLoader.Load<PackedScene>("res://Scenes/Wizard.tscn");
|
|
AddChild(packed.Instantiate());
|
|
return;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
if (@event.IsActionPressed("ui_toggle_fullscreen"))
|
|
{
|
|
var mode = DisplayServer.WindowGetMode();
|
|
DisplayServer.WindowSetMode(
|
|
mode == DisplayServer.WindowMode.Fullscreen
|
|
? DisplayServer.WindowMode.Windowed
|
|
: DisplayServer.WindowMode.Fullscreen);
|
|
}
|
|
}
|
|
}
|