diff --git a/tests/test_research_points_explainer.gd b/tests/test_research_points_explainer.gd index aaef71a..6fc638e 100644 --- a/tests/test_research_points_explainer.gd +++ b/tests/test_research_points_explainer.gd @@ -113,6 +113,10 @@ func _process(delta: float) -> bool: break if game_state.research_point_cost(String(options[controller.lab_choice].id)) <= 0: return _fail("no lab option costs research data, so this proves nothing") + # The lab panel is open when the player presses buy - that is what + # puts the explainer on top of another dialog, which is the whole + # point of case D below. + controller.lab_menu.open_menu() controller._on_lab_buy() if not popup.visible: return _fail("the lab refused for want of data with no explanation") @@ -120,6 +124,30 @@ func _process(delta: float) -> bool: return _fail("the lab still blames the queue: '%s'" % controller.lab_menu.status.text) print("ship lab: refused and explained, not blamed on the queue") + # --- D. and the player can get back out of it ---------------- + # The explainer is the only dialog that opens ON TOP of another + # one, and IconButton refuses input to any button sitting behind + # an open panel. That used to include this popup's own close + # button, so the dialog could not be dismissed at all. Case B + # above left the ship lab open underneath, which is the real + # situation - assert that rather than arranging it. + if not controller.lab_menu.visible: + return _fail("the lab is not open underneath, so this proves nothing") + var close: Control = popup.get_node("CloseButton") + if close._blocked_by_modal(): + return _fail("the explainer close button is blocked by the lab behind it - the dialog is inescapable") + close.pressed.emit() + if popup.visible: + return _fail("pressing close left the explainer open") + # The reverse must still hold: the lab's own buttons stay dead + # while a dialog covers them. + popup.open_for_shortfall(10, "anything") + var lab_close: Control = controller.lab_menu.get_node("CloseButton") + if not lab_close._blocked_by_modal(): + return _fail("the lab buttons are live underneath an open dialog") + popup.close_menu() + print("close button works over the lab, and the lab stays blocked underneath") + print("TEST_RESEARCH_POINTS_EXPLAINER: PASS") quit(0) return true diff --git a/ui/IconButton.gd b/ui/IconButton.gd index 75ca542..c6af89c 100644 --- a/ui/IconButton.gd +++ b/ui/IconButton.gd @@ -130,6 +130,7 @@ func _blocked_by_modal() -> bool: # touches through the GUI system swallows them before they ever get # there. So the panels stay transparent to input and the buttons check # instead. + var own_modal: Node = _own_modal() for menu in get_tree().get_nodes_in_group("modal_menus"): # is_visible_in_tree(), NOT visible. Hiding a parent leaves the # child's own `visible` flag true, and the build menu's info popup is @@ -137,11 +138,42 @@ func _blocked_by_modal() -> bool: # invisible popup still claiming to be open, and every button in the # game was blocked from then on. That is not a state a player can # get out of. - if menu.is_visible_in_tree() and not menu.is_ancestor_of(self): - return true + if not menu.is_visible_in_tree(): + continue + if menu.is_ancestor_of(self): + continue + # Stacking order matters, and used not to be considered at all. A + # panel that this button's own panel is drawn ON TOP OF sits behind + # it, not over it, so it must not swallow the input. The research + # shortfall popup opens over the skill tree and the ship lab, and + # without this every button in it - its close button included - was + # dead, leaving the player no way out of the dialog. + if own_modal != null and not _covers(menu, own_modal): + continue + return true return false +# The modal panel this button lives inside, or null for a HUD button that +# belongs to no panel (those stay blocked by any open panel, as before). +func _own_modal() -> Node: + var node: Node = get_parent() + while node != null: + if node.is_in_group("modal_menus"): + return node + node = node.get_parent() + return null + + +func _covers(other: Node, own: Node) -> bool: + # Later siblings draw on top. Different parents means one of them is + # nested inside something else - the build menu's info popup is a child + # of the build menu - and there the conservative answer is "covered". + if other.get_parent() != own.get_parent(): + return true + return other.get_index() > own.get_index() + + func _gui_input(event: InputEvent) -> void: # @tool runs this in the editor too, where a click on the canvas would # emit `pressed` at whatever it is wired to. The glyph is what we want