diff options
Diffstat (limited to 'scene/gui')
-rw-r--r-- | scene/gui/check_box.cpp | 18 | ||||
-rw-r--r-- | scene/gui/code_edit.cpp | 60 | ||||
-rw-r--r-- | scene/gui/code_edit.h | 2 | ||||
-rw-r--r-- | scene/gui/control.cpp | 29 | ||||
-rw-r--r-- | scene/gui/control.h | 3 | ||||
-rw-r--r-- | scene/gui/graph_edit.cpp | 10 | ||||
-rw-r--r-- | scene/gui/label.cpp | 77 | ||||
-rw-r--r-- | scene/gui/line_edit.cpp | 2 | ||||
-rw-r--r-- | scene/gui/link_button.cpp | 16 | ||||
-rw-r--r-- | scene/gui/link_button.h | 1 | ||||
-rw-r--r-- | scene/gui/option_button.h | 4 | ||||
-rw-r--r-- | scene/gui/popup_menu.h | 4 | ||||
-rw-r--r-- | scene/gui/rich_text_label.cpp | 28 | ||||
-rw-r--r-- | scene/gui/rich_text_label.h | 6 | ||||
-rw-r--r-- | scene/gui/scroll_bar.cpp | 26 | ||||
-rw-r--r-- | scene/gui/scroll_bar.h | 3 | ||||
-rw-r--r-- | scene/gui/tab_container.cpp | 1 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 220 | ||||
-rw-r--r-- | scene/gui/text_edit.h | 26 | ||||
-rw-r--r-- | scene/gui/tree.cpp | 113 | ||||
-rw-r--r-- | scene/gui/tree.h | 3 |
21 files changed, 504 insertions, 148 deletions
diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index d93107df2d..411fb2e1f0 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -34,11 +34,13 @@ Size2 CheckBox::get_icon_size() const { Ref<Texture2D> checked = Control::get_theme_icon(SNAME("checked")); - Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled")); Ref<Texture2D> unchecked = Control::get_theme_icon(SNAME("unchecked")); - Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled")); Ref<Texture2D> radio_checked = Control::get_theme_icon(SNAME("radio_checked")); Ref<Texture2D> radio_unchecked = Control::get_theme_icon(SNAME("radio_unchecked")); + Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled")); + Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled")); + Ref<Texture2D> radio_checked_disabled = Control::get_theme_icon(SNAME("radio_checked_disabled")); + Ref<Texture2D> radio_unchecked_disabled = Control::get_theme_icon(SNAME("radio_unchecked_disabled")); Size2 tex_size = Size2(0, 0); if (!checked.is_null()) { @@ -53,6 +55,18 @@ Size2 CheckBox::get_icon_size() const { if (!radio_unchecked.is_null()) { tex_size = Size2(MAX(tex_size.width, radio_unchecked->get_width()), MAX(tex_size.height, radio_unchecked->get_height())); } + if (!checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, checked_disabled->get_width()), MAX(tex_size.height, checked_disabled->get_height())); + } + if (!unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, unchecked_disabled->get_width()), MAX(tex_size.height, unchecked_disabled->get_height())); + } + if (!radio_checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, radio_checked_disabled->get_width()), MAX(tex_size.height, radio_checked_disabled->get_height())); + } + if (!radio_unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, radio_unchecked_disabled->get_width()), MAX(tex_size.height, radio_unchecked_disabled->get_height())); + } return tex_size; } diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 5f3ab18cca..e7769f9372 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -654,7 +654,7 @@ void CodeEdit::_backspace_internal() { // For space indentation we need to do a simple unindent if there are no chars to the left, acting in the // same way as tabs. if (indent_using_spaces && cc != 0) { - if (get_first_non_whitespace_column(cl) > cc) { + if (get_first_non_whitespace_column(cl) >= cc) { prev_column = cc - _calculate_spaces_till_next_left_indent(cc); prev_line = cl; } @@ -987,10 +987,10 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { /* No need to move the brace below if we are not taking the text with us. */ if (p_split_current_line) { brace_indent = true; - ins += "\n" + ins.substr(1, ins.length() - 2); + ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2); } else { brace_indent = false; - ins = "\n" + ins.substr(1, ins.length() - 2); + ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2); } } } @@ -1400,15 +1400,21 @@ void CodeEdit::fold_line(int p_line) { } /* Find the last line to be hidden. */ - int end_line = get_line_count(); + const int line_count = get_line_count() - 1; + int end_line = line_count; int in_comment = is_in_comment(p_line); int in_string = (in_comment == -1) ? is_in_string(p_line) : -1; if (in_string != -1 || in_comment != -1) { end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y; - /* End line is the same therefore we have a block. */ + /* End line is the same therefore we have a block of single line delimiters. */ if (end_line == p_line) { - for (int i = p_line + 1; i < get_line_count(); i++) { + for (int i = p_line + 1; i <= line_count; i++) { + if (i == line_count) { + end_line = line_count; + break; + } + if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) { end_line = i - 1; break; @@ -1417,14 +1423,27 @@ void CodeEdit::fold_line(int p_line) { } } else { int start_indent = get_indent_level(p_line); - for (int i = p_line + 1; i < get_line_count(); i++) { + for (int i = p_line + 1; i <= line_count; i++) { if (get_line(p_line).strip_edges().size() == 0 || is_in_string(i) != -1 || is_in_comment(i) != -1) { end_line = i; continue; } - if (get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0) { + if (i == line_count) { + /* Do not fold empty last line of script if any */ + end_line = i; + if (get_line(i).strip_edges().size() == 0) { + end_line--; + } + break; + } + + if ((get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0)) { + /* Keep an empty line unfolded if any */ end_line = i - 1; + if (get_line(i - 1).strip_edges().size() == 0 && i - 2 > p_line) { + end_line = i - 2; + } break; } } @@ -1603,7 +1622,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { } /* Region was found on this line and is not a multiline continuation. */ - if (start_position.x != -1 && start_position.x != get_line(p_line).length() + 1) { + int line_length = get_line(p_line).length(); + if (start_position.x != -1 && line_length > 0 && start_position.x != line_length + 1) { start_position.y = p_line; return start_position; } @@ -1622,7 +1642,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { start_position.x = delimiter_cache[i].back()->key(); /* Make sure it's not a multiline continuation. */ - if (start_position.x != get_line(i).length() + 1) { + line_length = get_line(i).length(); + if (line_length > 0 && start_position.x != line_length + 1) { break; } } @@ -1937,7 +1958,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) { if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column() > 0 && get_caret_column() < get_line(caret_line).length()) { pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column() + 1); - if (pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) { + if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) { remove_text(caret_line, get_caret_column() - 2, caret_line, get_caret_column()); if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1) != pre_brace_pair) { set_caret_column(get_caret_column() - 1); @@ -2013,7 +2034,9 @@ String CodeEdit::get_text_for_symbol_lookup() { void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) { symbol_lookup_word = p_valid ? symbol_lookup_new_word : ""; symbol_lookup_new_word = ""; - _set_symbol_lookup_word(symbol_lookup_word); + if (lookup_symbol_word != symbol_lookup_word) { + _set_symbol_lookup_word(symbol_lookup_word); + } } void CodeEdit::_bind_methods() { @@ -2548,7 +2571,10 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c } void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type) { - if (p_start_key.length() > 0) { + // If we are the editor allow "null" as a valid start key, otherwise users cannot add delimiters via the inspector. + if (!(Engine::get_singleton()->is_editor_hint() && p_start_key == "null")) { + ERR_FAIL_COND_MSG(p_start_key.is_empty(), "delimiter start key cannot be empty"); + for (int i = 0; i < p_start_key.length(); i++) { ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "delimiter must start with a symbol"); } @@ -2613,7 +2639,11 @@ void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, Delimiter _clear_delimiters(p_type); for (int i = 0; i < p_delimiters.size(); i++) { - String key = p_delimiters[i].is_null() ? "" : p_delimiters[i]; + String key = p_delimiters[i]; + + if (key.is_empty()) { + continue; + } const String start_key = key.get_slice(" ", 0); const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String(); @@ -2744,7 +2774,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { /* If we have a space, previous word might be a keyword. eg "func |". */ } else if (cofs > 0 && line[cofs - 1] == ' ') { int ofs = cofs - 1; - while (ofs >= 0 && line[ofs] == ' ') { + while (ofs > 0 && line[ofs] == ' ') { ofs--; } prev_is_word = _is_char(line[ofs]); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 4fbb5194e6..740548d559 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -248,7 +248,6 @@ private: void _text_changed(); protected: - void gui_input(const Ref<InputEvent> &p_gui_input) override; void _notification(int p_what); static void _bind_methods(); @@ -265,6 +264,7 @@ protected: public: /* General overrides */ + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; /* Indent management */ diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 4ac6a58d36..81411d5844 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -30,6 +30,7 @@ #include "control.h" +#include "container.h" #include "core/config/project_settings.h" #include "core/math/geometry_2d.h" #include "core/object/message_queue.h" @@ -168,6 +169,20 @@ Size2 Control::_edit_get_minimum_size() const { } #endif +String Control::properties_managed_by_container[] = { + "offset_left", + "offset_top", + "offset_right", + "offset_bottom", + "anchor_left", + "anchor_top", + "anchor_right", + "anchor_bottom", + "rect_position", + "rect_scale", + "rect_size" +}; + void Control::accept_event() { if (is_inside_tree()) { get_viewport()->_gui_accept_event(); @@ -442,6 +457,20 @@ void Control::_validate_property(PropertyInfo &property) const { property.hint_string = hint_string; } + if (!Object::cast_to<Container>(get_parent())) { + return; + } + // Disable the property if it's managed by the parent container. + bool property_is_managed_by_container = false; + for (unsigned i = 0; i < properties_managed_by_container_count; i++) { + property_is_managed_by_container = properties_managed_by_container[i] == property.name; + if (property_is_managed_by_container) { + break; + } + } + if (property_is_managed_by_container) { + property.usage |= PROPERTY_USAGE_READ_ONLY; + } } Control *Control::get_parent_control() const { diff --git a/scene/gui/control.h b/scene/gui/control.h index 0faa617f8d..9cec5d6e8d 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -230,6 +230,9 @@ private: } data; + static constexpr unsigned properties_managed_by_container_count = 11; + static String properties_managed_by_container[properties_managed_by_container_count]; + // used internally Control *_find_control_at_pos(CanvasItem *p_node, const Point2 &p_pos, const Transform2D &p_xform, Transform2D &r_inv_xform); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index cabae9feb2..b9b02b1427 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -356,16 +356,6 @@ void GraphEdit::_graph_node_raised(Node *p_gn) { } else { gn->raise(); } - int first_not_comment = 0; - for (int i = 0; i < get_child_count(); i++) { - GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); - if (gn2 && !gn2->is_comment()) { - first_not_comment = i; - break; - } - } - - move_child(connections_layer, first_not_comment); emit_signal(SNAME("node_selected"), p_gn); } diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 3f87003423..5600816b2d 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -227,7 +227,15 @@ void Label::_update_visible() { } } -inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { +inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Vector2 &p_ofs) { + if (p_gl.font_rid != RID()) { + TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } else { + TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } +} + +inline void draw_glyph_outline(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { if (p_gl.font_rid != RID()) { if (p_font_shadow_color.a > 0) { TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); @@ -240,9 +248,6 @@ inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const if (p_font_outline_color.a != 0.0 && p_outline_size > 0) { TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color); } - TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); - } else { - TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); } } @@ -385,12 +390,70 @@ void Label::_notification(int p_what) { int gl_size = visual.size(); TextServer::TrimData trim_data = TS->shaped_text_get_trim_data(lines_rid[i]); + // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps. + if (font_shadow_color.a > 0 || (font_outline_color.a != 0.0 && outline_size > 0)) { + Vector2 offset = ofs; + // Draw RTL ellipsis string when necessary. + if (rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; + } + } + } + + // Draw main text. + for (int j = 0; j < gl_size; j++) { + for (int k = 0; k < glyphs[j].repeat; k++) { + if (visible_glyphs != -1) { + if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (glyhps_drawn >= visible_glyphs) { + return; + } + } + } + + // Trim when necessary. + if (trim_data.trim_pos >= 0) { + if (rtl) { + if (j < trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + continue; + } + } else { + if (j >= trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + break; + } + } + } + + // Draw glyph outlines and shadow. + draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += glyphs[j].advance; + glyhps_drawn++; + } + } + // Draw LTR ellipsis string when necessary. + if (!rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; + } + } + } + } + + // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps. + // Draw RTL ellipsis string when necessary. if (rtl && trim_data.ellipsis_pos >= 0) { for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) { for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { //Draw glyph outlines and shadow. - draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs); ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; } } @@ -421,7 +484,7 @@ void Label::_notification(int p_what) { } // Draw glyph outlines and shadow. - draw_glyph(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + draw_glyph(glyphs[j], ci, font_color, ofs); ofs.x += glyphs[j].advance; glyhps_drawn++; } @@ -431,7 +494,7 @@ void Label::_notification(int p_what) { for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) { for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { //Draw glyph outlines and shadow. - draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs); ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; } } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 3605842224..d9acbeb828 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -674,7 +674,7 @@ void LineEdit::_notification(int p_what) { int y_ofs = style->get_offset().y + (y_area - text_height) / 2; Color selection_color = get_theme_color(SNAME("selection_color")); - Color font_color = is_editable() ? get_theme_color(SNAME("font_color")) : get_theme_color(SNAME("font_uneditable_color")); + Color font_color = get_theme_color(is_editable() ? SNAME("font_color") : SNAME("font_uneditable_color")); Color font_selected_color = get_theme_color(SNAME("font_selected_color")); Color caret_color = get_theme_color(SNAME("caret_color")); diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index 419d49bccf..925e6f5b97 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -41,12 +41,16 @@ void LinkButton::_shape() { } else { text_buf->set_direction((TextServer::Direction)text_direction); } - TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, text)); - text_buf->add_string(text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text)); + text_buf->add_string(xl_text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); } void LinkButton::set_text(const String &p_text) { + if (text == p_text) { + return; + } text = p_text; + xl_text = atr(text); _shape(); minimum_size_changed(); update(); @@ -141,7 +145,13 @@ Size2 LinkButton::get_minimum_size() const { void LinkButton::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + xl_text = atr(text); + _shape(); + + minimum_size_changed(); + update(); + } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { update(); } break; diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index 7eaa9f88b6..231543c63c 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -47,6 +47,7 @@ public: private: String text; + String xl_text; Ref<TextLine> text_buf; UnderlineMode underline_mode = UNDERLINE_MODE_ALWAYS; diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index d846e395ad..953337ecce 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -56,6 +56,10 @@ protected: static void _bind_methods(); public: + // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes, + // this value should be updated to reflect the new size. + static const int ITEM_PROPERTY_SIZE = 5; + void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1); void add_item(const String &p_label, int p_id = -1); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 5c427e43bc..428076c6da 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -144,6 +144,10 @@ protected: static void _bind_methods(); public: + // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes, + // this value should be updated to reflect the new size. + static const int ITEM_PROPERTY_SIZE = 10; + void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index fbc67d8a24..562bac60c2 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -366,7 +366,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> } if (p_line > 0) { - l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y; + l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); } else { l.offset.y = 0; } @@ -614,7 +614,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> *r_char_offset = l.char_offset + l.char_count; if (p_line > 0) { - l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y; + l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); } else { l.offset.y = 0; } @@ -3946,24 +3946,15 @@ float RichTextLabel::get_percent_visible() const { return percent_visible; } -void RichTextLabel::set_effects(const Vector<Variant> &effects) { - custom_effects.clear(); - for (int i = 0; i < effects.size(); i++) { - Ref<RichTextEffect> effect = Ref<RichTextEffect>(effects[i]); - custom_effects.push_back(effect); - } - +void RichTextLabel::set_effects(Array p_effects) { + custom_effects = p_effects; if ((bbcode != "") && use_bbcode) { parse_bbcode(bbcode); } } -Vector<Variant> RichTextLabel::get_effects() { - Vector<Variant> r; - for (int i = 0; i < custom_effects.size(); i++) { - r.push_back(custom_effects[i]); - } - return r; +Array RichTextLabel::get_effects() { + return custom_effects; } void RichTextLabel::install_effect(const Variant effect) { @@ -4279,12 +4270,13 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) { for (int i = 0; i < custom_effects.size(); i++) { - if (!custom_effects[i].is_valid()) { + Ref<RichTextEffect> effect = custom_effects[i]; + if (!effect.is_valid()) { continue; } - if (custom_effects[i]->get_bbcode() == p_bbcode_identifier) { - return custom_effects[i]; + if (effect->get_bbcode() == p_bbcode_identifier) { + return effect; } } diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index ae04a7e684..f25a8bf193 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -363,7 +363,7 @@ private: ItemMeta *meta_hovering = nullptr; Variant current_meta; - Vector<Ref<RichTextEffect>> custom_effects; + Array custom_effects; void _invalidate_current_line(ItemFrame *p_frame); void _validate_line_caches(ItemFrame *p_frame); @@ -577,8 +577,8 @@ public: void set_percent_visible(float p_percent); float get_percent_visible() const; - void set_effects(const Vector<Variant> &effects); - Vector<Variant> get_effects(); + void set_effects(Array p_effects); + Array get_effects(); void install_effect(const Variant effect); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 08bcb0bdda..4edf373fbf 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -80,12 +80,16 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { double total = orientation == VERTICAL ? get_size().height : get_size().width; if (ofs < decr_size) { + decr_active = true; set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + update(); return; } if (ofs > total - incr_size) { + incr_active = true; set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + update(); return; } @@ -130,6 +134,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { } } else { + incr_active = false; + decr_active = false; drag.active = false; update(); } @@ -215,8 +221,24 @@ void ScrollBar::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { RID ci = get_canvas_item(); - Ref<Texture2D> decr = highlight == HIGHLIGHT_DECR ? get_theme_icon(SNAME("decrement_highlight")) : get_theme_icon(SNAME("decrement")); - Ref<Texture2D> incr = highlight == HIGHLIGHT_INCR ? get_theme_icon(SNAME("increment_highlight")) : get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr, incr; + + if (decr_active) { + decr = get_theme_icon(SNAME("decrement_pressed")); + } else if (highlight == HIGHLIGHT_DECR) { + decr = get_theme_icon(SNAME("decrement_highlight")); + } else { + decr = get_theme_icon(SNAME("decrement")); + } + + if (incr_active) { + incr = get_theme_icon(SNAME("increment_pressed")); + } else if (highlight == HIGHLIGHT_INCR) { + incr = get_theme_icon(SNAME("increment_highlight")); + } else { + incr = get_theme_icon(SNAME("increment")); + } + Ref<StyleBox> bg = has_focus() ? get_theme_stylebox(SNAME("scroll_focus")) : get_theme_stylebox(SNAME("scroll")); Ref<StyleBox> grabber; diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index fbc035397f..574d17ee20 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -51,6 +51,9 @@ class ScrollBar : public Range { HighlightStatus highlight = HIGHLIGHT_NONE; + bool incr_active = false; + bool decr_active = false; + struct Drag { bool active = false; float pos_at_click = 0.0; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 845a2ec6c7..137ce7e96f 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -1212,6 +1212,7 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled); ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled); + ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabContainer::get_tab_idx_at_point); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 7e64c13b37..06dfc31621 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -37,6 +37,7 @@ #include "core/object/script_language.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "core/string/string_builder.h" #include "core/string/translation.h" #include "scene/main/window.h" @@ -78,7 +79,11 @@ void TextEdit::Text::set_font_size(int p_font_size) { } void TextEdit::Text::set_tab_size(int p_tab_size) { + if (tab_size == p_tab_size) { + return; + } tab_size = p_tab_size; + tab_size_dirty = true; } int TextEdit::Text::get_tab_size() const { @@ -118,10 +123,8 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { return text[p_line].data_buf->get_size().x; } -int TextEdit::Text::get_line_height(int p_line, int p_wrap_index) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); - - return text[p_line].data_buf->get_line_size(p_wrap_index).y; +int TextEdit::Text::get_line_height() const { + return line_height; } void TextEdit::Text::set_width(float p_width) { @@ -153,6 +156,36 @@ _FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { return text[p_line].data; } +void TextEdit::Text::_calculate_line_height() { + int height = 0; + for (int i = 0; i < text.size(); i++) { + // Found another line with the same height...nothing to update. + if (text[i].height == line_height) { + height = line_height; + break; + } + height = MAX(height, text[i].height); + } + line_height = height; +} + +void TextEdit::Text::_calculate_max_line_width() { + int width = 0; + for (int i = 0; i < text.size(); i++) { + if (is_hidden(i)) { + continue; + } + + // Found another line with the same width...nothing to update. + if (text[i].width == max_width) { + width = max_width; + break; + } + width = MAX(width, text[i].width); + } + max_width = width; +} + void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Vector<Vector2i> &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); @@ -182,17 +215,54 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); text.write[p_line].data_buf->tab_align(tabs); } + + // Update height. + const int old_height = text.write[p_line].height; + const int wrap_amount = get_line_wrap_amount(p_line); + int height = font->get_height(font_size); + for (int i = 0; i <= wrap_amount; i++) { + height = MAX(height, text[p_line].data_buf->get_line_size(i).y); + } + text.write[p_line].height = height; + + // If this line has shrunk, this may no longer the the tallest line. + if (old_height == line_height && height < line_height) { + _calculate_line_height(); + } else { + line_height = MAX(height, line_height); + } + + // Update width. + const int old_width = text.write[p_line].width; + int width = get_line_width(p_line); + text.write[p_line].width = width; + + // If this line has shrunk, this may no longer the the longest line. + if (old_width == max_width && width < max_width) { + _calculate_max_line_width(); + } else if (!is_hidden(p_line)) { + max_width = MAX(width, max_width); + } } void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { text.write[i].data_buf->set_width(width); - if (tab_size > 0) { - Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); - text.write[i].data_buf->tab_align(tabs); + if (tab_size_dirty) { + if (tab_size > 0) { + Vector<float> tabs; + tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); + text.write[i].data_buf->tab_align(tabs); + } + // Tabs have changes, force width update. + text.write[i].width = get_line_width(i); } } + + if (tab_size_dirty) { + _calculate_max_line_width(); + tab_size_dirty = false; + } } void TextEdit::Text::invalidate_all() { @@ -211,16 +281,8 @@ void TextEdit::Text::clear() { insert(0, "", Vector<Vector2i>()); } -int TextEdit::Text::get_max_width(bool p_exclude_hidden) const { - // Quite some work, but should be fast enough. - - int max = 0; - for (int i = 0; i < text.size(); i++) { - if (!p_exclude_hidden || !is_hidden(i)) { - max = MAX(max, get_line_width(i)); - } - } - return max; +int TextEdit::Text::get_max_width() const { + return max_width; } void TextEdit::Text::set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override) { @@ -243,7 +305,20 @@ void TextEdit::Text::insert(int p_at, const String &p_text, const Vector<Vector2 } void TextEdit::Text::remove(int p_at) { + int height = text[p_at].height; + int width = text[p_at].width; + text.remove(p_at); + + // If this is the tallest line, we need to get the next tallest. + if (height == line_height) { + _calculate_line_height(); + } + + // If this is the longest line, we need to get the next longest. + if (width == max_width) { + _calculate_max_line_width(); + } } void TextEdit::Text::add_gutter(int p_at) { @@ -293,7 +368,7 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { call_deferred(SNAME("_update_scrollbars")); - call_deferred(SNAME("_update_wrap_at")); + call_deferred(SNAME("_update_wrap_at_column")); } } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: @@ -557,13 +632,25 @@ void TextEdit::_notification(int p_what) { } int minimap_draw_amount = minimap_visible_lines + get_line_wrap_count(minimap_line + 1); - // draw the minimap - Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, 0.1) : Color(0, 0, 0, 0.1); + // Draw the minimap. + + // Add visual feedback when dragging or hovering the the visible area rectangle. + float viewport_alpha; + if (dragging_minimap) { + viewport_alpha = 0.25; + } else if (hovering_minimap) { + viewport_alpha = 0.175; + } else { + viewport_alpha = 0.1; + } + + const Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha); if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color); } else { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, minimap_width, viewport_height), viewport_color); } + for (int i = 0; i < minimap_draw_amount; i++) { minimap_line++; @@ -632,8 +719,9 @@ void TextEdit::_notification(int p_what) { int characters = 0; int tabs = 0; for (int j = 0; j < str.length(); j++) { - if (color_map.has(last_wrap_column + j)) { - current_color = color_map[last_wrap_column + j].get("color"); + const Variant *color_data = color_map.getptr(last_wrap_column + j); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); if (!editable) { current_color.a = font_readonly_color.a; } @@ -1011,8 +1099,9 @@ void TextEdit::_notification(int p_what) { char_ofs = 0; } for (int j = 0; j < gl_size; j++) { - if (color_map.has(glyphs[j].start)) { - current_color = color_map[glyphs[j].start].get("color"); + const Variant *color_data = color_map.getptr(glyphs[j].start); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); if (!editable && current_color.a > font_readonly_color.a) { current_color.a = font_readonly_color.a; } @@ -1549,6 +1638,10 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } + if (draw_minimap && !dragging_selection) { + _update_minimap_hover(); + } + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { accept_event(); // Accept event if scroll changed. } @@ -2517,10 +2610,10 @@ void TextEdit::set_text(const String &p_text) { set_caret_column(0); begin_complex_operation(); + deselect(); _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0)); insert_text_at_caret(p_text); end_complex_operation(); - selection.active = false; } set_caret_line(0); @@ -2532,15 +2625,15 @@ void TextEdit::set_text(const String &p_text) { } String TextEdit::get_text() const { - String longthing; - int len = text.size(); - for (int i = 0; i < len; i++) { - longthing += text[i]; - if (i != len - 1) { - longthing += "\n"; + StringBuilder ret_text; + const int text_size = text.size(); + for (int i = 0; i < text_size; i++) { + ret_text += text[i]; + if (i != text_size - 1) { + ret_text += "\n"; } } - return longthing; + return ret_text.as_string(); } int TextEdit::get_line_count() const { @@ -2576,13 +2669,7 @@ int TextEdit::get_line_width(int p_line, int p_wrap_index) const { } int TextEdit::get_line_height() const { - int height = font->get_height(font_size); - for (int i = 0; i < text.size(); i++) { - for (int j = 0; j <= text.get_line_wrap_amount(i); j++) { - height = MAX(height, text.get_line_height(i, j)); - } - } - return height + line_spacing; + return text.get_line_height() + line_spacing; } int TextEdit::get_indent_level(int p_line) const { @@ -2644,6 +2731,8 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) { } void TextEdit::insert_text_at_caret(const String &p_text) { + begin_complex_operation(); + delete_selection(); int new_column, new_line; @@ -2653,6 +2742,8 @@ void TextEdit::insert_text_at_caret(const String &p_text) { set_caret_line(new_line, false); set_caret_column(new_column); update(); + + end_complex_operation(); } void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -2937,13 +3028,20 @@ void TextEdit::menu_option(int p_option) { /* Versioning */ void TextEdit::begin_complex_operation() { _push_current_op(); - next_operation_is_complex = true; + if (complex_operation_count == 0) { + next_operation_is_complex = true; + } + complex_operation_count++; } void TextEdit::end_complex_operation() { _push_current_op(); ERR_FAIL_COND(undo_stack.size() == 0); + complex_operation_count = MAX(complex_operation_count - 1, 0); + if (complex_operation_count > 0) { + return; + } if (undo_stack.back()->get().chain_forward) { undo_stack.back()->get().chain_forward = false; return; @@ -4135,6 +4233,9 @@ TextEdit::GutterType TextEdit::get_gutter_type(int p_gutter) const { void TextEdit::set_gutter_width(int p_gutter, int p_width) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].width == p_width) { + return; + } gutters.write[p_gutter].width = p_width; _update_gutter_width(); } @@ -4150,6 +4251,9 @@ int TextEdit::get_total_gutter_width() const { void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].draw == p_draw) { + return; + } gutters.write[p_gutter].draw = p_draw; _update_gutter_width(); } @@ -5442,7 +5546,7 @@ void TextEdit::_update_scrollbars() { } int visible_width = size.width - style_normal->get_minimum_size().width; - int total_width = text.get_max_width(true) + vmin.x + gutters_width + gutter_padding; + int total_width = text.get_max_width() + vmin.x + gutters_width + gutter_padding; if (draw_minimap) { total_width += minimap_width; @@ -5644,6 +5748,33 @@ void TextEdit::_scroll_lines_down() { } // Minimap + +void TextEdit::_update_minimap_hover() { + const Point2 mp = get_local_mouse_pos(); + const int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT); + + const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end; + if (!hovering_sidebar) { + if (hovering_minimap) { + // Only redraw if the hovering status changed. + hovering_minimap = false; + update(); + } + + // Return early to avoid running the operations below when not needed. + return; + } + + const int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y)); + + const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); + if (new_hovering_minimap != hovering_minimap) { + // Only redraw if the hovering status changed. + hovering_minimap = new_hovering_minimap; + update(); + } +} + void TextEdit::_update_minimap_click() { Point2 mp = get_local_mouse_pos(); @@ -5867,8 +5998,6 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i text.set_hidden(p_line, false); } - text.invalidate_cache(p_line); - r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); @@ -5925,8 +6054,6 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li } text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text)); - text.invalidate_cache(p_from_line); - if (!text_changed_dirty && !setting_text) { if (is_inside_tree()) { MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); @@ -5943,7 +6070,6 @@ TextEdit::TextEdit() { set_default_cursor_shape(CURSOR_IBEAM); text.set_tab_size(text.get_tab_size()); - text.clear(); h_scroll = memnew(HScrollBar); v_scroll = memnew(VScrollBar); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index ced03e19d0..b1226f2aff 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -144,6 +144,8 @@ private: Color background_color = Color(0, 0, 0, 0); bool hidden = false; + int height = 0; + int width = 0; Line() { data_buf.instantiate(); @@ -152,6 +154,7 @@ private: private: bool is_dirty = false; + bool tab_size_dirty = false; mutable Vector<Line> text; Ref<Font> font; @@ -162,11 +165,16 @@ private: TextServer::Direction direction = TextServer::DIRECTION_AUTO; bool draw_control_chars = false; + int line_height = -1; + int max_width = -1; int width = -1; int tab_size = 4; int gutter_count = 0; + void _calculate_line_height(); + void _calculate_max_line_width(); + public: void set_tab_size(int p_tab_size); int get_tab_size() const; @@ -176,9 +184,9 @@ private: void set_direction_and_language(TextServer::Direction p_direction, const String &p_language); void set_draw_control_chars(bool p_draw_control_chars); - int get_line_height(int p_line, int p_wrap_index) const; + int get_line_height() const; int get_line_width(int p_line, int p_wrap_index = -1) const; - int get_max_width(bool p_exclude_hidden = false) const; + int get_max_width() const; void set_width(float p_width); int get_line_wrap_amount(int p_line) const; @@ -187,7 +195,14 @@ private: const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override); - void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; } + void set_hidden(int p_line, bool p_hidden) { + text.write[p_line].hidden = p_hidden; + if (!p_hidden && text[p_line].width > max_width) { + max_width = text[p_line].width; + } else if (p_hidden && text[p_line].width == max_width) { + _calculate_max_line_width(); + } + } bool is_hidden(int p_line) const { return text[p_line].hidden; } void insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override); void remove(int p_at); @@ -289,6 +304,7 @@ private: bool undo_enabled = true; int undo_stack_max_size = 50; + int complex_operation_count = 0; bool next_operation_is_complex = false; TextOperation current_op; @@ -446,12 +462,14 @@ private: // minimap scroll bool minimap_clicked = false; + bool hovering_minimap = false; bool dragging_minimap = false; bool can_drag_minimap = false; double minimap_scroll_ratio = 0.0; double minimap_scroll_click_pos = 0.0; + void _update_minimap_hover(); void _update_minimap_click(); void _update_minimap_drag(); @@ -528,7 +546,6 @@ private: protected: void _notification(int p_what); - virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; static void _bind_methods(); @@ -577,6 +594,7 @@ protected: public: /* General overrides. */ + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; virtual Size2 get_minimum_size() const override; virtual bool is_text_field() const override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index cb990892ed..f62c09925d 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -156,6 +156,7 @@ void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) { c.dirty = true; c.icon_max_w = 0; _changed_notify(p_column); + cached_minimum_size_dirty = true; } TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { @@ -169,6 +170,7 @@ void TreeItem::set_checked(int p_column, bool p_checked) { cells.write[p_column].checked = p_checked; cells.write[p_column].indeterminate = false; _changed_notify(p_column); + cached_minimum_size_dirty = true; } void TreeItem::set_indeterminate(int p_column, bool p_indeterminate) { @@ -180,6 +182,7 @@ void TreeItem::set_indeterminate(int p_column, bool p_indeterminate) { cells.write[p_column].indeterminate = p_indeterminate; cells.write[p_column].checked = false; _changed_notify(p_column); + cached_minimum_size_dirty = true; } bool TreeItem::is_checked(int p_column) const { @@ -212,6 +215,7 @@ void TreeItem::set_text(int p_column, String p_text) { cells.write[p_column].step = 0; } _changed_notify(p_column); + cached_minimum_size_dirty = true; } String TreeItem::get_text(int p_column) const { @@ -227,6 +231,7 @@ void TreeItem::set_text_direction(int p_column, Control::TextDirection p_text_di cells.write[p_column].dirty = true; _changed_notify(p_column); } + cached_minimum_size_dirty = true; } Control::TextDirection TreeItem::get_text_direction(int p_column) const { @@ -239,6 +244,7 @@ void TreeItem::clear_opentype_features(int p_column) { cells.write[p_column].opentype_features.clear(); cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_value) { @@ -248,6 +254,7 @@ void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_va cells.write[p_column].opentype_features[tag] = p_value; cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } } @@ -266,6 +273,7 @@ void TreeItem::set_structured_text_bidi_override(int p_column, Control::Structur cells.write[p_column].st_parser = p_parser; cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } } @@ -279,6 +287,7 @@ void TreeItem::set_structured_text_bidi_override_options(int p_column, Array p_a cells.write[p_column].st_args = p_args; cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } Array TreeItem::get_structured_text_bidi_override_options(int p_column) const { @@ -292,6 +301,7 @@ void TreeItem::set_language(int p_column, const String &p_language) { cells.write[p_column].language = p_language; cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } } @@ -305,6 +315,7 @@ void TreeItem::set_suffix(int p_column, String p_suffix) { cells.write[p_column].suffix = p_suffix; _changed_notify(p_column); + cached_minimum_size_dirty = true; } String TreeItem::get_suffix(int p_column) const { @@ -316,6 +327,7 @@ void TreeItem::set_icon(int p_column, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].icon = p_icon; _changed_notify(p_column); + cached_minimum_size_dirty = true; } Ref<Texture2D> TreeItem::get_icon(int p_column) const { @@ -327,6 +339,7 @@ void TreeItem::set_icon_region(int p_column, const Rect2 &p_icon_region) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].icon_region = p_icon_region; _changed_notify(p_column); + cached_minimum_size_dirty = true; } Rect2 TreeItem::get_icon_region(int p_column) const { @@ -349,6 +362,7 @@ void TreeItem::set_icon_max_width(int p_column, int p_max) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].icon_max_w = p_max; _changed_notify(p_column); + cached_minimum_size_dirty = true; } int TreeItem::get_icon_max_width(int p_column) const { @@ -461,6 +475,7 @@ void TreeItem::uncollapse_tree() { void TreeItem::set_custom_minimum_height(int p_height) { custom_min_height = p_height; _changed_notify(); + cached_minimum_size_dirty = true; } int TreeItem::get_custom_minimum_height() const { @@ -785,6 +800,7 @@ void TreeItem::add_button(int p_column, const Ref<Texture2D> &p_button, int p_id button.tooltip = p_tooltip; cells.write[p_column].buttons.push_back(button); _changed_notify(p_column); + cached_minimum_size_dirty = true; } int TreeItem::get_button_count(int p_column) const { @@ -828,6 +844,7 @@ void TreeItem::set_button(int p_column, int p_idx, const Ref<Texture2D> &p_butto ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); cells.write[p_column].buttons.write[p_idx].texture = p_button; _changed_notify(p_column); + cached_minimum_size_dirty = true; } void TreeItem::set_button_color(int p_column, int p_idx, const Color &p_color) { @@ -843,6 +860,7 @@ void TreeItem::set_button_disabled(int p_column, int p_idx, bool p_disabled) { cells.write[p_column].buttons.write[p_idx].disabled = p_disabled; _changed_notify(p_column); + cached_minimum_size_dirty = true; } bool TreeItem::is_button_disabled(int p_column, int p_idx) const { @@ -856,6 +874,7 @@ void TreeItem::set_editable(int p_column, bool p_editable) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].editable = p_editable; _changed_notify(p_column); + cached_minimum_size_dirty = true; } bool TreeItem::is_editable(int p_column) { @@ -888,6 +907,7 @@ void TreeItem::clear_custom_color(int p_column) { void TreeItem::set_custom_font(int p_column, const Ref<Font> &p_font) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].custom_font = p_font; + cached_minimum_size_dirty = true; } Ref<Font> TreeItem::get_custom_font(int p_column) const { @@ -898,6 +918,7 @@ Ref<Font> TreeItem::get_custom_font(int p_column) const { void TreeItem::set_custom_font_size(int p_column, int p_font_size) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].custom_font_size = p_font_size; + cached_minimum_size_dirty = true; } int TreeItem::get_custom_font_size(int p_column) const { @@ -941,6 +962,7 @@ Color TreeItem::get_custom_bg_color(int p_column) const { void TreeItem::set_custom_as_button(int p_column, bool p_button) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].custom_button = p_button; + cached_minimum_size_dirty = true; } bool TreeItem::is_custom_set_as_button(int p_column) const { @@ -952,6 +974,7 @@ void TreeItem::set_text_align(int p_column, TextAlign p_align) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].text_align = p_align; _changed_notify(p_column); + cached_minimum_size_dirty = true; } TreeItem::TextAlign TreeItem::get_text_align(int p_column) const { @@ -963,6 +986,7 @@ void TreeItem::set_expand_right(int p_column, bool p_enable) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].expand_right = p_enable; _changed_notify(p_column); + cached_minimum_size_dirty = true; } bool TreeItem::get_expand_right(int p_column) const { @@ -973,6 +997,7 @@ bool TreeItem::get_expand_right(int p_column) const { void TreeItem::set_disable_folding(bool p_disable) { disable_folding = p_disable; _changed_notify(0); + cached_minimum_size_dirty = true; } bool TreeItem::is_folding_disabled() const { @@ -984,49 +1009,54 @@ Size2 TreeItem::get_minimum_size(int p_column) { Tree *tree = get_tree(); ERR_FAIL_COND_V(!tree, Size2()); - Size2 size; + if (cached_minimum_size_dirty) { + Size2 size; - // Default offset? - //size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin; + // Default offset? + //size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin; - // Text. - const TreeItem::Cell &cell = cells[p_column]; - if (!cell.text.is_empty()) { - if (cell.dirty) { - tree->update_item_cell(this, p_column); + // Text. + const TreeItem::Cell &cell = cells[p_column]; + if (!cell.text.is_empty()) { + if (cell.dirty) { + tree->update_item_cell(this, p_column); + } + Size2 text_size = cell.text_buf->get_size(); + size.width += text_size.width; + size.height = MAX(size.height, text_size.height); } - Size2 text_size = cell.text_buf->get_size(); - size.width += text_size.width; - size.height = MAX(size.height, text_size.height); - } - // Icon. - if (cell.mode == CELL_MODE_CHECK) { - size.width += tree->cache.checked->get_width() + tree->cache.hseparation; - } - if (cell.icon.is_valid()) { - Size2i icon_size = cell.get_icon_size(); - if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) { - icon_size.width = cell.icon_max_w; + // Icon. + if (cell.mode == CELL_MODE_CHECK) { + size.width += tree->cache.checked->get_width() + tree->cache.hseparation; + } + if (cell.icon.is_valid()) { + Size2i icon_size = cell.get_icon_size(); + if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) { + icon_size.width = cell.icon_max_w; + } + size.width += icon_size.width + tree->cache.hseparation; + size.height = MAX(size.height, icon_size.height); } - size.width += icon_size.width + tree->cache.hseparation; - size.height = MAX(size.height, icon_size.height); - } - // Buttons. - for (int i = 0; i < cell.buttons.size(); i++) { - Ref<Texture2D> texture = cell.buttons[i].texture; - if (texture.is_valid()) { - Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size(); - size.width += button_size.width; - size.height = MAX(size.height, button_size.height); + // Buttons. + for (int i = 0; i < cell.buttons.size(); i++) { + Ref<Texture2D> texture = cell.buttons[i].texture; + if (texture.is_valid()) { + Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size(); + size.width += button_size.width; + size.height = MAX(size.height, button_size.height); + } } - } - if (cell.buttons.size() >= 2) { - size.width += (cell.buttons.size() - 1) * tree->cache.button_margin; + if (cell.buttons.size() >= 2) { + size.width += (cell.buttons.size() - 1) * tree->cache.button_margin; + } + + cached_minimum_size = size; + cached_minimum_size_dirty = false; } - return size; + return cached_minimum_size; } Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { @@ -1307,6 +1337,10 @@ void Tree::update_cache() { cache.title_button_color = get_theme_color(SNAME("title_button_color")); v_scroll->set_custom_step(cache.font->get_height(cache.font_size)); + + for (TreeItem *item = get_root(); item; item = item->get_next()) { + item->cached_minimum_size_dirty = true; + } } int Tree::compute_item_height(TreeItem *p_item) const { @@ -2339,13 +2373,22 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int cache.click_type = Cache::CLICK_NONE; return -1; } + + // Make sure the click is correct. + Point2 click_pos = get_global_mouse_position() - get_global_position(); + if (!get_item_at_position(click_pos)) { + pressed_button = -1; + cache.click_type = Cache::CLICK_NONE; + return -1; + } + pressed_button = j; cache.click_type = Cache::CLICK_BUTTON; cache.click_index = j; cache.click_id = c.buttons[j].id; cache.click_item = p_item; cache.click_column = col; - cache.click_pos = get_global_mouse_position() - get_global_position(); + cache.click_pos = click_pos; update(); //emit_signal(SNAME("button_pressed")); return -1; diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 8b7ddc3faf..85fed941dc 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -130,6 +130,9 @@ private: bool disable_folding = false; int custom_min_height = 0; + Size2i cached_minimum_size; + bool cached_minimum_size_dirty = true; + TreeItem *parent = nullptr; // parent item TreeItem *prev = nullptr; // previous in list TreeItem *next = nullptr; // next in list |