From d16e8b7ca4dd939541607aaa4edf41c9a5f187bc Mon Sep 17 00:00:00 2001 From: kit Date: Wed, 9 Apr 2025 12:40:40 -0400 Subject: [PATCH] Fix and rename mouse filter recursive behavior and focus mode recursive behavior. --- doc/classes/Control.xml | 45 ++-- .../tiles/tile_set_atlas_source_editor.cpp | 2 +- scene/gui/control.cpp | 222 +++++++++--------- scene/gui/control.h | 49 ++-- scene/gui/graph_edit.cpp | 2 +- scene/gui/slider.cpp | 4 +- scene/gui/tab_container.cpp | 2 +- scene/main/viewport.cpp | 34 +-- tests/scene/test_control.h | 56 +++++ tests/scene/test_viewport.h | 39 +++ 10 files changed, 282 insertions(+), 173 deletions(-) diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index c33bbfb6379..1ad5d03fc5f 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -415,10 +415,10 @@ Returns [member offset_right] and [member offset_bottom]. - + - Similar to the getter of [member focus_mode], but takes parent's [member focus_recursive_behavior] into account. + Returns the [member focus_mode], but takes the [member focus_behavior_recursive] into account. If [member focus_behavior_recursive] is set to [constant FOCUS_BEHAVIOR_DISABLED], or it is set to [constant FOCUS_BEHAVIOR_INHERITED] and its ancestor is set to [constant FOCUS_BEHAVIOR_DISABLED], then this returns [constant FOCUS_NONE]. @@ -443,10 +443,10 @@ Returns the minimum size for this control. See [member custom_minimum_size]. - + - Similar to the getter of [member mouse_filter], but takes parent's [member mouse_recursive_behavior] into account. + Returns the [member mouse_filter], but takes the [member mouse_behavior_recursive] into account. If [member mouse_behavior_recursive] is set to [constant MOUSE_BEHAVIOR_DISABLED], or it is set to [constant MOUSE_BEHAVIOR_INHERITED] and its ancestor is set to [constant MOUSE_BEHAVIOR_DISABLED], then this returns [constant MOUSE_FILTER_IGNORE]. @@ -983,8 +983,11 @@ The minimum size of the node's bounding rectangle. If you set it to a value greater than [code](0, 0)[/code], the node's bounding rectangle will always have at least this size. Note that [Control] nodes have their internal minimum size returned by [method get_minimum_size]. It depends on the control's contents, like text, textures, or style boxes. The actual minimum size is the maximum value of this property and the internal minimum size (see [method get_combined_minimum_size]). + + Determines which controls can be focused together with [member focus_mode]. See [method get_focus_mode_with_override]. Since the default behavior is [constant FOCUS_BEHAVIOR_INHERITED], this can be used to prevent all children controls from getting focused. + - The focus access mode for the control (None, Click or All). Only one Control can be focused at the same time, and it will receive keyboard, gamepad, and mouse signals. + Determines which controls can be focused. Only one control can be focused at a time, and the focused control will receive keyboard, gamepad, and mouse events in [method _gui_input]. Use [method get_focus_mode_with_override] to determine if a control can grab focus, since [member focus_behavior_recursive] also affects it. See also [method grab_focus]. Tells Godot which node it should give focus to if the user presses the down arrow on the keyboard or down on a gamepad by default. You can change the key by editing the [member ProjectSettings.input/ui_down] input action. The node must be a [Control]. If this property is not set, Godot will give focus to the closest [Control] to the bottom of this one. @@ -1006,9 +1009,6 @@ Tells Godot which node it should give focus to if the user presses [kbd]Shift + Tab[/kbd] on a keyboard by default. You can change the key by editing the [member ProjectSettings.input/ui_focus_prev] input action. If this property is not set, Godot will select a "best guess" based on surrounding nodes in the scene tree. - - Controls whether the recursive child nodes should have their [member focus_mode] overridden to [constant FOCUS_NONE] when evaluating input. - The node's global position, relative to the world (usually to the [CanvasLayer]). @@ -1025,21 +1025,21 @@ If [code]true[/code], automatically converts code line numbers, list indices, [SpinBox] and [ProgressBar] values from the Western Arabic (0..9) to the numeral systems used in current locale. [b]Note:[/b] Numbers within the text are not automatically converted, it can be done manually, using [method TextServer.format_number]. + + Determines which controls can receive mouse input together with [member mouse_filter]. See [method get_mouse_filter_with_override]. Since the default behavior is [constant MOUSE_BEHAVIOR_INHERITED], this can be used to prevent all children controls from receiving mouse input. + The default cursor shape for this control. Useful for Godot plugins and applications or games that use the system's mouse cursors. [b]Note:[/b] On Linux, shapes may vary depending on the cursor theme of the system. - Controls whether the control will be able to receive mouse button input events through [method _gui_input] and how these events should be handled. Also controls whether the control can receive the [signal mouse_entered], and [signal mouse_exited] signals. See the constants to learn what each does. + Determines which controls will be able to receive mouse button input events through [method _gui_input] and the [signal mouse_entered], and [signal mouse_exited] signals. Also determines how these events should be propagated. See the constants to learn what each does. Use [method get_mouse_filter_with_override] to determine if a control can receive mouse input, since [member mouse_behavior_recursive] also affects it. When enabled, scroll wheel events processed by [method _gui_input] will be passed to the parent control even if [member mouse_filter] is set to [constant MOUSE_FILTER_STOP]. You should disable it on the root of your UI if you do not want scroll events to go to the [method Node._unhandled_input] processing. [b]Note:[/b] Because this property defaults to [code]true[/code], this allows nested scrollable containers to work out of the box. - - Controls whether the recursive child nodes should have their [member mouse_filter] overridden to [constant MOUSE_FILTER_IGNORE] when evaluating input. - Distance between the node's bottom edge and its parent control, based on [member anchor_bottom]. Offsets are often controlled by one or multiple parent [Container] nodes, so you should not modify them manually if your node is a direct child of a [Container]. Offsets update automatically when you move or resize the node. @@ -1198,14 +1198,23 @@ The node can grab focus only when screen reader is active. Use with [member focus_mode]. - - Inherits the associated behavior from the control's parent. This is the default for any newly created control. + + Inherits the [member focus_behavior_recursive] from the parent control. If there is no parent control, this is the same as [constant FOCUS_BEHAVIOR_ENABLED]. - - The current control and all its recursive child controls have their associated behavior disabled, regardless of the parent control's configuration. + + Prevents the control from getting focused. [method get_focus_mode_with_override] will return [constant FOCUS_NONE]. - - The current control and all its recursive child controls have their associated behavior enabled, regardless of the parent control's configuration. + + Allows the control to be focused, depending on the [member focus_mode]. This can be used to ignore the parent's [member focus_behavior_recursive]. [method get_focus_mode_with_override] will return the [member focus_mode]. + + + Inherits the [member mouse_behavior_recursive] from the parent control. If there is no parent control, this is the same as [constant MOUSE_BEHAVIOR_ENABLED]. + + + Prevents the control from receiving mouse input. [method get_mouse_filter_with_override] will return [constant MOUSE_FILTER_IGNORE]. + + + Allows the control to be receive mouse input, depending on the [member mouse_filter]. This can be used to ignore the parent's [member mouse_behavior_recursive]. [method get_mouse_filter_with_override] will return the [member mouse_filter]. Sent when the node changes size. Use [member size] to get the new size. diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index c56d97e6a19..6fd24a38642 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -2764,7 +2764,7 @@ TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() { void EditorPropertyTilePolygon::_add_focusable_children(Node *p_node) { Control *control = Object::cast_to(p_node); - if (control && control->get_focus_mode_with_recursive() != Control::FOCUS_NONE) { + if (control && control->get_focus_mode_with_override() != Control::FOCUS_NONE) { add_focusable(control); } for (int i = 0; i < p_node->get_child_count(); i++) { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 3e1f89239fc..ffd2e8ec735 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1884,44 +1884,67 @@ Control::MouseFilter Control::get_mouse_filter() const { return data.mouse_filter; } -Control::MouseFilter Control::get_mouse_filter_with_recursive() const { +Control::MouseFilter Control::get_mouse_filter_with_override() const { ERR_READ_THREAD_GUARD_V(MOUSE_FILTER_IGNORE); - if (_is_parent_mouse_disabled()) { + if (!_is_mouse_filter_enabled()) { return MOUSE_FILTER_IGNORE; } return data.mouse_filter; } -void Control::set_mouse_recursive_behavior(RecursiveBehavior p_recursive_mouse_behavior) { +void Control::set_mouse_behavior_recursive(MouseBehaviorRecursive p_mouse_behavior_recursive) { ERR_MAIN_THREAD_GUARD; - ERR_FAIL_INDEX((int)p_recursive_mouse_behavior, 4); - if (data.mouse_recursive_behavior == p_recursive_mouse_behavior) { + ERR_FAIL_INDEX(p_mouse_behavior_recursive, 3); + if (data.mouse_behavior_recursive == p_mouse_behavior_recursive) { return; } - _set_mouse_recursive_behavior_ignore_cache(p_recursive_mouse_behavior); + data.mouse_behavior_recursive = p_mouse_behavior_recursive; + _update_mouse_behavior_recursive(); } -void Control::_set_mouse_recursive_behavior_ignore_cache(RecursiveBehavior p_recursive_mouse_behavior) { - data.mouse_recursive_behavior = p_recursive_mouse_behavior; - if (p_recursive_mouse_behavior == RECURSIVE_BEHAVIOR_INHERITED) { - Control *parent = get_parent_control(); - if (parent) { - _propagate_mouse_behavior_recursively(parent->data.parent_mouse_recursive_behavior, false); +Control::MouseBehaviorRecursive Control::get_mouse_behavior_recursive() const { + ERR_READ_THREAD_GUARD_V(MOUSE_BEHAVIOR_INHERITED); + return data.mouse_behavior_recursive; +} + +bool Control::_is_mouse_filter_enabled() const { + ERR_READ_THREAD_GUARD_V(false); + if (data.mouse_behavior_recursive == MOUSE_BEHAVIOR_INHERITED) { + if (data.parent_control) { + return data.parent_mouse_behavior_recursive_enabled; + } + return true; + } + return data.mouse_behavior_recursive == MOUSE_BEHAVIOR_ENABLED; +} + +void Control::_update_mouse_behavior_recursive() { + if (data.mouse_behavior_recursive == MOUSE_BEHAVIOR_INHERITED) { + if (data.parent_control) { + _propagate_mouse_behavior_recursive_recursively(data.parent_control->_is_mouse_filter_enabled(), false); } else { - _propagate_mouse_behavior_recursively(RECURSIVE_BEHAVIOR_ENABLED, false); + _propagate_mouse_behavior_recursive_recursively(true, false); } } else { - _propagate_mouse_behavior_recursively(p_recursive_mouse_behavior, false); + _propagate_mouse_behavior_recursive_recursively(data.mouse_behavior_recursive == MOUSE_BEHAVIOR_ENABLED, false); } - if (get_viewport()) { get_viewport()->_gui_update_mouse_over(); } } -Control::RecursiveBehavior Control::get_mouse_recursive_behavior() const { - ERR_READ_THREAD_GUARD_V(RECURSIVE_BEHAVIOR_INHERITED); - return data.mouse_recursive_behavior; +void Control::_propagate_mouse_behavior_recursive_recursively(bool p_enabled, bool p_skip_non_inherited) { + if (p_skip_non_inherited && data.mouse_behavior_recursive != MOUSE_BEHAVIOR_INHERITED) { + return; + } + + data.parent_mouse_behavior_recursive_enabled = p_enabled; + for (int i = 0; i < get_child_count(); i++) { + Control *control = Object::cast_to(get_child(i)); + if (control) { + control->_propagate_mouse_behavior_recursive_recursively(p_enabled, true); + } + } } void Control::set_force_pass_scroll_events(bool p_force_pass_scroll_events) { @@ -2105,40 +2128,69 @@ Control::FocusMode Control::get_focus_mode() const { return data.focus_mode; } -Control::FocusMode Control::get_focus_mode_with_recursive() const { +Control::FocusMode Control::get_focus_mode_with_override() const { ERR_READ_THREAD_GUARD_V(FOCUS_NONE); - if (_is_focus_disabled_recursively()) { + if (!_is_focus_mode_enabled()) { return FOCUS_NONE; } return data.focus_mode; } -void Control::set_focus_recursive_behavior(RecursiveBehavior p_recursive_focus_behavior) { +void Control::set_focus_behavior_recursive(FocusBehaviorRecursive p_focus_behavior_recursive) { ERR_MAIN_THREAD_GUARD; - ERR_FAIL_INDEX((int)p_recursive_focus_behavior, 4); - if (data.focus_recursive_behavior == p_recursive_focus_behavior) { + ERR_FAIL_INDEX((int)p_focus_behavior_recursive, 3); + if (data.focus_behavior_recursive == p_focus_behavior_recursive) { return; } - _set_focus_recursive_behavior_ignore_cache(p_recursive_focus_behavior); + data.focus_behavior_recursive = p_focus_behavior_recursive; + _update_focus_behavior_recursive(); } -void Control::_set_focus_recursive_behavior_ignore_cache(RecursiveBehavior p_recursive_focus_behavior) { - data.focus_recursive_behavior = p_recursive_focus_behavior; - if (p_recursive_focus_behavior == RECURSIVE_BEHAVIOR_INHERITED) { +Control::FocusBehaviorRecursive Control::get_focus_behavior_recursive() const { + ERR_READ_THREAD_GUARD_V(FOCUS_BEHAVIOR_INHERITED); + return data.focus_behavior_recursive; +} + +bool Control::_is_focus_mode_enabled() const { + if (data.focus_behavior_recursive == FOCUS_BEHAVIOR_INHERITED) { + if (data.parent_control) { + return data.parent_focus_behavior_recursive_enabled; + } + return true; + } + return data.focus_behavior_recursive == FOCUS_BEHAVIOR_ENABLED; +} + +void Control::_update_focus_behavior_recursive() { + if (data.focus_behavior_recursive == FOCUS_BEHAVIOR_INHERITED) { Control *parent = get_parent_control(); if (parent) { - _propagate_focus_behavior_recursively(parent->data.parent_focus_recursive_behavior, false); + _propagate_focus_behavior_recursive_recursively(parent->_is_focus_mode_enabled(), false); } else { - _propagate_focus_behavior_recursively(RECURSIVE_BEHAVIOR_ENABLED, false); + _propagate_focus_behavior_recursive_recursively(true, false); } } else { - _propagate_focus_behavior_recursively(p_recursive_focus_behavior, false); + _propagate_focus_behavior_recursive_recursively(data.focus_behavior_recursive == FOCUS_BEHAVIOR_ENABLED, false); } } -Control::RecursiveBehavior Control::get_focus_recursive_behavior() const { - ERR_READ_THREAD_GUARD_V(RECURSIVE_BEHAVIOR_INHERITED); - return data.focus_recursive_behavior; +void Control::_propagate_focus_behavior_recursive_recursively(bool p_enabled, bool p_skip_non_inherited) { + if (is_inside_tree() && (data.focus_behavior_recursive == FOCUS_BEHAVIOR_DISABLED || (data.focus_behavior_recursive == FOCUS_BEHAVIOR_INHERITED && !p_enabled)) && has_focus()) { + release_focus(); + } + + if (p_skip_non_inherited && data.focus_behavior_recursive != FOCUS_BEHAVIOR_INHERITED) { + return; + } + + data.parent_focus_behavior_recursive_enabled = p_enabled; + + for (int i = 0; i < get_child_count(); i++) { + Control *control = Object::cast_to(get_child(i)); + if (control) { + control->_propagate_focus_behavior_recursive_recursively(p_enabled, true); + } + } } bool Control::has_focus() const { @@ -2150,8 +2202,8 @@ void Control::grab_focus() { ERR_MAIN_THREAD_GUARD; ERR_FAIL_COND(!is_inside_tree()); - if (data.focus_mode == FOCUS_NONE) { - WARN_PRINT("This control can't grab focus. Use set_focus_mode() to allow a control to get focus."); + if (get_focus_mode_with_override() == FOCUS_NONE) { + WARN_PRINT("This control can't grab focus. Use set_focus_mode() and set_focus_behavior_recursive() to allow a control to get focus."); return; } @@ -2211,7 +2263,7 @@ Control *Control::find_next_valid_focus() const { ERR_FAIL_NULL_V_MSG(n, nullptr, "Next focus node path is invalid: '" + data.focus_next + "'."); Control *c = Object::cast_to(n); ERR_FAIL_NULL_V_MSG(c, nullptr, "Next focus node is not a control: '" + n->get_name() + "'."); - if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE) { + if (c->is_visible_in_tree() && c->get_focus_mode_with_override() != FOCUS_NONE) { return c; } } @@ -2278,7 +2330,7 @@ Control *Control::find_next_valid_focus() const { break; } - if ((next_child->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && next_child->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) { + if ((next_child->get_focus_mode_with_override() == FOCUS_ALL) || (ac_enabled && next_child->get_focus_mode_with_override() == FOCUS_ACCESSIBILITY)) { return next_child; } @@ -2315,7 +2367,7 @@ Control *Control::find_prev_valid_focus() const { ERR_FAIL_NULL_V_MSG(n, nullptr, "Previous focus node path is invalid: '" + data.focus_prev + "'."); Control *c = Object::cast_to(n); ERR_FAIL_NULL_V_MSG(c, nullptr, "Previous focus node is not a control: '" + n->get_name() + "'."); - if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE) { + if (c->is_visible_in_tree() && c->get_focus_mode_with_override() != FOCUS_NONE) { return c; } } @@ -2376,7 +2428,7 @@ Control *Control::find_prev_valid_focus() const { } } - if ((prev_child->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && prev_child->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) { + if ((prev_child->get_focus_mode_with_override() == FOCUS_ALL) || (ac_enabled && prev_child->get_focus_mode_with_override() == FOCUS_ACCESSIBILITY)) { return prev_child; } @@ -2435,7 +2487,7 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) { ERR_FAIL_NULL_V_MSG(n, nullptr, "Neighbor focus node path is invalid: '" + data.focus_neighbor[p_side] + "'."); Control *c = Object::cast_to(n); ERR_FAIL_NULL_V_MSG(c, nullptr, "Neighbor focus node is not a control: '" + n->get_name() + "'."); - if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE) { + if (c->is_visible_in_tree() && c->get_focus_mode_with_override() != FOCUS_NONE) { return c; } @@ -2544,64 +2596,6 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) { return result; } -bool Control::_is_focus_disabled_recursively() const { - switch (data.focus_recursive_behavior) { - case RECURSIVE_BEHAVIOR_INHERITED: - return data.parent_focus_recursive_behavior == RECURSIVE_BEHAVIOR_DISABLED; - case RECURSIVE_BEHAVIOR_DISABLED: - return true; - case RECURSIVE_BEHAVIOR_ENABLED: - return false; - } - return false; -} - -void Control::_propagate_focus_behavior_recursively(RecursiveBehavior p_focus_recursive_behavior, bool p_skip_non_inherited) { - if (is_inside_tree() && (data.focus_recursive_behavior == RECURSIVE_BEHAVIOR_DISABLED || (data.focus_recursive_behavior == RECURSIVE_BEHAVIOR_INHERITED && p_focus_recursive_behavior == RECURSIVE_BEHAVIOR_DISABLED)) && has_focus()) { - release_focus(); - } - - if (p_skip_non_inherited && data.focus_recursive_behavior != RECURSIVE_BEHAVIOR_INHERITED) { - return; - } - - data.parent_focus_recursive_behavior = p_focus_recursive_behavior; - - for (int i = 0; i < get_child_count(); i++) { - Control *control = Object::cast_to(get_child(i)); - if (control) { - control->_propagate_focus_behavior_recursively(p_focus_recursive_behavior, true); - } - } -} - -bool Control::_is_parent_mouse_disabled() const { - switch (data.mouse_recursive_behavior) { - case RECURSIVE_BEHAVIOR_INHERITED: - return data.parent_mouse_recursive_behavior == RECURSIVE_BEHAVIOR_DISABLED; - case RECURSIVE_BEHAVIOR_DISABLED: - return true; - case RECURSIVE_BEHAVIOR_ENABLED: - return false; - } - return false; -} - -void Control::_propagate_mouse_behavior_recursively(RecursiveBehavior p_mouse_recursive_behavior, bool p_skip_non_inherited) { - if (p_skip_non_inherited && data.mouse_recursive_behavior != RECURSIVE_BEHAVIOR_INHERITED) { - return; - } - - data.parent_mouse_recursive_behavior = p_mouse_recursive_behavior; - - for (int i = 0; i < get_child_count(); i++) { - Control *control = Object::cast_to(get_child(i)); - if (control) { - control->_propagate_mouse_behavior_recursively(p_mouse_recursive_behavior, true); - } - } -} - Control *Control::find_valid_focus_neighbor(Side p_side) const { return const_cast(this)->_get_focus_neighbor(p_side); } @@ -2617,7 +2611,7 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons Container *container = Object::cast_to(p_at); bool in_container = container ? container->is_ancestor_of(this) : false; - if (c && c != this && ((c->get_focus_mode_with_recursive() == FOCUS_ALL) || (ac_enabled && c->get_focus_mode_with_recursive() == FOCUS_ACCESSIBILITY)) && !in_container && p_clamp.intersects(c->get_global_rect())) { + if (c && c != this && ((c->get_focus_mode_with_override() == FOCUS_ALL) || (ac_enabled && c->get_focus_mode_with_override() == FOCUS_ACCESSIBILITY)) && !in_container && p_clamp.intersects(c->get_global_rect())) { Rect2 r_c = c->get_global_rect(); r_c = r_c.intersection(p_clamp); real_t begin_d = p_dir.dot(r_c.get_position()); @@ -3620,8 +3614,8 @@ void Control::_notification(int p_notification) { _update_layout_mode(); - _set_focus_recursive_behavior_ignore_cache(data.focus_recursive_behavior); - _set_mouse_recursive_behavior_ignore_cache(data.mouse_recursive_behavior); + _update_focus_behavior_recursive(); + _update_mouse_behavior_recursive(); } break; case NOTIFICATION_UNPARENTED: { @@ -3842,9 +3836,9 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("get_global_rect"), &Control::get_global_rect); ClassDB::bind_method(D_METHOD("set_focus_mode", "mode"), &Control::set_focus_mode); ClassDB::bind_method(D_METHOD("get_focus_mode"), &Control::get_focus_mode); - ClassDB::bind_method(D_METHOD("get_focus_mode_with_recursive"), &Control::get_focus_mode_with_recursive); - ClassDB::bind_method(D_METHOD("set_focus_recursive_behavior", "focus_recursive_behavior"), &Control::set_focus_recursive_behavior); - ClassDB::bind_method(D_METHOD("get_focus_recursive_behavior"), &Control::get_focus_recursive_behavior); + 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("release_focus"), &Control::release_focus); @@ -3943,10 +3937,10 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mouse_filter", "filter"), &Control::set_mouse_filter); ClassDB::bind_method(D_METHOD("get_mouse_filter"), &Control::get_mouse_filter); - ClassDB::bind_method(D_METHOD("get_mouse_filter_with_recursive"), &Control::get_mouse_filter_with_recursive); + ClassDB::bind_method(D_METHOD("get_mouse_filter_with_override"), &Control::get_mouse_filter_with_override); - ClassDB::bind_method(D_METHOD("set_mouse_recursive_behavior", "mouse_recursive_behavior"), &Control::set_mouse_recursive_behavior); - ClassDB::bind_method(D_METHOD("get_mouse_recursive_behavior"), &Control::get_mouse_recursive_behavior); + ClassDB::bind_method(D_METHOD("set_mouse_behavior_recursive", "mouse_behavior_recursive"), &Control::set_mouse_behavior_recursive); + ClassDB::bind_method(D_METHOD("get_mouse_behavior_recursive"), &Control::get_mouse_behavior_recursive); ClassDB::bind_method(D_METHOD("set_force_pass_scroll_events", "force_pass_scroll_events"), &Control::set_force_pass_scroll_events); ClassDB::bind_method(D_METHOD("is_force_pass_scroll_events"), &Control::is_force_pass_scroll_events); @@ -4043,11 +4037,11 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_next", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_next", "get_focus_next"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_previous", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_previous", "get_focus_previous"); ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All,Accessibility"), "set_focus_mode", "get_focus_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_recursive_behavior", PROPERTY_HINT_ENUM, "Inherited,Disabled,Enabled"), "set_focus_recursive_behavior", "get_focus_recursive_behavior"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_behavior_recursive", PROPERTY_HINT_ENUM, "Inherited,Disabled,Enabled"), "set_focus_behavior_recursive", "get_focus_behavior_recursive"); ADD_GROUP("Mouse", "mouse_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass (Propagate Up),Ignore"), "set_mouse_filter", "get_mouse_filter"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_recursive_behavior", PROPERTY_HINT_ENUM, "Inherited,Disabled,Enabled"), "set_mouse_recursive_behavior", "get_mouse_recursive_behavior"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_behavior_recursive", PROPERTY_HINT_ENUM, "Inherited,Disabled,Enabled"), "set_mouse_behavior_recursive", "get_mouse_behavior_recursive"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); @@ -4063,9 +4057,13 @@ void Control::_bind_methods() { BIND_ENUM_CONSTANT(FOCUS_ALL); BIND_ENUM_CONSTANT(FOCUS_ACCESSIBILITY); - BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_INHERITED); - BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_DISABLED); - BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_ENABLED); + BIND_ENUM_CONSTANT(FOCUS_BEHAVIOR_INHERITED); + BIND_ENUM_CONSTANT(FOCUS_BEHAVIOR_DISABLED); + BIND_ENUM_CONSTANT(FOCUS_BEHAVIOR_ENABLED); + + BIND_ENUM_CONSTANT(MOUSE_BEHAVIOR_INHERITED); + BIND_ENUM_CONSTANT(MOUSE_BEHAVIOR_DISABLED); + BIND_ENUM_CONSTANT(MOUSE_BEHAVIOR_ENABLED); BIND_CONSTANT(NOTIFICATION_RESIZED); BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER); diff --git a/scene/gui/control.h b/scene/gui/control.h index 4e21c986c0d..9924172f427 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -68,10 +68,10 @@ public: FOCUS_ACCESSIBILITY, }; - enum RecursiveBehavior { - RECURSIVE_BEHAVIOR_INHERITED, - RECURSIVE_BEHAVIOR_DISABLED, - RECURSIVE_BEHAVIOR_ENABLED, + enum FocusBehaviorRecursive { + FOCUS_BEHAVIOR_INHERITED, + FOCUS_BEHAVIOR_DISABLED, + FOCUS_BEHAVIOR_ENABLED, }; enum SizeFlags { @@ -90,6 +90,12 @@ public: MOUSE_FILTER_IGNORE }; + enum MouseBehaviorRecursive { + MOUSE_BEHAVIOR_INHERITED, + MOUSE_BEHAVIOR_DISABLED, + MOUSE_BEHAVIOR_ENABLED, + }; + enum CursorShape { CURSOR_ARROW, CURSOR_IBEAM, @@ -197,8 +203,8 @@ private: real_t offset[4] = { 0.0, 0.0, 0.0, 0.0 }; real_t anchor[4] = { ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN }; FocusMode focus_mode = FOCUS_NONE; - RecursiveBehavior parent_focus_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED; - RecursiveBehavior focus_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED; + FocusBehaviorRecursive focus_behavior_recursive = FOCUS_BEHAVIOR_INHERITED; + bool parent_focus_behavior_recursive_enabled = false; GrowDirection h_grow = GROW_DIRECTION_END; GrowDirection v_grow = GROW_DIRECTION_END; @@ -227,8 +233,8 @@ private: // Input events and rendering. MouseFilter mouse_filter = MOUSE_FILTER_STOP; - RecursiveBehavior parent_mouse_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED; - RecursiveBehavior mouse_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED; + MouseBehaviorRecursive mouse_behavior_recursive = MOUSE_BEHAVIOR_INHERITED; + bool parent_mouse_behavior_recursive_enabled = true; bool force_pass_scroll_events = true; bool clip_contents = false; @@ -324,17 +330,17 @@ private: // Mouse Filter. - bool _is_parent_mouse_disabled() const; + bool _is_mouse_filter_enabled() const; + void _update_mouse_behavior_recursive(); + void _propagate_mouse_behavior_recursive_recursively(bool p_enabled, bool p_skip_non_inherited); // Focus. void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Rect2 &p_rect, const Rect2 &p_clamp, real_t p_min, real_t &r_closest_dist_squared, Control **r_closest); Control *_get_focus_neighbor(Side p_side, int p_count = 0); - bool _is_focus_disabled_recursively() const; - void _propagate_focus_behavior_recursively(RecursiveBehavior p_focus_recursive_behavior, bool p_force); - void _propagate_mouse_behavior_recursively(RecursiveBehavior p_focus_recursive_behavior, bool p_force); - void _set_mouse_recursive_behavior_ignore_cache(RecursiveBehavior p_recursive_mouse_behavior); - void _set_focus_recursive_behavior_ignore_cache(RecursiveBehavior p_recursive_mouse_behavior); + bool _is_focus_mode_enabled() const; + void _update_focus_behavior_recursive(); + void _propagate_focus_behavior_recursive_recursively(bool p_enabled, bool p_skip_non_inherited); // Theming. @@ -540,10 +546,10 @@ public: void set_mouse_filter(MouseFilter p_filter); MouseFilter get_mouse_filter() const; - MouseFilter get_mouse_filter_with_recursive() const; + MouseFilter get_mouse_filter_with_override() const; - void set_mouse_recursive_behavior(RecursiveBehavior p_recursive_mouse_behavior); - RecursiveBehavior get_mouse_recursive_behavior() const; + void set_mouse_behavior_recursive(MouseBehaviorRecursive p_mouse_behavior_recursive); + MouseBehaviorRecursive get_mouse_behavior_recursive() const; void set_force_pass_scroll_events(bool p_force_pass_scroll_events); bool is_force_pass_scroll_events() const; @@ -570,9 +576,9 @@ public: void set_focus_mode(FocusMode p_focus_mode); FocusMode get_focus_mode() const; - FocusMode get_focus_mode_with_recursive() const; - void set_focus_recursive_behavior(RecursiveBehavior p_recursive_mouse_behavior); - RecursiveBehavior get_focus_recursive_behavior() const; + 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(); void grab_click_focus(); @@ -694,7 +700,8 @@ public: }; VARIANT_ENUM_CAST(Control::FocusMode); -VARIANT_ENUM_CAST(Control::RecursiveBehavior); +VARIANT_ENUM_CAST(Control::FocusBehaviorRecursive); +VARIANT_ENUM_CAST(Control::MouseBehaviorRecursive); VARIANT_BITFIELD_CAST(Control::SizeFlags); VARIANT_ENUM_CAST(Control::CursorShape); VARIANT_ENUM_CAST(Control::LayoutPreset); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index c1ab57ecb77..52397569e99 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -1421,7 +1421,7 @@ bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &mpos control_rect.size *= zoom; control_rect.position += p_offset; - if (!control_rect.has_point(mpos) || p_control->get_mouse_filter_with_recursive() == MOUSE_FILTER_IGNORE) { + if (!control_rect.has_point(mpos) || p_control->get_mouse_filter_with_override() == MOUSE_FILTER_IGNORE) { // Test children. for (int i = 0; i < p_control->get_child_count(); i++) { Control *child_rect = Object::cast_to(p_control->get_child(i)); diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 4d848cd6359..f963127fc7c 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -89,12 +89,12 @@ void Slider::gui_input(const Ref &p_event) { } } else if (scrollable) { if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) { - if (get_focus_mode_with_recursive() != FOCUS_NONE) { + if (get_focus_mode_with_override() != FOCUS_NONE) { grab_focus(); } set_value(get_value() + get_step()); } else if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) { - if (get_focus_mode_with_recursive() != FOCUS_NONE) { + if (get_focus_mode_with_override() != FOCUS_NONE) { grab_focus(); } set_value(get_value() - get_step()); diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 3c8921f7e8a..7d8d6abd0c6 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -773,7 +773,7 @@ void TabContainer::set_tab_focus_mode(Control::FocusMode p_focus_mode) { } Control::FocusMode TabContainer::get_tab_focus_mode() const { - return tab_bar->get_focus_mode_with_recursive(); + return tab_bar->get_focus_mode(); } void TabContainer::set_clip_tabs(bool p_clip_tabs) { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 1bc38e82710..5e53c0b866f 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -750,7 +750,7 @@ void Viewport::_process_picking() { #endif // PHYSICS_2D_DISABLED SubViewportContainer *parent_svc = Object::cast_to(get_parent()); - bool parent_ignore_mouse = (parent_svc && parent_svc->get_mouse_filter_with_recursive() == Control::MOUSE_FILTER_IGNORE); + bool parent_ignore_mouse = (parent_svc && parent_svc->get_mouse_filter_with_override() == Control::MOUSE_FILTER_IGNORE); bool create_passive_hover_event = true; if (gui.mouse_over || parent_ignore_mouse) { // When the mouse is over a Control node, passive hovering would cause input events for Colliders, that are behind Control nodes. @@ -1499,7 +1499,7 @@ String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Cont // Otherwise, we check parent controls unless some conditions prevent it. - if (p_control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { + if (p_control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP) { break; } if (p_control->is_set_as_top_level()) { @@ -1670,14 +1670,14 @@ void Viewport::_gui_call_input(Control *p_control, const Ref &p_inpu while (ci) { Control *control = Object::cast_to(ci); if (control) { - if (control->data.mouse_filter != Control::MOUSE_FILTER_IGNORE) { + if (control->get_mouse_filter_with_override() != Control::MOUSE_FILTER_IGNORE) { control->_call_gui_input(ev); } if (!control->is_inside_tree() || control->is_set_as_top_level()) { break; } - if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP && is_pointer_event && !(is_scroll_event && control->data.force_pass_scroll_events)) { + if (control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP && is_pointer_event && !(is_scroll_event && control->data.force_pass_scroll_events)) { // Mouse, ScreenDrag and ScreenTouch events are stopped by default with MOUSE_FILTER_STOP, unless we have a scroll event and force_pass_scroll_events set to true set_input_as_handled(); break; @@ -1703,7 +1703,7 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) { while (ci) { Control *control = Object::cast_to(ci); if (control) { - if (control->data.mouse_filter != Control::MOUSE_FILTER_IGNORE) { + if (control->get_mouse_filter_with_override() != Control::MOUSE_FILTER_IGNORE) { control->notification(p_what); } @@ -1714,7 +1714,7 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) { if (!control->is_inside_tree() || control->is_set_as_top_level()) { break; } - if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { + if (control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP) { break; } } @@ -1784,7 +1784,7 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } } - if (!c || c->data.mouse_filter == Control::MOUSE_FILTER_IGNORE) { + if (!c || c->get_mouse_filter_with_override() == Control::MOUSE_FILTER_IGNORE) { return nullptr; } @@ -1816,7 +1816,7 @@ bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_che return true; } - if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { + if (control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP) { break; } } @@ -1882,7 +1882,7 @@ void Viewport::_gui_input_event(Ref p_event) { while (ci) { Control *control = Object::cast_to(ci); if (control) { - if (control->get_focus_mode_with_recursive() != Control::FOCUS_NONE) { + if (control->get_focus_mode_with_override() != Control::FOCUS_NONE) { // 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)) { @@ -1891,7 +1891,7 @@ void Viewport::_gui_input_event(Ref p_event) { break; } - if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { + if (control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP) { break; } } @@ -1977,7 +1977,7 @@ void Viewport::_gui_input_event(Ref p_event) { section_root->gui.global_dragging = false; } - if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { + if (control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP) { break; } } @@ -2069,7 +2069,7 @@ void Viewport::_gui_input_event(Ref p_event) { if (cursor_shape != Control::CURSOR_ARROW) { break; } - if (c->data.mouse_filter == Control::MOUSE_FILTER_STOP) { + if (c->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP) { break; } if (c->is_set_as_top_level()) { @@ -2503,7 +2503,7 @@ void Viewport::_gui_update_mouse_over() { int found = gui.mouse_over_hierarchy.find(ancestor_control); if (found >= 0) { // Remove the node if the propagation chain has been broken or it is now MOUSE_FILTER_IGNORE. - if (removing || ancestor_control->get_mouse_filter_with_recursive() == Control::MOUSE_FILTER_IGNORE) { + if (removing || ancestor_control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_IGNORE) { needs_exit.push_back(found); } } @@ -2514,14 +2514,14 @@ void Viewport::_gui_update_mouse_over() { } reached_top = true; } - if (!removing && ancestor_control->get_mouse_filter_with_recursive() != Control::MOUSE_FILTER_IGNORE) { + if (!removing && ancestor_control->get_mouse_filter_with_override() != Control::MOUSE_FILTER_IGNORE) { new_mouse_over_hierarchy.push_back(ancestor_control); // Add the node if it was not found and it is now not MOUSE_FILTER_IGNORE. if (found < 0) { needs_enter.push_back(ancestor_control); } } - if (ancestor_control->get_mouse_filter_with_recursive() == Control::MOUSE_FILTER_STOP) { + if (ancestor_control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP) { // MOUSE_FILTER_STOP breaks the propagation chain. if (reached_top) { break; @@ -3182,7 +3182,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) { while (ancestor) { Control *ancestor_control = Object::cast_to(ancestor); if (ancestor_control) { - if (ancestor_control->get_mouse_filter_with_recursive() != Control::MOUSE_FILTER_IGNORE) { + if (ancestor_control->get_mouse_filter_with_override() != Control::MOUSE_FILTER_IGNORE) { int found = gui.mouse_over_hierarchy.find(ancestor_control); if (found >= 0) { common_ancestor = gui.mouse_over_hierarchy[found]; @@ -3190,7 +3190,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) { } over_ancestors.push_back(ancestor_control); } - if (ancestor_control->get_mouse_filter_with_recursive() == Control::MOUSE_FILTER_STOP) { + if (ancestor_control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP) { // MOUSE_FILTER_STOP breaks the propagation chain. break; } diff --git a/tests/scene/test_control.h b/tests/scene/test_control.h index 3d8eba21173..2cb519c996c 100644 --- a/tests/scene/test_control.h +++ b/tests/scene/test_control.h @@ -138,6 +138,62 @@ TEST_CASE("[SceneTree][Control] Focus") { memdelete(child_ctrl); } + SUBCASE("[SceneTree][Control] Grab focus with focus behavior recursive") { + CHECK_UNARY_FALSE(ctrl->has_focus()); + + // Cannot grab focus if focus behavior is disabled. + ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL); + ctrl->set_focus_behavior_recursive(Control::FOCUS_BEHAVIOR_DISABLED); + + ERR_PRINT_OFF + ctrl->grab_focus(); + ERR_PRINT_ON + CHECK_UNARY_FALSE(ctrl->has_focus()); + + // Cannot grab focus if focus behavior is enabled but focus mode is none. + ctrl->set_focus_mode(Control::FocusMode::FOCUS_NONE); + ctrl->set_focus_behavior_recursive(Control::FOCUS_BEHAVIOR_ENABLED); + + ERR_PRINT_OFF + ctrl->grab_focus(); + ERR_PRINT_ON + CHECK_UNARY_FALSE(ctrl->has_focus()); + + // Can grab focus if focus behavior is enabled and focus mode is all. + ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL); + ctrl->set_focus_behavior_recursive(Control::FOCUS_BEHAVIOR_ENABLED); + + ctrl->grab_focus(); + CHECK_UNARY(ctrl->has_focus()); + } + + SUBCASE("[SceneTree][Control] Children focus with focus behavior recursive") { + Control *child_control = memnew(Control); + ctrl->add_child(child_control); + + // Can grab focus on child if parent focus behavior is inherit. + ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL); + ctrl->set_focus_behavior_recursive(Control::FOCUS_BEHAVIOR_INHERITED); + child_control->set_focus_mode(Control::FocusMode::FOCUS_ALL); + child_control->set_focus_behavior_recursive(Control::FOCUS_BEHAVIOR_INHERITED); + + child_control->grab_focus(); + CHECK_UNARY(child_control->has_focus()); + + // Cannot grab focus on child if parent focus behavior is disabled. + ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL); + ctrl->set_focus_behavior_recursive(Control::FOCUS_BEHAVIOR_DISABLED); + child_control->set_focus_mode(Control::FocusMode::FOCUS_ALL); + child_control->set_focus_behavior_recursive(Control::FOCUS_BEHAVIOR_INHERITED); + + ERR_PRINT_OFF + child_control->grab_focus(); + ERR_PRINT_ON + CHECK_UNARY_FALSE(child_control->has_focus()); + + memdelete(child_control); + } + memdelete(ctrl); } diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h index 41f024579c1..1e2f8d8f847 100644 --- a/tests/scene/test_viewport.h +++ b/tests/scene/test_viewport.h @@ -512,6 +512,45 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_d->invalid_order); } + SUBCASE("[Viewport][GuiInputEvent] Mouse behavior recursive disables mouse motion events.") { + node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + // Enabled when parent is set to inherit. + node_h->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_INHERITED); + node_i->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_INHERITED); + SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE); + CHECK(node_i->mouse_over); + CHECK(node_i->mouse_over_self); + + // Enabled when parent is set to enabled. + node_h->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_ENABLED); + node_i->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_INHERITED); + SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE); + CHECK(node_i->mouse_over); + CHECK(node_i->mouse_over_self); + + // Disabled when parent is set to disabled. + node_h->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_DISABLED); + node_i->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_INHERITED); + SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + + // Enabled when set to enabled and parent is set to disabled. + node_h->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_DISABLED); + node_i->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_ENABLED); + SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE); + CHECK(node_i->mouse_over); + CHECK(node_i->mouse_over_self); + + // Disabled when it is set to disabled. + node_h->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_ENABLED); + node_i->set_mouse_behavior_recursive(Control::MOUSE_BEHAVIOR_DISABLED); + SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + } + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation.") { node_d->set_mouse_filter(Control::MOUSE_FILTER_PASS); node_g->set_mouse_filter(Control::MOUSE_FILTER_PASS);