Skip to content

Commit

Permalink
Cherry-pick godotengine#86361
Browse files Browse the repository at this point in the history
Optimize mouse UI picking on BoxContainer, FlowContainer and GridContainer
  • Loading branch information
rsubtil committed Feb 22, 2024
1 parent 0f9a42c commit 3f8b59f
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 1 deletion.
32 changes: 32 additions & 0 deletions scene/gui/box_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Control>(get_child(i));
if (!c || !c->is_visible_in_tree()) {
Expand Down Expand Up @@ -238,6 +240,7 @@ void BoxContainer::_resort() {
}

fit_child_in_rect(c, rect);
cached_children_nodes.push_back(c);

ofs = to;
idx++;
Expand Down Expand Up @@ -289,6 +292,35 @@ Size2 BoxContainer::get_minimum_size() const {
return minimum;
}

Vector<CanvasItem *> BoxContainer::get_children_at_pos(const Point2 &p_pos) const {
if (cached_children_nodes.is_empty()) {
return Vector<CanvasItem *>();
}

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<CanvasItem *> children;
int idx = MAX(bin_start - 1, 0);
ERR_FAIL_INDEX_V(idx, cached_children_nodes.size(), children);

CanvasItem *child = Object::cast_to<CanvasItem>(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: {
Expand Down
3 changes: 3 additions & 0 deletions scene/gui/box_container.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class BoxContainer : public Container {
int separation = 0;
} theme_cache;

Vector<Control *> cached_children_nodes;

void _resort();

protected:
Expand All @@ -70,6 +72,7 @@ class BoxContainer : public Container {
bool is_vertical() const;

virtual Size2 get_minimum_size() const override;
virtual Vector<CanvasItem *> get_children_at_pos(const Point2 &p_pos) const override;

virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
Expand Down
11 changes: 11 additions & 0 deletions scene/gui/container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ void Container::remove_child_notify(Node *p_child) {
queue_sort();
}

Vector<CanvasItem *> Container::get_children_at_pos(const Point2 &p_pos) const {
Vector<CanvasItem *> children;
for (int i = get_child_count() - 1; i >= 0; i--) {
CanvasItem *child = Object::cast_to<CanvasItem>(get_child(i));
if (child && child->is_visible()) {
children.push_back(child);
}
}
return children;
}

void Container::_sort_children() {
if (!is_inside_tree()) {
return;
Expand Down
1 change: 1 addition & 0 deletions scene/gui/container.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class Container : public Control {
};

void fit_child_in_rect(Control *p_child, const Rect2 &p_rect);
virtual Vector<CanvasItem *> get_children_at_pos(const Point2 &p_pos) const;

virtual Vector<int> get_allowed_size_flags_horizontal() const;
virtual Vector<int> get_allowed_size_flags_vertical() const;
Expand Down
49 changes: 49 additions & 0 deletions scene/gui/flow_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Control>(get_child(i));
Expand Down Expand Up @@ -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++;
Expand All @@ -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];
}

Expand Down Expand Up @@ -247,6 +255,47 @@ Size2 FlowContainer::get_minimum_size() const {
return minimum;
}

Vector<CanvasItem *> FlowContainer::get_children_at_pos(const Point2 &p_pos) const {
if (cached_children_nodes.is_empty()) {
return Vector<CanvasItem *>();
}

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<CanvasItem *>());
ERR_FAIL_INDEX_V(end_idx, cached_children_nodes.size(), Vector<CanvasItem *>());

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<CanvasItem *> children;
for (end_node_idx--; end_node_idx >= start_node_idx; end_node_idx--) {
CanvasItem *child = Object::cast_to<CanvasItem>(get_child(end_node_idx));
if (child && child->is_visible()) {
children.push_back(child);
}
}
return children;
}

Vector<int> FlowContainer::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
flags.append(SIZE_FILL);
Expand Down
3 changes: 3 additions & 0 deletions scene/gui/flow_container.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class FlowContainer : public Container {

void _resort();

Vector<Control *> cached_children_nodes;

protected:
bool is_fixed = false;

Expand All @@ -74,6 +76,7 @@ class FlowContainer : public Container {
bool is_vertical() const;

virtual Size2 get_minimum_size() const override;
virtual Vector<CanvasItem *> get_children_at_pos(const Point2 &p_pos) const override;

virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
Expand Down
48 changes: 48 additions & 0 deletions scene/gui/grid_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Control>(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++;
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -322,4 +330,44 @@ Size2 GridContainer::get_minimum_size() const {
return ms;
}

Vector<CanvasItem *> GridContainer::get_children_at_pos(const Point2 &p_pos) const {
if (cached_children_nodes.is_empty()) {
return Vector<CanvasItem *>();
}

// 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<CanvasItem *>());
ERR_FAIL_INDEX_V(end_idx, cached_children_nodes.size(), Vector<CanvasItem *>());

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<CanvasItem *> children;
for (end_node_idx--; end_node_idx >= start_node_idx; end_node_idx--) {
CanvasItem *child = Object::cast_to<CanvasItem>(get_child(end_node_idx));
if (child && child->is_visible()) {
children.push_back(child);
}
}
return children;
}

GridContainer::GridContainer() {}
3 changes: 3 additions & 0 deletions scene/gui/grid_container.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class GridContainer : public Container {
int v_separation = 0;
} theme_cache;

Vector<Control *> cached_children_nodes;

protected:
void _notification(int p_what);
static void _bind_methods();
Expand All @@ -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<CanvasItem *> get_children_at_pos(const Point2 &p_pos) const override;

int get_h_separation() const;

Expand Down
19 changes: 18 additions & 1 deletion scene/main/viewport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1699,8 +1699,25 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_
}

Control *c = Object::cast_to<Control>(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<Container>(p_node);
if (container && container->has_point(point)) {
Vector<CanvasItem *> 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<CanvasItem>(p_node->get_child(i));
if (!ci || ci->is_set_as_top_level()) {
Expand Down

0 comments on commit 3f8b59f

Please sign in to comment.