HUD: merge TimeDisplay into ImplantPanel (time/health/perception in one panel), remove dead tscn nodes, move HUD to InsertOverlay for D-051 bloom. Interaction prompt: convert from PanelContainer+StyleBoxFlat to Control with ImplantPanel+ImplantDataRow, fix panel height for 42px minimum. Tests updated for new scene structure and public API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+9
-10
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=29 format=3 uid="uid://bswrmh7w8dbgm"]
|
||||
[gd_scene load_steps=28 format=3 uid="uid://bswrmh7w8dbgm"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/main.gd" id="1_main"]
|
||||
[ext_resource type="Script" path="res://scripts/rendering/world_renderer.gd" id="2_world"]
|
||||
@@ -22,7 +22,6 @@
|
||||
[ext_resource type="PackedScene" path="res://ui/bug_report_dialog.tscn" id="19_bugreport"]
|
||||
[ext_resource type="PackedScene" path="res://ui/settings_dialog.tscn" id="21_settings"]
|
||||
[ext_resource type="Script" path="res://ui/debug_overlay.gd" id="22_debug"]
|
||||
[ext_resource type="PackedScene" path="res://ui/time_display.tscn" id="23_tdisplay"]
|
||||
[ext_resource type="PackedScene" path="res://ui/examine_display.tscn" id="24_examine"]
|
||||
[ext_resource type="PackedScene" path="res://ui/journal_panel.tscn" id="25_journal"]
|
||||
[ext_resource type="PackedScene" path="res://ui/loading_screen.tscn" id="26_loading"]
|
||||
@@ -124,9 +123,6 @@ zoom = Vector2(2, 2)
|
||||
[node name="InsertOverlay" type="CanvasLayer" parent="."]
|
||||
layer = 10
|
||||
|
||||
; #263: Time display — diegetic insert clock, top-left placeholder (D-013, D-031)
|
||||
[node name="TimeDisplay" parent="InsertOverlay" instance=ExtResource("23_tdisplay")]
|
||||
|
||||
; InteractionPrompt — v0.1 fallback single-line "E - Talk" display
|
||||
[node name="InteractionPrompt" parent="InsertOverlay" instance=ExtResource("9_prompt")]
|
||||
|
||||
@@ -148,13 +144,15 @@ layer = 10
|
||||
; #264: Journal panel — knowledge graph review, toggle J key, read-only (D-041)
|
||||
[node name="JournalPanel" parent="InsertOverlay" instance=ExtResource("25_journal")]
|
||||
|
||||
; HUD — implant-styled status panel (D-169, D-170). On InsertOverlay for bloom per D-051.
|
||||
[node name="HUD" parent="InsertOverlay" instance=ExtResource("6_hud")]
|
||||
|
||||
; --- UI layer (CanvasLayer 20) ---
|
||||
; HUD, monologue, cursor — always visible, not affected by fog or camera.
|
||||
; Monologue, cursor, stance — always visible, not affected by fog or camera.
|
||||
; HUD moved to InsertOverlay (layer 10) for bloom treatment per D-051.
|
||||
[node name="UILayer" type="CanvasLayer" parent="."]
|
||||
layer = 20
|
||||
|
||||
[node name="HUD" parent="UILayer" instance=ExtResource("6_hud")]
|
||||
|
||||
[node name="MonologueDisplay" parent="UILayer" instance=ExtResource("8_monologue")]
|
||||
|
||||
; D-053: Stance indicator — top-right, color-coded
|
||||
@@ -170,12 +168,13 @@ layer = 20
|
||||
[node name="InventoryGrid" parent="UILayer" instance=ExtResource("12_inv")]
|
||||
|
||||
; #511: F3 debug overlay — real-time game state, toggled by F3
|
||||
; Position: below merged HUD status panel (starts at y:16, ~80px), 10px gap
|
||||
[node name="DebugOverlay" type="Control" parent="UILayer"]
|
||||
anchors_preset = 0
|
||||
offset_left = 16
|
||||
offset_top = 120
|
||||
offset_top = 150
|
||||
offset_right = 400
|
||||
offset_bottom = 400
|
||||
offset_bottom = 430
|
||||
mouse_filter = 2
|
||||
script = ExtResource("22_debug")
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ var _dialogue: DialogueCoordinator # #775: dialogue consumers + signal handlers
|
||||
@onready var world_renderer = $World
|
||||
@onready var fog_entities = $World/FogEntities # D-059/D-060: cognitive delay fog visualization
|
||||
@onready var camera = $Camera2D
|
||||
@onready var hud = $UILayer/HUD
|
||||
@onready var hud = $InsertOverlay/HUD
|
||||
@onready var monologue_display = $UILayer/MonologueDisplay
|
||||
@onready var interaction_prompt = $InsertOverlay/InteractionPrompt # v0.1 single-line fallback
|
||||
@onready var interaction_list = $InsertOverlay/InteractionList # D-057: z-layer 6
|
||||
@@ -24,7 +24,6 @@ var _dialogue: DialogueCoordinator # #775: dialogue consumers + signal handlers
|
||||
@onready var cursor_renderer = $UILayer/CursorRenderer # D-056: z-layer 7
|
||||
@onready var gauntlet_hud = $UILayer/GauntletHUD # #496: room timer + personal bests
|
||||
@onready var checklist_overlay = $UILayer/ChecklistOverlay # #503: auto-checklist progress
|
||||
@onready var time_display = $InsertOverlay/TimeDisplay # #263: diegetic time display (D-013, D-031)
|
||||
@onready var minimap = $InsertOverlay/Minimap # #151: diegetic minimap overlay (D-013, D-049)
|
||||
@onready var examine_display = $InsertOverlay/ExamineDisplay # #174: examine result overlay
|
||||
@onready var journal_panel = $InsertOverlay/JournalPanel # #264: knowledge journal (D-041)
|
||||
@@ -34,7 +33,7 @@ var _dialogue: DialogueCoordinator # #775: dialogue consumers + signal handlers
|
||||
@onready var loading_screen = $ModalLayer/LoadingScreen # #257: blocking overlay during load
|
||||
@onready var debug_console = $ModalLayer/DebugConsole # #581: tilde debug console
|
||||
@onready var news_ticker = $UILayer/NewsTicker # #592: scrolling headline bar (D-049 z-7)
|
||||
@onready var star_map = $UILayer/HUD/StarMap # #674: star map insert module (hop-ring view)
|
||||
@onready var star_map = $InsertOverlay/HUD/StarMap # #674: star map insert module (hop-ring view)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -135,8 +134,8 @@ func _ready() -> void:
|
||||
_router.register_always(gauntlet_hud.update_from_state)
|
||||
if checklist_overlay:
|
||||
_router.register_always(checklist_overlay.update_from_state)
|
||||
if time_display:
|
||||
_router.register_always(time_display.update_from_state)
|
||||
if hud:
|
||||
_router.register_always(hud.update_from_state)
|
||||
if news_ticker:
|
||||
_router.register_always(news_ticker.update_from_state)
|
||||
if journal_panel:
|
||||
|
||||
@@ -222,15 +222,9 @@ func test_interact_with_data_encodes_as_data_variant() -> void:
|
||||
|
||||
# -- Helpers --
|
||||
|
||||
func _make_prompt() -> PanelContainer:
|
||||
func _make_prompt() -> Control:
|
||||
var PromptScript = load("res://ui/interaction_prompt.gd")
|
||||
var panel = PanelContainer.new()
|
||||
panel.set_script(PromptScript)
|
||||
var margin = MarginContainer.new()
|
||||
margin.name = "MarginContainer"
|
||||
panel.add_child(margin)
|
||||
var label = Label.new()
|
||||
label.name = "PromptLabel"
|
||||
margin.add_child(label)
|
||||
add_child(panel)
|
||||
return panel
|
||||
var ctrl = Control.new()
|
||||
ctrl.set_script(PromptScript)
|
||||
add_child(ctrl) # triggers _ready() which creates ImplantPanel + ImplantDataRow
|
||||
return ctrl
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
## D-031 (game time: 10 ticks = 1 game-minute, 1440 min/day, HH:MM display)
|
||||
## D-051 (diegetic insert display)
|
||||
##
|
||||
## Implementation: client/ui/time_display.gd — draw-based Control at InsertOverlay/TimeDisplay.
|
||||
## Implementation: client/ui/time_display.gd — draw-based Control (removed from main scene #786;
|
||||
## time display merged into HUD ImplantPanel at UILayer/HUD, see test_hud_* tests below).
|
||||
## Format function: Constants.format_game_time(time_of_day: int) -> String (extracted for
|
||||
## testability from time_display.gd:46 inline Constants.format_game_time(tod)).
|
||||
## testability from time_display.gd).
|
||||
##
|
||||
## Private state access: Tests read _time_str, _phase_str, _day_str, _has_data directly
|
||||
## because time_display.gd is draw-based (no Label nodes to inspect). This is an accepted
|
||||
## test pattern for draw-based UI — the private vars ARE the rendered output contract.
|
||||
## If the rendering approach changes (e.g. to Label nodes), these tests should switch to
|
||||
## reading Label.text via public node paths instead.
|
||||
## Scene integration tests now check UILayer/HUD.get_time_text() instead.
|
||||
class_name TestTimeDisplaySprint17
|
||||
extends GdUnitTestSuite
|
||||
|
||||
@@ -273,18 +273,18 @@ func test_sim_bridge_day_phase_is_valid() -> void:
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Scene: InsertClock node at InsertOverlay/TimeDisplay
|
||||
# Scene: HUD panel at UILayer/HUD (time row merged in, TimeDisplay removed #786)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_insert_clock_exists_in_ui_layer() -> void:
|
||||
func test_hud_panel_exists_in_ui_layer() -> void:
|
||||
var scene := MAIN_SCENE
|
||||
var instance = scene.instantiate()
|
||||
auto_free(instance)
|
||||
add_child(instance)
|
||||
|
||||
assert_that(instance.get_node_or_null("InsertOverlay/TimeDisplay")).is_not_null()
|
||||
assert_that(instance.get_node_or_null("UILayer/HUD")).is_not_null()
|
||||
|
||||
func test_insert_clock_time_str_updates_after_process() -> void:
|
||||
func test_hud_time_row_updates_after_process() -> void:
|
||||
var scene := MAIN_SCENE
|
||||
var instance = scene.instantiate()
|
||||
auto_free(instance)
|
||||
@@ -296,9 +296,9 @@ func test_insert_clock_time_str_updates_after_process() -> void:
|
||||
})
|
||||
instance._process(0.016)
|
||||
|
||||
var clock = instance.get_node_or_null("InsertOverlay/TimeDisplay")
|
||||
assert_that(clock).is_not_null()
|
||||
assert_that(clock._time_str).is_equal("12:00")
|
||||
var hud = instance.get_node_or_null("UILayer/HUD")
|
||||
assert_that(hud).is_not_null()
|
||||
assert_that(hud.get_time_text()).is_equal("12:00 · Afternoon · D1")
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
+20
-3
@@ -1,9 +1,10 @@
|
||||
extends Control
|
||||
|
||||
## HUD — implant-styled status panel (D-169, D-170).
|
||||
## Displays health and perception mode. Time is handled by TimeDisplay.
|
||||
## Merged panel: Time, Health, Perception mode. Time row replaces standalone TimeDisplay (#786).
|
||||
|
||||
var _panel: ImplantPanel
|
||||
var _time_row: ImplantDataRow
|
||||
var _health_row: ImplantDataRow
|
||||
var _perception_row: ImplantDataRow
|
||||
|
||||
@@ -22,13 +23,14 @@ func _ready() -> void:
|
||||
_panel.custom_minimum_size.x = 200.0
|
||||
_panel.size.x = 200.0
|
||||
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_panel.position = Vector2(16, 80) # below TimeDisplay (occupies 16-70)
|
||||
_panel.position = Vector2(16, 16) # top-left; replaces standalone TimeDisplay
|
||||
add_child(_panel)
|
||||
_panel.move_child(_panel.get_node("Content"), 0)
|
||||
|
||||
_time_row = ImplantDataRow.new("--:--")
|
||||
_health_row = ImplantDataRow.new("Health: 100")
|
||||
_perception_row = ImplantDataRow.new("Mode: Baseline")
|
||||
|
||||
_panel.add_component(_time_row)
|
||||
_panel.add_component(_health_row)
|
||||
_panel.add_component(_perception_row)
|
||||
|
||||
@@ -46,3 +48,18 @@ func update_from_hud_data(data: Dictionary) -> void:
|
||||
|
||||
func update_health(health: int) -> void:
|
||||
_health_row.text = UIStrings.get_text("hud.health") + ": " + str(health)
|
||||
|
||||
|
||||
func update_from_state() -> void:
|
||||
var gt: Dictionary = GameState.game_time
|
||||
if gt.is_empty():
|
||||
return
|
||||
var tod: int = int(gt.get("time_of_day", 0))
|
||||
var day: int = int(gt.get("day", 0))
|
||||
var phase: String = str(gt.get("day_phase", ""))
|
||||
_time_row.text = "%s · %s · D%d" % [Constants.format_game_time(tod), phase, day + 1]
|
||||
|
||||
|
||||
## Returns the time row text. Used by tests after standalone TimeDisplay was removed.
|
||||
func get_time_text() -> String:
|
||||
return _time_row.text if _time_row else "--:--"
|
||||
|
||||
+4
-27
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cq1y5w3hmxr8b"]
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cq1y5w3hmxr8b"]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/hud.gd" id="1_hud"]
|
||||
[ext_resource type="PackedScene" path="res://ui/star_map.tscn" id="2_starmap"]
|
||||
@@ -13,32 +13,9 @@ grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1_hud")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 100.0
|
||||
grow_horizontal = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 16
|
||||
theme_override_constants/margin_right = 16
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HealthLabel" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Health: 100"
|
||||
|
||||
[node name="PerceptionLabel" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Mode: Baseline"
|
||||
|
||||
[node name="TimeLabel" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Time: 08:00"
|
||||
|
||||
; #674: Star map insert — D-170 z-index layer managed, not visibility toggled
|
||||
; #674: Star map — full-screen insert overlay, hidden until player activates (M key / insert UI).
|
||||
; Toggled via star_map.toggle_visible() from main.gd.
|
||||
; TODO: migrate to HudGroups.open_app("implant/map/starchart") per D-170.
|
||||
[node name="StarMap" parent="." instance=ExtResource("2_starmap")]
|
||||
visible = false
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
extends PanelContainer
|
||||
extends Control
|
||||
|
||||
# Interaction prompt — displays available interaction verb for nearby entity.
|
||||
# Server-driven: shows when GameState.nearby_interactions is non-empty,
|
||||
@@ -17,24 +17,28 @@ var _current_target_id: int = -1
|
||||
# OQ-07 (#522): when false, prompt is suppressed (z-layer 6 insert overlay only)
|
||||
var _insert_active: bool = true
|
||||
|
||||
@onready var prompt_label: Label = $MarginContainer/PromptLabel
|
||||
var _panel: ImplantPanel
|
||||
var _row: ImplantDataRow
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
modulate.a = 0.0
|
||||
visible = false
|
||||
_is_showing = false
|
||||
# D-169: Apply implant theme
|
||||
var t := load("res://ui/implant/default_implant.tres") as ImplantTheme
|
||||
if t:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = t.panel_bg
|
||||
style.border_color = t.separator
|
||||
style.set_border_width_all(int(t.border_width))
|
||||
style.set_content_margin_all(t.padding)
|
||||
add_theme_stylebox_override("panel", style)
|
||||
prompt_label.add_theme_color_override("font_color", t.text_primary)
|
||||
prompt_label.add_theme_font_size_override("font_size", t.font_body)
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
var theme_res := load("res://ui/implant/default_implant.tres") as ImplantTheme
|
||||
|
||||
_panel = ImplantPanel.new()
|
||||
_panel.name = "PromptPanel"
|
||||
_panel.theme_resource = theme_res
|
||||
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(_panel)
|
||||
|
||||
_row = ImplantDataRow.new("", theme_res.text_primary if theme_res else Color.WHITE)
|
||||
_row.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_panel.add_component(_row)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
@@ -63,8 +67,8 @@ func _show_prompt(interaction: Dictionary) -> void:
|
||||
var verb_label: String = verbs[0].get("label", "")
|
||||
var display_text: String = "E - %s" % verb_label
|
||||
|
||||
if _current_target_id != target_id or prompt_label.text != display_text:
|
||||
prompt_label.text = display_text
|
||||
if _current_target_id != target_id or _row.text != display_text:
|
||||
_row.set_content(display_text)
|
||||
_current_target_id = target_id
|
||||
|
||||
if not _is_showing:
|
||||
|
||||
@@ -2,28 +2,17 @@
|
||||
|
||||
[ext_resource type="Script" path="res://ui/interaction_prompt.gd" id="1_prompt"]
|
||||
|
||||
[node name="InteractionPrompt" type="PanelContainer"]
|
||||
; Interaction prompt — implant-styled (D-169), positioned above DialogueBox, 16px gap
|
||||
[node name="InteractionPrompt" type="Control"]
|
||||
anchors_preset = 7
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 200.0
|
||||
offset_top = -240.0
|
||||
offset_top = -264.0
|
||||
offset_right = -200.0
|
||||
offset_bottom = -210.0
|
||||
offset_bottom = -222.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1_prompt")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="PromptLabel" type="Label" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
text = ""
|
||||
|
||||
Reference in New Issue
Block a user