NEO-122: Pin economy HUD to viewport so recipes stay visible.
Reparent EconomyHudSection out of HudRoot, clamp its height to the viewport, put recipes first in a single BodyScroll, and update tests.pull/161/head
parent
e041c02959
commit
d1fa08b8c4
|
|
@ -207,7 +207,7 @@ Epic 5 Slice 1 capstone — tab-target lock, cast, defeat, and gig XP visibility
|
|||
|
||||
**Scripts:** `ability_cast_client.gd`, `combat_targets_client.gd`, `gig_progression_client.gd` — wired from `main.gd`.
|
||||
|
||||
**Economy HUD:** **`GigXpLabel`** lives under **`EconomyHudSection/Body/`** (hidden when **`Economy HUD`** collapsed, NEO-75). Gig GET refreshes on boot and when cast accept carries **`targetDefeated: true`** only — not on every hit.
|
||||
**Economy HUD:** **`GigXpLabel`** lives under **`EconomyHudSection/BodyScroll/Body/`** (hidden when **`Economy HUD`** collapsed, NEO-75). Gig GET refreshes on boot and when cast accept carries **`targetDefeated: true`** only — not on every hit.
|
||||
|
||||
**Preconditions:** **Server restart** before capstone run resets in-memory dummy HP and gig XP baseline.
|
||||
|
||||
|
|
@ -267,7 +267,7 @@ Full capstone checklist: [`docs/manual-qa/NEO-111.md`](../docs/manual-qa/NEO-111
|
|||
- **`GET /game/players/{id}/inventory`** — server-authoritative bag (**24** slots) + equipment stub (**1** slot); see [server README — Player inventory](../server/README.md#player-inventory-neo-54-store-neo-55-http).
|
||||
- **`GET /game/world/item-definitions`** — cached on boot for **`displayName`** labels (NEO-53).
|
||||
- **Scripts:** `scripts/inventory_client.gd`, `scripts/item_definitions_client.gd`; wired from `main.gd` on boot and on **`inventory_refresh`** (**I**).
|
||||
- **HUD:** `UICanvas/EconomyHudSection/Body/InventoryLabel` lists **non-empty bag** rows and **equipment slot 0** (shows **`— empty`** when unoccupied). Failed GET paints **`error — …`** on the label and **`push_warning`** in Output. **`Economy HUD`** checkbox (NEO-75) collapses the economy block.
|
||||
- **HUD:** `UICanvas/EconomyHudSection/BodyScroll/Body/InventoryLabel` lists **non-empty bag** rows and **equipment slot 0** (shows **`— empty`** when unoccupied). Failed GET paints **`error — …`** on the label and **`push_warning`** in Output. **`Economy HUD`** checkbox (NEO-75) collapses the economy block.
|
||||
- **Refresh:** press **I** (or call **`InventoryClient.request_sync_from_server()`** from `main.gd` helpers — entrypoint for NEO-73 gather/craft refresh).
|
||||
|
||||
Full checklist: [`docs/manual-qa/NEO-72.md`](../docs/manual-qa/NEO-72.md).
|
||||
|
|
@ -275,9 +275,9 @@ Full checklist: [`docs/manual-qa/NEO-72.md`](../docs/manual-qa/NEO-72.md).
|
|||
## Gather feedback on interact (NEO-73)
|
||||
|
||||
- **`POST /game/players/{id}/interact`** on **`resource_node`** kinds — server gather engine (NEO-63); no separate gather HTTP on client.
|
||||
- **`GET /game/players/{id}/skill-progression`** — **`salvage`** row on **`UICanvas/EconomyHudSection/Body/SkillProgressionLabel`**; boot hydrate + refresh after successful gather.
|
||||
- **`GET /game/players/{id}/skill-progression`** — **`salvage`** row on **`UICanvas/EconomyHudSection/BodyScroll/Body/SkillProgressionLabel`**; boot hydrate + refresh after successful gather.
|
||||
- **Scripts:** `scripts/skill_progression_client.gd`, `scripts/prototype_interactable_picker.gd`; **`interaction_request_client.gd`** emits **`interaction_result_received`**; wired from `main.gd`.
|
||||
- **HUD:** `UICanvas/GatherFeedbackLabel` — success delta **`+N {displayName}`** or deny **`Gather: denied — {reasonCode}`** (including **`node_depleted`**). **`UICanvas/EconomyHudSection/Body/InventoryLabel`** refreshes automatically on successful gather (no **I**).
|
||||
- **HUD:** `UICanvas/GatherFeedbackLabel` — success delta **`+N {displayName}`** or deny **`Gather: denied — {reasonCode}`** (including **`node_depleted`**). **`UICanvas/EconomyHudSection/BodyScroll/Body/InventoryLabel`** refreshes automatically on successful gather (no **I**).
|
||||
- **R binding:** nearest in-range **`resource_node`** by horizontal distance to catalog anchors (same radius rule as glow preview).
|
||||
|
||||
Full checklist: [`docs/manual-qa/NEO-73.md`](../docs/manual-qa/NEO-73.md).
|
||||
|
|
@ -287,7 +287,7 @@ Full checklist: [`docs/manual-qa/NEO-73.md`](../docs/manual-qa/NEO-73.md).
|
|||
- **`GET /game/world/recipe-definitions`** — eight prototype recipes on boot; scrollable panel inside **`EconomyHudSection`** (below **`SkillProgressionLabel`**).
|
||||
- **`POST /game/players/{id}/craft`** — JSON body `schemaVersion`, `recipeId`, `quantity` (default **1**); see [server README — Craft HTTP](../server/README.md).
|
||||
- **Scripts:** `scripts/recipe_definitions_client.gd`, `scripts/craft_client.gd`, `scripts/craft_recipe_panel.gd`; wired from `main.gd`.
|
||||
- **HUD:** `UICanvas/CraftFeedbackLabel` — success **`Craft: +N {displayName}`** from POST **`outputsGranted`**; deny **`Craft: denied — {reasonCode}`**. **`UICanvas/EconomyHudSection/Body/SkillProgressionLabel`** adds **`refine`** row (boot + post-craft refresh). **`UICanvas/EconomyHudSection/Body/InventoryLabel`** refreshes on craft success only (no **I**).
|
||||
- **HUD:** `UICanvas/CraftFeedbackLabel` — success **`Craft: +N {displayName}`** from POST **`outputsGranted`**; deny **`Craft: denied — {reasonCode}`**. **`UICanvas/EconomyHudSection/BodyScroll/Body/SkillProgressionLabel`** adds **`refine`** row (boot + post-craft refresh). **`UICanvas/EconomyHudSection/BodyScroll/Body/InventoryLabel`** refreshes on craft success only (no **I**).
|
||||
- **Panel:** per-recipe **Craft** button (qty **1**); input summary uses cached item **`displayName`** when available.
|
||||
|
||||
Full checklist: [`docs/manual-qa/NEO-74.md`](../docs/manual-qa/NEO-74.md).
|
||||
|
|
@ -307,7 +307,7 @@ Capstone proof that Epic 3 prototype gather→craft works **in Godot** without B
|
|||
|
||||
**Material minimum:** **11** scrap from one **R** at each of four prototype nodes (+1, +2, +3, +5) → two **`refine_scrap_standard`** → one **`make_field_stim_mk0`**.
|
||||
|
||||
**HUD paths:** feedback labels stay on **`UICanvas`**; economy block under **`UICanvas/EconomyHudSection/Body/`** (`InventoryLabel`, `SkillProgressionLabel`, `CraftRecipePanel`).
|
||||
**HUD paths:** feedback labels stay on **`UICanvas`**; economy block under **`UICanvas/EconomyHudSection/BodyScroll/Body/`** (`InventoryLabel`, `SkillProgressionLabel`, `CraftRecipePanel`).
|
||||
|
||||
Full capstone checklist: [`docs/manual-qa/NEO-75.md`](../docs/manual-qa/NEO-75.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -1303,24 +1303,61 @@ theme_override_font_sizes/font_size = 20
|
|||
autowrap_mode = 3
|
||||
text = "Cooldowns: —"
|
||||
|
||||
[node name="EconomyHudSection" type="VBoxContainer" parent="UICanvas/HudRoot" unique_id=9000014]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
[node name="EconomyHudSection" type="VBoxContainer" parent="UICanvas" unique_id=9000014]
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = -440.0
|
||||
offset_right = 664.0
|
||||
offset_bottom = -8.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
script = ExtResource("20_economy_hud")
|
||||
|
||||
[node name="HeaderRow" type="HBoxContainer" parent="UICanvas/HudRoot/EconomyHudSection" unique_id=9000015]
|
||||
[node name="HeaderRow" type="HBoxContainer" parent="UICanvas/EconomyHudSection" unique_id=9000015]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ToggleButton" type="CheckButton" parent="UICanvas/HudRoot/EconomyHudSection/HeaderRow" unique_id=9000016]
|
||||
[node name="ToggleButton" type="CheckButton" parent="UICanvas/EconomyHudSection/HeaderRow" unique_id=9000016]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 20
|
||||
text = "Economy HUD"
|
||||
|
||||
[node name="Body" type="VBoxContainer" parent="UICanvas/HudRoot/EconomyHudSection" unique_id=9000017]
|
||||
[node name="BodyScroll" type="ScrollContainer" parent="UICanvas/EconomyHudSection" unique_id=9000027]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
custom_minimum_size = Vector2(656, 380)
|
||||
horizontal_scroll_mode = 0
|
||||
vertical_scroll_mode = 1
|
||||
follow_focus = true
|
||||
|
||||
[node name="Body" type="VBoxContainer" parent="UICanvas/EconomyHudSection/BodyScroll" unique_id=9000017]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="InventoryLabel" type="Label" parent="UICanvas/HudRoot/EconomyHudSection/Body" unique_id=9000007]
|
||||
[node name="RecipesHeaderLabel" type="Label" parent="UICanvas/EconomyHudSection/BodyScroll/Body" unique_id=9000028]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_colors/font_color = Color(0.9, 0.86, 0.98, 1)
|
||||
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
|
||||
theme_override_constants/outline_size = 8
|
||||
theme_override_font_sizes/font_size = 20
|
||||
text = "Recipes:"
|
||||
|
||||
[node name="CraftRecipePanel" type="VBoxContainer" parent="UICanvas/EconomyHudSection/BodyScroll/Body" unique_id=9000011]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource("19_craft_panel")
|
||||
|
||||
[node name="RecipeRows" type="VBoxContainer" parent="UICanvas/EconomyHudSection/BodyScroll/Body/CraftRecipePanel" unique_id=9000013]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="InventoryLabel" type="Label" parent="UICanvas/EconomyHudSection/BodyScroll/Body" unique_id=9000007]
|
||||
custom_minimum_size = Vector2(656, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
|
@ -1332,7 +1369,7 @@ autowrap_mode = 3
|
|||
text = "Inventory: (refresh: I)
|
||||
Loading…"
|
||||
|
||||
[node name="SkillProgressionLabel" type="Label" parent="UICanvas/HudRoot/EconomyHudSection/Body" unique_id=9000009]
|
||||
[node name="SkillProgressionLabel" type="Label" parent="UICanvas/EconomyHudSection/BodyScroll/Body" unique_id=9000009]
|
||||
custom_minimum_size = Vector2(656, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
|
@ -1344,7 +1381,7 @@ autowrap_mode = 3
|
|||
text = "Skills:
|
||||
Loading…"
|
||||
|
||||
[node name="GigXpLabel" type="Label" parent="UICanvas/HudRoot/EconomyHudSection/Body" unique_id=9000018]
|
||||
[node name="GigXpLabel" type="Label" parent="UICanvas/EconomyHudSection/BodyScroll/Body" unique_id=9000018]
|
||||
custom_minimum_size = Vector2(656, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
|
@ -1355,25 +1392,3 @@ theme_override_font_sizes/font_size = 20
|
|||
autowrap_mode = 3
|
||||
text = "Gig XP:
|
||||
Loading…"
|
||||
|
||||
[node name="CraftRecipePanel" type="Control" parent="UICanvas/HudRoot/EconomyHudSection/Body" unique_id=9000011]
|
||||
custom_minimum_size = Vector2(656, 360)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource("19_craft_panel")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="UICanvas/HudRoot/EconomyHudSection/Body/CraftRecipePanel" unique_id=9000012]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
horizontal_scroll_mode = 0
|
||||
vertical_scroll_mode = 1
|
||||
follow_focus = true
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="UICanvas/HudRoot/EconomyHudSection/Body/CraftRecipePanel/ScrollContainer" unique_id=9000013]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 8
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
extends Control
|
||||
extends VBoxContainer
|
||||
|
||||
## NEO-74: scrollable prototype recipe list with per-row Craft buttons.
|
||||
## NEO-74: prototype recipe list with per-row Craft buttons.
|
||||
## NEO-122: rows live in economy BodyScroll (no nested scroll).
|
||||
|
||||
signal craft_requested(recipe_id: String)
|
||||
|
||||
|
|
@ -10,15 +11,7 @@ const HudThemeConstants := preload("res://scripts/prototype_hud_theme_constants.
|
|||
var _item_defs_client: Node = null
|
||||
var _craft_buttons: Array[Button] = []
|
||||
|
||||
@onready var _scroll: ScrollContainer = $ScrollContainer
|
||||
@onready var _vbox: VBoxContainer = $ScrollContainer/VBoxContainer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if is_instance_valid(_scroll):
|
||||
_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO
|
||||
resized.connect(_sync_vbox_width)
|
||||
@onready var _rows: VBoxContainer = $RecipeRows
|
||||
|
||||
|
||||
func setup(item_defs_client: Node) -> void:
|
||||
|
|
@ -26,9 +19,9 @@ func setup(item_defs_client: Node) -> void:
|
|||
|
||||
|
||||
func populate(recipes: Array) -> void:
|
||||
if not is_instance_valid(_vbox):
|
||||
if not is_instance_valid(_rows):
|
||||
return
|
||||
for child in _vbox.get_children():
|
||||
for child in _rows.get_children():
|
||||
child.queue_free()
|
||||
_craft_buttons.clear()
|
||||
for row_variant in recipes:
|
||||
|
|
@ -55,9 +48,9 @@ func populate(recipes: Array) -> void:
|
|||
craft_btn.pressed.connect(_on_craft_pressed.bind(recipe_id))
|
||||
btn_row.add_child(craft_btn)
|
||||
row.add_child(btn_row)
|
||||
_vbox.add_child(row)
|
||||
_rows.add_child(row)
|
||||
_craft_buttons.append(craft_btn)
|
||||
call_deferred("_reset_scroll_after_populate")
|
||||
call_deferred("_scroll_economy_body_to_recipes")
|
||||
|
||||
|
||||
func set_craft_busy(busy: bool) -> void:
|
||||
|
|
@ -107,16 +100,10 @@ func _apply_recipe_row_label(label: Label) -> void:
|
|||
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
|
||||
|
||||
func _sync_vbox_width() -> void:
|
||||
if not is_instance_valid(_scroll) or not is_instance_valid(_vbox):
|
||||
func _scroll_economy_body_to_recipes() -> void:
|
||||
var node: Node = get_parent()
|
||||
while node != null:
|
||||
if node.has_method("scroll_body_to_recipes"):
|
||||
node.call("scroll_body_to_recipes")
|
||||
return
|
||||
var width: float = _scroll.size.x
|
||||
if width <= 0.0:
|
||||
return
|
||||
_vbox.custom_minimum_size.x = width
|
||||
|
||||
|
||||
func _reset_scroll_after_populate() -> void:
|
||||
_sync_vbox_width()
|
||||
if is_instance_valid(_scroll):
|
||||
_scroll.scroll_vertical = 0
|
||||
node = node.get_parent()
|
||||
|
|
|
|||
|
|
@ -141,14 +141,14 @@ var _dev_pulse_bootstrap_attempted: bool = false
|
|||
@onready var _npc_state_label: Label = $UICanvas/HudRoot/NpcStateLabel
|
||||
@onready var _telegraph_label: Label = $UICanvas/HudRoot/TelegraphLabel
|
||||
@onready var _cooldown_slots_label: Label = $UICanvas/HudRoot/CooldownSlotsLabel
|
||||
@onready var _economy_hud_section: VBoxContainer = $UICanvas/HudRoot/EconomyHudSection
|
||||
@onready var _inventory_label: Label = _economy_hud_section.get_node("Body/InventoryLabel")
|
||||
@onready var _economy_hud_section: VBoxContainer = $UICanvas/EconomyHudSection
|
||||
@onready var _inventory_label: Label = _economy_hud_section.get_node("BodyScroll/Body/InventoryLabel")
|
||||
@onready var _gather_feedback_label: Label = $UICanvas/HudRoot/GatherFeedbackLabel
|
||||
@onready var _craft_feedback_label: Label = $UICanvas/HudRoot/CraftFeedbackLabel
|
||||
@onready
|
||||
var _skill_progression_label: Label = _economy_hud_section.get_node("Body/SkillProgressionLabel")
|
||||
@onready var _gig_xp_label: Label = _economy_hud_section.get_node("Body/GigXpLabel")
|
||||
@onready var _craft_recipe_panel: Node = _economy_hud_section.get_node("Body/CraftRecipePanel")
|
||||
var _skill_progression_label: Label = _economy_hud_section.get_node("BodyScroll/Body/SkillProgressionLabel")
|
||||
@onready var _gig_xp_label: Label = _economy_hud_section.get_node("BodyScroll/Body/GigXpLabel")
|
||||
@onready var _craft_recipe_panel: Node = _economy_hud_section.get_node("BodyScroll/Body/CraftRecipePanel")
|
||||
@onready var _inventory_client: Node = $InventoryClient
|
||||
@onready var _item_defs_client: Node = $ItemDefinitionsClient
|
||||
@onready var _skill_progression_client: Node = $SkillProgressionClient
|
||||
|
|
@ -309,6 +309,8 @@ func _apply_prototype_hud_theme() -> void:
|
|||
PrototypeHudTheme.apply_to_label(_move_reject_label, true)
|
||||
if is_instance_valid(_hud_root):
|
||||
PrototypeHudTheme.apply_to_hud_root(_hud_root)
|
||||
if is_instance_valid(_economy_hud_section):
|
||||
PrototypeHudTheme.apply_to_economy_section(_economy_hud_section)
|
||||
|
||||
|
||||
func _on_authoritative_ack_for_hud(world: Vector3) -> void:
|
||||
|
|
|
|||
|
|
@ -1,25 +1,70 @@
|
|||
extends VBoxContainer
|
||||
|
||||
## NEO-75: collapsible economy HUD block (inventory, skills, craft panel).
|
||||
## NEO-122: pinned to viewport bottom-left so recipes stay on-screen.
|
||||
|
||||
const MAX_VIEWPORT_HEIGHT_FRACTION := 0.48
|
||||
const MIN_BODY_SCROLL_HEIGHT := 180.0
|
||||
const BOTTOM_MARGIN := 8.0
|
||||
const SIDE_MARGIN := 8.0
|
||||
const PANEL_WIDTH := 656.0
|
||||
|
||||
@onready var _toggle: CheckButton = $HeaderRow/ToggleButton
|
||||
@onready var _body: VBoxContainer = $Body
|
||||
@onready var _body_scroll: ScrollContainer = $BodyScroll
|
||||
@onready var _recipes_header: Label = $BodyScroll/Body/RecipesHeaderLabel
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_toggle.button_pressed = true
|
||||
_body.visible = true
|
||||
_body_scroll.visible = true
|
||||
_toggle.toggled.connect(_on_toggle_toggled)
|
||||
get_viewport().size_changed.connect(_apply_viewport_layout)
|
||||
call_deferred("_apply_viewport_layout")
|
||||
|
||||
|
||||
func _on_toggle_toggled(pressed: bool) -> void:
|
||||
_body.visible = pressed
|
||||
_body_scroll.visible = pressed
|
||||
call_deferred("_apply_viewport_layout")
|
||||
|
||||
|
||||
func set_expanded(expanded: bool) -> void:
|
||||
_toggle.button_pressed = expanded
|
||||
_body.visible = expanded
|
||||
_body_scroll.visible = expanded
|
||||
call_deferred("_apply_viewport_layout")
|
||||
|
||||
|
||||
func is_body_visible() -> bool:
|
||||
return _body.visible
|
||||
return _body_scroll.visible
|
||||
|
||||
|
||||
func scroll_body_to_recipes() -> void:
|
||||
if not is_instance_valid(_body_scroll) or not is_instance_valid(_recipes_header):
|
||||
return
|
||||
_body_scroll.scroll_vertical = int(_recipes_header.position.y)
|
||||
|
||||
|
||||
func _apply_viewport_layout() -> void:
|
||||
if not is_instance_valid(_body_scroll):
|
||||
return
|
||||
var vp_height: float = get_viewport().get_visible_rect().size.y
|
||||
var header_height: float = _measure_header_height()
|
||||
var body_height: float = max(
|
||||
MIN_BODY_SCROLL_HEIGHT, vp_height * MAX_VIEWPORT_HEIGHT_FRACTION - header_height
|
||||
)
|
||||
_body_scroll.custom_minimum_size = Vector2(PANEL_WIDTH, body_height)
|
||||
var panel_height: float = header_height
|
||||
if _body_scroll.visible:
|
||||
panel_height += body_height
|
||||
offset_left = SIDE_MARGIN
|
||||
offset_right = SIDE_MARGIN + PANEL_WIDTH
|
||||
offset_bottom = -BOTTOM_MARGIN
|
||||
offset_top = -panel_height - BOTTOM_MARGIN
|
||||
|
||||
|
||||
func _measure_header_height() -> float:
|
||||
if not is_instance_valid(_toggle):
|
||||
return 32.0
|
||||
var header_row: Node = _toggle.get_parent()
|
||||
if header_row is Control:
|
||||
return max(32.0, (header_row as Control).size.y)
|
||||
return 32.0
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ static func apply_to_hud_root(root: VBoxContainer) -> void:
|
|||
if child is Label:
|
||||
var label: Label = child as Label
|
||||
apply_to_label(label, not Constants.SECONDARY_LABEL_NAMES.has(label.name))
|
||||
elif child.name == "EconomyHudSection":
|
||||
apply_to_economy_section(child)
|
||||
|
||||
|
||||
static func apply_to_economy_section(section: Node) -> void:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
extends GdUnitTestSuite
|
||||
|
||||
## NEO-122 follow-up: craft recipe panel layout + scroll reset on populate.
|
||||
## NEO-122 follow-up: craft recipe panel rows + economy body scroll reset.
|
||||
|
||||
const CraftRecipePanel := preload("res://scripts/craft_recipe_panel.gd")
|
||||
const EconomyHudSection := preload("res://scripts/prototype_economy_hud_section.gd")
|
||||
|
||||
|
||||
static func _sample_recipes() -> Array:
|
||||
|
|
@ -22,20 +23,44 @@ static func _sample_recipes() -> Array:
|
|||
|
||||
func _build_panel() -> Control:
|
||||
var panel: Control = CraftRecipePanel.new()
|
||||
panel.custom_minimum_size = Vector2(656, 360)
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.name = "ScrollContainer"
|
||||
scroll.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.name = "VBoxContainer"
|
||||
scroll.add_child(vbox)
|
||||
panel.add_child(scroll)
|
||||
var rows := VBoxContainer.new()
|
||||
rows.name = "RecipeRows"
|
||||
panel.add_child(rows)
|
||||
auto_free(panel)
|
||||
add_child(panel)
|
||||
await get_tree().process_frame
|
||||
return panel
|
||||
|
||||
|
||||
func _build_panel_in_economy_section() -> Dictionary:
|
||||
var section: VBoxContainer = EconomyHudSection.new()
|
||||
var header := HBoxContainer.new()
|
||||
header.name = "HeaderRow"
|
||||
var toggle := CheckButton.new()
|
||||
toggle.name = "ToggleButton"
|
||||
header.add_child(toggle)
|
||||
var body_scroll := ScrollContainer.new()
|
||||
body_scroll.name = "BodyScroll"
|
||||
var body := VBoxContainer.new()
|
||||
body.name = "Body"
|
||||
var recipes_header := Label.new()
|
||||
recipes_header.name = "RecipesHeaderLabel"
|
||||
body.add_child(recipes_header)
|
||||
var panel: Control = CraftRecipePanel.new()
|
||||
panel.name = "CraftRecipePanel"
|
||||
var rows := VBoxContainer.new()
|
||||
rows.name = "RecipeRows"
|
||||
panel.add_child(rows)
|
||||
body.add_child(panel)
|
||||
body_scroll.add_child(body)
|
||||
section.add_child(header)
|
||||
section.add_child(body_scroll)
|
||||
auto_free(section)
|
||||
add_child(section)
|
||||
await get_tree().process_frame
|
||||
return {"section": section, "panel": panel, "body_scroll": body_scroll}
|
||||
|
||||
|
||||
func test_populate_builds_one_row_per_recipe() -> void:
|
||||
# Arrange
|
||||
var panel := await _build_panel()
|
||||
|
|
@ -43,22 +68,22 @@ func test_populate_builds_one_row_per_recipe() -> void:
|
|||
panel.call("populate", _sample_recipes())
|
||||
await get_tree().process_frame
|
||||
# Assert
|
||||
var vbox: VBoxContainer = panel.get_node("ScrollContainer/VBoxContainer")
|
||||
assert_that(vbox.get_child_count()).is_equal(2)
|
||||
var rows: VBoxContainer = panel.get_node("RecipeRows")
|
||||
assert_that(rows.get_child_count()).is_equal(2)
|
||||
|
||||
|
||||
func test_populate_resets_scroll_to_top() -> void:
|
||||
func test_populate_scrolls_economy_body_to_recipes() -> void:
|
||||
# Arrange
|
||||
var panel := await _build_panel()
|
||||
var scroll: ScrollContainer = panel.get_node("ScrollContainer")
|
||||
panel.call("populate", _sample_recipes())
|
||||
await get_tree().process_frame
|
||||
scroll.scroll_vertical = 999
|
||||
var built: Dictionary = await _build_panel_in_economy_section()
|
||||
var panel: Control = built["panel"]
|
||||
var body_scroll: ScrollContainer = built["body_scroll"]
|
||||
body_scroll.scroll_vertical = 999
|
||||
# Act
|
||||
panel.call("populate", _sample_recipes())
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
# Assert
|
||||
assert_that(scroll.scroll_vertical).is_equal(0)
|
||||
assert_that(body_scroll.scroll_vertical).is_equal(0)
|
||||
|
||||
|
||||
func test_populate_emits_craft_requested_for_row() -> void:
|
||||
|
|
@ -70,8 +95,8 @@ func test_populate_emits_craft_requested_for_row() -> void:
|
|||
)
|
||||
panel.call("populate", _sample_recipes())
|
||||
await get_tree().process_frame
|
||||
var vbox: VBoxContainer = panel.get_node("ScrollContainer/VBoxContainer")
|
||||
var first_row: VBoxContainer = vbox.get_child(0) as VBoxContainer
|
||||
var rows: VBoxContainer = panel.get_node("RecipeRows")
|
||||
var first_row: VBoxContainer = rows.get_child(0) as VBoxContainer
|
||||
var btn_row: HBoxContainer = first_row.get_child(1) as HBoxContainer
|
||||
var craft_btn: Button = btn_row.get_child(0) as Button
|
||||
# Act
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
extends GdUnitTestSuite
|
||||
|
||||
## NEO-75: economy HUD collapse toggle shows/hides body container.
|
||||
## Covers economy HUD node paths via EconomyHudSection.get_node in main.gd.
|
||||
## NEO-75: economy HUD collapse toggle shows/hides body scroll container.
|
||||
## NEO-122: BodyScroll/Body paths match main.tscn.
|
||||
|
||||
const EconomyHudSection := preload("res://scripts/prototype_economy_hud_section.gd")
|
||||
|
||||
|
|
@ -14,13 +14,16 @@ func _build_section() -> Node:
|
|||
toggle.name = "ToggleButton"
|
||||
toggle.text = "Economy HUD"
|
||||
header.add_child(toggle)
|
||||
var body_scroll := ScrollContainer.new()
|
||||
body_scroll.name = "BodyScroll"
|
||||
var body := VBoxContainer.new()
|
||||
body.name = "Body"
|
||||
var inventory := Label.new()
|
||||
inventory.name = "InventoryLabel"
|
||||
body.add_child(inventory)
|
||||
body_scroll.add_child(body)
|
||||
section.add_child(header)
|
||||
section.add_child(body)
|
||||
section.add_child(body_scroll)
|
||||
return section
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue