Use dedicated canvas item to properly cull tree items

This commit is contained in:
Boozee
2026-01-03 14:13:26 +01:00
parent 94971e1b47
commit bd54c553f4
2 changed files with 81 additions and 20 deletions

View File

@@ -2114,7 +2114,7 @@ void Tree::draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, co
break;
}
RID ci = get_canvas_item();
RID ci = content_ci;
if (rtl) {
if (ts.width > 0) {
@@ -2259,15 +2259,19 @@ void Tree::update_item_cache(TreeItem *p_item) const {
}
int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item, int &r_self_height) {
if (p_pos.y - theme_cache.offset.y > (p_draw_size.height)) {
return -1; // Draw no more!
const real_t bottom_margin = theme_cache.panel_style->get_margin(SIDE_BOTTOM); // Extra stylebox space below the content
const real_t draw_height = p_draw_size.height + bottom_margin; // Visible height including bottom margin
// Cull item if it's beyond the bottom of the visible area.
if (p_pos.y - theme_cache.offset.y > draw_height) {
return -1;
}
if (!p_item->is_visible_in_tree()) {
return 0;
}
RID ci = get_canvas_item();
RID ci = content_ci;
int htotal = 0;
@@ -2355,7 +2359,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
r_self_height = compute_item_height(p_item);
label_h = r_self_height + theme_cache.v_separation;
if (p_pos.y + label_h - theme_cache.offset.y < 0) {
// Bottom edge for top-culling
const real_t item_bottom = p_pos.y + label_h - theme_cache.offset.y + p_draw_ofs.y;
if (item_bottom < 0.0f) {
continue; // No need to draw.
}
@@ -2464,7 +2470,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
r.size.x = item_width + ofs;
}
r = convert_rtl_rect(r);
draw_style_box(p_item->cells[i].custom_stylebox, r);
p_item->cells[i].custom_stylebox->draw(ci, r);
}
if (drop_mode_flags && drop_mode_over) {
@@ -2617,7 +2623,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
Point2i icon_ofs = (item_rect.size - icon_size) / 2;
icon_ofs += item_rect.position;
draw_texture_rect(p_item->cells[i].icon, Rect2(icon_ofs, icon_size), false, icon_col);
p_item->cells[i].icon->draw_rect(ci, Rect2(icon_ofs, icon_size), false, icon_col);
} break;
case TreeItem::CELL_MODE_CUSTOM: {
@@ -2650,13 +2656,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (p_item->cells[i].custom_button) {
if (cache.hover_item == p_item && cache.hover_column == i) {
if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
draw_style_box(theme_cache.custom_button_pressed, ir);
theme_cache.custom_button_pressed->draw(ci, ir);
} else {
draw_style_box(theme_cache.custom_button_hover, ir);
theme_cache.custom_button_hover->draw(ci, ir);
cell_color = theme_cache.custom_button_font_highlight;
}
} else {
draw_style_box(theme_cache.custom_button, ir);
theme_cache.custom_button->draw(ci, ir);
}
ir.size -= theme_cache.custom_button->get_minimum_size();
ir.position += theme_cache.custom_button->get_offset();
@@ -2682,14 +2688,14 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (should_draw_pressed || should_draw_hovered) {
Point2 od = convert_rtl_position(button_ofs, button_size.x);
if (should_draw_pressed && should_draw_hovered) {
theme_cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
theme_cache.button_pressed->draw(ci, Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
} else {
theme_cache.button_hover->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
theme_cache.button_hover->draw(ci, Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
}
}
if (selected_item == p_item && selected_col == i && selected_button == j) {
Point2 od = convert_rtl_position(button_ofs, button_size.x);
theme_cache.button_hover->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
theme_cache.button_hover->draw(ci, Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
}
button_ofs.y += (label_h - button_size.height) / 2;
@@ -2716,7 +2722,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
if (p_pos.y + label_h - theme_cache.offset.y >= 0) {
// Bottom edge for arrow visibility
const real_t item_bottom = p_pos.y + label_h - theme_cache.offset.y + p_draw_ofs.y;
if (item_bottom >= 0.0f) {
// Draw the folding arrow.
if (!p_item->disable_folding && !hide_folding && p_item->first_child && p_item->get_visible_child_count() != 0) { // Has visible children, draw the guide box.
Ref<Texture2D> arrow;
@@ -2879,7 +2887,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
htotal = -1;
children_pos.y = theme_cache.offset.y + p_draw_size.height;
children_pos.y = theme_cache.offset.y + draw_height;
} else {
htotal += child_h;
children_pos.y += child_h;
@@ -5077,13 +5085,40 @@ void Tree::_notification(int p_what) {
Point2 draw_ofs = content_rect.position;
Size2 draw_size = content_rect.size;
bg->draw(ci, Rect2(Point2(), get_size()));
int tbh = _get_title_button_height();
draw_ofs.y += tbh;
draw_size.y -= tbh;
const real_t clip_top = show_column_titles ? draw_ofs.y : 0.0f; // Start clipping below headers when they are visible
const real_t bottom_margin = theme_cache.panel_style->get_margin(SIDE_BOTTOM);
const real_t clip_bottom = draw_ofs.y + draw_size.y + bottom_margin; // End clip after the bottom margin
const Rect2 main_clip_rect = Rect2(0.0f, clip_top, get_size().x, clip_bottom - clip_top);
RenderingServer *rendering_server = RenderingServer::get_singleton();
rendering_server->canvas_item_add_clip_ignore(ci, true);
bg->draw(ci, Rect2(Point2(), get_size()));
rendering_server->canvas_item_add_clip_ignore(ci, false);
Rect2 header_clip_rect = Rect2(content_rect.position.x, 0.0f, content_rect.size.x, get_size().y); // Keep headers out of the scrollbar area.
if (v_scroll->is_visible()) {
const Rect2 v_scroll_rect = Rect2(v_scroll->get_position(), v_scroll->get_size()); // Actual scrollbar rect in Tree coordinates.
if (v_scroll_rect.position.x <= header_clip_rect.position.x) {
const real_t header_right = header_clip_rect.get_end().x;
header_clip_rect.position.x = v_scroll_rect.get_end().x;
header_clip_rect.size.x = MAX(0.0f, header_right - header_clip_rect.position.x);
} else if (v_scroll_rect.position.x < header_clip_rect.get_end().x) {
header_clip_rect.size.x = MAX(0.0f, v_scroll_rect.position.x - header_clip_rect.position.x);
}
}
rendering_server->canvas_item_clear(header_ci);
rendering_server->canvas_item_set_custom_rect(header_ci, !is_visibility_clip_disabled(), header_clip_rect);
rendering_server->canvas_item_set_clip(header_ci, true);
rendering_server->canvas_item_clear(content_ci);
rendering_server->canvas_item_set_custom_rect(content_ci, !is_visibility_clip_disabled(), main_clip_rect);
rendering_server->canvas_item_set_clip(content_ci, true);
cache.rtl = is_layout_rtl();
content_scale_factor = popup_editor->is_embedded() ? 1.0 : popup_editor->get_parent_visible_window()->get_content_scale_factor();
@@ -5101,7 +5136,7 @@ void Tree::_notification(int p_what) {
if (cache.rtl) {
tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x;
}
sb->draw(ci, tbrect);
sb->draw(header_ci, tbrect);
ofs2 += tbrect.size.width;
// Text.
int clip_w = tbrect.size.width - sb->get_minimum_size().width;
@@ -5127,9 +5162,9 @@ void Tree::_notification(int p_what) {
}
if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
columns[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
columns[i].text_buf->draw_outline(header_ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
}
columns[i].text_buf->draw(ci, text_pos, theme_cache.title_button_color);
columns[i].text_buf->draw(header_ci, text_pos, theme_cache.title_button_color);
}
}
@@ -5176,6 +5211,16 @@ void Tree::_notification(int p_what) {
}
}
void Tree::set_self_modulate(const Color &p_self_modulate) {
if (get_self_modulate() == p_self_modulate) {
return;
}
CanvasItem::set_self_modulate(p_self_modulate);
RS::get_singleton()->canvas_item_set_self_modulate(header_ci, p_self_modulate);
RS::get_singleton()->canvas_item_set_self_modulate(content_ci, p_self_modulate);
}
void Tree::_update_all() {
for (int i = 0; i < columns.size(); i++) {
update_column(i);
@@ -6884,6 +6929,16 @@ Tree::Tree() {
set_focus_mode(FOCUS_ALL);
RenderingServer *rs = RenderingServer::get_singleton();
content_ci = rs->canvas_item_create();
rs->canvas_item_set_parent(content_ci, get_canvas_item());
rs->canvas_item_set_use_parent_material(content_ci, true);
header_ci = rs->canvas_item_create();
rs->canvas_item_set_parent(header_ci, get_canvas_item());
rs->canvas_item_set_use_parent_material(header_ci, true);
popup_editor = memnew(Popup);
add_child(popup_editor, false, INTERNAL_MODE_FRONT);
@@ -6935,4 +6990,6 @@ Tree::~Tree() {
if (root) {
memdelete(root);
}
RenderingServer::get_singleton()->free_rid(content_ci);
RenderingServer::get_singleton()->free_rid(header_ci);
}

View File

@@ -543,6 +543,8 @@ private:
bool popup_edit_committed = true;
RID accessibility_scroll_element;
RID header_ci; // Separate canvas item for drawing column headers
RID content_ci; // Separate canvas item for drawing tree rows
VBoxContainer *popup_editor_vb = nullptr;
Popup *popup_editor = nullptr;
@@ -818,6 +820,8 @@ public:
PackedStringArray get_accessibility_configuration_warnings() const override;
virtual RID get_focused_accessibility_element() const override;
virtual void set_self_modulate(const Color &p_self_modulate) override;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual String get_tooltip(const Point2 &p_pos) const override;