diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index a9673cf79b3..c40b4c79ed1 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1675,6 +1675,7 @@ ProjectSettings::ProjectSettings() { #endif GLOBAL_DEF_BASIC("gui/common/snap_controls_to_pixels", true); + GLOBAL_DEF("gui/common/always_show_focus_state", false); GLOBAL_DEF_BASIC("gui/fonts/dynamic_fonts/use_oversampling", true); GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/vsync/frame_queue_size", PROPERTY_HINT_RANGE, "2,3,1"), 2); diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index d9349d55b76..9fba54b7a60 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -11,7 +11,7 @@ Godot propagates input events via viewports. Each [Viewport] is responsible for propagating [InputEvent]s to their child nodes. As the [member SceneTree.root] is a [Window], this already happens automatically for all UI elements in your game. Input events are propagated through the [SceneTree] from the root node to all child nodes by calling [method Node._input]. For UI elements specifically, it makes more sense to override the virtual method [method _gui_input], which filters out unrelated input events, such as by checking z-order, [member mouse_filter], focus, or if the event was inside of the control's bounding box. Call [method accept_event] so no other node receives the event. Once you accept an input, it becomes handled so [method Node._unhandled_input] will not process it. - Only one [Control] node can be in focus. Only the node in focus will receive events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus. + Only one [Control] node can be in focus. Only the node in focus will receive events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus. Focus will not be represented visually if gained via mouse/touch input, only appearing with keyboard/gamepad input (for accessibility), or via [method grab_focus]. Sets [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] to tell a [Control] node to ignore mouse or touch events. You'll need it if you place an icon on top of a button. [Theme] resources change the control's appearance. The [member theme] of a [Control] node affects all of its direct and indirect children (as long as a chain of controls is uninterrupted). To override some of the theme items, call one of the [code]add_theme_*_override[/code] methods, like [method add_theme_font_override]. You can also override theme items in the Inspector. [b]Note:[/b] Theme items are [i]not[/i] [Object] properties. This means you can't access their values using [method Object.get] and [method Object.set]. Instead, use the [code]get_theme_*[/code] and [code]add_theme_*_override[/code] methods provided by this class. @@ -618,15 +618,19 @@ + Steal the focus from another control and become the focused control (see [member focus_mode]). + If [param hide_focus] is [code]true[/code], the control will not visually show its focused state. Has no effect if [member ProjectSettings.gui/common/always_show_focus_state] is set to [code]true[/code]. [b]Note:[/b] Using this method together with [method Callable.call_deferred] makes it more reliable, especially when called inside [method Node._ready]. + Returns [code]true[/code] if this is the current focused control. See [member focus_mode]. + If [param ignore_hidden_focus] is [code]true[/code], controls that have their focus hidden will always return [code]false[/code]. Hidden focus happens automatically when controls gain focus via mouse input, or manually using [method grab_focus] with [code]hide_focus[/code] set to [code]true[/code]. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 59362cb0f69..5a1601db1f7 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1177,6 +1177,9 @@ Override for [member filesystem/import/fbx2gltf/enabled] on the Web where FBX2glTF can't easily be accessed from Godot. + + If [code]true[/code], [Control]s will always show if they're focused, even if said focus was gained via mouse/touch input. + Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden. diff --git a/editor/animation/animation_bezier_editor.cpp b/editor/animation/animation_bezier_editor.cpp index 18f8c8cbecb..d41369ddb92 100644 --- a/editor/animation/animation_bezier_editor.cpp +++ b/editor/animation/animation_bezier_editor.cpp @@ -303,7 +303,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) { const int h_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationBezierTrackEdit")); const int v_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationBezierTrackEdit")); - if (has_focus()) { + if (has_focus(true)) { draw_rect(Rect2(Point2(), get_size()), focus_color, false, Math::round(EDSCALE)); } diff --git a/editor/animation/animation_blend_space_2d_editor.cpp b/editor/animation/animation_blend_space_2d_editor.cpp index f1701c477f2..c27efaa3eef 100644 --- a/editor/animation/animation_blend_space_2d_editor.cpp +++ b/editor/animation/animation_blend_space_2d_editor.cpp @@ -448,7 +448,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { Size2 s = blend_space_draw->get_size(); - if (blend_space_draw->has_focus()) { + if (blend_space_draw->has_focus(true)) { Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); blend_space_draw->draw_rect(Rect2(Point2(), s), color, false); } diff --git a/editor/animation/animation_state_machine_editor.cpp b/editor/animation/animation_state_machine_editor.cpp index 6232150940b..4b3297e6aff 100644 --- a/editor/animation/animation_state_machine_editor.cpp +++ b/editor/animation/animation_state_machine_editor.cpp @@ -952,7 +952,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { travel_path = playback->get_travel_path(); } - if (state_machine_draw->has_focus()) { + if (state_machine_draw->has_focus(true)) { state_machine_draw->draw_rect(Rect2(Point2(), state_machine_draw->get_size()), theme_cache.focus_color, false); } int sep = 3 * EDSCALE; diff --git a/editor/docks/filesystem_dock.cpp b/editor/docks/filesystem_dock.cpp index 020d37b4701..4fb2c0552d8 100644 --- a/editor/docks/filesystem_dock.cpp +++ b/editor/docks/filesystem_dock.cpp @@ -775,13 +775,13 @@ void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_fa item = item->get_next(); } if (p_grab_focus) { - tree->grab_focus(); + tree->grab_focus(true); } } else { (*directory_ptr)->select(0); _update_file_list(false); if (p_grab_focus) { - files->grab_focus(); + files->grab_focus(true); } } tree->ensure_cursor_is_visible(); @@ -1397,7 +1397,7 @@ void FileSystemDock::_update_history() { if (tree->is_visible()) { _update_tree(get_uncollapsed_paths()); - tree->grab_focus(); + tree->grab_focus(true); } if (file_list_vb->is_visible()) { @@ -3537,7 +3537,7 @@ void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button if (p_button != MouseButton::RIGHT) { return; } - tree->grab_focus(); + tree->grab_focus(true); // Right click is pressed in the tree. Vector paths = _tree_get_selected(false); diff --git a/editor/docks/scene_tree_dock.cpp b/editor/docks/scene_tree_dock.cpp index 0abeaac0451..6b3ce8cbddb 100644 --- a/editor/docks/scene_tree_dock.cpp +++ b/editor/docks/scene_tree_dock.cpp @@ -1553,7 +1553,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { editor_selection->clear(); editor_selection->add_node(new_node); - scene_tree->get_scene_tree()->grab_focus(); + scene_tree->get_scene_tree()->grab_focus(true); } break; default: { @@ -3146,7 +3146,7 @@ void SceneTreeDock::_create() { undo_redo->commit_action(); } - scene_tree->get_scene_tree()->grab_focus(); + scene_tree->get_scene_tree()->grab_focus(true); } void SceneTreeDock::replace_node(Node *p_node, Node *p_by_node) { diff --git a/editor/gui/code_editor.cpp b/editor/gui/code_editor.cpp index b49be128670..779bdd29e40 100644 --- a/editor/gui/code_editor.cpp +++ b/editor/gui/code_editor.cpp @@ -599,10 +599,10 @@ void FindReplaceBar::_show_search(bool p_with_replace, bool p_show_only) { if (focus_replace) { search_text->deselect(); - callable_mp((Control *)replace_text, &Control::grab_focus).call_deferred(); + callable_mp((Control *)replace_text, &Control::grab_focus).call_deferred(false); } else { replace_text->deselect(); - callable_mp((Control *)search_text, &Control::grab_focus).call_deferred(); + callable_mp((Control *)search_text, &Control::grab_focus).call_deferred(false); } if (on_one_line) { diff --git a/editor/gui/create_dialog.cpp b/editor/gui/create_dialog.cpp index 9c8a6305bfd..d0208e6733e 100644 --- a/editor/gui/create_dialog.cpp +++ b/editor/gui/create_dialog.cpp @@ -563,7 +563,7 @@ void CreateDialog::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { - callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(); // Still not visible. + callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(false); // Still not visible. search_box->select_all(); } else { EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "create_new_node", Rect2(get_position(), get_size())); diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index ce322c3d00a..28aafdc95ec 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -482,9 +482,9 @@ void EditorFileDialog::_post_popup() { set_current_dir(current); if (mode == FILE_MODE_SAVE_FILE) { - file->grab_focus(); + file->grab_focus(true); } else { - item_list->grab_focus(); + item_list->grab_focus(true); } bool is_open_directory_mode = mode == FILE_MODE_OPEN_DIR; diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index cbec2af2c7d..79494ee1656 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -162,7 +162,7 @@ void EditorSpinSlider::_grab_end() { grabbing_spinner = false; emit_signal("ungrabbed"); } else { - _focus_entered(); + _focus_entered(true); } grabbing_spinner_attempt = false; @@ -204,7 +204,7 @@ void EditorSpinSlider::_grabber_gui_input(const Ref &p_event) { grabbing_ratio = get_as_ratio(); grabbing_from = grabber->get_transform().xform(mb->get_position()).x; } - grab_focus(); + grab_focus(true); emit_signal("grabbed"); } else { grabbing_grabber = false; @@ -340,7 +340,7 @@ void EditorSpinSlider::_draw_spin_slider() { } } - if (has_focus()) { + if (has_focus(true)) { Ref focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit")); draw_style_box(focus, Rect2(Vector2(), size)); } @@ -672,7 +672,7 @@ bool EditorSpinSlider::is_grabbing() const { return grabbing_grabber || grabbing_spinner; } -void EditorSpinSlider::_focus_entered() { +void EditorSpinSlider::_focus_entered(bool p_hide_focus) { if (read_only) { return; } @@ -683,7 +683,7 @@ void EditorSpinSlider::_focus_entered() { value_input->set_focus_next(find_next_valid_focus()->get_path()); value_input->set_focus_previous(find_prev_valid_focus()->get_path()); callable_mp((CanvasItem *)value_input_popup, &CanvasItem::show).call_deferred(); - callable_mp((Control *)value_input, &Control::grab_focus).call_deferred(); + callable_mp((Control *)value_input, &Control::grab_focus).call_deferred(p_hide_focus); callable_mp(value_input, &LineEdit ::select_all).call_deferred(); emit_signal("value_focus_entered"); } diff --git a/editor/gui/editor_spin_slider.h b/editor/gui/editor_spin_slider.h index d6a4c171b86..bca3e3ae9f6 100644 --- a/editor/gui/editor_spin_slider.h +++ b/editor/gui/editor_spin_slider.h @@ -98,7 +98,7 @@ protected: static void _bind_methods(); void _grabber_mouse_entered(); void _grabber_mouse_exited(); - void _focus_entered(); + void _focus_entered(bool p_hide_focus = false); public: String get_tooltip(const Point2 &p_pos) const override; diff --git a/editor/inspector/editor_inspector.cpp b/editor/inspector/editor_inspector.cpp index 72ba1cf096e..4421b506874 100644 --- a/editor/inspector/editor_inspector.cpp +++ b/editor/inspector/editor_inspector.cpp @@ -940,9 +940,9 @@ void EditorProperty::grab_focus(int p_focusable) { if (p_focusable >= 0) { ERR_FAIL_INDEX(p_focusable, focusables.size()); - focusables[p_focusable]->grab_focus(); + focusables[p_focusable]->grab_focus(true); } else { - focusables[0]->grab_focus(); + focusables[0]->grab_focus(true); } } @@ -954,7 +954,7 @@ void EditorProperty::select(int p_focusable) { if (p_focusable >= 0) { ERR_FAIL_INDEX(p_focusable, focusables.size()); - focusables[p_focusable]->grab_focus(); + focusables[p_focusable]->grab_focus(true); } else { selected = true; queue_redraw(); diff --git a/editor/inspector/editor_properties.cpp b/editor/inspector/editor_properties.cpp index b27faecd4ca..a996d31e27e 100644 --- a/editor/inspector/editor_properties.cpp +++ b/editor/inspector/editor_properties.cpp @@ -2897,7 +2897,7 @@ void EditorPropertyNodePath::_menu_option(int p_idx) { const NodePath &np = _get_node_path(); edit->set_text(String(np)); edit->show(); - callable_mp((Control *)edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)edit, &Control::grab_focus).call_deferred(false); } break; case ACTION_SELECT: { diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index 790c2214e18..3f85be9679b 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -813,7 +813,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) { renderer_container->hide(); default_files_container->hide(); - callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(); + callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(false); callable_mp(project_name, &LineEdit::select_all).call_deferred(); } else { if (p_reset_name) { @@ -865,7 +865,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) { renderer_container->show(); default_files_container->show(); - callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(); + callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(false); callable_mp(project_name, &LineEdit::select_all).call_deferred(); } else if (mode == MODE_INSTALL) { set_title(TTR("Install Project:") + " " + zip_title); @@ -878,7 +878,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) { renderer_container->hide(); default_files_container->hide(); - callable_mp((Control *)project_path, &Control::grab_focus).call_deferred(); + callable_mp((Control *)project_path, &Control::grab_focus).call_deferred(false); } else if (mode == MODE_DUPLICATE) { set_title(TTRC("Duplicate Project")); set_ok_button_text(TTRC("Duplicate")); @@ -891,7 +891,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) { edit_check_box->hide(); } - callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(); + callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(false); callable_mp(project_name, &LineEdit::select_all).call_deferred(); } diff --git a/editor/project_manager/project_manager.cpp b/editor/project_manager/project_manager.cpp index 507931c7445..348cb6b4a18 100644 --- a/editor/project_manager/project_manager.cpp +++ b/editor/project_manager/project_manager.cpp @@ -363,7 +363,8 @@ void ProjectManager::_select_main_view(int p_id) { if (current_main_view == MAIN_VIEW_PROJECTS && search_box->is_inside_tree()) { // Automatically grab focus when the user moves from the Templates tab // back to the Projects tab. - search_box->grab_focus(); + // Needs to be deferred, otherwise the focus outline is always drawn. + callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(true); } // The Templates tab's search field is focused on display in the asset diff --git a/editor/run/embedded_process.cpp b/editor/run/embedded_process.cpp index 3a75f6db31d..80491ad3949 100644 --- a/editor/run/embedded_process.cpp +++ b/editor/run/embedded_process.cpp @@ -415,7 +415,7 @@ void EmbeddedProcess::_check_focused_process_id() { if (modal_window->get_mode() == Window::MODE_MINIMIZED) { modal_window->set_mode(Window::MODE_WINDOWED); } - callable_mp(modal_window, &Window::grab_focus).call_deferred(); + callable_mp(modal_window, &Window::grab_focus).call_deferred(false); } } } diff --git a/editor/scene/2d/tiles/tile_set_editor.cpp b/editor/scene/2d/tiles/tile_set_editor.cpp index 7e7df062219..942708203d1 100644 --- a/editor/scene/2d/tiles/tile_set_editor.cpp +++ b/editor/scene/2d/tiles/tile_set_editor.cpp @@ -997,7 +997,7 @@ void TileSourceInspectorPlugin::_show_id_edit_dialog(Object *p_for_source) { edited_source = p_for_source; id_input->set_value(p_for_source->get("id")); id_edit_dialog->popup_centered(Vector2i(400, 0) * EDSCALE); - callable_mp((Control *)id_input->get_line_edit(), &Control::grab_focus).call_deferred(); + callable_mp((Control *)id_input->get_line_edit(), &Control::grab_focus).call_deferred(false); } void TileSourceInspectorPlugin::_confirm_change_id() { diff --git a/editor/scene/canvas_item_editor_plugin.cpp b/editor/scene/canvas_item_editor_plugin.cpp index 7a73ce9c2c7..0e2bb2fd628 100644 --- a/editor/scene/canvas_item_editor_plugin.cpp +++ b/editor/scene/canvas_item_editor_plugin.cpp @@ -2797,7 +2797,7 @@ void CanvasItemEditor::_gui_input_viewport(const Ref &p_event) { // Grab focus if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) { - callable_mp((Control *)viewport, &Control::grab_focus).call_deferred(); + callable_mp((Control *)viewport, &Control::grab_focus).call_deferred(false); } } diff --git a/editor/scene/connections_dialog.cpp b/editor/scene/connections_dialog.cpp index f5b0f8b49a0..8c99cd34bd0 100644 --- a/editor/scene/connections_dialog.cpp +++ b/editor/scene/connections_dialog.cpp @@ -485,7 +485,7 @@ void ConnectDialog::_update_warning_label() { } void ConnectDialog::_post_popup() { - callable_mp((Control *)dst_method, &Control::grab_focus).call_deferred(); + callable_mp((Control *)dst_method, &Control::grab_focus).call_deferred(false); callable_mp(dst_method, &LineEdit::select_all).call_deferred(); } diff --git a/editor/scene/scene_create_dialog.cpp b/editor/scene/scene_create_dialog.cpp index f514570cfbf..56c00ac9584 100644 --- a/editor/scene/scene_create_dialog.cpp +++ b/editor/scene/scene_create_dialog.cpp @@ -66,7 +66,7 @@ void SceneCreateDialog::config(const String &p_dir) { directory = p_dir; root_name_edit->set_text(""); scene_name_edit->set_text(""); - callable_mp((Control *)scene_name_edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)scene_name_edit, &Control::grab_focus).call_deferred(false); validation_panel->update(); Ref profile = EditorFeatureProfileManager::get_singleton()->get_current_profile(); diff --git a/editor/scene/scene_tree_editor.cpp b/editor/scene/scene_tree_editor.cpp index c877eee2170..02c4bd0c36b 100644 --- a/editor/scene/scene_tree_editor.cpp +++ b/editor/scene/scene_tree_editor.cpp @@ -2274,7 +2274,7 @@ void SceneTreeDialog::_notification(int p_what) { tree->update_tree(); // Select the search bar by default. - callable_mp((Control *)filter, &Control::grab_focus).call_deferred(); + callable_mp((Control *)filter, &Control::grab_focus).call_deferred(false); } } break; diff --git a/editor/script/find_in_files.cpp b/editor/script/find_in_files.cpp index efd7e9b5228..313c3617f6f 100644 --- a/editor/script/find_in_files.cpp +++ b/editor/script/find_in_files.cpp @@ -467,16 +467,16 @@ void FindInFilesDialog::set_search_text(const String &text) { _search_text_line_edit->set_text(text); _on_search_text_modified(text); } - callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(false); _search_text_line_edit->select_all(); } else if (_mode == REPLACE_MODE) { if (!text.is_empty()) { _search_text_line_edit->set_text(text); - callable_mp((Control *)_replace_text_line_edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)_replace_text_line_edit, &Control::grab_focus).call_deferred(false); _replace_text_line_edit->select_all(); _on_search_text_modified(text); } else { - callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(false); _search_text_line_edit->select_all(); } } diff --git a/editor/script/script_text_editor.cpp b/editor/script/script_text_editor.cpp index 7f1e3f2fdae..2bac230ed55 100644 --- a/editor/script/script_text_editor.cpp +++ b/editor/script/script_text_editor.cpp @@ -1717,27 +1717,27 @@ void ScriptTextEditor::_edit_option(int p_op) { switch (p_op) { case EDIT_UNDO: { tx->undo(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_REDO: { tx->redo(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_CUT: { tx->cut(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_COPY: { tx->copy(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_PASTE: { tx->paste(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_SELECT_ALL: { tx->select_all(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_MOVE_LINE_UP: { code_editor->get_text_editor()->move_lines_up(); diff --git a/editor/script/text_editor.cpp b/editor/script/text_editor.cpp index bf27d4729b6..ad1499dd862 100644 --- a/editor/script/text_editor.cpp +++ b/editor/script/text_editor.cpp @@ -362,27 +362,27 @@ void TextEditor::_edit_option(int p_op) { switch (p_op) { case EDIT_UNDO: { tx->undo(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_REDO: { tx->redo(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_CUT: { tx->cut(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_COPY: { tx->copy(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_PASTE: { tx->paste(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_SELECT_ALL: { tx->select_all(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false); } break; case EDIT_MOVE_LINE_UP: { code_editor->get_text_editor()->move_lines_up(); diff --git a/editor/shader/text_shader_editor.cpp b/editor/shader/text_shader_editor.cpp index 99327da800c..c39728b80e9 100644 --- a/editor/shader/text_shader_editor.cpp +++ b/editor/shader/text_shader_editor.cpp @@ -787,7 +787,7 @@ void TextShaderEditor::_menu_option(int p_option) { } break; } if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) { - callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred(); + callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred(false); } } diff --git a/misc/extension_api_validation/4.5-stable.expected b/misc/extension_api_validation/4.5-stable.expected index 9ae0f44980e..7cafd1fa773 100644 --- a/misc/extension_api_validation/4.5-stable.expected +++ b/misc/extension_api_validation/4.5-stable.expected @@ -7,3 +7,10 @@ should instead be used to justify these changes and describe how users should wo Add new entries at the end of the file. ## Changes between 4.5-stable and 4.6-stable + +GH-110250 +--------- +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/grab_focus': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/has_focus': arguments + +Optional argument added. Compatibility methods registered. diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 2fac6ee0537..e3915b4e4b6 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -251,7 +251,7 @@ void Button::_notification(int p_what) { style->draw(ci, Rect2(Point2(), size)); } - if (has_focus()) { + if (has_focus(true)) { theme_cache.focus->draw(ci, Rect2(Point2(), size)); } @@ -315,7 +315,7 @@ void Button::_notification(int p_what) { switch (get_draw_mode()) { case DRAW_NORMAL: { // Focus colors only take precedence over normal state. - if (has_focus()) { + if (has_focus(true)) { font_color = theme_cache.font_focus_color; if (has_theme_color(SNAME("icon_focus_color"))) { icon_modulate_color = theme_cache.icon_focus_color; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 5a3d5e859d1..c9dd6a6de01 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -364,7 +364,7 @@ void ColorPicker::finish_shaders() { } void ColorPicker::set_focus_on_line_edit() { - callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(); + callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(false); } void ColorPicker::set_focus_on_picker_shape() { @@ -1491,7 +1491,7 @@ void ColorPicker::_sample_draw() { sample->draw_rect(rect_new, color); - if (display_old_color && !old_color.is_equal_approx(color) && sample->has_focus()) { + if (display_old_color && !old_color.is_equal_approx(color) && sample->has_focus(true)) { RID ci = sample->get_canvas_item(); theme_cache.sample_focus->draw(ci, rect_old); } @@ -2663,7 +2663,7 @@ void ColorPresetButton::_notification(int p_what) { WARN_PRINT("Unsupported StyleBox used for ColorPresetButton. Use StyleBoxFlat or StyleBoxTexture instead."); } - if (has_focus()) { + if (has_focus(true)) { RID ci = get_canvas_item(); theme_cache.focus_style->draw(ci, Rect2(Point2(), get_size())); } diff --git a/scene/gui/color_picker_shape.cpp b/scene/gui/color_picker_shape.cpp index ce9de12a29d..9def06ea43c 100644 --- a/scene/gui/color_picker_shape.cpp +++ b/scene/gui/color_picker_shape.cpp @@ -84,7 +84,7 @@ void ColorPickerShape::cancel_event() { } void ColorPickerShape::draw_focus_rect(Control *p_control, const Rect2 &p_rect) { - if (!p_control->has_focus()) { + if (!p_control->has_focus(true)) { return; } @@ -103,7 +103,7 @@ void ColorPickerShape::draw_focus_rect(Control *p_control, const Rect2 &p_rect) } void ColorPickerShape::draw_focus_circle(Control *p_control) { - if (!p_control->has_focus()) { + if (!p_control->has_focus(true)) { return; } diff --git a/scene/gui/control.compat.inc b/scene/gui/control.compat.inc new file mode 100644 index 00000000000..4e854fc98a8 --- /dev/null +++ b/scene/gui/control.compat.inc @@ -0,0 +1,46 @@ +/**************************************************************************/ +/* control.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +bool Control::_has_focus_bind_compat_110250() const { + return has_focus(false); +} + +void Control::_grab_focus_bind_compat_110250() { + return grab_focus(false); +} + +void Control::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("has_focus"), &Control::_has_focus_bind_compat_110250); + ClassDB::bind_compatibility_method(D_METHOD("grab_focus"), &Control::_grab_focus_bind_compat_110250); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 643f0732113..da4862b59ba 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "control.h" +#include "control.compat.inc" #include "container.h" #include "core/config/project_settings.h" @@ -2315,12 +2316,12 @@ void Control::_propagate_focus_behavior_recursive_recursively(bool p_enabled, bo } } -bool Control::has_focus() const { +bool Control::has_focus(bool p_ignore_hidden_focus) const { ERR_READ_THREAD_GUARD_V(false); - return is_inside_tree() && get_viewport()->_gui_control_has_focus(this); + return is_inside_tree() && get_viewport()->_gui_control_has_focus(this, p_ignore_hidden_focus); } -void Control::grab_focus() { +void Control::grab_focus(bool p_hide_focus) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_COND(!is_inside_tree()); @@ -2329,7 +2330,7 @@ void Control::grab_focus() { return; } - get_viewport()->_gui_control_grab_focus(this); + get_viewport()->_gui_control_grab_focus(this, p_hide_focus); } void Control::grab_click_focus() { @@ -4002,8 +4003,8 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("get_focus_mode_with_override"), &Control::get_focus_mode_with_override); ClassDB::bind_method(D_METHOD("set_focus_behavior_recursive", "focus_behavior_recursive"), &Control::set_focus_behavior_recursive); ClassDB::bind_method(D_METHOD("get_focus_behavior_recursive"), &Control::get_focus_behavior_recursive); - ClassDB::bind_method(D_METHOD("has_focus"), &Control::has_focus); - ClassDB::bind_method(D_METHOD("grab_focus"), &Control::grab_focus); + ClassDB::bind_method(D_METHOD("has_focus", "ignore_hidden_focus"), &Control::has_focus, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("grab_focus", "hide_focus"), &Control::grab_focus, DEFVAL(false)); ClassDB::bind_method(D_METHOD("release_focus"), &Control::release_focus); ClassDB::bind_method(D_METHOD("find_prev_valid_focus"), &Control::find_prev_valid_focus); ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_valid_focus); diff --git a/scene/gui/control.h b/scene/gui/control.h index b8b47c9be9d..df433b3930b 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -394,6 +394,12 @@ protected: void _accessibility_action_hide_tooltip(const Variant &p_data); void _accessibility_action_scroll_into_view(const Variant &p_data); +#ifndef DISABLE_DEPRECATED + bool _has_focus_bind_compat_110250() const; + void _grab_focus_bind_compat_110250(); + static void _bind_compatibility_methods(); +#endif //DISABLE_DEPRECATED + // Exposed virtual methods. GDVIRTUAL1RC(bool, _has_point, Vector2) @@ -592,8 +598,8 @@ public: FocusMode get_focus_mode_with_override() const; void set_focus_behavior_recursive(FocusBehaviorRecursive p_focus_behavior_recursive); FocusBehaviorRecursive get_focus_behavior_recursive() const; - bool has_focus() const; - void grab_focus(); + bool has_focus(bool p_ignore_hidden_focus = false) const; + void grab_focus(bool p_hide_focus = false); void grab_click_focus(); void release_focus(); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 8184b9300b4..07fca2524ce 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -421,9 +421,9 @@ void FileDialog::_save_confirm_pressed() { void FileDialog::_post_popup() { ConfirmationDialog::_post_popup(); if (mode == FILE_MODE_SAVE_FILE) { - filename_edit->grab_focus(); + filename_edit->grab_focus(true); } else { - file_list->grab_focus(); + file_list->grab_focus(true); } set_process_shortcut_input(true); @@ -2035,7 +2035,7 @@ void FileDialog::set_show_filename_filter(bool p_show) { filename_filter->grab_focus(); } else { if (filename_filter->has_focus()) { - callable_mp((Control *)file_list, &Control::grab_focus).call_deferred(); + callable_mp((Control *)file_list, &Control::grab_focus).call_deferred(false); } } show_filename_filter = p_show; diff --git a/scene/gui/foldable_container.cpp b/scene/gui/foldable_container.cpp index c93bab06faf..49328516aaf 100644 --- a/scene/gui/foldable_container.cpp +++ b/scene/gui/foldable_container.cpp @@ -317,7 +317,7 @@ void FoldableContainer::_notification(int p_what) { _draw_flippable_stylebox(theme_cache.panel_style, panel_rect); } - if (has_focus()) { + if (has_focus(true)) { Rect2 focus_rect = folded ? title_rect : Rect2(Point2(), size); _draw_flippable_stylebox(theme_cache.focus_style, focus_rect); } diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 538771aa3e4..3e4b9f55f9a 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -861,7 +861,7 @@ void GraphEdit::_notification(int p_what) { // Draw background fill. draw_style_box(theme_cache.panel, Rect2(Point2(), get_size())); - if (has_focus()) { + if (has_focus(true)) { draw_style_box(theme_cache.panel_focus, Rect2(Point2(), get_size())); } diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 333ae07ab64..587136878b9 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -1383,7 +1383,7 @@ void ItemList::_notification(int p_what) { Ref sbsel; Ref cursor; - if (has_focus()) { + if (has_focus(true)) { sbsel = theme_cache.selected_focus_style; cursor = theme_cache.cursor_focus_style; } else { @@ -1507,7 +1507,7 @@ void ItemList::_notification(int p_what) { draw_style_box(sbsel, r); } if (should_draw_hovered_selected_bg) { - if (has_focus()) { + if (has_focus(true)) { draw_style_box(theme_cache.hovered_selected_focus_style, r); } else { draw_style_box(theme_cache.hovered_selected_style, r); @@ -1695,7 +1695,7 @@ void ItemList::_notification(int p_what) { draw_style_box(cursor, cursor_rcache); } - if (has_focus()) { + if (has_focus(true)) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), true); size.x -= (scroll_bar_h->get_max() - scroll_bar_h->get_page()); draw_style_box(theme_cache.focus_style, Rect2(Point2(), size)); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index cd97ee265b2..972247e4c4c 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -762,7 +762,7 @@ void Label::_notification(int p_what) { Vector stacked_shadow_datas = has_settings ? settings->get_stacked_shadow_data() : Vector(); bool rtl_layout = is_layout_rtl(); - if (has_focus()) { + if (has_focus(true)) { theme_cache.focus_style->draw(ci, Rect2(Point2(0, 0), get_size())); } else { theme_cache.normal_style->draw(ci, Rect2(Point2(0, 0), get_size())); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index f188ed693ca..6989a72e2cc 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -1343,7 +1343,7 @@ void LineEdit::_notification(int p_what) { style->draw(ci, Rect2(Point2(), size)); } - if (has_focus()) { + if (has_focus(true)) { theme_cache.focus->draw(ci, Rect2(Point2(), size)); } diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index f9ef144400c..0f29e400d42 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -191,7 +191,7 @@ void LinkButton::_notification(int p_what) { switch (get_draw_mode()) { case DRAW_NORMAL: { - if (has_focus()) { + if (has_focus(true)) { color = theme_cache.font_focus_color; } else { color = theme_cache.font_color; @@ -222,7 +222,7 @@ void LinkButton::_notification(int p_what) { } break; } - if (has_focus()) { + if (has_focus(true)) { Ref style = theme_cache.focus; style->draw(ci, Rect2(Point2(), size)); } diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 9e149aff865..f72718e767c 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -493,7 +493,7 @@ void MenuBar::_draw_menu_item(int p_index) { style->draw(ci, item_rect); } // Focus colors only take precedence over normal state. - if (has_focus()) { + if (has_focus(true)) { color = theme_cache.font_focus_color; } else { color = theme_cache.font_color; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index c214af0cd7c..e8071584cbb 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -114,7 +114,7 @@ void OptionButton::_notification(int p_what) { clr = theme_cache.font_disabled_color; break; default: - if (has_focus()) { + if (has_focus(true)) { clr = theme_cache.font_focus_color; } else { clr = theme_cache.font_color; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 233c6e54514..51faf9ac1b8 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -2469,7 +2469,7 @@ void RichTextLabel::_notification(int p_what) { draw_style_box(theme_cache.normal_style, Rect2(Point2(), size)); - if (has_focus()) { + if (has_focus(true)) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); draw_style_box(theme_cache.focus_style, Rect2(Point2(), size)); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index ec2c46147ba..c8b848fc354 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -279,7 +279,7 @@ void ScrollBar::_notification(int p_what) { area.height -= incr->get_height() + decr->get_height(); } - if (has_focus()) { + if (has_focus(true)) { theme_cache.scroll_focus_style->draw(ci, Rect2(ofs, area)); } else { theme_cache.scroll_style->draw(ci, Rect2(ofs, area)); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 30991f05aea..d0f89561b8b 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -316,7 +316,7 @@ void ScrollContainer::_gui_focus_changed(Control *p_control) { ensure_control_visible(p_control); } if (draw_focus_border) { - const bool _should_draw_focus_border = has_focus() || child_has_focus(); + const bool _should_draw_focus_border = has_focus(true) || child_has_focus(); if (focus_border_is_drawn != _should_draw_focus_border) { queue_redraw(); } @@ -484,7 +484,7 @@ void ScrollContainer::_notification(int p_what) { case NOTIFICATION_DRAW: { draw_style_box(theme_cache.panel_style, Rect2(Vector2(), get_size())); - focus_border_is_drawn = draw_focus_border && (has_focus() || child_has_focus()); + focus_border_is_drawn = draw_focus_border && (has_focus(true) || child_has_focus()); focus_panel->set_visible(focus_border_is_drawn); } break; @@ -815,7 +815,7 @@ bool ScrollContainer::get_draw_focus_border() { bool ScrollContainer::child_has_focus() { const Control *focus_owner = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; - return focus_owner && is_ancestor_of(focus_owner); + return focus_owner && focus_owner->has_focus(true) && is_ancestor_of(focus_owner); } ScrollContainer::ScrollContainer() { diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index d32d631390f..eb48e52c8fa 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -56,7 +56,7 @@ void Slider::gui_input(const Ref &p_event) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { Ref grabber; - if (mouse_inside || has_focus()) { + if (mouse_inside || has_focus(true)) { grabber = theme_cache.grabber_hl_icon; } else { grabber = theme_cache.grabber_icon; @@ -275,7 +275,7 @@ void Slider::_notification(int p_what) { Ref style = theme_cache.slider_style; Ref tick = theme_cache.tick_icon; - bool highlighted = editable && (mouse_inside || has_focus()); + bool highlighted = editable && (mouse_inside || has_focus(true)); Ref grabber; if (editable) { if (highlighted) { diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 4c9c3f5ecef..0f60b938d50 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -543,7 +543,7 @@ void TabBar::_notification(int p_what) { if (current >= offset && current <= max_drawn_tab && !tabs[current].hidden) { Ref sb = tabs[current].disabled ? theme_cache.tab_disabled_style : theme_cache.tab_selected_style; - _draw_tab(sb, theme_cache.font_selected_color, current, rtl ? (size.width - tabs[current].ofs_cache - tabs[current].size_cache) : tabs[current].ofs_cache, has_focus()); + _draw_tab(sb, theme_cache.font_selected_color, current, rtl ? (size.width - tabs[current].ofs_cache - tabs[current].size_cache) : tabs[current].ofs_cache, has_focus(true)); } if (buttons_visible) { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 2659d0b1755..10def8b59cd 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -945,7 +945,7 @@ void TextEdit::_notification(int p_what) { theme_cache.style_readonly->draw(ci, Rect2(Point2(), size)); draw_caret = is_drawing_caret_when_editable_disabled(); } - if (has_focus()) { + if (has_focus(true)) { theme_cache.style_focus->draw(ci, Rect2(Point2(), size)); } diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index 70820604bfd..7b93c483877 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -168,7 +168,7 @@ void TextureButton::_notification(int p_what) { Point2 ofs; Size2 size; - bool draw_focus = (has_focus() && focused.is_valid()); + bool draw_focus = (has_focus(true) && focused.is_valid()); // If no other texture is valid, try using focused texture. bool draw_focus_only = draw_focus && texdraw.is_null(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 7d6ca962083..127a6f2fc01 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2324,13 +2324,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (p_item->cells[0].selected) { if (is_row_hovered) { - if (has_focus()) { + if (has_focus(true)) { theme_cache.hovered_selected_focus->draw(ci, row_rect); } else { theme_cache.hovered_selected->draw(ci, row_rect); } } else { - if (has_focus()) { + if (has_focus(true)) { theme_cache.selected_focus->draw(ci, row_rect); } else { theme_cache.selected->draw(ci, row_rect); @@ -2372,13 +2372,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } if (p_item->cells[i].selected) { if (is_cell_hovered) { - if (has_focus()) { + if (has_focus(true)) { theme_cache.hovered_selected_focus->draw(ci, r); } else { theme_cache.hovered_selected->draw(ci, r); } } else { - if (has_focus()) { + if (has_focus(true)) { theme_cache.selected_focus->draw(ci, r); } else { theme_cache.selected->draw(ci, r); @@ -2672,7 +2672,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (is_layout_rtl()) { cell_rect.position.x = get_size().width - cell_rect.position.x - cell_rect.size.x; } - if (has_focus()) { + if (has_focus(true)) { theme_cache.cursor->draw(ci, cell_rect); } else { theme_cache.cursor_unfocus->draw(ci, cell_rect); @@ -5078,7 +5078,7 @@ void Tree::_notification(int p_what) { // Draw the focus outline last, so that it is drawn in front of the section headings. // Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling. - if (has_focus()) { + if (has_focus(true)) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); theme_cache.focus_style->draw(ci, Rect2(Point2(), get_size())); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 76a7e3e3a55..f431a98ce04 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -531,6 +531,22 @@ void Viewport::_update_viewport_path() { } } +bool Viewport::_can_hide_focus_state() { + return Engine::get_singleton()->is_editor_hint() || !GLOBAL_GET_CACHED(bool, "gui/common/always_show_focus_state"); +} + +void Viewport::_on_settings_changed() { + if (!gui.hide_focus && _can_hide_focus_state()) { + return; + } + + gui.hide_focus = false; + // Show previously hidden focus. + if (gui.key_focus) { + gui.key_focus->queue_redraw(); + } +} + void Viewport::_notification(int p_what) { ERR_MAIN_THREAD_GUARD; @@ -1914,6 +1930,12 @@ void Viewport::_gui_input_event(Ref p_event) { gui.mouse_focus = gui_find_control(mpos); if (!gui.mouse_focus) { + // Focus should be hidden on click even if the focus holder didn't change. + if (gui.key_focus && mb->get_button_index() == MouseButton::LEFT && _can_hide_focus_state()) { + gui.hide_focus = true; + gui.key_focus->queue_redraw(); + } + return; } @@ -1946,8 +1968,9 @@ void Viewport::_gui_input_event(Ref p_event) { if (control->_is_focusable()) { // Grabbing unhovered focus can cause issues when mouse is dragged // with another button held down. - if (control != gui.key_focus && gui.mouse_over_hierarchy.has(control)) { - control->grab_focus(); + if (gui.mouse_over_hierarchy.has(control)) { + // Hide the focus when it comes from a click. + control->grab_focus(true); } break; } @@ -2301,6 +2324,7 @@ void Viewport::_gui_input_event(Ref p_event) { if (from && p_event->is_pressed()) { Control *next = nullptr; + bool show_focus = false; Ref joypadmotion_event = p_event; if (joypadmotion_event.is_valid()) { @@ -2308,10 +2332,12 @@ void Viewport::_gui_input_event(Ref p_event) { if (p_event->is_action_pressed(SNAME("ui_focus_next")) && input->is_action_just_pressed_by_event(SNAME("ui_focus_next"), p_event)) { next = from->find_next_valid_focus(); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_focus_prev")) && input->is_action_just_pressed_by_event(SNAME("ui_focus_prev"), p_event)) { next = from->find_prev_valid_focus(); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_accessibility_drag_and_drop")) && input->is_action_just_pressed_by_event(SNAME("ui_accessibility_drag_and_drop"), p_event)) { @@ -2324,26 +2350,32 @@ void Viewport::_gui_input_event(Ref p_event) { if (p_event->is_action_pressed(SNAME("ui_up")) && input->is_action_just_pressed_by_event(SNAME("ui_up"), p_event)) { next = from->_get_focus_neighbor(SIDE_TOP); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_left")) && input->is_action_just_pressed_by_event(SNAME("ui_left"), p_event)) { next = from->_get_focus_neighbor(SIDE_LEFT); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_right")) && input->is_action_just_pressed_by_event(SNAME("ui_right"), p_event)) { next = from->_get_focus_neighbor(SIDE_RIGHT); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_down")) && input->is_action_just_pressed_by_event(SNAME("ui_down"), p_event)) { next = from->_get_focus_neighbor(SIDE_BOTTOM); + show_focus = true; } } else { if (p_event->is_action_pressed(SNAME("ui_focus_next"), true, true)) { next = from->find_next_valid_focus(); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_focus_prev"), true, true)) { next = from->find_prev_valid_focus(); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_accessibility_drag_and_drop"), true, true)) { @@ -2356,23 +2388,32 @@ void Viewport::_gui_input_event(Ref p_event) { if (p_event->is_action_pressed(SNAME("ui_up"), true, true)) { next = from->_get_focus_neighbor(SIDE_TOP); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_left"), true, true)) { next = from->_get_focus_neighbor(SIDE_LEFT); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_right"), true, true)) { next = from->_get_focus_neighbor(SIDE_RIGHT); + show_focus = true; } if (p_event->is_action_pressed(SNAME("ui_down"), true, true)) { next = from->_get_focus_neighbor(SIDE_BOTTOM); + show_focus = true; } } + if (next) { next->grab_focus(); set_input_as_handled(); + } else if (show_focus && gui.hide_focus && gui.key_focus) { + // Show focus even it the holder didn't change, as visual feedback. + gui.hide_focus = false; + gui.key_focus->queue_redraw(); } } } @@ -2655,18 +2696,26 @@ void Viewport::_gui_remove_focus_for_window(Node *p_window) { } } -bool Viewport::_gui_control_has_focus(const Control *p_control) { - return gui.key_focus == p_control; +bool Viewport::_gui_control_has_focus(const Control *p_control, bool p_ignore_hidden_focus) { + return (!p_ignore_hidden_focus || !gui.hide_focus) && gui.key_focus == p_control; } -void Viewport::_gui_control_grab_focus(Control *p_control) { +void Viewport::_gui_control_grab_focus(Control *p_control, bool p_hide_focus) { if (gui.key_focus && gui.key_focus == p_control) { - // No need for change. + // Only worry about the focus visibility change. + if (p_hide_focus != gui.hide_focus && _can_hide_focus_state()) { + gui.hide_focus = p_hide_focus; + p_control->queue_redraw(); + } return; } + get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", get_base_window()); if (p_control->is_inside_tree() && p_control->get_viewport() == this) { gui.key_focus = p_control; + if (_can_hide_focus_state()) { + gui.hide_focus = p_hide_focus; + } emit_signal(SNAME("gui_focus_changed"), p_control); p_control->notification(Control::NOTIFICATION_FOCUS_ENTER); p_control->queue_redraw(); @@ -5386,6 +5435,8 @@ Viewport::Viewport() { // Viewports can thus inherit physics interpolation OFF, which is unexpected. // Setting to ON allows each viewport to have a fresh interpolation state. set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_ON); + + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Viewport::_on_settings_changed)); } Viewport::~Viewport() { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 0ed3864b08e..19ec30ff64e 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -330,6 +330,9 @@ private: void _update_viewport_path(); + bool _can_hide_focus_state(); + void _on_settings_changed(); + SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT; SDFScale sdf_scale = SDF_SCALE_50_PERCENT; @@ -374,6 +377,7 @@ private: Control *mouse_click_grabber = nullptr; BitField mouse_focus_mask = MouseButtonMask::NONE; Control *key_focus = nullptr; + bool hide_focus = false; Control *mouse_over = nullptr; LocalVector mouse_over_hierarchy; bool sending_mouse_enter_exit_notifications = false; @@ -459,8 +463,8 @@ private: void _gui_remove_focus_for_window(Node *p_window); void _gui_unfocus_control(Control *p_control); - bool _gui_control_has_focus(const Control *p_control); - void _gui_control_grab_focus(Control *p_control); + bool _gui_control_has_focus(const Control *p_control, bool p_ignore_hidden_focus = false); + void _gui_control_grab_focus(Control *p_control, bool p_hide_focus = false); void _gui_grab_click_focus(Control *p_control); void _post_gui_grab_click_focus(); void _gui_accept_event();