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