diff --git a/doc/classes/EditorInspector.xml b/doc/classes/EditorInspector.xml
index 6b25be490ed..652c894b6ea 100644
--- a/doc/classes/EditorInspector.xml
+++ b/doc/classes/EditorInspector.xml
@@ -28,6 +28,7 @@
+
diff --git a/doc/classes/ScrollContainer.xml b/doc/classes/ScrollContainer.xml
index 405bba35643..873dc68e86a 100644
--- a/doc/classes/ScrollContainer.xml
+++ b/doc/classes/ScrollContainer.xml
@@ -107,6 +107,9 @@
+
+ The focus border [StyleBox] of the [ScrollContainer].
+
The background [StyleBox] of the [ScrollContainer].
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 21f67772ea2..849d65dc049 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -4306,6 +4306,7 @@ EditorInspector::EditorInspector() {
search_box = nullptr;
_prop_edited = "property_edited";
set_process(false);
+ set_focus_mode(FocusMode::FOCUS_ALL);
property_focusable = -1;
property_clipboard = Variant();
@@ -4324,4 +4325,6 @@ EditorInspector::EditorInspector() {
// `use_settings_name_style` is true by default, set the name style accordingly.
set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style());
+
+ set_draw_focus_border(true);
}
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index c5a35e466c9..1dc77c33340 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -3263,6 +3263,8 @@ void EditorPropertyResource::update_property() {
sub_inspector->set_read_only(is_read_only());
sub_inspector->set_use_folding(is_using_folding());
+ sub_inspector->set_draw_focus_border(false);
+
sub_inspector->set_mouse_filter(MOUSE_FILTER_STOP);
add_child(sub_inspector);
set_bottom_editor(sub_inspector);
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 17bcbacfc20..bac540cdf41 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -1811,11 +1811,19 @@ void EditorThemeManager::_populate_editor_styles(const Ref &p_theme
// Editor focus.
p_theme->set_stylebox("Focus", EditorStringName(EditorStyles), p_config.button_style_focus);
- // Use a less opaque color to be less distracting for the 2D and 3D editor viewports.
+
Ref style_widget_focus_viewport = p_config.button_style_focus->duplicate();
+ // Make the focus outline appear to be flush with the buttons it's focusing, so not draw on top of the content.
+ style_widget_focus_viewport->set_expand_margin_all(2);
+ // Use a less opaque color to be less distracting for the 2D and 3D editor viewports.
style_widget_focus_viewport->set_border_color(p_config.accent_color * Color(1, 1, 1, 0.5));
p_theme->set_stylebox("FocusViewport", EditorStringName(EditorStyles), style_widget_focus_viewport);
+ Ref style_widget_scroll_container = p_config.button_style_focus->duplicate();
+ // Make the focus outline appear to be flush with the buttons it's focusing, so not draw on top of the content.
+ style_widget_scroll_container->set_expand_margin_all(4);
+ p_theme->set_stylebox(SNAME("focus"), "ScrollContainer", style_widget_scroll_container);
+
// This stylebox is used in 3d and 2d viewports (no borders).
Ref style_content_panel_vp = p_config.content_panel_style->duplicate();
style_content_panel_vp->set_content_margin_individual(p_config.border_width * 2, p_config.base_margin * EDSCALE, p_config.border_width * 2, p_config.border_width * 2);
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 1ac0e8b59fa..c68c4db3208 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -281,6 +281,7 @@ void ScrollContainer::_gui_focus_changed(Control *p_control) {
if (follow_focus && is_ancestor_of(p_control)) {
ensure_control_visible(p_control);
}
+ queue_redraw();
}
void ScrollContainer::ensure_control_visible(Control *p_control) {
@@ -366,6 +367,14 @@ void ScrollContainer::_notification(int p_what) {
case NOTIFICATION_DRAW: {
draw_style_box(theme_cache.panel_style, Rect2(Vector2(), get_size()));
+#ifdef TOOLS_ENABLED
+ if (_draw_focus_border && Engine::get_singleton()->is_editor_hint() && (has_focus() || child_has_focus())) {
+ RID ci = get_canvas_item();
+ RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
+ draw_style_box(theme_cache.focus_style, Rect2(Point2(), get_size()));
+ RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
+ }
+#endif
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
@@ -623,10 +632,26 @@ void ScrollContainer::_bind_methods() {
BIND_ENUM_CONSTANT(SCROLL_MODE_RESERVE);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, panel_style, "panel");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, focus_style, "focus");
GLOBAL_DEF("gui/common/default_scroll_deadzone", 0);
};
+#ifdef TOOLS_ENABLED
+void ScrollContainer::set_draw_focus_border(bool p_draw) {
+ _draw_focus_border = p_draw;
+}
+
+bool ScrollContainer::get_draw_focus_border() {
+ return _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);
+}
+#endif
+
ScrollContainer::ScrollContainer() {
h_scroll = memnew(HScrollBar);
h_scroll->set_name("_h_scroll");
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
index afd3c8bd57a..5885f5bb24b 100644
--- a/scene/gui/scroll_container.h
+++ b/scene/gui/scroll_container.h
@@ -72,6 +72,7 @@ private:
struct ThemeCache {
Ref panel_style;
+ Ref focus_style;
} theme_cache;
void _cancel_drag();
@@ -79,6 +80,11 @@ private:
bool _is_h_scroll_visible() const;
bool _is_v_scroll_visible() const;
+#ifdef TOOLS_ENABLED
+ bool _draw_focus_border = false;
+ bool child_has_focus();
+#endif
+
protected:
Size2 get_minimum_size() const override;
@@ -125,6 +131,11 @@ public:
PackedStringArray get_configuration_warnings() const override;
+#ifdef TOOLS_ENABLED
+ void set_draw_focus_border(bool p_draw);
+ bool get_draw_focus_border();
+#endif
+
ScrollContainer();
};
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index caf44ac392f..918b3ba1624 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -156,7 +156,7 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const
const Ref button_pressed = make_flat_stylebox(style_pressed_color);
const Ref button_disabled = make_flat_stylebox(style_disabled_color);
Ref focus = make_flat_stylebox(style_focus_color, default_margin, default_margin, default_margin, default_margin, default_corner_radius, false, 2);
- // Make the focus outline appear to be flush with the buttons it's focusing.
+ // Make the focus outline appear to be flush with the buttons it's focusing, so not draw on top of the content.
focus->set_expand_margin_all(Math::round(2 * scale));
theme->set_stylebox(CoreStringName(normal), "Button", button_normal);
@@ -657,6 +657,7 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const
Ref empty;
empty.instantiate();
theme->set_stylebox(SceneStringName(panel), "ScrollContainer", empty);
+ theme->set_stylebox(SNAME("focus"), "ScrollContainer", empty);
// Window
diff --git a/tests/scene/test_control.h b/tests/scene/test_control.h
index 3d7e389e0a7..557c5b232cf 100644
--- a/tests/scene/test_control.h
+++ b/tests/scene/test_control.h
@@ -61,6 +61,57 @@ TEST_CASE("[SceneTree][Control]") {
}
}
+TEST_CASE("[SceneTree][Control] Focus") {
+ Control *ctrl = memnew(Control);
+ SceneTree::get_singleton()->get_root()->add_child(ctrl);
+
+ SUBCASE("[SceneTree][Control] Default focus") {
+ CHECK_FALSE(ctrl->has_focus());
+ }
+
+ SUBCASE("[SceneTree][Control] Can't grab focus with default focus mode") {
+ ERR_PRINT_OFF
+ ctrl->grab_focus();
+ ERR_PRINT_ON
+
+ CHECK_FALSE(ctrl->has_focus());
+ }
+
+ SUBCASE("[SceneTree][Control] Can grab focus") {
+ ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL);
+ ctrl->grab_focus();
+
+ CHECK(ctrl->has_focus());
+ }
+
+ SUBCASE("[SceneTree][Control] Can release focus") {
+ ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL);
+ ctrl->grab_focus();
+ CHECK(ctrl->has_focus());
+
+ ctrl->release_focus();
+ CHECK_FALSE(ctrl->has_focus());
+ }
+
+ SUBCASE("[SceneTree][Control] Only one can grab focus at the same time") {
+ ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL);
+ ctrl->grab_focus();
+ CHECK(ctrl->has_focus());
+
+ Control *other_ctrl = memnew(Control);
+ SceneTree::get_singleton()->get_root()->add_child(other_ctrl);
+ other_ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL);
+ other_ctrl->grab_focus();
+
+ CHECK(other_ctrl->has_focus());
+ CHECK_FALSE(ctrl->has_focus());
+
+ memdelete(other_ctrl);
+ }
+
+ memdelete(ctrl);
+}
+
} // namespace TestControl
#endif // TEST_CONTROL_H