Browse Source

Hide `Control` focus when given via mouse input

Michael Alexsander 1 month ago
parent
commit
aeb3a45c97
53 changed files with 229 additions and 105 deletions
  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
 #endif
 
 
 	GLOBAL_DEF_BASIC("gui/common/snap_controls_to_pixels", true);
 	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_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);
 	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.
 		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.
 		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.
 		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.
 		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.
 		[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.
 		[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>
 		<method name="grab_focus">
 		<method name="grab_focus">
 			<return type="void" />
 			<return type="void" />
+			<param index="0" name="hide_focus" type="bool" default="false" />
 			<description>
 			<description>
 				Steal the focus from another control and become the focused control (see [member focus_mode]).
 				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].
 				[b]Note:[/b] Using this method together with [method Callable.call_deferred] makes it more reliable, especially when called inside [method Node._ready].
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="has_focus" qualifiers="const">
 		<method name="has_focus" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
+			<param index="0" name="ignore_hidden_focus" type="bool" default="false" />
 			<description>
 			<description>
 				Returns [code]true[/code] if this is the current focused control. See [member focus_mode].
 				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>
 			</description>
 		</method>
 		</method>
 		<method name="has_theme_color" qualifiers="const">
 		<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">
 		<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.
 			Override for [member filesystem/import/fbx2gltf/enabled] on the Web where FBX2glTF can't easily be accessed from Godot.
 		</member>
 		</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">
 		<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.
 			Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden.
 		</member>
 		</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 h_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationBezierTrackEdit"));
 			const int v_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));
 				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();
 	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));
 		Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
 		blend_space_draw->draw_rect(Rect2(Point2(), s), color, false);
 		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();
 		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);
 		state_machine_draw->draw_rect(Rect2(Point2(), state_machine_draw->get_size()), theme_cache.focus_color, false);
 	}
 	}
 	int sep = 3 * EDSCALE;
 	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();
 			item = item->get_next();
 		}
 		}
 		if (p_grab_focus) {
 		if (p_grab_focus) {
-			tree->grab_focus();
+			tree->grab_focus(true);
 		}
 		}
 	} else {
 	} else {
 		(*directory_ptr)->select(0);
 		(*directory_ptr)->select(0);
 		_update_file_list(false);
 		_update_file_list(false);
 		if (p_grab_focus) {
 		if (p_grab_focus) {
-			files->grab_focus();
+			files->grab_focus(true);
 		}
 		}
 	}
 	}
 	tree->ensure_cursor_is_visible();
 	tree->ensure_cursor_is_visible();
@@ -1397,7 +1397,7 @@ void FileSystemDock::_update_history() {
 
 
 	if (tree->is_visible()) {
 	if (tree->is_visible()) {
 		_update_tree(get_uncollapsed_paths());
 		_update_tree(get_uncollapsed_paths());
-		tree->grab_focus();
+		tree->grab_focus(true);
 	}
 	}
 
 
 	if (file_list_vb->is_visible()) {
 	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) {
 	if (p_button != MouseButton::RIGHT) {
 		return;
 		return;
 	}
 	}
-	tree->grab_focus();
+	tree->grab_focus(true);
 
 
 	// Right click is pressed in the tree.
 	// Right click is pressed in the tree.
 	Vector<String> paths = _tree_get_selected(false);
 	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->clear();
 			editor_selection->add_node(new_node);
 			editor_selection->add_node(new_node);
 
 
-			scene_tree->get_scene_tree()->grab_focus();
+			scene_tree->get_scene_tree()->grab_focus(true);
 		} break;
 		} break;
 
 
 		default: {
 		default: {
@@ -3146,7 +3146,7 @@ void SceneTreeDock::_create() {
 		undo_redo->commit_action();
 		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) {
 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) {
 	if (focus_replace) {
 		search_text->deselect();
 		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 {
 	} else {
 		replace_text->deselect();
 		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) {
 	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: {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 			if (is_visible()) {
 			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();
 				search_box->select_all();
 			} else {
 			} else {
 				EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "create_new_node", Rect2(get_position(), get_size()));
 				EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "create_new_node", Rect2(get_position(), get_size()));

+ 2 - 2
editor/gui/editor_file_dialog.cpp

@@ -482,9 +482,9 @@ void EditorFileDialog::_post_popup() {
 	set_current_dir(current);
 	set_current_dir(current);
 
 
 	if (mode == FILE_MODE_SAVE_FILE) {
 	if (mode == FILE_MODE_SAVE_FILE) {
-		file->grab_focus();
+		file->grab_focus(true);
 	} else {
 	} else {
-		item_list->grab_focus();
+		item_list->grab_focus(true);
 	}
 	}
 
 
 	bool is_open_directory_mode = mode == FILE_MODE_OPEN_DIR;
 	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;
 			grabbing_spinner = false;
 			emit_signal("ungrabbed");
 			emit_signal("ungrabbed");
 		} else {
 		} else {
-			_focus_entered();
+			_focus_entered(true);
 		}
 		}
 
 
 		grabbing_spinner_attempt = false;
 		grabbing_spinner_attempt = false;
@@ -204,7 +204,7 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
 				grabbing_ratio = get_as_ratio();
 				grabbing_ratio = get_as_ratio();
 				grabbing_from = grabber->get_transform().xform(mb->get_position()).x;
 				grabbing_from = grabber->get_transform().xform(mb->get_position()).x;
 			}
 			}
-			grab_focus();
+			grab_focus(true);
 			emit_signal("grabbed");
 			emit_signal("grabbed");
 		} else {
 		} else {
 			grabbing_grabber = false;
 			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"));
 		Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit"));
 		draw_style_box(focus, Rect2(Vector2(), size));
 		draw_style_box(focus, Rect2(Vector2(), size));
 	}
 	}
@@ -672,7 +672,7 @@ bool EditorSpinSlider::is_grabbing() const {
 	return grabbing_grabber || grabbing_spinner;
 	return grabbing_grabber || grabbing_spinner;
 }
 }
 
 
-void EditorSpinSlider::_focus_entered() {
+void EditorSpinSlider::_focus_entered(bool p_hide_focus) {
 	if (read_only) {
 	if (read_only) {
 		return;
 		return;
 	}
 	}
@@ -683,7 +683,7 @@ void EditorSpinSlider::_focus_entered() {
 	value_input->set_focus_next(find_next_valid_focus()->get_path());
 	value_input->set_focus_next(find_next_valid_focus()->get_path());
 	value_input->set_focus_previous(find_prev_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((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();
 	callable_mp(value_input, &LineEdit ::select_all).call_deferred();
 	emit_signal("value_focus_entered");
 	emit_signal("value_focus_entered");
 }
 }

+ 1 - 1
editor/gui/editor_spin_slider.h

@@ -98,7 +98,7 @@ protected:
 	static void _bind_methods();
 	static void _bind_methods();
 	void _grabber_mouse_entered();
 	void _grabber_mouse_entered();
 	void _grabber_mouse_exited();
 	void _grabber_mouse_exited();
-	void _focus_entered();
+	void _focus_entered(bool p_hide_focus = false);
 
 
 public:
 public:
 	String get_tooltip(const Point2 &p_pos) const override;
 	String get_tooltip(const Point2 &p_pos) const override;

+ 3 - 3
editor/inspector/editor_inspector.cpp

@@ -940,9 +940,9 @@ void EditorProperty::grab_focus(int p_focusable) {
 
 
 	if (p_focusable >= 0) {
 	if (p_focusable >= 0) {
 		ERR_FAIL_INDEX(p_focusable, focusables.size());
 		ERR_FAIL_INDEX(p_focusable, focusables.size());
-		focusables[p_focusable]->grab_focus();
+		focusables[p_focusable]->grab_focus(true);
 	} else {
 	} else {
-		focusables[0]->grab_focus();
+		focusables[0]->grab_focus(true);
 	}
 	}
 }
 }
 
 
@@ -954,7 +954,7 @@ void EditorProperty::select(int p_focusable) {
 
 
 	if (p_focusable >= 0) {
 	if (p_focusable >= 0) {
 		ERR_FAIL_INDEX(p_focusable, focusables.size());
 		ERR_FAIL_INDEX(p_focusable, focusables.size());
-		focusables[p_focusable]->grab_focus();
+		focusables[p_focusable]->grab_focus(true);
 	} else {
 	} else {
 		selected = true;
 		selected = true;
 		queue_redraw();
 		queue_redraw();

+ 1 - 1
editor/inspector/editor_properties.cpp

@@ -2897,7 +2897,7 @@ void EditorPropertyNodePath::_menu_option(int p_idx) {
 			const NodePath &np = _get_node_path();
 			const NodePath &np = _get_node_path();
 			edit->set_text(String(np));
 			edit->set_text(String(np));
 			edit->show();
 			edit->show();
-			callable_mp((Control *)edit, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)edit, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 
 
 		case ACTION_SELECT: {
 		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();
 		renderer_container->hide();
 		default_files_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();
 		callable_mp(project_name, &LineEdit::select_all).call_deferred();
 	} else {
 	} else {
 		if (p_reset_name) {
 		if (p_reset_name) {
@@ -865,7 +865,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
 			renderer_container->show();
 			renderer_container->show();
 			default_files_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();
 			callable_mp(project_name, &LineEdit::select_all).call_deferred();
 		} else if (mode == MODE_INSTALL) {
 		} else if (mode == MODE_INSTALL) {
 			set_title(TTR("Install Project:") + " " + zip_title);
 			set_title(TTR("Install Project:") + " " + zip_title);
@@ -878,7 +878,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
 			renderer_container->hide();
 			renderer_container->hide();
 			default_files_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) {
 		} else if (mode == MODE_DUPLICATE) {
 			set_title(TTRC("Duplicate Project"));
 			set_title(TTRC("Duplicate Project"));
 			set_ok_button_text(TTRC("Duplicate"));
 			set_ok_button_text(TTRC("Duplicate"));
@@ -891,7 +891,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
 				edit_check_box->hide();
 				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();
 			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()) {
 	if (current_main_view == MAIN_VIEW_PROJECTS && search_box->is_inside_tree()) {
 		// Automatically grab focus when the user moves from the Templates tab
 		// Automatically grab focus when the user moves from the Templates tab
 		// back to the Projects 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
 	// 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) {
 			if (modal_window->get_mode() == Window::MODE_MINIMIZED) {
 				modal_window->set_mode(Window::MODE_WINDOWED);
 				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;
 	edited_source = p_for_source;
 	id_input->set_value(p_for_source->get("id"));
 	id_input->set_value(p_for_source->get("id"));
 	id_edit_dialog->popup_centered(Vector2i(400, 0) * EDSCALE);
 	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() {
 void TileSourceInspectorPlugin::_confirm_change_id() {

+ 1 - 1
editor/scene/canvas_item_editor_plugin.cpp

@@ -2797,7 +2797,7 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
 
 
 	// Grab focus
 	// Grab focus
 	if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {
 	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() {
 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();
 	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;
 	directory = p_dir;
 	root_name_edit->set_text("");
 	root_name_edit->set_text("");
 	scene_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();
 	validation_panel->update();
 
 
 	Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();
 	Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();

+ 1 - 1
editor/scene/scene_tree_editor.cpp

@@ -2274,7 +2274,7 @@ void SceneTreeDialog::_notification(int p_what) {
 				tree->update_tree();
 				tree->update_tree();
 
 
 				// Select the search bar by default.
 				// 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;
 		} 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);
 			_search_text_line_edit->set_text(text);
 			_on_search_text_modified(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();
 		_search_text_line_edit->select_all();
 	} else if (_mode == REPLACE_MODE) {
 	} else if (_mode == REPLACE_MODE) {
 		if (!text.is_empty()) {
 		if (!text.is_empty()) {
 			_search_text_line_edit->set_text(text);
 			_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();
 			_replace_text_line_edit->select_all();
 			_on_search_text_modified(text);
 			_on_search_text_modified(text);
 		} else {
 		} 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();
 			_search_text_line_edit->select_all();
 		}
 		}
 	}
 	}

+ 6 - 6
editor/script/script_text_editor.cpp

@@ -1717,27 +1717,27 @@ void ScriptTextEditor::_edit_option(int p_op) {
 	switch (p_op) {
 	switch (p_op) {
 		case EDIT_UNDO: {
 		case EDIT_UNDO: {
 			tx->undo();
 			tx->undo();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_REDO: {
 		case EDIT_REDO: {
 			tx->redo();
 			tx->redo();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_CUT: {
 		case EDIT_CUT: {
 			tx->cut();
 			tx->cut();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_COPY: {
 		case EDIT_COPY: {
 			tx->copy();
 			tx->copy();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_PASTE: {
 		case EDIT_PASTE: {
 			tx->paste();
 			tx->paste();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_SELECT_ALL: {
 		case EDIT_SELECT_ALL: {
 			tx->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;
 		} break;
 		case EDIT_MOVE_LINE_UP: {
 		case EDIT_MOVE_LINE_UP: {
 			code_editor->get_text_editor()->move_lines_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) {
 	switch (p_op) {
 		case EDIT_UNDO: {
 		case EDIT_UNDO: {
 			tx->undo();
 			tx->undo();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_REDO: {
 		case EDIT_REDO: {
 			tx->redo();
 			tx->redo();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_CUT: {
 		case EDIT_CUT: {
 			tx->cut();
 			tx->cut();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_COPY: {
 		case EDIT_COPY: {
 			tx->copy();
 			tx->copy();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_PASTE: {
 		case EDIT_PASTE: {
 			tx->paste();
 			tx->paste();
-			callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
+			callable_mp((Control *)tx, &Control::grab_focus).call_deferred(false);
 		} break;
 		} break;
 		case EDIT_SELECT_ALL: {
 		case EDIT_SELECT_ALL: {
 			tx->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;
 		} break;
 		case EDIT_MOVE_LINE_UP: {
 		case EDIT_MOVE_LINE_UP: {
 			code_editor->get_text_editor()->move_lines_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;
 		} break;
 	}
 	}
 	if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) {
 	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.
 Add new entries at the end of the file.
 
 
 ## Changes between 4.5-stable and 4.6-stable
 ## 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));
 				style->draw(ci, Rect2(Point2(), size));
 			}
 			}
 
 
-			if (has_focus()) {
+			if (has_focus(true)) {
 				theme_cache.focus->draw(ci, Rect2(Point2(), size));
 				theme_cache.focus->draw(ci, Rect2(Point2(), size));
 			}
 			}
 
 
@@ -315,7 +315,7 @@ void Button::_notification(int p_what) {
 			switch (get_draw_mode()) {
 			switch (get_draw_mode()) {
 				case DRAW_NORMAL: {
 				case DRAW_NORMAL: {
 					// Focus colors only take precedence over normal state.
 					// Focus colors only take precedence over normal state.
-					if (has_focus()) {
+					if (has_focus(true)) {
 						font_color = theme_cache.font_focus_color;
 						font_color = theme_cache.font_focus_color;
 						if (has_theme_color(SNAME("icon_focus_color"))) {
 						if (has_theme_color(SNAME("icon_focus_color"))) {
 							icon_modulate_color = theme_cache.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() {
 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() {
 void ColorPicker::set_focus_on_picker_shape() {
@@ -1491,7 +1491,7 @@ void ColorPicker::_sample_draw() {
 
 
 	sample->draw_rect(rect_new, color);
 	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();
 		RID ci = sample->get_canvas_item();
 		theme_cache.sample_focus->draw(ci, rect_old);
 		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.");
 				WARN_PRINT("Unsupported StyleBox used for ColorPresetButton. Use StyleBoxFlat or StyleBoxTexture instead.");
 			}
 			}
 
 
-			if (has_focus()) {
+			if (has_focus(true)) {
 				RID ci = get_canvas_item();
 				RID ci = get_canvas_item();
 				theme_cache.focus_style->draw(ci, Rect2(Point2(), get_size()));
 				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) {
 void ColorPickerShape::draw_focus_rect(Control *p_control, const Rect2 &p_rect) {
-	if (!p_control->has_focus()) {
+	if (!p_control->has_focus(true)) {
 		return;
 		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) {
 void ColorPickerShape::draw_focus_circle(Control *p_control) {
-	if (!p_control->has_focus()) {
+	if (!p_control->has_focus(true)) {
 		return;
 		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.h"
+#include "control.compat.inc"
 
 
 #include "container.h"
 #include "container.h"
 #include "core/config/project_settings.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);
 	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_MAIN_THREAD_GUARD;
 	ERR_FAIL_COND(!is_inside_tree());
 	ERR_FAIL_COND(!is_inside_tree());
 
 
@@ -2329,7 +2330,7 @@ void Control::grab_focus() {
 		return;
 		return;
 	}
 	}
 
 
-	get_viewport()->_gui_control_grab_focus(this);
+	get_viewport()->_gui_control_grab_focus(this, p_hide_focus);
 }
 }
 
 
 void Control::grab_click_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("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("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("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("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_prev_valid_focus"), &Control::find_prev_valid_focus);
 	ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_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_hide_tooltip(const Variant &p_data);
 	void _accessibility_action_scroll_into_view(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.
 	// Exposed virtual methods.
 
 
 	GDVIRTUAL1RC(bool, _has_point, Vector2)
 	GDVIRTUAL1RC(bool, _has_point, Vector2)
@@ -592,8 +598,8 @@ public:
 	FocusMode get_focus_mode_with_override() const;
 	FocusMode get_focus_mode_with_override() const;
 	void set_focus_behavior_recursive(FocusBehaviorRecursive p_focus_behavior_recursive);
 	void set_focus_behavior_recursive(FocusBehaviorRecursive p_focus_behavior_recursive);
 	FocusBehaviorRecursive get_focus_behavior_recursive() const;
 	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 grab_click_focus();
 	void release_focus();
 	void release_focus();
 
 

+ 3 - 3
scene/gui/file_dialog.cpp

@@ -421,9 +421,9 @@ void FileDialog::_save_confirm_pressed() {
 void FileDialog::_post_popup() {
 void FileDialog::_post_popup() {
 	ConfirmationDialog::_post_popup();
 	ConfirmationDialog::_post_popup();
 	if (mode == FILE_MODE_SAVE_FILE) {
 	if (mode == FILE_MODE_SAVE_FILE) {
-		filename_edit->grab_focus();
+		filename_edit->grab_focus(true);
 	} else {
 	} else {
-		file_list->grab_focus();
+		file_list->grab_focus(true);
 	}
 	}
 
 
 	set_process_shortcut_input(true);
 	set_process_shortcut_input(true);
@@ -2035,7 +2035,7 @@ void FileDialog::set_show_filename_filter(bool p_show) {
 		filename_filter->grab_focus();
 		filename_filter->grab_focus();
 	} else {
 	} else {
 		if (filename_filter->has_focus()) {
 		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;
 	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);
 				_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);
 				Rect2 focus_rect = folded ? title_rect : Rect2(Point2(), size);
 				_draw_flippable_stylebox(theme_cache.focus_style, focus_rect);
 				_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 background fill.
 			draw_style_box(theme_cache.panel, Rect2(Point2(), get_size()));
 			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()));
 				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> sbsel;
 			Ref<StyleBox> cursor;
 			Ref<StyleBox> cursor;
 
 
-			if (has_focus()) {
+			if (has_focus(true)) {
 				sbsel = theme_cache.selected_focus_style;
 				sbsel = theme_cache.selected_focus_style;
 				cursor = theme_cache.cursor_focus_style;
 				cursor = theme_cache.cursor_focus_style;
 			} else {
 			} else {
@@ -1507,7 +1507,7 @@ void ItemList::_notification(int p_what) {
 						draw_style_box(sbsel, r);
 						draw_style_box(sbsel, r);
 					}
 					}
 					if (should_draw_hovered_selected_bg) {
 					if (should_draw_hovered_selected_bg) {
-						if (has_focus()) {
+						if (has_focus(true)) {
 							draw_style_box(theme_cache.hovered_selected_focus_style, r);
 							draw_style_box(theme_cache.hovered_selected_focus_style, r);
 						} else {
 						} else {
 							draw_style_box(theme_cache.hovered_selected_style, r);
 							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);
 				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);
 				RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), true);
 				size.x -= (scroll_bar_h->get_max() - scroll_bar_h->get_page());
 				size.x -= (scroll_bar_h->get_max() - scroll_bar_h->get_page());
 				draw_style_box(theme_cache.focus_style, Rect2(Point2(), size));
 				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>();
 			Vector<LabelSettings::StackedShadowData> stacked_shadow_datas = has_settings ? settings->get_stacked_shadow_data() : Vector<LabelSettings::StackedShadowData>();
 			bool rtl_layout = is_layout_rtl();
 			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()));
 				theme_cache.focus_style->draw(ci, Rect2(Point2(0, 0), get_size()));
 			} else {
 			} else {
 				theme_cache.normal_style->draw(ci, Rect2(Point2(0, 0), get_size()));
 				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));
 				style->draw(ci, Rect2(Point2(), size));
 			}
 			}
 
 
-			if (has_focus()) {
+			if (has_focus(true)) {
 				theme_cache.focus->draw(ci, Rect2(Point2(), size));
 				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()) {
 			switch (get_draw_mode()) {
 				case DRAW_NORMAL: {
 				case DRAW_NORMAL: {
-					if (has_focus()) {
+					if (has_focus(true)) {
 						color = theme_cache.font_focus_color;
 						color = theme_cache.font_focus_color;
 					} else {
 					} else {
 						color = theme_cache.font_color;
 						color = theme_cache.font_color;
@@ -222,7 +222,7 @@ void LinkButton::_notification(int p_what) {
 				} break;
 				} break;
 			}
 			}
 
 
-			if (has_focus()) {
+			if (has_focus(true)) {
 				Ref<StyleBox> style = theme_cache.focus;
 				Ref<StyleBox> style = theme_cache.focus;
 				style->draw(ci, Rect2(Point2(), size));
 				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);
 			style->draw(ci, item_rect);
 		}
 		}
 		// Focus colors only take precedence over normal state.
 		// Focus colors only take precedence over normal state.
-		if (has_focus()) {
+		if (has_focus(true)) {
 			color = theme_cache.font_focus_color;
 			color = theme_cache.font_focus_color;
 		} else {
 		} else {
 			color = theme_cache.font_color;
 			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;
 						clr = theme_cache.font_disabled_color;
 						break;
 						break;
 					default:
 					default:
-						if (has_focus()) {
+						if (has_focus(true)) {
 							clr = theme_cache.font_focus_color;
 							clr = theme_cache.font_focus_color;
 						} else {
 						} else {
 							clr = theme_cache.font_color;
 							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));
 			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);
 				RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
 				draw_style_box(theme_cache.focus_style, Rect2(Point2(), size));
 				draw_style_box(theme_cache.focus_style, Rect2(Point2(), size));
 				RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
 				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();
 				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));
 				theme_cache.scroll_focus_style->draw(ci, Rect2(ofs, area));
 			} else {
 			} else {
 				theme_cache.scroll_style->draw(ci, Rect2(ofs, area));
 				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);
 		ensure_control_visible(p_control);
 	}
 	}
 	if (draw_focus_border) {
 	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) {
 		if (focus_border_is_drawn != _should_draw_focus_border) {
 			queue_redraw();
 			queue_redraw();
 		}
 		}
@@ -484,7 +484,7 @@ void ScrollContainer::_notification(int p_what) {
 
 
 		case NOTIFICATION_DRAW: {
 		case NOTIFICATION_DRAW: {
 			draw_style_box(theme_cache.panel_style, Rect2(Vector2(), get_size()));
 			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);
 			focus_panel->set_visible(focus_border_is_drawn);
 		} break;
 		} break;
 
 
@@ -815,7 +815,7 @@ bool ScrollContainer::get_draw_focus_border() {
 
 
 bool ScrollContainer::child_has_focus() {
 bool ScrollContainer::child_has_focus() {
 	const Control *focus_owner = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;
 	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() {
 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->get_button_index() == MouseButton::LEFT) {
 			if (mb->is_pressed()) {
 			if (mb->is_pressed()) {
 				Ref<Texture2D> grabber;
 				Ref<Texture2D> grabber;
-				if (mouse_inside || has_focus()) {
+				if (mouse_inside || has_focus(true)) {
 					grabber = theme_cache.grabber_hl_icon;
 					grabber = theme_cache.grabber_hl_icon;
 				} else {
 				} else {
 					grabber = theme_cache.grabber_icon;
 					grabber = theme_cache.grabber_icon;
@@ -275,7 +275,7 @@ void Slider::_notification(int p_what) {
 			Ref<StyleBox> style = theme_cache.slider_style;
 			Ref<StyleBox> style = theme_cache.slider_style;
 			Ref<Texture2D> tick = theme_cache.tick_icon;
 			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;
 			Ref<Texture2D> grabber;
 			if (editable) {
 			if (editable) {
 				if (highlighted) {
 				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) {
 			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;
 				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) {
 			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));
 				theme_cache.style_readonly->draw(ci, Rect2(Point2(), size));
 				draw_caret = is_drawing_caret_when_editable_disabled();
 				draw_caret = is_drawing_caret_when_editable_disabled();
 			}
 			}
-			if (has_focus()) {
+			if (has_focus(true)) {
 				theme_cache.style_focus->draw(ci, Rect2(Point2(), size));
 				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;
 			Point2 ofs;
 			Size2 size;
 			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.
 			// If no other texture is valid, try using focused texture.
 			bool draw_focus_only = draw_focus && texdraw.is_null();
 			bool draw_focus_only = draw_focus && texdraw.is_null();

+ 6 - 6
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 (p_item->cells[0].selected) {
 						if (is_row_hovered) {
 						if (is_row_hovered) {
-							if (has_focus()) {
+							if (has_focus(true)) {
 								theme_cache.hovered_selected_focus->draw(ci, row_rect);
 								theme_cache.hovered_selected_focus->draw(ci, row_rect);
 							} else {
 							} else {
 								theme_cache.hovered_selected->draw(ci, row_rect);
 								theme_cache.hovered_selected->draw(ci, row_rect);
 							}
 							}
 						} else {
 						} else {
-							if (has_focus()) {
+							if (has_focus(true)) {
 								theme_cache.selected_focus->draw(ci, row_rect);
 								theme_cache.selected_focus->draw(ci, row_rect);
 							} else {
 							} else {
 								theme_cache.selected->draw(ci, row_rect);
 								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 (p_item->cells[i].selected) {
 						if (is_cell_hovered) {
 						if (is_cell_hovered) {
-							if (has_focus()) {
+							if (has_focus(true)) {
 								theme_cache.hovered_selected_focus->draw(ci, r);
 								theme_cache.hovered_selected_focus->draw(ci, r);
 							} else {
 							} else {
 								theme_cache.hovered_selected->draw(ci, r);
 								theme_cache.hovered_selected->draw(ci, r);
 							}
 							}
 						} else {
 						} else {
-							if (has_focus()) {
+							if (has_focus(true)) {
 								theme_cache.selected_focus->draw(ci, r);
 								theme_cache.selected_focus->draw(ci, r);
 							} else {
 							} else {
 								theme_cache.selected->draw(ci, r);
 								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()) {
 				if (is_layout_rtl()) {
 					cell_rect.position.x = get_size().width - cell_rect.position.x - cell_rect.size.x;
 					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);
 					theme_cache.cursor->draw(ci, cell_rect);
 				} else {
 				} else {
 					theme_cache.cursor_unfocus->draw(ci, cell_rect);
 					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.
 			// 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.
 			// 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);
 				RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
 				theme_cache.focus_style->draw(ci, Rect2(Point2(), get_size()));
 				theme_cache.focus_style->draw(ci, Rect2(Point2(), get_size()));
 				RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
 				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) {
 void Viewport::_notification(int p_what) {
 	ERR_MAIN_THREAD_GUARD;
 	ERR_MAIN_THREAD_GUARD;
 
 
@@ -1914,6 +1930,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
 				gui.mouse_focus = gui_find_control(mpos);
 				gui.mouse_focus = gui_find_control(mpos);
 
 
 				if (!gui.mouse_focus) {
 				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;
 					return;
 				}
 				}
 
 
@@ -1946,8 +1968,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
 						if (control->_is_focusable()) {
 						if (control->_is_focusable()) {
 							// Grabbing unhovered focus can cause issues when mouse is dragged
 							// Grabbing unhovered focus can cause issues when mouse is dragged
 							// with another button held down.
 							// 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;
 							break;
 						}
 						}
@@ -2301,6 +2324,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
 
 
 		if (from && p_event->is_pressed()) {
 		if (from && p_event->is_pressed()) {
 			Control *next = nullptr;
 			Control *next = nullptr;
+			bool show_focus = false;
 
 
 			Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
 			Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
 			if (joypadmotion_event.is_valid()) {
 			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)) {
 				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();
 					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)) {
 				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();
 					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)) {
 				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)) {
 				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);
 					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)) {
 				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);
 					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)) {
 				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);
 					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)) {
 				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);
 					next = from->_get_focus_neighbor(SIDE_BOTTOM);
+					show_focus = true;
 				}
 				}
 			} else {
 			} else {
 				if (p_event->is_action_pressed(SNAME("ui_focus_next"), true, true)) {
 				if (p_event->is_action_pressed(SNAME("ui_focus_next"), true, true)) {
 					next = from->find_next_valid_focus();
 					next = from->find_next_valid_focus();
+					show_focus = true;
 				}
 				}
 
 
 				if (p_event->is_action_pressed(SNAME("ui_focus_prev"), true, true)) {
 				if (p_event->is_action_pressed(SNAME("ui_focus_prev"), true, true)) {
 					next = from->find_prev_valid_focus();
 					next = from->find_prev_valid_focus();
+					show_focus = true;
 				}
 				}
 
 
 				if (p_event->is_action_pressed(SNAME("ui_accessibility_drag_and_drop"), true, 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)) {
 				if (p_event->is_action_pressed(SNAME("ui_up"), true, true)) {
 					next = from->_get_focus_neighbor(SIDE_TOP);
 					next = from->_get_focus_neighbor(SIDE_TOP);
+					show_focus = true;
 				}
 				}
 
 
 				if (p_event->is_action_pressed(SNAME("ui_left"), true, true)) {
 				if (p_event->is_action_pressed(SNAME("ui_left"), true, true)) {
 					next = from->_get_focus_neighbor(SIDE_LEFT);
 					next = from->_get_focus_neighbor(SIDE_LEFT);
+					show_focus = true;
 				}
 				}
 
 
 				if (p_event->is_action_pressed(SNAME("ui_right"), true, true)) {
 				if (p_event->is_action_pressed(SNAME("ui_right"), true, true)) {
 					next = from->_get_focus_neighbor(SIDE_RIGHT);
 					next = from->_get_focus_neighbor(SIDE_RIGHT);
+					show_focus = true;
 				}
 				}
 
 
 				if (p_event->is_action_pressed(SNAME("ui_down"), true, true)) {
 				if (p_event->is_action_pressed(SNAME("ui_down"), true, true)) {
 					next = from->_get_focus_neighbor(SIDE_BOTTOM);
 					next = from->_get_focus_neighbor(SIDE_BOTTOM);
+					show_focus = true;
 				}
 				}
 			}
 			}
+
 			if (next) {
 			if (next) {
 				next->grab_focus();
 				next->grab_focus();
 				set_input_as_handled();
 				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) {
 	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;
 		return;
 	}
 	}
+
 	get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", get_base_window());
 	get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", get_base_window());
 	if (p_control->is_inside_tree() && p_control->get_viewport() == this) {
 	if (p_control->is_inside_tree() && p_control->get_viewport() == this) {
 		gui.key_focus = p_control;
 		gui.key_focus = p_control;
+		if (_can_hide_focus_state()) {
+			gui.hide_focus = p_hide_focus;
+		}
 		emit_signal(SNAME("gui_focus_changed"), p_control);
 		emit_signal(SNAME("gui_focus_changed"), p_control);
 		p_control->notification(Control::NOTIFICATION_FOCUS_ENTER);
 		p_control->notification(Control::NOTIFICATION_FOCUS_ENTER);
 		p_control->queue_redraw();
 		p_control->queue_redraw();
@@ -5386,6 +5435,8 @@ Viewport::Viewport() {
 	// Viewports can thus inherit physics interpolation OFF, which is unexpected.
 	// Viewports can thus inherit physics interpolation OFF, which is unexpected.
 	// Setting to ON allows each viewport to have a fresh interpolation state.
 	// Setting to ON allows each viewport to have a fresh interpolation state.
 	set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_ON);
 	set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_ON);
+
+	ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Viewport::_on_settings_changed));
 }
 }
 
 
 Viewport::~Viewport() {
 Viewport::~Viewport() {

+ 6 - 2
scene/main/viewport.h

@@ -330,6 +330,9 @@ private:
 
 
 	void _update_viewport_path();
 	void _update_viewport_path();
 
 
+	bool _can_hide_focus_state();
+	void _on_settings_changed();
+
 	SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT;
 	SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT;
 	SDFScale sdf_scale = SDF_SCALE_50_PERCENT;
 	SDFScale sdf_scale = SDF_SCALE_50_PERCENT;
 
 
@@ -374,6 +377,7 @@ private:
 		Control *mouse_click_grabber = nullptr;
 		Control *mouse_click_grabber = nullptr;
 		BitField<MouseButtonMask> mouse_focus_mask = MouseButtonMask::NONE;
 		BitField<MouseButtonMask> mouse_focus_mask = MouseButtonMask::NONE;
 		Control *key_focus = nullptr;
 		Control *key_focus = nullptr;
+		bool hide_focus = false;
 		Control *mouse_over = nullptr;
 		Control *mouse_over = nullptr;
 		LocalVector<Control *> mouse_over_hierarchy;
 		LocalVector<Control *> mouse_over_hierarchy;
 		bool sending_mouse_enter_exit_notifications = false;
 		bool sending_mouse_enter_exit_notifications = false;
@@ -459,8 +463,8 @@ private:
 
 
 	void _gui_remove_focus_for_window(Node *p_window);
 	void _gui_remove_focus_for_window(Node *p_window);
 	void _gui_unfocus_control(Control *p_control);
 	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 _gui_grab_click_focus(Control *p_control);
 	void _post_gui_grab_click_focus();
 	void _post_gui_grab_click_focus();
 	void _gui_accept_event();
 	void _gui_accept_event();