diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 80d4a751ab..e9f0be7424 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -983,6 +983,9 @@
Expanding main editor window content to the title, if supported by [DisplayServer]. See [constant DisplayServer.WINDOW_FLAG_EXTEND_TO_TITLE].
Specific to the macOS platform.
+
+ The maximum number of tree items allowed to stick to the top while its children are in view. A value of [code]0[/code] disables sticking.
+
The preferred monitor to display the project manager.
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index 9aa5e070f2..7002b4f3ee 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -650,6 +650,9 @@
The maximum distance between the mouse cursor and the control's border to trigger border scrolling when dragging.
+
+ The maximum number of tree items allowed to stick to the top while its children are in view. A value of [code]0[/code] disables sticking.
+
The speed of border scrolling.
diff --git a/editor/settings/editor_settings.cpp b/editor/settings/editor_settings.cpp
index 9182680ec7..5b30f4dbea 100644
--- a/editor/settings/editor_settings.cpp
+++ b/editor/settings/editor_settings.cpp
@@ -59,7 +59,6 @@
#include "scene/gui/file_dialog.h"
#include "scene/main/node.h"
#include "scene/main/scene_tree.h"
-#include "scene/main/window.h"
#include "scene/resources/animation.h"
#include "servers/display/display_server.h"
@@ -506,6 +505,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) {
EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/appearance/use_embedded_menu", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_BASIC_SETTING)
EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/appearance/use_native_file_dialogs", false, "", PROPERTY_USAGE_DEFAULT)
EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/appearance/expand_to_title", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING)
+ EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_RANGE, "interface/editor/appearance/max_sticky_tree_items", 6, "0,16")
EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_RANGE, "interface/editor/fonts/main_font_size", 14, "8,48,1")
EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_RANGE, "interface/editor/fonts/code_font_size", 14, "8,48,1")
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index ff04e1bdc7..ac30b03859 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -268,6 +268,7 @@ EditorThemeManager::ThemeConfiguration EditorThemeManager::_create_theme_config(
config.gizmo_handle_scale = EDITOR_GET("interface/touchscreen/scale_gizmo_handles");
config.subresource_hue_tint = EDITOR_GET("docks/property_editor/subresource_hue_tint");
config.dragging_hover_wait_msec = (float)EDITOR_GET("interface/editor/timers/dragging_hover_wait_seconds") * 1000;
+ config.max_sticky_tree_items = EDITOR_GET("interface/editor/appearance/max_sticky_tree_items");
// Handle theme style.
if (config.preset != "Custom") {
@@ -718,6 +719,7 @@ bool EditorThemeManager::is_generated_theme_outdated() {
// TODO: We can use this information more intelligently to do partial theme updates and speed things up.
outdated_cache = EditorSettings::get_singleton()->check_changed_settings_in_group("interface/theme") ||
EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/fonts") ||
+ EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/appearance/max_sticky_tree_items") ||
EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen/enable_touch_optimizations") ||
EditorSettings::get_singleton()->check_changed_settings_in_group("editors/visual_editors") ||
EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme") ||
diff --git a/editor/themes/editor_theme_manager.h b/editor/themes/editor_theme_manager.h
index 26d858f9e2..d2eb4540f1 100644
--- a/editor/themes/editor_theme_manager.h
+++ b/editor/themes/editor_theme_manager.h
@@ -80,6 +80,7 @@ public:
int inspector_property_height = 28;
float subresource_hue_tint = 0.0;
float dragging_hover_wait_msec = 0;
+ int max_sticky_tree_items = 5;
// Make sure to keep those in sync with the definitions in the editor settings.
const float default_icon_saturation = 2.0;
diff --git a/editor/themes/theme_classic.cpp b/editor/themes/theme_classic.cpp
index 9b745af1d0..5d74efe87d 100644
--- a/editor/themes/theme_classic.cpp
+++ b/editor/themes/theme_classic.cpp
@@ -624,6 +624,7 @@ void ThemeClassic::populate_standard_styles(const Ref &p_theme, Edi
p_theme->set_constant("icon_h_separation", "Tree", (p_config.increased_margin + 2) * EDSCALE);
p_theme->set_constant("button_margin", "Tree", p_config.base_margin * EDSCALE);
p_theme->set_constant("dragging_unfold_wait_msec", "Tree", p_config.dragging_hover_wait_msec);
+ p_theme->set_constant("scroll_max_sticky_items", "Tree", p_config.max_sticky_tree_items);
p_theme->set_constant("scroll_border", "Tree", 40 * EDSCALE);
p_theme->set_constant("scroll_speed", "Tree", 12);
p_theme->set_constant("outline_size", "Tree", 0);
diff --git a/editor/themes/theme_modern.cpp b/editor/themes/theme_modern.cpp
index eefe3f8a69..5e4fae6b0d 100644
--- a/editor/themes/theme_modern.cpp
+++ b/editor/themes/theme_modern.cpp
@@ -650,6 +650,7 @@ void ThemeModern::populate_standard_styles(const Ref &p_theme, Edit
p_theme->set_constant("icon_h_separation", "Tree", p_config.base_margin * 1.5 * EDSCALE);
p_theme->set_constant("button_margin", "Tree", p_config.base_margin * EDSCALE);
p_theme->set_constant("dragging_unfold_wait_msec", "Tree", p_config.dragging_hover_wait_msec);
+ p_theme->set_constant("scroll_max_sticky_items", "Tree", p_config.max_sticky_tree_items);
p_theme->set_constant("scroll_border", "Tree", 40 * EDSCALE);
p_theme->set_constant("scroll_speed", "Tree", 12);
p_theme->set_constant("outline_size", "Tree", 0);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 238f4681b7..218d2399bd 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -122,6 +122,7 @@ void TreeItem::_change_tree(Tree *p_tree) {
}
if (tree) {
+ tree->sticky_list.clear();
if (tree->root == this) {
tree->root = nullptr;
}
@@ -2129,7 +2130,7 @@ Rect2i Tree::convert_rtl_rect(const Rect2i &p_rect) const {
return p_rect;
}
-void Tree::draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) const {
+void Tree::draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color, const RID &p_ci) const {
ERR_FAIL_COND(theme_cache.font.is_null());
Rect2i rect = p_rect;
@@ -2169,7 +2170,7 @@ void Tree::draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, co
break;
}
- RID ci = content_ci;
+ RID ci = p_ci;
if (rtl) {
if (ts.width > 0) {
@@ -2319,7 +2320,7 @@ 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) {
+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, const RID &p_ci) {
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
@@ -2332,9 +2333,11 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
return 0;
}
- RID ci = content_ci;
+ RID ci = p_ci;
+ RID sb_ci = ci == content_ci ? stylebox_ci : ci;
int htotal = 0;
+ int htotal_raw = 0;
int label_h = 0;
bool rtl = cache.rtl;
@@ -2444,22 +2447,22 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (p_item->cells[0].selected) {
if (is_row_hovered) {
if (has_focus(true)) {
- theme_cache.hovered_selected_focus->draw(stylebox_ci, row_rect);
+ theme_cache.hovered_selected_focus->draw(sb_ci, row_rect);
} else {
- theme_cache.hovered_selected->draw(stylebox_ci, row_rect);
+ theme_cache.hovered_selected->draw(sb_ci, row_rect);
}
} else {
if (has_focus(true)) {
- theme_cache.selected_focus->draw(stylebox_ci, row_rect);
+ theme_cache.selected_focus->draw(sb_ci, row_rect);
} else {
- theme_cache.selected->draw(stylebox_ci, row_rect);
+ theme_cache.selected->draw(sb_ci, row_rect);
}
}
} else if (!drop_mode_flags) {
if (is_cell_button_hovered) {
- theme_cache.hovered_dimmed->draw(stylebox_ci, row_rect);
+ theme_cache.hovered_dimmed->draw(sb_ci, row_rect);
} else {
- theme_cache.hovered->draw(stylebox_ci, row_rect);
+ theme_cache.hovered->draw(sb_ci, row_rect);
}
}
}
@@ -2471,9 +2474,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
// Cell hover.
if (is_cell_hovered && !p_item->cells[i].selected && !drop_mode_flags) {
if (is_cell_button_hovered) {
- theme_cache.hovered_dimmed->draw(stylebox_ci, r);
+ theme_cache.hovered_dimmed->draw(sb_ci, r);
} else {
- theme_cache.hovered->draw(stylebox_ci, r);
+ theme_cache.hovered->draw(sb_ci, r);
}
}
}
@@ -2487,15 +2490,15 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (p_item->cells[i].selected) {
if (is_cell_hovered) {
if (has_focus(true)) {
- theme_cache.hovered_selected_focus->draw(stylebox_ci, r);
+ theme_cache.hovered_selected_focus->draw(sb_ci, r);
} else {
- theme_cache.hovered_selected->draw(stylebox_ci, r);
+ theme_cache.hovered_selected->draw(sb_ci, r);
}
} else {
if (has_focus(true)) {
- theme_cache.selected_focus->draw(stylebox_ci, r);
+ theme_cache.selected_focus->draw(sb_ci, r);
} else {
- theme_cache.selected->draw(stylebox_ci, r);
+ theme_cache.selected->draw(sb_ci, r);
}
}
}
@@ -2561,7 +2564,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
switch (p_item->cells[i].mode) {
case TreeItem::CELL_MODE_STRING: {
- draw_item_rect(p_item->cells[i], item_rect, cell_color, icon_col, outline_size, font_outline_color);
+ draw_item_rect(p_item->cells[i], item_rect, cell_color, icon_col, outline_size, font_outline_color, ci);
} break;
case TreeItem::CELL_MODE_CHECK: {
Point2i check_ofs = item_rect.position;
@@ -2596,7 +2599,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
cell_color = theme_cache.font_disabled_color;
}
- draw_item_rect(p_item->cells[i], item_rect, cell_color, icon_col, outline_size, font_outline_color);
+ draw_item_rect(p_item->cells[i], item_rect, cell_color, icon_col, outline_size, font_outline_color, ci);
} break;
case TreeItem::CELL_MODE_RANGE: {
@@ -2679,7 +2682,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
if (!p_item->cells[i].editable) {
- draw_item_rect(p_item->cells[i], item_rect, cell_color, icon_col, outline_size, font_outline_color);
+ draw_item_rect(p_item->cells[i], item_rect, cell_color, icon_col, outline_size, font_outline_color, ci);
break;
}
@@ -2707,7 +2710,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
ir.position += theme_cache.custom_button->get_offset();
}
- draw_item_rect(p_item->cells[i], ir, cell_color, icon_col, outline_size, font_outline_color);
+ draw_item_rect(p_item->cells[i], ir, cell_color, icon_col, outline_size, font_outline_color, ci);
downarrow->draw(ci, arrow_pos);
@@ -2808,10 +2811,11 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (!skip) {
children_pos.x += theme_cache.item_margin;
htotal += label_h;
- children_pos.y += htotal;
+ htotal_raw += label_h;
+ children_pos.y += htotal_raw;
}
- if (!p_item->collapsed) { // If not collapsed, check the children.
+ if (!p_item->collapsed && p_ci == content_ci) { // If not collapsed, check the children.
TreeItem *c = p_item->first_child;
int base_ofs = children_pos.y - theme_cache.offset.y + p_draw_ofs.y;
@@ -2823,7 +2827,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int child_h = -1;
int child_self_height = 0;
if (htotal >= 0) {
- child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c, child_self_height);
+ child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c, child_self_height, ci);
child_self_height += theme_cache.v_separation;
}
@@ -2940,7 +2944,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
break; // Last loop done, stop.
}
- if (theme_cache.draw_relationship_lines == 0) {
+ if (theme_cache.draw_relationship_lines == 0 && theme_cache.scroll_max_sticky_items == 0) {
return -1; // No need to draw anymore, full stop.
}
@@ -2948,6 +2952,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
children_pos.y = theme_cache.offset.y + draw_height;
} else {
htotal += child_h;
+ htotal_raw += child_h;
children_pos.y += child_h;
}
@@ -2955,7 +2960,14 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
- return htotal;
+ if (theme_cache.scroll_max_sticky_items > 0 && p_ci == content_ci && !p_item->collapsed) {
+ sticky_candidates.push_back(p_item);
+ p_item->cached_start = p_pos.y;
+ p_item->cached_total_end = p_pos.y + htotal_raw;
+ p_item->cached_label_height = label_h;
+ }
+
+ return htotal_raw;
}
int Tree::_count_selected_items(TreeItem *p_from) const {
@@ -3145,7 +3157,24 @@ void Tree::_range_click_timeout() {
}
}
-int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref &p_mod) {
+int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref &p_mod, bool p_first_call, bool p_skip_children) {
+ if (p_first_call) {
+ // Handle sticky item input first
+ for (TreeItem *item : sticky_list) {
+ Point2 relative_pos = p_pos;
+ relative_pos.y -= theme_cache.offset.y;
+ relative_pos.y -= item->sticky_offset.y;
+ if (relative_pos.y > item->cached_label_height) {
+ continue;
+ }
+ int result = propagate_mouse_event(relative_pos, x_ofs, y_ofs, x_limit, p_double_click, item, p_button, p_mod, false, true);
+ if (result < 0) {
+ return result;
+ }
+ }
+ p_first_call = false;
+ }
+
if (p_item && !p_item->is_visible_in_tree()) {
// Skip any processing of invisible items.
return 0;
@@ -3257,12 +3286,18 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
if (select_mode == SELECT_MULTI && icount > 1 && p_button != MouseButton::RIGHT) {
if (!already_selected) {
select_single_item(p_item, root, col);
+ if (p_skip_children) {
+ ensure_cursor_is_visible();
+ }
}
single_select_defer = p_item;
single_select_defer_column = col;
} else {
if (p_button != MouseButton::RIGHT || !c.selected) {
select_single_item(p_item, root, col);
+ if (p_skip_children && p_button != MouseButton::RIGHT) {
+ ensure_cursor_is_visible();
+ }
}
emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button);
@@ -3409,12 +3444,12 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
new_pos.y -= item_h;
}
- if (!p_item->collapsed) { // If not collapsed, check the children.
+ if ((p_item && !p_item->collapsed) && !p_skip_children) { // If not collapsed, check the children.
TreeItem *c = p_item->first_child;
while (c) {
- int child_h = propagate_mouse_event(new_pos, x_ofs, y_ofs, x_limit, p_double_click, c, p_button, p_mod);
+ int child_h = propagate_mouse_event(new_pos, x_ofs, y_ofs, x_limit, p_double_click, c, p_button, p_mod, false, false);
if (child_h < 0) {
return -1; // Break, stop propagating, no need to anymore.
@@ -5251,12 +5286,16 @@ void Tree::_notification(int p_what) {
rendering_server->canvas_item_set_custom_rect(drop_indicator_ci, !is_visibility_clip_disabled(), main_clip_rect);
rendering_server->canvas_item_set_clip(drop_indicator_ci, true);
+ rendering_server->canvas_item_clear(last_sticky_ci);
+ rendering_server->canvas_item_set_clip(last_sticky_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();
+ sticky_candidates.clear();
if (root && get_size().x > 0 && get_size().y > 0) {
int self_height = 0; // Just to pass a reference, we don't need the root's `self_height`.
- draw_item(Point2(), draw_ofs, draw_size, root, self_height);
+ draw_item(Point2(), draw_ofs, draw_size, root, self_height, content_ci);
// Draw drop indicator.
if (drop_mode_flags && drop_mode_over) {
@@ -5511,6 +5550,55 @@ void Tree::_notification(int p_what) {
}
}
+ sticky_stack_end = 0;
+ if (root) {
+ sticky_list.clear();
+ Vector2 stick_ofs;
+ Vector2 last_ofs = stick_ofs;
+ real_t height_limit = get_size().y * .4; // Prevent sticky items from ever exceeding 40% of the space
+
+ // Filter sticky candidates and cache their sticky positions
+ for (int i = sticky_candidates.size() - 1; i >= 0; i--) {
+ TreeItem *item = sticky_candidates[i];
+ bool past_top = item->cached_start - theme_cache.offset.y < stick_ofs.y;
+ bool stickable = !item->collapsed && item->get_child_count() > 0;
+ bool hidden_root = hide_root && item == root;
+ if (past_top && stickable && !hidden_root) {
+ if (item->cached_total_end - theme_cache.offset.y > stick_ofs.y) {
+ last_ofs = stick_ofs;
+ stick_ofs.y = MIN(stick_ofs.y, item->cached_total_end - theme_cache.offset.y - item->cached_label_height);
+ item->sticky_offset = stick_ofs;
+ sticky_list.push_back(item);
+ stick_ofs.x += theme_cache.item_margin;
+ stick_ofs.y += item->cached_label_height;
+ }
+ }
+ if ((int)sticky_list.size() >= theme_cache.scroll_max_sticky_items || stick_ofs.y >= height_limit) {
+ break;
+ }
+ }
+
+ sticky_stack_end = stick_ofs.y;
+ for (int i = sticky_list.size() - 1; i >= 0; i--) {
+ bool is_last = i == (int)sticky_list.size() - 1;
+ TreeItem *item = sticky_list[i];
+
+ // Draw sticky items without a scroll offset temporarily
+ Point2 old_offset = theme_cache.offset;
+ theme_cache.offset.y = 0;
+ int self_height = 0;
+ draw_item(item->sticky_offset, draw_ofs, draw_size, item, self_height, is_last ? last_sticky_ci : header_ci);
+ theme_cache.offset = old_offset;
+ }
+
+ if (!sticky_list.is_empty()) {
+ rendering_server->canvas_item_set_custom_rect(last_sticky_ci, !is_visibility_clip_disabled(), Rect2(0, last_ofs.y + draw_ofs.y, get_size().x, sticky_stack_end - last_ofs.y));
+ rendering_server->canvas_item_set_custom_rect(content_ci, !is_visibility_clip_disabled(), main_clip_rect.grow_side(SIDE_TOP, -(sticky_stack_end + content_rect.position.y)));
+ rendering_server->canvas_item_set_custom_rect(stylebox_ci, !is_visibility_clip_disabled(), main_clip_rect.grow_side(SIDE_TOP, -(sticky_stack_end + content_rect.position.y)));
+ rendering_server->canvas_item_set_custom_rect(header_ci, !is_visibility_clip_disabled(), header_clip_rect.grow_side(SIDE_BOTTOM, last_ofs.y + content_rect.position.y));
+ }
+ }
+
if (scroll_hint_mode != SCROLL_HINT_MODE_DISABLED) {
Size2 size = get_size();
float v_scroll_value = v_scroll->get_value();
@@ -5518,7 +5606,7 @@ void Tree::_notification(int p_what) {
if (v_scroll_value > 1 || v_scroll_below_max) {
int hint_height = theme_cache.scroll_hint->get_height();
if ((scroll_hint_mode == SCROLL_HINT_MODE_BOTH || scroll_hint_mode == SCROLL_HINT_MODE_TOP) && v_scroll_value > 1) {
- draw_texture_rect(theme_cache.scroll_hint, Rect2(Point2(), Size2(size.width, hint_height)), tile_scroll_hint, theme_cache.scroll_hint_color);
+ draw_texture_rect(theme_cache.scroll_hint, Rect2(0, sticky_stack_end > 0 ? sticky_stack_end + draw_ofs.y : 0, size.width, hint_height), tile_scroll_hint, theme_cache.scroll_hint_color);
}
if ((scroll_hint_mode == SCROLL_HINT_MODE_BOTH || scroll_hint_mode == SCROLL_HINT_MODE_BOTTOM) && v_scroll_below_max) {
draw_texture_rect(theme_cache.scroll_hint, Rect2(Point2(0, size.height - hint_height), Size2(size.width, -hint_height)), tile_scroll_hint, theme_cache.scroll_hint_color);
@@ -5561,6 +5649,7 @@ void Tree::set_self_modulate(const Color &p_self_modulate) {
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(last_sticky_ci, p_self_modulate);
RS::get_singleton()->canvas_item_set_self_modulate(content_ci, p_self_modulate);
RS::get_singleton()->canvas_item_set_self_modulate(custom_ci, p_self_modulate);
RS::get_singleton()->canvas_item_set_self_modulate(stylebox_ci, p_self_modulate);
@@ -6131,8 +6220,21 @@ void Tree::ensure_cursor_is_visible() {
const int tbh = _get_title_button_height();
y_offset -= tbh;
+ bool selected_is_sticky = false;
+ for (TreeItem *item : sticky_list) {
+ if (selected_item == item) {
+ y_offset -= item->sticky_offset.y;
+ selected_is_sticky = true;
+ break;
+ }
+ }
+
+ if (!selected_is_sticky) {
+ y_offset -= sticky_stack_end;
+ }
+
const int cell_h = compute_item_height(selected_item) + theme_cache.v_separation;
- int screen_h = area_size.height - tbh;
+ int screen_h = area_size.height - tbh - sticky_stack_end;
if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet.
delta_v = y_offset - v_scroll->get_value();
@@ -6555,7 +6657,7 @@ void Tree::_do_incr_search(const String &p_add) {
ensure_cursor_is_visible();
}
-TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &r_height, int &r_section) const {
+TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &r_height, int &r_section, bool p_sticky_check) const {
r_column = -1;
r_height = 0;
r_section = COLUMN_NOT_FOUND;
@@ -6564,6 +6666,16 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
return nullptr;
}
+ if (p_sticky_check) {
+ Point2 relative_pos = p_pos - theme_cache.offset;
+ for (TreeItem *item : sticky_list) {
+ if (item && relative_pos.y >= item->sticky_offset.y && relative_pos.y < item->sticky_offset.y + item->cached_label_height) {
+ item = _find_item_at_pos(item, relative_pos - Point2(0, item->cached_start), r_column, r_height, r_section, false);
+ return item;
+ }
+ }
+ }
+
Point2 pos = p_pos;
// Calculate target parent based on horizontal mouse position.
@@ -6690,7 +6802,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
TreeItem *n = p_item->get_first_child();
while (n) {
int ch;
- TreeItem *r = _find_item_at_pos(n, pos, r_column, ch, r_section);
+ TreeItem *r = _find_item_at_pos(n, pos, r_column, ch, r_section, false);
pos.y -= ch;
r_height += ch;
if (r) {
@@ -7406,6 +7518,7 @@ void Tree::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, scroll_border);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, scroll_speed);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, scroll_max_sticky_items);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, scrollbar_margin_top);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, scrollbar_margin_right);
@@ -7455,6 +7568,10 @@ Tree::Tree() {
rs->canvas_item_set_parent(drop_indicator_ci, get_canvas_item());
rs->canvas_item_set_use_parent_material(drop_indicator_ci, true);
+ last_sticky_ci = rs->canvas_item_create();
+ rs->canvas_item_set_parent(last_sticky_ci, get_canvas_item());
+ rs->canvas_item_set_use_parent_material(last_sticky_ci, true);
+
popup_editor = memnew(Popup);
add_child(popup_editor, false, INTERNAL_MODE_FRONT);
@@ -7512,4 +7629,5 @@ Tree::~Tree() {
RenderingServer::get_singleton()->free_rid(header_ci);
RenderingServer::get_singleton()->free_rid(custom_ci);
RenderingServer::get_singleton()->free_rid(stylebox_ci);
+ RenderingServer::get_singleton()->free_rid(last_sticky_ci);
}
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 6bef6adfcb..a27f96f375 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -156,6 +156,11 @@ private:
TreeItem *first_child = nullptr;
TreeItem *last_child = nullptr;
+ int cached_start = 0;
+ int cached_label_height = 0;
+ int cached_total_end = 0;
+ Point2 sticky_offset;
+
LocalVector children_cache;
bool is_root = false; // For tree root.
Tree *tree = nullptr; // Tree (for reference).
@@ -517,6 +522,10 @@ private:
bool propagate_mouse_activated = false;
float content_scale_factor = 0.0;
+ int sticky_stack_end = 0;
+ LocalVector sticky_candidates;
+ LocalVector sticky_list;
+
Rect2 custom_popup_rect;
int edited_col = -1;
int selected_col = -1;
@@ -560,6 +569,7 @@ private:
RID custom_ci; // Separate canvas item for drawing custom content.
RID header_ci; // Separate canvas item for drawing column headers.
RID content_ci; // Separate canvas item for drawing tree rows.
+ RID last_sticky_ci; // Separate canvas item for drawing the last sticky item, which can partially hide underneath other sticky items.
RID drop_indicator_ci;
VBoxContainer *popup_editor_vb = nullptr;
@@ -587,10 +597,10 @@ private:
void update_column(int p_col);
void update_item_cell(TreeItem *p_item, int p_col) const;
void update_item_cache(TreeItem *p_item) const;
- void draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) const;
- int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item, int &r_self_height);
+ void draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color, const RID &p_ci) const;
+ int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item, int &r_self_height, const RID &p_ci);
void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false);
- int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref &p_mod);
+ int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref &p_mod, bool p_first_call = true, bool p_skip_children = false);
void _line_editor_submit(String p_text);
void _apply_multiline_edit(bool p_hide_focus = false);
void _text_editor_popup_modal_close();
@@ -693,6 +703,7 @@ private:
int scroll_border = 0;
int scroll_speed = 0;
+ int scroll_max_sticky_items = 5;
int scrollbar_margin_top = -1;
int scrollbar_margin_right = -1;
@@ -748,7 +759,7 @@ private:
TreeItem *_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards = false);
- TreeItem *_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &r_height, int &r_section) const;
+ TreeItem *_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &r_height, int &r_section, bool p_sticky_check = true) const;
void _find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index, int &r_section) const;