summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/classes/Tree.xml15
-rw-r--r--editor/project_manager/project_list.cpp2
-rw-r--r--editor/themes/editor_theme_manager.cpp10
-rw-r--r--scene/gui/tree.cpp395
-rw-r--r--scene/gui/tree.h18
-rw-r--r--scene/theme/default_theme.cpp5
6 files changed, 340 insertions, 105 deletions
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index b0cb25fafd..9510d237da 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -509,6 +509,12 @@
<theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
Text [Color] for a [constant TreeItem.CELL_MODE_CHECK] mode cell when it's non-editable (see [method TreeItem.set_editable]).
</theme_item>
+ <theme_item name="font_hovered_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
+ Text [Color] used when the item is hovered.
+ </theme_item>
+ <theme_item name="font_hovered_dimmed_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
+ Text [Color] used when the item is hovered, while a button of the same item is hovered as the same time.
+ </theme_item>
<theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)">
The tint of text outline of the item.
</theme_item>
@@ -645,6 +651,9 @@
<theme_item name="updown" data_type="icon" type="Texture2D">
The updown arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell.
</theme_item>
+ <theme_item name="button_hover" data_type="style" type="StyleBox">
+ [StyleBox] used when a button in the tree is hovered.
+ </theme_item>
<theme_item name="button_pressed" data_type="style" type="StyleBox">
[StyleBox] used when a button in the tree is pressed.
</theme_item>
@@ -666,6 +675,12 @@
<theme_item name="focus" data_type="style" type="StyleBox">
The focused style for the [Tree], drawn on top of everything.
</theme_item>
+ <theme_item name="hovered" data_type="style" type="StyleBox">
+ [StyleBox] for the item being hovered.
+ </theme_item>
+ <theme_item name="hovered_dimmed" data_type="style" type="StyleBox">
+ [StyleBox] for the item being hovered, while a button of the same item is hovered as the same time.
+ </theme_item>
<theme_item name="panel" data_type="style" type="StyleBox">
The background style for the [Tree].
</theme_item>
diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp
index 541ab01e62..39c1a78c4a 100644
--- a/editor/project_manager/project_list.cpp
+++ b/editor/project_manager/project_list.cpp
@@ -88,7 +88,7 @@ void ProjectListItemControl::_notification(int p_what) {
draw_style_box(get_theme_stylebox(SNAME("selected"), SNAME("Tree")), Rect2(Point2(), get_size()));
}
if (is_hovering) {
- draw_style_box(get_theme_stylebox(SNAME("hover"), SNAME("Tree")), Rect2(Point2(), get_size()));
+ draw_style_box(get_theme_stylebox(SNAME("hovered"), SNAME("Tree")), Rect2(Point2(), get_size()));
}
draw_line(Point2(0, get_size().y + 1), Point2(get_size().x, get_size().y + 1), get_theme_color(SNAME("guide_color"), SNAME("Tree")));
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 17bcbacfc2..cdc4087142 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -945,6 +945,8 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_color("custom_button_font_highlight", "Tree", p_config.font_hover_color);
p_theme->set_color(SceneStringName(font_color), "Tree", p_config.font_color);
+ p_theme->set_color("font_hovered_color", "Tree", p_config.mono_color);
+ p_theme->set_color("font_hovered_dimmed_color", "Tree", p_config.font_color);
p_theme->set_color("font_selected_color", "Tree", p_config.mono_color);
p_theme->set_color("font_disabled_color", "Tree", p_config.font_disabled_color);
p_theme->set_color("font_outline_color", "Tree", p_config.font_outline_color);
@@ -997,7 +999,13 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
Ref<StyleBoxFlat> style_tree_hover = p_config.base_style->duplicate();
style_tree_hover->set_bg_color(p_config.highlight_color * Color(1, 1, 1, 0.4));
style_tree_hover->set_border_width_all(0);
- p_theme->set_stylebox("hover", "Tree", style_tree_hover);
+ p_theme->set_stylebox("hovered", "Tree", style_tree_hover);
+ p_theme->set_stylebox("button_hover", "Tree", style_tree_hover);
+
+ Ref<StyleBoxFlat> style_tree_hover_dimmed = p_config.base_style->duplicate();
+ style_tree_hover_dimmed->set_bg_color(p_config.highlight_color * Color(1, 1, 1, 0.2));
+ style_tree_hover_dimmed->set_border_width_all(0);
+ p_theme->set_stylebox("hovered_dimmed", "Tree", style_tree_hover_dimmed);
p_theme->set_stylebox("selected_focus", "Tree", style_tree_focus);
p_theme->set_stylebox("selected", "Tree", style_tree_selected);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 646cd9c70e..c2a9e80287 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -2165,12 +2165,18 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.h_separation : theme_cache.item_margin);
int skip2 = 0;
+
+ bool is_row_hovered = (!cache.hover_header_row && cache.hover_item == p_item);
+
for (int i = 0; i < columns.size(); i++) {
if (skip2) {
skip2--;
continue;
}
+ bool is_col_hovered = cache.hover_column == i;
+ bool is_cell_hovered = is_row_hovered && is_col_hovered;
+ bool is_cell_button_hovered = is_cell_hovered && cache.hover_button_index_in_column != -1;
int item_width = get_column_width(i);
if (i == 0) {
@@ -2203,6 +2209,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int total_ofs = ofs - theme_cache.offset.x;
+ // If part of the column is beyond the right side of the control due to scrolling, clamp the label width
+ // so that all buttons attached to the cell remain within view.
if (total_ofs + item_width > p_draw_size.width) {
item_width = MAX(buttons_width, p_draw_size.width - total_ofs);
}
@@ -2247,17 +2255,42 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(r.position.x, r.position.y + r.size.height), r.position + r.size, theme_cache.guide_color, 1);
}
- if (i == 0) {
- if (p_item->cells[0].selected && select_mode == SELECT_ROW) {
+ if (i == 0 && select_mode == SELECT_ROW) {
+ if (p_item->cells[0].selected || is_row_hovered) {
const Rect2 content_rect = _get_content_rect();
Rect2i row_rect = Rect2i(Point2i(content_rect.position.x, item_rect.position.y), Size2i(content_rect.size.x, item_rect.size.y));
if (rtl) {
row_rect.position.x = get_size().width - row_rect.position.x - row_rect.size.x;
}
- if (has_focus()) {
- theme_cache.selected_focus->draw(ci, row_rect);
+
+ if (p_item->cells[0].selected) {
+ if (has_focus()) {
+ theme_cache.selected_focus->draw(ci, row_rect);
+ } else {
+ theme_cache.selected->draw(ci, row_rect);
+ }
+ } else if (!drop_mode_flags) {
+ if (is_cell_button_hovered) {
+ theme_cache.hovered_dimmed->draw(ci, row_rect);
+ } else {
+ theme_cache.hovered->draw(ci, row_rect);
+ }
+ }
+ }
+ }
+
+ if (select_mode != SELECT_ROW) {
+ Rect2i r = cell_rect;
+ if (rtl) {
+ r.position.x = get_size().width - r.position.x - r.size.x;
+ }
+
+ // Cell hover.
+ if (is_cell_hovered && !p_item->cells[i].selected && !drop_mode_flags) {
+ if (is_cell_button_hovered) {
+ theme_cache.hovered_dimmed->draw(ci, r);
} else {
- theme_cache.selected->draw(ci, row_rect);
+ theme_cache.hovered->draw(ci, r);
}
}
}
@@ -2335,7 +2368,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (p_item->cells[i].custom_color) {
cell_color = p_item->cells[i].color;
} else {
- cell_color = p_item->cells[i].selected ? theme_cache.font_selected_color : theme_cache.font_color;
+ bool draw_as_hover = !drop_mode_flags && (select_mode == SELECT_ROW ? is_row_hovered : is_cell_hovered);
+ bool draw_as_hover_dim = draw_as_hover && is_cell_button_hovered;
+ cell_color = p_item->cells[i].selected ? theme_cache.font_selected_color : (draw_as_hover_dim ? theme_cache.font_hovered_dimmed_color : (draw_as_hover ? theme_cache.font_hovered_color : theme_cache.font_color));
}
Color font_outline_color = theme_cache.font_outline_color;
@@ -2487,7 +2522,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
ir.size.width -= downarrow->get_width();
if (p_item->cells[i].custom_button) {
- if (cache.hover_item == p_item && cache.hover_cell == i) {
+ 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);
} else {
@@ -2508,19 +2543,26 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
} break;
}
+ // Draw the buttons inside the cell.
for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> button_texture = p_item->cells[i].buttons[j].texture;
Size2 button_size = button_texture->get_size() + theme_cache.button_pressed->get_minimum_size();
Point2i button_ofs = Point2i(ofs + item_width_with_buttons - button_size.width, p_pos.y) - theme_cache.offset + p_draw_ofs;
- if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) {
- // Being pressed.
+ bool should_draw_pressed = cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled;
+ bool should_draw_hovered = !should_draw_pressed && !drop_mode_flags && cache.hover_item == p_item && cache.hover_column == i && cache.hover_button_index_in_column == j && !p_item->cells[i].buttons[j].disabled;
+
+ if (should_draw_pressed || should_draw_hovered) {
Point2 od = button_ofs;
if (rtl) {
od.x = get_size().width - od.x - button_size.x;
}
- theme_cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
+ if (should_draw_pressed) {
+ theme_cache.button_pressed->draw(get_canvas_item(), 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)));
+ }
}
button_ofs.y += (label_h - button_size.height) / 2;
@@ -2551,6 +2593,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
+ // 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;
@@ -2966,6 +3009,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
col_width = MAX(button_w, MIN(limit_w, col_width));
}
+ // Cell button detection code.
for (int j = c.buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = c.buttons[j].texture;
int w = b->get_size().width + theme_cache.button_pressed->get_minimum_size().width;
@@ -3485,7 +3529,11 @@ bool Tree::_scroll(bool p_horizontal, float p_pages) {
double prev_value = scroll->get_value();
scroll->set_value(scroll->get_value() + scroll->get_page() * p_pages);
- return scroll->get_value() != prev_value;
+ bool scroll_happened = scroll->get_value() != prev_value;
+ if (scroll_happened) {
+ _determine_hovered_item();
+ }
+ return scroll_happened;
}
Rect2 Tree::_get_scrollbar_layout_rect() const {
@@ -3710,92 +3758,10 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
- Ref<StyleBox> bg = theme_cache.panel_style;
- bool rtl = is_layout_rtl();
-
- Point2 pos = mm->get_position();
- if (rtl) {
- pos.x = get_size().width - pos.x;
- }
- pos -= theme_cache.panel_style->get_offset();
-
- Cache::ClickType old_hover = cache.hover_type;
- int old_index = cache.hover_index;
-
- cache.hover_type = Cache::CLICK_NONE;
- cache.hover_index = 0;
- if (show_column_titles) {
- pos.y -= _get_title_button_height();
- if (pos.y < 0) {
- pos.x += theme_cache.offset.x;
- int len = 0;
- for (int i = 0; i < columns.size(); i++) {
- len += get_column_width(i);
- if (pos.x < len) {
- cache.hover_type = Cache::CLICK_TITLE;
- cache.hover_index = i;
- break;
- }
- }
- }
- }
-
- if (root) {
- Point2 mpos = mm->get_position();
- if (rtl) {
- mpos.x = get_size().width - mpos.x;
- }
- mpos -= theme_cache.panel_style->get_offset();
- mpos.y -= _get_title_button_height();
- if (mpos.y >= 0) {
- if (h_scroll->is_visible_in_tree()) {
- mpos.x += h_scroll->get_value();
- }
- if (v_scroll->is_visible_in_tree()) {
- mpos.y += v_scroll->get_value();
- }
-
- TreeItem *old_it = cache.hover_item;
- int old_col = cache.hover_cell;
-
- int col, h, section;
- TreeItem *it = _find_item_at_pos(root, mpos, col, h, section);
-
- if (drop_mode_flags) {
- if (it != drop_mode_over) {
- drop_mode_over = it;
- queue_redraw();
- }
- if (it && section != drop_mode_section) {
- drop_mode_section = section;
- queue_redraw();
- }
- }
-
- cache.hover_item = it;
- cache.hover_cell = col;
-
- if (it != old_it || col != old_col) {
- if (old_it && old_col >= old_it->cells.size()) {
- // Columns may have changed since last redraw().
- queue_redraw();
- } else {
- // Only need to update if mouse enters/exits a button
- bool was_over_button = old_it && old_it->cells[old_col].custom_button;
- bool is_over_button = it && it->cells[col].custom_button;
- if (was_over_button || is_over_button) {
- queue_redraw();
- }
- }
- }
- }
- }
-
- // Update if mouse enters/exits columns
- if (cache.hover_type != old_hover || cache.hover_index != old_index) {
- queue_redraw();
- }
+ hovered_pos = mm->get_position();
+ _determine_hovered_item();
+ bool rtl = is_layout_rtl();
if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) {
/* This needs to happen now, because the popup can be closed when pressing another item, and must remain the popup edited item until it actually closes */
popup_edited_item = popup_pressing_edited_item;
@@ -4080,6 +4046,174 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
}
+void Tree::_determine_hovered_item() {
+ Ref<StyleBox> bg = theme_cache.panel_style;
+ bool rtl = is_layout_rtl();
+
+ Point2 pos = hovered_pos;
+ if (rtl) {
+ pos.x = get_size().width - pos.x;
+ }
+ pos -= theme_cache.panel_style->get_offset();
+
+ bool old_header_row = cache.hover_header_row;
+ int old_header_column = cache.hover_header_column;
+ TreeItem *old_item = cache.hover_item;
+ int old_column = cache.hover_column;
+ int old_button_index_in_column = cache.hover_button_index_in_column;
+
+ // Determine hover on column headers.
+ cache.hover_header_row = false;
+ cache.hover_header_column = 0;
+ if (show_column_titles && is_mouse_hovering) {
+ pos.y -= _get_title_button_height();
+ if (pos.y < 0) {
+ pos.x += theme_cache.offset.x;
+ int len = 0;
+ for (int i = 0; i < columns.size(); i++) {
+ len += get_column_width(i);
+ if (pos.x < len) {
+ cache.hover_header_row = true;
+ cache.hover_header_column = i;
+ cache.hover_button_index_in_column = -1;
+ break;
+ }
+ }
+ }
+ }
+
+ // Determine hover on rows and items.
+ if (root && is_mouse_hovering) {
+ Point2 mpos = hovered_pos;
+ if (rtl) {
+ mpos.x = get_size().width - mpos.x;
+ }
+ mpos -= theme_cache.panel_style->get_offset();
+ mpos.y -= _get_title_button_height();
+ if (mpos.y >= 0) {
+ if (h_scroll->is_visible_in_tree()) {
+ mpos.x += h_scroll->get_value();
+ }
+ if (v_scroll->is_visible_in_tree()) {
+ mpos.y += v_scroll->get_value();
+ }
+
+ int col, h, section;
+ TreeItem *it = _find_item_at_pos(root, mpos, col, h, section);
+
+ // Find possible hovered button in cell.
+ int col_button_index = -1;
+
+ Point2 cpos = mpos;
+
+ if (it) {
+ const TreeItem::Cell &c = it->cells[col];
+ int col_width = get_column_width(col);
+
+ // In the first column, tree nesting indent impacts the leftmost possible buttons position
+ // and the clickable area of the folding arrow.
+ int col_indent = 0;
+ if (col == 0) {
+ col_indent = _get_item_h_offset(it);
+ }
+
+ // Compute total width of buttons block including spacings.
+ int buttons_width = 0;
+ for (int j = c.buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = c.buttons[j].texture;
+ Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
+ buttons_width += size.width + theme_cache.button_margin;
+ }
+
+ // Adjust when buttons are shifted left into view so that they remain visible even
+ // if part of the cell is beyond the right border due to horizontal scrolling and
+ // a long string in one of the items. This matches the drawing & click handling algorithms
+ // that are based on recursion.
+ int clamped_column_offset = 0;
+ int col_left = 0;
+
+ for (int i = 0; i < col; i++) {
+ int i_col_w = get_column_width(i);
+ cpos.x -= i_col_w;
+ col_left += i_col_w;
+ }
+ col_left -= theme_cache.offset.x;
+
+ // Compute buttons offset that makes them visible, in comparison to what would be their
+ // natural position that would cut them off.
+ if (!rtl) {
+ const Rect2 content_rect = _get_content_rect();
+ int cw = content_rect.size.width;
+ int col_right = col_left + col_width;
+ if (col_right > cw) {
+ clamped_column_offset = col_right - cw - theme_cache.scrollbar_h_separation;
+ int max_clamp_offset = col_width - col_indent - buttons_width;
+ if (clamped_column_offset > max_clamp_offset) {
+ clamped_column_offset = max_clamp_offset;
+ }
+ }
+ }
+ col_width -= clamped_column_offset;
+
+ // Find the actual button under coordinates.
+ for (int j = c.buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = c.buttons[j].texture;
+ Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
+ if (cpos.x > col_width - size.width && col_button_index == -1) {
+ col_button_index = j;
+ }
+ col_width -= size.width + theme_cache.button_margin;
+ }
+ }
+
+ if (drop_mode_flags) {
+ if (it != drop_mode_over) {
+ drop_mode_over = it;
+ queue_redraw();
+ }
+ if (it && section != drop_mode_section) {
+ drop_mode_section = section;
+ queue_redraw();
+ }
+ }
+
+ cache.hover_item = it;
+ cache.hover_column = col;
+ cache.hover_button_index_in_column = col_button_index;
+
+ if (it != old_item || col != old_column) {
+ if (old_item && old_column >= old_item->cells.size()) {
+ // Columns may have changed since last redraw().
+ queue_redraw();
+ } else {
+ // Only need to update if mouse enters/exits a button.
+ bool was_over_button = old_item && old_item->cells[old_column].custom_button;
+ bool is_over_button = it && it->cells[col].custom_button;
+ if (was_over_button || is_over_button) {
+ queue_redraw();
+ }
+ }
+ }
+ }
+ }
+
+ // Reduce useless redraw calls.
+
+ bool hovered_cell_button_changed = (cache.hover_button_index_in_column != old_button_index_in_column);
+ bool hovered_column_changed = (cache.hover_column != old_column);
+
+ // Mouse has moved from row to row, or from cell to cell within same row unless selection mode is full row which saves a useless redraw.
+ bool item_hover_needs_redraw = !cache.hover_header_row && (cache.hover_item != old_item || hovered_cell_button_changed || (select_mode != SELECT_ROW && hovered_column_changed));
+ // Mouse has moved between two different column header sections.
+ bool header_hover_needs_redraw = cache.hover_header_row && cache.hover_header_column != old_header_column;
+ // Mouse has moved between header and "main" areas.
+ bool whole_needs_redraw = cache.hover_header_row != old_header_row;
+
+ if (whole_needs_redraw || header_hover_needs_redraw || item_hover_needs_redraw) {
+ queue_redraw();
+ }
+}
+
bool Tree::edit_selected(bool p_force_edit) {
TreeItem *s = get_selected();
ERR_FAIL_NULL_V_MSG(s, false, "No item selected.");
@@ -4304,9 +4438,20 @@ void Tree::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_MOUSE_ENTER: {
+ is_mouse_hovering = true;
+ _determine_hovered_item();
+ } break;
+
case NOTIFICATION_MOUSE_EXIT: {
- if (cache.hover_type != Cache::CLICK_NONE) {
- cache.hover_type = Cache::CLICK_NONE;
+ is_mouse_hovering = false;
+ // Clear hovered item cache.
+ if (cache.hover_header_row || cache.hover_item != nullptr) {
+ cache.hover_header_row = false;
+ cache.hover_header_column = -1;
+ cache.hover_item = nullptr;
+ cache.hover_column = -1;
+ cache.hover_button_index_in_column = -1;
queue_redraw();
}
} break;
@@ -4420,7 +4565,7 @@ void Tree::_notification(int p_what) {
//title buttons
int ofs2 = theme_cache.panel_style->get_margin(SIDE_LEFT);
for (int i = 0; i < columns.size(); i++) {
- Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? theme_cache.title_button_hover : theme_cache.title_button);
+ Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_header_row && cache.hover_header_column == i) ? theme_cache.title_button_hover : theme_cache.title_button);
Rect2 tbrect = Rect2(ofs2 - theme_cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh);
if (cache.rtl) {
tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x;
@@ -4536,6 +4681,8 @@ TreeItem *Tree::create_item(TreeItem *p_parent, int p_index) {
}
}
+ _determine_hovered_item();
+
return ti;
}
@@ -4679,6 +4826,8 @@ void Tree::clear() {
popup_edited_item = nullptr;
popup_pressing_edited_item = nullptr;
+ _determine_hovered_item();
+
queue_redraw();
};
@@ -4931,6 +5080,7 @@ int Tree::get_columns() const {
}
void Tree::_scroll_moved(float) {
+ _determine_hovered_item();
queue_redraw();
}
@@ -4972,7 +5122,47 @@ int Tree::get_item_offset(TreeItem *p_item) const {
}
}
- return -1; //not found
+ return -1; // Not found.
+}
+
+int Tree::_get_item_h_offset(TreeItem *p_item) const {
+ TreeItem *it = root;
+ int nesting_level = 0;
+ if (!it) {
+ return 0;
+ }
+
+ while (true) {
+ if (it == p_item) {
+ if (!hide_root) {
+ nesting_level += 1;
+ }
+ if (hide_folding) {
+ nesting_level -= 1;
+ }
+ return nesting_level * theme_cache.item_margin;
+ }
+
+ if (it->first_child && !it->collapsed) {
+ it = it->first_child;
+ nesting_level += 1;
+
+ } else if (it->next) {
+ it = it->next;
+ } else {
+ while (!it->next) {
+ it = it->parent;
+ nesting_level -= 1;
+ if (it == nullptr) {
+ return 0;
+ }
+ }
+
+ it = it->next;
+ }
+ }
+
+ return -1; // Not found.
}
void Tree::ensure_cursor_is_visible() {
@@ -5803,10 +5993,13 @@ void Tree::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT, Tree, tb_font, "title_button_font");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT_SIZE, Tree, tb_font_size, "title_button_font_size");
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, hovered);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, hovered_dimmed);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, selected_focus);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, cursor);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Tree, cursor_unfocus, "cursor_unfocused");
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, button_hover);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, button_pressed);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, checked);
@@ -5827,6 +6020,8 @@ void Tree::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, custom_button_font_highlight);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_color);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_hovered_color);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_hovered_dimmed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_selected_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_disabled_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, drop_position_color);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 86efdfec52..b417b7ee31 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -450,6 +450,9 @@ private:
Vector2 pressing_pos;
Rect2 pressing_item_rect;
+ Vector2 hovered_pos;
+ bool is_mouse_hovering = false;
+
float range_drag_base = 0.0;
bool range_drag_enabled = false;
Vector2 range_drag_capture_pos;
@@ -545,10 +548,13 @@ private:
int font_size = 0;
int tb_font_size = 0;
+ Ref<StyleBox> hovered;
+ Ref<StyleBox> hovered_dimmed;
Ref<StyleBox> selected;
Ref<StyleBox> selected_focus;
Ref<StyleBox> cursor;
Ref<StyleBox> cursor_unfocus;
+ Ref<StyleBox> button_hover;
Ref<StyleBox> button_pressed;
Ref<StyleBox> title_button;
Ref<StyleBox> title_button_hover;
@@ -572,6 +578,8 @@ private:
Ref<Texture2D> updown;
Color font_color;
+ Color font_hovered_color;
+ Color font_hovered_dimmed_color;
Color font_selected_color;
Color font_disabled_color;
Color guide_color;
@@ -623,16 +631,17 @@ private:
};
ClickType click_type = Cache::CLICK_NONE;
- ClickType hover_type = Cache::CLICK_NONE;
int click_index = -1;
int click_id = -1;
TreeItem *click_item = nullptr;
int click_column = 0;
- int hover_index = -1;
+ int hover_header_column = -1;
+ bool hover_header_row = false;
Point2 click_pos;
TreeItem *hover_item = nullptr;
- int hover_cell = -1;
+ int hover_column = -1;
+ int hover_button_index_in_column = -1;
bool rtl = false;
} cache;
@@ -660,6 +669,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 &h, int &section) const;
+ int _get_item_h_offset(TreeItem *p_item) const;
void _find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const;
@@ -689,6 +699,8 @@ private:
bool enable_recursive_folding = true;
+ void _determine_hovered_item();
+
int _count_selected_items(TreeItem *p_from) const;
bool _is_branch_selected(TreeItem *p_from) const;
bool _is_sibling_branch_selected(TreeItem *p_from) const;
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index be0c810be8..f5065e8de1 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -833,10 +833,13 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_stylebox(SceneStringName(panel), "Tree", make_flat_stylebox(style_normal_color, 4, 4, 4, 5));
theme->set_stylebox("focus", "Tree", focus);
+ theme->set_stylebox("hovered", "Tree", make_flat_stylebox(Color(1, 1, 1, 0.07)));
+ theme->set_stylebox("hovered_dimmed", "Tree", make_flat_stylebox(Color(1, 1, 1, 0.03)));
theme->set_stylebox("selected", "Tree", make_flat_stylebox(style_selected_color));
theme->set_stylebox("selected_focus", "Tree", make_flat_stylebox(style_selected_color));
theme->set_stylebox("cursor", "Tree", focus);
theme->set_stylebox("cursor_unfocused", "Tree", focus);
+ theme->set_stylebox("button_hover", "Tree", make_flat_stylebox(Color(1, 1, 1, 0.07)));
theme->set_stylebox("button_pressed", "Tree", button_pressed);
theme->set_stylebox("title_button_normal", "Tree", make_flat_stylebox(style_pressed_color, 4, 4, 4, 4));
theme->set_stylebox("title_button_pressed", "Tree", make_flat_stylebox(style_hover_color, 4, 4, 4, 4));
@@ -864,6 +867,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("title_button_color", "Tree", control_font_color);
theme->set_color(SceneStringName(font_color), "Tree", control_font_low_color);
+ theme->set_color("font_hovered_color", "Tree", control_font_hover_color);
+ theme->set_color("font_hovered_dimmed_color", "Tree", control_font_color);
theme->set_color("font_selected_color", "Tree", control_font_pressed_color);
theme->set_color("font_disabled_color", "Tree", control_font_disabled_color);
theme->set_color("font_outline_color", "Tree", Color(0, 0, 0));