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 26, 2024
1 parent a0d1ba4 commit 853fbdf
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 8 deletions.
18 changes: 18 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 [member get_focus_mode], but takes parent's [member focus_none_recursive] 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 [member get_mouse_filter], but takes parent's [member mouse_ignore_recursive] 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 @@ -965,6 +977,9 @@
Tells Godot which node it should give focus to if the user presses [kbd]Tab[/kbd] on a keyboard by default. You can change the key by editing the [member ProjectSettings.input/ui_focus_next] 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_none_recursive" type="bool" setter="set_focus_none_recursive" getter="is_focus_none_recursive" default="false">
When set to true, all recursive child nodes will have their [member focus_mode] overridden to [constant FOCUS_NONE].
</member>
<member name="focus_previous" type="NodePath" setter="set_focus_previous" getter="get_focus_previous" default="NodePath(&quot;&quot;)">
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.
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_ignore_recursive" type="bool" setter="set_mouse_ignore_recursive" getter="is_mouse_ignore_recursive" default="false">
When set to true, all recursive child nodes will have their [member mouse_filter] overridden to [constant MOUSE_FILTER_IGNORE].
</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
90 changes: 90 additions & 0 deletions scene/gui/control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1868,6 +1868,28 @@ 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 (_get_parent_ignore_mouse_state()) {
return MOUSE_FILTER_IGNORE;
}
return data.mouse_filter;
}

void Control::set_mouse_ignore_recursive(const bool p_mouse_ignore_recursive) {
ERR_MAIN_THREAD_GUARD;
data.mouse_ignore_recursive = p_mouse_ignore_recursive;

if (get_viewport()) {
get_viewport()->_gui_update_mouse_over();
}
}

bool Control::is_mouse_ignore_recursive() const {
ERR_READ_THREAD_GUARD_V(false);
return data.mouse_ignore_recursive;
}

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 +2038,25 @@ 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_none_state()) {
return FOCUS_NONE;
}
return data.focus_mode;
}

void Control::set_focus_none_recursive(bool p_focus_none_recursive) {
ERR_MAIN_THREAD_GUARD;
data.focus_none_recursive = p_focus_none_recursive;
_update_focus_mode_recursive(p_focus_none_recursive);
}

bool Control::is_focus_none_recursive() const {
ERR_READ_THREAD_GUARD_V(false);
return data.focus_none_recursive;
}

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 @@ -2334,6 +2375,46 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
return result;
}

bool Control::_get_parent_focus_none_state() const {
if (data.focus_none_recursive) {
return true;
}

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

return false;
}

void Control::_update_focus_mode_recursive(const bool p_disable_focus_recursive) {
if (is_inside_tree() && p_disable_focus_recursive && data.focus_mode != FOCUS_NONE && 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) {
control->_update_focus_mode_recursive(p_disable_focus_recursive);
}
}
}

bool Control::_get_parent_ignore_mouse_state() const {
if (data.mouse_ignore_recursive) {
return true;
}

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

return false;
}

Control *Control::find_valid_focus_neighbor(Side p_side) const {
return const_cast<Control *>(this)->_get_focus_neighbor(p_side);
}
Expand Down Expand Up @@ -3438,6 +3519,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_none_recursive", "disable_focus_recursive"), &Control::set_focus_none_recursive);
ClassDB::bind_method(D_METHOD("is_focus_none_recursive"), &Control::is_focus_none_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);
Expand Down Expand Up @@ -3531,6 +3615,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_ignore_recursive", "mouse_ignore_recursive"), &Control::set_mouse_ignore_recursive);
ClassDB::bind_method(D_METHOD("is_mouse_ignore_recursive"), &Control::is_mouse_ignore_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);
Expand Down Expand Up @@ -3626,9 +3714,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::BOOL, "focus_none_recursive"), "set_focus_none_recursive", "is_focus_none_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::BOOL, "mouse_ignore_recursive"), "set_mouse_ignore_recursive", "is_mouse_ignore_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");

Expand Down
15 changes: 15 additions & 0 deletions scene/gui/control.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,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;
bool focus_none_recursive = false;
GrowDirection h_grow = GROW_DIRECTION_END;
GrowDirection v_grow = GROW_DIRECTION_END;

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

MouseFilter mouse_filter = MOUSE_FILTER_STOP;
bool mouse_ignore_recursive = false;
bool force_pass_scroll_events = true;

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

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

// Mouse Filter.

bool _get_parent_ignore_mouse_state() 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);
bool _get_parent_focus_none_state() const;
void _update_focus_mode_recursive(bool p_disable_focus_recursive);

// Theming.

Expand Down Expand Up @@ -507,6 +515,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_ignore_recursive(bool p_mouse_ignore_recursive);
bool is_mouse_ignore_recursive() const;

void set_force_pass_scroll_events(bool p_force_pass_scroll_events);
bool is_force_pass_scroll_events() const;
Expand All @@ -531,6 +543,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_none_recursive(bool p_focus_none_recursive);
bool is_focus_none_recursive() const;
bool has_focus() const;
void grab_focus();
void grab_click_focus();
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
14 changes: 7 additions & 7 deletions scene/main/viewport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ void Viewport::_process_picking() {
PhysicsDirectSpaceState2D *ss2d = PhysicsServer2D::get_singleton()->space_get_direct_state(find_world_2d()->get_space());

SubViewportContainer *parent_svc = Object::cast_to<SubViewportContainer>(get_parent());
bool parent_ignore_mouse = (parent_svc && parent_svc->get_mouse_filter() == Control::MOUSE_FILTER_IGNORE);
bool parent_ignore_mouse = (parent_svc && parent_svc->get_mouse_filter_with_recursive() == 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.
Expand Down Expand Up @@ -1775,7 +1775,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() != Control::FOCUS_NONE) {
if (control->get_focus_mode_with_recursive() != 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)) {
Expand Down Expand Up @@ -2354,7 +2354,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() == Control::MOUSE_FILTER_IGNORE) {
if (removing || ancestor_control->get_mouse_filter_with_recursive() == Control::MOUSE_FILTER_IGNORE) {
needs_exit.push_back(found);
}
}
Expand All @@ -2365,14 +2365,14 @@ void Viewport::_gui_update_mouse_over() {
}
reached_top = true;
}
if (!removing && ancestor_control->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) {
if (!removing && ancestor_control->get_mouse_filter_with_recursive() != 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() == Control::MOUSE_FILTER_STOP) {
if (ancestor_control->get_mouse_filter_with_recursive() == Control::MOUSE_FILTER_STOP) {
// MOUSE_FILTER_STOP breaks the propagation chain.
if (reached_top) {
break;
Expand Down Expand Up @@ -2990,15 +2990,15 @@ 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() != Control::MOUSE_FILTER_IGNORE) {
if (ancestor_control->get_mouse_filter_with_recursive() != Control::MOUSE_FILTER_IGNORE) {
int found = gui.mouse_over_hierarchy.find(ancestor_control);
if (found >= 0) {
common_ancestor = gui.mouse_over_hierarchy[found];
break;
}
over_ancestors.push_back(ancestor_control);
}
if (ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_STOP) {
if (ancestor_control->get_mouse_filter_with_recursive() == Control::MOUSE_FILTER_STOP) {
// MOUSE_FILTER_STOP breaks the propagation chain.
break;
}
Expand Down

0 comments on commit 853fbdf

Please sign in to comment.