fix: stabilize controller menu navigation
This commit is contained in:
parent
5df8b2e7cc
commit
a8be597d27
32 changed files with 910 additions and 2376 deletions
11
main/main.gd
11
main/main.gd
|
|
@ -1328,20 +1328,13 @@ func _handle_data_root_controller_input(event: InputEvent) -> bool:
|
||||||
if picker_visible
|
if picker_visible
|
||||||
else _data_setup_dialog.gui_get_focus_owner()
|
else _data_setup_dialog.gui_get_focus_owner()
|
||||||
)
|
)
|
||||||
var focused_button := focused as BaseButton
|
|
||||||
if (
|
|
||||||
focused_button != null
|
|
||||||
and FileDialogControllerNavigation.activate_control(focused_button)
|
|
||||||
):
|
|
||||||
return true
|
|
||||||
if (
|
if (
|
||||||
(focused is LineEdit or focused is TextEdit)
|
(focused is LineEdit or focused is TextEdit)
|
||||||
and _game_ui.request_controller_text_entry_for(focused)
|
and _game_ui.request_controller_text_entry_for(focused)
|
||||||
):
|
):
|
||||||
return true
|
return true
|
||||||
if setup_visible:
|
# Buttons are activated once through Godot's native ui_accept path. This
|
||||||
_data_setup_dialog.get_ok_button().pressed.emit()
|
# handler only owns modal back behavior and controller text entry.
|
||||||
return true
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ readonly -a QUICK_TESTS=(
|
||||||
"tests/android_readiness_validation.gd"
|
"tests/android_readiness_validation.gd"
|
||||||
"tests/camera_drag_validation.gd"
|
"tests/camera_drag_validation.gd"
|
||||||
"tests/controller_focus_presentation_validation.gd"
|
"tests/controller_focus_presentation_validation.gd"
|
||||||
"tests/controller_focus_recovery_validation.gd"
|
|
||||||
"tests/controller_menu_accessibility_validation.gd"
|
"tests/controller_menu_accessibility_validation.gd"
|
||||||
"tests/controller_mapping_validation.gd"
|
"tests/controller_mapping_validation.gd"
|
||||||
"tests/controller_world_interaction_validation.gd"
|
"tests/controller_world_interaction_validation.gd"
|
||||||
|
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
extends SceneTree
|
|
||||||
|
|
||||||
const ControllerFocusRecoveryType = preload(
|
|
||||||
"res://ui/controller_focus_recovery.gd"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _failures: Array[String] = []
|
|
||||||
|
|
||||||
|
|
||||||
func _initialize() -> void:
|
|
||||||
_run.call_deferred()
|
|
||||||
|
|
||||||
|
|
||||||
func _run() -> void:
|
|
||||||
var stage := Control.new()
|
|
||||||
stage.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
||||||
root.add_child(stage)
|
|
||||||
var recovery := ControllerFocusRecoveryType.new()
|
|
||||||
stage.add_child(recovery)
|
|
||||||
var option_list := VBoxContainer.new()
|
|
||||||
option_list.position = Vector2(100.0, 100.0)
|
|
||||||
stage.add_child(option_list)
|
|
||||||
var original := _make_button("cute", "cute (show variants)")
|
|
||||||
option_list.add_child(original)
|
|
||||||
await process_frame
|
|
||||||
original.grab_focus()
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == null,
|
|
||||||
"programmatic focus stays pending outside navigation mode",
|
|
||||||
)
|
|
||||||
var keyboard_navigation := InputEventKey.new()
|
|
||||||
keyboard_navigation.keycode = KEY_DOWN
|
|
||||||
keyboard_navigation.pressed = true
|
|
||||||
recovery._input(keyboard_navigation)
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == original,
|
|
||||||
"keyboard navigation restores pending initial focus",
|
|
||||||
)
|
|
||||||
var menu_shortcut := InputEventKey.new()
|
|
||||||
menu_shortcut.physical_keycode = KEY_TAB
|
|
||||||
menu_shortcut.pressed = true
|
|
||||||
recovery._input(menu_shortcut)
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == null,
|
|
||||||
"the menu shortcut does not leave a pseudo-focused option",
|
|
||||||
)
|
|
||||||
|
|
||||||
var controller_event := InputEventJoypadButton.new()
|
|
||||||
controller_event.button_index = JOY_BUTTON_A
|
|
||||||
controller_event.pressed = true
|
|
||||||
recovery._input(controller_event)
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == original,
|
|
||||||
"controller navigation restores pending initial focus",
|
|
||||||
)
|
|
||||||
|
|
||||||
option_list.remove_child(original)
|
|
||||||
original.queue_free()
|
|
||||||
var replacement := _make_button("cute", "cute (hide variants)")
|
|
||||||
option_list.add_child(replacement)
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == replacement,
|
|
||||||
"focus follows a rebuilt semantic option",
|
|
||||||
)
|
|
||||||
|
|
||||||
var explicit_target := _make_button("explicit", "explicit")
|
|
||||||
option_list.add_child(explicit_target)
|
|
||||||
option_list.remove_child(replacement)
|
|
||||||
replacement.queue_free()
|
|
||||||
explicit_target.grab_focus()
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == explicit_target,
|
|
||||||
"explicit focus changes take precedence over recovery",
|
|
||||||
)
|
|
||||||
var pointer_motion := InputEventMouseMotion.new()
|
|
||||||
pointer_motion.position = Vector2(4.0, 4.0)
|
|
||||||
recovery._input(pointer_motion)
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == null,
|
|
||||||
"mouse motion releases controller focus",
|
|
||||||
)
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == null,
|
|
||||||
"mouse motion prevents automatic focus recovery",
|
|
||||||
)
|
|
||||||
recovery._input(controller_event)
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == explicit_target,
|
|
||||||
"controller navigation can resume from pointer mode",
|
|
||||||
)
|
|
||||||
var pointer_press := InputEventMouseButton.new()
|
|
||||||
pointer_press.button_index = MOUSE_BUTTON_LEFT
|
|
||||||
pointer_press.pressed = true
|
|
||||||
recovery._input(pointer_press)
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == explicit_target,
|
|
||||||
"mouse-down preserves focus through button activation",
|
|
||||||
)
|
|
||||||
var pointer_release := InputEventMouseButton.new()
|
|
||||||
pointer_release.button_index = MOUSE_BUTTON_LEFT
|
|
||||||
pointer_release.pressed = false
|
|
||||||
recovery._input(pointer_release)
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == null,
|
|
||||||
"mouse-up clears the completed button focus",
|
|
||||||
)
|
|
||||||
recovery._input(controller_event)
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
|
|
||||||
var outside_button := _make_button("outside", "outside")
|
|
||||||
stage.add_child(outside_button)
|
|
||||||
option_list.hide()
|
|
||||||
root.gui_release_focus()
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == null,
|
|
||||||
"focus recovery never escapes a hidden menu scope",
|
|
||||||
)
|
|
||||||
option_list.show()
|
|
||||||
explicit_target.grab_focus()
|
|
||||||
await process_frame
|
|
||||||
var leave_world_ui_event := InputEventJoypadButton.new()
|
|
||||||
leave_world_ui_event.button_index = JOY_BUTTON_LEFT_SHOULDER
|
|
||||||
leave_world_ui_event.pressed = true
|
|
||||||
recovery._input(leave_world_ui_event)
|
|
||||||
root.gui_release_focus()
|
|
||||||
await process_frame
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
root.gui_get_focus_owner() == null,
|
|
||||||
"LB intentionally leaving world UI is never recovered",
|
|
||||||
)
|
|
||||||
var accessibility_only := Control.new()
|
|
||||||
accessibility_only.focus_mode = Control.FOCUS_ACCESSIBILITY
|
|
||||||
stage.add_child(accessibility_only)
|
|
||||||
await process_frame
|
|
||||||
_expect(
|
|
||||||
not bool(recovery.call("_is_focusable", accessibility_only)),
|
|
||||||
"accessibility-only controls are not recovery targets",
|
|
||||||
)
|
|
||||||
|
|
||||||
stage.queue_free()
|
|
||||||
if _failures.is_empty():
|
|
||||||
print("Controller focus recovery validation: PASS")
|
|
||||||
quit(0)
|
|
||||||
return
|
|
||||||
for failure: String in _failures:
|
|
||||||
push_error(failure)
|
|
||||||
quit(1)
|
|
||||||
|
|
||||||
|
|
||||||
func _make_button(text: String, tooltip: String) -> Button:
|
|
||||||
var button := Button.new()
|
|
||||||
button.text = text
|
|
||||||
button.tooltip_text = tooltip
|
|
||||||
button.custom_minimum_size = Vector2(120.0, 48.0)
|
|
||||||
return button
|
|
||||||
|
|
||||||
|
|
||||||
func _expect(condition: bool, message: String) -> void:
|
|
||||||
if not condition:
|
|
||||||
_failures.append(message)
|
|
||||||
|
|
@ -44,6 +44,7 @@ func _run() -> void:
|
||||||
await _validate_join_game_navigation()
|
await _validate_join_game_navigation()
|
||||||
await _validate_data_settings_navigation()
|
await _validate_data_settings_navigation()
|
||||||
await _validate_settings_adjustment_navigation()
|
await _validate_settings_adjustment_navigation()
|
||||||
|
await _validate_settings_presentation_requires_apply()
|
||||||
await _validate_mail_navigation()
|
await _validate_mail_navigation()
|
||||||
await _validate_profile_confirmation_focus()
|
await _validate_profile_confirmation_focus()
|
||||||
await _validate_profile_voice_navigation()
|
await _validate_profile_voice_navigation()
|
||||||
|
|
@ -163,6 +164,47 @@ func _validate_join_game_navigation() -> void:
|
||||||
)
|
)
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
|
var saved_entries: Array[SavedServerEntry] = []
|
||||||
|
server_list.clear()
|
||||||
|
for index: int in 3:
|
||||||
|
var entry := SavedServerEntry.new()
|
||||||
|
entry.entry_id = "controller-saved-%d" % index
|
||||||
|
entry.display_name = "Saved server %d" % index
|
||||||
|
saved_entries.append(entry)
|
||||||
|
server_list.add_item(entry.display_name)
|
||||||
|
page.set("_mode", 2)
|
||||||
|
page.set("_visible_entries", saved_entries)
|
||||||
|
server_list.show()
|
||||||
|
page.call("_configure_controller_navigation")
|
||||||
|
page.call("_restore_entry_selection_and_focus", 1)
|
||||||
|
await process_frame
|
||||||
|
_expect(
|
||||||
|
server_list.is_selected(1)
|
||||||
|
and root.gui_get_focus_owner() == server_list,
|
||||||
|
"Saved-server refresh did not restore the same list position.",
|
||||||
|
)
|
||||||
|
saved_entries.remove_at(1)
|
||||||
|
server_list.remove_item(1)
|
||||||
|
page.set("_visible_entries", saved_entries)
|
||||||
|
page.call("_restore_entry_selection_and_focus", 1)
|
||||||
|
await process_frame
|
||||||
|
_expect(
|
||||||
|
server_list.is_selected(1)
|
||||||
|
and (
|
||||||
|
page.get("_selected_entry") as SavedServerEntry
|
||||||
|
).entry_id == "controller-saved-2",
|
||||||
|
"Deleting a saved server did not select the row that replaced it.",
|
||||||
|
)
|
||||||
|
saved_entries.clear()
|
||||||
|
server_list.clear()
|
||||||
|
page.set("_visible_entries", saved_entries)
|
||||||
|
page.call("_restore_entry_selection_and_focus", 0)
|
||||||
|
await process_frame
|
||||||
|
_expect(
|
||||||
|
root.gui_get_focus_owner() == saved,
|
||||||
|
"An empty saved-server list did not return focus to its mode tab.",
|
||||||
|
)
|
||||||
|
|
||||||
page.queue_free()
|
page.queue_free()
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
|
|
@ -190,13 +232,13 @@ func _validate_data_settings_navigation() -> void:
|
||||||
open_activation["count"] = int(open_activation["count"]) + 1
|
open_activation["count"] = int(open_activation["count"]) + 1
|
||||||
)
|
)
|
||||||
data_tab.grab_focus()
|
data_tab.grab_focus()
|
||||||
var accept_press := InputEventAction.new()
|
var accept_press := InputEventJoypadButton.new()
|
||||||
accept_press.action = &"ui_accept"
|
accept_press.button_index = JOY_BUTTON_A
|
||||||
accept_press.pressed = true
|
accept_press.pressed = true
|
||||||
Input.parse_input_event(accept_press)
|
Input.parse_input_event(accept_press)
|
||||||
await process_frame
|
await process_frame
|
||||||
var accept_release := InputEventAction.new()
|
var accept_release := InputEventJoypadButton.new()
|
||||||
accept_release.action = &"ui_accept"
|
accept_release.button_index = JOY_BUTTON_A
|
||||||
accept_release.pressed = false
|
accept_release.pressed = false
|
||||||
Input.parse_input_event(accept_release)
|
Input.parse_input_event(accept_release)
|
||||||
for _frame: int in 2:
|
for _frame: int in 2:
|
||||||
|
|
@ -213,10 +255,6 @@ func _validate_data_settings_navigation() -> void:
|
||||||
root.gui_get_focus_owner() == data_tab,
|
root.gui_get_focus_owner() == data_tab,
|
||||||
"Selecting the Data tab transferred focus into its actions.",
|
"Selecting the Data tab transferred focus into its actions.",
|
||||||
)
|
)
|
||||||
var test_data_root := PlayerDataRoot.new()
|
|
||||||
root.add_child(test_data_root)
|
|
||||||
test_data_root.root_path = "/tmp/netfishing-controller-data"
|
|
||||||
panel.set("_data_root", test_data_root)
|
|
||||||
open_data_folder.grab_focus()
|
open_data_folder.grab_focus()
|
||||||
_expect(
|
_expect(
|
||||||
root.gui_get_focus_owner() == open_data_folder,
|
root.gui_get_focus_owner() == open_data_folder,
|
||||||
|
|
@ -226,13 +264,13 @@ func _validate_data_settings_navigation() -> void:
|
||||||
not open_data_folder.disabled,
|
not open_data_folder.disabled,
|
||||||
"Open Data Folder is unexpectedly disabled.",
|
"Open Data Folder is unexpectedly disabled.",
|
||||||
)
|
)
|
||||||
var action_press := InputEventAction.new()
|
var action_press := InputEventJoypadButton.new()
|
||||||
action_press.action = &"ui_accept"
|
action_press.button_index = JOY_BUTTON_A
|
||||||
action_press.pressed = true
|
action_press.pressed = true
|
||||||
Input.parse_input_event(action_press)
|
Input.parse_input_event(action_press)
|
||||||
await process_frame
|
await process_frame
|
||||||
var action_release := InputEventAction.new()
|
var action_release := InputEventJoypadButton.new()
|
||||||
action_release.action = &"ui_accept"
|
action_release.button_index = JOY_BUTTON_A
|
||||||
action_release.pressed = false
|
action_release.pressed = false
|
||||||
Input.parse_input_event(action_release)
|
Input.parse_input_event(action_release)
|
||||||
for _frame: int in 2:
|
for _frame: int in 2:
|
||||||
|
|
@ -244,31 +282,27 @@ func _validate_data_settings_navigation() -> void:
|
||||||
% int(open_activation["count"])
|
% int(open_activation["count"])
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
var confirmations: Array[Node] = panel.find_children(
|
|
||||||
"*", "ConfirmationDialog", true, false
|
|
||||||
)
|
|
||||||
_expect(
|
_expect(
|
||||||
confirmations.size() == 1
|
panel.find_children(
|
||||||
and (confirmations.front() as ConfirmationDialog).visible,
|
"*", "ConfirmationDialog", true, false
|
||||||
"Explicitly accepting Open Data Folder did not request confirmation.",
|
).is_empty(),
|
||||||
|
"Open Data Folder left a controller-activatable dialog behind.",
|
||||||
)
|
)
|
||||||
if not confirmations.is_empty():
|
var change_data_folder := panel.get_node("%ChangeDataFolder") as Button
|
||||||
var confirmation := confirmations.front() as ConfirmationDialog
|
var copy_fingerprint := panel.get_node("%CopyPlayerFingerprint") as Button
|
||||||
_expect(
|
var export_player := panel.get_node("%ExportPlayerIdentity") as Button
|
||||||
confirmation.gui_get_focus_owner()
|
var import_player := panel.get_node("%ImportPlayerIdentity") as Button
|
||||||
== confirmation.get_cancel_button(),
|
var export_host := panel.get_node("%ExportHostIdentity") as Button
|
||||||
"Open Data Folder confirmation did not default to Cancel.",
|
var import_host := panel.get_node("%ImportHostIdentity") as Button
|
||||||
)
|
|
||||||
confirmation.queue_free()
|
|
||||||
var controls: Array[Control] = [
|
var controls: Array[Control] = [
|
||||||
data_tab,
|
data_tab,
|
||||||
open_data_folder,
|
open_data_folder,
|
||||||
panel.get_node("%ChangeDataFolder") as Control,
|
change_data_folder,
|
||||||
panel.get_node("%CopyPlayerFingerprint") as Control,
|
copy_fingerprint,
|
||||||
panel.get_node("%ExportPlayerIdentity") as Control,
|
export_player,
|
||||||
panel.get_node("%ImportPlayerIdentity") as Control,
|
import_player,
|
||||||
panel.get_node("%ExportHostIdentity") as Control,
|
export_host,
|
||||||
panel.get_node("%ImportHostIdentity") as Control,
|
import_host,
|
||||||
panel.get_node("%ApplySettingsButton") as Control,
|
panel.get_node("%ApplySettingsButton") as Control,
|
||||||
panel.get_node("%SettingsBackButton") as Control,
|
panel.get_node("%SettingsBackButton") as Control,
|
||||||
]
|
]
|
||||||
|
|
@ -278,13 +312,113 @@ func _validate_data_settings_navigation() -> void:
|
||||||
"Data & Identity control %s is not controller-focusable."
|
"Data & Identity control %s is not controller-focusable."
|
||||||
% control.name,
|
% control.name,
|
||||||
)
|
)
|
||||||
|
_assert_neighbor(data_tab, &"focus_neighbor_bottom", open_data_folder)
|
||||||
|
_assert_neighbor(
|
||||||
|
open_data_folder,
|
||||||
|
&"focus_neighbor_right",
|
||||||
|
change_data_folder,
|
||||||
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
open_data_folder,
|
||||||
|
&"focus_neighbor_bottom",
|
||||||
|
copy_fingerprint,
|
||||||
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
change_data_folder,
|
||||||
|
&"focus_neighbor_left",
|
||||||
|
open_data_folder,
|
||||||
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
change_data_folder,
|
||||||
|
&"focus_neighbor_bottom",
|
||||||
|
copy_fingerprint,
|
||||||
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
copy_fingerprint,
|
||||||
|
&"focus_neighbor_top",
|
||||||
|
open_data_folder,
|
||||||
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
copy_fingerprint,
|
||||||
|
&"focus_neighbor_bottom",
|
||||||
|
export_player,
|
||||||
|
)
|
||||||
|
_assert_neighbor(export_player, &"focus_neighbor_right", import_player)
|
||||||
|
_assert_neighbor(export_player, &"focus_neighbor_bottom", export_host)
|
||||||
|
_assert_neighbor(import_player, &"focus_neighbor_left", export_player)
|
||||||
|
_assert_neighbor(import_player, &"focus_neighbor_bottom", import_host)
|
||||||
|
_assert_neighbor(export_host, &"focus_neighbor_top", export_player)
|
||||||
|
_assert_neighbor(export_host, &"focus_neighbor_right", import_host)
|
||||||
|
_assert_neighbor(import_host, &"focus_neighbor_top", import_player)
|
||||||
|
_assert_neighbor(import_host, &"focus_neighbor_left", export_host)
|
||||||
_assert_directionally_reachable(controls.front(), controls)
|
_assert_directionally_reachable(controls.front(), controls)
|
||||||
|
await process_frame
|
||||||
|
open_data_folder.grab_focus()
|
||||||
|
panel.call("_select_page", &"sound", true)
|
||||||
|
_expect(
|
||||||
|
panel.get_active_page_id() == &"sound",
|
||||||
|
"Settings did not leave the Data page.",
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
root.gui_get_focus_owner() == panel.get_node("%SoundTab"),
|
||||||
|
"Leaving Data retained focus on a hidden Data action.",
|
||||||
|
)
|
||||||
|
var sound_down: Control = (
|
||||||
|
(panel.get_node("%SoundTab") as Control).find_valid_focus_neighbor(
|
||||||
|
SIDE_BOTTOM
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
sound_down != null and not panel.get_node("%DataPage").is_ancestor_of(
|
||||||
|
sound_down
|
||||||
|
),
|
||||||
|
"Sound navigation still pointed into the hidden Data page.",
|
||||||
|
)
|
||||||
|
var hidden_data_activation_count: int = int(open_activation["count"])
|
||||||
|
Input.parse_input_event(action_press)
|
||||||
|
await process_frame
|
||||||
|
Input.parse_input_event(action_release)
|
||||||
|
for _frame: int in 2:
|
||||||
|
await process_frame
|
||||||
|
_expect(
|
||||||
|
int(open_activation["count"]) == hidden_data_activation_count,
|
||||||
|
"Controller A activated Open Data Folder from another settings page.",
|
||||||
|
)
|
||||||
|
open_data_folder.pressed.emit()
|
||||||
|
await process_frame
|
||||||
|
_expect(
|
||||||
|
panel.find_children(
|
||||||
|
"*", "ConfirmationDialog", true, false
|
||||||
|
).is_empty(),
|
||||||
|
"A hidden Data action created a folder dialog.",
|
||||||
|
)
|
||||||
|
panel.call("_select_page", &"data", true)
|
||||||
|
await process_frame
|
||||||
|
copy_fingerprint.grab_focus()
|
||||||
|
var feedback := panel.get_node("%SettingsFeedback") as Label
|
||||||
|
feedback.text = "activation boundary intact"
|
||||||
|
open_data_folder.pressed.emit()
|
||||||
|
await process_frame
|
||||||
|
_expect(
|
||||||
|
feedback.text == "activation boundary intact",
|
||||||
|
(
|
||||||
|
"Open Data Folder crossed its activation boundary without owning "
|
||||||
|
+ "controller focus."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
panel.hide()
|
||||||
|
await process_frame
|
||||||
|
var hidden_focus_owner: Control = root.gui_get_focus_owner()
|
||||||
|
_expect(
|
||||||
|
hidden_focus_owner == null
|
||||||
|
or not panel.is_ancestor_of(hidden_focus_owner),
|
||||||
|
"Hiding Settings retained focus on one of its controls.",
|
||||||
|
)
|
||||||
_expect(
|
_expect(
|
||||||
panel.find_children("*", "BubbleButton", true, false).is_empty(),
|
panel.find_children("*", "BubbleButton", true, false).is_empty(),
|
||||||
"Settings children still contain bubble controls.",
|
"Settings children still contain bubble controls.",
|
||||||
)
|
)
|
||||||
panel.queue_free()
|
panel.queue_free()
|
||||||
test_data_root.queue_free()
|
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -313,6 +447,11 @@ func _validate_settings_adjustment_navigation() -> void:
|
||||||
var controller_slider := panel.get_node(
|
var controller_slider := panel.get_node(
|
||||||
"%ControllerSensitivitySlider"
|
"%ControllerSensitivitySlider"
|
||||||
) as HSlider
|
) as HSlider
|
||||||
|
var on_screen_keyboard := panel.get_node(
|
||||||
|
"%OnScreenKeyboardToggle"
|
||||||
|
) as Button
|
||||||
|
var controller_binds := panel.get_node("%ControllerMapping") as Button
|
||||||
|
var keyboard_binds := panel.get_node("%KeyboardMapping") as Button
|
||||||
_expect(
|
_expect(
|
||||||
mouse_slider.focus_mode == Control.FOCUS_ALL
|
mouse_slider.focus_mode == Control.FOCUS_ALL
|
||||||
and controller_slider.focus_mode == Control.FOCUS_ALL,
|
and controller_slider.focus_mode == Control.FOCUS_ALL,
|
||||||
|
|
@ -323,14 +462,39 @@ func _validate_settings_adjustment_navigation() -> void:
|
||||||
and is_equal_approx(controller_slider.step, 0.1),
|
and is_equal_approx(controller_slider.step, 0.1),
|
||||||
"Sensitivity sliders do not retain their authored increments.",
|
"Sensitivity sliders do not retain their authored increments.",
|
||||||
)
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
on_screen_keyboard,
|
||||||
|
&"focus_neighbor_bottom",
|
||||||
|
controller_binds,
|
||||||
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
controller_binds,
|
||||||
|
&"focus_neighbor_top",
|
||||||
|
on_screen_keyboard,
|
||||||
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
controller_binds,
|
||||||
|
&"focus_neighbor_right",
|
||||||
|
keyboard_binds,
|
||||||
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
keyboard_binds,
|
||||||
|
&"focus_neighbor_top",
|
||||||
|
on_screen_keyboard,
|
||||||
|
)
|
||||||
|
_assert_neighbor(
|
||||||
|
keyboard_binds,
|
||||||
|
&"focus_neighbor_left",
|
||||||
|
controller_binds,
|
||||||
|
)
|
||||||
var control_inputs: Array[Control] = [
|
var control_inputs: Array[Control] = [
|
||||||
panel.get_node("%ControlsTab") as Control,
|
panel.get_node("%ControlsTab") as Control,
|
||||||
mouse_slider,
|
mouse_slider,
|
||||||
controller_slider,
|
controller_slider,
|
||||||
panel.get_node("%InvertYToggle") as Control,
|
panel.get_node("%InvertYToggle") as Control,
|
||||||
panel.get_node("%OnScreenKeyboardToggle") as Control,
|
on_screen_keyboard,
|
||||||
panel.get_node("%ControllerMapping") as Control,
|
controller_binds,
|
||||||
panel.get_node("%KeyboardMapping") as Control,
|
keyboard_binds,
|
||||||
panel.get_node("%ApplySettingsButton") as Control,
|
panel.get_node("%ApplySettingsButton") as Control,
|
||||||
panel.get_node("%SettingsBackButton") as Control,
|
panel.get_node("%SettingsBackButton") as Control,
|
||||||
]
|
]
|
||||||
|
|
@ -339,6 +503,38 @@ func _validate_settings_adjustment_navigation() -> void:
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_settings_presentation_requires_apply() -> void:
|
||||||
|
var panel := SettingsPanelScene.instantiate() as SettingsPanel
|
||||||
|
var settings_manager := PlayerSettingsManager.new()
|
||||||
|
root.add_child(settings_manager)
|
||||||
|
root.add_child(panel)
|
||||||
|
await process_frame
|
||||||
|
panel.open_panel(settings_manager)
|
||||||
|
await process_frame
|
||||||
|
var chat_dock := panel.get_node("%ChatDockSelector") as OptionButton
|
||||||
|
var chat_mode := panel.get_node("%ChatModeSelector") as OptionButton
|
||||||
|
var paint_dock := panel.get_node("%PaintDockSelector") as OptionButton
|
||||||
|
chat_dock.select(1)
|
||||||
|
chat_dock.item_selected.emit(1)
|
||||||
|
chat_mode.select(1)
|
||||||
|
chat_mode.item_selected.emit(1)
|
||||||
|
paint_dock.select(0)
|
||||||
|
paint_dock.item_selected.emit(0)
|
||||||
|
_expect(
|
||||||
|
not settings_manager.current_settings.chat_dock_right
|
||||||
|
and not settings_manager.current_settings.chat_mobile_mode
|
||||||
|
and settings_manager.current_settings.paint_dock_right,
|
||||||
|
(
|
||||||
|
"Navigating presentation choices changed saved docking settings "
|
||||||
|
+ "before Apply."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
panel.close_panel(true)
|
||||||
|
panel.queue_free()
|
||||||
|
settings_manager.queue_free()
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
|
||||||
func _validate_mail_navigation() -> void:
|
func _validate_mail_navigation() -> void:
|
||||||
var page := MailPageType.new() as Control
|
var page := MailPageType.new() as Control
|
||||||
root.add_child(page)
|
root.add_child(page)
|
||||||
|
|
@ -1334,13 +1530,8 @@ func _assert_directionally_reachable(
|
||||||
var pending: Array[Control] = [start]
|
var pending: Array[Control] = [start]
|
||||||
while not pending.is_empty():
|
while not pending.is_empty():
|
||||||
var current: Control = pending.pop_front()
|
var current: Control = pending.pop_front()
|
||||||
for path: NodePath in [
|
for side: Side in [SIDE_LEFT, SIDE_RIGHT, SIDE_TOP, SIDE_BOTTOM]:
|
||||||
current.focus_neighbor_left,
|
var neighbor: Control = current.find_valid_focus_neighbor(side)
|
||||||
current.focus_neighbor_right,
|
|
||||||
current.focus_neighbor_top,
|
|
||||||
current.focus_neighbor_bottom,
|
|
||||||
]:
|
|
||||||
var neighbor := current.get_node_or_null(path) as Control
|
|
||||||
if (
|
if (
|
||||||
neighbor == null
|
neighbor == null
|
||||||
or not expected.has(neighbor.get_instance_id())
|
or not expected.has(neighbor.get_instance_id())
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,11 @@ func _validate_player_page_zone_contracts() -> void:
|
||||||
var net_source: String = FileAccess.get_file_as_string(
|
var net_source: String = FileAccess.get_file_as_string(
|
||||||
"res://ui/the_net_page.gd"
|
"res://ui/the_net_page.gd"
|
||||||
)
|
)
|
||||||
assert(net_source.contains("ROLE_RIGHT_STICK_Y"))
|
var game_ui_source: String = FileAccess.get_file_as_string(
|
||||||
|
"res://ui/game_ui.gd"
|
||||||
|
)
|
||||||
|
assert(not net_source.contains("ROLE_RIGHT_STICK_Y"))
|
||||||
|
assert(game_ui_source.contains("_update_controller_menu_scroll"))
|
||||||
|
|
||||||
|
|
||||||
func _validate_shop_navigation_contract() -> void:
|
func _validate_shop_navigation_contract() -> void:
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,15 @@ func _run() -> void:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert(dialog.gui_get_focus_owner() == select_button)
|
assert(dialog.gui_get_focus_owner() == select_button)
|
||||||
assert(FileDialogControllerNavigationType.activate_control(sort_button))
|
sort_button.grab_focus()
|
||||||
|
var accept_press := InputEventAction.new()
|
||||||
|
accept_press.action = &"ui_accept"
|
||||||
|
accept_press.pressed = true
|
||||||
|
Input.parse_input_event(accept_press)
|
||||||
|
var accept_release := InputEventAction.new()
|
||||||
|
accept_release.action = &"ui_accept"
|
||||||
|
accept_release.pressed = false
|
||||||
|
Input.parse_input_event(accept_release)
|
||||||
await process_frame
|
await process_frame
|
||||||
assert(sort_button.get_popup().visible)
|
assert(sort_button.get_popup().visible)
|
||||||
sort_button.get_popup().hide()
|
sort_button.get_popup().hide()
|
||||||
|
|
@ -283,13 +291,16 @@ func _validate_compact_dialog() -> void:
|
||||||
var down_event := InputEventJoypadButton.new()
|
var down_event := InputEventJoypadButton.new()
|
||||||
down_event.button_index = JOY_BUTTON_DPAD_DOWN
|
down_event.button_index = JOY_BUTTON_DPAD_DOWN
|
||||||
down_event.pressed = true
|
down_event.pressed = true
|
||||||
font_controller.call("_input", down_event)
|
var dialog_controller := font_controller.get(
|
||||||
|
"_file_dialog_controller"
|
||||||
|
) as FileDialogController
|
||||||
|
dialog_controller.call("_input", down_event)
|
||||||
assert(scope.gui_get_focus_owner() == select_button)
|
assert(scope.gui_get_focus_owner() == select_button)
|
||||||
path_edit.grab_focus()
|
path_edit.grab_focus()
|
||||||
var accept_event := InputEventJoypadButton.new()
|
var accept_event := InputEventJoypadButton.new()
|
||||||
accept_event.button_index = JOY_BUTTON_A
|
accept_event.button_index = JOY_BUTTON_A
|
||||||
accept_event.pressed = true
|
accept_event.pressed = true
|
||||||
font_controller.call("_input", accept_event)
|
dialog_controller.call("_input", accept_event)
|
||||||
assert(keyboard.is_open())
|
assert(keyboard.is_open())
|
||||||
keyboard.call("_close_keyboard", true)
|
keyboard.call("_close_keyboard", true)
|
||||||
dialog.queue_free()
|
dialog.queue_free()
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ func _run() -> void:
|
||||||
var accept := InputEventJoypadButton.new()
|
var accept := InputEventJoypadButton.new()
|
||||||
accept.button_index = JOY_BUTTON_A
|
accept.button_index = JOY_BUTTON_A
|
||||||
accept.pressed = true
|
accept.pressed = true
|
||||||
|
var accept_release := InputEventJoypadButton.new()
|
||||||
|
accept_release.button_index = JOY_BUTTON_A
|
||||||
|
accept_release.pressed = false
|
||||||
var cancel := InputEventJoypadButton.new()
|
var cancel := InputEventJoypadButton.new()
|
||||||
cancel.button_index = JOY_BUTTON_B
|
cancel.button_index = JOY_BUTTON_B
|
||||||
cancel.pressed = true
|
cancel.pressed = true
|
||||||
|
|
@ -72,7 +75,10 @@ func _run() -> void:
|
||||||
))
|
))
|
||||||
if section_index == FishingShop.ShopSection.BAIT:
|
if section_index == FishingShop.ShopSection.BAIT:
|
||||||
assert(root.gui_get_focus_owner() == dummy_stock)
|
assert(root.gui_get_focus_owner() == dummy_stock)
|
||||||
shop.call("_input", accept)
|
Input.parse_input_event(accept)
|
||||||
|
await process_frame
|
||||||
|
Input.parse_input_event(accept_release)
|
||||||
|
await process_frame
|
||||||
assert(activations[0] == 1)
|
assert(activations[0] == 1)
|
||||||
shop.call("_input", cancel)
|
shop.call("_input", cancel)
|
||||||
for _frame: int in 2:
|
for _frame: int in 2:
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ enum InitialFocus {
|
||||||
^"BubbleCluster/CancelButton/CancelLabel"
|
^"BubbleCluster/CancelButton/CancelLabel"
|
||||||
)
|
)
|
||||||
@export var maximum_layout_size: Vector2 = Vector2(720.0, 520.0)
|
@export var maximum_layout_size: Vector2 = Vector2(720.0, 520.0)
|
||||||
@export var compact_maximum_layout_size: Vector2 = Vector2(544.0, 400.0)
|
|
||||||
@export var compact_width_threshold: float = 680.0
|
|
||||||
@export var compact_height_threshold: float = 500.0
|
|
||||||
@export_range(0.0, 128.0, 1.0) var transition_safe_margin: float = 24.0
|
@export_range(0.0, 128.0, 1.0) var transition_safe_margin: float = 24.0
|
||||||
|
|
||||||
var _cluster: BubbleCluster
|
var _cluster: BubbleCluster
|
||||||
|
|
@ -158,15 +155,9 @@ func _process(delta: float) -> void:
|
||||||
func _update_layout() -> void:
|
func _update_layout() -> void:
|
||||||
if not is_node_ready() or _cluster == null:
|
if not is_node_ready() or _cluster == null:
|
||||||
return
|
return
|
||||||
var compact: bool = false
|
|
||||||
var layout_maximum: Vector2 = (
|
|
||||||
compact_maximum_layout_size
|
|
||||||
if compact
|
|
||||||
else maximum_layout_size
|
|
||||||
)
|
|
||||||
var field_size := Vector2(
|
var field_size := Vector2(
|
||||||
minf(size.x, layout_maximum.x),
|
minf(size.x, maximum_layout_size.x),
|
||||||
minf(size.y, layout_maximum.y)
|
minf(size.y, maximum_layout_size.y)
|
||||||
)
|
)
|
||||||
field_size.x = maxf(1.0, field_size.x)
|
field_size.x = maxf(1.0, field_size.x)
|
||||||
field_size.y = maxf(1.0, field_size.y)
|
field_size.y = maxf(1.0, field_size.y)
|
||||||
|
|
@ -174,7 +165,7 @@ func _update_layout() -> void:
|
||||||
if not _is_transitioning:
|
if not _is_transitioning:
|
||||||
_cluster.position = _resting_cluster_position
|
_cluster.position = _resting_cluster_position
|
||||||
_cluster.size = field_size
|
_cluster.size = field_size
|
||||||
_cluster.apply_layout(field_size, compact)
|
_cluster.apply_layout(field_size, false)
|
||||||
|
|
||||||
|
|
||||||
func _configure_focus() -> void:
|
func _configure_focus() -> void:
|
||||||
|
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
||||||
class_name ControllerFocusRecovery
|
|
||||||
extends Node
|
|
||||||
|
|
||||||
const CONTROLLER_AXIS_THRESHOLD: float = 0.35
|
|
||||||
const SEMANTIC_MATCH_BONUS: float = 1000000.0
|
|
||||||
const KEYBOARD_NAVIGATION_ACTIONS: Array[StringName] = [
|
|
||||||
&"ui_accept",
|
|
||||||
&"ui_cancel",
|
|
||||||
&"ui_up",
|
|
||||||
&"ui_down",
|
|
||||||
&"ui_left",
|
|
||||||
&"ui_right",
|
|
||||||
&"ui_focus_next",
|
|
||||||
&"ui_focus_prev",
|
|
||||||
&"ui_page_up",
|
|
||||||
&"ui_page_down",
|
|
||||||
&"ui_home",
|
|
||||||
&"ui_end",
|
|
||||||
]
|
|
||||||
|
|
||||||
var _controller_active: bool = false
|
|
||||||
var _focus_navigation_active: bool = false
|
|
||||||
var _last_focus_center: Vector2 = Vector2.ZERO
|
|
||||||
var _last_focus_key: String = ""
|
|
||||||
var _scope_chain: Array[WeakRef] = []
|
|
||||||
var _pending_focus: WeakRef
|
|
||||||
var _pointer_button_down: bool = false
|
|
||||||
var _recovery_generation: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
set_process_input(true)
|
|
||||||
set_process(true)
|
|
||||||
get_viewport().gui_focus_changed.connect(_on_gui_focus_changed)
|
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
var viewport := get_viewport()
|
|
||||||
if viewport.gui_focus_changed.is_connected(_on_gui_focus_changed):
|
|
||||||
viewport.gui_focus_changed.disconnect(_on_gui_focus_changed)
|
|
||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
|
||||||
if event is InputEventJoypadButton:
|
|
||||||
var button_event := event as InputEventJoypadButton
|
|
||||||
if button_event.pressed:
|
|
||||||
_pointer_button_down = false
|
|
||||||
_controller_active = true
|
|
||||||
_focus_navigation_active = true
|
|
||||||
if button_event.button_index == JOY_BUTTON_LEFT_SHOULDER:
|
|
||||||
_recovery_generation += 1
|
|
||||||
_scope_chain.clear()
|
|
||||||
_pending_focus = null
|
|
||||||
return
|
|
||||||
_request_pending_focus()
|
|
||||||
return
|
|
||||||
if event is InputEventJoypadMotion:
|
|
||||||
if absf((event as InputEventJoypadMotion).axis_value) >= (
|
|
||||||
CONTROLLER_AXIS_THRESHOLD
|
|
||||||
):
|
|
||||||
_pointer_button_down = false
|
|
||||||
_controller_active = true
|
|
||||||
_focus_navigation_active = true
|
|
||||||
_request_pending_focus()
|
|
||||||
return
|
|
||||||
if event is InputEventMouseMotion:
|
|
||||||
_pointer_button_down = (
|
|
||||||
(event as InputEventMouseMotion).button_mask != 0
|
|
||||||
)
|
|
||||||
_leave_focus_navigation(not _pointer_button_down)
|
|
||||||
return
|
|
||||||
if event is InputEventMouseButton:
|
|
||||||
var mouse_button := event as InputEventMouseButton
|
|
||||||
if mouse_button.button_index in [
|
|
||||||
MOUSE_BUTTON_LEFT,
|
|
||||||
MOUSE_BUTTON_RIGHT,
|
|
||||||
MOUSE_BUTTON_MIDDLE,
|
|
||||||
]:
|
|
||||||
_pointer_button_down = mouse_button.pressed
|
|
||||||
_leave_focus_navigation(false)
|
|
||||||
if not mouse_button.pressed:
|
|
||||||
_release_current_pointer_focus.call_deferred()
|
|
||||||
return
|
|
||||||
if event is InputEventKey and (event as InputEventKey).pressed:
|
|
||||||
_pointer_button_down = false
|
|
||||||
_controller_active = false
|
|
||||||
if _is_keyboard_navigation_event(event):
|
|
||||||
_focus_navigation_active = true
|
|
||||||
_request_pending_focus()
|
|
||||||
else:
|
|
||||||
_leave_focus_navigation()
|
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
if (
|
|
||||||
_controller_active
|
|
||||||
and not _scope_chain.is_empty()
|
|
||||||
and get_viewport().gui_get_focus_owner() == null
|
|
||||||
):
|
|
||||||
_recover_focus(_recovery_generation)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_gui_focus_changed(control: Control) -> void:
|
|
||||||
_recovery_generation += 1
|
|
||||||
if control != null:
|
|
||||||
if _focus_navigation_active:
|
|
||||||
_pending_focus = null
|
|
||||||
if _controller_active and control is BaseButton:
|
|
||||||
_remember_focus(control)
|
|
||||||
return
|
|
||||||
if _keeps_pointer_focus(control):
|
|
||||||
return
|
|
||||||
if _is_focusable(control):
|
|
||||||
_pending_focus = weakref(control)
|
|
||||||
_release_focus_if_inactive.call_deferred(control)
|
|
||||||
return
|
|
||||||
if not _controller_active or _scope_chain.is_empty():
|
|
||||||
return
|
|
||||||
var generation: int = _recovery_generation
|
|
||||||
_recover_focus.call_deferred(generation)
|
|
||||||
|
|
||||||
|
|
||||||
func _remember_focus(control: Control) -> void:
|
|
||||||
_last_focus_center = control.get_global_rect().get_center()
|
|
||||||
_last_focus_key = _semantic_key(control)
|
|
||||||
_scope_chain.clear()
|
|
||||||
var recovery_root: Node = get_parent()
|
|
||||||
var ancestor: Node = control.get_parent()
|
|
||||||
while ancestor != null:
|
|
||||||
if ancestor is Control:
|
|
||||||
_scope_chain.append(weakref(ancestor))
|
|
||||||
if ancestor == recovery_root:
|
|
||||||
break
|
|
||||||
ancestor = ancestor.get_parent()
|
|
||||||
|
|
||||||
|
|
||||||
func _recover_focus(generation: int) -> void:
|
|
||||||
if generation != _recovery_generation:
|
|
||||||
return
|
|
||||||
if not _controller_active or get_viewport().gui_get_focus_owner() != null:
|
|
||||||
return
|
|
||||||
for scope_reference: WeakRef in _scope_chain:
|
|
||||||
var scope := scope_reference.get_ref() as Control
|
|
||||||
if (
|
|
||||||
scope == null
|
|
||||||
or not is_instance_valid(scope)
|
|
||||||
or not scope.is_inside_tree()
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
if not scope.is_visible_in_tree():
|
|
||||||
_scope_chain.clear()
|
|
||||||
return
|
|
||||||
var replacement := _best_replacement_in(scope)
|
|
||||||
if replacement != null:
|
|
||||||
replacement.grab_focus()
|
|
||||||
else:
|
|
||||||
_scope_chain.clear()
|
|
||||||
return
|
|
||||||
_scope_chain.clear()
|
|
||||||
|
|
||||||
|
|
||||||
func _best_replacement_in(scope: Control) -> Control:
|
|
||||||
var best: Control = null
|
|
||||||
var best_score: float = INF
|
|
||||||
for node: Node in scope.find_children("*", "Control", true, false):
|
|
||||||
var candidate := node as Control
|
|
||||||
if not _is_focusable(candidate):
|
|
||||||
continue
|
|
||||||
var distance: float = candidate.get_global_rect().get_center().distance_squared_to(
|
|
||||||
_last_focus_center
|
|
||||||
)
|
|
||||||
if not _last_focus_key.is_empty() and _semantic_key(candidate) == _last_focus_key:
|
|
||||||
distance -= SEMANTIC_MATCH_BONUS
|
|
||||||
if distance < best_score:
|
|
||||||
best = candidate
|
|
||||||
best_score = distance
|
|
||||||
return best
|
|
||||||
|
|
||||||
|
|
||||||
func _is_focusable(control: Control) -> bool:
|
|
||||||
if (
|
|
||||||
control == null
|
|
||||||
or not control.is_inside_tree()
|
|
||||||
or not control.is_visible_in_tree()
|
|
||||||
or control.focus_mode not in [Control.FOCUS_CLICK, Control.FOCUS_ALL]
|
|
||||||
):
|
|
||||||
return false
|
|
||||||
var button := control as BaseButton
|
|
||||||
return button == null or not button.disabled
|
|
||||||
|
|
||||||
|
|
||||||
func _semantic_key(control: Control) -> String:
|
|
||||||
if control.has_meta(&"controller_focus_key"):
|
|
||||||
return str(control.get_meta(&"controller_focus_key"))
|
|
||||||
var button := control as Button
|
|
||||||
var label: String = button.text if button != null else ""
|
|
||||||
var tooltip: String = control.tooltip_text
|
|
||||||
tooltip = tooltip.trim_suffix(" (show variants)")
|
|
||||||
tooltip = tooltip.trim_suffix(" (hide variants)")
|
|
||||||
return "%s|%s|%s" % [control.get_class(), label, tooltip]
|
|
||||||
|
|
||||||
|
|
||||||
func _leave_focus_navigation(release_focus: bool = true) -> void:
|
|
||||||
_controller_active = false
|
|
||||||
_focus_navigation_active = false
|
|
||||||
_recovery_generation += 1
|
|
||||||
_scope_chain.clear()
|
|
||||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
|
||||||
if focus_owner == null or _keeps_pointer_focus(focus_owner):
|
|
||||||
return
|
|
||||||
if _is_focusable(focus_owner):
|
|
||||||
_pending_focus = weakref(focus_owner)
|
|
||||||
if release_focus:
|
|
||||||
_release_focus_if_inactive.call_deferred(focus_owner)
|
|
||||||
|
|
||||||
|
|
||||||
func _release_focus_if_inactive(control: Control) -> void:
|
|
||||||
if (
|
|
||||||
_focus_navigation_active
|
|
||||||
or _pointer_button_down
|
|
||||||
or control == null
|
|
||||||
or not is_instance_valid(control)
|
|
||||||
or get_viewport().gui_get_focus_owner() != control
|
|
||||||
or _keeps_pointer_focus(control)
|
|
||||||
):
|
|
||||||
return
|
|
||||||
get_viewport().gui_release_focus()
|
|
||||||
|
|
||||||
|
|
||||||
func _release_current_pointer_focus() -> void:
|
|
||||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
|
||||||
if focus_owner != null:
|
|
||||||
_release_focus_if_inactive(focus_owner)
|
|
||||||
|
|
||||||
|
|
||||||
func _request_pending_focus() -> void:
|
|
||||||
if (
|
|
||||||
not _focus_navigation_active
|
|
||||||
or _pending_focus == null
|
|
||||||
or get_viewport().gui_get_focus_owner() != null
|
|
||||||
):
|
|
||||||
return
|
|
||||||
_restore_pending_focus.call_deferred(_recovery_generation)
|
|
||||||
|
|
||||||
|
|
||||||
func _restore_pending_focus(generation: int) -> void:
|
|
||||||
if (
|
|
||||||
generation != _recovery_generation
|
|
||||||
or not _focus_navigation_active
|
|
||||||
or _pending_focus == null
|
|
||||||
or get_viewport().gui_get_focus_owner() != null
|
|
||||||
):
|
|
||||||
return
|
|
||||||
var target := _pending_focus.get_ref() as Control
|
|
||||||
if not _is_focusable(target):
|
|
||||||
_pending_focus = null
|
|
||||||
return
|
|
||||||
_pending_focus = null
|
|
||||||
target.grab_focus()
|
|
||||||
|
|
||||||
|
|
||||||
func _is_keyboard_navigation_event(event: InputEvent) -> bool:
|
|
||||||
if event.is_action_pressed(&"open_backpack"):
|
|
||||||
return false
|
|
||||||
if (
|
|
||||||
event.is_action_pressed(&"ui_cancel")
|
|
||||||
and get_viewport().gui_get_focus_owner() == null
|
|
||||||
):
|
|
||||||
return false
|
|
||||||
for action: StringName in KEYBOARD_NAVIGATION_ACTIONS:
|
|
||||||
if event.is_action_pressed(action):
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
func _keeps_pointer_focus(control: Control) -> bool:
|
|
||||||
return control is LineEdit or control is TextEdit
|
|
||||||
|
|
@ -1,153 +1,28 @@
|
||||||
class_name EmoteRadialMenu
|
class_name EmoteRadialMenu
|
||||||
extends Control
|
extends RadialActionMenu
|
||||||
|
|
||||||
signal emote_selected(emote_id: StringName)
|
signal emote_selected(emote_id: StringName)
|
||||||
|
|
||||||
const BubbleButtonScene: PackedScene = preload(
|
|
||||||
"res://ui/components/bubble_menu/bubble_button.tscn"
|
|
||||||
)
|
|
||||||
const BubbleProfile: BubbleMenuProfile = preload(
|
|
||||||
"res://ui/components/bubble_menu/bubble_menu_profile.tres"
|
|
||||||
)
|
|
||||||
const SECTOR_COUNT: int = 8
|
const SECTOR_COUNT: int = 8
|
||||||
const SIT_SECTOR: int = 0
|
const SIT_SECTOR: int = 0
|
||||||
const RING_RADIUS: float = 172.0
|
|
||||||
const BUBBLE_SIZE: Vector2 = Vector2(92.0, 88.0)
|
|
||||||
const CONTROLLER_SELECTION_DEADZONE: float = 0.35
|
|
||||||
const ControllerMappingManagerType = preload(
|
|
||||||
"res://settings/controller_mapping_manager.gd"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _buttons: Array[BubbleButton] = []
|
|
||||||
var _is_open: bool = false
|
|
||||||
var _selected_sector: int = SIT_SECTOR
|
|
||||||
var _controller_selection_mode: bool = false
|
|
||||||
var _controller_mapping_manager: ControllerMappingManagerType
|
|
||||||
|
|
||||||
|
|
||||||
func setup_controller_mapping(
|
func _input_action_name() -> StringName:
|
||||||
mapping_manager: ControllerMappingManagerType,
|
return &"open_emotes"
|
||||||
) -> void:
|
|
||||||
_controller_mapping_manager = mapping_manager
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _sector_count() -> int:
|
||||||
visible = false
|
return SECTOR_COUNT
|
||||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
||||||
for sector: int in SECTOR_COUNT:
|
|
||||||
var bubble: BubbleButton = BubbleButtonScene.instantiate() as BubbleButton
|
|
||||||
bubble.profile = BubbleProfile
|
|
||||||
bubble.focus_mode = Control.FOCUS_NONE
|
|
||||||
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
||||||
bubble.text = "Sit" if sector == SIT_SECTOR else ""
|
|
||||||
add_child(bubble)
|
|
||||||
_buttons.append(bubble)
|
|
||||||
_apply_selection_styles()
|
|
||||||
|
|
||||||
|
|
||||||
func handle_input(event: InputEvent, can_open: bool) -> bool:
|
func _initial_sector() -> int:
|
||||||
if not event.is_action("open_emotes"):
|
return SIT_SECTOR
|
||||||
return false
|
|
||||||
var key_event: InputEventKey = event as InputEventKey
|
|
||||||
if key_event != null and key_event.echo:
|
func _label_for_sector(sector: int) -> String:
|
||||||
return false
|
return "Sit" if sector == SIT_SECTOR else ""
|
||||||
if event.is_pressed():
|
|
||||||
if _is_open or not can_open:
|
|
||||||
return _is_open
|
func _emit_selected_sector(sector: int) -> void:
|
||||||
open_menu(event is InputEventJoypadButton)
|
if sector == SIT_SECTOR:
|
||||||
return true
|
|
||||||
if not _is_open:
|
|
||||||
return false
|
|
||||||
var selected: int = _selected_sector
|
|
||||||
close_menu()
|
|
||||||
if selected == SIT_SECTOR:
|
|
||||||
emote_selected.emit(&"sit")
|
emote_selected.emit(&"sit")
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func open_menu(controller_selection: bool = false) -> void:
|
|
||||||
_is_open = true
|
|
||||||
_selected_sector = SIT_SECTOR
|
|
||||||
_controller_selection_mode = controller_selection
|
|
||||||
visible = true
|
|
||||||
_layout_bubbles()
|
|
||||||
_apply_selection_styles()
|
|
||||||
|
|
||||||
|
|
||||||
func close_menu() -> void:
|
|
||||||
_is_open = false
|
|
||||||
visible = false
|
|
||||||
|
|
||||||
|
|
||||||
func is_open() -> bool:
|
|
||||||
return _is_open
|
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
if not _is_open:
|
|
||||||
return
|
|
||||||
_layout_bubbles()
|
|
||||||
_update_selection()
|
|
||||||
|
|
||||||
|
|
||||||
func _layout_bubbles() -> void:
|
|
||||||
var center: Vector2 = size * 0.5
|
|
||||||
for sector: int in SECTOR_COUNT:
|
|
||||||
var angle: float = -PI * 0.5 + TAU * float(sector) / float(SECTOR_COUNT)
|
|
||||||
var bubble_center: Vector2 = center + Vector2.from_angle(angle) * RING_RADIUS
|
|
||||||
var bubble: BubbleButton = _buttons[sector]
|
|
||||||
bubble.position = bubble_center - BUBBLE_SIZE * 0.5
|
|
||||||
bubble.size = BUBBLE_SIZE
|
|
||||||
bubble.pivot_offset = BUBBLE_SIZE * 0.5
|
|
||||||
|
|
||||||
|
|
||||||
func _update_selection() -> void:
|
|
||||||
var stick: Vector2 = _get_selection_stick()
|
|
||||||
if stick.length() >= CONTROLLER_SELECTION_DEADZONE:
|
|
||||||
_select_sector_from_offset(stick)
|
|
||||||
return
|
|
||||||
if _controller_selection_mode:
|
|
||||||
return
|
|
||||||
_update_mouse_selection()
|
|
||||||
|
|
||||||
|
|
||||||
func _get_selection_stick() -> Vector2:
|
|
||||||
if _controller_mapping_manager != null:
|
|
||||||
return Vector2(
|
|
||||||
_controller_mapping_manager.get_role_axis(
|
|
||||||
ControllerMappingManagerType.ROLE_RIGHT_STICK_X
|
|
||||||
),
|
|
||||||
_controller_mapping_manager.get_role_axis(
|
|
||||||
ControllerMappingManagerType.ROLE_RIGHT_STICK_Y
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return Vector2(
|
|
||||||
Input.get_joy_axis(0, JOY_AXIS_RIGHT_X),
|
|
||||||
Input.get_joy_axis(0, JOY_AXIS_RIGHT_Y),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _update_mouse_selection() -> void:
|
|
||||||
var offset: Vector2 = get_local_mouse_position() - size * 0.5
|
|
||||||
if offset.length() < 24.0:
|
|
||||||
return
|
|
||||||
_select_sector_from_offset(offset)
|
|
||||||
|
|
||||||
|
|
||||||
func _select_sector_from_offset(offset: Vector2) -> void:
|
|
||||||
var angle: float = fposmod(offset.angle() + PI * 0.5 + PI / 8.0, TAU)
|
|
||||||
var sector: int = int(floor(angle / (TAU / float(SECTOR_COUNT))))
|
|
||||||
if sector == _selected_sector:
|
|
||||||
return
|
|
||||||
_selected_sector = sector
|
|
||||||
_apply_selection_styles()
|
|
||||||
|
|
||||||
|
|
||||||
func _apply_selection_styles() -> void:
|
|
||||||
for sector: int in _buttons.size():
|
|
||||||
var bubble: BubbleButton = _buttons[sector]
|
|
||||||
bubble.apply_profile()
|
|
||||||
if sector == _selected_sector:
|
|
||||||
bubble.add_theme_stylebox_override(
|
|
||||||
"normal", BubbleProfile.make_hover_style()
|
|
||||||
)
|
|
||||||
|
|
|
||||||
128
ui/file_dialog_controller.gd
Normal file
128
ui/file_dialog_controller.gd
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
class_name FileDialogController
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
const FileDialogControllerNavigationType = preload(
|
||||||
|
"res://ui/file_dialog_controller_navigation.gd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _text_entry_request: Callable
|
||||||
|
var _text_entry_is_open: Callable
|
||||||
|
var _tracked_dialogs: Dictionary[int, WeakRef] = {}
|
||||||
|
|
||||||
|
|
||||||
|
func setup_text_entry(
|
||||||
|
request: Callable,
|
||||||
|
is_open: Callable = Callable(),
|
||||||
|
) -> void:
|
||||||
|
_text_entry_request = request
|
||||||
|
_text_entry_is_open = is_open
|
||||||
|
|
||||||
|
|
||||||
|
func track_dialog(dialog: FileDialog) -> void:
|
||||||
|
if dialog != null:
|
||||||
|
_tracked_dialogs[dialog.get_instance_id()] = weakref(dialog)
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if _text_entry_is_active():
|
||||||
|
return
|
||||||
|
for instance_id: int in _tracked_dialogs.keys():
|
||||||
|
var reference: WeakRef = _tracked_dialogs[instance_id]
|
||||||
|
var dialog := reference.get_ref() as FileDialog
|
||||||
|
if dialog == null or not is_instance_valid(dialog):
|
||||||
|
_tracked_dialogs.erase(instance_id)
|
||||||
|
continue
|
||||||
|
if not dialog.visible:
|
||||||
|
continue
|
||||||
|
FileDialogControllerNavigationType.configure(dialog)
|
||||||
|
_connect_text_controls(dialog)
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if _text_entry_is_active():
|
||||||
|
return
|
||||||
|
if not (event is InputEventJoypadButton or event is InputEventJoypadMotion):
|
||||||
|
return
|
||||||
|
var scope: Window = _active_scope()
|
||||||
|
if scope == null:
|
||||||
|
return
|
||||||
|
var focused: Control = scope.gui_get_focus_owner()
|
||||||
|
if focused == null:
|
||||||
|
return
|
||||||
|
var direction: Vector2 = _controller_direction(event)
|
||||||
|
var item_list := focused as ItemList
|
||||||
|
if (
|
||||||
|
item_list != null
|
||||||
|
and direction != Vector2.ZERO
|
||||||
|
and FileDialogControllerNavigationType.move_from_item_list(
|
||||||
|
item_list, direction
|
||||||
|
)
|
||||||
|
):
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
|
var button_event := event as InputEventJoypadButton
|
||||||
|
if (
|
||||||
|
button_event != null
|
||||||
|
and button_event.pressed
|
||||||
|
and event.is_action_pressed(&"ui_accept")
|
||||||
|
and (focused is LineEdit or focused is TextEdit)
|
||||||
|
and _text_entry_request.is_valid()
|
||||||
|
and bool(_text_entry_request.call(focused))
|
||||||
|
):
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
func _active_scope() -> Window:
|
||||||
|
for reference_value: WeakRef in _tracked_dialogs.values():
|
||||||
|
var dialog := reference_value.get_ref() as FileDialog
|
||||||
|
if dialog == null or not is_instance_valid(dialog) or not dialog.visible:
|
||||||
|
continue
|
||||||
|
return FileDialogControllerNavigationType.active_scope(dialog)
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _controller_direction(event: InputEvent) -> Vector2:
|
||||||
|
if event.is_action_pressed(&"ui_up"):
|
||||||
|
return Vector2.UP
|
||||||
|
if event.is_action_pressed(&"ui_down"):
|
||||||
|
return Vector2.DOWN
|
||||||
|
if event.is_action_pressed(&"ui_left"):
|
||||||
|
return Vector2.LEFT
|
||||||
|
if event.is_action_pressed(&"ui_right"):
|
||||||
|
return Vector2.RIGHT
|
||||||
|
return Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
|
func _connect_text_controls(dialog: FileDialog) -> void:
|
||||||
|
var scope: Window = FileDialogControllerNavigationType.active_scope(dialog)
|
||||||
|
for control: Control in (
|
||||||
|
FileDialogControllerNavigationType.interactive_controls(scope)
|
||||||
|
):
|
||||||
|
if not (control is LineEdit or control is TextEdit):
|
||||||
|
continue
|
||||||
|
var callback := _on_text_gui_input.bind(control)
|
||||||
|
if not control.gui_input.is_connected(callback):
|
||||||
|
control.gui_input.connect(callback)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_text_gui_input(event: InputEvent, control: Control) -> void:
|
||||||
|
var button_event := event as InputEventJoypadButton
|
||||||
|
if (
|
||||||
|
button_event == null
|
||||||
|
or not button_event.pressed
|
||||||
|
or (
|
||||||
|
button_event.button_index != JOY_BUTTON_A
|
||||||
|
and not event.is_action_pressed(&"ui_accept")
|
||||||
|
)
|
||||||
|
or not _text_entry_request.is_valid()
|
||||||
|
):
|
||||||
|
return
|
||||||
|
if bool(_text_entry_request.call(control)):
|
||||||
|
control.accept_event()
|
||||||
|
|
||||||
|
|
||||||
|
func _text_entry_is_active() -> bool:
|
||||||
|
return (
|
||||||
|
_text_entry_is_open.is_valid()
|
||||||
|
and bool(_text_entry_is_open.call())
|
||||||
|
)
|
||||||
1
ui/file_dialog_controller.gd.uid
Normal file
1
ui/file_dialog_controller.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dtqkbg7flp1pl
|
||||||
|
|
@ -72,20 +72,6 @@ static func interactive_controls(scope: Window) -> Array[Control]:
|
||||||
return controls
|
return controls
|
||||||
|
|
||||||
|
|
||||||
static func activate_control(control: Control) -> bool:
|
|
||||||
if control == null or not control.is_visible_in_tree():
|
|
||||||
return false
|
|
||||||
var menu_button := control as MenuButton
|
|
||||||
if menu_button != null and not menu_button.disabled:
|
|
||||||
menu_button.show_popup()
|
|
||||||
return true
|
|
||||||
var button := control as BaseButton
|
|
||||||
if button != null and not button.disabled:
|
|
||||||
button.pressed.emit()
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
static func move_from_item_list(
|
static func move_from_item_list(
|
||||||
item_list: ItemList,
|
item_list: ItemList,
|
||||||
direction: Vector2,
|
direction: Vector2,
|
||||||
|
|
|
||||||
|
|
@ -236,13 +236,15 @@ func _input(event: InputEvent) -> void:
|
||||||
close_shop()
|
close_shop()
|
||||||
return
|
return
|
||||||
if uses_accept:
|
if uses_accept:
|
||||||
|
if _controller_zone != ControllerZone.TABS:
|
||||||
|
# Content buttons use Godot's native ui_accept activation. Keeping a
|
||||||
|
# second manual pressed.emit() path here allowed one controller press
|
||||||
|
# to be interpreted by two different owners.
|
||||||
|
return
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
if not button_event.pressed or _transaction_in_progress:
|
if not button_event.pressed or _transaction_in_progress:
|
||||||
return
|
return
|
||||||
if _controller_zone == ControllerZone.TABS:
|
_enter_shop_content_zone()
|
||||||
_enter_shop_content_zone()
|
|
||||||
else:
|
|
||||||
_activate_focused_shop_control()
|
|
||||||
return
|
return
|
||||||
var uses_left_bumper: bool = (
|
var uses_left_bumper: bool = (
|
||||||
_controller_mapping_manager.event_uses_role(
|
_controller_mapping_manager.event_uses_role(
|
||||||
|
|
@ -295,19 +297,6 @@ func _enter_shop_content_zone() -> void:
|
||||||
_select_shop_section(section_index, true)
|
_select_shop_section(section_index, true)
|
||||||
|
|
||||||
|
|
||||||
func _activate_focused_shop_control() -> bool:
|
|
||||||
var focused := get_viewport().gui_get_focus_owner() as BaseButton
|
|
||||||
if (
|
|
||||||
focused == null
|
|
||||||
or focused.disabled
|
|
||||||
or _is_shop_tab(focused)
|
|
||||||
or not focused.is_visible_in_tree()
|
|
||||||
):
|
|
||||||
return false
|
|
||||||
focused.pressed.emit()
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func _focused_shop_tab_index() -> int:
|
func _focused_shop_tab_index() -> int:
|
||||||
var focused: Control = get_viewport().gui_get_focus_owner()
|
var focused: Control = get_viewport().gui_get_focus_owner()
|
||||||
for tab_index: int in _shop_tabs.size():
|
for tab_index: int in _shop_tabs.size():
|
||||||
|
|
@ -430,11 +419,34 @@ func _configure_controller_focus() -> void:
|
||||||
if not visible or _cooler_page_active:
|
if not visible or _cooler_page_active:
|
||||||
return
|
return
|
||||||
_apply_shop_controller_zone_focus_modes()
|
_apply_shop_controller_zone_focus_modes()
|
||||||
var candidates: Array[Control] = []
|
var candidates: Array[Control] = _active_shop_controller_controls()
|
||||||
_collect_controller_focusables(self, candidates)
|
|
||||||
ControllerFocusNavigationType.configure_spatial_neighbors(candidates)
|
ControllerFocusNavigationType.configure_spatial_neighbors(candidates)
|
||||||
|
|
||||||
|
|
||||||
|
func _active_shop_controller_controls() -> Array[Control]:
|
||||||
|
var controls: Array[Control] = []
|
||||||
|
if _controller_zone == ControllerZone.TABS:
|
||||||
|
for tab: OrganizerTab in _shop_tabs:
|
||||||
|
if ControllerFocusNavigationType.is_focusable(tab):
|
||||||
|
controls.append(tab)
|
||||||
|
return controls
|
||||||
|
var active_root: Control = (
|
||||||
|
_upgrades_content
|
||||||
|
if _shop_section == ShopSection.UPGRADES
|
||||||
|
else _supplies_content
|
||||||
|
)
|
||||||
|
for node: Node in active_root.find_children(
|
||||||
|
"*", "BaseButton", true, false
|
||||||
|
):
|
||||||
|
var button := node as BaseButton
|
||||||
|
if ControllerFocusNavigationType.is_focusable(button):
|
||||||
|
controls.append(button)
|
||||||
|
var close_button := %CloseButton as Button
|
||||||
|
if ControllerFocusNavigationType.is_focusable(close_button):
|
||||||
|
controls.append(close_button)
|
||||||
|
return controls
|
||||||
|
|
||||||
|
|
||||||
func _apply_shop_controller_zone_focus_modes() -> void:
|
func _apply_shop_controller_zone_focus_modes() -> void:
|
||||||
var tabs_active: bool = _controller_zone == ControllerZone.TABS
|
var tabs_active: bool = _controller_zone == ControllerZone.TABS
|
||||||
for tab: OrganizerTab in _shop_tabs:
|
for tab: OrganizerTab in _shop_tabs:
|
||||||
|
|
@ -459,22 +471,6 @@ func _set_descendant_button_focus_mode(
|
||||||
button.focus_mode = focus_mode
|
button.focus_mode = focus_mode
|
||||||
|
|
||||||
|
|
||||||
func _collect_controller_focusables(
|
|
||||||
root: Node,
|
|
||||||
output: Array[Control],
|
|
||||||
) -> void:
|
|
||||||
for child: Node in root.get_children():
|
|
||||||
var control := child as Control
|
|
||||||
if control != null and not control.is_visible_in_tree():
|
|
||||||
continue
|
|
||||||
if (
|
|
||||||
ControllerFocusNavigationType.is_focusable(control)
|
|
||||||
and not control is ScrollBar
|
|
||||||
):
|
|
||||||
output.append(control)
|
|
||||||
_collect_controller_focusables(child, output)
|
|
||||||
|
|
||||||
|
|
||||||
func _update_shop_tab_selection() -> void:
|
func _update_shop_tab_selection() -> void:
|
||||||
for tab_index: int in range(_shop_tabs.size()):
|
for tab_index: int in range(_shop_tabs.size()):
|
||||||
_shop_tabs[tab_index].set_selected(
|
_shop_tabs[tab_index].set_selected(
|
||||||
|
|
|
||||||
|
|
@ -1268,6 +1268,10 @@ func _update_controller_menu_scroll(delta: float) -> void:
|
||||||
_controller_mapping_manager == null
|
_controller_mapping_manager == null
|
||||||
or _virtual_mouse_active
|
or _virtual_mouse_active
|
||||||
or is_input_mapping_capturing()
|
or is_input_mapping_capturing()
|
||||||
|
or (
|
||||||
|
_player_menu.visible
|
||||||
|
and not _player_menu.allows_global_controller_scroll()
|
||||||
|
)
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
var stick_y: float = _controller_menu_scroll_axis()
|
var stick_y: float = _controller_menu_scroll_axis()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
class_name InterfaceFontController
|
class_name InterfaceFontController
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const FileDialogControllerNavigationType = preload(
|
const FileDialogControllerType = preload(
|
||||||
"res://ui/file_dialog_controller_navigation.gd"
|
"res://ui/file_dialog_controller.gd"
|
||||||
)
|
)
|
||||||
const STANDARD_FONT: Font = preload("res://ui/fonts/Tuffy_Bold.otf")
|
const STANDARD_FONT: Font = preload("res://ui/fonts/Tuffy_Bold.otf")
|
||||||
const COMPACT_FILE_DIALOG_LIMIT := Vector2i(800, 600)
|
const COMPACT_FILE_DIALOG_LIMIT := Vector2i(800, 600)
|
||||||
|
|
@ -14,13 +14,19 @@ var _controller_text_entry_is_open: Callable
|
||||||
var _game_theme: Theme = preload("res://ui/game_theme.tres")
|
var _game_theme: Theme = preload("res://ui/game_theme.tres")
|
||||||
var _utility_theme: Theme
|
var _utility_theme: Theme
|
||||||
var _compact_file_dialog_theme: Theme
|
var _compact_file_dialog_theme: Theme
|
||||||
var _tracked_file_dialogs: Dictionary[int, WeakRef] = {}
|
var _file_dialog_controller: FileDialogControllerType
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
enforce_standard_font()
|
enforce_standard_font()
|
||||||
_utility_theme = _game_theme.duplicate(true)
|
_utility_theme = _game_theme.duplicate(true)
|
||||||
_utility_theme.default_font = STANDARD_FONT
|
_utility_theme.default_font = STANDARD_FONT
|
||||||
|
_file_dialog_controller = FileDialogControllerType.new()
|
||||||
|
add_child(_file_dialog_controller)
|
||||||
|
_file_dialog_controller.setup_text_entry(
|
||||||
|
_controller_text_entry_request,
|
||||||
|
_controller_text_entry_is_open,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func enforce_standard_font() -> void:
|
func enforce_standard_font() -> void:
|
||||||
|
|
@ -35,6 +41,8 @@ func set_controller_text_entry_request(
|
||||||
) -> void:
|
) -> void:
|
||||||
_controller_text_entry_request = request
|
_controller_text_entry_request = request
|
||||||
_controller_text_entry_is_open = is_open
|
_controller_text_entry_is_open = is_open
|
||||||
|
if _file_dialog_controller != null:
|
||||||
|
_file_dialog_controller.setup_text_entry(request, is_open)
|
||||||
|
|
||||||
|
|
||||||
func set_readable_font_enabled(_enabled: bool) -> void:
|
func set_readable_font_enabled(_enabled: bool) -> void:
|
||||||
|
|
@ -61,7 +69,7 @@ func apply_utility_theme(themed_node: Node) -> void:
|
||||||
func popup_file_dialog(dialog: FileDialog) -> void:
|
func popup_file_dialog(dialog: FileDialog) -> void:
|
||||||
if dialog == null or not dialog.is_inside_tree():
|
if dialog == null or not dialog.is_inside_tree():
|
||||||
return
|
return
|
||||||
_tracked_file_dialogs[dialog.get_instance_id()] = weakref(dialog)
|
_file_dialog_controller.track_dialog(dialog)
|
||||||
var host_window: Window = dialog.get_parent().get_window()
|
var host_window: Window = dialog.get_parent().get_window()
|
||||||
var host_size: Vector2i = host_window.size
|
var host_size: Vector2i = host_window.size
|
||||||
var compact: bool = (
|
var compact: bool = (
|
||||||
|
|
@ -98,113 +106,6 @@ func popup_file_dialog(dialog: FileDialog) -> void:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
if (
|
|
||||||
_controller_text_entry_is_open.is_valid()
|
|
||||||
and bool(_controller_text_entry_is_open.call())
|
|
||||||
):
|
|
||||||
return
|
|
||||||
for instance_id: int in _tracked_file_dialogs.keys():
|
|
||||||
var reference: WeakRef = _tracked_file_dialogs[instance_id]
|
|
||||||
var dialog := reference.get_ref() as FileDialog
|
|
||||||
if dialog == null or not is_instance_valid(dialog):
|
|
||||||
_tracked_file_dialogs.erase(instance_id)
|
|
||||||
continue
|
|
||||||
if not dialog.visible:
|
|
||||||
continue
|
|
||||||
FileDialogControllerNavigationType.configure(dialog)
|
|
||||||
_connect_file_dialog_text_controls(dialog)
|
|
||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
|
||||||
if (
|
|
||||||
_controller_text_entry_is_open.is_valid()
|
|
||||||
and bool(_controller_text_entry_is_open.call())
|
|
||||||
):
|
|
||||||
return
|
|
||||||
if not (event is InputEventJoypadButton or event is InputEventJoypadMotion):
|
|
||||||
return
|
|
||||||
var scope: Window = _active_file_dialog_scope()
|
|
||||||
if scope == null:
|
|
||||||
return
|
|
||||||
var focused: Control = scope.gui_get_focus_owner()
|
|
||||||
if focused == null:
|
|
||||||
return
|
|
||||||
var direction: Vector2 = _controller_direction(event)
|
|
||||||
var item_list := focused as ItemList
|
|
||||||
if (
|
|
||||||
item_list != null
|
|
||||||
and direction != Vector2.ZERO
|
|
||||||
and FileDialogControllerNavigationType.move_from_item_list(
|
|
||||||
item_list, direction
|
|
||||||
)
|
|
||||||
):
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
return
|
|
||||||
var button_event := event as InputEventJoypadButton
|
|
||||||
if (
|
|
||||||
button_event != null
|
|
||||||
and button_event.pressed
|
|
||||||
and event.is_action_pressed(&"ui_accept")
|
|
||||||
and (focused is LineEdit or focused is TextEdit)
|
|
||||||
and _controller_text_entry_request.is_valid()
|
|
||||||
and bool(_controller_text_entry_request.call(focused))
|
|
||||||
):
|
|
||||||
get_viewport().set_input_as_handled()
|
|
||||||
|
|
||||||
|
|
||||||
func _active_file_dialog_scope() -> Window:
|
|
||||||
for reference_value: WeakRef in _tracked_file_dialogs.values():
|
|
||||||
var dialog := reference_value.get_ref() as FileDialog
|
|
||||||
if dialog == null or not is_instance_valid(dialog) or not dialog.visible:
|
|
||||||
continue
|
|
||||||
return FileDialogControllerNavigationType.active_scope(dialog)
|
|
||||||
return null
|
|
||||||
|
|
||||||
|
|
||||||
func _controller_direction(event: InputEvent) -> Vector2:
|
|
||||||
if event.is_action_pressed(&"ui_up"):
|
|
||||||
return Vector2.UP
|
|
||||||
if event.is_action_pressed(&"ui_down"):
|
|
||||||
return Vector2.DOWN
|
|
||||||
if event.is_action_pressed(&"ui_left"):
|
|
||||||
return Vector2.LEFT
|
|
||||||
if event.is_action_pressed(&"ui_right"):
|
|
||||||
return Vector2.RIGHT
|
|
||||||
return Vector2.ZERO
|
|
||||||
|
|
||||||
|
|
||||||
func _connect_file_dialog_text_controls(dialog: FileDialog) -> void:
|
|
||||||
var scope: Window = FileDialogControllerNavigationType.active_scope(dialog)
|
|
||||||
for control: Control in (
|
|
||||||
FileDialogControllerNavigationType.interactive_controls(scope)
|
|
||||||
):
|
|
||||||
if not (control is LineEdit or control is TextEdit):
|
|
||||||
continue
|
|
||||||
var callback := _on_file_dialog_text_gui_input.bind(control)
|
|
||||||
if not control.gui_input.is_connected(callback):
|
|
||||||
control.gui_input.connect(callback)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_file_dialog_text_gui_input(
|
|
||||||
event: InputEvent,
|
|
||||||
control: Control,
|
|
||||||
) -> void:
|
|
||||||
var button_event := event as InputEventJoypadButton
|
|
||||||
if (
|
|
||||||
button_event == null
|
|
||||||
or not button_event.pressed
|
|
||||||
or (
|
|
||||||
button_event.button_index != JOY_BUTTON_A
|
|
||||||
and not event.is_action_pressed("ui_accept")
|
|
||||||
)
|
|
||||||
or not _controller_text_entry_request.is_valid()
|
|
||||||
):
|
|
||||||
return
|
|
||||||
if bool(_controller_text_entry_request.call(control)):
|
|
||||||
control.accept_event()
|
|
||||||
|
|
||||||
|
|
||||||
func _finalize_compact_file_dialog(
|
func _finalize_compact_file_dialog(
|
||||||
dialog: FileDialog,
|
dialog: FileDialog,
|
||||||
host_size: Vector2i,
|
host_size: Vector2i,
|
||||||
|
|
|
||||||
110
ui/mail_page.gd
110
ui/mail_page.gd
|
|
@ -137,32 +137,96 @@ func handle_controller_input(event: InputEvent) -> bool:
|
||||||
|
|
||||||
|
|
||||||
func _refresh_controller_navigation() -> void:
|
func _refresh_controller_navigation() -> void:
|
||||||
var controls: Array[Control] = []
|
var active_controls: Array[Control] = _active_controller_controls()
|
||||||
_collect_visible_controller_controls(self, controls)
|
for control: Control in _all_controller_controls():
|
||||||
for control: Control in controls:
|
|
||||||
var button := control as BaseButton
|
var button := control as BaseButton
|
||||||
control.focus_mode = (
|
control.focus_mode = (
|
||||||
Control.FOCUS_ALL
|
Control.FOCUS_ALL
|
||||||
if _interactive and (button == null or not button.disabled)
|
if (
|
||||||
|
_interactive
|
||||||
|
and control in active_controls
|
||||||
|
and control.is_visible_in_tree()
|
||||||
|
and (button == null or not button.disabled)
|
||||||
|
)
|
||||||
else Control.FOCUS_NONE
|
else Control.FOCUS_NONE
|
||||||
)
|
)
|
||||||
ControllerFocusNavigation.configure_spatial_neighbors(controls)
|
ControllerFocusNavigation.configure_spatial_neighbors(active_controls)
|
||||||
if _compose != null and _compose.visible:
|
if _compose != null and _compose.visible:
|
||||||
_configure_compose_controller_navigation()
|
_configure_compose_controller_navigation()
|
||||||
elif _inbox != null and _inbox.visible:
|
elif _inbox != null and _inbox.visible:
|
||||||
_configure_inbox_controller_navigation()
|
_configure_inbox_controller_navigation()
|
||||||
elif _letter != null and _letter.visible:
|
elif _letter != null and _letter.visible:
|
||||||
_configure_letter_controller_navigation()
|
_configure_letter_controller_navigation()
|
||||||
if not _interactive or controls.is_empty():
|
if not _interactive or active_controls.is_empty():
|
||||||
return
|
return
|
||||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||||
if focus_owner == null or not is_ancestor_of(focus_owner):
|
if focus_owner == null or not is_ancestor_of(focus_owner):
|
||||||
controls.sort_custom(func(first: Control, second: Control) -> bool:
|
active_controls.front().grab_focus()
|
||||||
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
|
func _active_controller_controls() -> Array[Control]:
|
||||||
)
|
if _compose != null and _compose.visible:
|
||||||
controls.front().grab_focus()
|
return _compose_controller_controls()
|
||||||
|
if _letter != null and _letter.visible:
|
||||||
|
return _letter_controller_controls()
|
||||||
|
return _inbox_controller_controls()
|
||||||
|
|
||||||
|
|
||||||
|
func _all_controller_controls() -> Array[Control]:
|
||||||
|
var controls: Array[Control] = _inbox_controller_controls()
|
||||||
|
controls.append_array(_compose_controller_controls())
|
||||||
|
controls.append_array(_letter_controller_controls())
|
||||||
|
return controls
|
||||||
|
|
||||||
|
|
||||||
|
func _inbox_controller_controls() -> Array[Control]:
|
||||||
|
var controls: Array[Control] = []
|
||||||
|
_append_controller_control(controls, _archive_view_button)
|
||||||
|
_append_controller_control(controls, _send_mail_button)
|
||||||
|
if _inbox_list != null:
|
||||||
|
for child: Node in _inbox_list.get_children():
|
||||||
|
_append_controller_control(controls, child as Button)
|
||||||
|
return controls
|
||||||
|
|
||||||
|
|
||||||
|
func _compose_controller_controls() -> Array[Control]:
|
||||||
|
var controls: Array[Control] = []
|
||||||
|
for control: Control in [
|
||||||
|
_greeting,
|
||||||
|
_recipient,
|
||||||
|
_attachment_kind,
|
||||||
|
_body,
|
||||||
|
_attachment_choice,
|
||||||
|
_salutation,
|
||||||
|
_amount_minus,
|
||||||
|
_attachment_amount,
|
||||||
|
_amount_plus,
|
||||||
|
_compose_cancel,
|
||||||
|
_send_button,
|
||||||
|
]:
|
||||||
|
_append_controller_control(controls, control)
|
||||||
|
return controls
|
||||||
|
|
||||||
|
|
||||||
|
func _letter_controller_controls() -> Array[Control]:
|
||||||
|
var controls: Array[Control] = []
|
||||||
|
for control: Control in [
|
||||||
|
_accept,
|
||||||
|
_decline,
|
||||||
|
_letter_close,
|
||||||
|
_archive,
|
||||||
|
_delete,
|
||||||
|
]:
|
||||||
|
_append_controller_control(controls, control)
|
||||||
|
return controls
|
||||||
|
|
||||||
|
|
||||||
|
func _append_controller_control(
|
||||||
|
controls: Array[Control],
|
||||||
|
control: Control,
|
||||||
|
) -> void:
|
||||||
|
if control != null and control.is_visible_in_tree():
|
||||||
|
controls.append(control)
|
||||||
|
|
||||||
|
|
||||||
func _configure_inbox_controller_navigation() -> void:
|
func _configure_inbox_controller_navigation() -> void:
|
||||||
|
|
@ -358,22 +422,6 @@ func _controller_focus_eligible(control: Control) -> bool:
|
||||||
return button == null or not button.disabled
|
return button == null or not button.disabled
|
||||||
|
|
||||||
|
|
||||||
func _collect_visible_controller_controls(
|
|
||||||
root: Node,
|
|
||||||
output: Array[Control],
|
|
||||||
) -> void:
|
|
||||||
for child: Node in root.get_children():
|
|
||||||
var control := child as Control
|
|
||||||
if control != null and not control.is_visible_in_tree():
|
|
||||||
continue
|
|
||||||
if (
|
|
||||||
control != null
|
|
||||||
and (control is BaseButton or control is LineEdit or control is TextEdit)
|
|
||||||
):
|
|
||||||
output.append(control)
|
|
||||||
_collect_visible_controller_controls(child, output)
|
|
||||||
|
|
||||||
|
|
||||||
func _build_ui() -> void:
|
func _build_ui() -> void:
|
||||||
var margin: MarginContainer = UtilityPageStyle.build_laptop_screen(self)
|
var margin: MarginContainer = UtilityPageStyle.build_laptop_screen(self)
|
||||||
var root := Control.new()
|
var root := Control.new()
|
||||||
|
|
@ -1041,12 +1089,6 @@ func _attachment_text(attachment: Dictionary) -> String:
|
||||||
return "Invalid gift."
|
return "Invalid gift."
|
||||||
|
|
||||||
|
|
||||||
func _focus_first() -> void:
|
|
||||||
var buttons := _inbox_list.find_children("*", "Button", true, false)
|
|
||||||
if not buttons.is_empty():
|
|
||||||
(buttons.front() as Button).grab_focus()
|
|
||||||
|
|
||||||
|
|
||||||
func _style_controls(root: Node) -> void:
|
func _style_controls(root: Node) -> void:
|
||||||
for node: Node in root.find_children("*", "", true, false):
|
for node: Node in root.find_children("*", "", true, false):
|
||||||
if node is BaseButton:
|
if node is BaseButton:
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,7 @@ func _on_save_pressed() -> void:
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
_set_mode(Mode.SAVED)
|
_set_mode(Mode.SAVED)
|
||||||
_select_entry_id(existing.entry_id)
|
_restore_entry_selection_and_focus(0, existing.entry_id)
|
||||||
return
|
return
|
||||||
_address.text = endpoint.normalized_display
|
_address.text = endpoint.normalized_display
|
||||||
_name_edit.text = (
|
_name_edit.text = (
|
||||||
|
|
@ -389,8 +389,7 @@ func _commit_name_entry() -> void:
|
||||||
_clear_edit_state()
|
_clear_edit_state()
|
||||||
_mode = Mode.SAVED
|
_mode = Mode.SAVED
|
||||||
_refresh_entries()
|
_refresh_entries()
|
||||||
_select_entry_id(saved.entry_id)
|
_restore_entry_selection_and_focus(0, saved.entry_id)
|
||||||
_refresh()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_edit_pressed() -> void:
|
func _on_edit_pressed() -> void:
|
||||||
|
|
@ -412,8 +411,7 @@ func _on_favorite_pressed() -> void:
|
||||||
var entry_id: String = _selected_entry.entry_id
|
var entry_id: String = _selected_entry.entry_id
|
||||||
_saved_servers.set_favorite(entry_id, not _selected_entry.favorite)
|
_saved_servers.set_favorite(entry_id, not _selected_entry.favorite)
|
||||||
_refresh_entries()
|
_refresh_entries()
|
||||||
_select_entry_id(entry_id)
|
_restore_entry_selection_and_focus(0, entry_id)
|
||||||
_refresh()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_delete_pressed() -> void:
|
func _on_delete_pressed() -> void:
|
||||||
|
|
@ -437,10 +435,12 @@ func _confirm_delete() -> void:
|
||||||
if _selected_entry == null or not _delete_armed:
|
if _selected_entry == null or not _delete_armed:
|
||||||
_cancel_delete()
|
_cancel_delete()
|
||||||
return
|
return
|
||||||
|
var removed_index: int = _visible_entries.find(_selected_entry)
|
||||||
|
var selected_id: String = _selected_entry.entry_id
|
||||||
var removed: bool = (
|
var removed: bool = (
|
||||||
_saved_servers.remove_recent_entry(_selected_entry.entry_id)
|
_saved_servers.remove_recent_entry(selected_id)
|
||||||
if _mode == Mode.RECENT
|
if _mode == Mode.RECENT
|
||||||
else _saved_servers.remove_saved_entry(_selected_entry.entry_id)
|
else _saved_servers.remove_saved_entry(selected_id)
|
||||||
)
|
)
|
||||||
_set_status(
|
_set_status(
|
||||||
(
|
(
|
||||||
|
|
@ -452,11 +452,13 @@ func _confirm_delete() -> void:
|
||||||
else "Could not remove the local entry.",
|
else "Could not remove the local entry.",
|
||||||
not removed,
|
not removed,
|
||||||
)
|
)
|
||||||
_selected_entry = null
|
|
||||||
_delete_armed = false
|
_delete_armed = false
|
||||||
_refresh_entries()
|
|
||||||
_refresh()
|
|
||||||
_delete_confirmation.hide_page()
|
_delete_confirmation.hide_page()
|
||||||
|
_refresh_entries()
|
||||||
|
_restore_entry_selection_and_focus(
|
||||||
|
maxi(removed_index, 0),
|
||||||
|
"" if removed else selected_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _cancel_delete() -> void:
|
func _cancel_delete() -> void:
|
||||||
|
|
@ -478,12 +480,51 @@ func _on_list_item_selected(index: int) -> void:
|
||||||
_refresh()
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
func _select_entry_id(entry_id: String) -> void:
|
func _select_entry_id(entry_id: String) -> bool:
|
||||||
for index: int in range(_visible_entries.size()):
|
for index: int in range(_visible_entries.size()):
|
||||||
if _visible_entries[index].entry_id == entry_id:
|
if _visible_entries[index].entry_id == entry_id:
|
||||||
_server_list.select(index)
|
return _select_entry_index(index)
|
||||||
_selected_entry = _visible_entries[index]
|
return false
|
||||||
return
|
|
||||||
|
|
||||||
|
func _select_entry_index(index: int) -> bool:
|
||||||
|
if index < 0 or index >= _visible_entries.size():
|
||||||
|
return false
|
||||||
|
_server_list.select(index)
|
||||||
|
_server_list.ensure_current_is_visible()
|
||||||
|
_selected_entry = _visible_entries[index]
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _restore_entry_selection_and_focus(
|
||||||
|
preferred_index: int,
|
||||||
|
preferred_id: String = "",
|
||||||
|
) -> void:
|
||||||
|
var selected: bool = (
|
||||||
|
not preferred_id.is_empty() and _select_entry_id(preferred_id)
|
||||||
|
)
|
||||||
|
if not selected and not _visible_entries.is_empty():
|
||||||
|
selected = _select_entry_index(
|
||||||
|
clampi(preferred_index, 0, _visible_entries.size() - 1)
|
||||||
|
)
|
||||||
|
if not selected:
|
||||||
|
_selected_entry = null
|
||||||
|
_server_list.deselect_all()
|
||||||
|
_refresh()
|
||||||
|
_defer_focus_control(
|
||||||
|
_server_list if selected else _current_mode_button()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _current_mode_button() -> Button:
|
||||||
|
match _mode:
|
||||||
|
Mode.DIRECT:
|
||||||
|
return _direct_button
|
||||||
|
Mode.SAVED:
|
||||||
|
return _saved_button
|
||||||
|
Mode.RECENT:
|
||||||
|
return _recent_button
|
||||||
|
return _discover_button
|
||||||
|
|
||||||
|
|
||||||
func _select_discovery_index(index: int) -> bool:
|
func _select_discovery_index(index: int) -> bool:
|
||||||
|
|
@ -1041,10 +1082,18 @@ func _on_peer_count_changed(player_count: int, max_players: int) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _on_store_changed() -> void:
|
func _on_store_changed() -> void:
|
||||||
|
var selected_index: int = _visible_entries.find(_selected_entry)
|
||||||
var selected_id: String = (
|
var selected_id: String = (
|
||||||
_selected_entry.entry_id if _selected_entry != null else ""
|
_selected_entry.entry_id if _selected_entry != null else ""
|
||||||
)
|
)
|
||||||
_refresh_entries()
|
_refresh_entries()
|
||||||
if not selected_id.is_empty():
|
_selected_entry = null
|
||||||
_select_entry_id(selected_id)
|
if (
|
||||||
|
not selected_id.is_empty()
|
||||||
|
and not _select_entry_id(selected_id)
|
||||||
|
and not _visible_entries.is_empty()
|
||||||
|
):
|
||||||
|
_select_entry_index(
|
||||||
|
clampi(maxi(selected_index, 0), 0, _visible_entries.size() - 1)
|
||||||
|
)
|
||||||
_refresh()
|
_refresh()
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,13 @@ func _preserve_native_text_focus(control: Control) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _can_edit(control: Control) -> bool:
|
func _can_edit(control: Control) -> bool:
|
||||||
|
if (
|
||||||
|
control == null
|
||||||
|
or not is_instance_valid(control)
|
||||||
|
or not control.is_inside_tree()
|
||||||
|
or not control.is_visible_in_tree()
|
||||||
|
):
|
||||||
|
return false
|
||||||
if control is LineEdit:
|
if control is LineEdit:
|
||||||
return (control as LineEdit).editable
|
return (control as LineEdit).editable
|
||||||
if control is TextEdit:
|
if control is TextEdit:
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ extends Control
|
||||||
|
|
||||||
const INPUT_OWNER: StringName = &"game_menu"
|
const INPUT_OWNER: StringName = &"game_menu"
|
||||||
const PAUSE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
|
const PAUSE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
|
||||||
const PAUSE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0)
|
|
||||||
const COMPACT_HEIGHT_THRESHOLD: float = 560.0
|
|
||||||
const VISIBILITY_FADE_DURATION: float = 0.55
|
const VISIBILITY_FADE_DURATION: float = 0.55
|
||||||
const PlayerType = preload("res://player/player.gd")
|
const PlayerType = preload("res://player/player.gd")
|
||||||
const SaveManagerType = preload("res://save/player_save_manager.gd")
|
const SaveManagerType = preload("res://save/player_save_manager.gd")
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,6 @@ focus_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath(
|
||||||
initial_focus_path = NodePath("BubbleCluster/ResumeButton")
|
initial_focus_path = NodePath("BubbleCluster/ResumeButton")
|
||||||
back_focus_path = NodePath("BubbleCluster/ResumeButton")
|
back_focus_path = NodePath("BubbleCluster/ResumeButton")
|
||||||
maximum_layout_size = Vector2(720, 520)
|
maximum_layout_size = Vector2(720, 520)
|
||||||
compact_maximum_layout_size = Vector2(544, 400)
|
|
||||||
compact_width_threshold = 680.0
|
|
||||||
compact_height_threshold = 500.0
|
|
||||||
|
|
||||||
[node name="BubbleCluster" type="Control" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage"]
|
[node name="BubbleCluster" type="Control" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage"]
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,6 @@ const BubbleButtonType = preload(
|
||||||
const BubbleClusterType = preload(
|
const BubbleClusterType = preload(
|
||||||
"res://ui/components/bubble_menu/bubble_cluster.gd"
|
"res://ui/components/bubble_menu/bubble_cluster.gd"
|
||||||
)
|
)
|
||||||
const BubbleContentShellType = preload(
|
|
||||||
"res://ui/components/bubble_menu/bubble_content_shell.gd"
|
|
||||||
)
|
|
||||||
const BubbleStatusBubbleType = preload(
|
|
||||||
"res://ui/components/bubble_menu/bubble_status_bubble.gd"
|
|
||||||
)
|
|
||||||
const NotepadInkActionType = preload(
|
const NotepadInkActionType = preload(
|
||||||
"res://ui/components/bubble_menu/notepad_ink_action.gd"
|
"res://ui/components/bubble_menu/notepad_ink_action.gd"
|
||||||
)
|
)
|
||||||
|
|
@ -78,17 +72,7 @@ const BagItemSpriteScene = preload(
|
||||||
const BagStorageSlotType = preload(
|
const BagStorageSlotType = preload(
|
||||||
"res://ui/components/bubble_menu/bag_storage_slot.gd"
|
"res://ui/components/bubble_menu/bag_storage_slot.gd"
|
||||||
)
|
)
|
||||||
const LogbookEntryType = preload(
|
|
||||||
"res://ui/components/bubble_menu/logbook_entry.gd"
|
|
||||||
)
|
|
||||||
const LogbookEntryScene = preload(
|
|
||||||
"res://ui/components/bubble_menu/logbook_entry.tscn"
|
|
||||||
)
|
|
||||||
|
|
||||||
const LOGBOOK_PAGE_DURATION: float = 0.18
|
|
||||||
const DESKTOP_REFERENCE_SIZE := Vector2(1280.0, 720.0)
|
const DESKTOP_REFERENCE_SIZE := Vector2(1280.0, 720.0)
|
||||||
const COMPACT_REFERENCE_SIZE := Vector2(640.0, 480.0)
|
|
||||||
const COMPACT_HEIGHT_THRESHOLD: float = 560.0
|
|
||||||
const NAVIGATION_PRESENTATION_SCALE: float = 0.60
|
const NAVIGATION_PRESENTATION_SCALE: float = 0.60
|
||||||
const NAVIGATION_CANONICAL_POSITION := Vector2(424.0, 44.0)
|
const NAVIGATION_CANONICAL_POSITION := Vector2(424.0, 44.0)
|
||||||
const NAVIGATION_SELECTED_SCALE: float = 1.02
|
const NAVIGATION_SELECTED_SCALE: float = 1.02
|
||||||
|
|
@ -236,18 +220,6 @@ const CONTROLLER_PICKUP_HOLD_SECONDS: float = 0.42
|
||||||
@onready var _mail_page: MailPage = %MailPage
|
@onready var _mail_page: MailPage = %MailPage
|
||||||
@onready var _profile_page: ProfilePage = %ProfilePage
|
@onready var _profile_page: ProfilePage = %ProfilePage
|
||||||
@onready var _players_page: PlayersPage = %PlayersPage
|
@onready var _players_page: PlayersPage = %PlayersPage
|
||||||
@onready var _book_backing: PanelContainer = %BookBacking
|
|
||||||
@onready var _book_spread: BoxContainer = %BookSpread
|
|
||||||
@onready var _left_page: PanelContainer = %LeftPage
|
|
||||||
@onready var _right_page: PanelContainer = %RightPage
|
|
||||||
@onready var _left_heading: Label = %LeftHeading
|
|
||||||
@onready var _right_heading: Label = %RightHeading
|
|
||||||
@onready var _left_entry_field: BoxContainer = %LeftEntryField
|
|
||||||
@onready var _right_entry_field: BoxContainer = %RightEntryField
|
|
||||||
@onready var _logbook_empty_state: Label = %LogbookEmptyState
|
|
||||||
@onready var _logbook_previous: BubbleButtonType = %LogbookPrevious
|
|
||||||
@onready var _logbook_next: BubbleButtonType = %LogbookNext
|
|
||||||
@onready var _logbook_page_status: BubbleStatusBubbleType = %LogbookPageStatus
|
|
||||||
@onready var _inventory_tab: BubbleButtonType = %InventoryTab
|
@onready var _inventory_tab: BubbleButtonType = %InventoryTab
|
||||||
@onready var _logbook_tab: BubbleButtonType = %LogbookTab
|
@onready var _logbook_tab: BubbleButtonType = %LogbookTab
|
||||||
@onready var _the_net_tab: BubbleButtonType = %TheNetTab
|
@onready var _the_net_tab: BubbleButtonType = %TheNetTab
|
||||||
|
|
@ -256,42 +228,12 @@ const CONTROLLER_PICKUP_HOLD_SECONDS: float = 0.42
|
||||||
@onready var _players_tab: BubbleButtonType = %PlayersTab
|
@onready var _players_tab: BubbleButtonType = %PlayersTab
|
||||||
@onready var _mail_unread_badge: Label = %MailUnreadBadge
|
@onready var _mail_unread_badge: Label = %MailUnreadBadge
|
||||||
@onready var _close_button: BubbleButtonType = %CloseButton
|
@onready var _close_button: BubbleButtonType = %CloseButton
|
||||||
@onready var _content_shell: BubbleContentShellType = %MenuPanel
|
|
||||||
@onready var _content_stage: Control = %Content
|
|
||||||
@onready var _cooler_scroll: ScrollContainer = %CoolerScroll
|
@onready var _cooler_scroll: ScrollContainer = %CoolerScroll
|
||||||
@onready var _cooler_host: Control = %CoolerHost
|
@onready var _cooler_host: Control = %CoolerHost
|
||||||
@onready var _wallet_balance: CurrencyAmount = %WalletBalance
|
|
||||||
@onready var _header: Control = %Header
|
|
||||||
@onready var _separator: Control = %Separator
|
|
||||||
@onready var _wallet_status: BubbleStatusBubbleType = %WalletStatus
|
|
||||||
@onready var _capacity_status: BubbleStatusBubbleType = %CapacityStatus
|
|
||||||
@onready var _held_value_status: BubbleStatusBubbleType = %HeldValueStatus
|
|
||||||
@onready var _selection_status: BubbleStatusBubbleType = %SelectionStatus
|
|
||||||
@onready var _offer_status: BubbleStatusBubbleType = %OfferStatus
|
|
||||||
@onready var _inventory_section: Control = %InventorySection
|
|
||||||
@onready var _bag_section: Control = %BagSection
|
|
||||||
@onready var _bag_empty: Label = %BagEmpty
|
|
||||||
@onready var _bag_grid: GridContainer = %BagGrid
|
|
||||||
@onready var _bag_list: Control = %BagList
|
|
||||||
@onready var _bag_detail: Control = %BagDetail
|
|
||||||
@onready var _bag_detail_texture: TextureRect = %BagDetailTexture
|
|
||||||
@onready var _bag_detail_name: Label = %BagDetailName
|
|
||||||
@onready var _bag_detail_data: Label = %BagDetailData
|
|
||||||
@onready var _sort_option: OptionButton = %SortOption
|
|
||||||
@onready var _sort_direction: Button = %SortDirection
|
|
||||||
@onready var _held_value: CurrencyAmount = %HeldValue
|
|
||||||
@onready var _cooler_count: Label = %CoolerCount
|
|
||||||
@onready var _inventory_empty: Label = %InventoryEmpty
|
|
||||||
@onready var _selection_summary: Label = %SelectionSummary
|
|
||||||
@onready var _favorite_button: Button = %FavoriteButton
|
|
||||||
@onready var _sell_button: Button = %SellButton
|
|
||||||
@onready var _sale_unavailable: Label = %SaleUnavailable
|
|
||||||
@onready var _transaction_feedback: RichTextLabel = %TransactionFeedback
|
|
||||||
@onready var _sale_confirmation: PanelContainer = %SaleConfirmation
|
@onready var _sale_confirmation: PanelContainer = %SaleConfirmation
|
||||||
@onready var _confirmation_message: RichTextLabel = %ConfirmationMessage
|
@onready var _confirmation_message: RichTextLabel = %ConfirmationMessage
|
||||||
@onready var _confirm_sale_button: Button = %ConfirmSaleButton
|
@onready var _confirm_sale_button: Button = %ConfirmSaleButton
|
||||||
@onready var _cancel_sale_button: Button = %CancelSaleButton
|
@onready var _cancel_sale_button: Button = %CancelSaleButton
|
||||||
@onready var _logbook_empty: Label = %LogbookEmpty
|
|
||||||
|
|
||||||
var _compact_layout: bool = false
|
var _compact_layout: bool = false
|
||||||
var _player: PlayerType
|
var _player: PlayerType
|
||||||
|
|
@ -361,8 +303,6 @@ var _transition_generation: int = 0
|
||||||
var _page_transition_generation: int = 0
|
var _page_transition_generation: int = 0
|
||||||
var _transitioning: bool = false
|
var _transitioning: bool = false
|
||||||
var _page_transitioning: bool = false
|
var _page_transitioning: bool = false
|
||||||
var _presentation_rest_position: Vector2 = Vector2.ZERO
|
|
||||||
var _content_rest_position: Vector2 = Vector2.ZERO
|
|
||||||
var _cooler_rest_position: Vector2 = Vector2.ZERO
|
var _cooler_rest_position: Vector2 = Vector2.ZERO
|
||||||
var _bag_rest_position: Vector2 = Vector2.ZERO
|
var _bag_rest_position: Vector2 = Vector2.ZERO
|
||||||
var _tackle_rest_position: Vector2 = Vector2.ZERO
|
var _tackle_rest_position: Vector2 = Vector2.ZERO
|
||||||
|
|
@ -382,12 +322,6 @@ var _bag_slot_nodes: Array[BagStorageSlotType] = []
|
||||||
var _sorted_bag_items: Array[OwnedItemType] = []
|
var _sorted_bag_items: Array[OwnedItemType] = []
|
||||||
var _bag_drag_active: bool = false
|
var _bag_drag_active: bool = false
|
||||||
var _motion_elapsed: float = 0.0
|
var _motion_elapsed: float = 0.0
|
||||||
var _logbook_species: Array[FishDataType] = []
|
|
||||||
var _logbook_current_page: int = 0
|
|
||||||
var _logbook_page_count: int = 1
|
|
||||||
var _logbook_page_transitioning: bool = false
|
|
||||||
var _logbook_page_generation: int = 0
|
|
||||||
var _logbook_page_tween: Tween
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -410,22 +344,14 @@ func _ready() -> void:
|
||||||
_profile_tab.pressed.connect(_show_section.bind(Section.PROFILE))
|
_profile_tab.pressed.connect(_show_section.bind(Section.PROFILE))
|
||||||
_players_tab.pressed.connect(_show_section.bind(Section.PLAYERS))
|
_players_tab.pressed.connect(_show_section.bind(Section.PLAYERS))
|
||||||
_close_button.pressed.connect(close_menu)
|
_close_button.pressed.connect(close_menu)
|
||||||
_sort_option.item_selected.connect(_on_sort_selected.bind(_sort_option))
|
|
||||||
_sort_direction.pressed.connect(_on_sort_direction_pressed)
|
|
||||||
_cooler_sort_option.item_selected.connect(_on_cooler_sort_selected)
|
_cooler_sort_option.item_selected.connect(_on_cooler_sort_selected)
|
||||||
_cooler_sort_direction.pressed.connect(_on_sort_direction_pressed)
|
_cooler_sort_direction.pressed.connect(_on_sort_direction_pressed)
|
||||||
_favorite_button.pressed.connect(_on_favorite_pressed)
|
|
||||||
_sell_button.pressed.connect(_on_sell_pressed)
|
|
||||||
_favorite_bubble.pressed.connect(_on_favorite_pressed)
|
_favorite_bubble.pressed.connect(_on_favorite_pressed)
|
||||||
_sell_bubble.pressed.connect(_on_sell_pressed)
|
_sell_bubble.pressed.connect(_on_sell_pressed)
|
||||||
_sell_all_bubble.pressed.connect(_on_sell_all_pressed)
|
_sell_all_bubble.pressed.connect(_on_sell_all_pressed)
|
||||||
_confirm_sale_button.pressed.connect(_on_confirm_sale_pressed)
|
_confirm_sale_button.pressed.connect(_on_confirm_sale_pressed)
|
||||||
_cancel_sale_button.pressed.connect(_close_sale_confirmation)
|
_cancel_sale_button.pressed.connect(_close_sale_confirmation)
|
||||||
_configure_sale_confirmation_focus()
|
_configure_sale_confirmation_focus()
|
||||||
_sort_option.add_item("catch order", SortMode.CATCH_ORDER)
|
|
||||||
_sort_option.add_item("name", SortMode.NAME)
|
|
||||||
_sort_option.add_item("rarity", SortMode.RARITY)
|
|
||||||
_sort_option.select(SortMode.CATCH_ORDER)
|
|
||||||
_cooler_sort_option.add_item("catch order", SortMode.CATCH_ORDER)
|
_cooler_sort_option.add_item("catch order", SortMode.CATCH_ORDER)
|
||||||
_cooler_sort_option.add_item("name", SortMode.NAME)
|
_cooler_sort_option.add_item("name", SortMode.NAME)
|
||||||
_cooler_sort_option.add_item("rarity", SortMode.RARITY)
|
_cooler_sort_option.add_item("rarity", SortMode.RARITY)
|
||||||
|
|
@ -446,7 +372,6 @@ func _ready() -> void:
|
||||||
# Keep the exposed tab hitboxes above the full-page roots. The individual
|
# Keep the exposed tab hitboxes above the full-page roots. The individual
|
||||||
# main panels retain a higher z-index and cover the tab bodies below y=166.
|
# main panels retain a higher z-index and cover the tab bodies below y=166.
|
||||||
_inventory_sub_tabs.move_to_front()
|
_inventory_sub_tabs.move_to_front()
|
||||||
_apply_cooler_control_styles()
|
|
||||||
_apply_cooler_wall_styles()
|
_apply_cooler_wall_styles()
|
||||||
_apply_cooler_notepad_style()
|
_apply_cooler_notepad_style()
|
||||||
_cooler_water_surface.resized.connect(_update_cooler_water_mask)
|
_cooler_water_surface.resized.connect(_update_cooler_water_mask)
|
||||||
|
|
@ -626,6 +551,12 @@ func set_profile_preview_world_pixel_size(pixel_size: int) -> void:
|
||||||
_profile_page.set_world_pixel_size(pixel_size)
|
_profile_page.set_world_pixel_size(pixel_size)
|
||||||
|
|
||||||
|
|
||||||
|
func allows_global_controller_scroll() -> bool:
|
||||||
|
# The profile page owns the right stick for preview orbit, zoom, and its
|
||||||
|
# color picker. The shared menu scroller must not interpret that axis too.
|
||||||
|
return _current_section != Section.PROFILE
|
||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
if event is InputEventKey and event.echo:
|
if event is InputEventKey and event.echo:
|
||||||
return
|
return
|
||||||
|
|
@ -1768,12 +1699,6 @@ func _show_section_immediate(section: Section) -> void:
|
||||||
_mail_page.visible = section == Section.MAIL
|
_mail_page.visible = section == Section.MAIL
|
||||||
_profile_page.visible = section == Section.PROFILE
|
_profile_page.visible = section == Section.PROFILE
|
||||||
_players_page.visible = section == Section.PLAYERS
|
_players_page.visible = section == Section.PLAYERS
|
||||||
_content_shell.visible = false
|
|
||||||
_inventory_section.visible = false
|
|
||||||
_bag_section.visible = false
|
|
||||||
_content_shell.set_background_visible(true)
|
|
||||||
_header.visible = section != Section.COOLER
|
|
||||||
_separator.visible = section != Section.COOLER
|
|
||||||
if section == Section.COOLER:
|
if section == Section.COOLER:
|
||||||
_refresh_inventory()
|
_refresh_inventory()
|
||||||
else:
|
else:
|
||||||
|
|
@ -1787,7 +1712,6 @@ func _show_section_immediate(section: Section) -> void:
|
||||||
_refresh_bag()
|
_refresh_bag()
|
||||||
_refresh_tackle_box()
|
_refresh_tackle_box()
|
||||||
if section != Section.LOGBOOK:
|
if section != Section.LOGBOOK:
|
||||||
_cancel_logbook_page_transition(true)
|
|
||||||
_catalog_logbook.deactivate()
|
_catalog_logbook.deactivate()
|
||||||
else:
|
else:
|
||||||
_catalog_logbook.activate()
|
_catalog_logbook.activate()
|
||||||
|
|
@ -1874,12 +1798,9 @@ func _focus_current_section() -> void:
|
||||||
if _shop_cooler_context_active:
|
if _shop_cooler_context_active:
|
||||||
_focus_shop_cooler()
|
_focus_shop_cooler()
|
||||||
return
|
return
|
||||||
_reserve_main_navigation_for_page_switching()
|
var candidates: Array[Control] = _inventory_content_focus_candidates()
|
||||||
_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():
|
if candidates.is_empty():
|
||||||
|
_enter_inventory_tabs_zone()
|
||||||
return
|
return
|
||||||
ControllerFocusNavigationType.configure_spatial_neighbors(candidates)
|
ControllerFocusNavigationType.configure_spatial_neighbors(candidates)
|
||||||
candidates.sort_custom(func(first: Control, second: Control) -> bool:
|
candidates.sort_custom(func(first: Control, second: Control) -> bool:
|
||||||
|
|
@ -1890,23 +1811,22 @@ func _focus_current_section() -> void:
|
||||||
candidates[0].grab_focus()
|
candidates[0].grab_focus()
|
||||||
|
|
||||||
|
|
||||||
func _collect_focusable_content(
|
func _inventory_content_focus_candidates() -> Array[Control]:
|
||||||
root: Node,
|
var candidates: Array[Control] = []
|
||||||
navigation_cluster: Control,
|
match _current_section:
|
||||||
output: Array[Control],
|
Section.COOLER:
|
||||||
) -> void:
|
for fish_node: CoolerFishSpriteType in _fish_nodes.values():
|
||||||
for child: Node in root.get_children():
|
if ControllerFocusNavigationType.is_focusable(fish_node):
|
||||||
if child == navigation_cluster:
|
candidates.append(fish_node)
|
||||||
continue
|
Section.BAG:
|
||||||
var control := child as Control
|
for item_node: BagItemSpriteType in _bag_item_nodes.values():
|
||||||
if control != null and not control.is_visible_in_tree():
|
if ControllerFocusNavigationType.is_focusable(item_node):
|
||||||
continue
|
candidates.append(item_node)
|
||||||
if (
|
Section.TACKLE_BOX:
|
||||||
ControllerFocusNavigationType.is_focusable(control)
|
for tackle_button: Button in _tackle_item_buttons.values():
|
||||||
and not control is ScrollBar
|
if ControllerFocusNavigationType.is_focusable(tackle_button):
|
||||||
):
|
candidates.append(tackle_button)
|
||||||
output.append(control)
|
return candidates
|
||||||
_collect_focusable_content(child, navigation_cluster, output)
|
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
|
@ -2573,35 +2493,6 @@ func _on_active_lure_changed(_item_id: StringName) -> void:
|
||||||
_update_tackle_detail()
|
_update_tackle_detail()
|
||||||
|
|
||||||
|
|
||||||
func _apply_cooler_control_styles() -> void:
|
|
||||||
var profile: BubbleMenuProfile = _inventory_tab.profile
|
|
||||||
if profile == null:
|
|
||||||
return
|
|
||||||
_sort_option.add_theme_stylebox_override(
|
|
||||||
"normal",
|
|
||||||
profile.make_normal_style(),
|
|
||||||
)
|
|
||||||
_sort_option.add_theme_stylebox_override(
|
|
||||||
"hover",
|
|
||||||
profile.make_hover_style(),
|
|
||||||
)
|
|
||||||
_sort_option.add_theme_stylebox_override(
|
|
||||||
"focus",
|
|
||||||
profile.make_hover_style(),
|
|
||||||
)
|
|
||||||
_sort_option.add_theme_stylebox_override(
|
|
||||||
"pressed",
|
|
||||||
profile.make_pressed_style(),
|
|
||||||
)
|
|
||||||
_sort_option.add_theme_color_override("font_color", profile.text_color)
|
|
||||||
_sort_option.add_theme_color_override(
|
|
||||||
"font_hover_color",
|
|
||||||
profile.text_hover_color,
|
|
||||||
)
|
|
||||||
_sort_option.add_theme_color_override(
|
|
||||||
"font_focus_color",
|
|
||||||
profile.text_hover_color,
|
|
||||||
)
|
|
||||||
func _apply_cooler_wall_styles() -> void:
|
func _apply_cooler_wall_styles() -> void:
|
||||||
var outer := StyleBoxFlat.new()
|
var outer := StyleBoxFlat.new()
|
||||||
outer.bg_color = Color(0.76, 0.9, 0.96, 1.0)
|
outer.bg_color = Color(0.76, 0.9, 0.96, 1.0)
|
||||||
|
|
@ -2708,59 +2599,6 @@ func _apply_bag_styles() -> void:
|
||||||
inner.set_border_width_all(0)
|
inner.set_border_width_all(0)
|
||||||
inner.set_corner_radius_all(INVENTORY_INNER_CORNER_RADIUS)
|
inner.set_corner_radius_all(INVENTORY_INNER_CORNER_RADIUS)
|
||||||
_bag_inner_liner.add_theme_stylebox_override("panel", inner)
|
_bag_inner_liner.add_theme_stylebox_override("panel", inner)
|
||||||
func _apply_logbook_styles() -> void:
|
|
||||||
var backing := StyleBoxFlat.new()
|
|
||||||
backing.bg_color = Color(0.54, 0.42, 0.27, 1.0)
|
|
||||||
backing.border_color = Color(0.72, 0.59, 0.39, 1.0)
|
|
||||||
backing.set_border_width_all(6)
|
|
||||||
backing.corner_radius_top_left = 42
|
|
||||||
backing.corner_radius_top_right = 34
|
|
||||||
backing.corner_radius_bottom_right = 46
|
|
||||||
backing.corner_radius_bottom_left = 36
|
|
||||||
_book_backing.add_theme_stylebox_override("panel", backing)
|
|
||||||
var left_paper := StyleBoxFlat.new()
|
|
||||||
left_paper.bg_color = Color(0.95, 0.91, 0.79, 1.0)
|
|
||||||
left_paper.border_color = Color(0.80, 0.70, 0.52, 1.0)
|
|
||||||
left_paper.set_border_width_all(3)
|
|
||||||
left_paper.corner_radius_top_left = 30
|
|
||||||
left_paper.corner_radius_bottom_left = 38
|
|
||||||
left_paper.corner_radius_top_right = 8
|
|
||||||
left_paper.corner_radius_bottom_right = 6
|
|
||||||
_left_page.add_theme_stylebox_override("panel", left_paper)
|
|
||||||
var right_paper := StyleBoxFlat.new()
|
|
||||||
right_paper.bg_color = Color(0.925, 0.875, 0.74, 1.0)
|
|
||||||
right_paper.border_color = Color(0.77, 0.66, 0.48, 1.0)
|
|
||||||
right_paper.set_border_width_all(3)
|
|
||||||
right_paper.corner_radius_top_left = 8
|
|
||||||
right_paper.corner_radius_bottom_left = 6
|
|
||||||
right_paper.corner_radius_top_right = 34
|
|
||||||
right_paper.corner_radius_bottom_right = 40
|
|
||||||
_right_page.add_theme_stylebox_override("panel", right_paper)
|
|
||||||
for label: Label in [_left_heading, _right_heading, _logbook_empty_state]:
|
|
||||||
label.add_theme_color_override(
|
|
||||||
"font_color",
|
|
||||||
Color(0.22, 0.16, 0.09, 1.0),
|
|
||||||
)
|
|
||||||
var page_profile: BubbleMenuProfile = _logbook_previous.profile
|
|
||||||
if page_profile != null:
|
|
||||||
var disabled_style: StyleBoxFlat = page_profile.make_normal_style()
|
|
||||||
disabled_style.bg_color.a = 0.82
|
|
||||||
disabled_style.border_color.a = 0.72
|
|
||||||
for control: BubbleButtonType in [
|
|
||||||
_logbook_previous,
|
|
||||||
_logbook_next,
|
|
||||||
]:
|
|
||||||
control.add_theme_stylebox_override(
|
|
||||||
"disabled",
|
|
||||||
disabled_style.duplicate(),
|
|
||||||
)
|
|
||||||
control.add_theme_color_override(
|
|
||||||
"font_disabled_color",
|
|
||||||
Color(0.035, 0.145, 0.22, 0.68),
|
|
||||||
)
|
|
||||||
_logbook_page_status.set_content("pages", "1 / 1")
|
|
||||||
|
|
||||||
|
|
||||||
func _update_navigation_selection() -> void:
|
func _update_navigation_selection() -> void:
|
||||||
_set_navigation_target(_current_section)
|
_set_navigation_target(_current_section)
|
||||||
|
|
||||||
|
|
@ -2790,9 +2628,6 @@ func _update_shell_layout() -> void:
|
||||||
_presentation_scale_root.scale = Vector2.ONE
|
_presentation_scale_root.scale = Vector2.ONE
|
||||||
_presentation_scale_root.position = Vector2.ZERO
|
_presentation_scale_root.position = Vector2.ZERO
|
||||||
_presentation_scale_root.pivot_offset = reference_size * 0.5
|
_presentation_scale_root.pivot_offset = reference_size * 0.5
|
||||||
_content_shell.position = Vector2(14.0, 92.0) if compact else Vector2(42.0, 104.0)
|
|
||||||
_content_shell.size = Vector2(612.0, 286.0) if compact else Vector2(1196.0, 478.0)
|
|
||||||
_presentation_rest_position = _content_shell.position
|
|
||||||
var navigation_size := (
|
var navigation_size := (
|
||||||
Vector2(840.0, 75.0) if compact else Vector2(840.0, 100.0)
|
Vector2(840.0, 75.0) if compact else Vector2(840.0, 100.0)
|
||||||
)
|
)
|
||||||
|
|
@ -2982,13 +2817,7 @@ func _update_shell_layout() -> void:
|
||||||
520.0 if compact else 800.0,
|
520.0 if compact else 800.0,
|
||||||
512.0 if compact else 420.0,
|
512.0 if compact else 420.0,
|
||||||
)
|
)
|
||||||
_bag_grid.columns = 2 if compact else 3
|
|
||||||
_bag_list.custom_minimum_size.x = 300.0 if compact else 360.0
|
|
||||||
_bag_detail.custom_minimum_size.x = 176.0 if compact else 220.0
|
|
||||||
_content_stage.custom_minimum_size.y = 220.0 if compact else 260.0
|
|
||||||
_content_rest_position = _content_stage.position
|
|
||||||
if not _transitioning:
|
if not _transitioning:
|
||||||
_content_shell.position = _presentation_rest_position
|
|
||||||
_cooler_page.position = _cooler_rest_position
|
_cooler_page.position = _cooler_rest_position
|
||||||
_bag_page.position = _bag_rest_position
|
_bag_page.position = _bag_rest_position
|
||||||
_tackle_box_page.position = _tackle_rest_position
|
_tackle_box_page.position = _tackle_rest_position
|
||||||
|
|
@ -3005,8 +2834,6 @@ func _update_shell_layout() -> void:
|
||||||
_mail_page.modulate.a = 1.0
|
_mail_page.modulate.a = 1.0
|
||||||
_profile_page.modulate.a = 1.0
|
_profile_page.modulate.a = 1.0
|
||||||
_players_page.modulate.a = 1.0
|
_players_page.modulate.a = 1.0
|
||||||
if not _page_transitioning:
|
|
||||||
_content_stage.position = _content_rest_position
|
|
||||||
_layout_cooler_fish(false)
|
_layout_cooler_fish(false)
|
||||||
_layout_bag_items()
|
_layout_bag_items()
|
||||||
|
|
||||||
|
|
@ -3177,7 +3004,6 @@ func _finish_close(
|
||||||
) -> void:
|
) -> void:
|
||||||
_cancel_presentation_tween()
|
_cancel_presentation_tween()
|
||||||
_cancel_page_tween()
|
_cancel_page_tween()
|
||||||
_cancel_logbook_page_transition(true)
|
|
||||||
_the_net_page.deactivate()
|
_the_net_page.deactivate()
|
||||||
_transitioning = false
|
_transitioning = false
|
||||||
_page_transitioning = false
|
_page_transitioning = false
|
||||||
|
|
@ -3243,7 +3069,6 @@ func _get_section_rest_position(section: Section) -> Vector2:
|
||||||
|
|
||||||
|
|
||||||
func _begin_page_transition(section: Section) -> void:
|
func _begin_page_transition(section: Section) -> void:
|
||||||
_cancel_logbook_page_transition(true)
|
|
||||||
_page_transition_generation += 1
|
_page_transition_generation += 1
|
||||||
_cancel_page_tween()
|
_cancel_page_tween()
|
||||||
_page_transitioning = true
|
_page_transitioning = true
|
||||||
|
|
@ -3354,11 +3179,6 @@ func _set_shell_interactive(interactive: bool) -> void:
|
||||||
|
|
||||||
func _set_content_interactive(interactive: bool) -> void:
|
func _set_content_interactive(interactive: bool) -> void:
|
||||||
_content_interactive_enabled = interactive
|
_content_interactive_enabled = interactive
|
||||||
_content_stage.mouse_filter = (
|
|
||||||
Control.MOUSE_FILTER_PASS
|
|
||||||
if interactive
|
|
||||||
else Control.MOUSE_FILTER_IGNORE
|
|
||||||
)
|
|
||||||
_cooler_page.mouse_filter = (
|
_cooler_page.mouse_filter = (
|
||||||
Control.MOUSE_FILTER_PASS
|
Control.MOUSE_FILTER_PASS
|
||||||
if interactive and _current_section == Section.COOLER
|
if interactive and _current_section == Section.COOLER
|
||||||
|
|
@ -3499,19 +3319,8 @@ func _reset_page_transition_visuals() -> void:
|
||||||
_page_outgoing_content_root = null
|
_page_outgoing_content_root = null
|
||||||
|
|
||||||
|
|
||||||
func _on_sort_selected(index: int, source: OptionButton) -> void:
|
|
||||||
var selected_id: int = source.get_item_id(index)
|
|
||||||
if selected_id < SortMode.CATCH_ORDER or selected_id > SortMode.RARITY:
|
|
||||||
return
|
|
||||||
_sort_mode = selected_id as SortMode
|
|
||||||
_sort_option.select(_sort_mode)
|
|
||||||
_cooler_sort_option.select(_sort_mode)
|
|
||||||
_refresh_inventory()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cooler_sort_selected(sort_id: int) -> void:
|
func _on_cooler_sort_selected(sort_id: int) -> void:
|
||||||
_sort_mode = sort_id as SortMode
|
_sort_mode = sort_id as SortMode
|
||||||
_sort_option.select(_sort_mode)
|
|
||||||
_cooler_sort_option.select(_sort_mode)
|
_cooler_sort_option.select(_sort_mode)
|
||||||
_refresh_inventory()
|
_refresh_inventory()
|
||||||
|
|
||||||
|
|
@ -3541,7 +3350,6 @@ func _update_sort_direction_text() -> void:
|
||||||
direction_text = (
|
direction_text = (
|
||||||
"high to low" if _sort_descending else "low to high"
|
"high to low" if _sort_descending else "low to high"
|
||||||
)
|
)
|
||||||
_sort_direction.text = direction_text
|
|
||||||
_cooler_sort_direction.text = direction_text
|
_cooler_sort_direction.text = direction_text
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3592,7 +3400,6 @@ func _refresh_bag() -> void:
|
||||||
owned_items = filtered_items
|
owned_items = filtered_items
|
||||||
owned_items.sort_custom(_sort_bag_storage_items)
|
owned_items.sort_custom(_sort_bag_storage_items)
|
||||||
_sorted_bag_items = owned_items
|
_sorted_bag_items = owned_items
|
||||||
_bag_empty.visible = false
|
|
||||||
_bag_empty_state.visible = false
|
_bag_empty_state.visible = false
|
||||||
_bag_empty_state.text = (
|
_bag_empty_state.text = (
|
||||||
"No equipment in your Bag."
|
"No equipment in your Bag."
|
||||||
|
|
@ -3932,23 +3739,8 @@ func _update_bag_detail() -> void:
|
||||||
item_state = "equippable"
|
item_state = "equippable"
|
||||||
elif item.usable:
|
elif item.usable:
|
||||||
item_state = "usable"
|
item_state = "usable"
|
||||||
_bag_detail_texture.texture = null
|
|
||||||
_bag_detail_name.text = item.display_name if item != null else ""
|
|
||||||
_bag_detail_data.text = (
|
|
||||||
"%s\nquantity: %d\n%s\n%s\n%s"
|
|
||||||
% [
|
|
||||||
_item_category_display_name(item),
|
|
||||||
quantity,
|
|
||||||
item_state,
|
|
||||||
hotbar_assignment,
|
|
||||||
item.description,
|
|
||||||
]
|
|
||||||
if item != null
|
|
||||||
else "select a bag item for details."
|
|
||||||
)
|
|
||||||
_bag_sprite_detail_texture.texture = null
|
_bag_sprite_detail_texture.texture = null
|
||||||
if item != null:
|
if item != null:
|
||||||
_bag_detail_texture.texture = item.icon
|
|
||||||
_bag_sprite_detail_texture.texture = item.icon
|
_bag_sprite_detail_texture.texture = item.icon
|
||||||
_bag_sprite_detail_name.text = (
|
_bag_sprite_detail_name.text = (
|
||||||
item.display_name if item != null else ""
|
item.display_name if item != null else ""
|
||||||
|
|
@ -4065,23 +3857,11 @@ func _refresh_economy_summary() -> void:
|
||||||
if _inventory != null
|
if _inventory != null
|
||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
_wallet_balance.set_amount(balance)
|
|
||||||
_wallet_status.set_currency_amount("wallet", balance)
|
|
||||||
_notepad_wallet_value.set_amount(balance)
|
_notepad_wallet_value.set_amount(balance)
|
||||||
_capacity_status.set_content("cooler", "%d / %d" % [
|
|
||||||
_inventory.get_all_catches().size() if _inventory != null else 0,
|
|
||||||
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
|
||||||
])
|
|
||||||
_notepad_capacity_value.text = "%d / %d" % [
|
_notepad_capacity_value.text = "%d / %d" % [
|
||||||
_inventory.get_all_catches().size() if _inventory != null else 0,
|
_inventory.get_all_catches().size() if _inventory != null else 0,
|
||||||
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
||||||
]
|
]
|
||||||
_held_value_status.set_currency_amount("held value", held_total)
|
|
||||||
_held_value.set_amount(held_total)
|
|
||||||
_cooler_count.text = "%d / %d" % [
|
|
||||||
_inventory.get_all_catches().size() if _inventory != null else 0,
|
|
||||||
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cooler_capacity_changed(_level: int, _capacity: int) -> void:
|
func _on_cooler_capacity_changed(_level: int, _capacity: int) -> void:
|
||||||
|
|
@ -4098,7 +3878,6 @@ func _refresh_inventory() -> void:
|
||||||
catches.append(fish_catch)
|
catches.append(fish_catch)
|
||||||
catches.sort_custom(_compare_catches)
|
catches.sort_custom(_compare_catches)
|
||||||
_sorted_catches = catches
|
_sorted_catches = catches
|
||||||
_inventory_empty.visible = catches.is_empty()
|
|
||||||
var visible_ids: Array[StringName] = []
|
var visible_ids: Array[StringName] = []
|
||||||
for fish_catch: FishCatchType in catches:
|
for fish_catch: FishCatchType in catches:
|
||||||
visible_ids.append(fish_catch.catch_id)
|
visible_ids.append(fish_catch.catch_id)
|
||||||
|
|
@ -4358,7 +4137,6 @@ func _on_catch_card_pressed(catch_id: StringName) -> void:
|
||||||
Input.is_key_pressed(KEY_CTRL),
|
Input.is_key_pressed(KEY_CTRL),
|
||||||
Input.is_key_pressed(KEY_SHIFT)
|
Input.is_key_pressed(KEY_SHIFT)
|
||||||
)
|
)
|
||||||
_set_transaction_feedback("")
|
|
||||||
_refresh_inventory()
|
_refresh_inventory()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4388,7 +4166,6 @@ func _on_fish_field_gui_input(event: InputEvent) -> void:
|
||||||
and not get_viewport().gui_is_dragging()
|
and not get_viewport().gui_is_dragging()
|
||||||
):
|
):
|
||||||
_fish_selection.clear()
|
_fish_selection.clear()
|
||||||
_set_transaction_feedback("")
|
|
||||||
_refresh_inventory()
|
_refresh_inventory()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4465,8 +4242,6 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
||||||
_cooler_detail_texture.visible = false
|
_cooler_detail_texture.visible = false
|
||||||
_cooler_detail_name.text = "select a fish"
|
_cooler_detail_name.text = "select a fish"
|
||||||
_clear_cooler_detail_stats()
|
_clear_cooler_detail_stats()
|
||||||
_favorite_button.disabled = true
|
|
||||||
_favorite_button.text = "favorite"
|
|
||||||
_favorite_bubble.disabled = true
|
_favorite_bubble.disabled = true
|
||||||
_favorite_bubble.text = "favorite"
|
_favorite_bubble.text = "favorite"
|
||||||
_favorite_bubble.persistent_mark = false
|
_favorite_bubble.persistent_mark = false
|
||||||
|
|
@ -4511,12 +4286,10 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
||||||
else:
|
else:
|
||||||
_cooler_offer_label.text = "buyer unavailable"
|
_cooler_offer_label.text = "buyer unavailable"
|
||||||
_cooler_offer_value.visible = false
|
_cooler_offer_value.visible = false
|
||||||
_favorite_button.disabled = false
|
_favorite_bubble.disabled = false
|
||||||
_favorite_button.text = (
|
_favorite_bubble.text = (
|
||||||
"unfavorite" if fish_catch.is_favorited else "favorite"
|
"unfavorite" if fish_catch.is_favorited else "favorite"
|
||||||
)
|
)
|
||||||
_favorite_bubble.disabled = false
|
|
||||||
_favorite_bubble.text = _favorite_button.text
|
|
||||||
_favorite_bubble.persistent_mark = fish_catch.is_favorited
|
_favorite_bubble.persistent_mark = fish_catch.is_favorited
|
||||||
_favorite_bubble.refresh_ink_state()
|
_favorite_bubble.refresh_ink_state()
|
||||||
_detail_constellation.visible = _current_section == Section.COOLER
|
_detail_constellation.visible = _current_section == Section.COOLER
|
||||||
|
|
@ -4555,28 +4328,19 @@ func _update_sale_summary() -> void:
|
||||||
active_buyer.id if active_buyer != null else StringName()
|
active_buyer.id if active_buyer != null else StringName()
|
||||||
)
|
)
|
||||||
_update_sell_all_action(active_buyer, buyer_id)
|
_update_sell_all_action(active_buyer, buyer_id)
|
||||||
var offer_label: String = _get_offer_label(active_buyer)
|
|
||||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||||
var selected_count: int = selected_ids.size()
|
var selected_count: int = selected_ids.size()
|
||||||
_sell_button.text = (
|
_sell_bubble.text = (
|
||||||
"sell fish"
|
"sell fish"
|
||||||
if selected_count == 1
|
if selected_count == 1
|
||||||
else "sell %d fish" % selected_count
|
else "sell %d fish" % selected_count
|
||||||
)
|
)
|
||||||
_sell_bubble.text = _sell_button.text
|
|
||||||
if selected_count == 0:
|
if selected_count == 0:
|
||||||
_selection_summary.text = "no fish selected"
|
|
||||||
_selection_status.set_content("selected", "none")
|
|
||||||
_offer_status.set_content(offer_label, "—")
|
|
||||||
_sell_button.text = "sell fish"
|
|
||||||
_sell_button.disabled = true
|
|
||||||
_sell_bubble.disabled = true
|
_sell_bubble.disabled = true
|
||||||
_sell_bubble.text = "sell fish"
|
_sell_bubble.text = "sell fish"
|
||||||
_sell_bubble.persistent_mark = false
|
_sell_bubble.persistent_mark = false
|
||||||
_sell_bubble.refresh_ink_state()
|
_sell_bubble.refresh_ink_state()
|
||||||
_set_cooler_selection_summary_empty()
|
_set_cooler_selection_summary_empty()
|
||||||
_sale_unavailable.text = ""
|
|
||||||
_sale_unavailable.visible = false
|
|
||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
_sale_in_progress
|
_sale_in_progress
|
||||||
|
|
@ -4585,51 +4349,24 @@ func _update_sale_summary() -> void:
|
||||||
and _network_sale_service.is_local_sale_pending()
|
and _network_sale_service.is_local_sale_pending()
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
_selection_summary.text = (
|
|
||||||
"1 fish selected"
|
|
||||||
if selected_count == 1
|
|
||||||
else "%d fish selected" % selected_count
|
|
||||||
)
|
|
||||||
_selection_status.set_content("selected", str(selected_count))
|
|
||||||
_offer_status.set_content(offer_label, "pending")
|
|
||||||
_sell_button.disabled = true
|
|
||||||
_sell_bubble.disabled = true
|
_sell_bubble.disabled = true
|
||||||
_sale_unavailable.text = "Selling…"
|
_set_cooler_selection_summary(selected_count, -1)
|
||||||
_sale_unavailable.visible = true
|
|
||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
_network_sale_service == null
|
_network_sale_service == null
|
||||||
or buyer_id.is_empty()
|
or buyer_id.is_empty()
|
||||||
or not _network_sale_service.can_request_sale(buyer_id)
|
or not _network_sale_service.can_request_sale(buyer_id)
|
||||||
):
|
):
|
||||||
_selection_summary.text = (
|
|
||||||
"1 fish selected"
|
|
||||||
if selected_count == 1
|
|
||||||
else "%d fish selected" % selected_count
|
|
||||||
)
|
|
||||||
_selection_status.set_content("selected", str(selected_count))
|
|
||||||
_offer_status.set_content(offer_label, "unavailable")
|
|
||||||
_sell_button.disabled = true
|
|
||||||
_sell_bubble.disabled = true
|
_sell_bubble.disabled = true
|
||||||
_sell_bubble.persistent_mark = false
|
_sell_bubble.persistent_mark = false
|
||||||
_sell_bubble.refresh_ink_state()
|
_sell_bubble.refresh_ink_state()
|
||||||
_sale_unavailable.text = (
|
_set_cooler_selection_summary(selected_count, -1)
|
||||||
"Selling is not supported by this server."
|
|
||||||
)
|
|
||||||
_sale_unavailable.visible = true
|
|
||||||
return
|
return
|
||||||
var preview: FishSaleResultType = (
|
var preview: FishSaleResultType = (
|
||||||
_sale_service.preview_batch(selected_ids, active_buyer)
|
_sale_service.preview_batch(selected_ids, active_buyer)
|
||||||
if _sale_service != null and active_buyer != null
|
if _sale_service != null and active_buyer != null
|
||||||
else null
|
else null
|
||||||
)
|
)
|
||||||
var count_text: String = (
|
|
||||||
"1 fish selected"
|
|
||||||
if selected_count == 1
|
|
||||||
else "%d fish selected" % selected_count
|
|
||||||
)
|
|
||||||
_selection_summary.text = count_text
|
|
||||||
_selection_status.set_content("selected", str(selected_count))
|
|
||||||
if (
|
if (
|
||||||
preview != null
|
preview != null
|
||||||
and preview.payout >= 0
|
and preview.payout >= 0
|
||||||
|
|
@ -4640,24 +4377,11 @@ func _update_sale_summary() -> void:
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
_set_cooler_selection_summary(selected_count, preview.payout)
|
_set_cooler_selection_summary(selected_count, preview.payout)
|
||||||
_offer_status.set_currency_amount(offer_label, preview.payout)
|
|
||||||
else:
|
else:
|
||||||
_set_cooler_selection_summary(selected_count, -1)
|
_set_cooler_selection_summary(selected_count, -1)
|
||||||
_offer_status.set_content(offer_label, "unavailable")
|
_sell_bubble.disabled = preview == null or not preview.is_success()
|
||||||
_sell_button.disabled = preview == null or not preview.is_success()
|
|
||||||
_sell_bubble.disabled = _sell_button.disabled
|
|
||||||
_sell_bubble.persistent_mark = false
|
_sell_bubble.persistent_mark = false
|
||||||
_sell_bubble.refresh_ink_state()
|
_sell_bubble.refresh_ink_state()
|
||||||
if preview != null and preview.status == FishSaleResultType.Status.FAVORITED:
|
|
||||||
_sale_unavailable.text = (
|
|
||||||
"favorited fish cannot be sold. "
|
|
||||||
+ "remove them from the selection first."
|
|
||||||
)
|
|
||||||
elif preview != null and not preview.is_success():
|
|
||||||
_sale_unavailable.text = preview.get_message()
|
|
||||||
else:
|
|
||||||
_sale_unavailable.text = ""
|
|
||||||
_sale_unavailable.visible = not _sale_unavailable.text.is_empty()
|
|
||||||
|
|
||||||
|
|
||||||
func _update_sell_all_action(
|
func _update_sell_all_action(
|
||||||
|
|
@ -4723,13 +4447,7 @@ func _on_favorite_pressed() -> void:
|
||||||
fish_catch.catch_id,
|
fish_catch.catch_id,
|
||||||
not fish_catch.is_favorited
|
not fish_catch.is_favorited
|
||||||
):
|
):
|
||||||
_set_transaction_feedback(
|
_refresh_inventory()
|
||||||
"%s %s."
|
|
||||||
% [
|
|
||||||
fish_catch.fish.display_name,
|
|
||||||
"favorited" if fish_catch.is_favorited else "unfavorited",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_sell_pressed() -> void:
|
func _on_sell_pressed() -> void:
|
||||||
|
|
@ -4739,7 +4457,6 @@ func _on_sell_pressed() -> void:
|
||||||
func _on_sell_all_pressed() -> void:
|
func _on_sell_all_pressed() -> void:
|
||||||
var catch_ids: Array[StringName] = _get_sell_all_catch_ids()
|
var catch_ids: Array[StringName] = _get_sell_all_catch_ids()
|
||||||
if catch_ids.is_empty():
|
if catch_ids.is_empty():
|
||||||
_set_transaction_feedback("No sellable fish in the cooler.")
|
|
||||||
return
|
return
|
||||||
_begin_sale_confirmation(catch_ids)
|
_begin_sale_confirmation(catch_ids)
|
||||||
|
|
||||||
|
|
@ -4756,16 +4473,12 @@ func _begin_sale_confirmation(catch_ids: Array[StringName]) -> void:
|
||||||
and _network_sale_service.is_local_sale_pending()
|
and _network_sale_service.is_local_sale_pending()
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
_set_transaction_feedback("Selling…")
|
|
||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
_network_sale_service == null
|
_network_sale_service == null
|
||||||
or buyer_id.is_empty()
|
or buyer_id.is_empty()
|
||||||
or not _network_sale_service.can_request_sale(buyer_id)
|
or not _network_sale_service.can_request_sale(buyer_id)
|
||||||
):
|
):
|
||||||
_set_transaction_feedback(
|
|
||||||
"Selling is not supported by this server."
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
_inventory == null
|
_inventory == null
|
||||||
|
|
@ -4779,12 +4492,6 @@ func _begin_sale_confirmation(catch_ids: Array[StringName]) -> void:
|
||||||
active_buyer
|
active_buyer
|
||||||
)
|
)
|
||||||
if not preview.is_success():
|
if not preview.is_success():
|
||||||
_set_transaction_feedback(
|
|
||||||
"favorited fish cannot be sold. "
|
|
||||||
+ "remove them from the selection first."
|
|
||||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
|
||||||
else preview.get_message()
|
|
||||||
)
|
|
||||||
_refresh_inventory()
|
_refresh_inventory()
|
||||||
return
|
return
|
||||||
_confirmation_catch_ids = catch_ids.duplicate()
|
_confirmation_catch_ids = catch_ids.duplicate()
|
||||||
|
|
@ -4847,7 +4554,7 @@ func _on_confirm_sale_pressed() -> void:
|
||||||
and transaction_generation == _menu_generation
|
and transaction_generation == _menu_generation
|
||||||
and (visible or _shop_cooler_context_active)
|
and (visible or _shop_cooler_context_active)
|
||||||
):
|
):
|
||||||
_set_transaction_feedback("Selling…")
|
_update_sale_summary()
|
||||||
|
|
||||||
|
|
||||||
func _can_use_shared_world_actions() -> bool:
|
func _can_use_shared_world_actions() -> bool:
|
||||||
|
|
@ -4860,16 +4567,15 @@ func _can_use_shared_world_actions() -> bool:
|
||||||
func _on_network_sale_pending(_request_id: String) -> void:
|
func _on_network_sale_pending(_request_id: String) -> void:
|
||||||
_sale_in_progress = true
|
_sale_in_progress = true
|
||||||
if visible or _shop_cooler_context_active:
|
if visible or _shop_cooler_context_active:
|
||||||
_set_transaction_feedback("Selling…")
|
|
||||||
_update_sale_summary()
|
_update_sale_summary()
|
||||||
|
|
||||||
|
|
||||||
func _on_network_sale_finished(
|
func _on_network_sale_finished(
|
||||||
_request_id: String,
|
_request_id: String,
|
||||||
accepted: bool,
|
accepted: bool,
|
||||||
message: String,
|
_message: String,
|
||||||
catch_ids: Array[StringName],
|
catch_ids: Array[StringName],
|
||||||
payout: int,
|
_payout: int,
|
||||||
) -> void:
|
) -> void:
|
||||||
_sale_in_progress = false
|
_sale_in_progress = false
|
||||||
if accepted:
|
if accepted:
|
||||||
|
|
@ -4877,16 +4583,6 @@ func _on_network_sale_finished(
|
||||||
if not visible and not _shop_cooler_context_active:
|
if not visible and not _shop_cooler_context_active:
|
||||||
return
|
return
|
||||||
_refresh_all()
|
_refresh_all()
|
||||||
var feedback_message: String = (
|
|
||||||
"Sale complete • %s received."
|
|
||||||
% CurrencyPresentationType.bbcode_amount(payout, 20)
|
|
||||||
if accepted
|
|
||||||
else message
|
|
||||||
)
|
|
||||||
_set_transaction_feedback(feedback_message)
|
|
||||||
if not accepted:
|
|
||||||
_sale_unavailable.text = feedback_message
|
|
||||||
_sale_unavailable.visible = true
|
|
||||||
if _current_section == Section.COOLER:
|
if _current_section == Section.COOLER:
|
||||||
call_deferred("_restore_inventory_tab_focus")
|
call_deferred("_restore_inventory_tab_focus")
|
||||||
|
|
||||||
|
|
@ -4939,7 +4635,6 @@ func _revalidate_confirmation() -> void:
|
||||||
or _confirmation_buyer.id != _confirmation_buyer_id
|
or _confirmation_buyer.id != _confirmation_buyer_id
|
||||||
):
|
):
|
||||||
_close_sale_confirmation()
|
_close_sale_confirmation()
|
||||||
_set_transaction_feedback("sale selection is no longer available.")
|
|
||||||
return
|
return
|
||||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||||
_confirmation_catch_ids,
|
_confirmation_catch_ids,
|
||||||
|
|
@ -4954,12 +4649,6 @@ func _revalidate_confirmation() -> void:
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
_close_sale_confirmation()
|
_close_sale_confirmation()
|
||||||
_set_transaction_feedback(
|
|
||||||
"favorited fish cannot be sold. "
|
|
||||||
+ "remove them from the selection first."
|
|
||||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
|
||||||
else preview.get_message()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _get_buyer_display_group(
|
func _get_buyer_display_group(
|
||||||
|
|
@ -4974,10 +4663,6 @@ func _get_buyer_display_group(
|
||||||
return "buyer"
|
return "buyer"
|
||||||
|
|
||||||
|
|
||||||
func _set_transaction_feedback(message: String) -> void:
|
|
||||||
_transaction_feedback.text = "[center]%s[/center]" % message
|
|
||||||
|
|
||||||
|
|
||||||
func _get_active_sale_buyer() -> FishBuyerProfileType:
|
func _get_active_sale_buyer() -> FishBuyerProfileType:
|
||||||
if _sale_buyer_override != null and _sale_buyer_override.is_valid():
|
if _sale_buyer_override != null and _sale_buyer_override.is_valid():
|
||||||
return _sale_buyer_override
|
return _sale_buyer_override
|
||||||
|
|
@ -4994,108 +4679,6 @@ func _get_offer_label(buyer: FishBuyerProfileType) -> String:
|
||||||
return "buyer offer"
|
return "buyer offer"
|
||||||
|
|
||||||
|
|
||||||
func _refresh_logbook() -> void:
|
|
||||||
if not is_node_ready():
|
|
||||||
return
|
|
||||||
var valid_species: Array[FishDataType] = []
|
|
||||||
if _catalog != null:
|
|
||||||
for fish: FishDataType in _catalog.candidates:
|
|
||||||
if fish != null and fish.active and not fish.id.is_empty():
|
|
||||||
valid_species.append(fish)
|
|
||||||
_logbook_species = valid_species
|
|
||||||
_logbook_empty.visible = valid_species.is_empty()
|
|
||||||
_logbook_empty.text = (
|
|
||||||
"no fish catalog configured."
|
|
||||||
if valid_species.is_empty()
|
|
||||||
else "no species discovered yet."
|
|
||||||
)
|
|
||||||
_logbook_empty_state.visible = valid_species.is_empty()
|
|
||||||
_logbook_empty_state.text = (
|
|
||||||
"no fish catalog configured"
|
|
||||||
if valid_species.is_empty()
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
_refresh_logbook_page(false)
|
|
||||||
|
|
||||||
|
|
||||||
func _refresh_logbook_page(keep_transition_state: bool = true) -> void:
|
|
||||||
if not is_node_ready():
|
|
||||||
return
|
|
||||||
var entries_per_spread: int = 2 if _compact_layout else 4
|
|
||||||
_logbook_page_count = maxi(
|
|
||||||
1,
|
|
||||||
ceili(float(_logbook_species.size()) / float(entries_per_spread)),
|
|
||||||
)
|
|
||||||
_logbook_current_page = clampi(
|
|
||||||
_logbook_current_page,
|
|
||||||
0,
|
|
||||||
_logbook_page_count - 1,
|
|
||||||
)
|
|
||||||
_clear_logbook_entries()
|
|
||||||
var start_index: int = _logbook_current_page * entries_per_spread
|
|
||||||
var end_index: int = mini(
|
|
||||||
start_index + entries_per_spread,
|
|
||||||
_logbook_species.size(),
|
|
||||||
)
|
|
||||||
var visible_species: Array[FishDataType] = []
|
|
||||||
for index: int in range(start_index, end_index):
|
|
||||||
visible_species.append(_logbook_species[index])
|
|
||||||
if _compact_layout:
|
|
||||||
for fish: FishDataType in visible_species:
|
|
||||||
_left_entry_field.add_child(_create_logbook_entry(fish))
|
|
||||||
else:
|
|
||||||
var left_count: int = mini(2, visible_species.size())
|
|
||||||
for index: int in visible_species.size():
|
|
||||||
var destination: BoxContainer = (
|
|
||||||
_left_entry_field
|
|
||||||
if index < left_count
|
|
||||||
else _right_entry_field
|
|
||||||
)
|
|
||||||
destination.add_child(_create_logbook_entry(
|
|
||||||
visible_species[index]
|
|
||||||
))
|
|
||||||
_logbook_page_status.set_content(
|
|
||||||
"pages",
|
|
||||||
"%d / %d" % [_logbook_current_page + 1, _logbook_page_count],
|
|
||||||
)
|
|
||||||
_logbook_page_status.visible = _logbook_page_count > 1
|
|
||||||
_update_logbook_page_control_state(
|
|
||||||
keep_transition_state
|
|
||||||
and visible
|
|
||||||
and _current_section == Section.LOGBOOK
|
|
||||||
and not _transitioning
|
|
||||||
and not _page_transitioning
|
|
||||||
and not _logbook_page_transitioning
|
|
||||||
)
|
|
||||||
_configure_logbook_focus()
|
|
||||||
|
|
||||||
|
|
||||||
func _create_logbook_entry(fish: FishDataType) -> LogbookEntryType:
|
|
||||||
var discovered: bool = (
|
|
||||||
_collection_log != null
|
|
||||||
and _collection_log.has_discovered(fish.id)
|
|
||||||
)
|
|
||||||
var entry := LogbookEntryScene.instantiate() as LogbookEntryType
|
|
||||||
entry.set_meta("fish_id", fish.id)
|
|
||||||
entry.configure(
|
|
||||||
fish.display_texture,
|
|
||||||
fish.display_name,
|
|
||||||
fish.get_rarity_name(),
|
|
||||||
_inventory.get_count(fish.id) if _inventory != null else 0,
|
|
||||||
discovered,
|
|
||||||
)
|
|
||||||
entry.apply_compact_layout(_compact_layout)
|
|
||||||
return entry
|
|
||||||
|
|
||||||
|
|
||||||
func _clear_logbook_entries() -> void:
|
|
||||||
for field: BoxContainer in [_left_entry_field, _right_entry_field]:
|
|
||||||
for child: Node in field.get_children():
|
|
||||||
_release_focus_from(child, _logbook_tab)
|
|
||||||
field.remove_child(child)
|
|
||||||
child.queue_free()
|
|
||||||
|
|
||||||
|
|
||||||
func _release_focus_from(node: Node, fallback: Control) -> void:
|
func _release_focus_from(node: Node, fallback: Control) -> void:
|
||||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||||
if (
|
if (
|
||||||
|
|
@ -5119,164 +4702,6 @@ func _release_focus_from(node: Node, fallback: Control) -> void:
|
||||||
fallback.call_deferred("grab_focus")
|
fallback.call_deferred("grab_focus")
|
||||||
|
|
||||||
|
|
||||||
func _request_logbook_page(direction: int) -> void:
|
|
||||||
if (
|
|
||||||
_current_section != Section.LOGBOOK
|
|
||||||
or _logbook_page_transitioning
|
|
||||||
or _transitioning
|
|
||||||
or _page_transitioning
|
|
||||||
):
|
|
||||||
return
|
|
||||||
var target_page: int = clampi(
|
|
||||||
_logbook_current_page + direction,
|
|
||||||
0,
|
|
||||||
_logbook_page_count - 1,
|
|
||||||
)
|
|
||||||
if target_page == _logbook_current_page:
|
|
||||||
return
|
|
||||||
_logbook_page_generation += 1
|
|
||||||
_logbook_page_transitioning = true
|
|
||||||
_update_logbook_page_control_state(false)
|
|
||||||
var generation: int = _logbook_page_generation
|
|
||||||
var travel: float = -12.0 if direction > 0 else 12.0
|
|
||||||
_logbook_page_tween = create_tween()
|
|
||||||
_logbook_page_tween.tween_property(
|
|
||||||
_book_spread,
|
|
||||||
"modulate:a",
|
|
||||||
0.0,
|
|
||||||
LOGBOOK_PAGE_DURATION * 0.5,
|
|
||||||
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
|
||||||
_logbook_page_tween.parallel().tween_property(
|
|
||||||
_book_spread,
|
|
||||||
"position:x",
|
|
||||||
travel,
|
|
||||||
LOGBOOK_PAGE_DURATION * 0.5,
|
|
||||||
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
|
||||||
_logbook_page_tween.tween_callback(
|
|
||||||
_swap_logbook_page.bind(target_page, travel, generation)
|
|
||||||
)
|
|
||||||
_logbook_page_tween.tween_property(
|
|
||||||
_book_spread,
|
|
||||||
"modulate:a",
|
|
||||||
1.0,
|
|
||||||
LOGBOOK_PAGE_DURATION * 0.5,
|
|
||||||
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
|
||||||
_logbook_page_tween.parallel().tween_property(
|
|
||||||
_book_spread,
|
|
||||||
"position:x",
|
|
||||||
0.0,
|
|
||||||
LOGBOOK_PAGE_DURATION * 0.5,
|
|
||||||
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
|
||||||
_logbook_page_tween.finished.connect(
|
|
||||||
_finish_logbook_page_transition.bind(generation),
|
|
||||||
CONNECT_ONE_SHOT,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _swap_logbook_page(
|
|
||||||
target_page: int,
|
|
||||||
travel: float,
|
|
||||||
generation: int,
|
|
||||||
) -> void:
|
|
||||||
if (
|
|
||||||
generation != _logbook_page_generation
|
|
||||||
or not visible
|
|
||||||
or _current_section != Section.LOGBOOK
|
|
||||||
):
|
|
||||||
return
|
|
||||||
_logbook_current_page = target_page
|
|
||||||
_refresh_logbook_page(false)
|
|
||||||
_book_spread.position.x = -travel
|
|
||||||
_book_spread.modulate.a = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
func _finish_logbook_page_transition(generation: int) -> void:
|
|
||||||
if generation != _logbook_page_generation:
|
|
||||||
return
|
|
||||||
_logbook_page_tween = null
|
|
||||||
_logbook_page_transitioning = false
|
|
||||||
_book_spread.position.x = 0.0
|
|
||||||
_book_spread.modulate.a = 1.0
|
|
||||||
_update_logbook_page_control_state(
|
|
||||||
visible and _current_section == Section.LOGBOOK
|
|
||||||
)
|
|
||||||
_configure_logbook_focus()
|
|
||||||
if _logbook_next.visible and not _logbook_next.disabled:
|
|
||||||
_logbook_next.grab_focus()
|
|
||||||
elif _logbook_previous.visible and not _logbook_previous.disabled:
|
|
||||||
_logbook_previous.grab_focus()
|
|
||||||
|
|
||||||
|
|
||||||
func _cancel_logbook_page_transition(reset_visuals: bool) -> void:
|
|
||||||
_logbook_page_generation += 1
|
|
||||||
if _logbook_page_tween != null:
|
|
||||||
_logbook_page_tween.kill()
|
|
||||||
_logbook_page_tween = null
|
|
||||||
_logbook_page_transitioning = false
|
|
||||||
if reset_visuals and is_instance_valid(_book_spread):
|
|
||||||
_book_spread.position.x = 0.0
|
|
||||||
_book_spread.modulate.a = 1.0
|
|
||||||
|
|
||||||
|
|
||||||
func _update_logbook_page_control_state(interactive: bool) -> void:
|
|
||||||
var has_multiple_pages: bool = _logbook_page_count > 1
|
|
||||||
_logbook_previous.visible = has_multiple_pages
|
|
||||||
_logbook_next.visible = has_multiple_pages
|
|
||||||
_logbook_previous.disabled = (
|
|
||||||
not interactive or _logbook_current_page <= 0
|
|
||||||
)
|
|
||||||
_logbook_next.disabled = (
|
|
||||||
not interactive
|
|
||||||
or _logbook_current_page >= _logbook_page_count - 1
|
|
||||||
)
|
|
||||||
for control: BubbleButtonType in [_logbook_previous, _logbook_next]:
|
|
||||||
control.focus_mode = (
|
|
||||||
Control.FOCUS_ALL
|
|
||||||
if control.visible and not control.disabled
|
|
||||||
else Control.FOCUS_NONE
|
|
||||||
)
|
|
||||||
control.mouse_filter = (
|
|
||||||
Control.MOUSE_FILTER_STOP
|
|
||||||
if control.visible and not control.disabled
|
|
||||||
else Control.MOUSE_FILTER_IGNORE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _configure_logbook_focus() -> void:
|
|
||||||
if _current_section != Section.LOGBOOK:
|
|
||||||
return
|
|
||||||
_logbook_tab.focus_neighbor_bottom = _logbook_tab.focus_neighbor_right
|
|
||||||
if _logbook_page_count <= 1:
|
|
||||||
return
|
|
||||||
var first_control: BubbleButtonType = (
|
|
||||||
_logbook_previous
|
|
||||||
if not _logbook_previous.disabled
|
|
||||||
else _logbook_next
|
|
||||||
)
|
|
||||||
_logbook_tab.focus_neighbor_bottom = _logbook_tab.get_path_to(
|
|
||||||
first_control
|
|
||||||
)
|
|
||||||
for control: BubbleButtonType in [_logbook_previous, _logbook_next]:
|
|
||||||
control.focus_neighbor_top = control.get_path_to(_logbook_tab)
|
|
||||||
control.focus_neighbor_bottom = control.focus_neighbor_top
|
|
||||||
control.focus_neighbor_left = control.get_path_to(
|
|
||||||
_logbook_previous
|
|
||||||
)
|
|
||||||
control.focus_neighbor_right = control.get_path_to(_logbook_next)
|
|
||||||
|
|
||||||
|
|
||||||
func _advance_logbook_page_controls(delta: float) -> void:
|
|
||||||
for control: BubbleButtonType in [_logbook_previous, _logbook_next]:
|
|
||||||
if not control.visible:
|
|
||||||
continue
|
|
||||||
control.advance_emphasis(delta)
|
|
||||||
control.apply_presentation(
|
|
||||||
control.calculate_target(_motion_elapsed, 1.0, 1.0),
|
|
||||||
control.calculate_visual_scale(_motion_elapsed, 1.0),
|
|
||||||
minf(1.0, delta * 10.0),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _create_texture_frame(
|
func _create_texture_frame(
|
||||||
texture: Texture2D,
|
texture: Texture2D,
|
||||||
minimum_size: Vector2,
|
minimum_size: Vector2,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
[gd_scene load_steps=34 format=3]
|
[gd_scene load_steps=30 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://ui/player_menu.gd" id="1_menu"]
|
[ext_resource type="Script" path="res://ui/player_menu.gd" id="1_menu"]
|
||||||
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
||||||
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_content_shell.gd" id="3_shell"]
|
|
||||||
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="4_profile"]
|
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="4_profile"]
|
||||||
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_cluster.gd" id="5_cluster"]
|
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_cluster.gd" id="5_cluster"]
|
||||||
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_button.gd" id="6_bubble"]
|
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_button.gd" id="6_bubble"]
|
||||||
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_status_bubble.tscn" id="7_status"]
|
|
||||||
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_information_panel.gd" id="8_info"]
|
|
||||||
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_information_panel.tscn" id="9_info_scene"]
|
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_information_panel.tscn" id="9_info_scene"]
|
||||||
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_status_bubble.tscn" id="10_status_scene"]
|
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_status_bubble.tscn" id="10_status_scene"]
|
||||||
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_button.gd" id="11_bubble"]
|
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_button.gd" id="11_bubble"]
|
||||||
|
|
@ -33,8 +30,6 @@
|
||||||
[ext_resource type="Texture2D" path="res://ui/icons/tab_menu/close.png" id="31_close"]
|
[ext_resource type="Texture2D" path="res://ui/icons/tab_menu/close.png" id="31_close"]
|
||||||
[ext_resource type="PackedScene" path="res://ui/components/currency_amount.tscn" id="32_currency"]
|
[ext_resource type="PackedScene" path="res://ui/components/currency_amount.tscn" id="32_currency"]
|
||||||
|
|
||||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_collection"]
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_cooler_water"]
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_cooler_water"]
|
||||||
shader = ExtResource("14_water")
|
shader = ExtResource("14_water")
|
||||||
shader_parameter/decorative_bubble_1 = ExtResource("15_bubble1")
|
shader_parameter/decorative_bubble_1 = ExtResource("15_bubble1")
|
||||||
|
|
@ -814,597 +809,6 @@ offset_bottom = 720.0
|
||||||
mouse_filter = 1
|
mouse_filter = 1
|
||||||
script = ExtResource("20_players_page")
|
script = ExtResource("20_players_page")
|
||||||
|
|
||||||
[node name="BookBacking" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 58.0
|
|
||||||
offset_top = 128.0
|
|
||||||
offset_right = 1222.0
|
|
||||||
offset_bottom = 588.0
|
|
||||||
mouse_filter = 2
|
|
||||||
|
|
||||||
[node name="BookOuterMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking"]
|
|
||||||
layout_mode = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_constants/margin_left = 12
|
|
||||||
theme_override_constants/margin_top = 12
|
|
||||||
theme_override_constants/margin_right = 12
|
|
||||||
theme_override_constants/margin_bottom = 12
|
|
||||||
|
|
||||||
[node name="BookSpread" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_constants/separation = 0
|
|
||||||
|
|
||||||
[node name="LeftPage" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
mouse_filter = 2
|
|
||||||
|
|
||||||
[node name="LeftPageMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread/LeftPage"]
|
|
||||||
layout_mode = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_constants/margin_left = 22
|
|
||||||
theme_override_constants/margin_top = 18
|
|
||||||
theme_override_constants/margin_right = 24
|
|
||||||
theme_override_constants/margin_bottom = 18
|
|
||||||
|
|
||||||
[node name="LeftPageLayout" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread/LeftPage/LeftPageMargin"]
|
|
||||||
layout_mode = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_constants/separation = 8
|
|
||||||
|
|
||||||
[node name="LeftHeading" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread/LeftPage/LeftPageMargin/LeftPageLayout"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_colors/font_color = Color(0.22, 0.16, 0.09, 1)
|
|
||||||
theme_override_font_sizes/font_size = 20
|
|
||||||
text = "field notes"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="LeftEntryField" type="BoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread/LeftPage/LeftPageMargin/LeftPageLayout"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_constants/separation = 10
|
|
||||||
vertical = true
|
|
||||||
|
|
||||||
[node name="BookGutter" type="ColorRect" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(18, 0)
|
|
||||||
layout_mode = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
color = Color(0.44, 0.34, 0.21, 0.52)
|
|
||||||
|
|
||||||
[node name="RightPage" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
mouse_filter = 2
|
|
||||||
|
|
||||||
[node name="RightPageMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread/RightPage"]
|
|
||||||
layout_mode = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_constants/margin_left = 24
|
|
||||||
theme_override_constants/margin_top = 18
|
|
||||||
theme_override_constants/margin_right = 22
|
|
||||||
theme_override_constants/margin_bottom = 18
|
|
||||||
|
|
||||||
[node name="RightPageLayout" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread/RightPage/RightPageMargin"]
|
|
||||||
layout_mode = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_constants/separation = 8
|
|
||||||
|
|
||||||
[node name="RightHeading" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread/RightPage/RightPageMargin/RightPageLayout"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_colors/font_color = Color(0.22, 0.16, 0.09, 1)
|
|
||||||
theme_override_font_sizes/font_size = 20
|
|
||||||
text = "catch record"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="RightEntryField" type="BoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage/BookBacking/BookOuterMargin/BookSpread/RightPage/RightPageMargin/RightPageLayout"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_constants/separation = 10
|
|
||||||
vertical = true
|
|
||||||
|
|
||||||
[node name="LogbookEmptyState" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 402.0
|
|
||||||
offset_top = 314.0
|
|
||||||
offset_right = 878.0
|
|
||||||
offset_bottom = 366.0
|
|
||||||
mouse_filter = 2
|
|
||||||
theme_override_colors/font_color = Color(0.28, 0.21, 0.13, 0.82)
|
|
||||||
theme_override_font_sizes/font_size = 20
|
|
||||||
text = "no fish catalog configured"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
vertical_alignment = 1
|
|
||||||
|
|
||||||
[node name="LogbookPrevious" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
custom_minimum_size = Vector2(76, 72)
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 78.0
|
|
||||||
offset_top = 494.0
|
|
||||||
offset_right = 154.0
|
|
||||||
offset_bottom = 566.0
|
|
||||||
text = "previous"
|
|
||||||
script = ExtResource("11_bubble")
|
|
||||||
profile = ExtResource("4_profile")
|
|
||||||
neutral_size = Vector2(76, 72)
|
|
||||||
desktop_anchor = Vector2(116, 530)
|
|
||||||
compact_anchor = Vector2(58, 332)
|
|
||||||
compact_minimum_size = Vector2(66, 62)
|
|
||||||
minimum_font_size = 11
|
|
||||||
maximum_font_size = 15
|
|
||||||
horizontal_amplitude = 0.0
|
|
||||||
vertical_amplitude = 0.5
|
|
||||||
deformation_amplitude = 0.006
|
|
||||||
|
|
||||||
[node name="LogbookNext" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
custom_minimum_size = Vector2(76, 72)
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 1126.0
|
|
||||||
offset_top = 494.0
|
|
||||||
offset_right = 1202.0
|
|
||||||
offset_bottom = 566.0
|
|
||||||
text = "next"
|
|
||||||
script = ExtResource("11_bubble")
|
|
||||||
profile = ExtResource("4_profile")
|
|
||||||
neutral_size = Vector2(76, 72)
|
|
||||||
desktop_anchor = Vector2(1164, 530)
|
|
||||||
compact_anchor = Vector2(582, 332)
|
|
||||||
compact_minimum_size = Vector2(66, 62)
|
|
||||||
minimum_font_size = 11
|
|
||||||
maximum_font_size = 15
|
|
||||||
horizontal_amplitude = 0.0
|
|
||||||
vertical_amplitude = 0.5
|
|
||||||
motion_phase = 2.2
|
|
||||||
deformation_amplitude = 0.006
|
|
||||||
|
|
||||||
[node name="LogbookPageStatus" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/LogbookPage" instance=ExtResource("10_status_scene")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 555.0
|
|
||||||
offset_top = 524.0
|
|
||||||
offset_right = 725.0
|
|
||||||
offset_bottom = 580.0
|
|
||||||
mouse_filter = 2
|
|
||||||
|
|
||||||
[node name="MenuPanel" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 42.0
|
|
||||||
offset_top = 104.0
|
|
||||||
offset_right = 1238.0
|
|
||||||
offset_bottom = 582.0
|
|
||||||
script = ExtResource("3_shell")
|
|
||||||
profile = ExtResource("4_profile")
|
|
||||||
content_margin = 22.0
|
|
||||||
corner_radius = 74
|
|
||||||
|
|
||||||
[node name="Margin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/margin_left = 14
|
|
||||||
theme_override_constants/margin_top = 12
|
|
||||||
theme_override_constants/margin_right = 14
|
|
||||||
theme_override_constants/margin_bottom = 12
|
|
||||||
|
|
||||||
[node name="Layout" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 8
|
|
||||||
|
|
||||||
[node name="Header" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 8
|
|
||||||
|
|
||||||
[node name="WalletHeading" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Header"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "wallet"
|
|
||||||
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
|
||||||
|
|
||||||
[node name="WalletBalance" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Header" instance=ExtResource("32_currency")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
alignment = 0
|
|
||||||
|
|
||||||
[node name="Separator" type="HSeparator" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="TransactionFeedback" type="RichTextLabel" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(0, 30)
|
|
||||||
layout_mode = 2
|
|
||||||
text = ""
|
|
||||||
bbcode_enabled = true
|
|
||||||
fit_content = true
|
|
||||||
scroll_active = false
|
|
||||||
theme_override_colors/default_color = Color(1, 0.82, 0.4, 1)
|
|
||||||
theme_override_font_sizes/normal_font_size = 21
|
|
||||||
|
|
||||||
[node name="Content" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(0, 260)
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
|
|
||||||
[node name="InventorySection" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
theme_override_constants/separation = 7
|
|
||||||
|
|
||||||
[node name="StatusShoal" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 8
|
|
||||||
alignment = 1
|
|
||||||
|
|
||||||
[node name="WalletStatus" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/StatusShoal" instance=ExtResource("7_status")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="CapacityStatus" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/StatusShoal" instance=ExtResource("7_status")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="HeldValueStatus" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/StatusShoal" instance=ExtResource("7_status")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="SortBar" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 6
|
|
||||||
|
|
||||||
[node name="SortLabel" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "sort:"
|
|
||||||
|
|
||||||
[node name="SortOption" type="OptionButton" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
custom_minimum_size = Vector2(136, 104)
|
|
||||||
|
|
||||||
[node name="SortDirection" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
custom_minimum_size = Vector2(136, 104)
|
|
||||||
text = "newest first"
|
|
||||||
script = ExtResource("6_bubble")
|
|
||||||
profile = ExtResource("4_profile")
|
|
||||||
neutral_size = Vector2(136, 104)
|
|
||||||
minimum_font_size = 15
|
|
||||||
maximum_font_size = 20
|
|
||||||
horizontal_amplitude = 0.0
|
|
||||||
vertical_amplitude = 0.0
|
|
||||||
deformation_amplitude = 0.0
|
|
||||||
|
|
||||||
[node name="SortSpacer" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="CoolerCount" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
text = "0 / 12"
|
|
||||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
|
||||||
|
|
||||||
[node name="HeldValueHeading" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
text = "held fish base value"
|
|
||||||
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
|
||||||
|
|
||||||
[node name="HeldValue" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar" instance=ExtResource("32_currency")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="InventoryBody" type="BoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
theme_override_constants/separation = 8
|
|
||||||
|
|
||||||
[node name="InventoryList" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(350, 0)
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_styles/panel = SubResource("StyleBoxEmpty_collection")
|
|
||||||
|
|
||||||
[node name="InventoryListMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/margin_left = 8
|
|
||||||
theme_override_constants/margin_top = 8
|
|
||||||
theme_override_constants/margin_right = 8
|
|
||||||
theme_override_constants/margin_bottom = 8
|
|
||||||
|
|
||||||
[node name="InventoryStack" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 6
|
|
||||||
|
|
||||||
[node name="InventoryEmpty" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "your cooler is empty."
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="InventoryScroll" type="ScrollContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
horizontal_scroll_mode = 0
|
|
||||||
|
|
||||||
[node name="InventoryGrid" type="GridContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack/InventoryScroll"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
theme_override_constants/h_separation = 7
|
|
||||||
theme_override_constants/v_separation = 7
|
|
||||||
columns = 3
|
|
||||||
|
|
||||||
[node name="DetailPanel" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(210, 0)
|
|
||||||
layout_mode = 2
|
|
||||||
script = ExtResource("8_info")
|
|
||||||
profile = ExtResource("4_profile")
|
|
||||||
content_margin = 12.0
|
|
||||||
corner_radius = 96
|
|
||||||
|
|
||||||
[node name="DetailMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/margin_left = 10
|
|
||||||
theme_override_constants/margin_top = 9
|
|
||||||
theme_override_constants/margin_right = 10
|
|
||||||
theme_override_constants/margin_bottom = 9
|
|
||||||
|
|
||||||
[node name="DetailStack" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 6
|
|
||||||
|
|
||||||
[node name="DetailTexture" type="TextureRect" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(178, 100)
|
|
||||||
layout_mode = 2
|
|
||||||
expand_mode = 1
|
|
||||||
stretch_mode = 5
|
|
||||||
texture_filter = 1
|
|
||||||
|
|
||||||
[node name="DetailName" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
|
||||||
theme_override_font_sizes/font_size = 29
|
|
||||||
horizontal_alignment = 1
|
|
||||||
theme_override_colors/font_color = Color(0.035, 0.145, 0.22, 1)
|
|
||||||
|
|
||||||
[node name="DetailData" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
autowrap_mode = 2
|
|
||||||
horizontal_alignment = 1
|
|
||||||
theme_override_colors/font_color = Color(0.035, 0.145, 0.22, 1)
|
|
||||||
|
|
||||||
[node name="SelectionSummary" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
text = "no fish selected"
|
|
||||||
autowrap_mode = 2
|
|
||||||
horizontal_alignment = 1
|
|
||||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
|
||||||
|
|
||||||
[node name="ActionRow" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 8
|
|
||||||
alignment = 1
|
|
||||||
|
|
||||||
[node name="FavoriteButton" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack/ActionRow"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
disabled = true
|
|
||||||
text = "favorite"
|
|
||||||
custom_minimum_size = Vector2(112, 104)
|
|
||||||
size_flags_horizontal = 4
|
|
||||||
script = ExtResource("6_bubble")
|
|
||||||
profile = ExtResource("4_profile")
|
|
||||||
neutral_size = Vector2(112, 104)
|
|
||||||
minimum_font_size = 15
|
|
||||||
maximum_font_size = 20
|
|
||||||
horizontal_amplitude = 0.0
|
|
||||||
vertical_amplitude = 0.0
|
|
||||||
deformation_amplitude = 0.0
|
|
||||||
|
|
||||||
[node name="SellButton" type="Button" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack/ActionRow"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
disabled = true
|
|
||||||
text = "sell"
|
|
||||||
custom_minimum_size = Vector2(112, 104)
|
|
||||||
size_flags_horizontal = 4
|
|
||||||
script = ExtResource("6_bubble")
|
|
||||||
profile = ExtResource("4_profile")
|
|
||||||
neutral_size = Vector2(112, 104)
|
|
||||||
minimum_font_size = 15
|
|
||||||
maximum_font_size = 20
|
|
||||||
horizontal_amplitude = 0.0
|
|
||||||
vertical_amplitude = 0.0
|
|
||||||
deformation_amplitude = 0.0
|
|
||||||
|
|
||||||
[node name="ContextStatus" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 6
|
|
||||||
alignment = 1
|
|
||||||
|
|
||||||
[node name="SelectionStatus" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack/ContextStatus" instance=ExtResource("7_status")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(108, 78)
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="OfferStatus" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack/ContextStatus" instance=ExtResource("7_status")]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(128, 78)
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="SaleUnavailable" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
autowrap_mode = 2
|
|
||||||
horizontal_alignment = 1
|
|
||||||
theme_override_colors/font_color = Color(1, 0.702, 0.278, 1)
|
|
||||||
|
|
||||||
[node name="BagSection" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
theme_override_constants/separation = 7
|
|
||||||
|
|
||||||
[node name="BagHint" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "drag fish or hotbar-compatible items onto a slot below. right-click a slot to clear it."
|
|
||||||
horizontal_alignment = 1
|
|
||||||
autowrap_mode = 2
|
|
||||||
|
|
||||||
[node name="BagBody" type="HSplitContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
split_offset = 540
|
|
||||||
theme_override_constants/separation = 8
|
|
||||||
|
|
||||||
[node name="BagList" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(360, 0)
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Margin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/margin_left = 8
|
|
||||||
theme_override_constants/margin_top = 8
|
|
||||||
theme_override_constants/margin_right = 8
|
|
||||||
theme_override_constants/margin_bottom = 8
|
|
||||||
|
|
||||||
[node name="Stack" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 6
|
|
||||||
|
|
||||||
[node name="BagEmpty" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin/Stack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "your bag is empty."
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="BagScroll" type="ScrollContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin/Stack"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
horizontal_scroll_mode = 0
|
|
||||||
|
|
||||||
[node name="BagGrid" type="GridContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin/Stack/BagScroll"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
theme_override_constants/h_separation = 7
|
|
||||||
theme_override_constants/v_separation = 7
|
|
||||||
columns = 3
|
|
||||||
|
|
||||||
[node name="BagDetail" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(220, 0)
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Margin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/margin_left = 10
|
|
||||||
theme_override_constants/margin_top = 9
|
|
||||||
theme_override_constants/margin_right = 10
|
|
||||||
theme_override_constants/margin_bottom = 9
|
|
||||||
|
|
||||||
[node name="Stack" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 6
|
|
||||||
|
|
||||||
[node name="BagDetailTexture" type="TextureRect" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(180, 96)
|
|
||||||
layout_mode = 2
|
|
||||||
expand_mode = 1
|
|
||||||
stretch_mode = 5
|
|
||||||
texture_filter = 1
|
|
||||||
|
|
||||||
[node name="BagDetailName" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_font_sizes/font_size = 29
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="BagDetailData" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "select a bag item for details."
|
|
||||||
horizontal_alignment = 1
|
|
||||||
autowrap_mode = 2
|
|
||||||
|
|
||||||
[node name="LogbookSection" type="VBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
visible = false
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
theme_override_constants/separation = 7
|
|
||||||
|
|
||||||
[node name="LogbookEmpty" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/LogbookSection"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "no species discovered yet."
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="LogbookScroll" type="ScrollContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/LogbookSection"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
horizontal_scroll_mode = 0
|
|
||||||
|
|
||||||
[node name="LogbookGrid" type="GridContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/LogbookSection/LogbookScroll"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
theme_override_constants/h_separation = 7
|
|
||||||
theme_override_constants/v_separation = 7
|
|
||||||
columns = 4
|
|
||||||
|
|
||||||
[node name="SaleConfirmation" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
|
[node name="SaleConfirmation" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
visible = false
|
visible = false
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,12 @@ func consume_escape() -> bool:
|
||||||
func handle_controller_input(event: InputEvent) -> bool:
|
func handle_controller_input(event: InputEvent) -> bool:
|
||||||
if not _profile_active or not _profile_interactive:
|
if not _profile_active or not _profile_interactive:
|
||||||
return false
|
return false
|
||||||
if event is InputEventJoypadButton or event is InputEventJoypadMotion:
|
if (
|
||||||
|
event.is_action_pressed(&"ui_up")
|
||||||
|
or event.is_action_pressed(&"ui_down")
|
||||||
|
or event.is_action_pressed(&"ui_left")
|
||||||
|
or event.is_action_pressed(&"ui_right")
|
||||||
|
):
|
||||||
_ensure_controller_zone_focus()
|
_ensure_controller_zone_focus()
|
||||||
var cancel_pressed := _event_matches_controller_press(
|
var cancel_pressed := _event_matches_controller_press(
|
||||||
event,
|
event,
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,8 @@
|
||||||
class_name QuickRadialMenu
|
class_name QuickRadialMenu
|
||||||
extends Control
|
extends RadialActionMenu
|
||||||
|
|
||||||
signal action_selected(action_id: StringName)
|
signal action_selected(action_id: StringName)
|
||||||
|
|
||||||
const BubbleButtonScene: PackedScene = preload(
|
|
||||||
"res://ui/components/bubble_menu/bubble_button.tscn"
|
|
||||||
)
|
|
||||||
const BubbleProfile: BubbleMenuProfile = preload(
|
|
||||||
"res://ui/components/bubble_menu/bubble_menu_profile.tres"
|
|
||||||
)
|
|
||||||
const RING_RADIUS: float = 172.0
|
|
||||||
const BUBBLE_SIZE: Vector2 = Vector2(92.0, 88.0)
|
|
||||||
const CONTROLLER_SELECTION_DEADZONE: float = 0.35
|
|
||||||
const ControllerMappingManagerType = preload(
|
|
||||||
"res://settings/controller_mapping_manager.gd"
|
|
||||||
)
|
|
||||||
const ACTIONS: Array[StringName] = [
|
const ACTIONS: Array[StringName] = [
|
||||||
&"chat",
|
&"chat",
|
||||||
&"freecam",
|
&"freecam",
|
||||||
|
|
@ -27,80 +15,20 @@ const LABELS: Array[String] = [
|
||||||
]
|
]
|
||||||
const SECTOR_COUNT: int = 3
|
const SECTOR_COUNT: int = 3
|
||||||
|
|
||||||
var _buttons: Array[BubbleButton] = []
|
|
||||||
var _is_open: bool = false
|
|
||||||
var _selected_sector: int = 0
|
|
||||||
var _controller_selection_mode: bool = false
|
|
||||||
var _controller_mapping_manager: ControllerMappingManagerType
|
|
||||||
var _hud_hidden: bool = false
|
var _hud_hidden: bool = false
|
||||||
|
|
||||||
|
|
||||||
func setup_controller_mapping(
|
|
||||||
mapping_manager: ControllerMappingManagerType,
|
|
||||||
) -> void:
|
|
||||||
_controller_mapping_manager = mapping_manager
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
visible = false
|
|
||||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
||||||
for sector: int in SECTOR_COUNT:
|
|
||||||
var bubble: BubbleButton = BubbleButtonScene.instantiate() as BubbleButton
|
|
||||||
bubble.profile = BubbleProfile
|
|
||||||
bubble.focus_mode = Control.FOCUS_NONE
|
|
||||||
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
||||||
bubble.text = _label_for_sector(sector)
|
|
||||||
add_child(bubble)
|
|
||||||
_buttons.append(bubble)
|
|
||||||
_apply_selection_styles()
|
|
||||||
|
|
||||||
|
|
||||||
func handle_input(event: InputEvent, can_open: bool) -> bool:
|
|
||||||
if not event.is_action("open_quick_actions"):
|
|
||||||
return false
|
|
||||||
var key_event: InputEventKey = event as InputEventKey
|
|
||||||
if key_event != null and key_event.echo:
|
|
||||||
return false
|
|
||||||
if event.is_pressed():
|
|
||||||
if _is_open or not can_open:
|
|
||||||
return _is_open
|
|
||||||
open_menu(event is InputEventJoypadButton)
|
|
||||||
return true
|
|
||||||
if not _is_open:
|
|
||||||
return false
|
|
||||||
var selected: int = _selected_sector
|
|
||||||
close_menu()
|
|
||||||
call_deferred("_emit_selected_action", ACTIONS[selected])
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func open_menu(controller_selection: bool = false) -> void:
|
|
||||||
_is_open = true
|
|
||||||
_selected_sector = 0
|
|
||||||
_controller_selection_mode = controller_selection
|
|
||||||
visible = true
|
|
||||||
_refresh_labels()
|
|
||||||
_layout_bubbles()
|
|
||||||
_apply_selection_styles()
|
|
||||||
|
|
||||||
|
|
||||||
func close_menu() -> void:
|
|
||||||
_is_open = false
|
|
||||||
visible = false
|
|
||||||
|
|
||||||
|
|
||||||
func is_open() -> bool:
|
|
||||||
return _is_open
|
|
||||||
|
|
||||||
|
|
||||||
func set_hud_hidden(hidden: bool) -> void:
|
func set_hud_hidden(hidden: bool) -> void:
|
||||||
_hud_hidden = hidden
|
_hud_hidden = hidden
|
||||||
_refresh_labels()
|
_refresh_labels()
|
||||||
|
|
||||||
|
|
||||||
func _refresh_labels() -> void:
|
func _input_action_name() -> StringName:
|
||||||
for sector: int in _buttons.size():
|
return &"open_quick_actions"
|
||||||
_buttons[sector].text = _label_for_sector(sector)
|
|
||||||
|
|
||||||
|
func _sector_count() -> int:
|
||||||
|
return SECTOR_COUNT
|
||||||
|
|
||||||
|
|
||||||
func _label_for_sector(sector: int) -> String:
|
func _label_for_sector(sector: int) -> String:
|
||||||
|
|
@ -109,76 +37,9 @@ func _label_for_sector(sector: int) -> String:
|
||||||
return LABELS[sector]
|
return LABELS[sector]
|
||||||
|
|
||||||
|
|
||||||
|
func _emit_selected_sector(sector: int) -> void:
|
||||||
|
call_deferred("_emit_selected_action", ACTIONS[sector])
|
||||||
|
|
||||||
|
|
||||||
func _emit_selected_action(action_id: StringName) -> void:
|
func _emit_selected_action(action_id: StringName) -> void:
|
||||||
action_selected.emit(action_id)
|
action_selected.emit(action_id)
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
if not _is_open:
|
|
||||||
return
|
|
||||||
_layout_bubbles()
|
|
||||||
_update_selection()
|
|
||||||
|
|
||||||
|
|
||||||
func _layout_bubbles() -> void:
|
|
||||||
var center: Vector2 = size * 0.5
|
|
||||||
for sector: int in SECTOR_COUNT:
|
|
||||||
var angle: float = -PI * 0.5 + TAU * float(sector) / float(SECTOR_COUNT)
|
|
||||||
var bubble_center: Vector2 = center + Vector2.from_angle(angle) * RING_RADIUS
|
|
||||||
var bubble: BubbleButton = _buttons[sector]
|
|
||||||
bubble.position = bubble_center - BUBBLE_SIZE * 0.5
|
|
||||||
bubble.size = BUBBLE_SIZE
|
|
||||||
bubble.pivot_offset = BUBBLE_SIZE * 0.5
|
|
||||||
|
|
||||||
|
|
||||||
func _update_selection() -> void:
|
|
||||||
var stick: Vector2 = _get_selection_stick()
|
|
||||||
if stick.length() >= CONTROLLER_SELECTION_DEADZONE:
|
|
||||||
_select_sector_from_offset(stick)
|
|
||||||
return
|
|
||||||
if _controller_selection_mode:
|
|
||||||
return
|
|
||||||
_update_mouse_selection()
|
|
||||||
|
|
||||||
|
|
||||||
func _get_selection_stick() -> Vector2:
|
|
||||||
if _controller_mapping_manager != null:
|
|
||||||
return Vector2(
|
|
||||||
_controller_mapping_manager.get_role_axis(
|
|
||||||
ControllerMappingManagerType.ROLE_RIGHT_STICK_X
|
|
||||||
),
|
|
||||||
_controller_mapping_manager.get_role_axis(
|
|
||||||
ControllerMappingManagerType.ROLE_RIGHT_STICK_Y
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return Vector2(
|
|
||||||
Input.get_joy_axis(0, JOY_AXIS_RIGHT_X),
|
|
||||||
Input.get_joy_axis(0, JOY_AXIS_RIGHT_Y),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _update_mouse_selection() -> void:
|
|
||||||
var offset: Vector2 = get_local_mouse_position() - size * 0.5
|
|
||||||
if offset.length() < 24.0:
|
|
||||||
return
|
|
||||||
_select_sector_from_offset(offset)
|
|
||||||
|
|
||||||
|
|
||||||
func _select_sector_from_offset(offset: Vector2) -> void:
|
|
||||||
var half_sector: float = TAU / (float(SECTOR_COUNT) * 2.0)
|
|
||||||
var angle: float = fposmod(offset.angle() + PI * 0.5 + half_sector, TAU)
|
|
||||||
var sector: int = int(floor(angle / (TAU / float(SECTOR_COUNT))))
|
|
||||||
if sector == _selected_sector:
|
|
||||||
return
|
|
||||||
_selected_sector = sector
|
|
||||||
_apply_selection_styles()
|
|
||||||
|
|
||||||
|
|
||||||
func _apply_selection_styles() -> void:
|
|
||||||
for sector: int in _buttons.size():
|
|
||||||
var bubble: BubbleButton = _buttons[sector]
|
|
||||||
bubble.apply_profile()
|
|
||||||
if sector == _selected_sector:
|
|
||||||
bubble.add_theme_stylebox_override(
|
|
||||||
"normal", BubbleProfile.make_hover_style()
|
|
||||||
)
|
|
||||||
|
|
|
||||||
176
ui/radial_action_menu.gd
Normal file
176
ui/radial_action_menu.gd
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
class_name RadialActionMenu
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const BubbleButtonScene: PackedScene = preload(
|
||||||
|
"res://ui/components/bubble_menu/bubble_button.tscn"
|
||||||
|
)
|
||||||
|
const BubbleProfile: BubbleMenuProfile = preload(
|
||||||
|
"res://ui/components/bubble_menu/bubble_menu_profile.tres"
|
||||||
|
)
|
||||||
|
const RING_RADIUS: float = 172.0
|
||||||
|
const BUBBLE_SIZE: Vector2 = Vector2(92.0, 88.0)
|
||||||
|
const CONTROLLER_SELECTION_DEADZONE: float = 0.35
|
||||||
|
const ControllerMappingManagerType = preload(
|
||||||
|
"res://settings/controller_mapping_manager.gd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _buttons: Array[BubbleButton] = []
|
||||||
|
var _is_open: bool = false
|
||||||
|
var _selected_sector: int = 0
|
||||||
|
var _controller_selection_mode: bool = false
|
||||||
|
var _controller_mapping_manager: ControllerMappingManagerType
|
||||||
|
|
||||||
|
|
||||||
|
func setup_controller_mapping(
|
||||||
|
mapping_manager: ControllerMappingManagerType,
|
||||||
|
) -> void:
|
||||||
|
_controller_mapping_manager = mapping_manager
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
visible = false
|
||||||
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
for sector: int in _sector_count():
|
||||||
|
var bubble := BubbleButtonScene.instantiate() as BubbleButton
|
||||||
|
bubble.profile = BubbleProfile
|
||||||
|
bubble.focus_mode = Control.FOCUS_NONE
|
||||||
|
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
bubble.text = _label_for_sector(sector)
|
||||||
|
add_child(bubble)
|
||||||
|
_buttons.append(bubble)
|
||||||
|
_apply_selection_styles()
|
||||||
|
|
||||||
|
|
||||||
|
func handle_input(event: InputEvent, can_open: bool) -> bool:
|
||||||
|
if not event.is_action(_input_action_name()):
|
||||||
|
return false
|
||||||
|
var key_event := event as InputEventKey
|
||||||
|
if key_event != null and key_event.echo:
|
||||||
|
return false
|
||||||
|
if event.is_pressed():
|
||||||
|
if _is_open or not can_open:
|
||||||
|
return _is_open
|
||||||
|
open_menu(event is InputEventJoypadButton)
|
||||||
|
return true
|
||||||
|
if not _is_open:
|
||||||
|
return false
|
||||||
|
var selected: int = _selected_sector
|
||||||
|
close_menu()
|
||||||
|
_emit_selected_sector(selected)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func open_menu(controller_selection: bool = false) -> void:
|
||||||
|
_is_open = true
|
||||||
|
_selected_sector = _initial_sector()
|
||||||
|
_controller_selection_mode = controller_selection
|
||||||
|
visible = true
|
||||||
|
_refresh_labels()
|
||||||
|
_layout_bubbles()
|
||||||
|
_apply_selection_styles()
|
||||||
|
|
||||||
|
|
||||||
|
func close_menu() -> void:
|
||||||
|
_is_open = false
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func is_open() -> bool:
|
||||||
|
return _is_open
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh_labels() -> void:
|
||||||
|
for sector: int in _buttons.size():
|
||||||
|
_buttons[sector].text = _label_for_sector(sector)
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if not _is_open:
|
||||||
|
return
|
||||||
|
_layout_bubbles()
|
||||||
|
_update_selection()
|
||||||
|
|
||||||
|
|
||||||
|
func _layout_bubbles() -> void:
|
||||||
|
var center: Vector2 = size * 0.5
|
||||||
|
var sector_count: int = _sector_count()
|
||||||
|
for sector: int in sector_count:
|
||||||
|
var angle: float = (
|
||||||
|
-PI * 0.5 + TAU * float(sector) / float(sector_count)
|
||||||
|
)
|
||||||
|
var bubble_center: Vector2 = (
|
||||||
|
center + Vector2.from_angle(angle) * RING_RADIUS
|
||||||
|
)
|
||||||
|
var bubble: BubbleButton = _buttons[sector]
|
||||||
|
bubble.position = bubble_center - BUBBLE_SIZE * 0.5
|
||||||
|
bubble.size = BUBBLE_SIZE
|
||||||
|
bubble.pivot_offset = BUBBLE_SIZE * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
func _update_selection() -> void:
|
||||||
|
var stick: Vector2 = _get_selection_stick()
|
||||||
|
if stick.length() >= CONTROLLER_SELECTION_DEADZONE:
|
||||||
|
_select_sector_from_offset(stick)
|
||||||
|
return
|
||||||
|
if _controller_selection_mode:
|
||||||
|
return
|
||||||
|
var offset: Vector2 = get_local_mouse_position() - size * 0.5
|
||||||
|
if offset.length() >= 24.0:
|
||||||
|
_select_sector_from_offset(offset)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_selection_stick() -> Vector2:
|
||||||
|
if _controller_mapping_manager != null:
|
||||||
|
return Vector2(
|
||||||
|
_controller_mapping_manager.get_role_axis(
|
||||||
|
ControllerMappingManagerType.ROLE_RIGHT_STICK_X
|
||||||
|
),
|
||||||
|
_controller_mapping_manager.get_role_axis(
|
||||||
|
ControllerMappingManagerType.ROLE_RIGHT_STICK_Y
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return Vector2(
|
||||||
|
Input.get_joy_axis(0, JOY_AXIS_RIGHT_X),
|
||||||
|
Input.get_joy_axis(0, JOY_AXIS_RIGHT_Y),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _select_sector_from_offset(offset: Vector2) -> void:
|
||||||
|
var sector_count: int = _sector_count()
|
||||||
|
var half_sector: float = TAU / (float(sector_count) * 2.0)
|
||||||
|
var angle: float = fposmod(offset.angle() + PI * 0.5 + half_sector, TAU)
|
||||||
|
var sector: int = int(floor(angle / (TAU / float(sector_count))))
|
||||||
|
if sector == _selected_sector:
|
||||||
|
return
|
||||||
|
_selected_sector = sector
|
||||||
|
_apply_selection_styles()
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_selection_styles() -> void:
|
||||||
|
for sector: int in _buttons.size():
|
||||||
|
var bubble: BubbleButton = _buttons[sector]
|
||||||
|
bubble.apply_profile()
|
||||||
|
if sector == _selected_sector:
|
||||||
|
bubble.add_theme_stylebox_override(
|
||||||
|
"normal", BubbleProfile.make_hover_style()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _input_action_name() -> StringName:
|
||||||
|
return StringName()
|
||||||
|
|
||||||
|
|
||||||
|
func _sector_count() -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
func _initial_sector() -> int:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
func _label_for_sector(_sector: int) -> String:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
func _emit_selected_sector(_sector: int) -> void:
|
||||||
|
pass
|
||||||
1
ui/radial_action_menu.gd.uid
Normal file
1
ui/radial_action_menu.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://clthphpughvu2
|
||||||
|
|
@ -13,9 +13,6 @@ const ControllerFocusNavigationType = preload(
|
||||||
@export var back_focus_path: NodePath
|
@export var back_focus_path: NodePath
|
||||||
@export var back_entry_path: NodePath
|
@export var back_entry_path: NodePath
|
||||||
@export var maximum_layout_size: Vector2 = Vector2(720.0, 520.0)
|
@export var maximum_layout_size: Vector2 = Vector2(720.0, 520.0)
|
||||||
@export var compact_maximum_layout_size: Vector2 = Vector2.ZERO
|
|
||||||
@export var compact_width_threshold: float = 680.0
|
|
||||||
@export var compact_height_threshold: float = 500.0
|
|
||||||
@export_range(0.0, 128.0, 1.0) var transition_safe_margin: float = 24.0
|
@export_range(0.0, 128.0, 1.0) var transition_safe_margin: float = 24.0
|
||||||
|
|
||||||
var _cluster: BubbleCluster
|
var _cluster: BubbleCluster
|
||||||
|
|
@ -176,13 +173,9 @@ func _process(delta: float) -> void:
|
||||||
func _update_layout() -> void:
|
func _update_layout() -> void:
|
||||||
if not is_node_ready() or _cluster == null:
|
if not is_node_ready() or _cluster == null:
|
||||||
return
|
return
|
||||||
var compact: bool = false
|
|
||||||
var layout_maximum: Vector2 = maximum_layout_size
|
|
||||||
if compact and compact_maximum_layout_size != Vector2.ZERO:
|
|
||||||
layout_maximum = compact_maximum_layout_size
|
|
||||||
var field_size := Vector2(
|
var field_size := Vector2(
|
||||||
minf(size.x, layout_maximum.x),
|
minf(size.x, maximum_layout_size.x),
|
||||||
minf(size.y, layout_maximum.y)
|
minf(size.y, maximum_layout_size.y)
|
||||||
)
|
)
|
||||||
field_size.x = maxf(1.0, field_size.x)
|
field_size.x = maxf(1.0, field_size.x)
|
||||||
field_size.y = maxf(1.0, field_size.y)
|
field_size.y = maxf(1.0, field_size.y)
|
||||||
|
|
@ -190,7 +183,7 @@ func _update_layout() -> void:
|
||||||
if not _is_transitioning:
|
if not _is_transitioning:
|
||||||
_cluster.position = _resting_cluster_position
|
_cluster.position = _resting_cluster_position
|
||||||
_cluster.size = field_size
|
_cluster.size = field_size
|
||||||
_cluster.apply_layout(field_size, compact)
|
_cluster.apply_layout(field_size, false)
|
||||||
_configure_focus_navigation()
|
_configure_focus_navigation()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,6 @@ const KeyboardMouseMappingPanelType = preload(
|
||||||
const DialogControllerNavigationType = preload(
|
const DialogControllerNavigationType = preload(
|
||||||
"res://ui/file_dialog_controller_navigation.gd"
|
"res://ui/file_dialog_controller_navigation.gd"
|
||||||
)
|
)
|
||||||
const ControllerFocusNavigationType = preload(
|
|
||||||
"res://ui/controller_focus_navigation.gd"
|
|
||||||
)
|
|
||||||
|
|
||||||
signal applied
|
signal applied
|
||||||
signal closed
|
signal closed
|
||||||
signal closing
|
signal closing
|
||||||
|
|
@ -117,6 +113,10 @@ var _mouse_sensitivity: float = 0.005
|
||||||
var _controller_sensitivity: float = 2.5
|
var _controller_sensitivity: float = 2.5
|
||||||
var _invert_camera_y: bool = false
|
var _invert_camera_y: bool = false
|
||||||
var _on_screen_keyboard_enabled: bool = false
|
var _on_screen_keyboard_enabled: bool = false
|
||||||
|
var _chat_dock_right: bool = false
|
||||||
|
var _chat_mobile_mode: bool = false
|
||||||
|
var _paint_dock_right: bool = true
|
||||||
|
var _fullscreen_enabled: bool = false
|
||||||
var _master_volume: float = 1.0
|
var _master_volume: float = 1.0
|
||||||
var _music_volume: float = 1.0
|
var _music_volume: float = 1.0
|
||||||
var _effects_volume: float = 1.0
|
var _effects_volume: float = 1.0
|
||||||
|
|
@ -144,6 +144,11 @@ var _pending_identity_path := ""
|
||||||
var _pending_import_data: Dictionary = {}
|
var _pending_import_data: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
if what == NOTIFICATION_VISIBILITY_CHANGED and not visible:
|
||||||
|
_release_owned_focus()
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_pages = {
|
_pages = {
|
||||||
PAGE_DISPLAY: _display_page,
|
PAGE_DISPLAY: _display_page,
|
||||||
|
|
@ -235,7 +240,7 @@ func _connect_controls() -> void:
|
||||||
)
|
)
|
||||||
%ControllerMapping.pressed.connect(_open_controller_mapping)
|
%ControllerMapping.pressed.connect(_open_controller_mapping)
|
||||||
%KeyboardMapping.pressed.connect(_open_keyboard_mouse_mapping)
|
%KeyboardMapping.pressed.connect(_open_keyboard_mouse_mapping)
|
||||||
%OpenDataFolder.pressed.connect(_confirm_open_data_folder)
|
%OpenDataFolder.pressed.connect(_open_data_folder)
|
||||||
%ChangeDataFolder.pressed.connect(_choose_data_folder)
|
%ChangeDataFolder.pressed.connect(_choose_data_folder)
|
||||||
%ExportPlayerIdentity.pressed.connect(
|
%ExportPlayerIdentity.pressed.connect(
|
||||||
_choose_identity_export.bind("player")
|
_choose_identity_export.bind("player")
|
||||||
|
|
@ -348,6 +353,7 @@ func open_panel(
|
||||||
animate_gameplay_host_transitions: bool = false,
|
animate_gameplay_host_transitions: bool = false,
|
||||||
) -> void:
|
) -> void:
|
||||||
_cancel_panel_transition()
|
_cancel_panel_transition()
|
||||||
|
_release_owned_focus()
|
||||||
_presentation_mode = presentation_mode
|
_presentation_mode = presentation_mode
|
||||||
_animate_gameplay_host_transitions = animate_gameplay_host_transitions
|
_animate_gameplay_host_transitions = animate_gameplay_host_transitions
|
||||||
_settings_manager = settings_manager
|
_settings_manager = settings_manager
|
||||||
|
|
@ -359,7 +365,6 @@ func open_panel(
|
||||||
panel_visibility_changed.emit(true)
|
panel_visibility_changed.emit(true)
|
||||||
_main_panel.modulate.a = 1.0
|
_main_panel.modulate.a = 1.0
|
||||||
_main_panel.scale = Vector2.ONE
|
_main_panel.scale = Vector2.ONE
|
||||||
call_deferred("_configure_controller_navigation")
|
|
||||||
call_deferred("_focus_active_tab")
|
call_deferred("_focus_active_tab")
|
||||||
if (
|
if (
|
||||||
presentation_mode == PresentationMode.TITLE_EMBEDDED
|
presentation_mode == PresentationMode.TITLE_EMBEDDED
|
||||||
|
|
@ -472,6 +477,7 @@ func _finish_panel_close(applied_result: bool) -> void:
|
||||||
_keyboard_mouse_mapping_panel.close_panel()
|
_keyboard_mouse_mapping_panel.close_panel()
|
||||||
_main_panel.modulate.a = 1.0
|
_main_panel.modulate.a = 1.0
|
||||||
_main_panel.scale = Vector2.ONE
|
_main_panel.scale = Vector2.ONE
|
||||||
|
_release_owned_focus()
|
||||||
hide()
|
hide()
|
||||||
panel_visibility_changed.emit(false)
|
panel_visibility_changed.emit(false)
|
||||||
if applied_result:
|
if applied_result:
|
||||||
|
|
@ -521,6 +527,7 @@ func handle_back() -> void:
|
||||||
func _select_page(page_id: StringName, focus_tab: bool = true) -> void:
|
func _select_page(page_id: StringName, focus_tab: bool = true) -> void:
|
||||||
if not _pages.has(page_id):
|
if not _pages.has(page_id):
|
||||||
return
|
return
|
||||||
|
var target_tab: OrganizerTab = _tabs.get(page_id)
|
||||||
_active_page_id = page_id
|
_active_page_id = page_id
|
||||||
for candidate_id: StringName in _pages:
|
for candidate_id: StringName in _pages:
|
||||||
_pages[candidate_id].visible = candidate_id == page_id
|
_pages[candidate_id].visible = candidate_id == page_id
|
||||||
|
|
@ -530,9 +537,8 @@ func _select_page(page_id: StringName, focus_tab: bool = true) -> void:
|
||||||
)
|
)
|
||||||
if page_id == PAGE_DATA:
|
if page_id == PAGE_DATA:
|
||||||
_refresh_data_page()
|
_refresh_data_page()
|
||||||
call_deferred("_configure_controller_navigation")
|
if focus_tab and target_tab != null:
|
||||||
if focus_tab:
|
target_tab.grab_focus()
|
||||||
_tabs[page_id].call_deferred("grab_focus")
|
|
||||||
|
|
||||||
|
|
||||||
func _focus_active_tab() -> void:
|
func _focus_active_tab() -> void:
|
||||||
|
|
@ -541,30 +547,6 @@ func _focus_active_tab() -> void:
|
||||||
tab.grab_focus()
|
tab.grab_focus()
|
||||||
|
|
||||||
|
|
||||||
func _configure_controller_navigation() -> void:
|
|
||||||
if not is_node_ready() or not visible:
|
|
||||||
return
|
|
||||||
var controls: Array[Control] = []
|
|
||||||
_collect_focusable_controls(_main_panel, controls)
|
|
||||||
ControllerFocusNavigationType.configure_spatial_neighbors(controls)
|
|
||||||
|
|
||||||
|
|
||||||
func _collect_focusable_controls(
|
|
||||||
node: Node,
|
|
||||||
controls: Array[Control],
|
|
||||||
) -> void:
|
|
||||||
for child: Node in node.get_children():
|
|
||||||
var control := child as Control
|
|
||||||
if control != null and not control.is_visible_in_tree():
|
|
||||||
continue
|
|
||||||
if (
|
|
||||||
control != null
|
|
||||||
and ControllerFocusNavigationType.is_focusable(control)
|
|
||||||
):
|
|
||||||
controls.append(control)
|
|
||||||
_collect_focusable_controls(child, controls)
|
|
||||||
|
|
||||||
|
|
||||||
func _open_controller_mapping() -> void:
|
func _open_controller_mapping() -> void:
|
||||||
if _controller_mapping_manager == null:
|
if _controller_mapping_manager == null:
|
||||||
_feedback.text = "no controller mapping service is available"
|
_feedback.text = "no controller mapping service is available"
|
||||||
|
|
@ -627,38 +609,30 @@ func _refresh_data_page() -> void:
|
||||||
"copy player fingerprint · %s"
|
"copy player fingerprint · %s"
|
||||||
% NetworkIdentityCrypto.compact_suffix(fingerprint)
|
% NetworkIdentityCrypto.compact_suffix(fingerprint)
|
||||||
)
|
)
|
||||||
call_deferred("_configure_controller_navigation")
|
|
||||||
|
|
||||||
|
|
||||||
func _open_data_folder() -> void:
|
func _open_data_folder() -> void:
|
||||||
if _data_root == null or not _data_root.open_folder():
|
if (
|
||||||
_feedback.text = "could not open the data folder."
|
not is_visible_in_tree()
|
||||||
|
or _active_page_id != PAGE_DATA
|
||||||
|
or not _data_page.is_visible_in_tree()
|
||||||
func _confirm_open_data_folder() -> void:
|
or not %OpenDataFolder.is_visible_in_tree()
|
||||||
|
or get_viewport().gui_get_focus_owner() != %OpenDataFolder
|
||||||
|
):
|
||||||
|
return
|
||||||
if _data_root == null:
|
if _data_root == null:
|
||||||
_feedback.text = "the data folder is unavailable."
|
_feedback.text = "the data folder is unavailable."
|
||||||
return
|
return
|
||||||
var dialog := ConfirmationDialog.new()
|
if not _data_root.open_folder():
|
||||||
dialog.title = "open data folder?"
|
_feedback.text = "could not open the data folder."
|
||||||
dialog.ok_button_text = "open folder"
|
|
||||||
dialog.cancel_button_text = "cancel"
|
|
||||||
dialog.dialog_text = (
|
func _release_owned_focus() -> void:
|
||||||
"open this folder outside NETfishing?\n\n" + _data_root.root_path
|
if not is_inside_tree():
|
||||||
)
|
return
|
||||||
dialog.confirmed.connect(func() -> void:
|
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||||
_open_data_folder()
|
if focus_owner != null and is_ancestor_of(focus_owner):
|
||||||
dialog.queue_free()
|
focus_owner.release_focus()
|
||||||
)
|
|
||||||
dialog.canceled.connect(dialog.queue_free)
|
|
||||||
if _interface_fonts != null:
|
|
||||||
_interface_fonts.apply_utility_theme(dialog)
|
|
||||||
add_child(dialog)
|
|
||||||
dialog.popup_centered(Vector2i(620, 300))
|
|
||||||
_configure_confirmation_dialog.call_deferred(
|
|
||||||
dialog,
|
|
||||||
dialog.get_cancel_button(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _copy_player_fingerprint() -> void:
|
func _copy_player_fingerprint() -> void:
|
||||||
|
|
@ -950,6 +924,10 @@ func _apply_settings() -> void:
|
||||||
edited.controller_camera_sensitivity = _controller_sensitivity
|
edited.controller_camera_sensitivity = _controller_sensitivity
|
||||||
edited.invert_camera_y = _invert_camera_y
|
edited.invert_camera_y = _invert_camera_y
|
||||||
edited.on_screen_keyboard_enabled = _on_screen_keyboard_enabled
|
edited.on_screen_keyboard_enabled = _on_screen_keyboard_enabled
|
||||||
|
edited.chat_dock_right = _chat_dock_right
|
||||||
|
edited.chat_mobile_mode = _chat_mobile_mode
|
||||||
|
edited.paint_dock_right = _paint_dock_right
|
||||||
|
edited.fullscreen_enabled = _fullscreen_enabled
|
||||||
edited.master_volume = _master_volume
|
edited.master_volume = _master_volume
|
||||||
edited.music_volume = _music_volume
|
edited.music_volume = _music_volume
|
||||||
edited.effects_volume = _effects_volume
|
edited.effects_volume = _effects_volume
|
||||||
|
|
@ -976,16 +954,20 @@ func _load_controls() -> void:
|
||||||
_controller_sensitivity = settings.controller_camera_sensitivity
|
_controller_sensitivity = settings.controller_camera_sensitivity
|
||||||
_invert_camera_y = settings.invert_camera_y
|
_invert_camera_y = settings.invert_camera_y
|
||||||
_on_screen_keyboard_enabled = settings.on_screen_keyboard_enabled
|
_on_screen_keyboard_enabled = settings.on_screen_keyboard_enabled
|
||||||
|
_chat_dock_right = settings.chat_dock_right
|
||||||
|
_chat_mobile_mode = settings.chat_mobile_mode
|
||||||
|
_paint_dock_right = settings.paint_dock_right
|
||||||
|
_fullscreen_enabled = settings.fullscreen_enabled
|
||||||
_master_volume = settings.master_volume
|
_master_volume = settings.master_volume
|
||||||
_music_volume = settings.music_volume
|
_music_volume = settings.music_volume
|
||||||
_effects_volume = settings.effects_volume
|
_effects_volume = settings.effects_volume
|
||||||
_environment_volume = settings.environment_volume
|
_environment_volume = settings.environment_volume
|
||||||
_world_pixelation.select(settings.world_pixel_size - 1)
|
_world_pixelation.select(settings.world_pixel_size - 1)
|
||||||
_ui_pixelation.select(settings.ui_pixel_size - 1)
|
_ui_pixelation.select(settings.ui_pixel_size - 1)
|
||||||
_chat_dock.select(1 if settings.chat_dock_right else 0)
|
_chat_dock.select(1 if _chat_dock_right else 0)
|
||||||
_chat_mode.select(1 if settings.chat_mobile_mode else 0)
|
_chat_mode.select(1 if _chat_mobile_mode else 0)
|
||||||
_paint_dock.select(1 if settings.paint_dock_right else 0)
|
_paint_dock.select(1 if _paint_dock_right else 0)
|
||||||
_fullscreen_toggle.set_pressed_no_signal(settings.fullscreen_enabled)
|
_fullscreen_toggle.set_pressed_no_signal(_fullscreen_enabled)
|
||||||
_master_volume_slider.set_value_no_signal(_master_volume * 100.0)
|
_master_volume_slider.set_value_no_signal(_master_volume * 100.0)
|
||||||
_music_volume_slider.set_value_no_signal(_music_volume * 100.0)
|
_music_volume_slider.set_value_no_signal(_music_volume * 100.0)
|
||||||
_effects_volume_slider.set_value_no_signal(_effects_volume * 100.0)
|
_effects_volume_slider.set_value_no_signal(_effects_volume * 100.0)
|
||||||
|
|
@ -1084,43 +1066,22 @@ func _set_ui_pixelation(pixel_size: int) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _on_chat_dock_selected(index: int) -> void:
|
func _on_chat_dock_selected(index: int) -> void:
|
||||||
_set_presentation_toggle(
|
_chat_dock_right = _chat_dock.get_item_id(index) == 1
|
||||||
&"chat_dock_right",
|
|
||||||
_chat_dock.get_item_id(index) == 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_chat_mode_selected(index: int) -> void:
|
func _on_chat_mode_selected(index: int) -> void:
|
||||||
_set_presentation_toggle(
|
_chat_mobile_mode = _chat_mode.get_item_id(index) == 1
|
||||||
&"chat_mobile_mode",
|
|
||||||
_chat_mode.get_item_id(index) == 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_paint_dock_selected(index: int) -> void:
|
func _on_paint_dock_selected(index: int) -> void:
|
||||||
_set_presentation_toggle(
|
_paint_dock_right = _paint_dock.get_item_id(index) == 1
|
||||||
&"paint_dock_right",
|
|
||||||
_paint_dock.get_item_id(index) == 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _set_fullscreen(enabled: bool) -> void:
|
func _set_fullscreen(enabled: bool) -> void:
|
||||||
_set_presentation_toggle(&"fullscreen_enabled", enabled)
|
_fullscreen_enabled = enabled
|
||||||
_refresh_value_labels()
|
_refresh_value_labels()
|
||||||
|
|
||||||
|
|
||||||
func _set_presentation_toggle(
|
|
||||||
property_name: StringName,
|
|
||||||
enabled: bool,
|
|
||||||
) -> void:
|
|
||||||
if _settings_manager == null:
|
|
||||||
return
|
|
||||||
var edited: PlayerSettings = _settings_manager.current_settings.copy()
|
|
||||||
edited.set(property_name, enabled)
|
|
||||||
if not _settings_manager.apply_settings(edited):
|
|
||||||
_feedback.text = "failed to save display setting."
|
|
||||||
|
|
||||||
|
|
||||||
func _on_mouse_sensitivity_changed(value: float) -> void:
|
func _on_mouse_sensitivity_changed(value: float) -> void:
|
||||||
_mouse_sensitivity = clampf(
|
_mouse_sensitivity = clampf(
|
||||||
value,
|
value,
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(115, 52)
|
custom_minimum_size = Vector2(115, 52)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_bottom = NodePath("../../ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/FolderRow/OpenDataFolder")
|
||||||
text = "data"
|
text = "data"
|
||||||
script = ExtResource("3_tab")
|
script = ExtResource("3_tab")
|
||||||
palette_index = 1
|
palette_index = 1
|
||||||
|
|
@ -417,6 +418,7 @@ unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(330, 48)
|
custom_minimum_size = Vector2(330, 48)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_bottom = NodePath("../../BindingButtons/ControllerMapping")
|
||||||
toggle_mode = true
|
toggle_mode = true
|
||||||
text = "off"
|
text = "off"
|
||||||
|
|
||||||
|
|
@ -433,6 +435,8 @@ unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(300, 52)
|
custom_minimum_size = Vector2(300, 52)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_right = NodePath("../KeyboardMapping")
|
||||||
|
focus_neighbor_top = NodePath("../../Grid/OnScreenKeyboardToggle")
|
||||||
text = "controller binds"
|
text = "controller binds"
|
||||||
|
|
||||||
[node name="KeyboardMapping" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/ControlsPage/BindingButtons"]
|
[node name="KeyboardMapping" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/ControlsPage/BindingButtons"]
|
||||||
|
|
@ -440,6 +444,8 @@ unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(300, 52)
|
custom_minimum_size = Vector2(300, 52)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_left = NodePath("../ControllerMapping")
|
||||||
|
focus_neighbor_top = NodePath("../../Grid/OnScreenKeyboardToggle")
|
||||||
text = "keyboard binds"
|
text = "keyboard binds"
|
||||||
|
|
||||||
[node name="AccessibilityPage" type="VBoxContainer" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack"]
|
[node name="AccessibilityPage" type="VBoxContainer" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack"]
|
||||||
|
|
@ -554,6 +560,9 @@ custom_minimum_size = Vector2(0, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_right = NodePath("../ChangeDataFolder")
|
||||||
|
focus_neighbor_top = NodePath("../../../../../../../../TabBar/DataTab")
|
||||||
|
focus_neighbor_bottom = NodePath("../../CopyPlayerFingerprint")
|
||||||
text = "open data folder"
|
text = "open data folder"
|
||||||
|
|
||||||
[node name="ChangeDataFolder" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/FolderRow"]
|
[node name="ChangeDataFolder" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/FolderRow"]
|
||||||
|
|
@ -562,6 +571,9 @@ custom_minimum_size = Vector2(0, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_left = NodePath("../OpenDataFolder")
|
||||||
|
focus_neighbor_top = NodePath("../../../../../../../../TabBar/DataTab")
|
||||||
|
focus_neighbor_bottom = NodePath("../../CopyPlayerFingerprint")
|
||||||
text = "change data folder"
|
text = "change data folder"
|
||||||
|
|
||||||
[node name="CopyPlayerFingerprint" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons"]
|
[node name="CopyPlayerFingerprint" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons"]
|
||||||
|
|
@ -569,6 +581,8 @@ unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(0, 44)
|
custom_minimum_size = Vector2(0, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_top = NodePath("../FolderRow/OpenDataFolder")
|
||||||
|
focus_neighbor_bottom = NodePath("../PlayerIdentityRow/ExportPlayerIdentity")
|
||||||
text = "copy player fingerprint"
|
text = "copy player fingerprint"
|
||||||
|
|
||||||
[node name="PlayerIdentityRow" type="HBoxContainer" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons"]
|
[node name="PlayerIdentityRow" type="HBoxContainer" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons"]
|
||||||
|
|
@ -581,6 +595,9 @@ custom_minimum_size = Vector2(0, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_right = NodePath("../ImportPlayerIdentity")
|
||||||
|
focus_neighbor_top = NodePath("../../CopyPlayerFingerprint")
|
||||||
|
focus_neighbor_bottom = NodePath("../../HostIdentityRow/ExportHostIdentity")
|
||||||
text = "export player identity"
|
text = "export player identity"
|
||||||
|
|
||||||
[node name="ImportPlayerIdentity" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/PlayerIdentityRow"]
|
[node name="ImportPlayerIdentity" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/PlayerIdentityRow"]
|
||||||
|
|
@ -589,6 +606,9 @@ custom_minimum_size = Vector2(0, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_left = NodePath("../ExportPlayerIdentity")
|
||||||
|
focus_neighbor_top = NodePath("../../CopyPlayerFingerprint")
|
||||||
|
focus_neighbor_bottom = NodePath("../../HostIdentityRow/ImportHostIdentity")
|
||||||
text = "import player identity"
|
text = "import player identity"
|
||||||
|
|
||||||
[node name="HostIdentityRow" type="HBoxContainer" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons"]
|
[node name="HostIdentityRow" type="HBoxContainer" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons"]
|
||||||
|
|
@ -601,6 +621,8 @@ custom_minimum_size = Vector2(0, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_right = NodePath("../ImportHostIdentity")
|
||||||
|
focus_neighbor_top = NodePath("../../PlayerIdentityRow/ExportPlayerIdentity")
|
||||||
text = "export host identity"
|
text = "export host identity"
|
||||||
|
|
||||||
[node name="ImportHostIdentity" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/HostIdentityRow"]
|
[node name="ImportHostIdentity" type="Button" parent="MainPanel/OuterMargin/Layout/ContentPanel/ContentMargin/PageStack/DataPage/DataScroll/DataButtons/HostIdentityRow"]
|
||||||
|
|
@ -609,6 +631,8 @@ custom_minimum_size = Vector2(0, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
focus_mode = 2
|
focus_mode = 2
|
||||||
|
focus_neighbor_left = NodePath("../ExportHostIdentity")
|
||||||
|
focus_neighbor_top = NodePath("../../PlayerIdentityRow/ImportPlayerIdentity")
|
||||||
text = "import host identity"
|
text = "import host identity"
|
||||||
|
|
||||||
[node name="FooterGroup" type="MarginContainer" parent="MainPanel/OuterMargin/Layout"]
|
[node name="FooterGroup" type="MarginContainer" parent="MainPanel/OuterMargin/Layout"]
|
||||||
|
|
|
||||||
|
|
@ -128,8 +128,6 @@ func reset_controller_zone() -> void:
|
||||||
func handle_controller_input(event: InputEvent) -> bool:
|
func handle_controller_input(event: InputEvent) -> bool:
|
||||||
if not _active or not _interactive:
|
if not _active or not _interactive:
|
||||||
return false
|
return false
|
||||||
if _handle_controller_scroll(event):
|
|
||||||
return true
|
|
||||||
if event.is_action_pressed("ui_cancel"):
|
if event.is_action_pressed("ui_cancel"):
|
||||||
if _controller_zone == ControllerZone.CLAIMS:
|
if _controller_zone == ControllerZone.CLAIMS:
|
||||||
_controller_zone = ControllerZone.TABS
|
_controller_zone = ControllerZone.TABS
|
||||||
|
|
@ -160,37 +158,10 @@ func handle_controller_input(event: InputEvent) -> bool:
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
func _handle_controller_scroll(event: InputEvent) -> bool:
|
|
||||||
var motion := event as InputEventJoypadMotion
|
|
||||||
if motion == null or _jobs_scroll == null:
|
|
||||||
return false
|
|
||||||
var uses_right_y: bool = (
|
|
||||||
_controller_mapping_manager.event_uses_role(
|
|
||||||
event,
|
|
||||||
ControllerMappingManager.ROLE_RIGHT_STICK_Y,
|
|
||||||
)
|
|
||||||
if _controller_mapping_manager != null
|
|
||||||
else motion.axis == JOY_AXIS_RIGHT_Y
|
|
||||||
)
|
|
||||||
if not uses_right_y:
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
|
||||||
if _active and _jobs != null:
|
if _active and _jobs != null:
|
||||||
_refresh_label.text = _daily_refresh_text()
|
_refresh_label.text = _daily_refresh_text()
|
||||||
_refresh_forecast(false)
|
_refresh_forecast(false)
|
||||||
var right_y: float = (
|
|
||||||
_controller_mapping_manager.get_role_axis(
|
|
||||||
ControllerMappingManager.ROLE_RIGHT_STICK_Y
|
|
||||||
)
|
|
||||||
if _controller_mapping_manager != null
|
|
||||||
else Input.get_joy_axis(0, JOY_AXIS_RIGHT_Y)
|
|
||||||
)
|
|
||||||
if _jobs_scroll != null and absf(right_y) >= 0.18:
|
|
||||||
_jobs_scroll.scroll_vertical += roundi(right_y * 320.0 * delta)
|
|
||||||
|
|
||||||
|
|
||||||
func _build_laptop() -> void:
|
func _build_laptop() -> void:
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@ const UIReferencePresentationType = preload(
|
||||||
const ControllerFocusPresentationType = preload(
|
const ControllerFocusPresentationType = preload(
|
||||||
"res://ui/controller_focus_presentation.gd"
|
"res://ui/controller_focus_presentation.gd"
|
||||||
)
|
)
|
||||||
const ControllerFocusRecoveryType = preload(
|
|
||||||
"res://ui/controller_focus_recovery.gd"
|
|
||||||
)
|
|
||||||
const ControllerMappingManagerType = preload(
|
const ControllerMappingManagerType = preload(
|
||||||
"res://settings/controller_mapping_manager.gd"
|
"res://settings/controller_mapping_manager.gd"
|
||||||
)
|
)
|
||||||
|
|
@ -50,8 +47,6 @@ func _ready() -> void:
|
||||||
Callable(_on_screen_keyboard, "request_for_control"),
|
Callable(_on_screen_keyboard, "request_for_control"),
|
||||||
Callable(_on_screen_keyboard, "is_open"),
|
Callable(_on_screen_keyboard, "is_open"),
|
||||||
)
|
)
|
||||||
var controller_focus_recovery := ControllerFocusRecoveryType.new()
|
|
||||||
_ui_root.add_child(controller_focus_recovery)
|
|
||||||
var controller_focus_presentation := ControllerFocusPresentationType.new()
|
var controller_focus_presentation := ControllerFocusPresentationType.new()
|
||||||
_ui_root.add_child(controller_focus_presentation)
|
_ui_root.add_child(controller_focus_presentation)
|
||||||
var root_viewport: Viewport = get_viewport()
|
var root_viewport: Viewport = get_viewport()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue