diff --git a/tests/controller_menu_accessibility_validation.gd b/tests/controller_menu_accessibility_validation.gd index b6f9c43..8eeaf0f 100644 --- a/tests/controller_menu_accessibility_validation.gd +++ b/tests/controller_menu_accessibility_validation.gd @@ -172,12 +172,97 @@ func _validate_data_settings_navigation() -> void: root.add_child(panel) await process_frame panel.show() - panel.call("_select_page", &"data", false) for _frame: int in 2: await process_frame + var data_tab := panel.get_node("%DataTab") as Button + var open_data_folder := panel.get_node("%OpenDataFolder") as Button + var content_panel := panel.get_node("%ContentPanel") as PanelContainer + var tab_overlap: float = ( + data_tab.get_global_rect().end.y + - content_panel.get_global_rect().position.y + ) + _expect( + is_equal_approx(tab_overlap, data_tab.size.y * 0.5), + "Settings content does not cover the lower half of its organizer tabs.", + ) + var open_activation := {"count": 0} + open_data_folder.pressed.connect(func() -> void: + open_activation["count"] = int(open_activation["count"]) + 1 + ) + data_tab.grab_focus() + var accept_press := InputEventAction.new() + accept_press.action = &"ui_accept" + accept_press.pressed = true + Input.parse_input_event(accept_press) + await process_frame + var accept_release := InputEventAction.new() + accept_release.action = &"ui_accept" + accept_release.pressed = false + Input.parse_input_event(accept_release) + for _frame: int in 2: + await process_frame + _expect( + panel.get_active_page_id() == &"data", + "Selecting the Data tab did not open the Data page.", + ) + _expect( + int(open_activation["count"]) == 0, + "Selecting the Data tab also activated Open Data Folder.", + ) + _expect( + root.gui_get_focus_owner() == data_tab, + "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() + _expect( + root.gui_get_focus_owner() == open_data_folder, + "Open Data Folder did not accept controller focus.", + ) + _expect( + not open_data_folder.disabled, + "Open Data Folder is unexpectedly disabled.", + ) + var action_press := InputEventAction.new() + action_press.action = &"ui_accept" + action_press.pressed = true + Input.parse_input_event(action_press) + await process_frame + var action_release := InputEventAction.new() + action_release.action = &"ui_accept" + action_release.pressed = false + Input.parse_input_event(action_release) + for _frame: int in 2: + await process_frame + _expect( + int(open_activation["count"]) == 1, + ( + "Explicitly accepting Open Data Folder pressed it %d times." + % int(open_activation["count"]) + ), + ) + var confirmations: Array[Node] = panel.find_children( + "*", "ConfirmationDialog", true, false + ) + _expect( + confirmations.size() == 1 + and (confirmations.front() as ConfirmationDialog).visible, + "Explicitly accepting Open Data Folder did not request confirmation.", + ) + if not confirmations.is_empty(): + var confirmation := confirmations.front() as ConfirmationDialog + _expect( + confirmation.gui_get_focus_owner() + == confirmation.get_cancel_button(), + "Open Data Folder confirmation did not default to Cancel.", + ) + confirmation.queue_free() var controls: Array[Control] = [ - panel.get_node("%DataTab") as Control, - panel.get_node("%OpenDataFolder") as Control, + data_tab, + open_data_folder, panel.get_node("%ChangeDataFolder") as Control, panel.get_node("%CopyPlayerFingerprint") as Control, panel.get_node("%ExportPlayerIdentity") as Control, @@ -199,6 +284,7 @@ func _validate_data_settings_navigation() -> void: "Settings children still contain bubble controls.", ) panel.queue_free() + test_data_root.queue_free() await process_frame diff --git a/ui/settings_panel.gd b/ui/settings_panel.gd index e1f2039..ee8c049 100644 --- a/ui/settings_panel.gd +++ b/ui/settings_panel.gd @@ -235,7 +235,7 @@ func _connect_controls() -> void: ) %ControllerMapping.pressed.connect(_open_controller_mapping) %KeyboardMapping.pressed.connect(_open_keyboard_mouse_mapping) - %OpenDataFolder.pressed.connect(_open_data_folder) + %OpenDataFolder.pressed.connect(_confirm_open_data_folder) %ChangeDataFolder.pressed.connect(_choose_data_folder) %ExportPlayerIdentity.pressed.connect( _choose_identity_export.bind("player") @@ -635,6 +635,32 @@ func _open_data_folder() -> void: _feedback.text = "could not open the data folder." +func _confirm_open_data_folder() -> void: + if _data_root == null: + _feedback.text = "the data folder is unavailable." + return + var dialog := ConfirmationDialog.new() + dialog.title = "open data folder?" + dialog.ok_button_text = "open folder" + dialog.cancel_button_text = "cancel" + dialog.dialog_text = ( + "open this folder outside NETfishing?\n\n" + _data_root.root_path + ) + dialog.confirmed.connect(func() -> void: + _open_data_folder() + dialog.queue_free() + ) + 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: var fingerprint: String = ( _player_identity.fingerprint if _player_identity != null else "" diff --git a/ui/settings_panel.tscn b/ui/settings_panel.tscn index 2aa46a6..01dcccd 100644 --- a/ui/settings_panel.tscn +++ b/ui/settings_panel.tscn @@ -30,9 +30,10 @@ theme_override_constants/margin_bottom = 18 [node name="Layout" type="VBoxContainer" parent="MainPanel/OuterMargin"] layout_mode = 2 -theme_override_constants/separation = 8 +theme_override_constants/separation = -26 [node name="Heading" type="Label" parent="MainPanel/OuterMargin/Layout"] +custom_minimum_size = Vector2(0, 66) layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "settings" @@ -90,7 +91,7 @@ palette_index = 1 [node name="ContentPanel" type="PanelContainer" parent="MainPanel/OuterMargin/Layout"] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 440) +custom_minimum_size = Vector2(0, 474) layout_mode = 2 size_flags_vertical = 3 @@ -610,7 +611,15 @@ size_flags_horizontal = 3 focus_mode = 2 text = "import host identity" -[node name="SettingsFeedback" type="Label" parent="MainPanel/OuterMargin/Layout"] +[node name="FooterGroup" type="MarginContainer" parent="MainPanel/OuterMargin/Layout"] +layout_mode = 2 +theme_override_constants/margin_top = 34 + +[node name="FooterLayout" type="VBoxContainer" parent="MainPanel/OuterMargin/Layout/FooterGroup"] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="SettingsFeedback" type="Label" parent="MainPanel/OuterMargin/Layout/FooterGroup/FooterLayout"] unique_name_in_owner = true custom_minimum_size = Vector2(0, 24) layout_mode = 2 @@ -619,19 +628,19 @@ horizontal_alignment = 1 vertical_alignment = 1 text_overrun_behavior = 3 -[node name="Footer" type="HBoxContainer" parent="MainPanel/OuterMargin/Layout"] +[node name="Footer" type="HBoxContainer" parent="MainPanel/OuterMargin/Layout/FooterGroup/FooterLayout"] layout_mode = 2 theme_override_constants/separation = 12 alignment = 2 -[node name="ApplySettingsButton" type="Button" parent="MainPanel/OuterMargin/Layout/Footer"] +[node name="ApplySettingsButton" type="Button" parent="MainPanel/OuterMargin/Layout/FooterGroup/FooterLayout/Footer"] unique_name_in_owner = true custom_minimum_size = Vector2(180, 44) layout_mode = 2 focus_mode = 2 text = "apply" -[node name="SettingsBackButton" type="Button" parent="MainPanel/OuterMargin/Layout/Footer"] +[node name="SettingsBackButton" type="Button" parent="MainPanel/OuterMargin/Layout/FooterGroup/FooterLayout/Footer"] unique_name_in_owner = true custom_minimum_size = Vector2(180, 44) layout_mode = 2