diff --git a/tests/controller_menu_accessibility_validation.gd b/tests/controller_menu_accessibility_validation.gd index 3e37223..4176ddc 100644 --- a/tests/controller_menu_accessibility_validation.gd +++ b/tests/controller_menu_accessibility_validation.gd @@ -50,6 +50,7 @@ func _run() -> void: await _validate_mail_navigation() await _validate_profile_confirmation_focus() await _validate_profile_voice_navigation() + await _validate_player_menu_toggle_queue() await _validate_inventory_tab_zone_transitions() await _validate_player_menu_nested_controller_back() await _validate_confirmation_dialog_navigation() @@ -64,6 +65,39 @@ func _run() -> void: quit(1) +func _validate_player_menu_toggle_queue() -> void: + var menu := PlayerMenuScene.instantiate() as Control + root.add_child(menu) + await process_frame + menu.visible = true + menu.set("_transitioning", true) + var toggle := InputEventAction.new() + toggle.action = &"open_backpack" + toggle.pressed = true + menu.call("_input", toggle) + _expect( + bool(menu.get("_backpack_toggle_queued")), + "Player menu discarded TAB during its entry transition.", + ) + menu.call("_input", toggle) + _expect( + not bool(menu.get("_backpack_toggle_queued")), + "Repeated TAB presses did not preserve toggle parity.", + ) + menu.call("_input", toggle) + menu.call( + "_finish_menu_entry", + int(menu.get("_transition_generation")), + ) + await create_timer(0.5).timeout + _expect( + not menu.visible, + "A queued TAB press did not close the menu after entry.", + ) + menu.queue_free() + await process_frame + + func _validate_primary_menu_navigation() -> void: var title := TitleScreenScene.instantiate() as Control root.add_child(title) diff --git a/ui/player_menu.gd b/ui/player_menu.gd index 3bb428e..95ebc2e 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -336,6 +336,7 @@ var _transition_generation: int = 0 var _page_transition_generation: int = 0 var _transitioning: bool = false var _page_transitioning: bool = false +var _backpack_toggle_queued: bool = false var _cooler_rest_position: Vector2 = Vector2.ZERO var _bag_rest_position: Vector2 = Vector2.ZERO var _tackle_rest_position: Vector2 = Vector2.ZERO @@ -676,7 +677,9 @@ func _input(event: InputEvent) -> void: return if visible: if event.is_action_pressed("open_backpack"): - if not _transitioning and not _page_transitioning: + if _transitioning or _page_transitioning: + _queue_backpack_toggle() + else: close_menu() get_viewport().set_input_as_handled() return @@ -3251,6 +3254,7 @@ func _finish_menu_entry(generation: int) -> void: _transitioning = false _set_shell_interactive(true) _reset_controller_zone_for_section() + _consume_queued_backpack_toggle() func _begin_menu_exit(reason: CloseReason, restore_controls: bool) -> void: @@ -3311,6 +3315,10 @@ func _finish_close( restore_controls: bool, closing_generation: int, ) -> void: + var reopen_after_close: bool = ( + reason == CloseReason.USER and _backpack_toggle_queued + ) + _backpack_toggle_queued = false _cancel_presentation_tween() _cancel_page_tween() _the_net_page.deactivate() @@ -3339,6 +3347,35 @@ func _finish_close( _menu_generation += 1 _transition_generation += 1 menu_visibility_changed.emit(false) + if reopen_after_close: + call_deferred("_open_queued_backpack_toggle") + + +func _consume_queued_backpack_toggle() -> void: + if not _backpack_toggle_queued: + return + _backpack_toggle_queued = false + if visible: + close_menu() + else: + _open_queued_backpack_toggle() + + +func _queue_backpack_toggle() -> void: + # Preserve press parity: one press reverses the pending menu state, while a + # second press during the same animation cancels that request. + _backpack_toggle_queued = not _backpack_toggle_queued + + +func _open_queued_backpack_toggle() -> void: + if ( + visible + or _fishing_spot == null + or not is_instance_valid(_fishing_spot) + or not _fishing_spot.can_open_player_menu() + ): + return + open_menu() func _get_section_root(section: Section) -> Control: @@ -3469,6 +3506,7 @@ func _finish_page_transition(generation: int) -> void: _reset_page_transition_visuals() _set_content_interactive(true) _reset_controller_zone_for_section() + _consume_queued_backpack_toggle() func _set_shell_interactive(interactive: bool) -> void: