extends Control ## NEO-74: scrollable prototype recipe list with per-row Craft buttons. signal craft_requested(recipe_id: String) @onready var _vbox: VBoxContainer = $ScrollContainer/VBoxContainer var _item_defs_client: Node = null var _craft_buttons: Array[Button] = [] func setup(item_defs_client: Node) -> void: _item_defs_client = item_defs_client func populate(recipes: Array) -> void: if not is_instance_valid(_vbox): return for child in _vbox.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 := HBoxContainer.new() row.size_flags_horizontal = Control.SIZE_EXPAND_FILL var info := Label.new() info.size_flags_horizontal = Control.SIZE_EXPAND_FILL info.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART info.text = _format_recipe_line(recipe) row.add_child(info) var craft_btn := Button.new() craft_btn.text = "Craft" craft_btn.pressed.connect(_on_craft_pressed.bind(recipe_id)) row.add_child(craft_btn) _vbox.add_child(row) _craft_buttons.append(craft_btn) 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)