refactor(ui): tokenize HUD notification colors off raw Color() literals
p2-74 cluster 2 (HUD panels + notifications), notifications sub-cluster. Route turn_notification.gd / comms_toast.gd / capital_blackout_overlay.gd inline Color() literals onto ThemeAssets.color() design tokens. - turn_notification: const CATEGORY_COLORS dict (a const trap — token lookups can't sit in a const initializer) converted to var _category_colors populated in _ready(); category palette → semantic.negative/positive, accent.science, accent.goldResource, player.purple, semantic.diplomacy, text.primary. Panel bg/border → background.panel/border.panel; title/header → text.title; scrim dims → background.hud/overlay; muted hints → text.muted. - comms_toast: panel bg/border → background.panel/border.panel; accent strip → accent.gold; title → text.title; body → text.primary. The configure() accent default Color literal (param defaults can't call the autoload) becomes a null sentinel resolved to accent.gold in the body. - capital_blackout_overlay: dim/glitch scrims sourced from background.deepest / semantic.negative with explicit .a; title → semantic.negative; subtitle → text.title. Adds tools/capture-proof.sh: reusable single-proof-scene capture under a private headless weston on the RUN host, pulling the PNG back. Visual-only; no logic change (Rail 3). 0 Color() remain in the three scripts. Proof: hud_proof.tscn captured on apricot (headless weston) shows the themed purple Turn Summary panel, gold border/title, copper filter checkboxes, and semantic per-category entry coloring (red combat, green founding, blue tech, gold economy, violet magic). .project/screenshots/p2-74-cluster2-hud-notifications.png Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3a04052385
commit
d6c84e4b4f
5 changed files with 96 additions and 32 deletions
BIN
.project/screenshots/p2-74-cluster2-hud-notifications.png
Normal file
BIN
.project/screenshots/p2-74-cluster2-hud-notifications.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
57
tools/capture-proof.sh
Executable file
57
tools/capture-proof.sh
Executable file
|
|
@ -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:<abs>" (the convention used by scenes/tests/*_proof.gd).
|
||||
#
|
||||
# Usage: tools/capture-proof.sh <scene_res_path> <local_out_png> [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 <<REMOTE
|
||||
set -uo pipefail
|
||||
cd "$RROOT/src/game" || exit 3
|
||||
WSOCK="proof-\$\$"
|
||||
weston --backend=headless --no-config --socket="\$WSOCK" --width=1280 --height=720 \
|
||||
>/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"
|
||||
Loading…
Add table
Reference in a new issue