extends Node const GAME := preload("res://scenes/game/game.tscn") func motion(point: Vector2, relative: Vector2 = Vector2.ZERO) -> void: var event := InputEventMouseMotion.new() event.position = point event.global_position = point event.relative = relative event.button_mask = MOUSE_BUTTON_MASK_LEFT if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) else 0 get_viewport().push_input(event, true) func mouse(point: Vector2, pressed: bool) -> void: var event := InputEventMouseButton.new() event.position = point event.global_position = point event.button_index = MOUSE_BUTTON_LEFT event.pressed = pressed event.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0 get_viewport().push_input(event, true) func click(point: Vector2) -> void: motion(point) mouse(point, true) mouse(point, false) func drag(header: Control, displacement: Vector2) -> void: var origin := header.get_global_rect().position + Vector2(15, header.get_global_rect().size.y * 0.5) motion(origin) mouse(origin, true) motion(origin + displacement, displacement) mouse(origin + displacement, false) func _ready() -> void: process_mode = Node.PROCESS_MODE_ALWAYS GameState.start_new_run() var game := GAME.instantiate() as Node2D add_child(game) await get_tree().process_frame await get_tree().process_frame game.set_physics_process(false) var ship := game.get_node("PlayerShip") as CharacterBody2D ship.set_physics_process(false) ship.get_node("WeaponController").set_physics_process(false) for enemy in get_tree().get_nodes_in_group("enemies"): enemy.set_physics_process(false) var hud := game.get_node("HUD") var manager := hud.get_node("HUDRoot/FloatingWindows") var craft: PanelContainer = hud.get("_crafting_window") var inventory: Control = hud.get("inventory_panel") var drawer: Control = hud.get("_inventory_clip") var sidebar: Control = hud.get("_sidebar") hud.call("_toggle_inventory") craft.show() await get_tree().process_frame await get_tree().process_frame # Put inventory directly behind crafting. Its salvage button occupies the same pixel. drawer.set_meta("floating_window_dragged", true) var recipe: Button = craft.get_node("Contents/CraftingPanel").get("_recipe_buttons")["parts"] var point := recipe.get_global_rect().get_center() var first_slot := inventory.get_node("Contents/CargoGrid").get_child(0) as Control drawer.global_position += point - first_slot.get_global_rect().get_center() manager.call("focus_window", drawer) manager.call("focus_window", craft) var stock := ship.get_node("ShipInventory") as ShipInventory var item := ItemInstance.new() item.definition = preload("res://resources/items/plasma_cannon.tres") assert(stock.add_item(item)) GameState.captain.minerals.merge({"iron": 2, "titanium": 2, "aluminium": 2}, true) GameState.captain.emit_changed() await get_tree().process_frame assert(manager.call("window_at", point) == craft) click(point) assert(GameState.captain.parts == 1.0, "Top crafting button must receive the click") assert(stock.cargo[0] == item, "Underlying inventory must not consume the click") assert(craft.z_index > drawer.z_index) assert(craft.get_index() > drawer.get_index()) # Dragging a title moves its window; content clicks do not start a window drag. var before := craft.global_position drag(craft.get_node("Contents/Header"), Vector2(-80, -60)) assert(craft.global_position.is_equal_approx(before + Vector2(-80, -60))) await get_tree().process_frame assert(craft.global_position.is_equal_approx(before + Vector2(-80, -60))) # The exposed inventory header can activate and raise the lower window. drawer.global_position = Vector2(70, 80) manager.call("focus_window", craft) var inv_header := inventory.get_node("Contents/Header") as Control click(inv_header.get_global_rect().position + Vector2(10, 8)) assert(drawer.z_index > craft.z_index and drawer.get_index() > craft.get_index()) before = drawer.global_position drag(inv_header, Vector2(100, 80)) assert(drawer.global_position.is_equal_approx(before + Vector2(100, 80))) hud.call("_layout_inventory") assert(drawer.global_position.is_equal_approx(before + Vector2(100, 80))) # Close buttons do not turn into drag handles; reopening preserves the moved position. click(inventory.get_node("Contents/Header/CloseButton").get_global_rect().get_center()) assert(not inventory.visible) inventory.call("_set_drawer_open", true) assert(drawer.global_position.is_equal_approx(before + Vector2(100, 80))) # Sidebar dragging survives equipment relayout and viewport clamping. manager.call("focus_window", sidebar) before = sidebar.global_position drag(sidebar.get_node("Scroll/Contents/ShipEquipmentPanel/Contents/Title"), Vector2(-100, -50)) hud.call("_layout_sidebar") assert(sidebar.global_position.is_equal_approx(before + Vector2(-100, -50))) # The captain modal remains draggable while paused and blocks windows below it. var skills: CanvasLayer = hud.get("_captain_skills_ui") skills.call("open_management") await get_tree().process_frame await get_tree().process_frame assert(get_tree().paused) var panel: Control = skills.get("_panel") before = panel.global_position drag((skills.get("_contents") as Control).get_node("Header"), Vector2(50, 30)) assert(panel.global_position.is_equal_approx(before + Vector2(50, 30))) skills.call("_layout") assert(panel.global_position.is_equal_approx(before + Vector2(50, 30))) skills.call("_close") assert(not get_tree().paused) assert(not ship.get_node("WeaponController").get("_fire_buttons_down")[0]) print("FLOATING_WINDOWS_TEST: PASS (real GUI click routing over inventory, matching z/tree order, title drag, focus/raise, close/reopen, relayout persistence, paused captain modal, gameplay input blocked)") get_tree().quit()