Merge pull request #105222 from kitbdev/fix-mouse-filter-recursive-and-rename

Fix and rename mouse filter recursive behavior and focus mode recursive behavior
This commit is contained in:
Thaddeus Crews 2025-04-16 10:45:12 -05:00
commit 00bd421089
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
10 changed files with 282 additions and 173 deletions

View file

@ -415,10 +415,10 @@
Returns [member offset_right] and [member offset_bottom].
</description>
</method>
<method name="get_focus_mode_with_recursive" qualifiers="const">
<method name="get_focus_mode_with_override" qualifiers="const">
<return type="int" enum="Control.FocusMode" />
<description>
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].
</description>
</method>
<method name="get_focus_neighbor" qualifiers="const">
@ -443,10 +443,10 @@
Returns the minimum size for this control. See [member custom_minimum_size].
</description>
</method>
<method name="get_mouse_filter_with_recursive" qualifiers="const">
<method name="get_mouse_filter_with_override" qualifiers="const">
<return type="int" enum="Control.MouseFilter" />
<description>
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].
</description>
</method>
<method name="get_offset" qualifiers="const">
@ -983,8 +983,11 @@
<member name="custom_minimum_size" type="Vector2" setter="set_custom_minimum_size" getter="get_custom_minimum_size" default="Vector2(0, 0)">
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]).
</member>
<member name="focus_behavior_recursive" type="int" setter="set_focus_behavior_recursive" getter="get_focus_behavior_recursive" enum="Control.FocusBehaviorRecursive" default="0">
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.
</member>
<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" enum="Control.FocusMode" default="0">
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].
</member>
<member name="focus_neighbor_bottom" type="NodePath" setter="set_focus_neighbor" getter="get_focus_neighbor" default="NodePath(&quot;&quot;)" keywords="focus_neighbour_bottom">
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.
</member>
<member name="focus_recursive_behavior" type="int" setter="set_focus_recursive_behavior" getter="get_focus_recursive_behavior" enum="Control.RecursiveBehavior" default="0">
Controls whether the recursive child nodes should have their [member focus_mode] overridden to [constant FOCUS_NONE] when evaluating input.
</member>
<member name="global_position" type="Vector2" setter="_set_global_position" getter="get_global_position">
The node's global position, relative to the world (usually to the [CanvasLayer]).
</member>
@ -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].
</member>
<member name="mouse_behavior_recursive" type="int" setter="set_mouse_behavior_recursive" getter="get_mouse_behavior_recursive" enum="Control.MouseBehaviorRecursive" default="0">
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.
</member>
<member name="mouse_default_cursor_shape" type="int" setter="set_default_cursor_shape" getter="get_default_cursor_shape" enum="Control.CursorShape" default="0">
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.
</member>
<member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" enum="Control.MouseFilter" default="0">
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.
</member>
<member name="mouse_force_pass_scroll_events" type="bool" setter="set_force_pass_scroll_events" getter="is_force_pass_scroll_events" default="true">
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.
</member>
<member name="mouse_recursive_behavior" type="int" setter="set_mouse_recursive_behavior" getter="get_mouse_recursive_behavior" enum="Control.RecursiveBehavior" default="0">
Controls whether the recursive child nodes should have their [member mouse_filter] overridden to [constant MOUSE_FILTER_IGNORE] when evaluating input.
</member>
<member name="offset_bottom" type="float" setter="set_offset" getter="get_offset" default="0.0">
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 @@
<constant name="FOCUS_ACCESSIBILITY" value="3" enum="FocusMode">
The node can grab focus only when screen reader is active. Use with [member focus_mode].
</constant>
<constant name="RECURSIVE_BEHAVIOR_INHERITED" value="0" enum="RecursiveBehavior">
Inherits the associated behavior from the control's parent. This is the default for any newly created control.
<constant name="FOCUS_BEHAVIOR_INHERITED" value="0" enum="FocusBehaviorRecursive">
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].
</constant>
<constant name="RECURSIVE_BEHAVIOR_DISABLED" value="1" enum="RecursiveBehavior">
The current control and all its recursive child controls have their associated behavior disabled, regardless of the parent control's configuration.
<constant name="FOCUS_BEHAVIOR_DISABLED" value="1" enum="FocusBehaviorRecursive">
Prevents the control from getting focused. [method get_focus_mode_with_override] will return [constant FOCUS_NONE].
</constant>
<constant name="RECURSIVE_BEHAVIOR_ENABLED" value="2" enum="RecursiveBehavior">
The current control and all its recursive child controls have their associated behavior enabled, regardless of the parent control's configuration.
<constant name="FOCUS_BEHAVIOR_ENABLED" value="2" enum="FocusBehaviorRecursive">
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].
</constant>
<constant name="MOUSE_BEHAVIOR_INHERITED" value="0" enum="MouseBehaviorRecursive">
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].
</constant>
<constant name="MOUSE_BEHAVIOR_DISABLED" value="1" enum="MouseBehaviorRecursive">
Prevents the control from receiving mouse input. [method get_mouse_filter_with_override] will return [constant MOUSE_FILTER_IGNORE].
</constant>
<constant name="MOUSE_BEHAVIOR_ENABLED" value="2" enum="MouseBehaviorRecursive">
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].
</constant>
<constant name="NOTIFICATION_RESIZED" value="40">
Sent when the node changes size. Use [member size] to get the new size.

View file

@ -2764,7 +2764,7 @@ TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() {
void EditorPropertyTilePolygon::_add_focusable_children(Node *p_node) {
Control *control = Object::cast_to<Control>(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++) {

View file

@ -1891,44 +1891,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<Control>(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) {
@ -2112,40 +2135,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<Control>(get_child(i));
if (control) {
control->_propagate_focus_behavior_recursive_recursively(p_enabled, true);
}
}
}
bool Control::has_focus() const {
@ -2157,8 +2209,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;
}
@ -2218,7 +2270,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<Control>(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;
}
}
@ -2285,7 +2337,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;
}
@ -2322,7 +2374,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<Control>(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;
}
}
@ -2383,7 +2435,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;
}
@ -2442,7 +2494,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<Control>(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;
}
@ -2551,64 +2603,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<Control>(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<Control>(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<Control *>(this)->_get_focus_neighbor(p_side);
}
@ -2624,7 +2618,7 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
Container *container = Object::cast_to<Container>(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());
@ -3627,8 +3621,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: {
@ -3849,9 +3843,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);
@ -3950,10 +3944,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);
@ -4050,11 +4044,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");
@ -4070,9 +4064,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);

View file

@ -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;
@ -325,17 +331,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.
@ -541,10 +547,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;
@ -571,9 +577,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();
@ -695,7 +701,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);

View file

@ -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<Control>(p_control->get_child(i));

View file

@ -89,12 +89,12 @@ void Slider::gui_input(const Ref<InputEvent> &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());

View file

@ -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) {

View file

@ -750,7 +750,7 @@ void Viewport::_process_picking() {
#endif // PHYSICS_2D_DISABLED
SubViewportContainer *parent_svc = Object::cast_to<SubViewportContainer>(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.
@ -1546,7 +1546,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()) {
@ -1717,14 +1717,14 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu
while (ci) {
Control *control = Object::cast_to<Control>(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;
@ -1750,7 +1750,7 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) {
while (ci) {
Control *control = Object::cast_to<Control>(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);
}
@ -1761,7 +1761,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;
}
}
@ -1831,7 +1831,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;
}
@ -1863,7 +1863,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;
}
}
@ -1929,7 +1929,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
while (ci) {
Control *control = Object::cast_to<Control>(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)) {
@ -1938,7 +1938,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
break;
}
if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
if (control->get_mouse_filter_with_override() == Control::MOUSE_FILTER_STOP) {
break;
}
}
@ -2024,7 +2024,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> 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;
}
}
@ -2116,7 +2116,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> 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()) {
@ -2550,7 +2550,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);
}
}
@ -2561,14 +2561,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;
@ -3229,7 +3229,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
while (ancestor) {
Control *ancestor_control = Object::cast_to<Control>(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];
@ -3237,7 +3237,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;
}

View file

@ -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);
}

View file

@ -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);