feat: overhaul controller menu accessibility

This commit is contained in:
Alexander Sellite 2026-08-15 19:20:42 -04:00
parent a7837c3711
commit 4f9c85989b
46 changed files with 4091 additions and 396 deletions

View file

@ -36,6 +36,9 @@ const PlayerExperienceType = preload(
const ControllerMappingManagerType = preload(
"res://settings/controller_mapping_manager.gd"
)
const ControllerFocusNavigationType = preload(
"res://ui/controller_focus_navigation.gd"
)
const FishBatchSelectionType = preload(
"res://ui/fish_batch_selection.gd"
)
@ -131,10 +134,13 @@ enum CloseReason {
}
enum ControllerOwnership {
INVENTORY_TABS,
ITEM_LIST,
NOTEPAD_ACTIONS,
SORT_FILTER,
HOTBAR_MANAGEMENT,
HOTBAR_PLACEMENT,
PAGE_CONTENT,
}
const INVENTORY_MAIN_POSITION := Vector2(54.0, 166.0)
@ -149,6 +155,7 @@ const INVENTORY_MAIN_CORNER_RADIUS: int = 58
const INVENTORY_INNER_CORNER_RADIUS: int = 45
const TACKLE_GRID_COLUMNS: int = 3
const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
const CONTROLLER_PICKUP_HOLD_SECONDS: float = 0.42
@onready var _navigation_cluster: BubbleClusterType = %NavigationCluster
@onready var _presentation_scale_root: Control = %PlayerMenuPresentationScaleRoot
@ -308,7 +315,9 @@ var _last_inventory_section: Section = Section.COOLER
var _bag_view: BagView = BagView.EQUIPMENT
var _selected_tackle_item_id: StringName
var _tackle_item_buttons: Dictionary[StringName, Button] = {}
var _controller_ownership: ControllerOwnership = ControllerOwnership.ITEM_LIST
var _controller_ownership: ControllerOwnership = (
ControllerOwnership.INVENTORY_TABS
)
var _controller_source_section: Section = Section.COOLER
var _controller_source_identity: StringName
var _controller_notepad_actions: Array[BaseButton] = []
@ -317,6 +326,8 @@ var _controller_hotbar_assignment_kind: PlayerHotbarType.AssignmentKind = (
)
var _controller_hotbar_identity: StringName
var _controller_previous_hotbar_slot: int = 0
var _controller_accept_held: bool = false
var _controller_accept_hold_elapsed: float = 0.0
var _sort_mode: SortMode = SortMode.CATCH_ORDER
var _sort_descending: bool = true
var _fish_selection := FishBatchSelectionType.new()
@ -352,8 +363,10 @@ var _page_incoming_root: Control
var _page_outgoing_content_root: Control
var _inventory_transition_group: Control
var _fish_nodes: Dictionary[StringName, CoolerFishSpriteType] = {}
var _cooler_slot_nodes: Array[Panel] = []
var _sorted_catches: Array[FishCatchType] = []
var _bag_item_nodes: Dictionary[StringName, BagItemSpriteType] = {}
var _bag_slot_nodes: Array[Panel] = []
var _sorted_bag_items: Array[OwnedItemType] = []
var _bag_drag_active: bool = false
var _motion_elapsed: float = 0.0
@ -594,16 +607,13 @@ func setup_controller_mapping(
) -> void:
_controller_mapping_manager = mapping_manager
_profile_page.setup_controller_mapping(_controller_mapping_manager)
_the_net_page.setup_controller_mapping(_controller_mapping_manager)
func set_profile_preview_world_pixel_size(pixel_size: int) -> void:
_profile_page.set_world_pixel_size(pixel_size)
var _left_page_trigger_held: bool = false
var _right_page_trigger_held: bool = false
func _input(event: InputEvent) -> void:
if event is InputEventKey and event.echo:
return
@ -616,9 +626,6 @@ func _input(event: InputEvent) -> void:
if _handle_controller_page_switch(event):
get_viewport().set_input_as_handled()
return
if _handle_controller_secondary_switch(event):
get_viewport().set_input_as_handled()
return
if _handle_direct_page_shortcut(event):
get_viewport().set_input_as_handled()
return
@ -640,16 +647,21 @@ func _input(event: InputEvent) -> void:
func _handle_controller_ownership_input(event: InputEvent) -> bool:
if not visible:
return false
if not _is_inventory_section(_current_section):
return _handle_active_page_controller_input(event)
var button_event := event as InputEventJoypadButton
var accept_pressed: bool = (
var accept_event: bool = (
button_event != null
and button_event.pressed
and _event_matches_controller_role(
event,
ControllerMappingManagerType.ROLE_A,
JOY_BUTTON_A,
)
)
var accept_pressed: bool = (
accept_event
and button_event.pressed
)
var cancel_pressed: bool = (
button_event != null
and button_event.pressed
@ -659,23 +671,24 @@ func _handle_controller_ownership_input(event: InputEvent) -> bool:
JOY_BUTTON_B,
)
)
var alternate_pressed: bool = (
button_event != null
and button_event.pressed
and _event_matches_controller_role(
event,
ControllerMappingManagerType.ROLE_Y,
JOY_BUTTON_Y,
)
)
if _controller_ownership == ControllerOwnership.INVENTORY_TABS:
if cancel_pressed:
close_menu()
return true
if accept_pressed:
_enter_inventory_content_zone()
return true
if event.is_action_pressed("ui_left"):
_switch_inventory_tab_direction(-1)
return true
if event.is_action_pressed("ui_right"):
_switch_inventory_tab_direction(1)
return true
return false
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
@ -686,34 +699,90 @@ func _handle_controller_ownership_input(event: InputEvent) -> bool:
if cancel_pressed:
_release_controller_ownership(true, true)
return true
if alternate_pressed:
return true
return false
if _controller_ownership == ControllerOwnership.NOTEPAD_ACTIONS:
if cancel_pressed:
_release_controller_ownership(true, false)
return true
if alternate_pressed:
return true
var direction: int = 0
if event.is_action_pressed("ui_left") or event.is_action_pressed("ui_up"):
direction = -1
elif (
event.is_action_pressed("ui_right")
or event.is_action_pressed("ui_down")
):
direction = 1
if direction != 0:
_focus_next_notepad_action(direction)
return false
if _controller_ownership == ControllerOwnership.SORT_FILTER:
if cancel_pressed:
_enter_inventory_content_zone()
return true
return false
if alternate_pressed:
return _try_begin_controller_hotbar_placement()
if accept_pressed:
return _try_enter_notepad_controller_ownership()
if _controller_ownership == ControllerOwnership.ITEM_LIST:
if cancel_pressed:
_cancel_controller_accept_hold()
_enter_inventory_tabs_zone()
return true
if _event_matches_controller_role(
event,
ControllerMappingManagerType.ROLE_SELECT,
JOY_BUTTON_BACK,
):
if button_event != null and button_event.pressed:
if _current_section == Section.COOLER:
_enter_inventory_sort_zone()
return true
if accept_event:
if button_event.pressed:
_controller_accept_held = true
_controller_accept_hold_elapsed = 0.0
else:
var was_pending: bool = _controller_accept_held
_cancel_controller_accept_hold()
if was_pending:
_activate_inventory_selection()
return true
return false
func _handle_active_page_controller_input(event: InputEvent) -> bool:
var handled: bool = false
match _current_section:
Section.LOGBOOK:
handled = _catalog_logbook.handle_controller_input(event)
Section.NET:
handled = _the_net_page.handle_controller_input(event)
Section.MAIL:
handled = _mail_page.handle_controller_input(event)
Section.PROFILE:
handled = _profile_page.handle_controller_input(event)
Section.PLAYERS:
handled = _players_page.handle_controller_input(event)
if handled:
return true
var button_event := event as InputEventJoypadButton
if (
button_event != null
and button_event.pressed
and _event_matches_controller_role(
event,
ControllerMappingManagerType.ROLE_B,
JOY_BUTTON_B,
)
):
close_menu()
return true
return false
func _activate_inventory_selection() -> void:
if _try_enter_notepad_controller_ownership():
return
if _current_section != Section.BAG:
return
var focus_owner: Control = get_viewport().gui_get_focus_owner()
var item_node := focus_owner as BagItemSpriteType
if item_node != null and not item_node.item_id.is_empty():
_select_bag_item(item_node.item_id)
func _cancel_controller_accept_hold() -> void:
_controller_accept_held = false
_controller_accept_hold_elapsed = 0.0
func _event_matches_controller_role(
event: InputEvent,
role: StringName,
@ -761,35 +830,11 @@ func _try_enter_notepad_controller_ownership() -> bool:
_controller_source_identity = source_identity
_controller_notepad_actions = actions
_controller_ownership = ControllerOwnership.NOTEPAD_ACTIONS
ControllerFocusNavigationType.configure_spatial_neighbors(actions)
actions.front().call_deferred("grab_focus")
return true
func _focus_next_notepad_action(direction: int) -> void:
var available: Array[BaseButton] = []
for action: BaseButton in _controller_notepad_actions:
if (
is_instance_valid(action)
and action.visible
and not action.disabled
and action.focus_mode != Control.FOCUS_NONE
):
available.append(action)
if available.is_empty():
return
var focused: Control = get_viewport().gui_get_focus_owner()
var current_index: int = available.find(focused)
if current_index < 0:
current_index = 0 if direction > 0 else available.size() - 1
else:
current_index = wrapi(
current_index + direction,
0,
available.size(),
)
available[current_index].grab_focus()
func _try_begin_controller_hotbar_placement() -> bool:
if _hotbar == null:
return false
@ -946,88 +991,6 @@ func _restore_controller_item_focus(
func _handle_controller_page_switch(event: InputEvent) -> bool:
var button_event: InputEventJoypadButton = event as InputEventJoypadButton
var motion_event: InputEventJoypadMotion = event as InputEventJoypadMotion
var use_mapping: bool = (
_controller_mapping_manager != null
)
var uses_left_trigger: bool = (
_controller_mapping_manager.event_uses_role(
event, ControllerMappingManagerType.ROLE_POINTER_MODIFIER
)
if use_mapping
else (
motion_event != null
and motion_event.axis == JOY_AXIS_TRIGGER_LEFT
)
)
var uses_right_trigger: bool = (
_controller_mapping_manager.event_uses_role(
event, ControllerMappingManagerType.ROLE_CAMERA_ZOOM
)
if use_mapping
else (
motion_event != null
and motion_event.axis == JOY_AXIS_TRIGGER_RIGHT
)
)
if (
not (uses_left_trigger or uses_right_trigger)
or not visible
):
return false
var is_pressed: bool = (
button_event.pressed
if button_event != null
else motion_event.axis_value > 0.5
)
if not is_pressed:
if uses_left_trigger:
_left_page_trigger_held = false
if uses_right_trigger:
_right_page_trigger_held = false
return true
if (
(uses_left_trigger and _left_page_trigger_held)
or (uses_right_trigger and _right_page_trigger_held)
):
return true
if uses_left_trigger:
_left_page_trigger_held = true
if uses_right_trigger:
_right_page_trigger_held = true
# Trigger input belongs to the Player Menu while it is visible, even when a
# transition or modal temporarily prevents changing pages.
if (
_transitioning
or _page_transitioning
or _sale_confirmation.visible
or get_viewport().gui_is_dragging()
or _controller_ownership != ControllerOwnership.ITEM_LIST
):
return true
var sections: Array[Section] = [
_last_inventory_section,
Section.LOGBOOK,
Section.NET,
Section.MAIL,
Section.PROFILE,
Section.PLAYERS,
]
var current_index: int = 0
if not _is_inventory_section(_current_section):
current_index = sections.find(_current_section)
if current_index < 0:
current_index = 0
var direction: int = (
-1 if uses_left_trigger else 1
)
var next_index: int = wrapi(current_index + direction, 0, sections.size())
_show_section(sections[next_index])
return true
func _handle_controller_secondary_switch(event: InputEvent) -> bool:
var button_event := event as InputEventJoypadButton
if button_event == null or not visible:
return false
@ -1050,59 +1013,40 @@ 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
or _sale_confirmation.visible
or get_viewport().gui_is_dragging()
or _controller_ownership != ControllerOwnership.ITEM_LIST
):
return true
if _is_inventory_section(_current_section):
var inventory_index: int = _get_inventory_tab_index()
if (
(direction > 0 and inventory_index == 3)
or (direction < 0 and inventory_index == 0)
):
_begin_controller_hotbar_management()
return true
_show_inventory_tab(inventory_index + direction)
var sections: Array[Section] = [
_last_inventory_section,
Section.LOGBOOK,
Section.NET,
Section.MAIL,
Section.PROFILE,
Section.PLAYERS,
]
var current_index: int = 0
if not _is_inventory_section(_current_section):
current_index = sections.find(_current_section)
if current_index < 0:
current_index = 0
var direction: int = -1 if uses_left_bumper else 1
var next_index: int = clampi(
current_index + direction,
0,
sections.size() - 1,
)
if next_index == current_index:
return true
_cycle_visible_secondary_tabs(direction)
_cancel_controller_accept_hold()
_release_controller_ownership(false, true)
_show_section(sections[next_index])
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_index: int = 0 if direction > 0 else 3
if target_index == _get_inventory_tab_index():
call_deferred("_focus_current_section")
else:
_show_inventory_tab(target_index)
func _get_inventory_tab_index() -> int:
if _current_section == Section.COOLER:
return 0
@ -1123,41 +1067,69 @@ func _show_inventory_tab(index: int) -> void:
_show_section(Section.TACKLE_BOX)
func _cycle_visible_secondary_tabs(direction: int) -> bool:
var navigation_cluster := get_node_or_null("%NavigationCluster") as Control
var grouped_buttons: Dictionary = {}
_collect_visible_toggle_buttons(self, navigation_cluster, grouped_buttons)
var selected_group: Variant = null
var selected_group_y: float = INF
for group_value: Variant in grouped_buttons.keys():
var buttons := grouped_buttons[group_value] as Array
if buttons.size() < 2:
continue
var group_y: float = INF
for item: Variant in buttons:
var button := item as BaseButton
group_y = minf(group_y, button.global_position.y)
if group_y < selected_group_y:
selected_group = group_value
selected_group_y = group_y
if selected_group == null:
return false
var tabs := grouped_buttons[selected_group] as Array
tabs.sort_custom(func(first: BaseButton, second: BaseButton) -> bool:
return first.global_position.x < second.global_position.x
func _switch_inventory_tab_direction(direction: int) -> void:
var current_index: int = _get_inventory_tab_index()
var target_index: int = clampi(current_index + direction, 0, 3)
if target_index == current_index:
return
_show_inventory_tab(target_index)
func _enter_inventory_tabs_zone() -> void:
if not _is_inventory_section(_current_section):
return
_cancel_controller_accept_hold()
_controller_ownership = ControllerOwnership.INVENTORY_TABS
var active_tab: Button = _inventory_tab_for_section(_current_section)
if (
active_tab != null
and active_tab.is_visible_in_tree()
and active_tab.focus_mode != Control.FOCUS_NONE
):
active_tab.call_deferred("grab_focus")
func _enter_inventory_content_zone() -> void:
if not _is_inventory_section(_current_section):
return
_cancel_controller_accept_hold()
_controller_ownership = ControllerOwnership.ITEM_LIST
call_deferred("_focus_current_section")
func _enter_inventory_sort_zone() -> void:
if _current_section != Section.COOLER:
return
_cancel_controller_accept_hold()
_controller_ownership = ControllerOwnership.SORT_FILTER
_controller_source_section = _current_section
var sort_controls: Array[Control] = [
_cooler_sort_option,
_cooler_sort_direction,
]
ControllerFocusNavigationType.configure_spatial_neighbors(
sort_controls
)
var current_index: int = 0
for index: int in tabs.size():
var tab := tabs[index] as BaseButton
if tab.button_pressed:
current_index = index
break
var target := tabs[wrapi(
current_index + direction, 0, tabs.size()
)] as BaseButton
target.set_pressed_no_signal(true)
target.pressed.emit()
return true
_cooler_sort_option.call_deferred("grab_focus")
func _reset_controller_zone_for_section() -> void:
_cancel_controller_accept_hold()
if _is_inventory_section(_current_section):
_enter_inventory_tabs_zone()
return
_controller_ownership = ControllerOwnership.PAGE_CONTENT
match _current_section:
Section.LOGBOOK:
_catalog_logbook.reset_controller_zone()
Section.NET:
_the_net_page.reset_controller_zone()
Section.MAIL:
_mail_page.reset_controller_zone()
Section.PROFILE:
_profile_page.reset_controller_zone()
Section.PLAYERS:
_players_page.reset_controller_zone()
func _collect_visible_toggle_buttons(
@ -1659,6 +1631,7 @@ func _focus_current_section() -> void:
_collect_focusable_content(self, navigation_cluster, candidates)
if candidates.is_empty():
return
ControllerFocusNavigationType.configure_spatial_neighbors(candidates)
candidates.sort_custom(func(first: Control, second: Control) -> bool:
if not is_equal_approx(first.global_position.y, second.global_position.y):
return first.global_position.y < second.global_position.y
@ -1679,8 +1652,7 @@ func _collect_focusable_content(
if control != null and not control.is_visible_in_tree():
continue
if (
control != null
and control.focus_mode != Control.FOCUS_NONE
ControllerFocusNavigationType.is_focusable(control)
and not control is ScrollBar
):
output.append(control)
@ -1689,6 +1661,19 @@ func _collect_focusable_content(
func _process(delta: float) -> void:
if visible or _shop_cooler_context_active:
if (
visible
and _controller_accept_held
and _controller_ownership == ControllerOwnership.ITEM_LIST
and _current_section in [Section.COOLER, Section.BAG]
):
_controller_accept_hold_elapsed += delta
if (
_controller_accept_hold_elapsed
>= CONTROLLER_PICKUP_HOLD_SECONDS
):
_cancel_controller_accept_hold()
_try_begin_controller_hotbar_placement()
_motion_elapsed += delta
if visible:
_navigation_cluster.advance_motion(delta)
@ -1815,10 +1800,10 @@ func _configure_sale_confirmation_focus() -> void:
_confirm_sale_button.focus_neighbor_left
)
_confirm_sale_button.focus_neighbor_top = (
_confirm_sale_button.focus_neighbor_left
_confirm_sale_button.get_path_to(_confirm_sale_button)
)
_confirm_sale_button.focus_neighbor_bottom = (
_confirm_sale_button.focus_neighbor_left
_confirm_sale_button.focus_neighbor_top
)
_cancel_sale_button.focus_neighbor_left = (
_cancel_sale_button.get_path_to(_confirm_sale_button)
@ -1827,10 +1812,10 @@ func _configure_sale_confirmation_focus() -> void:
_cancel_sale_button.focus_neighbor_left
)
_cancel_sale_button.focus_neighbor_top = (
_cancel_sale_button.focus_neighbor_left
_cancel_sale_button.get_path_to(_cancel_sale_button)
)
_cancel_sale_button.focus_neighbor_bottom = (
_cancel_sale_button.focus_neighbor_left
_cancel_sale_button.focus_neighbor_top
)
if not _sale_confirmation.visible:
for button: Button in [_confirm_sale_button, _cancel_sale_button]:
@ -1906,6 +1891,9 @@ func _show_bag_view(view: BagView) -> void:
return
if _bag_view == view:
return
var preserve_tab_zone: bool = (
_controller_ownership == ControllerOwnership.INVENTORY_TABS
)
_release_controller_ownership(false, true)
_bag_view = view
_selected_bag_item_id = StringName()
@ -1915,7 +1903,10 @@ func _show_bag_view(view: BagView) -> void:
_refresh_inventory_organizer_tabs()
_reserve_visible_secondary_navigation()
_set_content_interactive(true)
call_deferred("_focus_current_section")
if preserve_tab_zone:
_enter_inventory_tabs_zone()
else:
call_deferred("_focus_current_section")
func _refresh_tackle_box() -> void:
@ -2058,7 +2049,7 @@ func _configure_tackle_column_focus(
button.focus_neighbor_left = button.get_path_to(left_target)
button.focus_neighbor_right = button.get_path_to(right_target)
button.focus_neighbor_top = (
button.get_path_to(_tackle_sub_tab)
button.get_path_to(button)
if index < TACKLE_GRID_COLUMNS
else button.get_path_to(buttons[index - TACKLE_GRID_COLUMNS])
)
@ -2756,7 +2747,7 @@ func _finish_menu_entry(generation: int) -> void:
_presentation_tween = null
_transitioning = false
_set_shell_interactive(true)
_focus_current_section()
_reset_controller_zone_for_section()
func _begin_menu_exit(reason: CloseReason, restore_controls: bool) -> void:
@ -2972,7 +2963,7 @@ func _finish_page_transition(generation: int) -> void:
_inventory_sub_tabs.modulate.a = 1.0
_reset_page_transition_visuals()
_set_content_interactive(true)
_focus_current_section()
_reset_controller_zone_for_section()
func _set_shell_interactive(interactive: bool) -> void:
@ -3049,6 +3040,9 @@ func _set_content_interactive(interactive: bool) -> void:
_profile_page.set_interactive(
interactive and _current_section == Section.PROFILE
)
_players_page.set_interactive(
interactive and _current_section == Section.PLAYERS
)
var cooler_interactive: bool = (
interactive and _current_section == Section.COOLER
)
@ -3303,7 +3297,7 @@ func _sync_bag_item_nodes(owned_items: Array[OwnedItemType]) -> void:
func _layout_bag_items() -> void:
if not is_node_ready() or _sorted_bag_items.is_empty():
if not is_node_ready():
return
_bag_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
var columns: int = 3
@ -3327,6 +3321,16 @@ func _layout_bag_items() -> void:
if _compact_layout
else Vector2(820.0, 240.0)
)
_sync_inventory_slot_visuals(
_bag_item_field,
_bag_slot_nodes,
maxi(9, _sorted_bag_items.size()),
columns,
cell_size,
item_size,
origin,
18.0,
)
for index: int in _sorted_bag_items.size():
var owned: OwnedItemType = _sorted_bag_items[index]
var item_node := _bag_item_nodes.get(
@ -3354,6 +3358,44 @@ func _layout_bag_items() -> void:
)
func _sync_inventory_slot_visuals(
parent: Control,
slots: Array[Panel],
slot_count: int,
columns: int,
cell_size: Vector2,
slot_size: Vector2,
origin: Vector2,
alternate_lane_offset: float,
) -> void:
for slot: Panel in slots:
if is_instance_valid(slot):
slot.queue_free()
slots.clear()
var slot_color: Color = UtilityPageStyle.OCEAN_PANEL_MID
slot_color.a = 0.42
for index: int in slot_count:
var row: int = floori(float(index) / float(columns))
var column: int = index % columns
var slot := Panel.new()
slot.name = "InventorySlot%d" % index
slot.mouse_filter = Control.MOUSE_FILTER_IGNORE
slot.z_index = -1
slot.size = slot_size
slot.position = origin + Vector2(
float(column) * cell_size.x
+ (alternate_lane_offset if row % 2 == 1 else 0.0),
float(row) * cell_size.y,
)
slot.add_theme_stylebox_override(
"panel",
UtilityPageStyle.rounded_style(slot_color, 14),
)
parent.add_child(slot)
parent.move_child(slot, 0)
slots.append(slot)
func _configure_bag_item_focus() -> void:
if _current_section != Section.BAG:
return
@ -3374,9 +3416,12 @@ func _configure_bag_item_focus() -> void:
for index: int in controls.size():
var control: BagItemSpriteType = controls[index]
var column: int = index % 3
var row: int = floori(float(index) / 3.0)
var left_index: int = row * 3 + maxi(column - 1, 0)
var right_index: int = mini(index + 1, controls.size() - 1)
var left_index: int = index - 1 if column > 0 else index
var right_index: int = (
index + 1
if column < 2 and index + 1 < controls.size()
else index
)
control.focus_neighbor_left = control.get_path_to(
controls[left_index]
)
@ -3384,12 +3429,14 @@ func _configure_bag_item_focus() -> void:
controls[right_index]
)
control.focus_neighbor_top = (
control.get_path_to(active_tab)
control.get_path_to(control)
if index < 3
else control.get_path_to(controls[index - 3])
)
control.focus_neighbor_bottom = control.get_path_to(
controls[mini(index + 3, controls.size() - 1)]
controls[index + 3]
if index + 3 < controls.size()
else control
)
@ -3654,9 +3701,13 @@ func _layout_cooler_fish(animate: bool = true) -> void:
)
var side_margin: float = 2.0 if _compact_layout else 8.0
var top_margin: float = 4.0 if _compact_layout else 6.0
var slot_count: int = maxi(
_sorted_catches.size(),
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
)
var required_rows: int = ceili(
float(_sorted_catches.size()) / float(columns)
) if not _sorted_catches.is_empty() else 0
float(slot_count) / float(columns)
) if slot_count > 0 else 0
var required_height: float = (
top_margin * 2.0 + float(required_rows) * cell_size.y
)
@ -3667,6 +3718,16 @@ func _layout_cooler_fish(animate: bool = true) -> void:
_cooler_host.custom_minimum_size = content_size
_fish_field.custom_minimum_size = content_size
_cooler_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO
_sync_inventory_slot_visuals(
_fish_field,
_cooler_slot_nodes,
slot_count,
columns,
cell_size,
fish_size,
Vector2(side_margin, top_margin),
5.0,
)
for index: int in _sorted_catches.size():
var fish_catch: FishCatchType = _sorted_catches[index]
var fish_node := _fish_nodes.get(
@ -3724,31 +3785,38 @@ func _configure_cooler_fish_focus() -> void:
)
for index: int in controls.size():
var control: CoolerFishSpriteType = controls[index]
var column: int = index % columns
control.focus_neighbor_left = control.get_path_to(
controls[maxi(index - 1, 0)]
controls[index - 1] if column > 0 else control
)
control.focus_neighbor_right = control.get_path_to(
controls[mini(index + 1, controls.size() - 1)]
controls[index + 1]
if column < columns - 1 and index + 1 < controls.size()
else control
)
control.focus_neighbor_top = (
control.get_path_to(top_control)
if _shop_cooler_context_active and index < columns
else control.get_path_to(control)
if index < columns
else control.get_path_to(controls[index - columns])
)
control.focus_neighbor_bottom = (
control.get_path_to(_favorite_bubble)
if (
not _favorite_bubble.disabled
_shop_cooler_context_active
and not _favorite_bubble.disabled
and index + columns >= controls.size()
)
else control.get_path_to(_sell_all_bubble)
if (
not _sell_all_bubble.disabled
_shop_cooler_context_active
and not _sell_all_bubble.disabled
and index + columns >= controls.size()
)
else control.get_path_to(
controls[mini(index + columns, controls.size() - 1)]
)
else control.get_path_to(controls[index + columns])
if index + columns < controls.size()
else control.get_path_to(control)
)
var focused_node := _fish_nodes.get(
_fish_selection.get_focused_id()