feat: complete controller-first menu navigation
This commit is contained in:
parent
26c9308fcf
commit
4ac7d5edb8
35 changed files with 2013 additions and 85 deletions
|
|
@ -85,6 +85,12 @@ const MAIN_SHOP_BUYER_ID: StringName = &"main_fishing_shop"
|
|||
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
signal inventory_hotbar_context_changed(show_hotbar: bool)
|
||||
signal controller_hotbar_placement_requested(
|
||||
assignment_kind: PlayerHotbarType.AssignmentKind,
|
||||
identity: StringName,
|
||||
initial_slot: int,
|
||||
)
|
||||
signal controller_hotbar_placement_ended
|
||||
signal menu_exit_started
|
||||
signal shop_cooler_modal_changed(is_open: bool)
|
||||
|
||||
|
|
@ -124,6 +130,12 @@ enum CloseReason {
|
|||
TEARDOWN,
|
||||
}
|
||||
|
||||
enum ControllerOwnership {
|
||||
ITEM_LIST,
|
||||
NOTEPAD_ACTIONS,
|
||||
HOTBAR_PLACEMENT,
|
||||
}
|
||||
|
||||
const INVENTORY_MAIN_POSITION := Vector2(54.0, 166.0)
|
||||
const INVENTORY_MAIN_SIZE := Vector2(882.0, 484.0)
|
||||
const INVENTORY_PANEL_GAP := 16.0
|
||||
|
|
@ -302,6 +314,15 @@ var _last_inventory_section: Section = Section.COOLER
|
|||
var _bag_view: BagView = BagView.EQUIPMENT
|
||||
var _tackle_view: TackleView = TackleView.BAIT
|
||||
var _selected_tackle_item_id: StringName
|
||||
var _controller_ownership: ControllerOwnership = ControllerOwnership.ITEM_LIST
|
||||
var _controller_source_section: Section = Section.COOLER
|
||||
var _controller_source_identity: StringName
|
||||
var _controller_notepad_actions: Array[BaseButton] = []
|
||||
var _controller_hotbar_assignment_kind: PlayerHotbarType.AssignmentKind = (
|
||||
PlayerHotbarType.AssignmentKind.EMPTY
|
||||
)
|
||||
var _controller_hotbar_identity: StringName
|
||||
var _controller_previous_hotbar_slot: int = 0
|
||||
var _sort_mode: SortMode = SortMode.CATCH_ORDER
|
||||
var _sort_descending: bool = true
|
||||
var _fish_selection := FishBatchSelectionType.new()
|
||||
|
|
@ -589,12 +610,25 @@ 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
|
||||
if _handle_controller_ownership_input(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if visible:
|
||||
_reserve_main_navigation_for_page_switching()
|
||||
_reserve_visible_secondary_navigation()
|
||||
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
|
||||
|
|
@ -613,47 +647,360 @@ func _input(event: InputEvent) -> void:
|
|||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _handle_controller_ownership_input(event: InputEvent) -> bool:
|
||||
if not visible:
|
||||
return false
|
||||
var button_event := event as InputEventJoypadButton
|
||||
var accept_pressed: bool = (
|
||||
button_event != null
|
||||
and button_event.pressed
|
||||
and _event_matches_controller_role(
|
||||
event,
|
||||
ControllerMappingManagerType.ROLE_A,
|
||||
JOY_BUTTON_A,
|
||||
)
|
||||
)
|
||||
var cancel_pressed: bool = (
|
||||
button_event != null
|
||||
and button_event.pressed
|
||||
and _event_matches_controller_role(
|
||||
event,
|
||||
ControllerMappingManagerType.ROLE_B,
|
||||
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.HOTBAR_PLACEMENT:
|
||||
if accept_pressed:
|
||||
_confirm_controller_hotbar_placement()
|
||||
return true
|
||||
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 true
|
||||
return false
|
||||
if alternate_pressed:
|
||||
return _try_begin_controller_hotbar_placement()
|
||||
if accept_pressed:
|
||||
return _try_enter_notepad_controller_ownership()
|
||||
return false
|
||||
|
||||
|
||||
func _event_matches_controller_role(
|
||||
event: InputEvent,
|
||||
role: StringName,
|
||||
fallback_button: JoyButton,
|
||||
) -> bool:
|
||||
if _controller_mapping_manager != null:
|
||||
return _controller_mapping_manager.event_matches_role(event, role)
|
||||
var button_event := event as InputEventJoypadButton
|
||||
return button_event != null and button_event.button_index == fallback_button
|
||||
|
||||
|
||||
func _try_enter_notepad_controller_ownership() -> bool:
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
if focus_owner == null:
|
||||
return false
|
||||
var source_identity: StringName
|
||||
var actions: Array[BaseButton] = []
|
||||
if _current_section == Section.COOLER:
|
||||
var fish_node := focus_owner as CoolerFishSpriteType
|
||||
if fish_node == null or fish_node.catch_id.is_empty():
|
||||
return false
|
||||
source_identity = fish_node.catch_id
|
||||
_on_catch_card_pressed(source_identity)
|
||||
if not _favorite_bubble.disabled:
|
||||
actions.append(_favorite_bubble)
|
||||
if not _sell_bubble.disabled:
|
||||
actions.append(_sell_bubble)
|
||||
elif _current_section == Section.TACKLE_BOX:
|
||||
if not focus_owner.has_meta(&"controller_tackle_item_id"):
|
||||
return false
|
||||
source_identity = StringName(
|
||||
str(focus_owner.get_meta(&"controller_tackle_item_id"))
|
||||
)
|
||||
_select_tackle_item(source_identity)
|
||||
if _tackle_equip_button.visible and not _tackle_equip_button.disabled:
|
||||
actions.append(_tackle_equip_button)
|
||||
else:
|
||||
return false
|
||||
if actions.is_empty():
|
||||
_restore_controller_item_focus(_current_section, source_identity)
|
||||
return true
|
||||
_controller_source_section = _current_section
|
||||
_controller_source_identity = source_identity
|
||||
_controller_notepad_actions = actions
|
||||
_controller_ownership = ControllerOwnership.NOTEPAD_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
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
if focus_owner == null:
|
||||
return false
|
||||
var assignment_kind: PlayerHotbarType.AssignmentKind = (
|
||||
PlayerHotbarType.AssignmentKind.EMPTY
|
||||
)
|
||||
var identity: StringName
|
||||
if _current_section == Section.COOLER:
|
||||
var fish_node := focus_owner as CoolerFishSpriteType
|
||||
if fish_node == null or fish_node.catch_id.is_empty():
|
||||
return false
|
||||
assignment_kind = PlayerHotbarType.AssignmentKind.FISH
|
||||
identity = fish_node.catch_id
|
||||
elif _current_section == Section.BAG:
|
||||
var item_node := focus_owner as BagItemSpriteType
|
||||
if item_node == null or item_node.item_id.is_empty():
|
||||
return false
|
||||
var item: ItemDataType = (
|
||||
_item_catalog.get_item_by_id(item_node.item_id)
|
||||
if _item_catalog != null
|
||||
else null
|
||||
)
|
||||
if item == null or not item.hotbar_allowed:
|
||||
return true
|
||||
assignment_kind = PlayerHotbarType.AssignmentKind.ITEM
|
||||
identity = item_node.item_id
|
||||
else:
|
||||
return false
|
||||
_controller_source_section = _current_section
|
||||
_controller_source_identity = identity
|
||||
_controller_hotbar_assignment_kind = assignment_kind
|
||||
_controller_hotbar_identity = identity
|
||||
_controller_previous_hotbar_slot = _hotbar.get_selected_slot()
|
||||
var initial_slot: int = _find_controller_hotbar_assignment(
|
||||
assignment_kind,
|
||||
identity,
|
||||
)
|
||||
if initial_slot < 0:
|
||||
initial_slot = _controller_previous_hotbar_slot
|
||||
_controller_ownership = ControllerOwnership.HOTBAR_PLACEMENT
|
||||
controller_hotbar_placement_requested.emit(
|
||||
assignment_kind,
|
||||
identity,
|
||||
initial_slot,
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
func _find_controller_hotbar_assignment(
|
||||
assignment_kind: PlayerHotbarType.AssignmentKind,
|
||||
identity: StringName,
|
||||
) -> int:
|
||||
for slot_index: int in range(PlayerHotbarType.SLOT_COUNT):
|
||||
if (
|
||||
assignment_kind == PlayerHotbarType.AssignmentKind.FISH
|
||||
and _hotbar.get_fish_catch_id(slot_index) == identity
|
||||
):
|
||||
return slot_index
|
||||
if (
|
||||
assignment_kind == PlayerHotbarType.AssignmentKind.ITEM
|
||||
and _hotbar.get_item_id(slot_index) == identity
|
||||
):
|
||||
return slot_index
|
||||
return -1
|
||||
|
||||
|
||||
func _confirm_controller_hotbar_placement() -> void:
|
||||
if (
|
||||
_controller_ownership != ControllerOwnership.HOTBAR_PLACEMENT
|
||||
or _hotbar == null
|
||||
):
|
||||
return
|
||||
var slot_index: int = _hotbar.get_selected_slot()
|
||||
var assigned: bool = false
|
||||
if (
|
||||
_controller_hotbar_assignment_kind
|
||||
== PlayerHotbarType.AssignmentKind.FISH
|
||||
):
|
||||
assigned = _hotbar.assign_fish(
|
||||
slot_index,
|
||||
_controller_hotbar_identity,
|
||||
)
|
||||
elif (
|
||||
_controller_hotbar_assignment_kind
|
||||
== PlayerHotbarType.AssignmentKind.ITEM
|
||||
):
|
||||
assigned = _hotbar.assign_item(
|
||||
slot_index,
|
||||
_controller_hotbar_identity,
|
||||
)
|
||||
if assigned:
|
||||
_release_controller_ownership(true, false)
|
||||
|
||||
|
||||
func _release_controller_ownership(
|
||||
restore_source_focus: bool,
|
||||
restore_previous_hotbar_slot: bool,
|
||||
) -> void:
|
||||
if _controller_ownership == ControllerOwnership.ITEM_LIST:
|
||||
return
|
||||
var prior_ownership: ControllerOwnership = _controller_ownership
|
||||
var source_section: Section = _controller_source_section
|
||||
var source_identity: StringName = _controller_source_identity
|
||||
_controller_ownership = ControllerOwnership.ITEM_LIST
|
||||
_controller_notepad_actions.clear()
|
||||
_controller_source_identity = StringName()
|
||||
_controller_hotbar_assignment_kind = PlayerHotbarType.AssignmentKind.EMPTY
|
||||
_controller_hotbar_identity = StringName()
|
||||
if prior_ownership == ControllerOwnership.HOTBAR_PLACEMENT:
|
||||
if restore_previous_hotbar_slot and _hotbar != null:
|
||||
_hotbar.select_slot(_controller_previous_hotbar_slot)
|
||||
controller_hotbar_placement_ended.emit()
|
||||
if restore_source_focus:
|
||||
call_deferred(
|
||||
"_restore_controller_item_focus",
|
||||
source_section,
|
||||
source_identity,
|
||||
)
|
||||
|
||||
|
||||
func _restore_controller_item_focus(
|
||||
section: Section,
|
||||
identity: StringName,
|
||||
) -> void:
|
||||
if not visible or section != _current_section or identity.is_empty():
|
||||
return
|
||||
var target: Control
|
||||
if section == Section.COOLER:
|
||||
target = _fish_nodes.get(identity) as CoolerFishSpriteType
|
||||
elif section == Section.BAG:
|
||||
target = _bag_item_nodes.get(identity) as BagItemSpriteType
|
||||
elif section == Section.TACKLE_BOX:
|
||||
for child: Node in _tackle_item_list.get_children():
|
||||
var button := child as BaseButton
|
||||
if (
|
||||
button != null
|
||||
and button.has_meta(&"controller_tackle_item_id")
|
||||
and StringName(str(button.get_meta(
|
||||
&"controller_tackle_item_id"
|
||||
))) == identity
|
||||
):
|
||||
target = button
|
||||
break
|
||||
if (
|
||||
target != null
|
||||
and is_instance_valid(target)
|
||||
and target.is_visible_in_tree()
|
||||
and target.focus_mode != Control.FOCUS_NONE
|
||||
):
|
||||
target.grab_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_bumper: bool = (
|
||||
var uses_left_trigger: bool = (
|
||||
_controller_mapping_manager.event_uses_role(
|
||||
event, ControllerMappingManagerType.ROLE_LB
|
||||
event, ControllerMappingManagerType.ROLE_POINTER_MODIFIER
|
||||
)
|
||||
if use_mapping
|
||||
else (
|
||||
button_event != null
|
||||
and button_event.button_index == JOY_BUTTON_LEFT_SHOULDER
|
||||
motion_event != null
|
||||
and motion_event.axis == JOY_AXIS_TRIGGER_LEFT
|
||||
)
|
||||
)
|
||||
var uses_right_bumper: bool = (
|
||||
var uses_right_trigger: bool = (
|
||||
_controller_mapping_manager.event_uses_role(
|
||||
event, ControllerMappingManagerType.ROLE_RB
|
||||
event, ControllerMappingManagerType.ROLE_CAMERA_ZOOM
|
||||
)
|
||||
if use_mapping
|
||||
else (
|
||||
button_event != null
|
||||
and button_event.button_index == JOY_BUTTON_RIGHT_SHOULDER
|
||||
motion_event != null
|
||||
and motion_event.axis == JOY_AXIS_TRIGGER_RIGHT
|
||||
)
|
||||
)
|
||||
if (
|
||||
button_event == null
|
||||
or not (uses_left_bumper or uses_right_bumper)
|
||||
not (uses_left_trigger or uses_right_trigger)
|
||||
or not visible
|
||||
):
|
||||
return false
|
||||
if not button_event.pressed:
|
||||
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
|
||||
# Shoulder input belongs to the Player Menu while it is visible, even when
|
||||
# a transition or modal temporarily prevents changing pages. This keeps LB
|
||||
# from opening Chat and RB from leaking into gameplay behind the menu.
|
||||
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] = [
|
||||
|
|
@ -670,13 +1017,131 @@ func _handle_controller_page_switch(event: InputEvent) -> bool:
|
|||
if current_index < 0:
|
||||
current_index = 0
|
||||
var direction: int = (
|
||||
-1 if uses_left_bumper else 1
|
||||
-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
|
||||
var use_mapping: bool = _controller_mapping_manager != null
|
||||
var uses_left_bumper: bool = (
|
||||
_controller_mapping_manager.event_uses_role(
|
||||
event, ControllerMappingManagerType.ROLE_LB
|
||||
)
|
||||
if use_mapping
|
||||
else button_event.button_index == JOY_BUTTON_LEFT_SHOULDER
|
||||
)
|
||||
var uses_right_bumper: bool = (
|
||||
_controller_mapping_manager.event_uses_role(
|
||||
event, ControllerMappingManagerType.ROLE_RB
|
||||
)
|
||||
if use_mapping
|
||||
else button_event.button_index == JOY_BUTTON_RIGHT_SHOULDER
|
||||
)
|
||||
if not (uses_left_bumper or uses_right_bumper):
|
||||
return false
|
||||
if not button_event.pressed:
|
||||
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
|
||||
var direction: int = -1 if uses_left_bumper else 1
|
||||
if _is_inventory_section(_current_section):
|
||||
var inventory_sections: Array[Section] = [
|
||||
Section.COOLER,
|
||||
Section.BAG,
|
||||
Section.TACKLE_BOX,
|
||||
]
|
||||
var inventory_index: int = inventory_sections.find(_current_section)
|
||||
_show_section(inventory_sections[wrapi(
|
||||
inventory_index + direction,
|
||||
0,
|
||||
inventory_sections.size(),
|
||||
)])
|
||||
return true
|
||||
_cycle_visible_secondary_tabs(direction)
|
||||
return true
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
func _collect_visible_toggle_buttons(
|
||||
root: Node,
|
||||
navigation_cluster: Control,
|
||||
grouped_buttons: Dictionary,
|
||||
) -> void:
|
||||
for child: Node in root.get_children():
|
||||
var child_control := child as Control
|
||||
if child_control != null and not child_control.is_visible_in_tree():
|
||||
continue
|
||||
var button := child as BaseButton
|
||||
if (
|
||||
button != null
|
||||
and button.toggle_mode
|
||||
and (
|
||||
navigation_cluster == null
|
||||
or not navigation_cluster.is_ancestor_of(button)
|
||||
)
|
||||
):
|
||||
var group_key: Variant = (
|
||||
button.button_group
|
||||
if button.button_group != null
|
||||
else button.get_parent()
|
||||
)
|
||||
if not grouped_buttons.has(group_key):
|
||||
grouped_buttons[group_key] = []
|
||||
var buttons := grouped_buttons[group_key] as Array
|
||||
buttons.append(button)
|
||||
_collect_visible_toggle_buttons(
|
||||
child, navigation_cluster, grouped_buttons
|
||||
)
|
||||
|
||||
|
||||
func _handle_direct_page_shortcut(event: InputEvent) -> bool:
|
||||
var key_event := event as InputEventKey
|
||||
if (
|
||||
|
|
@ -772,6 +1237,7 @@ func open_menu() -> void:
|
|||
or not _fishing_spot.can_open_player_menu()
|
||||
):
|
||||
return
|
||||
_release_controller_ownership(false, true)
|
||||
_menu_generation += 1
|
||||
_transition_generation += 1
|
||||
_cancel_presentation_tween()
|
||||
|
|
@ -921,6 +1387,7 @@ func close_menu(
|
|||
if reason != CloseReason.USER:
|
||||
_finish_close(reason, restore_controls, _menu_generation)
|
||||
return
|
||||
_release_controller_ownership(false, true)
|
||||
get_viewport().gui_cancel_drag()
|
||||
_close_sale_confirmation()
|
||||
if reason in [
|
||||
|
|
@ -949,6 +1416,7 @@ func close_for_session_end() -> void:
|
|||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_release_controller_ownership(false, true)
|
||||
_cancel_presentation_tween()
|
||||
_cancel_page_tween()
|
||||
if visible:
|
||||
|
|
@ -1007,6 +1475,7 @@ func _show_section(section: Section) -> void:
|
|||
and _profile_page.request_close_confirmation()
|
||||
):
|
||||
return
|
||||
_release_controller_ownership(false, true)
|
||||
_begin_page_transition(section)
|
||||
|
||||
|
||||
|
|
@ -1122,22 +1591,39 @@ func _focus_current_section() -> void:
|
|||
if _shop_cooler_context_active:
|
||||
_focus_shop_cooler()
|
||||
return
|
||||
if _current_section == Section.COOLER:
|
||||
_cooler_sub_tab.grab_focus()
|
||||
elif _current_section == Section.BAG:
|
||||
_bag_sub_tab.grab_focus()
|
||||
elif _current_section == Section.TACKLE_BOX:
|
||||
_tackle_sub_tab.grab_focus()
|
||||
elif _current_section == Section.LOGBOOK:
|
||||
_catalog_logbook.focus_initial()
|
||||
elif _current_section == Section.NET:
|
||||
_the_net_page.focus_initial()
|
||||
elif _current_section == Section.MAIL:
|
||||
_mail_tab.grab_focus()
|
||||
elif _current_section == Section.PLAYERS:
|
||||
_players_tab.grab_focus()
|
||||
else:
|
||||
_profile_tab.grab_focus()
|
||||
_reserve_main_navigation_for_page_switching()
|
||||
_reserve_visible_secondary_navigation()
|
||||
var navigation_cluster := get_node_or_null("%NavigationCluster") as Control
|
||||
var candidates: Array[Control] = []
|
||||
_collect_focusable_content(self, navigation_cluster, candidates)
|
||||
if candidates.is_empty():
|
||||
return
|
||||
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
|
||||
return first.global_position.x < second.global_position.x
|
||||
)
|
||||
candidates[0].grab_focus()
|
||||
|
||||
|
||||
func _collect_focusable_content(
|
||||
root: Node,
|
||||
navigation_cluster: Control,
|
||||
output: Array[Control],
|
||||
) -> void:
|
||||
for child: Node in root.get_children():
|
||||
if child == navigation_cluster:
|
||||
continue
|
||||
var control := child as Control
|
||||
if control != null and not control.is_visible_in_tree():
|
||||
continue
|
||||
if (
|
||||
control != null
|
||||
and control.focus_mode != Control.FOCUS_NONE
|
||||
and not control is ScrollBar
|
||||
):
|
||||
output.append(control)
|
||||
_collect_focusable_content(child, navigation_cluster, output)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
|
|
@ -1161,6 +1647,7 @@ func _process(delta: float) -> void:
|
|||
|
||||
|
||||
func _configure_navigation_focus() -> void:
|
||||
_reserve_main_navigation_for_page_switching()
|
||||
var navigation: Array[BubbleButtonType] = [
|
||||
_inventory_tab,
|
||||
_logbook_tab,
|
||||
|
|
@ -1182,6 +1669,48 @@ func _configure_navigation_focus() -> void:
|
|||
bubble.focus_neighbor_right = bubble.get_path_to(next)
|
||||
bubble.focus_neighbor_top = bubble.focus_neighbor_left
|
||||
bubble.focus_neighbor_bottom = bubble.focus_neighbor_right
|
||||
|
||||
|
||||
func _reserve_main_navigation_for_page_switching() -> void:
|
||||
var navigation_cluster := get_node_or_null("%NavigationCluster") as Control
|
||||
if navigation_cluster == null:
|
||||
return
|
||||
_set_descendant_focus_disabled(navigation_cluster)
|
||||
|
||||
|
||||
func _set_descendant_focus_disabled(root: Node) -> void:
|
||||
for child: Node in root.get_children():
|
||||
var control := child as Control
|
||||
if control != null:
|
||||
control.focus_mode = Control.FOCUS_NONE
|
||||
_set_descendant_focus_disabled(child)
|
||||
|
||||
|
||||
func _reserve_visible_secondary_navigation() -> void:
|
||||
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_tabs: Array = []
|
||||
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_tabs = buttons
|
||||
selected_group_y = group_y
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
var displaced_focus: bool = selected_tabs.has(focus_owner)
|
||||
for item: Variant in selected_tabs:
|
||||
var tab := item as Control
|
||||
tab.focus_mode = Control.FOCUS_NONE
|
||||
if displaced_focus:
|
||||
focus_owner.release_focus()
|
||||
call_deferred("_focus_current_section")
|
||||
var inventory_tabs: Array[Button] = [
|
||||
_cooler_sub_tab,
|
||||
_tackle_sub_tab,
|
||||
|
|
@ -1359,6 +1888,7 @@ func _refresh_tackle_box() -> void:
|
|||
row.text = "%s ×%d" % [item.display_name, owned.quantity]
|
||||
row.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
row.toggle_mode = true
|
||||
row.set_meta(&"controller_tackle_item_id", owned.item_id)
|
||||
row.button_pressed = owned.item_id == _selected_tackle_item_id
|
||||
if item.is_bait() and item.icon != null:
|
||||
_apply_tackle_bait_button_style(row)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue