extends VBoxContainer ## 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) const PrototypeHudTheme := preload("res://scripts/prototype_hud_theme.gd") const HudThemeConstants := preload("res://scripts/prototype_hud_theme_constants.gd") var _item_defs_client: Node = null var _craft_buttons: Array[Button] = [] @onready var _rows: VBoxContainer = $RecipeRows func setup(item_defs_client: Node) -> void: _item_defs_client = item_defs_client func populate(recipes: Array) -> void: if not is_instance_valid(_rows): return for child in _rows.get_children(): child.queue_free() _craft_buttons.clear() for row_variant in recipes: if not row_variant is Dictionary: continue var recipe: Dictionary = row_variant var recipe_id: String = str(recipe.get("id", "")) if recipe_id.is_empty(): continue var row := VBoxContainer.new() row.size_flags_horizontal = Control.SIZE_EXPAND_FILL row.add_theme_constant_override("separation", 4) var info := Label.new() info.size_flags_horizontal = Control.SIZE_EXPAND_FILL info.text = _format_recipe_line(recipe) _apply_recipe_row_label(info) row.add_child(info) var btn_row := HBoxContainer.new() btn_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL btn_row.alignment = BoxContainer.ALIGNMENT_END var craft_btn := Button.new() craft_btn.text = "Craft" PrototypeHudTheme.apply_to_button(craft_btn, false) craft_btn.pressed.connect(_on_craft_pressed.bind(recipe_id)) btn_row.add_child(craft_btn) row.add_child(btn_row) _rows.add_child(row) _craft_buttons.append(craft_btn) call_deferred("_scroll_economy_body_to_recipes") func set_craft_busy(busy: bool) -> void: for btn in _craft_buttons: if is_instance_valid(btn): btn.disabled = busy func _format_recipe_line(recipe: Dictionary) -> String: var recipe_id: String = str(recipe.get("id", "")) var display_name: String = str(recipe.get("displayName", recipe_id)) var inputs_line: String = _format_inputs(recipe.get("inputs", [])) return "%s (%s)\nneeds: %s" % [display_name, recipe_id, inputs_line] func _format_inputs(raw_inputs: Variant) -> String: if raw_inputs == null or not raw_inputs is Array: return "—" var parts: PackedStringArray = PackedStringArray() for row_variant in raw_inputs as Array: if not row_variant is Dictionary: continue var row: Dictionary = row_variant var item_id: String = str(row.get("itemId", "")) var qty: int = int(row.get("quantity", 0)) if item_id.is_empty() or qty <= 0: continue parts.append("%d× %s" % [qty, _item_display_name(item_id)]) if parts.is_empty(): return "—" return ", ".join(parts) func _item_display_name(item_id: String) -> String: if is_instance_valid(_item_defs_client) and _item_defs_client.has_method("display_name_for"): return str(_item_defs_client.call("display_name_for", item_id)) return item_id func _on_craft_pressed(recipe_id: String) -> void: craft_requested.emit(recipe_id) func _apply_recipe_row_label(label: Label) -> void: label.add_theme_font_size_override("font_size", HudThemeConstants.FONT_SIZE_SECONDARY) label.add_theme_constant_override("outline_size", HudThemeConstants.OUTLINE_SIZE) label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART 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 node = node.get_parent()