diff --git a/Theriapolis.Godot/Main.cs b/Theriapolis.Godot/Main.cs index ff9f30f..f83c8b8 100644 --- a/Theriapolis.Godot/Main.cs +++ b/Theriapolis.Godot/Main.cs @@ -29,6 +29,19 @@ public partial class Main : Control 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") diff --git a/Theriapolis.Godot/Scenes/Widgets/CodexCard.cs b/Theriapolis.Godot/Scenes/Widgets/CodexCard.cs index af615a7..47c2979 100644 --- a/Theriapolis.Godot/Scenes/Widgets/CodexCard.cs +++ b/Theriapolis.Godot/Scenes/Widgets/CodexCard.cs @@ -21,8 +21,11 @@ public static class CodexCard /// /// Creates a PanelContainer with ThemeTypeVariation = "Card" plus - /// hover signal wiring. The MouseEntered/MouseExited handlers update - /// the hover meta and re-apply the right stylebox. + /// hover signal wiring. MouseEntered marks hover; MouseExited defers + /// a recheck against the card's global rect so moving the cursor + /// from the card body onto an inner Button (which captures the + /// parent's MouseExited via mouse-filter Stop) does not clear the + /// hover state — the cursor is still visually within the card. /// public static PanelContainer Make() { @@ -32,20 +35,52 @@ public static class CodexCard MouseFilter = Control.MouseFilterEnum.Stop, }; card.MouseEntered += () => SetHover(card, true); - card.MouseExited += () => SetHover(card, false); + card.MouseExited += () => + Callable.From(() => RecheckHover(card)).CallDeferred(); return card; } + private static void RecheckHover(PanelContainer card) + { + if (!GodotObject.IsInstanceValid(card)) return; + // Hover stays true as long as the cursor is anywhere within the + // card's rect (including over any child control). Drop only when + // the cursor has truly left the card area. + bool stillOver = card.GetGlobalRect().HasPoint(card.GetGlobalMousePosition()); + SetHover(card, stillOver); + } + public static void SetSelected(PanelContainer card, bool selected) { card.SetMeta(SelectedMeta, selected); - Apply(card); + ApplyOrDefer(card); } private static void SetHover(PanelContainer card, bool hover) { card.SetMeta(HoverMeta, hover); - Apply(card); + ApplyOrDefer(card); + } + + /// + /// Apply now if the card is already in the scene tree, otherwise defer + /// until end-of-frame so the parent theme cascade is reachable. Step + /// builders call SetSelected on a freshly-created card before + /// AddChild — the theme isn't visible at that point and HasThemeStylebox + /// returns false, which previously meant the override silently dropped + /// and only re-attached when MouseEntered later re-ran Apply. + /// + private static void ApplyOrDefer(PanelContainer card) + { + if (card.IsInsideTree()) + { + Apply(card); + return; + } + Callable.From(() => + { + if (GodotObject.IsInstanceValid(card)) Apply(card); + }).CallDeferred(); } private static void Apply(PanelContainer card) diff --git a/Theriapolis.Godot/UI/CodexTheme.cs b/Theriapolis.Godot/UI/CodexTheme.cs index d7b4b61..a66b08e 100644 --- a/Theriapolis.Godot/UI/CodexTheme.cs +++ b/Theriapolis.Godot/UI/CodexTheme.cs @@ -29,7 +29,15 @@ public static class CodexTheme private static FontFile? _mono; private static bool _fontsLoaded; - public static Theme Build() => Build(CodexPalette.Parchment); + /// + /// Palette used by the no-arg . Set this before any + /// UI mounts to swap the active codex palette globally — e.g. Main reads + /// the --dark command-line flag and assigns + /// here. Defaults to . + /// + public static CodexPalette DefaultPalette { get; set; } = CodexPalette.Parchment; + + public static Theme Build() => Build(DefaultPalette); public static Theme Build(CodexPalette palette) { @@ -101,9 +109,15 @@ public static class CodexTheme var cardSelected = (StyleBoxFlat)card.Duplicate(); cardSelected.BorderColor = p.Seal; cardSelected.SetBorderWidthAll(3); - cardSelected.ShadowColor = WithAlpha(p.Seal, 0.6f); - cardSelected.ShadowSize = 14; - cardSelected.ShadowOffset = new Vector2(0, 14); + // Drop shadow: directional (light from upper-left) and sized so the + // shadow's bottom edge stays clear of the next card. Card grids + // separate cards by 12px (v_separation in StepClade / StepSpecies / + // StepClass / etc.) — offset.y + size ≤ 11 keeps a 1px-minimum gap + // before the next card so the shadow reads as a shadow on the + // surface below, not as a smudge between cards. + cardSelected.ShadowColor = WithAlpha(p.Seal, 0.55f); + cardSelected.ShadowSize = 6; + cardSelected.ShadowOffset = new Vector2(4, 4); theme.SetStylebox("panel_selected", "Card", cardSelected); // Popover frame — gild border + soft shadow + rounded corners. diff --git a/Theriapolis.Godot/UI/Widgets/CodexStepper.cs b/Theriapolis.Godot/UI/Widgets/CodexStepper.cs index 35e67a6..3c00dc3 100644 --- a/Theriapolis.Godot/UI/Widgets/CodexStepper.cs +++ b/Theriapolis.Godot/UI/Widgets/CodexStepper.cs @@ -104,7 +104,7 @@ public partial class CodexStepper : HBoxContainer { var underline = new ColorRect { - Color = TryGetThemeColor("font_color", "Gild") ?? new Color("#b48a3c"), + Color = CodexTheme.DefaultPalette.Gild, MouseFilter = MouseFilterEnum.Ignore, }; underline.AnchorTop = 1.0f; @@ -124,16 +124,20 @@ public partial class CodexStepper : HBoxContainer // Default theme colours come from the StepperNum/StepperName variations // (ink-mute). State overrides bring active steps to ink and complete // to seal-red. Locked uses the dim default plus reduced opacity. + // Pull the colours from CodexTheme.DefaultPalette so the stepper + // tracks the active palette (parchment vs dark) instead of forcing + // the parchment values regardless. + var palette = CodexTheme.DefaultPalette; Color? numColor = state switch { - StepState.Active => TryGetGlobalThemeColor("Ink", new Color("#2b1d10")), - StepState.Complete => TryGetGlobalThemeColor("Seal", new Color("#7a1f12")), + StepState.Active => palette.Ink, + StepState.Complete => palette.Seal, _ => null, }; Color? nameColor = state switch { - StepState.Active => TryGetGlobalThemeColor("Ink", new Color("#2b1d10")), - _ => null, + StepState.Active => palette.Ink, + _ => null, }; if (numColor.HasValue) num.AddThemeColorOverride("font_color", numColor.Value); @@ -149,14 +153,6 @@ public partial class CodexStepper : HBoxContainer } } - private static Color? TryGetGlobalThemeColor(string name, Color fallback) => fallback; - - private Color? TryGetThemeColor(string property, string variation) - { - if (HasThemeColor(property, variation)) return GetThemeColor(property, variation); - return null; - } - private static string Roman(int n) => n switch { 1 => "I",