diff --git a/.project/screenshots/p2-74-cluster2-hud-notifications.png b/.project/screenshots/p2-74-cluster2-hud-notifications.png new file mode 100644 index 00000000..7799d649 Binary files /dev/null and b/.project/screenshots/p2-74-cluster2-hud-notifications.png differ diff --git a/src/game/engine/scenes/hud/turn_notification.gd b/src/game/engine/scenes/hud/turn_notification.gd index c9668753..1d7e2699 100644 --- a/src/game/engine/scenes/hud/turn_notification.gd +++ b/src/game/engine/scenes/hud/turn_notification.gd @@ -8,16 +8,9 @@ const AUTO_DISMISS_SECONDS: float = 3.0 const MAX_ENTRIES: int = 8 ## Category color palette for log entries. Keys must match the strings passed -## to `_add_entry` — see `_on_*` handlers below. -const CATEGORY_COLORS: Dictionary = { - "combat": Color(1.0, 0.55, 0.55, 1.0), # red-ish - "founding": Color(0.65, 1.0, 0.65, 1.0), # green-ish - "tech": Color(0.65, 0.85, 1.0, 1.0), # blue-ish - "economy": Color(1.0, 0.9, 0.55, 1.0), # gold - "magic": Color(0.85, 0.7, 1.0, 1.0), # violet - "event": Color(1.0, 0.75, 0.5, 1.0), # orange - "default": Color(0.9, 0.88, 0.78, 1.0), -} +## to `_add_entry` — see `_on_*` handlers below. Populated from design tokens in +## `_ready()` (ThemeAssets.color() is an autoload call, not a const expression). +var _category_colors: Dictionary = {} ## Filter buckets map one or more entry categories to a single checkbox. "all" ## is a meta-bucket that flips every other filter in lockstep. @@ -55,6 +48,15 @@ var _filter_checks: Dictionary = {} func _ready() -> void: layer = 20 visible = false + _category_colors = { + "combat": ThemeAssets.color("semantic.negative"), + "founding": ThemeAssets.color("semantic.positive"), + "tech": ThemeAssets.color("accent.science"), + "economy": ThemeAssets.color("accent.goldResource"), + "magic": ThemeAssets.color("player.purple"), + "event": ThemeAssets.color("semantic.diplomacy"), + "default": ThemeAssets.color("text.primary"), + } _build_ui() _connect_signals() @@ -62,7 +64,7 @@ func _ready() -> void: func _build_ui() -> void: _dim_rect = ColorRect.new() _dim_rect.name = "DimRect" - _dim_rect.color = Color(0.0, 0.0, 0.0, 0.4) + _dim_rect.color = ThemeAssets.color("background.hud") _dim_rect.set_anchors_preset(Control.PRESET_FULL_RECT) _dim_rect.mouse_filter = Control.MOUSE_FILTER_STOP _dim_rect.gui_input.connect(_on_dim_clicked) @@ -72,7 +74,7 @@ func _build_ui() -> void: _processing_label.name = "ProcessingLabel" _processing_label.text = ThemeVocabulary.lookup("action_processing") _processing_label.add_theme_font_size_override("font_size", 28) - _processing_label.add_theme_color_override("font_color", Color(1.0, 0.94, 0.75, 1.0)) + _processing_label.add_theme_color_override("font_color", ThemeAssets.color("text.title")) _processing_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _processing_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _processing_label.set_anchors_preset(Control.PRESET_CENTER) @@ -92,8 +94,8 @@ func _build_ui() -> void: add_child(_log_panel) var panel_style: StyleBoxFlat = StyleBoxFlat.new() - panel_style.bg_color = Color(0.05, 0.04, 0.02, 0.92) - panel_style.border_color = Color(0.8, 0.7, 0.3, 0.9) + panel_style.bg_color = ThemeAssets.color("background.panel") + panel_style.border_color = ThemeAssets.color("border.panel") panel_style.set_border_width_all(2) panel_style.set_corner_radius_all(6) _log_panel.add_theme_stylebox_override("panel", panel_style) @@ -113,7 +115,7 @@ func _build_ui() -> void: header.name = "LogHeader" header.text = "%s Summary" % ThemeVocabulary.lookup("turn") header.add_theme_font_size_override("font_size", 20) - header.add_theme_color_override("font_color", Color(1.0, 0.94, 0.75, 1.0)) + header.add_theme_color_override("font_color", ThemeAssets.color("text.title")) header.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER outer_vbox.add_child(header) @@ -142,7 +144,7 @@ func _build_ui() -> void: hint.name = "DismissHint" hint.text = ThemeVocabulary.lookup("dismiss_hint") hint.add_theme_font_size_override("font_size", 12) - hint.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6, 0.8)) + hint.add_theme_color_override("font_color", ThemeAssets.color("text.muted")) hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER outer_vbox.add_child(hint) @@ -189,7 +191,7 @@ func show_processing() -> void: _is_processing = true _processing_label.visible = true _log_panel.visible = false - _dim_rect.color = Color(0.0, 0.0, 0.0, 0.4) + _dim_rect.color = ThemeAssets.color("background.hud") visible = true @@ -200,7 +202,7 @@ func _show_log() -> void: return _processing_label.visible = false - _dim_rect.color = Color(0.0, 0.0, 0.0, 0.5) + _dim_rect.color = ThemeAssets.color("background.overlay") _rebuild_log_entries() _log_panel.visible = true @@ -225,13 +227,13 @@ func _rebuild_log_entries() -> void: var more_label: Label = Label.new() more_label.text = ThemeVocabulary.lookup("fmt_and_n_more") % hidden_count more_label.add_theme_font_size_override("font_size", 13) - more_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6, 0.8)) + more_label.add_theme_color_override("font_color", ThemeAssets.color("text.muted")) _log_vbox.add_child(more_label) func _build_entry_node(entry: Dictionary) -> Control: var category: String = entry.get("category", "default") - var color: Color = CATEGORY_COLORS.get(category, CATEGORY_COLORS["default"]) + var color: Color = _category_colors.get(category, _category_colors["default"]) var text: String = entry.get("text", "") if entry.has("hex_pos"): var btn: Button = Button.new() diff --git a/src/game/engine/scenes/notifications/capital_blackout_overlay.gd b/src/game/engine/scenes/notifications/capital_blackout_overlay.gd index c528f735..5c7048e1 100644 --- a/src/game/engine/scenes/notifications/capital_blackout_overlay.gd +++ b/src/game/engine/scenes/notifications/capital_blackout_overlay.gd @@ -24,7 +24,8 @@ func _ready() -> void: func _build_ui() -> void: _dim = ColorRect.new() _dim.set_anchors_preset(Control.PRESET_FULL_RECT) - _dim.color = Color(0.04, 0.0, 0.02, 0.88) + _dim.color = ThemeAssets.color("background.deepest") + _dim.color.a = 0.88 _dim.mouse_filter = Control.MOUSE_FILTER_STOP add_child(_dim) @@ -33,7 +34,8 @@ func _build_ui() -> void: _glitch_band.anchor_right = 1.0 _glitch_band.anchor_top = 0.42 _glitch_band.anchor_bottom = 0.58 - _glitch_band.color = Color(0.55, 0.05, 0.05, 0.35) + _glitch_band.color = ThemeAssets.color("semantic.negative") + _glitch_band.color.a = 0.35 add_child(_glitch_band) var center: CenterContainer = CenterContainer.new() @@ -47,14 +49,14 @@ func _build_ui() -> void: _title_label = Label.new() _title_label.text = TITLE_DEFAULT _title_label.add_theme_font_size_override("font_size", 42) - _title_label.add_theme_color_override("font_color", Color(1.0, 0.45, 0.45, 1.0)) + _title_label.add_theme_color_override("font_color", ThemeAssets.color("semantic.negative")) _title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER vbox.add_child(_title_label) _subtitle_label = Label.new() _subtitle_label.text = SUBTITLE_DEFAULT _subtitle_label.add_theme_font_size_override("font_size", 22) - _subtitle_label.add_theme_color_override("font_color", Color(0.9, 0.85, 0.65, 0.95)) + _subtitle_label.add_theme_color_override("font_color", ThemeAssets.color("text.title")) _subtitle_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER vbox.add_child(_subtitle_label) diff --git a/src/game/engine/scenes/notifications/comms_toast.gd b/src/game/engine/scenes/notifications/comms_toast.gd index 909ca4c6..f077b8cd 100644 --- a/src/game/engine/scenes/notifications/comms_toast.gd +++ b/src/game/engine/scenes/notifications/comms_toast.gd @@ -22,8 +22,8 @@ func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE var style: StyleBoxFlat = StyleBoxFlat.new() - style.bg_color = Color(0.06, 0.06, 0.10, 0.92) - style.border_color = Color(0.85, 0.78, 0.40, 0.95) + style.bg_color = ThemeAssets.color("background.panel") + style.border_color = ThemeAssets.color("border.panel") style.set_border_width_all(2) style.set_corner_radius_all(6) style.content_margin_left = 12 @@ -39,7 +39,7 @@ func _ready() -> void: _icon_rect = ColorRect.new() _icon_rect.custom_minimum_size = Vector2(8, 0) _icon_rect.size_flags_vertical = Control.SIZE_EXPAND_FILL - _icon_rect.color = Color(0.85, 0.78, 0.40, 1.0) + _icon_rect.color = ThemeAssets.color("accent.gold") row.add_child(_icon_rect) var col: VBoxContainer = VBoxContainer.new() @@ -49,12 +49,12 @@ func _ready() -> void: _title = Label.new() _title.add_theme_font_size_override("font_size", 16) - _title.add_theme_color_override("font_color", Color(1.0, 0.94, 0.75, 1.0)) + _title.add_theme_color_override("font_color", ThemeAssets.color("text.title")) col.add_child(_title) _body = Label.new() _body.add_theme_font_size_override("font_size", 13) - _body.add_theme_color_override("font_color", Color(0.88, 0.88, 0.84, 1.0)) + _body.add_theme_color_override("font_color", ThemeAssets.color("text.primary")) _body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART col.add_child(_body) @@ -66,16 +66,19 @@ func _ready() -> void: ## Parameterise the toast at spawn time. Call after add_child / before showing. -func configure(title: String, body: String, accent: Color = Color(0.85, 0.78, 0.40, 1.0)) -> void: +## A null accent falls back to the gold design token (param defaults can't call +## the ThemeAssets autoload, so the sentinel is resolved here in the body). +func configure(title: String, body: String, accent: Variant = null) -> void: _title.text = title _body.text = body if _title.text.is_empty(): _title.visible = false - _icon_rect.color = accent + var accent_color: Color = accent if accent is Color else ThemeAssets.color("accent.gold") + _icon_rect.color = accent_color var style: StyleBoxFlat = get_theme_stylebox("panel") as StyleBoxFlat if style != null: - style.border_color = accent + style.border_color = accent_color modulate.a = 0.0 var tween: Tween = create_tween() diff --git a/tools/capture-proof.sh b/tools/capture-proof.sh new file mode 100755 index 00000000..cdb0a669 --- /dev/null +++ b/tools/capture-proof.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Capture a single proof scene under a private headless weston on the RUN host, +# then pull the resulting PNG back to the EDIT host. +# +# Reusable across UI proof work (p2-74 de-hardcode clusters etc). The proof +# scene must self-capture via get_viewport().get_image().save_png() and print a +# line "SCREENSHOT_PATH:" (the convention used by scenes/tests/*_proof.gd). +# +# Usage: tools/capture-proof.sh [timeout_s] +# scene_res_path: e.g. res://engine/scenes/tests/hud_proof.tscn +# local_out_png : destination on the EDIT host +# +# Env: AUTOPLAY_HOST (default lilith@apricot.lan), PROJECT_ROOT_REMOTE. +set -uo pipefail + +SCENE="${1:?scene res:// path required}" +OUT="${2:?local output png path required}" +TIMEOUT="${3:-180}" + +RH="${AUTOPLAY_HOST:-lilith@apricot.lan}" +RROOT="${PROJECT_ROOT_REMOTE:-/var/home/lilith/Code/@projects/@magic-civilization}" + +remote_cmd=$(cat </tmp/weston-\$WSOCK.log 2>&1 & +WPID=\$! +sleep 1.5 +XDG_RUNTIME_DIR="\${XDG_RUNTIME_DIR:-/run/user/\$(id -u)}" \ +timeout "$TIMEOUT" flatpak run --user \ + --filesystem=home \ + --socket=wayland \ + --unset-env=DISPLAY \ + --env=WAYLAND_DISPLAY="\$WSOCK" \ + --filesystem=xdg-run/"\$WSOCK" \ + org.godotengine.Godot \ + --path . \ + --display-driver wayland --rendering-driver opengl3 --rendering-method gl_compatibility \ + "$SCENE" 2>&1 | tee /tmp/proof-\$WSOCK.log +kill "\$WPID" 2>/dev/null || true +grep -m1 '^SCREENSHOT_PATH:' /tmp/proof-\$WSOCK.log | sed 's/^SCREENSHOT_PATH://' +REMOTE +) + +echo "=== capture-proof: $SCENE on $RH ===" +REMOTE_PNG="$(ssh "$RH" "$remote_cmd" | tail -n1)" + +if [ -z "$REMOTE_PNG" ]; then + echo "ERROR: no SCREENSHOT_PATH emitted by proof scene" >&2 + exit 1 +fi +echo "Remote PNG: $REMOTE_PNG" +mkdir -p "$(dirname "$OUT")" +scp "$RH:$REMOTE_PNG" "$OUT" +echo "Saved: $OUT"