Skip to content

Commit

Permalink
Implement a property that recursively disables children's controls.
Browse files Browse the repository at this point in the history
  • Loading branch information
Delsin-Yu committed Sep 27, 2024
1 parent a0d1ba4 commit 4642d22
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 20 deletions.
27 changes: 27 additions & 0 deletions doc/classes/Control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,12 @@
Returns [member offset_right] and [member offset_bottom].
</description>
</method>
<method name="get_focus_mode_with_recursive" 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.
</description>
</method>
<method name="get_focus_neighbor" qualifiers="const">
<return type="NodePath" />
<param index="0" name="side" type="int" enum="Side" />
Expand All @@ -414,6 +420,12 @@
Returns the minimum size for this control. See [member custom_minimum_size].
</description>
</method>
<method name="get_mouse_filter_with_recursive" qualifiers="const">
<return type="int" enum="Control.MouseFilter" />
<description>
Similar to to the getter of [member mouse_filter], but takes parent's [member mouse_recursive_behavior] into account.
</description>
</method>
<method name="get_offset" qualifiers="const">
<return type="float" />
<param index="0" name="offset" type="int" enum="Side" />
Expand Down Expand Up @@ -969,6 +981,9 @@
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>
Expand Down Expand Up @@ -996,6 +1011,9 @@
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]. As it defaults to true, this allows nested scrollable containers to work out of the box.
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.
</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.
Expand Down Expand Up @@ -1146,6 +1164,15 @@
<constant name="FOCUS_ALL" value="2" enum="FocusMode">
The node can grab focus on mouse click, using the arrows and the Tab keys on the keyboard, or using the D-pad buttons on a gamepad. 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>
<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>
<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>
<constant name="NOTIFICATION_RESIZED" value="40">
Sent when the node changes size. Use [member size] to get the new size.
</constant>
Expand Down
2 changes: 1 addition & 1 deletion editor/plugins/tiles/tile_set_atlas_source_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2758,7 +2758,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() != Control::FOCUS_NONE) {
if (control && control->get_focus_mode_with_recursive() != Control::FOCUS_NONE) {
add_focusable(control);
}
for (int i = 0; i < p_node->get_child_count(); i++) {
Expand Down
124 changes: 116 additions & 8 deletions scene/gui/control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1868,6 +1868,29 @@ Control::MouseFilter Control::get_mouse_filter() const {
return data.mouse_filter;
}

Control::MouseFilter Control::get_mouse_filter_with_recursive() const {
ERR_READ_THREAD_GUARD_V(MOUSE_FILTER_IGNORE);
if (_is_parent_mouse_disabled()) {
return MOUSE_FILTER_IGNORE;
}
return data.mouse_filter;
}

void Control::set_mouse_recursive_behavior(const RecursiveBehavior p_recursive_mouse_behavior) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX((int)p_recursive_mouse_behavior, 4);
data.mouse_recursive_behavior = p_recursive_mouse_behavior;

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::set_force_pass_scroll_events(bool p_force_pass_scroll_events) {
ERR_MAIN_THREAD_GUARD;
data.force_pass_scroll_events = p_force_pass_scroll_events;
Expand Down Expand Up @@ -2016,6 +2039,30 @@ Control::FocusMode Control::get_focus_mode() const {
return data.focus_mode;
}

Control::FocusMode Control::get_focus_mode_with_recursive() const {
ERR_READ_THREAD_GUARD_V(FOCUS_NONE);
if (_get_parent_focus_recursive_behavior() == RECURSIVE_BEHAVIOR_DISABLED) {
return FOCUS_NONE;
}
return data.focus_mode;
}

void Control::set_focus_recursive_behavior(RecursiveBehavior p_recursive_mouse_behavior) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX((int)p_recursive_mouse_behavior, 4);
data.focus_recursive_behavior = p_recursive_mouse_behavior;
if (p_recursive_mouse_behavior == RECURSIVE_BEHAVIOR_INHERITED) {
_update_focus_mode_recursive(_get_parent_focus_recursive_behavior());
} else {
_update_focus_mode_recursive(p_recursive_mouse_behavior);
}
}

Control::RecursiveBehavior Control::get_focus_recursive_behavior() const {
ERR_READ_THREAD_GUARD_V(RECURSIVE_BEHAVIOR_INHERITED);
return data.focus_recursive_behavior;
}

bool Control::has_focus() const {
ERR_READ_THREAD_GUARD_V(false);
return is_inside_tree() && get_viewport()->_gui_control_has_focus(this);
Expand Down Expand Up @@ -2089,7 +2136,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() && c->get_focus_mode() != FOCUS_NONE) {
if (c->is_visible() && c->get_focus_mode_with_recursive() != FOCUS_NONE) {
return c;
}
}
Expand Down Expand Up @@ -2129,10 +2176,10 @@ Control *Control::find_next_valid_focus() const {
}

if (next_child == from || next_child == this) { // No next control.
return (get_focus_mode() == FOCUS_ALL) ? next_child : nullptr;
return (get_focus_mode_with_recursive() == FOCUS_ALL) ? next_child : nullptr;
}
if (next_child) {
if (next_child->get_focus_mode() == FOCUS_ALL) {
if (next_child->get_focus_mode_with_recursive() == FOCUS_ALL) {
return next_child;
}
from = next_child;
Expand Down Expand Up @@ -2176,7 +2223,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() && c->get_focus_mode() != FOCUS_NONE) {
if (c->is_visible() && c->get_focus_mode_with_recursive() != FOCUS_NONE) {
return c;
}
}
Expand Down Expand Up @@ -2210,10 +2257,10 @@ Control *Control::find_prev_valid_focus() const {
}

if (prev_child == from || prev_child == this) { // No prev control.
return (get_focus_mode() == FOCUS_ALL) ? prev_child : nullptr;
return (get_focus_mode_with_recursive() == FOCUS_ALL) ? prev_child : nullptr;
}

if (prev_child->get_focus_mode() == FOCUS_ALL) {
if (prev_child->get_focus_mode_with_recursive() == FOCUS_ALL) {
return prev_child;
}

Expand Down Expand Up @@ -2272,7 +2319,7 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
if (!c->is_visible()) {
valid = false;
}
if (c->get_focus_mode() == FOCUS_NONE) {
if (c->get_focus_mode_with_recursive() == FOCUS_NONE) {
valid = false;
}
if (valid) {
Expand Down Expand Up @@ -2334,6 +2381,54 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
return result;
}

Control::RecursiveBehavior Control::_get_parent_focus_recursive_behavior() const {
if (data.focus_recursive_behavior != RECURSIVE_BEHAVIOR_INHERITED) {
return data.focus_recursive_behavior;
}

const Control *parent = get_parent_control();
if (parent) {
return parent->_get_parent_focus_recursive_behavior();
}

return RECURSIVE_BEHAVIOR_INHERITED;
}

void Control::_update_focus_mode_recursive(const RecursiveBehavior p_focus_recursive_behavior) {
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();
return;
}

for (int i = 0; i < get_child_count(); i++) {
Control *control = Object::cast_to<Control>(get_child(i));
if (control) {
if (data.focus_recursive_behavior == RECURSIVE_BEHAVIOR_INHERITED) {
control->_update_focus_mode_recursive(p_focus_recursive_behavior);
} else {
control->_update_focus_mode_recursive(data.focus_recursive_behavior);
}
}
}
}

bool Control::_is_parent_mouse_disabled() const {
if (data.mouse_recursive_behavior == RECURSIVE_BEHAVIOR_DISABLED) {
return true;
}

if (data.mouse_recursive_behavior == RECURSIVE_BEHAVIOR_ENABLED) {
return false;
}

const Control *parent = get_parent_control();
if (parent) {
return parent->_is_parent_mouse_disabled();
}

return false;
}

Control *Control::find_valid_focus_neighbor(Side p_side) const {
return const_cast<Control *>(this)->_get_focus_neighbor(p_side);
}
Expand All @@ -2345,7 +2440,7 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons

Control *c = Object::cast_to<Control>(p_at);

if (c && c != this && c->get_focus_mode() == FOCUS_ALL && c->is_visible_in_tree()) {
if (c && c != this && c->get_focus_mode_with_recursive() == FOCUS_ALL && c->is_visible_in_tree()) {
Point2 points[4];

Transform2D xform = c->get_global_transform();
Expand Down Expand Up @@ -3438,6 +3533,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("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);
Expand Down Expand Up @@ -3531,6 +3629,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("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_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);
Expand Down Expand Up @@ -3626,9 +3728,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"), "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_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::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");

Expand All @@ -3643,6 +3747,10 @@ void Control::_bind_methods() {
BIND_ENUM_CONSTANT(FOCUS_CLICK);
BIND_ENUM_CONSTANT(FOCUS_ALL);

BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_INHERITED);
BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_DISABLED);
BIND_ENUM_CONSTANT(RECURSIVE_BEHAVIOR_ENABLED);

BIND_CONSTANT(NOTIFICATION_RESIZED);
BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER);
BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT);
Expand Down
22 changes: 22 additions & 0 deletions scene/gui/control.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ class Control : public CanvasItem {
FOCUS_ALL
};

enum RecursiveBehavior {
RECURSIVE_BEHAVIOR_INHERITED,
RECURSIVE_BEHAVIOR_DISABLED,
RECURSIVE_BEHAVIOR_ENABLED,
};

enum SizeFlags {
SIZE_SHRINK_BEGIN = 0,
SIZE_FILL = 1,
Expand Down Expand Up @@ -187,6 +193,7 @@ class Control : public CanvasItem {
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 focus_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED;
GrowDirection h_grow = GROW_DIRECTION_END;
GrowDirection v_grow = GROW_DIRECTION_END;

Expand Down Expand Up @@ -215,6 +222,7 @@ class Control : public CanvasItem {
// Input events and rendering.

MouseFilter mouse_filter = MOUSE_FILTER_STOP;
RecursiveBehavior mouse_recursive_behavior = RECURSIVE_BEHAVIOR_INHERITED;
bool force_pass_scroll_events = true;

bool clip_contents = false;
Expand Down Expand Up @@ -307,10 +315,16 @@ class Control : public CanvasItem {

void _call_gui_input(const Ref<InputEvent> &p_event);

// Mouse Filter.

bool _is_parent_mouse_disabled() const;

// Focus.

void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest);
Control *_get_focus_neighbor(Side p_side, int p_count = 0);
RecursiveBehavior _get_parent_focus_recursive_behavior() const;
void _update_focus_mode_recursive(RecursiveBehavior p_focus_recursive_behavior);

// Theming.

Expand Down Expand Up @@ -507,6 +521,10 @@ class Control : public CanvasItem {

void set_mouse_filter(MouseFilter p_filter);
MouseFilter get_mouse_filter() const;
MouseFilter get_mouse_filter_with_recursive() const;

void set_mouse_recursive_behavior(RecursiveBehavior p_recursive_mouse_behavior);
RecursiveBehavior get_mouse_recursive_behavior() const;

void set_force_pass_scroll_events(bool p_force_pass_scroll_events);
bool is_force_pass_scroll_events() const;
Expand All @@ -531,6 +549,9 @@ class Control : public CanvasItem {

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;
bool has_focus() const;
void grab_focus();
void grab_click_focus();
Expand Down Expand Up @@ -645,6 +666,7 @@ class Control : public CanvasItem {
};

VARIANT_ENUM_CAST(Control::FocusMode);
VARIANT_ENUM_CAST(Control::RecursiveBehavior);
VARIANT_BITFIELD_CAST(Control::SizeFlags);
VARIANT_ENUM_CAST(Control::CursorShape);
VARIANT_ENUM_CAST(Control::LayoutPreset);
Expand Down
2 changes: 1 addition & 1 deletion scene/gui/graph_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1160,7 +1160,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() == MOUSE_FILTER_IGNORE) {
if (!control_rect.has_point(mpos) || p_control->get_mouse_filter_with_recursive() == 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));
Expand Down
4 changes: 2 additions & 2 deletions scene/gui/slider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() != FOCUS_NONE) {
if (get_focus_mode_with_recursive() != 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() != FOCUS_NONE) {
if (get_focus_mode_with_recursive() != FOCUS_NONE) {
grab_focus();
}
set_value(get_value() - get_step());
Expand Down
Loading

0 comments on commit 4642d22

Please sign in to comment.