From ded8494cc603822701001e65629ef44944fef6df Mon Sep 17 00:00:00 2001 From: Voyager Date: Sat, 8 Aug 2026 16:20:40 -0400 Subject: [PATCH] feat: add controller hotbar management --- main/main.gd | 3 ++ ui/game_ui.gd | 52 ++++++++++++++++++++++++++++++++-- ui/hotbar.gd | 40 ++++++++++++++++++++++++-- ui/player_menu.gd | 71 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 159 insertions(+), 7 deletions(-) diff --git a/main/main.gd b/main/main.gd index 6344dd6..e62acfb 100644 --- a/main/main.gd +++ b/main/main.gd @@ -1102,6 +1102,9 @@ func _unhandled_input(event: InputEvent) -> void: ): _game_ui.set_shop_prompt_visible(false) get_viewport().set_input_as_handled() + elif event is InputEventJoypadButton and _player != null: + _player.hotbar.clear_slot(_player.hotbar.get_selected_slot()) + get_viewport().set_input_as_handled() func _process(_delta: float) -> void: diff --git a/ui/game_ui.gd b/ui/game_ui.gd index e96137c..a9af18e 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -210,10 +210,16 @@ func _ready() -> void: _on_hotbar_presentation_transition_finished ) _player_menu.controller_hotbar_placement_requested.connect( - _hotbar_ui.begin_controller_placement + _on_controller_hotbar_placement_requested ) _player_menu.controller_hotbar_placement_ended.connect( - _hotbar_ui.end_controller_placement + _on_controller_hotbar_placement_ended + ) + _player_menu.controller_hotbar_management_requested.connect( + _on_controller_hotbar_management_requested + ) + _player_menu.controller_hotbar_management_ended.connect( + _on_controller_hotbar_management_ended ) _title_settings_panel.panel_visibility_changed.connect( _on_settings_visibility_changed @@ -2045,6 +2051,48 @@ func _on_inventory_hotbar_context_changed(show_hotbar: bool) -> void: _refresh_hotbar_visibility() +func _on_controller_hotbar_placement_requested( + assignment_kind: PlayerHotbarType.AssignmentKind, + identity: StringName, + initial_slot: int, +) -> void: + _player_menu_hotbar_visible = true + _hotbar_ui.set_player_menu_context(true) + _hotbar_ui.set_drag_enabled(false) + _hotbar_ui.set_presentation_visible(true, false) + _hotbar_ui.begin_controller_placement( + assignment_kind, + identity, + initial_slot, + ) + + +func _on_controller_hotbar_placement_ended() -> void: + _hotbar_ui.end_controller_placement() + _hotbar_ui.set_drag_enabled( + _player_menu_open + and _player_menu_hotbar_visible + and not _system_menu_open + ) + + +func _on_controller_hotbar_management_requested(initial_slot: int) -> void: + _player_menu_hotbar_visible = true + _hotbar_ui.set_player_menu_context(true) + _hotbar_ui.set_drag_enabled(false) + _hotbar_ui.set_presentation_visible(true, false) + _hotbar_ui.begin_controller_management(initial_slot) + + +func _on_controller_hotbar_management_ended() -> void: + _hotbar_ui.end_controller_management() + _hotbar_ui.set_drag_enabled( + _player_menu_open + and _player_menu_hotbar_visible + and not _system_menu_open + ) + + func _on_hotbar_presentation_transition_finished( is_visible: bool, ) -> void: diff --git a/ui/hotbar.gd b/ui/hotbar.gd index ffd0487..e058f3f 100644 --- a/ui/hotbar.gd +++ b/ui/hotbar.gd @@ -4,6 +4,7 @@ extends Control signal presentation_transition_finished(is_visible: bool) const ItemCatalogType = preload("res://items/item_catalog.gd") +const ItemDataType = preload("res://items/item_data.gd") const PlayerBagType = preload("res://inventory/player_bag.gd") const PlayerHotbarType = preload("res://inventory/player_hotbar.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") @@ -42,6 +43,7 @@ var _motion_elapsed: float = 0.0 var _compact_layout: bool = false var _player_menu_context: bool = false var _controller_placement_active: bool = false +var _controller_management_active: bool = false var _controller_placement_kind: PlayerHotbarType.AssignmentKind = ( PlayerHotbarType.AssignmentKind.EMPTY ) @@ -147,6 +149,36 @@ func end_controller_placement() -> void: _show_selected_item_briefly() +func begin_controller_management(initial_slot: int) -> void: + if _hotbar == null or _slots.is_empty(): + return + _controller_management_active = true + var slot_count: int = _slots.size() + for index: int in slot_count: + var slot: BubbleHotbarSlotType = _slots[index] + slot.focus_mode = Control.FOCUS_ALL + slot.focus_neighbor_left = slot.get_path_to( + _slots[wrapi(index - 1, 0, slot_count)] + ) + slot.focus_neighbor_right = slot.get_path_to( + _slots[wrapi(index + 1, 0, slot_count)] + ) + slot.focus_neighbor_top = slot.get_path_to(slot) + slot.focus_neighbor_bottom = slot.get_path_to(slot) + var target_index: int = clampi(initial_slot, 0, slot_count - 1) + _hotbar.select_slot(target_index) + _slots[target_index].call_deferred("grab_focus") + + +func end_controller_management() -> void: + if not _controller_management_active: + return + _controller_management_active = false + for slot: BubbleHotbarSlotType in _slots: + slot.focus_mode = Control.FOCUS_NONE + _show_selected_item_briefly() + + func _resolve_controller_placement_texture() -> Texture2D: if ( _controller_placement_kind == PlayerHotbarType.AssignmentKind.FISH @@ -368,10 +400,14 @@ func _on_selected_slot_changed( func _on_controller_slot_focused(slot_index: int) -> void: - if not _controller_placement_active or _hotbar == null: + if ( + not (_controller_placement_active or _controller_management_active) + or _hotbar == null + ): return _hotbar.select_slot(slot_index) - _refresh_controller_placement_preview() + if _controller_placement_active: + _refresh_controller_placement_preview() func _on_slot_item_hovered( diff --git a/ui/player_menu.gd b/ui/player_menu.gd index a2ac7ef..3d99289 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -91,6 +91,8 @@ signal controller_hotbar_placement_requested( initial_slot: int, ) signal controller_hotbar_placement_ended +signal controller_hotbar_management_requested(initial_slot: int) +signal controller_hotbar_management_ended signal menu_exit_started signal shop_cooler_modal_changed(is_open: bool) @@ -133,6 +135,7 @@ enum CloseReason { enum ControllerOwnership { ITEM_LIST, NOTEPAD_ACTIONS, + HOTBAR_MANAGEMENT, HOTBAR_PLACEMENT, } @@ -678,6 +681,17 @@ func _handle_controller_ownership_input(event: InputEvent) -> bool: JOY_BUTTON_Y, ) ) + if _controller_ownership == ControllerOwnership.HOTBAR_MANAGEMENT: + if cancel_pressed: + _release_controller_ownership(true, false) + return true + if alternate_pressed: + if _hotbar != null: + _hotbar.clear_slot(_hotbar.get_selected_slot()) + return true + if accept_pressed: + return true + return false if _controller_ownership == ControllerOwnership.HOTBAR_PLACEMENT: if accept_pressed: _confirm_controller_hotbar_placement() @@ -902,6 +916,11 @@ func _release_controller_ownership( if restore_previous_hotbar_slot and _hotbar != null: _hotbar.select_slot(_controller_previous_hotbar_slot) controller_hotbar_placement_ended.emit() + if prior_ownership == ControllerOwnership.HOTBAR_MANAGEMENT: + controller_hotbar_management_ended.emit() + inventory_hotbar_context_changed.emit( + _current_section in [Section.COOLER, Section.BAG] + ) if restore_source_focus: call_deferred( "_restore_controller_item_focus", @@ -914,7 +933,10 @@ func _restore_controller_item_focus( section: Section, identity: StringName, ) -> void: - if not visible or section != _current_section or identity.is_empty(): + if not visible or section != _current_section: + return + if identity.is_empty(): + _focus_current_section() return var target: Control if section == Section.COOLER: @@ -940,6 +962,8 @@ func _restore_controller_item_focus( and target.focus_mode != Control.FOCUS_NONE ): target.grab_focus() + else: + _focus_current_section() func _handle_controller_page_switch(event: InputEvent) -> bool: @@ -1047,6 +1071,10 @@ func _handle_controller_secondary_switch(event: InputEvent) -> bool: return false if not button_event.pressed: return true + var direction: int = -1 if uses_left_bumper else 1 + if _controller_ownership == ControllerOwnership.HOTBAR_MANAGEMENT: + _cycle_from_controller_hotbar_management(direction) + return true if ( _transitioning or _page_transitioning @@ -1055,12 +1083,17 @@ func _handle_controller_secondary_switch(event: InputEvent) -> bool: or _controller_ownership != ControllerOwnership.ITEM_LIST ): return true - var direction: int = -1 if uses_left_bumper else 1 if _is_inventory_section(_current_section): + if ( + (direction > 0 and _current_section == Section.BAG) + or (direction < 0 and _current_section == Section.COOLER) + ): + _begin_controller_hotbar_management() + return true var inventory_sections: Array[Section] = [ Section.COOLER, - Section.BAG, Section.TACKLE_BOX, + Section.BAG, ] var inventory_index: int = inventory_sections.find(_current_section) _show_section(inventory_sections[wrapi( @@ -1073,6 +1106,35 @@ func _handle_controller_secondary_switch(event: InputEvent) -> bool: return true +func _begin_controller_hotbar_management() -> void: + if _hotbar == null: + return + _controller_source_section = _current_section + _controller_source_identity = StringName() + var focus_owner: Control = get_viewport().gui_get_focus_owner() + if _current_section == Section.COOLER: + var fish_node := focus_owner as CoolerFishSpriteType + if fish_node != null: + _controller_source_identity = fish_node.catch_id + elif _current_section == Section.BAG: + var item_node := focus_owner as BagItemSpriteType + if item_node != null: + _controller_source_identity = item_node.item_id + _controller_ownership = ControllerOwnership.HOTBAR_MANAGEMENT + controller_hotbar_management_requested.emit(_hotbar.get_selected_slot()) + + +func _cycle_from_controller_hotbar_management(direction: int) -> void: + _release_controller_ownership(false, false) + var target_section: Section = ( + Section.COOLER if direction > 0 else Section.BAG + ) + if target_section == _current_section: + call_deferred("_focus_current_section") + else: + _show_section(target_section) + + func _cycle_visible_secondary_tabs(direction: int) -> bool: var navigation_cluster := get_node_or_null("%NavigationCluster") as Control var grouped_buttons: Dictionary = {} @@ -1257,6 +1319,9 @@ func open_menu() -> void: _update_shell_layout() _begin_menu_entry() menu_visibility_changed.emit(true) + inventory_hotbar_context_changed.emit( + _current_section in [Section.COOLER, Section.BAG] + ) func open_section(section: Section) -> bool: