瀏覽代碼

Merge pull request #110250 from YeldhamDev/i_just_cant_keep_focused

Hide `Control` focus when given via mouse input
Thaddeus Crews 2 周之前
父節點
當前提交
be421bcdd4
共有 53 個文件被更改,包括 229 次插入105 次删除
  1. 1 0
      core/config/project_settings.cpp
  2. 5 1
      doc/classes/Control.xml
  3. 3 0
      doc/classes/ProjectSettings.xml
  4. 1 1
      editor/animation/animation_bezier_editor.cpp
  5. 1 1
      editor/animation/animation_blend_space_2d_editor.cpp
  6. 1 1
      editor/animation/animation_state_machine_editor.cpp
  7. 4 4
      editor/docks/filesystem_dock.cpp
  8. 2 2
      editor/docks/scene_tree_dock.cpp
  9. 2 2
      editor/gui/code_editor.cpp
  10. 1 1
      editor/gui/create_dialog.cpp
  11. 2 2
      editor/gui/editor_file_dialog.cpp
  12. 5 5
      editor/gui/editor_spin_slider.cpp
  13. 1 1
      editor/gui/editor_spin_slider.h
  14. 3 3
      editor/inspector/editor_inspector.cpp
  15. 1 1
      editor/inspector/editor_properties.cpp
  16. 4 4
      editor/project_manager/project_dialog.cpp
  17. 2 1
      editor/project_manager/project_manager.cpp
  18. 1 1
      editor/run/embedded_process.cpp
  19. 1 1
      editor/scene/2d/tiles/tile_set_editor.cpp
  20. 1 1
      editor/scene/canvas_item_editor_plugin.cpp
  21. 1 1
      editor/scene/connections_dialog.cpp
  22. 1 1
      editor/scene/scene_create_dialog.cpp
  23. 1 1
      editor/scene/scene_tree_editor.cpp
  24. 3 3
      editor/script/find_in_files.cpp
  25. 6 6
      editor/script/script_text_editor.cpp
  26. 6 6
      editor/script/text_editor.cpp
  27. 1 1
      editor/shader/text_shader_editor.cpp
  28. 7 0
      misc/extension_api_validation/4.5-stable.expected
  29. 2 2
      scene/gui/button.cpp
  30. 3 3
      scene/gui/color_picker.cpp
  31. 2 2
      scene/gui/color_picker_shape.cpp
  32. 46 0
      scene/gui/control.compat.inc
  33. 7 6
      scene/gui/control.cpp
  34. 8 2
      scene/gui/control.h
  35. 3 3
      scene/gui/file_dialog.cpp
  36. 1 1
      scene/gui/foldable_container.cpp
  37. 1 1
      scene/gui/graph_edit.cpp
  38. 3 3
      scene/gui/item_list.cpp
  39. 1 1
      scene/gui/label.cpp
  40. 1 1
      scene/gui/line_edit.cpp
  41. 2 2
      scene/gui/link_button.cpp
  42. 1 1
      scene/gui/menu_bar.cpp
  43. 1 1
      scene/gui/option_button.cpp
  44. 1 1
      scene/gui/rich_text_label.cpp
  45. 1 1
      scene/gui/scroll_bar.cpp
  46. 3 3
      scene/gui/scroll_container.cpp
  47. 2 2
      scene/gui/slider.cpp
  48. 1 1
      scene/gui/tab_bar.cpp
  49. 1 1
      scene/gui/text_edit.cpp
  50. 1 1
      scene/gui/texture_button.cpp
  51. 6 6
      scene/gui/tree.cpp
  52. 57 6
      scene/main/viewport.cpp
  53. 6 2
      scene/main/viewport.h

+ 1 - 0
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);

+ 5 - 1
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 @@
 		</method>
 		<method name="grab_focus">
 			<return type="void" />
+			<param index="0" name="hide_focus" type="bool" default="false" />
 			<description>
 				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].
 			</description>
 		</method>
 		<method name="has_focus" qualifiers="const">
 			<return type="bool" />
+			<param index="0" name="ignore_hidden_focus" type="bool" default="false" />
 			<description>
 				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].
 			</description>
 		</method>
 		<method name="has_theme_color" qualifiers="const">

+ 3 - 0
doc/classes/ProjectSettings.xml

@@ -1177,6 +1177,9 @@
 		<member name="filesystem/import/fbx2gltf/enabled.web" type="bool" setter="" getter="" default="false">
 			Override for [member filesystem/import/fbx2gltf/enabled] on the Web where FBX2glTF can't easily be accessed from Godot.
 		</member>
+		<member name="gui/common/always_show_focus_state" type="bool" setter="" getter="" default="false">
+			If [code]true[/code], [Control]s will always show if they're focused, even if said focus was gained via mouse/touch input.
+		</member>
 		<member name="gui/common/default_scroll_deadzone" type="int" setter="" getter="" default="0">
 			Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden.
 		</member>

+ 1 - 1
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));
 			}
 

+ 1 - 1
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);
 	}

+ 1 - 1
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;

+ 4 - 4
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<String> paths = _tree_get_selected(false);

+ 2 - 2
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) {

+ 2 - 2
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) {

+ 1 - 1
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()));

+ 2 - 2
editor/gui/editor_file_dialog.cpp

@@ -483,9 +483,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;

+ 5 - 5
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<InputEvent> &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<StyleBox> 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");
 }

+ 1 - 1
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;

+ 3 - 3
editor/inspector/editor_inspector.cpp

@@ -942,9 +942,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);
 	}
 }
 
@@ -956,7 +956,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();

+ 1 - 1
editor/inspector/editor_properties.cpp

@@ -2904,7 +2904,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: {

+ 4 - 4
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) {
@@ -882,7 +882,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);
@@ -895,7 +895,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"));
@@ -908,7 +908,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();
 		}
 

+ 2 - 1
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

+ 1 - 1
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);
 		}
 	}
 }

+ 1 - 1
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() {

+ 1 - 1
editor/scene/canvas_item_editor_plugin.cpp

@@ -2796,7 +2796,7 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &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);
 	}
 }
 

+ 1 - 1
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();
 }
 

+ 1 - 1
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<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();

+ 1 - 1
editor/scene/scene_tree_editor.cpp

@@ -2279,7 +2279,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;
 

+ 3 - 3
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();
 		}
 	}

+ 6 - 6
editor/script/script_text_editor.cpp

@@ -1728,27 +1728,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();

+ 6 - 6
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();

+ 1 - 1
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);
 	}
 }
 

+ 7 - 0
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.

+ 2 - 2
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;

+ 3 - 3
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()));
 			}

+ 2 - 2
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;
 	}
 

+ 46 - 0
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

+ 7 - 6
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);

+ 8 - 2
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();
 

+ 3 - 3
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;

+ 1 - 1
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);
 			}

+ 1 - 1
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()));
 			}
 

+ 3 - 3
scene/gui/item_list.cpp

@@ -1383,7 +1383,7 @@ void ItemList::_notification(int p_what) {
 			Ref<StyleBox> sbsel;
 			Ref<StyleBox> 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));

+ 1 - 1
scene/gui/label.cpp

@@ -762,7 +762,7 @@ void Label::_notification(int p_what) {
 			Vector<LabelSettings::StackedShadowData> stacked_shadow_datas = has_settings ? settings->get_stacked_shadow_data() : Vector<LabelSettings::StackedShadowData>();
 			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()));

+ 1 - 1
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));
 			}
 

+ 2 - 2
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<StyleBox> style = theme_cache.focus;
 				style->draw(ci, Rect2(Point2(), size));
 			}

+ 1 - 1
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;

+ 1 - 1
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;

+ 1 - 1
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);

+ 1 - 1
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));

+ 3 - 3
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() {

+ 2 - 2
scene/gui/slider.cpp

@@ -56,7 +56,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
 		if (mb->get_button_index() == MouseButton::LEFT) {
 			if (mb->is_pressed()) {
 				Ref<Texture2D> 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<StyleBox> style = theme_cache.slider_style;
 			Ref<Texture2D> tick = theme_cache.tick_icon;
 
-			bool highlighted = editable && (mouse_inside || has_focus());
+			bool highlighted = editable && (mouse_inside || has_focus(true));
 			Ref<Texture2D> grabber;
 			if (editable) {
 				if (highlighted) {

+ 1 - 1
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<StyleBox> 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) {

+ 1 - 1
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));
 			}
 

+ 1 - 1
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();

+ 6 - 6
scene/gui/tree.cpp

@@ -2327,13 +2327,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);
@@ -2375,13 +2375,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);
@@ -2675,7 +2675,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);
@@ -5080,7 +5080,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);

+ 57 - 6
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<InputEvent> 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<InputEvent> 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<InputEvent> p_event) {
 
 		if (from && p_event->is_pressed()) {
 			Control *next = nullptr;
+			bool show_focus = false;
 
 			Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
 			if (joypadmotion_event.is_valid()) {
@@ -2308,10 +2332,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> 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<InputEvent> 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<InputEvent> 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() {

+ 6 - 2
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<MouseButtonMask> mouse_focus_mask = MouseButtonMask::NONE;
 		Control *key_focus = nullptr;
+		bool hide_focus = false;
 		Control *mouse_over = nullptr;
 		LocalVector<Control *> 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();