From 8032999a4d2c90d16abdf9a1f7f71821fc9743f6 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 1 Nov 2026 16:29:35 +0100 Subject: [PATCH] Use the radio and wand mockups behind their forms Draw radio_report/emergency_radio behind a transparent, padded form and switch the backdrop with the call type. The wand HUD popup now shows wand_screening behind its text while open. Both covers use the mockup panel bounds so baked-in controls stay hidden. Headless 226/226 verified in a clean HEAD worktree. --- TODO.md | 6 ++++-- src/game/ui/hud.gd | 28 +++++++++++++++++++++++++++ src/game/ui/radio_panel.gd | 39 +++++++++++++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/TODO.md b/TODO.md index d7ddf66..019edcc 100644 --- a/TODO.md +++ b/TODO.md @@ -49,14 +49,16 @@ and scene visuals with those mockups — verified with - [x] Wire the pause overlay to `pause_overlay` scene art (backdrop + opaque panel cover). - [x] Wire the settings panel to `settings_panel` art. - [x] Wire the shift-review screen to `shift_review` art (backdrop + opaque panel cover). -- [ ] Wire the radial menu and HUD chrome to their reference images. +- [x] Wire the wand popup to `wand_screening` art. +- [ ] Wire the radial menu and remaining HUD chrome to their reference images. ### Align in-game visuals - [x] Make the venue selection screen match the `start_menu` mockup (layout, palette, background). - [ ] Make the HUD match the `targeting_npc` / `escalation_hud` mockups (focus ring, escalation meter, log, hints). - [x] Align the pause, settings, and shift-review panels to their mockups. -- [ ] Align the radio, wand, and timeline panels to their mockups. +- [x] Align the radio and wand panels to their mockups. +- [ ] Align the body-camera timeline to its mockup. - [ ] Align scene lighting, fog, and post-processing for `venue_overview` and `late_night_avenue`. ### Verify against the mockups diff --git a/src/game/ui/hud.gd b/src/game/ui/hud.gd index ceb662d..928ddee 100644 --- a/src/game/ui/hud.gd +++ b/src/game/ui/hud.gd @@ -5,6 +5,7 @@ extends CanvasLayer const Rules := preload("res://src/core/rules.gd") const Strings := preload("res://src/core/strings.gd") +const UiArt := preload("res://src/game/ui/art.gd") const T_VIGNETTE := preload("res://assets/textures/ai/game_ready/vignette.png") const T_ICON_FLASH := preload("res://assets/textures/ai/game_ready/icons/flashlight_on.png") const T_ICON_DOOR := preload("res://assets/textures/ai/game_ready/icons/door_locked.png") @@ -51,6 +52,8 @@ var _controls_label: Label var _debug_enabled := false var _log_lines: Array[String] = [] var _cam_overlay: Control = null +var _wand_art_layer: Control = null +var _wand_art := UiArt.new() var _body_camera_on := false var _font_scale := 1.0 var _base_font_sizes: Dictionary = {} @@ -235,12 +238,33 @@ func _init() -> void: inspect_center.add_child(inspect_panel) # --- wand screening panel (popup center) --- + _wand_art_layer = Control.new() + _wand_art_layer.name = "WandArt" + _wand_art_layer.set_anchors_preset(Control.PRESET_FULL_RECT) + _wand_art_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE + _wand_art_layer.visible = false + add_child(_wand_art_layer) + if not _wand_art.add_screen_backdrop(_wand_art_layer, "wand_screening", + Rect2(0.236, 0.094, 0.453, 0.884)): + var wand_dim := ColorRect.new() + wand_dim.color = Color(0, 0, 0, 0.55) + wand_dim.set_anchors_preset(Control.PRESET_FULL_RECT) + wand_dim.mouse_filter = Control.MOUSE_FILTER_IGNORE + _wand_art_layer.add_child(wand_dim) var wand_center := CenterContainer.new() wand_center.set_anchors_preset(Control.PRESET_FULL_RECT) wand_center.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(wand_center) wand_panel = PanelContainer.new() wand_panel.visible = false + wand_panel.custom_minimum_size = Vector2(440, 0) + var wand_style := StyleBoxFlat.new() + wand_style.bg_color = Color(0, 0, 0, 0) + wand_style.content_margin_left = 26 + wand_style.content_margin_right = 26 + wand_style.content_margin_top = 18 + wand_style.content_margin_bottom = 18 + wand_panel.add_theme_stylebox_override("panel", wand_style) var wvbox := VBoxContainer.new() wand_panel.add_child(wvbox) wand_text = Label.new() @@ -526,6 +550,10 @@ func show_wand(controller: Object, result: Dictionary) -> void: lines.append("No alert. The guest appears clear.") wand_meta.text = "\n".join(lines) wand_panel.visible = true + if _wand_art_layer: + _wand_art_layer.visible = true func hide_wand() -> void: wand_panel.visible = false + if _wand_art_layer: + _wand_art_layer.visible = false diff --git a/src/game/ui/radio_panel.gd b/src/game/ui/radio_panel.gd index 5c30598..c2330b0 100644 --- a/src/game/ui/radio_panel.gd +++ b/src/game/ui/radio_panel.gd @@ -8,6 +8,7 @@ signal submitted(report: Dictionary, emergency: bool) signal cancelled() const Rules := preload("res://src/core/rules.gd") +const UiArt := preload("res://src/game/ui/art.gd") var panel: PanelContainer var npc_name_label: Label @@ -26,19 +27,32 @@ var _hold_time := 0.0 const HOLD_NEEDED := 1.6 var _truth := {"weapons_seen": false, "danger_level": 0} +var _art := UiArt.new() +var _art_layer: Control = null + func _init() -> void: name = "RadioPanel" layer = 20 - var dim := ColorRect.new() - dim.color = Color(0, 0, 0, 0.55) - dim.set_anchors_preset(Control.PRESET_FULL_RECT) - add_child(dim) + _art_layer = Control.new() + _art_layer.name = "RadioArt" + _art_layer.set_anchors_preset(Control.PRESET_FULL_RECT) + _art_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE + add_child(_art_layer) + _paint_backdrop() var center := CenterContainer.new() center.set_anchors_preset(Control.PRESET_FULL_RECT) center.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(center) panel = PanelContainer.new() + panel.custom_minimum_size = Vector2(560, 0) + var panel_style := StyleBoxFlat.new() + panel_style.bg_color = Color(0, 0, 0, 0) + panel_style.content_margin_left = 28 + panel_style.content_margin_right = 28 + panel_style.content_margin_top = 22 + panel_style.content_margin_bottom = 22 + panel.add_theme_stylebox_override("panel", panel_style) center.add_child(panel) var vbox := VBoxContainer.new() @@ -99,13 +113,28 @@ func _init() -> void: var _title_label: Label +func _paint_backdrop() -> void: + if _art_layer == null: + return + for child in _art_layer.get_children(): + _art_layer.remove_child(child) + child.queue_free() + var art_name := "emergency_radio" if _emergency else "radio_report" + var rect := Rect2(0.120, 0.058, 0.697, 0.882) if _emergency else Rect2(0.176, 0.033, 0.585, 0.923) + if not _art.add_screen_backdrop(_art_layer, art_name, rect): + var dim := ColorRect.new() + dim.color = Color(0, 0, 0, 0.55) + dim.set_anchors_preset(Control.PRESET_FULL_RECT) + _art_layer.add_child(dim) + func open(controller: Object, emergency: bool, truth: Dictionary) -> void: _emergency = emergency + _paint_backdrop() _truth = truth _npc_id = controller.npc_id if controller else "" npc_name_label.text = "Target: %s" % (controller.profile.display_name if controller else "?") var title: Label = panel.find_child("RadioTitle", true, false) - title.text = "EMERGENCY CALL" if emergency else "Security Assistance" + title.text = "EMERGENCY RADIO" if emergency else "RADIO DISPATCH" weapons_check.button_pressed = truth.get("weapons_seen", false) danger_option.select(truth.get("danger_level", 1)) hold_hint.text = "Hold the button for %.1fs" % HOLD_NEEDED if emergency else ""