diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index b0cb25fafd8..9510d237da9 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -509,6 +509,12 @@ Text [Color] for a [constant TreeItem.CELL_MODE_CHECK] mode cell when it's non-editable (see [method TreeItem.set_editable]). + + Text [Color] used when the item is hovered. + + + Text [Color] used when the item is hovered, while a button of the same item is hovered as the same time. + The tint of text outline of the item. @@ -645,6 +651,9 @@ The updown arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell. + + [StyleBox] used when a button in the tree is hovered. + [StyleBox] used when a button in the tree is pressed. @@ -666,6 +675,12 @@ The focused style for the [Tree], drawn on top of everything. + + [StyleBox] for the item being hovered. + + + [StyleBox] for the item being hovered, while a button of the same item is hovered as the same time. + The background style for the [Tree]. diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index 541ab01e626..39c1a78c4ad 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 17bcbacfc20..cdc40871422 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 &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 &p_the Ref 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 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 646cd9c70e3..c2a9e80287d 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 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 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 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 &p_event) { Ref mm = p_event; if (mm.is_valid()) { - Ref bg = theme_cache.panel_style; + hovered_pos = mm->get_position(); + _determine_hovered_item(); + 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)) { /* 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 &p_event) { } } +void Tree::_determine_hovered_item() { + Ref 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 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 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 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 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 86efdfec522..b417b7ee316 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 hovered; + Ref hovered_dimmed; Ref selected; Ref selected_focus; Ref cursor; Ref cursor_unfocus; + Ref button_hover; Ref button_pressed; Ref title_button; Ref title_button_hover; @@ -572,6 +578,8 @@ private: Ref 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 §ion) 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 be0c810be8b..f5065e8de10 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -833,10 +833,13 @@ void fill_default_theme(Ref &theme, const Ref &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, const Ref &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));