diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index 2728126e6491..a23c19236fcc 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -200,6 +200,8 @@ void BoxContainer::_resort() { delta = -1; } + cached_children_nodes.clear(); + for (int i = start; i != end; i += delta) { Control *c = Object::cast_to(get_child(i)); if (!c || !c->is_visible_in_tree()) { @@ -238,6 +240,7 @@ void BoxContainer::_resort() { } fit_child_in_rect(c, rect); + cached_children_nodes.push_back(c); ofs = to; idx++; @@ -289,6 +292,35 @@ Size2 BoxContainer::get_minimum_size() const { return minimum; } +Vector BoxContainer::get_children_at_pos(const Point2 &p_pos) const { + if (cached_children_nodes.is_empty()) { + return Vector(); + } + + int coord = vertical ? p_pos.y : p_pos.x; + // Custom binary search; we are searching for the closest value to coord, not an exact one. + int bin_start = 0; + int bin_end = cached_children_nodes.size(); + while (bin_start < bin_end) { + int mid = (bin_start + bin_end) / 2; + if (coord < (vertical ? cached_children_nodes[mid]->get_rect().position.y : cached_children_nodes[mid]->get_rect().position.x)) { + bin_end = mid; + } else { + bin_start = mid + 1; + } + } + + Vector children; + int idx = MAX(bin_start - 1, 0); + ERR_FAIL_INDEX_V(idx, cached_children_nodes.size(), children); + + CanvasItem *child = Object::cast_to(cached_children_nodes[idx]); + if (child) { + children.push_back(child); + } + return children; +} + void BoxContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { diff --git a/scene/gui/box_container.h b/scene/gui/box_container.h index c8cfe1106295..c99d71a5dcd5 100644 --- a/scene/gui/box_container.h +++ b/scene/gui/box_container.h @@ -51,6 +51,8 @@ class BoxContainer : public Container { int separation = 0; } theme_cache; + Vector cached_children_nodes; + void _resort(); protected: @@ -70,6 +72,7 @@ class BoxContainer : public Container { bool is_vertical() const; virtual Size2 get_minimum_size() const override; + virtual Vector get_children_at_pos(const Point2 &p_pos) const override; virtual Vector get_allowed_size_flags_horizontal() const override; virtual Vector get_allowed_size_flags_vertical() const override; diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 4e23db4caef5..38e8026c50b5 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -81,6 +81,17 @@ void Container::remove_child_notify(Node *p_child) { queue_sort(); } +Vector Container::get_children_at_pos(const Point2 &p_pos) const { + Vector children; + for (int i = get_child_count() - 1; i >= 0; i--) { + CanvasItem *child = Object::cast_to(get_child(i)); + if (child && child->is_visible()) { + children.push_back(child); + } + } + return children; +} + void Container::_sort_children() { if (!is_inside_tree()) { return; diff --git a/scene/gui/container.h b/scene/gui/container.h index 94c3c540d72b..d0c62c6cf5d4 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -59,6 +59,7 @@ class Container : public Control { }; void fit_child_in_rect(Control *p_child, const Rect2 &p_rect); + virtual Vector get_children_at_pos(const Point2 &p_pos) const; virtual Vector get_allowed_size_flags_horizontal() const; virtual Vector get_allowed_size_flags_vertical() const; diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index 5b5f8e5f2dff..4866b8f91f3d 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -59,6 +59,8 @@ void FlowContainer::_resort() { int current_container_size = vertical ? get_rect().size.y : get_rect().size.x; int children_in_current_line = 0; + cached_children_nodes.clear(); + // First pass for line wrapping and minimum size calculation. for (int i = 0; i < get_child_count(); i++) { Control *child = Object::cast_to(get_child(i)); @@ -140,6 +142,11 @@ void FlowContainer::_resort() { } Size2i child_size = children_minsize_cache[child]; + if (cached_children_nodes.is_empty()) { + // Add the first child. + cached_children_nodes.push_back(child); + } + _LineData line_data = lines_data[current_line_idx]; if (child_idx_in_line >= lines_data[current_line_idx].child_count) { current_line_idx++; @@ -151,6 +158,7 @@ void FlowContainer::_resort() { ofs.x = 0; ofs.y += line_data.min_line_height + theme_cache.v_separation; } + cached_children_nodes.push_back(child); line_data = lines_data[current_line_idx]; } @@ -247,6 +255,47 @@ Size2 FlowContainer::get_minimum_size() const { return minimum; } +Vector FlowContainer::get_children_at_pos(const Point2 &p_pos) const { + if (cached_children_nodes.is_empty()) { + return Vector(); + } + + int coord = vertical ? p_pos.x : p_pos.y; + // Custom binary search; we are searching for the closest value to coord, not an exact one. + int bin_start = 0; + int bin_end = cached_children_nodes.size(); + while (bin_start < bin_end) { + int mid = (bin_start + bin_end) / 2; + if (coord < (vertical ? cached_children_nodes[mid]->get_rect().position.x : cached_children_nodes[mid]->get_rect().position.y)) { + bin_end = mid; + } else { + bin_start = mid + 1; + } + } + + int start_idx = MAX(bin_start - 1, 0); + int end_idx = MIN(bin_end, cached_children_nodes.size() - 1); + ERR_FAIL_INDEX_V(start_idx, cached_children_nodes.size(), Vector()); + ERR_FAIL_INDEX_V(end_idx, cached_children_nodes.size(), Vector()); + + int start_node_idx = cached_children_nodes[start_idx]->get_index(); + int end_node_idx = cached_children_nodes[end_idx]->get_index(); + + if (start_node_idx == end_node_idx) { + // Last row, so include the remaining children. + end_node_idx = get_child_count(); + } + + Vector children; + for (end_node_idx--; end_node_idx >= start_node_idx; end_node_idx--) { + CanvasItem *child = Object::cast_to(get_child(end_node_idx)); + if (child && child->is_visible()) { + children.push_back(child); + } + } + return children; +} + Vector FlowContainer::get_allowed_size_flags_horizontal() const { Vector flags; flags.append(SIZE_FILL); diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index 1c06c8281501..7f437cdce295 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -57,6 +57,8 @@ class FlowContainer : public Container { void _resort(); + Vector cached_children_nodes; + protected: bool is_fixed = false; @@ -74,6 +76,7 @@ class FlowContainer : public Container { bool is_vertical() const; virtual Size2 get_minimum_size() const override; + virtual Vector get_children_at_pos(const Point2 &p_pos) const override; virtual Vector get_allowed_size_flags_horizontal() const override; virtual Vector get_allowed_size_flags_vertical() const override; diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index a4baf3bb8d1e..58d7e753ecb6 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -185,11 +185,18 @@ void GridContainer::_notification(int p_what) { } valid_controls_index = 0; + cached_children_nodes.clear(); + for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to(get_child(i)); if (!c || !c->is_visible_in_tree()) { continue; } + if (cached_children_nodes.is_empty()) { + // Add the first child. + cached_children_nodes.push_back(c); + } + int row = valid_controls_index / columns; int col = valid_controls_index % columns; valid_controls_index++; @@ -207,6 +214,7 @@ void GridContainer::_notification(int p_what) { // Apply the remaining pixel of the previous row. row_ofs++; } + cached_children_nodes.push_back(c); } } @@ -322,4 +330,44 @@ Size2 GridContainer::get_minimum_size() const { return ms; } +Vector GridContainer::get_children_at_pos(const Point2 &p_pos) const { + if (cached_children_nodes.is_empty()) { + return Vector(); + } + + // Custom binary search; we are searching for the closest value to coord, not an exact one. + int bin_start = 0; + int bin_end = cached_children_nodes.size(); + while (bin_start < bin_end) { + int mid = (bin_start + bin_end) / 2; + if (p_pos.y < cached_children_nodes[mid]->get_rect().position.y) { + bin_end = mid; + } else { + bin_start = mid + 1; + } + } + + int start_idx = MAX(bin_start - 1, 0); + int end_idx = MIN(bin_end, cached_children_nodes.size() - 1); + ERR_FAIL_INDEX_V(start_idx, cached_children_nodes.size(), Vector()); + ERR_FAIL_INDEX_V(end_idx, cached_children_nodes.size(), Vector()); + + int start_node_idx = cached_children_nodes[start_idx]->get_index(); + int end_node_idx = cached_children_nodes[end_idx]->get_index(); + + if (start_node_idx == end_node_idx) { + // Last row, so include the remaining children. + end_node_idx = get_child_count(); + } + + Vector children; + for (end_node_idx--; end_node_idx >= start_node_idx; end_node_idx--) { + CanvasItem *child = Object::cast_to(get_child(end_node_idx)); + if (child && child->is_visible()) { + children.push_back(child); + } + } + return children; +} + GridContainer::GridContainer() {} diff --git a/scene/gui/grid_container.h b/scene/gui/grid_container.h index f6a51511a98f..a805e3c70d77 100644 --- a/scene/gui/grid_container.h +++ b/scene/gui/grid_container.h @@ -43,6 +43,8 @@ class GridContainer : public Container { int v_separation = 0; } theme_cache; + Vector cached_children_nodes; + protected: void _notification(int p_what); static void _bind_methods(); @@ -51,6 +53,7 @@ class GridContainer : public Container { void set_columns(int p_columns); int get_columns() const; virtual Size2 get_minimum_size() const override; + virtual Vector get_children_at_pos(const Point2 &p_pos) const override; int get_h_separation() const; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 89ec5636ab73..852b6743f4b9 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1691,8 +1691,25 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } Control *c = Object::cast_to(p_node); + Point2 point = matrix.affine_inverse().xform(p_global); + + if (!c || !c->is_clipping_contents() || c->has_point(point)) { + // If node is a Container, it can optimize the search at the given position. + Container *container = Object::cast_to(p_node); + if (container && container->has_point(point)) { + Vector children = container->get_children_at_pos(point); + for (CanvasItem *ci : children) { + if (ci->is_set_as_top_level()) { + continue; + } - if (!c || !c->is_clipping_contents() || c->has_point(matrix.affine_inverse().xform(p_global))) { + Control *ret = _gui_find_control_at_pos(ci, p_global, matrix); + if (ret) { + return ret; + } + } + } + // Fallback: Iterate over all children. for (int i = p_node->get_child_count() - 1; i >= 0; i--) { CanvasItem *ci = Object::cast_to(p_node->get_child(i)); if (!ci || ci->is_set_as_top_level()) {