Merge pull request #88530 from davthedev/tree-hover-items

Add hover state to Tree items display
This commit is contained in:
Thaddeus Crews 2024-10-21 16:39:16 -05:00
commit 77da16ce69
No known key found for this signature in database
GPG Key ID: 62181B86FE9E5D84
6 changed files with 340 additions and 105 deletions

View File

@ -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)"> <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]). 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>
<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)"> <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. The tint of text outline of the item.
</theme_item> </theme_item>
@ -645,6 +651,9 @@
<theme_item name="updown" data_type="icon" type="Texture2D"> <theme_item name="updown" data_type="icon" type="Texture2D">
The updown arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell. The updown arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell.
</theme_item> </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"> <theme_item name="button_pressed" data_type="style" type="StyleBox">
[StyleBox] used when a button in the tree is pressed. [StyleBox] used when a button in the tree is pressed.
</theme_item> </theme_item>
@ -666,6 +675,12 @@
<theme_item name="focus" data_type="style" type="StyleBox"> <theme_item name="focus" data_type="style" type="StyleBox">
The focused style for the [Tree], drawn on top of everything. The focused style for the [Tree], drawn on top of everything.
</theme_item> </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"> <theme_item name="panel" data_type="style" type="StyleBox">
The background style for the [Tree]. The background style for the [Tree].
</theme_item> </theme_item>

View File

@ -88,7 +88,7 @@ void ProjectListItemControl::_notification(int p_what) {
draw_style_box(get_theme_stylebox(SNAME("selected"), SNAME("Tree")), Rect2(Point2(), get_size())); draw_style_box(get_theme_stylebox(SNAME("selected"), SNAME("Tree")), Rect2(Point2(), get_size()));
} }
if (is_hovering) { 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"))); draw_line(Point2(0, get_size().y + 1), Point2(get_size().x, get_size().y + 1), get_theme_color(SNAME("guide_color"), SNAME("Tree")));

View File

@ -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("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(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_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_disabled_color", "Tree", p_config.font_disabled_color);
p_theme->set_color("font_outline_color", "Tree", p_config.font_outline_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(); 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_bg_color(p_config.highlight_color * Color(1, 1, 1, 0.4));
style_tree_hover->set_border_width_all(0); 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_focus", "Tree", style_tree_focus);
p_theme->set_stylebox("selected", "Tree", style_tree_selected); p_theme->set_stylebox("selected", "Tree", style_tree_selected);

View File

@ -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 ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.h_separation : theme_cache.item_margin);
int skip2 = 0; int skip2 = 0;
bool is_row_hovered = (!cache.hover_header_row && cache.hover_item == p_item);
for (int i = 0; i < columns.size(); i++) { for (int i = 0; i < columns.size(); i++) {
if (skip2) { if (skip2) {
skip2--; skip2--;
continue; 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); int item_width = get_column_width(i);
if (i == 0) { 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; 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) { if (total_ofs + item_width > p_draw_size.width) {
item_width = MAX(buttons_width, p_draw_size.width - total_ofs); 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); 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 (i == 0 && select_mode == SELECT_ROW) {
if (p_item->cells[0].selected && select_mode == SELECT_ROW) { if (p_item->cells[0].selected || is_row_hovered) {
const Rect2 content_rect = _get_content_rect(); 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)); Rect2i row_rect = Rect2i(Point2i(content_rect.position.x, item_rect.position.y), Size2i(content_rect.size.x, item_rect.size.y));
if (rtl) { if (rtl) {
row_rect.position.x = get_size().width - row_rect.position.x - row_rect.size.x; 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 { } 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) { if (p_item->cells[i].custom_color) {
cell_color = p_item->cells[i].color; cell_color = p_item->cells[i].color;
} else { } 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; 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(); ir.size.width -= downarrow->get_width();
if (p_item->cells[i].custom_button) { 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)) { if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
draw_style_box(theme_cache.custom_button_pressed, ir); draw_style_box(theme_cache.custom_button_pressed, ir);
} else { } else {
@ -2508,19 +2543,26 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
} break; } break;
} }
// Draw the buttons inside the cell.
for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> button_texture = p_item->cells[i].buttons[j].texture; 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(); 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; 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) { 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;
// Being pressed. 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; Point2 od = button_ofs;
if (rtl) { if (rtl) {
od.x = get_size().width - od.x - button_size.x; 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; 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 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; 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)); col_width = MAX(button_w, MIN(limit_w, col_width));
} }
// Cell button detection code.
for (int j = c.buttons.size() - 1; j >= 0; j--) { for (int j = c.buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = c.buttons[j].texture; Ref<Texture2D> b = c.buttons[j].texture;
int w = b->get_size().width + theme_cache.button_pressed->get_minimum_size().width; 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(); double prev_value = scroll->get_value();
scroll->set_value(scroll->get_value() + scroll->get_page() * p_pages); 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 { 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; Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) { if (mm.is_valid()) {
Ref<StyleBox> bg = theme_cache.panel_style; hovered_pos = mm->get_position();
_determine_hovered_item();
bool rtl = is_layout_rtl(); 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();
}
if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) { 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 */ /* 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; 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) { bool Tree::edit_selected(bool p_force_edit) {
TreeItem *s = get_selected(); TreeItem *s = get_selected();
ERR_FAIL_NULL_V_MSG(s, false, "No item selected."); ERR_FAIL_NULL_V_MSG(s, false, "No item selected.");
@ -4304,9 +4438,20 @@ void Tree::_notification(int p_what) {
} }
} break; } break;
case NOTIFICATION_MOUSE_ENTER: {
is_mouse_hovering = true;
_determine_hovered_item();
} break;
case NOTIFICATION_MOUSE_EXIT: { case NOTIFICATION_MOUSE_EXIT: {
if (cache.hover_type != Cache::CLICK_NONE) { is_mouse_hovering = false;
cache.hover_type = Cache::CLICK_NONE; // 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(); queue_redraw();
} }
} break; } break;
@ -4420,7 +4565,7 @@ void Tree::_notification(int p_what) {
//title buttons //title buttons
int ofs2 = theme_cache.panel_style->get_margin(SIDE_LEFT); int ofs2 = theme_cache.panel_style->get_margin(SIDE_LEFT);
for (int i = 0; i < columns.size(); i++) { 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); Rect2 tbrect = Rect2(ofs2 - theme_cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh);
if (cache.rtl) { if (cache.rtl) {
tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x; 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; return ti;
} }
@ -4679,6 +4826,8 @@ void Tree::clear() {
popup_edited_item = nullptr; popup_edited_item = nullptr;
popup_pressing_edited_item = nullptr; popup_pressing_edited_item = nullptr;
_determine_hovered_item();
queue_redraw(); queue_redraw();
}; };
@ -4931,6 +5080,7 @@ int Tree::get_columns() const {
} }
void Tree::_scroll_moved(float) { void Tree::_scroll_moved(float) {
_determine_hovered_item();
queue_redraw(); 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() { 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, 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_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);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, selected_focus); BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, selected_focus);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, cursor); 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_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_STYLEBOX, Tree, button_pressed);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, checked); 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, custom_button_font_highlight);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_color); 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_selected_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_disabled_color); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_disabled_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, drop_position_color); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, drop_position_color);

View File

@ -450,6 +450,9 @@ private:
Vector2 pressing_pos; Vector2 pressing_pos;
Rect2 pressing_item_rect; Rect2 pressing_item_rect;
Vector2 hovered_pos;
bool is_mouse_hovering = false;
float range_drag_base = 0.0; float range_drag_base = 0.0;
bool range_drag_enabled = false; bool range_drag_enabled = false;
Vector2 range_drag_capture_pos; Vector2 range_drag_capture_pos;
@ -545,10 +548,13 @@ private:
int font_size = 0; int font_size = 0;
int tb_font_size = 0; int tb_font_size = 0;
Ref<StyleBox> hovered;
Ref<StyleBox> hovered_dimmed;
Ref<StyleBox> selected; Ref<StyleBox> selected;
Ref<StyleBox> selected_focus; Ref<StyleBox> selected_focus;
Ref<StyleBox> cursor; Ref<StyleBox> cursor;
Ref<StyleBox> cursor_unfocus; Ref<StyleBox> cursor_unfocus;
Ref<StyleBox> button_hover;
Ref<StyleBox> button_pressed; Ref<StyleBox> button_pressed;
Ref<StyleBox> title_button; Ref<StyleBox> title_button;
Ref<StyleBox> title_button_hover; Ref<StyleBox> title_button_hover;
@ -572,6 +578,8 @@ private:
Ref<Texture2D> updown; Ref<Texture2D> updown;
Color font_color; Color font_color;
Color font_hovered_color;
Color font_hovered_dimmed_color;
Color font_selected_color; Color font_selected_color;
Color font_disabled_color; Color font_disabled_color;
Color guide_color; Color guide_color;
@ -623,16 +631,17 @@ private:
}; };
ClickType click_type = Cache::CLICK_NONE; ClickType click_type = Cache::CLICK_NONE;
ClickType hover_type = Cache::CLICK_NONE;
int click_index = -1; int click_index = -1;
int click_id = -1; int click_id = -1;
TreeItem *click_item = nullptr; TreeItem *click_item = nullptr;
int click_column = 0; int click_column = 0;
int hover_index = -1; int hover_header_column = -1;
bool hover_header_row = false;
Point2 click_pos; Point2 click_pos;
TreeItem *hover_item = nullptr; TreeItem *hover_item = nullptr;
int hover_cell = -1; int hover_column = -1;
int hover_button_index_in_column = -1;
bool rtl = false; bool rtl = false;
} cache; } 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 *_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; 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; 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; bool enable_recursive_folding = true;
void _determine_hovered_item();
int _count_selected_items(TreeItem *p_from) const; int _count_selected_items(TreeItem *p_from) const;
bool _is_branch_selected(TreeItem *p_from) const; bool _is_branch_selected(TreeItem *p_from) const;
bool _is_sibling_branch_selected(TreeItem *p_from) const; bool _is_sibling_branch_selected(TreeItem *p_from) const;

View File

@ -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(SceneStringName(panel), "Tree", make_flat_stylebox(style_normal_color, 4, 4, 4, 5));
theme->set_stylebox("focus", "Tree", focus); 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", "Tree", make_flat_stylebox(style_selected_color));
theme->set_stylebox("selected_focus", "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", "Tree", focus);
theme->set_stylebox("cursor_unfocused", "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("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_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)); 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("title_button_color", "Tree", control_font_color);
theme->set_color(SceneStringName(font_color), "Tree", control_font_low_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_selected_color", "Tree", control_font_pressed_color);
theme->set_color("font_disabled_color", "Tree", control_font_disabled_color); theme->set_color("font_disabled_color", "Tree", control_font_disabled_color);
theme->set_color("font_outline_color", "Tree", Color(0, 0, 0)); theme->set_color("font_outline_color", "Tree", Color(0, 0, 0));