diff options
Diffstat (limited to 'scene/gui')
29 files changed, 1306 insertions, 726 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 66155958cf..c1ae0479f5 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -391,7 +391,7 @@ bool BaseButton::_is_focus_owner_in_shorcut_context() const { Control *vp_focus = get_focus_owner(); // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. - return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_a_parent_of(vp_focus)); + return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); } void BaseButton::_bind_methods() { diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index c0df5271b4..bcc273114b 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -49,9 +49,14 @@ Size2 Button::get_minimum_size() const { if (!_icon.is_null()) { minsize.height = MAX(minsize.height, _icon->get_height()); - minsize.width += _icon->get_width(); - if (xl_text != "") { - minsize.width += get_theme_constant("hseparation"); + + if (icon_align != ALIGN_CENTER) { + minsize.width += _icon->get_width(); + if (xl_text != "") { + minsize.width += get_theme_constant("hseparation"); + } + } else { + minsize.width = MAX(minsize.width, _icon->get_width()); } } } @@ -202,6 +207,21 @@ void Button::_notification(int p_what) { } Rect2 icon_region = Rect2(); + TextAlign icon_align_rtl_checked = icon_align; + TextAlign align_rtl_checked = align; + // Swap icon and text alignment sides if right-to-left layout is set. + if (rtl) { + if (icon_align == ALIGN_RIGHT) { + icon_align_rtl_checked = ALIGN_LEFT; + } else if (icon_align == ALIGN_LEFT) { + icon_align_rtl_checked = ALIGN_RIGHT; + } + if (align == ALIGN_RIGHT) { + align_rtl_checked = ALIGN_LEFT; + } else if (align == ALIGN_LEFT) { + align_rtl_checked = ALIGN_RIGHT; + } + } if (!_icon.is_null()) { int valign = size.height - style->get_minimum_size().y; if (is_disabled()) { @@ -209,20 +229,27 @@ void Button::_notification(int p_what) { } float icon_ofs_region = 0.0; - if (rtl) { - if (_internal_margin[SIDE_RIGHT] > 0) { - icon_ofs_region = _internal_margin[SIDE_RIGHT] + get_theme_constant("hseparation"); - } - } else { + Point2 style_offset; + Size2 icon_size = _icon->get_size(); + if (icon_align_rtl_checked == ALIGN_LEFT) { + style_offset.x = style->get_margin(SIDE_LEFT); if (_internal_margin[SIDE_LEFT] > 0) { icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant("hseparation"); } + } else if (icon_align_rtl_checked == ALIGN_CENTER) { + style_offset.x = 0.0; + } else if (icon_align_rtl_checked == ALIGN_RIGHT) { + style_offset.x = -style->get_margin(SIDE_RIGHT); + if (_internal_margin[SIDE_RIGHT] > 0) { + icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant("hseparation"); + } } + style_offset.y = style->get_margin(SIDE_TOP); if (expand_icon) { Size2 _size = get_size() - style->get_offset() * 2; _size.width -= get_theme_constant("hseparation") + icon_ofs_region; - if (!clip_text) { + if (!clip_text && icon_align_rtl_checked != ALIGN_CENTER) { _size.width -= text_buf->get_size().width; } float icon_width = _icon->get_width() * _size.height / _icon->get_height(); @@ -233,21 +260,26 @@ void Button::_notification(int p_what) { icon_height = _icon->get_height() * icon_width / _icon->get_width(); } - if (rtl) { - icon_region = Rect2(Point2(size.width - (icon_ofs_region + icon_width + style->get_margin(SIDE_RIGHT)), style->get_margin(SIDE_TOP) + (_size.height - icon_height) / 2), Size2(icon_width, icon_height)); - } else { - icon_region = Rect2(style->get_offset() + Point2(icon_ofs_region, (_size.height - icon_height) / 2), Size2(icon_width, icon_height)); - } + icon_size = Size2(icon_width, icon_height); + } + + if (icon_align_rtl_checked == ALIGN_LEFT) { + icon_region = Rect2(style_offset + Point2(icon_ofs_region, Math::floor((valign - icon_size.y) * 0.5)), icon_size); + } else if (icon_align_rtl_checked == ALIGN_CENTER) { + icon_region = Rect2(style_offset + Point2(icon_ofs_region + Math::floor((size.x - icon_size.x) * 0.5), Math::floor((valign - icon_size.y) * 0.5)), icon_size); } else { - if (rtl) { - icon_region = Rect2(Point2(size.width - (icon_ofs_region + _icon->get_size().width + style->get_margin(SIDE_RIGHT)), style->get_margin(SIDE_TOP) + Math::floor((valign - _icon->get_height()) / 2.0)), _icon->get_size()); - } else { - icon_region = Rect2(style->get_offset() + Point2(icon_ofs_region, Math::floor((valign - _icon->get_height()) / 2.0)), _icon->get_size()); - } + icon_region = Rect2(style_offset + Point2(icon_ofs_region + size.x - icon_size.x, Math::floor((valign - icon_size.y) * 0.5)), icon_size); + } + + if (icon_region.size.width > 0) { + draw_texture_rect_region(_icon, icon_region, Rect2(Point2(), _icon->get_size()), color_icon); } } Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant("hseparation"), 0) : Point2(); + if (align_rtl_checked == ALIGN_CENTER && icon_align_rtl_checked == ALIGN_CENTER) { + icon_ofs.x = 0.0; + } int text_clip = size.width - style->get_minimum_size().width - icon_ofs.width; text_buf->set_width(clip_text ? text_clip : -1); @@ -262,20 +294,15 @@ void Button::_notification(int p_what) { Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - text_buf->get_size() - Point2(_internal_margin[SIDE_RIGHT] - _internal_margin[SIDE_LEFT], 0)) / 2.0; - switch (align) { + switch (align_rtl_checked) { case ALIGN_LEFT: { - if (rtl) { - if (_internal_margin[SIDE_RIGHT] > 0) { - text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant("hseparation"); - } else { - text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width; - } + if (icon_align_rtl_checked != ALIGN_LEFT) { + icon_ofs.x = 0.0; + } + if (_internal_margin[SIDE_LEFT] > 0) { + text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant("hseparation"); } else { - if (_internal_margin[SIDE_LEFT] > 0) { - text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant("hseparation"); - } else { - text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x; - } + text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x; } text_ofs.y += style->get_offset().y; } break; @@ -283,31 +310,24 @@ void Button::_notification(int p_what) { if (text_ofs.x < 0) { text_ofs.x = 0; } - text_ofs += icon_ofs; + if (icon_align_rtl_checked == ALIGN_LEFT) { + text_ofs += icon_ofs; + } text_ofs += style->get_offset(); } break; case ALIGN_RIGHT: { - if (rtl) { - if (_internal_margin[SIDE_LEFT] > 0) { - text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant("hseparation"); - } else { - text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x; - } + if (_internal_margin[SIDE_RIGHT] > 0) { + text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant("hseparation"); } else { - if (_internal_margin[SIDE_RIGHT] > 0) { - text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant("hseparation"); - } else { - text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width; - } + text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width; } text_ofs.y += style->get_offset().y; + if (icon_align_rtl_checked == ALIGN_RIGHT) { + text_ofs.x -= icon_ofs.x; + } } break; } - if (rtl) { - text_ofs.x -= icon_ofs.x; - } - Color font_outline_color = get_theme_color("font_outline_color"); int outline_size = get_theme_constant("outline_size"); if (outline_size > 0 && font_outline_color.a > 0) { @@ -315,10 +335,6 @@ void Button::_notification(int p_what) { } text_buf->draw(ci, text_ofs, color); - - if (!_icon.is_null() && icon_region.size.width > 0) { - draw_texture_rect_region(_icon, icon_region, Rect2(Point2(), _icon->get_size()), color_icon); - } } break; } } @@ -457,6 +473,16 @@ Button::TextAlign Button::get_text_align() const { return align; } +void Button::set_icon_align(TextAlign p_align) { + icon_align = p_align; + minimum_size_changed(); + update(); +} + +Button::TextAlign Button::get_icon_align() const { + return icon_align; +} + bool Button::_set(const StringName &p_name, const Variant &p_value) { String str = p_name; if (str.begins_with("opentype_features/")) { @@ -519,14 +545,16 @@ void Button::_bind_methods() { ClassDB::bind_method(D_METHOD("get_language"), &Button::get_language); ClassDB::bind_method(D_METHOD("set_button_icon", "texture"), &Button::set_icon); ClassDB::bind_method(D_METHOD("get_button_icon"), &Button::get_icon); - ClassDB::bind_method(D_METHOD("set_expand_icon"), &Button::set_expand_icon); - ClassDB::bind_method(D_METHOD("is_expand_icon"), &Button::is_expand_icon); ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &Button::set_flat); + ClassDB::bind_method(D_METHOD("is_flat"), &Button::is_flat); ClassDB::bind_method(D_METHOD("set_clip_text", "enabled"), &Button::set_clip_text); ClassDB::bind_method(D_METHOD("get_clip_text"), &Button::get_clip_text); ClassDB::bind_method(D_METHOD("set_text_align", "align"), &Button::set_text_align); ClassDB::bind_method(D_METHOD("get_text_align"), &Button::get_text_align); - ClassDB::bind_method(D_METHOD("is_flat"), &Button::is_flat); + ClassDB::bind_method(D_METHOD("set_icon_align", "icon_align"), &Button::set_icon_align); + ClassDB::bind_method(D_METHOD("get_icon_align"), &Button::get_icon_align); + ClassDB::bind_method(D_METHOD("set_expand_icon"), &Button::set_expand_icon); + ClassDB::bind_method(D_METHOD("is_expand_icon"), &Button::is_expand_icon); BIND_ENUM_CONSTANT(ALIGN_LEFT); BIND_ENUM_CONSTANT(ALIGN_CENTER); @@ -539,11 +567,12 @@ void Button::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text"); ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_text_align", "get_text_align"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_icon_align", "get_icon_align"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_icon"), "set_expand_icon", "is_expand_icon"); } Button::Button(const String &p_text) { - text_buf.instance(); + text_buf.instantiate(); text_buf->set_flags(TextServer::BREAK_MANDATORY); set_mouse_filter(MOUSE_FILTER_STOP); diff --git a/scene/gui/button.h b/scene/gui/button.h index d968f63f51..253d079680 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -58,6 +58,7 @@ private: bool expand_icon = false; bool clip_text = false; TextAlign align = ALIGN_CENTER; + TextAlign icon_align = ALIGN_LEFT; float _internal_margin[4] = {}; void _shape(); @@ -102,6 +103,9 @@ public: void set_text_align(TextAlign p_align); TextAlign get_text_align() const; + void set_icon_align(TextAlign p_align); + TextAlign get_icon_align() const; + Button(const String &p_text = String()); ~Button(); }; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 5b720945b8..ba1534ed5c 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -122,7 +122,7 @@ void CodeEdit::_notification(int p_what) { ERR_CONTINUE(l < 0 || l >= code_completion_options_count); Ref<TextLine> tl; - tl.instance(); + tl.instantiate(); tl->add_string(code_completion_options[l].display, cache.font, cache.font_size); int yofs = (row_height - tl->get_size().y) / 2; @@ -248,6 +248,8 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } update(); } break; + default: + break; } return; } @@ -358,7 +360,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } if (k->is_action("ui_text_backspace", true)) { - backspace_at_cursor(); + backspace(); _filter_code_completion_candidates(); accept_event(); return; @@ -385,14 +387,34 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { set_code_hint(""); } - /* Override input to unfold lines where needed. */ - if (!is_readonly()) { - if (k->is_action("ui_text_newline_above", true) || k->is_action("ui_text_newline_blank", true) || k->is_action("ui_text_newline", true)) { - unfold_line(cursor_get_line()); - } - if (cursor_get_line() > 0 && k->is_action("ui_text_backspace", true)) { - unfold_line(cursor_get_line() - 1); - } + /* Indentation */ + if (k->is_action("ui_text_indent", true)) { + do_indent(); + accept_event(); + return; + } + + if (k->is_action("ui_text_dedent", true)) { + do_unindent(); + accept_event(); + return; + } + + // Override new line actions, for auto indent + if (k->is_action("ui_text_newline_above", true)) { + _new_line(false, true); + accept_event(); + return; + } + if (k->is_action("ui_text_newline_blank", true)) { + _new_line(false); + accept_event(); + return; + } + if (k->is_action("ui_text_newline", true)) { + _new_line(); + accept_event(); + return; } /* Remove shift otherwise actions will not match. */ @@ -437,6 +459,443 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { return TextEdit::get_cursor_shape(p_pos); } +/* Indent management */ +void CodeEdit::set_indent_size(const int p_size) { + ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0."); + if (indent_size == p_size) { + return; + } + + indent_size = p_size; + if (indent_using_spaces) { + indent_text = String(" ").repeat(p_size); + } else { + indent_text = "\t"; + } + set_tab_size(p_size); +} + +int CodeEdit::get_indent_size() const { + return indent_size; +} + +void CodeEdit::set_indent_using_spaces(const bool p_use_spaces) { + indent_using_spaces = p_use_spaces; + if (indent_using_spaces) { + indent_text = String(" ").repeat(indent_size); + } else { + indent_text = "\t"; + } +} + +bool CodeEdit::is_indent_using_spaces() const { + return indent_using_spaces; +} + +void CodeEdit::set_auto_indent_enabled(bool p_enabled) { + auto_indent = p_enabled; +} + +bool CodeEdit::is_auto_indent_enabled() const { + return auto_indent; +} + +void CodeEdit::set_auto_indent_prefixes(const TypedArray<String> &p_prefixes) { + auto_indent_prefixes.clear(); + for (int i = 0; i < p_prefixes.size(); i++) { + const String prefix = p_prefixes[i]; + auto_indent_prefixes.insert(prefix[0]); + } +} + +TypedArray<String> CodeEdit::get_auto_indent_prefixes() const { + TypedArray<String> prefixes; + for (const Set<char32_t>::Element *E = auto_indent_prefixes.front(); E; E = E->next()) { + prefixes.push_back(String::chr(E->get())); + } + return prefixes; +} + +void CodeEdit::do_indent() { + if (is_readonly()) { + return; + } + + if (is_selection_active()) { + indent_lines(); + return; + } + + if (!indent_using_spaces) { + _insert_text_at_cursor("\t"); + return; + } + + int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor_get_column()); + if (spaces_to_add > 0) { + _insert_text_at_cursor(String(" ").repeat(spaces_to_add)); + } +} + +void CodeEdit::indent_lines() { + if (is_readonly()) { + return; + } + + begin_complex_operation(); + + /* This value informs us by how much we changed selection position by indenting right. */ + /* Default is 1 for tab indentation. */ + int selection_offset = 1; + + int start_line = cursor_get_line(); + int end_line = start_line; + if (is_selection_active()) { + start_line = get_selection_from_line(); + end_line = get_selection_to_line(); + + /* Ignore the last line if the selection is not past the first column. */ + if (get_selection_to_column() == 0) { + selection_offset = 0; + end_line--; + } + } + + for (int i = start_line; i <= end_line; i++) { + const String line_text = get_line(i); + if (line_text.size() == 0 && is_selection_active()) { + continue; + } + + if (!indent_using_spaces) { + set_line(i, '\t' + line_text); + continue; + } + + /* We don't really care where selection is - we just need to know indentation level at the beginning of the line. */ + /* Since we will add this many spaces, we want to move the whole selection and caret by this much. */ + int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); + set_line(i, String(" ").repeat(spaces_to_add) + line_text); + selection_offset = spaces_to_add; + } + + /* Fix selection and caret being off after shifting selection right.*/ + if (is_selection_active()) { + select(start_line, get_selection_from_column() + selection_offset, get_selection_to_line(), get_selection_to_column() + selection_offset); + } + cursor_set_column(cursor_get_column() + selection_offset, false); + + end_complex_operation(); +} + +void CodeEdit::do_unindent() { + if (is_readonly()) { + return; + } + + int cc = cursor_get_column(); + + if (is_selection_active() || cc <= 0) { + unindent_lines(); + return; + } + + int cl = cursor_get_line(); + const String &line = get_line(cl); + + if (line[cc - 1] == '\t') { + _remove_text(cl, cc - 1, cl, cc); + cursor_set_column(MAX(0, cc - 1)); + return; + } + + if (line[cc - 1] != ' ') { + return; + } + + int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc); + if (spaces_to_remove > 0) { + for (int i = 1; i <= spaces_to_remove; i++) { + if (line[cc - i] != ' ') { + spaces_to_remove = i - 1; + break; + } + } + _remove_text(cl, cc - spaces_to_remove, cl, cc); + cursor_set_column(MAX(0, cc - spaces_to_remove)); + } +} + +void CodeEdit::unindent_lines() { + if (is_readonly()) { + return; + } + + begin_complex_operation(); + + /* Moving caret and selection after unindenting can get tricky because */ + /* changing content of line can move caret and selection on its own (if new line ends before previous position of either), */ + /* therefore we just remember initial values and at the end of the operation offset them by number of removed characters. */ + int removed_characters = 0; + int initial_selection_end_column = 0; + int initial_cursor_column = cursor_get_column(); + + int start_line = cursor_get_line(); + int end_line = start_line; + if (is_selection_active()) { + start_line = get_selection_from_line(); + end_line = get_selection_to_line(); + + /* Ignore the last line if the selection is not past the first column. */ + initial_selection_end_column = get_selection_to_column(); + if (initial_selection_end_column == 0) { + end_line--; + } + } + + bool first_line_edited = false; + bool last_line_edited = false; + + for (int i = start_line; i <= end_line; i++) { + String line_text = get_line(i); + + if (line_text.begins_with("\t")) { + line_text = line_text.substr(1, line_text.length()); + + set_line(i, line_text); + removed_characters = 1; + + first_line_edited = (i == start_line) ? true : first_line_edited; + last_line_edited = (i == end_line) ? true : last_line_edited; + continue; + } + + if (line_text.begins_with(" ")) { + /* When unindenting we aim to remove spaces before line that has selection no matter what is selected, */ + /* Here we remove only enough spaces to align text to nearest full multiple of indentation_size. */ + /* In case where selection begins at the start of indentation_size multiple we remove whole indentation level. */ + int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i)); + line_text = line_text.substr(spaces_to_remove, line_text.length()); + + set_line(i, line_text); + removed_characters = spaces_to_remove; + + first_line_edited = (i == start_line) ? true : first_line_edited; + last_line_edited = (i == end_line) ? true : last_line_edited; + } + } + + if (is_selection_active()) { + /* Fix selection being off by one on the first line. */ + if (first_line_edited) { + select(get_selection_from_line(), get_selection_from_column() - removed_characters, get_selection_to_line(), initial_selection_end_column); + } + + /* Fix selection being off by one on the last line. */ + if (last_line_edited) { + select(get_selection_from_line(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters); + } + } + cursor_set_column(initial_cursor_column - removed_characters, false); + + end_complex_operation(); +} + +int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const { + int spaces_till_indent = p_column % indent_size; + if (spaces_till_indent == 0) { + spaces_till_indent = indent_size; + } + return spaces_till_indent; +} + +int CodeEdit::_calculate_spaces_till_next_right_indent(int p_column) const { + return indent_size - p_column % indent_size; +} + +/* TODO: remove once brace completion is refactored. */ +static char32_t _get_right_pair_symbol(char32_t c) { + if (c == '"') { + return '"'; + } + if (c == '\'') { + return '\''; + } + if (c == '(') { + return ')'; + } + if (c == '[') { + return ']'; + } + if (c == '{') { + return '}'; + } + return 0; +} + +static bool _is_pair_left_symbol(char32_t c) { + return c == '"' || + c == '\'' || + c == '(' || + c == '[' || + c == '{'; +} + +void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { + if (is_readonly()) { + return; + } + + const int cc = cursor_get_column(); + const int cl = cursor_get_line(); + const String line = get_line(cl); + + String ins = "\n"; + + /* Append current indentation. */ + int space_count = 0; + int line_col = 0; + for (; line_col < cc; line_col++) { + if (line[line_col] == '\t') { + ins += indent_text; + space_count = 0; + continue; + } + + if (line[line_col] == ' ') { + space_count++; + + if (space_count == indent_size) { + ins += indent_text; + space_count = 0; + } + continue; + } + break; + } + + if (is_line_folded(cl)) { + unfold_line(cl); + } + + /* Indent once again if the previous line needs it, ie ':'. */ + /* Then add an addition new line for any closing pairs aka '()'. */ + /* Skip this in comments or if we are going above. */ + bool brace_indent = false; + if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) { + bool should_indent = false; + char32_t indent_char = ' '; + + for (; line_col < cc; line_col++) { + char32_t c = line[line_col]; + if (auto_indent_prefixes.has(c)) { + should_indent = true; + indent_char = c; + continue; + } + + /* Make sure this is the last char, trailing whitespace or comments are okay. */ + if (should_indent && (!_is_whitespace(c) && is_in_comment(cl, cc) == -1)) { + should_indent = false; + } + } + + if (should_indent) { + ins += indent_text; + + /* TODO: Change when brace completion is refactored. */ + char32_t closing_char = _get_right_pair_symbol(indent_char); + if (closing_char != 0 && closing_char == line[cc]) { + /* 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); + } else { + brace_indent = false; + ins = "\n" + ins.substr(1, ins.length() - 2); + } + } + } + } + + begin_complex_operation(); + + bool first_line = false; + if (!p_split_current_line) { + if (p_above) { + if (cl > 0) { + cursor_set_line(cl - 1, false); + cursor_set_column(get_line(cursor_get_line()).length()); + } else { + cursor_set_column(0); + first_line = true; + } + } else { + cursor_set_column(line.length()); + } + } + + insert_text_at_cursor(ins); + + if (first_line) { + cursor_set_line(0); + } else if (brace_indent) { + cursor_set_line(cursor_get_line() - 1, false); + cursor_set_column(get_line(cursor_get_line()).length()); + } + + end_complex_operation(); +} + +void CodeEdit::backspace() { + if (is_readonly()) { + return; + } + + int cc = cursor_get_column(); + int cl = cursor_get_line(); + + if (cc == 0 && cl == 0) { + return; + } + + if (is_selection_active()) { + delete_selection(); + return; + } + + if (cl > 0 && is_line_hidden(cl - 1)) { + unfold_line(cursor_get_line() - 1); + } + + int prev_line = cc ? cl : cl - 1; + int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); + + merge_gutters(cl, prev_line); + + /* TODO: Change when brace completion is refactored. */ + if (auto_brace_completion_enabled && cc > 0 && _is_pair_left_symbol(get_line(cl)[cc - 1])) { + _consume_backspace_for_pair_symbol(prev_line, prev_column); + cursor_set_line(prev_line, false, true); + cursor_set_column(prev_column); + return; + } + + /* 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) { + prev_column = cc - _calculate_spaces_till_next_left_indent(cc); + prev_line = cl; + } + } + + _remove_text(prev_line, prev_column, cl, cc); + + cursor_set_line(prev_line, false, true); + cursor_set_column(prev_column); +} + /* Main Gutter */ void CodeEdit::_update_draw_main_gutter() { set_gutter_draw(main_gutter, draw_breakpoints || draw_bookmarks || draw_executing_lines); @@ -615,7 +1074,7 @@ bool CodeEdit::is_line_numbers_zero_padded() const { void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { String fc = TS->format_number(String::num(p_line + 1).lpad(line_number_digits, line_number_padding)); Ref<TextLine> tl; - tl.instance(); + tl.instantiate(); tl->add_string(fc, cache.font, cache.font_size); int yofs = p_region.position.y + (get_row_height() - tl->get_size().y) / 2; Color number_color = get_line_gutter_item_color(p_line, line_number_gutter); @@ -1284,6 +1743,25 @@ void CodeEdit::cancel_code_completion() { } void CodeEdit::_bind_methods() { + /* Indent management */ + ClassDB::bind_method(D_METHOD("set_indent_size", "size"), &CodeEdit::set_indent_size); + ClassDB::bind_method(D_METHOD("get_indent_size"), &CodeEdit::get_indent_size); + + ClassDB::bind_method(D_METHOD("set_indent_using_spaces", "use_spaces"), &CodeEdit::set_indent_using_spaces); + ClassDB::bind_method(D_METHOD("is_indent_using_spaces"), &CodeEdit::is_indent_using_spaces); + + ClassDB::bind_method(D_METHOD("set_auto_indent_enabled", "enable"), &CodeEdit::set_auto_indent_enabled); + ClassDB::bind_method(D_METHOD("is_auto_indent_enabled"), &CodeEdit::is_auto_indent_enabled); + + ClassDB::bind_method(D_METHOD("set_auto_indent_prefixes", "prefixes"), &CodeEdit::set_auto_indent_prefixes); + ClassDB::bind_method(D_METHOD("get_auto_indent_prefixes"), &CodeEdit::get_auto_indent_prefixes); + + ClassDB::bind_method(D_METHOD("do_indent"), &CodeEdit::do_indent); + ClassDB::bind_method(D_METHOD("do_unindent"), &CodeEdit::do_unindent); + + ClassDB::bind_method(D_METHOD("indent_lines"), &CodeEdit::indent_lines); + ClassDB::bind_method(D_METHOD("unindent_lines"), &CodeEdit::unindent_lines); + /* Main Gutter */ ClassDB::bind_method(D_METHOD("_main_gutter_draw_callback"), &CodeEdit::_main_gutter_draw_callback); @@ -1434,6 +1912,12 @@ void CodeEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "code_completion_enabled"), "set_code_completion_enabled", "is_code_completion_enabled"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "code_completion_prefixes"), "set_code_completion_prefixes", "get_code_comletion_prefixes"); + ADD_GROUP("Indentation", "indent_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "indent_size"), "set_indent_size", "get_indent_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_use_spaces"), "set_indent_using_spaces", "is_indent_using_spaces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_automatic"), "set_auto_indent_enabled", "is_auto_indent_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "indent_automatic_prefixes"), "set_auto_indent_prefixes", "get_auto_indent_prefixes"); + /* Signals */ ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("request_code_completion")); @@ -2060,6 +2544,12 @@ void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) { } CodeEdit::CodeEdit() { + /* Indent management */ + auto_indent_prefixes.insert(':'); + auto_indent_prefixes.insert('{'); + auto_indent_prefixes.insert('['); + auto_indent_prefixes.insert('('); + /* Text Direction */ set_layout_direction(LAYOUT_DIRECTION_LTR); set_text_direction(TEXT_DIRECTION_LTR); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 52b3f52a03..25b518402b 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -53,6 +53,19 @@ public: }; private: + /* Indent management */ + int indent_size = 4; + String indent_text = "\t"; + + bool auto_indent = false; + Set<char32_t> auto_indent_prefixes; + + bool indent_using_spaces = false; + int _calculate_spaces_till_next_left_indent(int p_column) const; + int _calculate_spaces_till_next_right_indent(int p_column) const; + + void _new_line(bool p_split_current_line = true, bool p_above = false); + /* Main Gutter */ enum MainGutterType { MAIN_GUTTER_BREAKPOINT = 0x01, @@ -206,6 +219,27 @@ protected: public: virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; + /* Indent management */ + void set_indent_size(const int p_size); + int get_indent_size() const; + + void set_indent_using_spaces(const bool p_use_spaces); + bool is_indent_using_spaces() const; + + void set_auto_indent_enabled(bool p_enabled); + bool is_auto_indent_enabled() const; + + void set_auto_indent_prefixes(const TypedArray<String> &p_prefixes); + TypedArray<String> get_auto_indent_prefixes() const; + + void do_indent(); + void do_unindent(); + + void indent_lines(); + void unindent_lines(); + + virtual void backspace() override; + /* Main Gutter */ void set_draw_breakpoints_gutter(bool p_draw); bool is_drawing_breakpoints_gutter() const; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index f394b9e3a5..049de4c8c5 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -88,7 +88,7 @@ Ref<Shader> ColorPicker::wheel_shader; Ref<Shader> ColorPicker::circle_shader; void ColorPicker::init_shaders() { - wheel_shader.instance(); + wheel_shader.instantiate(); wheel_shader->set_code( "shader_type canvas_item;" "void fragment() {" @@ -107,7 +107,7 @@ void ColorPicker::init_shaders() { " COLOR = vec4(clamp((abs(fract(((a - TAU) / TAU) + vec3(3.0, 2.0, 1.0) / 3.0) * 6.0 - 3.0) - 1.0), 0.0, 1.0), (b + b2 + b3 + b4) / 4.00);" "}"); - circle_shader.instance(); + circle_shader.instantiate(); circle_shader->set_code( "shader_type canvas_item;" "uniform float v = 1.0;" @@ -1213,9 +1213,9 @@ ColorPicker::ColorPicker() : wheel_edit->set_custom_minimum_size(Size2(get_theme_constant("sv_width"), get_theme_constant("sv_height"))); hb_edit->add_child(wheel_edit); - wheel_mat.instance(); + wheel_mat.instantiate(); wheel_mat->set_shader(wheel_shader); - circle_mat.instance(); + circle_mat.instantiate(); circle_mat->set_shader(circle_shader); MarginContainer *wheel_margin(memnew(MarginContainer)); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 64a3f1ca77..1bfdff1134 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -650,27 +650,11 @@ void Control::_notification(int p_notification) { } } -bool Control::clips_input() const { - if (get_script_instance()) { - return get_script_instance()->call(SceneStringNames::get_singleton()->_clips_input); - } - return false; -} - bool Control::has_point(const Point2 &p_point) const { - if (get_script_instance()) { - Variant v = p_point; - const Variant *p = &v; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_has_point, &p, 1, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return ret; - } + bool ret; + if (GDVIRTUAL_CALL(_has_point, p_point, ret)) { + return ret; } - /*if (has_stylebox("mask")) { - Ref<StyleBox> mask = get_stylebox("mask"); - return mask->test_mask(p_point,Rect2(Point2(),get_size())); - }*/ return Rect2(Point2(), get_size()).has_point(p_point); } @@ -1084,7 +1068,7 @@ Rect2 Control::get_parent_anchorable_rect() const { } else { #ifdef TOOLS_ENABLED Node *edited_root = get_tree()->get_edited_scene_root(); - if (edited_root && (this == edited_root || edited_root->is_a_parent_of(this))) { + if (edited_root && (this == edited_root || edited_root->is_ancestor_of(this))) { parent_rect.size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); } else { parent_rect = get_viewport()->get_visible_rect(); @@ -2784,7 +2768,6 @@ void Control::_bind_methods() { BIND_VMETHOD(MethodInfo( PropertyInfo(Variant::OBJECT, "control", PROPERTY_HINT_RESOURCE_TYPE, "Control"), "_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_clips_input")); ADD_GROUP("Anchor", "anchor_"); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_LEFT); @@ -2940,5 +2923,5 @@ void Control::_bind_methods() { ADD_SIGNAL(MethodInfo("minimum_size_changed")); ADD_SIGNAL(MethodInfo("theme_changed")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_point", PropertyInfo(Variant::VECTOR2, "point"))); + GDVIRTUAL_BIND(_has_point); } diff --git a/scene/gui/control.h b/scene/gui/control.h index a05025c32d..87fad96571 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -32,6 +32,7 @@ #define CONTROL_H #include "core/math/transform_2d.h" +#include "core/object/gdvirtual.gen.inc" #include "core/templates/rid.h" #include "scene/gui/shortcut.h" #include "scene/main/canvas_item.h" @@ -264,6 +265,7 @@ private: static bool has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; + GDVIRTUAL1RC(bool, _has_point, Vector2) protected: virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; @@ -329,7 +331,6 @@ public: virtual Size2 get_minimum_size() const; virtual Size2 get_combined_minimum_size() const; virtual bool has_point(const Point2 &p_point) const; - virtual bool clips_input() const; virtual void set_drag_forwarding(Control *p_target); virtual Variant get_drag_data(const Point2 &p_point); virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 5ef89e38f0..39aa6749e7 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -239,10 +239,6 @@ void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const } } -bool GraphEdit::clips_input() const { - return true; -} - void GraphEdit::get_connection_list(List<Connection> *r_connections) const { *r_connections = connections; } diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index e8300f901c..5251de1722 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -235,7 +235,6 @@ protected: virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; void _notification(int p_what); - virtual bool clips_input() const override; public: Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 77c502cf8d..836bffdf46 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -757,7 +757,7 @@ void GraphNode::_connpos_update() { continue; } - Size2i size = c->get_combined_minimum_size(); + Size2i size = c->get_rect().size; int y = sb->get_margin(SIDE_TOP) + vofs; int h = size.y; @@ -1021,6 +1021,6 @@ void GraphNode::_bind_methods() { } GraphNode::GraphNode() { - title_buf.instance(); + title_buf.instantiate(); set_mouse_filter(MOUSE_FILTER_STOP); } diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 150980b2e9..b0d54bf8c9 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -57,7 +57,7 @@ int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bo item.icon_region = Rect2i(); item.icon_modulate = Color(1, 1, 1, 1); item.text = p_item; - item.text_buf.instance(); + item.text_buf.instantiate(); item.selectable = p_selectable; item.selected = false; item.disabled = false; @@ -80,7 +80,7 @@ int ItemList::add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable) { item.icon_region = Rect2i(); item.icon_modulate = Color(1, 1, 1, 1); //item.text=p_item; - item.text_buf.instance(); + item.text_buf.instantiate(); item.selectable = p_selectable; item.selected = false; item.disabled = false; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 0ce0130ad5..6580d794d1 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -453,6 +453,7 @@ void Label::set_text(const String &p_string) { visible_chars = get_total_character_count() * percent_visible; } update(); + minimum_size_changed(); } void Label::set_text_direction(Control::TextDirection p_text_direction) { diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index d45ffde715..ee0618a991 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -301,7 +301,7 @@ void LinkButton::_bind_methods() { } LinkButton::LinkButton() { - text_buf.instance(); + text_buf.instantiate(); set_focus_mode(FOCUS_NONE); set_default_cursor_shape(CURSOR_POINTING_HAND); } diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index e4cbe984c9..74718395d3 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -80,8 +80,8 @@ class PopupMenu : public Popup { } Item() { - text_buf.instance(); - accel_text_buf.instance(); + text_buf.instantiate(); + accel_text_buf.instantiate(); checkable_type = CHECKABLE_TYPE_NONE; } }; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 987d05bf71..f32ad2144a 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -934,6 +934,11 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } + Vector2 fbg_line_off = off + p_ofs; + // Draw background color box + Vector2i chr_range = TS->shaped_text_get_range(rid); + _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0); + // Draw main text. Color selection_fg = get_theme_color("font_selected_color"); Color selection_bg = get_theme_color("selection_color"); @@ -1079,6 +1084,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o off.x += glyphs[i].advance; } } + // Draw foreground color box + _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1); + off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom(); } @@ -2036,6 +2044,36 @@ bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item) return false; } +Color RichTextLabel::_find_bgcolor(Item *p_item) { + Item *item = p_item; + + while (item) { + if (item->type == ITEM_BGCOLOR) { + ItemBGColor *color = static_cast<ItemBGColor *>(item); + return color->color; + } + + item = item->parent; + } + + return Color(0, 0, 0, 0); +} + +Color RichTextLabel::_find_fgcolor(Item *p_item) { + Item *item = p_item; + + while (item) { + if (item->type == ITEM_FGCOLOR) { + ItemFGColor *color = static_cast<ItemFGColor *>(item); + return color->color; + } + + item = item->parent; + } + + return Color(0, 0, 0, 0); +} + bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { if (from && from != to) { if (from->type != ITEM_FONT && from->type != ITEM_COLOR && from->type != ITEM_UNDERLINE && from->type != ITEM_STRIKETHROUGH) { @@ -2546,6 +2584,22 @@ void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_freq _add_item(item, true); } +void RichTextLabel::push_bgcolor(const Color &p_color) { + ERR_FAIL_COND(current->type == ITEM_TABLE); + ItemBGColor *item = memnew(ItemBGColor); + + item->color = p_color; + _add_item(item, true); +} + +void RichTextLabel::push_fgcolor(const Color &p_color) { + ERR_FAIL_COND(current->type == ITEM_TABLE); + ItemFGColor *item = memnew(ItemFGColor); + + item->color = p_color; + _add_item(item, true); +} + void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment) { ItemCustomFX *item = memnew(ItemCustomFX); item->custom_effect = p_custom_effect; @@ -3352,6 +3406,23 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("rainbow"); set_process_internal(true); + + } else if (tag.begins_with("bgcolor=")) { + String color_str = tag.substr(8, tag.length()); + Color color = Color::from_string(color_str, base_color); + + push_bgcolor(color); + pos = brk_end + 1; + tag_stack.push_front("bgcolor"); + + } else if (tag.begins_with("fgcolor=")) { + String color_str = tag.substr(8, tag.length()); + Color color = Color::from_string(color_str, base_color); + + push_fgcolor(color); + pos = brk_end + 1; + tag_stack.push_front("fgcolor"); + } else { Vector<String> &expr = split_tag_block; if (expr.size() < 1) { @@ -3918,6 +3989,8 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell_size_override", "min_size", "max_size"), &RichTextLabel::set_cell_size_override); ClassDB::bind_method(D_METHOD("set_cell_padding", "padding"), &RichTextLabel::set_cell_padding); ClassDB::bind_method(D_METHOD("push_cell"), &RichTextLabel::push_cell); + ClassDB::bind_method(D_METHOD("push_fgcolor", "fgcolor"), &RichTextLabel::push_fgcolor); + ClassDB::bind_method(D_METHOD("push_bgcolor", "bgcolor"), &RichTextLabel::push_bgcolor); ClassDB::bind_method(D_METHOD("pop"), &RichTextLabel::pop); ClassDB::bind_method(D_METHOD("clear"), &RichTextLabel::clear); @@ -4012,7 +4085,7 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, "RichTextEffect", (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language"); @@ -4056,6 +4129,8 @@ void RichTextLabel::_bind_methods() { BIND_ENUM_CONSTANT(ITEM_WAVE); BIND_ENUM_CONSTANT(ITEM_TORNADO); BIND_ENUM_CONSTANT(ITEM_RAINBOW); + BIND_ENUM_CONSTANT(ITEM_BGCOLOR); + BIND_ENUM_CONSTANT(ITEM_FGCOLOR); BIND_ENUM_CONSTANT(ITEM_META); BIND_ENUM_CONSTANT(ITEM_DROPCAP); BIND_ENUM_CONSTANT(ITEM_CUSTOMFX); @@ -4108,6 +4183,65 @@ Size2 RichTextLabel::get_minimum_size() const { return size; } +void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag) { + Vector2i fbg_index = Vector2i(end, start); + Color last_color = Color(0, 0, 0, 0); + bool draw_box = false; + // Draw a box based on color tags associated with glyphs + for (int i = start; i < end; i++) { + Item *it = _get_item_at_pos(it_from, it_to, i); + Color color = Color(0, 0, 0, 0); + + if (fbg_flag == 0) { + color = _find_bgcolor(it); + } else { + color = _find_fgcolor(it); + } + + bool change_to_color = ((color.a > 0) && ((last_color.a - 0.0) < 0.01)); + bool change_from_color = (((color.a - 0.0) < 0.01) && (last_color.a > 0.0)); + bool change_color = (((color.a > 0) == (last_color.a > 0)) && (color != last_color)); + + if (change_to_color) { + fbg_index.x = MIN(i, fbg_index.x); + fbg_index.y = MAX(i, fbg_index.y); + } + + if (change_from_color || change_color) { + fbg_index.x = MIN(i, fbg_index.x); + fbg_index.y = MAX(i, fbg_index.y); + draw_box = true; + } + + if (draw_box) { + Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, fbg_index.y); + for (int j = 0; j < sel.size(); j++) { + Vector2 rect_off = line_off + Vector2(sel[j].x, -TS->shaped_text_get_ascent(p_rid)); + Vector2 rect_size = Vector2(sel[j].y - sel[j].x, TS->shaped_text_get_size(p_rid).y); + RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); + } + fbg_index = Vector2i(end, start); + draw_box = false; + } + + if (change_color) { + fbg_index.x = MIN(i, fbg_index.x); + fbg_index.y = MAX(i, fbg_index.y); + } + + last_color = color; + } + + if (last_color.a > 0) { + Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, end); + for (int i = 0; i < sel.size(); i++) { + Vector2 rect_off = line_off + Vector2(sel[i].x, -TS->shaped_text_get_ascent(p_rid)); + Vector2 rect_size = Vector2(sel[i].y - sel[i].x, TS->shaped_text_get_size(p_rid).y); + RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); + } + } +} + 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()) { diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 9cf94b0cd7..999d8b05fd 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -75,6 +75,8 @@ public: ITEM_WAVE, ITEM_TORNADO, ITEM_RAINBOW, + ITEM_BGCOLOR, + ITEM_FGCOLOR, ITEM_META, ITEM_DROPCAP, ITEM_CUSTOMFX @@ -100,7 +102,7 @@ private: int char_offset = 0; int char_count = 0; - Line() { text_buf.instance(); } + Line() { text_buf.instantiate(); } }; struct Item { @@ -307,13 +309,23 @@ private: ItemRainbow() { type = ITEM_RAINBOW; } }; + struct ItemBGColor : public Item { + Color color; + ItemBGColor() { type = ITEM_BGCOLOR; } + }; + + struct ItemFGColor : public Item { + Color color; + ItemFGColor() { type = ITEM_FGCOLOR; } + }; + struct ItemCustomFX : public ItemFX { Ref<CharFXTransform> char_fx_transform; Ref<RichTextEffect> custom_effect; ItemCustomFX() { type = ITEM_CUSTOMFX; - char_fx_transform.instance(); + char_fx_transform.instantiate(); } virtual ~ItemCustomFX() { @@ -422,6 +434,8 @@ private: bool _find_underline(Item *p_item); bool _find_strikethrough(Item *p_item); bool _find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item = nullptr); + Color _find_bgcolor(Item *p_item); + Color _find_fgcolor(Item *p_item); bool _find_layout_subitem(Item *from, Item *to); void _fetch_item_fx_stack(Item *p_item, Vector<ItemFX *> &r_stack); @@ -437,6 +451,8 @@ private: Ref<RichTextEffect> _get_custom_effect_by_code(String p_bbcode_identifier); virtual Dictionary parse_expressions_for_values(Vector<String> p_expressions); + void _draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag); + bool use_bbcode = false; String bbcode; @@ -474,6 +490,8 @@ public: void push_wave(float p_frequency, float p_amplitude); void push_tornado(float p_frequency, float p_radius); void push_rainbow(float p_saturation, float p_value, float p_frequency); + void push_bgcolor(const Color &p_color); + void push_fgcolor(const Color &p_color); void push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment); void set_table_column_expand(int p_column, bool p_expand, int p_ratio = 1); void set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 5f872644ab..177f146b6a 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -32,10 +32,6 @@ #include "core/os/os.h" #include "scene/main/window.h" -bool ScrollContainer::clips_input() const { - return true; -} - Size2 ScrollContainer::get_minimum_size() const { Ref<StyleBox> sb = get_theme_stylebox("bg"); Size2 min_size; @@ -239,13 +235,13 @@ void ScrollContainer::_update_scrollbar_position() { } void ScrollContainer::_gui_focus_changed(Control *p_control) { - if (follow_focus && is_a_parent_of(p_control)) { + if (follow_focus && is_ancestor_of(p_control)) { ensure_control_visible(p_control); } } void ScrollContainer::ensure_control_visible(Control *p_control) { - ERR_FAIL_COND_MSG(!is_a_parent_of(p_control), "Must be a parent of the control."); + ERR_FAIL_COND_MSG(!is_ancestor_of(p_control), "Must be an ancestor of the control."); Rect2 global_rect = get_global_rect(); Rect2 other_rect = p_control->get_global_rect(); diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index c77a0d62f5..4733fdabca 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -108,8 +108,6 @@ public: VScrollBar *get_v_scrollbar(); void ensure_control_visible(Control *p_control); - virtual bool clips_input() const override; - TypedArray<String> get_configuration_warnings() const override; ScrollContainer(); diff --git a/scene/gui/shortcut.cpp b/scene/gui/shortcut.cpp index cbbcf9e069..962c6dcc60 100644 --- a/scene/gui/shortcut.cpp +++ b/scene/gui/shortcut.cpp @@ -42,7 +42,7 @@ Ref<InputEvent> Shortcut::get_shortcut() const { } bool Shortcut::is_shortcut(const Ref<InputEvent> &p_event) const { - return shortcut.is_valid() && shortcut->shortcut_match(p_event); + return shortcut.is_valid() && shortcut->is_match(p_event, true); } String Shortcut::get_as_text() const { diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 4ac73ae6b5..941dd30057 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -52,7 +52,7 @@ void SpinBox::_value_changed(double) { void SpinBox::_text_submitted(const String &p_string) { Ref<Expression> expr; - expr.instance(); + expr.instantiate(); String num = TS->parse_number(p_string); // Ignore the prefix and suffix in the expression @@ -140,6 +140,8 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { accept_event(); } } break; + default: + break; } } diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index acf0641005..133966013b 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -586,7 +586,7 @@ void TabContainer::_refresh_texts() { Control *control = Object::cast_to<Control>(tabs[i]); String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(tr(control->get_name())); Ref<TextLine> name; - name.instance(); + name.instantiate(); name->set_direction(rtl ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); name->add_string(text, font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); text_buf.push_back(name); diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index 11096e7976..6f1cff9ec8 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -743,7 +743,7 @@ void Tabs::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) { Tab t; t.text = p_str; t.xl_text = tr(p_str); - t.text_buf.instance(); + t.text_buf.instantiate(); t.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); t.text_buf->add_string(t.xl_text, get_theme_font("font"), get_theme_font_size("font_size"), Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); t.icon = p_icon; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 6f78d586f1..370fdd8b88 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -102,14 +102,6 @@ static char32_t _get_right_pair_symbol(char32_t c) { return 0; } -static int _find_first_non_whitespace_column_of_line(const String &line) { - int left = 0; - while (left < line.length() && _is_whitespace(line[left])) { - left++; - } - return left; -} - /////////////////////////////////////////////////////////////////////////////// void TextEdit::Text::set_font(const Ref<Font> &p_font) { @@ -120,8 +112,12 @@ void TextEdit::Text::set_font_size(int p_font_size) { font_size = p_font_size; } -void TextEdit::Text::set_indent_size(int p_indent_size) { - indent_size = p_indent_size; +void TextEdit::Text::set_tab_size(int p_tab_size) { + tab_size = p_tab_size; +} + +int TextEdit::Text::get_tab_size() const { + return tab_size; } void TextEdit::Text::set_font_features(const Dictionary &p_features) { @@ -204,9 +200,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ } // Apply tab align. - if (indent_size > 0) { + if (tab_size > 0) { Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * indent_size); + tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); text.write[p_line].data_buf->tab_align(tabs); } } @@ -214,9 +210,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { text.write[i].data_buf->set_width(width); - if (indent_size > 0) { + if (tab_size > 0) { Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * indent_size); + tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); text.write[i].data_buf->tab_align(tabs); } } @@ -822,7 +818,7 @@ void TextEdit::_notification(int p_what) { if (draw_minimap) { int minimap_visible_lines = _get_minimap_visible_rows(); int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); - int minimap_tab_size = minimap_char_size.x * indent_size; + int minimap_tab_size = minimap_char_size.x * text.get_tab_size(); // calculate viewport size and y offset int viewport_height = (draw_amount - 1) * minimap_line_height; @@ -1114,7 +1110,7 @@ void TextEdit::_notification(int p_what) { } Ref<TextLine> tl; - tl.instance(); + tl.instantiate(); tl->add_string(text, cache.font, cache.font_size); int yofs = ofs_y + (row_height - tl->get_size().y) / 2; @@ -1695,7 +1691,13 @@ void TextEdit::_consume_backspace_for_pair_symbol(int prev_line, int prev_column } } -void TextEdit::backspace_at_cursor() { +void TextEdit::backspace() { + ScriptInstance *si = get_script_instance(); + if (si && si->has_method("_backspace")) { + si->call("_backspace"); + return; + } + if (readonly) { return; } @@ -1704,34 +1706,15 @@ void TextEdit::backspace_at_cursor() { return; } + if (is_selection_active()) { + delete_selection(); + return; + } + int prev_line = cursor.column ? cursor.line : cursor.line - 1; int prev_column = cursor.column ? (cursor.column - 1) : (text[cursor.line - 1].length()); - if (cursor.line != prev_line) { - for (int i = 0; i < gutters.size(); i++) { - if (!gutters[i].overwritable) { - continue; - } - - if (text.get_line_gutter_text(cursor.line, i) != "") { - text.set_line_gutter_text(prev_line, i, text.get_line_gutter_text(cursor.line, i)); - text.set_line_gutter_item_color(prev_line, i, text.get_line_gutter_item_color(cursor.line, i)); - } - - if (text.get_line_gutter_icon(cursor.line, i).is_valid()) { - text.set_line_gutter_icon(prev_line, i, text.get_line_gutter_icon(cursor.line, i)); - text.set_line_gutter_item_color(prev_line, i, text.get_line_gutter_item_color(cursor.line, i)); - } - - if (text.get_line_gutter_metadata(cursor.line, i) != "") { - text.set_line_gutter_metadata(prev_line, i, text.get_line_gutter_metadata(cursor.line, i)); - } - - if (text.is_line_gutter_clickable(cursor.line, i)) { - text.set_line_gutter_clickable(prev_line, i, true); - } - } - } + merge_gutters(cursor.line, prev_line); if (is_line_hidden(cursor.line)) { set_line_as_hidden(prev_line, true); @@ -1742,168 +1725,13 @@ void TextEdit::backspace_at_cursor() { _is_pair_left_symbol(text[cursor.line][cursor.column - 1])) { _consume_backspace_for_pair_symbol(prev_line, prev_column); } else { - // Handle space indentation. - if (cursor.column != 0 && indent_using_spaces) { - // Check if there are no other chars before cursor, just indentation. - bool unindent = true; - int i = 0; - while (i < cursor.column && i < text[cursor.line].length()) { - if (!_is_whitespace(text[cursor.line][i])) { - unindent = false; - break; - } - i++; - } - - // Then we can remove all spaces as a single character. - if (unindent) { - // We want to remove spaces up to closest indent, or whole indent if cursor is pointing at it. - int spaces_to_delete = _calculate_spaces_till_next_left_indent(cursor.column); - prev_column = cursor.column - spaces_to_delete; - _remove_text(cursor.line, prev_column, cursor.line, cursor.column); - } else { - _remove_text(prev_line, prev_column, cursor.line, cursor.column); - } - } else { - _remove_text(prev_line, prev_column, cursor.line, cursor.column); - } + _remove_text(prev_line, prev_column, cursor.line, cursor.column); } cursor_set_line(prev_line, false, true); cursor_set_column(prev_column); } -void TextEdit::indent_selected_lines_right() { - int start_line; - int end_line; - - // This value informs us by how much we changed selection position by indenting right. - // Default is 1 for tab indentation. - int selection_offset = 1; - begin_complex_operation(); - - if (is_selection_active()) { - start_line = get_selection_from_line(); - end_line = get_selection_to_line(); - } else { - start_line = cursor.line; - end_line = start_line; - } - - // Ignore if the cursor is not past the first column. - if (is_selection_active() && get_selection_to_column() == 0) { - selection_offset = 0; - end_line--; - } - - for (int i = start_line; i <= end_line; i++) { - String line_text = get_line(i); - if (line_text.size() == 0 && is_selection_active()) { - continue; - } - if (indent_using_spaces) { - // We don't really care where selection is - we just need to know indentation level at the beginning of the line. - int left = _find_first_non_whitespace_column_of_line(line_text); - int spaces_to_add = _calculate_spaces_till_next_right_indent(left); - // Since we will add these many spaces, we want to move the whole selection and cursor by this much. - selection_offset = spaces_to_add; - for (int j = 0; j < spaces_to_add; j++) { - line_text = ' ' + line_text; - } - } else { - line_text = '\t' + line_text; - } - set_line(i, line_text); - } - - // Fix selection and cursor being off after shifting selection right. - if (is_selection_active()) { - select(selection.from_line, selection.from_column + selection_offset, selection.to_line, selection.to_column + selection_offset); - } - cursor_set_column(cursor.column + selection_offset, false); - end_complex_operation(); - update(); -} - -void TextEdit::indent_selected_lines_left() { - int start_line; - int end_line; - - // Moving cursor and selection after unindenting can get tricky because - // changing content of line can move cursor and selection on its own (if new line ends before previous position of either), - // therefore we just remember initial values and at the end of the operation offset them by number of removed characters. - int removed_characters = 0; - int initial_selection_end_column = selection.to_column; - int initial_cursor_column = cursor.column; - - begin_complex_operation(); - - if (is_selection_active()) { - start_line = get_selection_from_line(); - end_line = get_selection_to_line(); - } else { - start_line = cursor.line; - end_line = start_line; - } - - // Ignore if the cursor is not past the first column. - if (is_selection_active() && get_selection_to_column() == 0) { - end_line--; - } - String first_line_text = get_line(start_line); - String last_line_text = get_line(end_line); - - for (int i = start_line; i <= end_line; i++) { - String line_text = get_line(i); - - if (line_text.begins_with("\t")) { - line_text = line_text.substr(1, line_text.length()); - set_line(i, line_text); - removed_characters = 1; - } else if (line_text.begins_with(" ")) { - // When unindenting we aim to remove spaces before line that has selection no matter what is selected, - // so we start of by finding first non whitespace character of line - int left = _find_first_non_whitespace_column_of_line(line_text); - - // Here we remove only enough spaces to align text to nearest full multiple of indentation_size. - // In case where selection begins at the start of indentation_size multiple we remove whole indentation level. - int spaces_to_remove = _calculate_spaces_till_next_left_indent(left); - - line_text = line_text.substr(spaces_to_remove, line_text.length()); - set_line(i, line_text); - removed_characters = spaces_to_remove; - } - } - - if (is_selection_active()) { - // Fix selection being off by one on the first line. - if (first_line_text != get_line(start_line)) { - select(selection.from_line, selection.from_column - removed_characters, - selection.to_line, initial_selection_end_column); - } - // Fix selection being off by one on the last line. - if (last_line_text != get_line(end_line)) { - select(selection.from_line, selection.from_column, - selection.to_line, initial_selection_end_column - removed_characters); - } - } - cursor_set_column(initial_cursor_column - removed_characters, false); - end_complex_operation(); - update(); -} - -int TextEdit::_calculate_spaces_till_next_left_indent(int column) { - int spaces_till_indent = column % indent_size; - if (spaces_till_indent == 0) { - spaces_till_indent = indent_size; - } - return spaces_till_indent; -} - -int TextEdit::_calculate_spaces_till_next_right_indent(int column) { - return indent_size - column % indent_size; -} - void TextEdit::_swap_current_input_direction() { if (input_direction == TEXT_DIRECTION_LTR) { input_direction = TEXT_DIRECTION_RTL; @@ -1919,90 +1747,8 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) { return; } - String ins = "\n"; - - // Keep indentation. - int space_count = 0; - for (int i = 0; i < cursor.column; i++) { - if (text[cursor.line][i] == '\t') { - if (indent_using_spaces) { - ins += space_indent; - } else { - ins += "\t"; - } - space_count = 0; - } else if (text[cursor.line][i] == ' ') { - space_count++; - - if (space_count == indent_size) { - if (indent_using_spaces) { - ins += space_indent; - } else { - ins += "\t"; - } - space_count = 0; - } - } else { - break; - } - } - - bool brace_indent = false; - - // No need to indent if we are going upwards. - if (auto_indent && !p_above) { - // Indent once again if previous line will end with ':','{','[','(' and the line is not a comment - // (i.e. colon/brace precedes current cursor position). - if (cursor.column > 0) { - bool indent_char_found = false; - bool should_indent = false; - char indent_char = ':'; - char c = text[cursor.line][cursor.column]; - - for (int i = 0; i < cursor.column; i++) { - c = text[cursor.line][i]; - switch (c) { - case ':': - case '{': - case '[': - case '(': - indent_char_found = true; - should_indent = true; - indent_char = c; - continue; - } - - if (indent_char_found && is_line_comment(cursor.line)) { - should_indent = true; - break; - } else if (indent_char_found && !_is_whitespace(c)) { - should_indent = false; - indent_char_found = false; - } - } - - if (!is_line_comment(cursor.line) && should_indent) { - if (indent_using_spaces) { - ins += space_indent; - } else { - ins += "\t"; - } - - // No need to move the brace below if we are not taking the text with us. - char32_t closing_char = _get_right_pair_symbol(indent_char); - if ((closing_char != 0) && (closing_char == text[cursor.line][cursor.column])) { - if (p_split_current_line) { - brace_indent = true; - ins += "\n" + ins.substr(1, ins.length() - 2); - } else { - brace_indent = false; - ins = "\n" + ins.substr(1, ins.length() - 2); - } - } - } - } - } begin_complex_operation(); + bool first_line = false; if (!p_split_current_line) { if (p_above) { @@ -2018,83 +1764,13 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) { } } - insert_text_at_cursor(ins); + insert_text_at_cursor("\n"); if (first_line) { cursor_set_line(0); - } else if (brace_indent) { - cursor_set_line(cursor.line - 1, false); - cursor_set_column(text[cursor.line].length()); - } - end_complex_operation(); -} - -void TextEdit::_indent_right() { - if (readonly) { - return; - } - - if (is_selection_active()) { - indent_selected_lines_right(); - } else { - // Simple indent. - if (indent_using_spaces) { - // Insert only as much spaces as needed till next indentation level. - int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor.column); - String indent_to_insert = String(); - for (int i = 0; i < spaces_to_add; i++) { - indent_to_insert = ' ' + indent_to_insert; - } - _insert_text_at_cursor(indent_to_insert); - } else { - _insert_text_at_cursor("\t"); - } - } -} - -void TextEdit::_indent_left() { - if (readonly) { - return; } - if (is_selection_active()) { - indent_selected_lines_left(); - } else { - // Simple unindent. - int cc = cursor.column; - const String &line = text[cursor.line]; - - int left = _find_first_non_whitespace_column_of_line(line); - cc = MIN(cc, left); - - while (cc < indent_size && cc < left && line[cc] == ' ') { - cc++; - } - - if (cc > 0 && cc <= text[cursor.line].length()) { - if (text[cursor.line][cc - 1] == '\t') { - // Tabs unindentation. - _remove_text(cursor.line, cc - 1, cursor.line, cc); - if (cursor.column >= left) { - cursor_set_column(MAX(0, cursor.column - 1)); - } - update(); - } else { - // Spaces unindentation. - int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc); - if (spaces_to_remove > 0) { - _remove_text(cursor.line, cc - spaces_to_remove, cursor.line, cc); - if (cursor.column > left - spaces_to_remove) { // Inside text? - cursor_set_column(MAX(0, cursor.column - spaces_to_remove)); - } - update(); - } - } - } else if (cc == 0 && line.length() > 0 && line[0] == '\t') { - _remove_text(cursor.line, 0, cursor.line, 1); - update(); - } - } + end_complex_operation(); } void TextEdit::_move_cursor_left(bool p_select, bool p_move_by_word) { @@ -2331,20 +2007,24 @@ void TextEdit::_move_cursor_page_down(bool p_select) { } } -void TextEdit::_backspace(bool p_word, bool p_all_to_left) { +void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { if (readonly) { return; } - if (is_selection_active()) { - _delete_selection(); + if (is_selection_active() || (!p_all_to_left && !p_word)) { + backspace(); return; } + if (p_all_to_left) { int cursor_current_column = cursor.column; cursor.column = 0; _remove_text(cursor.line, 0, cursor.line, cursor_current_column); - } else if (p_word) { + return; + } + + if (p_word) { int line = cursor.line; int column = cursor.column; @@ -2360,8 +2040,7 @@ void TextEdit::_backspace(bool p_word, bool p_all_to_left) { cursor_set_line(line, false); cursor_set_column(column); - } else { - backspace_at_cursor(); + return; } } @@ -2371,7 +2050,7 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { } if (is_selection_active()) { - _delete_selection(); + delete_selection(); return; } int curline_len = text[cursor.line].length(); @@ -2416,15 +2095,16 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { update(); } -void TextEdit::_delete_selection() { - if (is_selection_active()) { - selection.active = false; - update(); - _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - cursor_set_line(selection.from_line, false, false); - cursor_set_column(selection.from_column); - update(); +void TextEdit::delete_selection() { + if (!is_selection_active()) { + return; } + + selection.active = false; + _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); + cursor_set_line(selection.from_line, false, false); + cursor_set_column(selection.from_column); + update(); } void TextEdit::_move_cursor_document_start(bool p_select) { @@ -2459,7 +2139,7 @@ void TextEdit::_move_cursor_document_end(bool p_select) { void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection) { if (p_had_selection) { - _delete_selection(); + delete_selection(); } // Remove the old character if in insert mode and no selection. @@ -2942,31 +2622,19 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } - // INDENTATION. - if (k->is_action("ui_text_dedent", true)) { - _indent_left(); - accept_event(); - return; - } - if (k->is_action("ui_text_indent", true)) { - _indent_right(); - accept_event(); - return; - } - // BACKSPACE AND DELETE. if (k->is_action("ui_text_backspace_all_to_left", true)) { - _backspace(false, true); + _do_backspace(false, true); accept_event(); return; } if (k->is_action("ui_text_backspace_word", true)) { - _backspace(true); + _do_backspace(true); accept_event(); return; } if (k->is_action("ui_text_backspace", true)) { - _backspace(); + _do_backspace(); accept_event(); return; } @@ -4540,6 +4208,39 @@ bool TextEdit::is_gutter_overwritable(int p_gutter) const { return gutters[p_gutter].overwritable; } +void TextEdit::merge_gutters(int p_from_line, int p_to_line) { + ERR_FAIL_INDEX(p_from_line, text.size()); + ERR_FAIL_INDEX(p_to_line, text.size()); + if (p_from_line == p_to_line) { + return; + } + + for (int i = 0; i < gutters.size(); i++) { + if (!gutters[i].overwritable) { + continue; + } + + if (text.get_line_gutter_text(p_from_line, i) != "") { + text.set_line_gutter_text(p_to_line, i, text.get_line_gutter_text(p_from_line, i)); + text.set_line_gutter_item_color(p_to_line, i, text.get_line_gutter_item_color(p_from_line, i)); + } + + if (text.get_line_gutter_icon(p_from_line, i).is_valid()) { + text.set_line_gutter_icon(p_to_line, i, text.get_line_gutter_icon(p_from_line, i)); + text.set_line_gutter_item_color(p_to_line, i, text.get_line_gutter_item_color(p_from_line, i)); + } + + if (text.get_line_gutter_metadata(p_from_line, i) != "") { + text.set_line_gutter_metadata(p_to_line, i, text.get_line_gutter_metadata(p_from_line, i)); + } + + if (text.is_line_gutter_clickable(p_from_line, i)) { + text.set_line_gutter_clickable(p_to_line, i, true); + } + } + update(); +} + void TextEdit::set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback) { ERR_FAIL_INDEX(p_gutter, gutters.size()); ERR_FAIL_NULL(p_object); @@ -4625,18 +4326,6 @@ Color TextEdit::get_line_background_color(int p_line) { return text.get_line_background_color(p_line); } -void TextEdit::add_keyword(const String &p_keyword) { - keywords.insert(p_keyword); -} - -void TextEdit::clear_keywords() { - keywords.clear(); -} - -void TextEdit::set_auto_indent(bool p_auto_indent) { - auto_indent = p_auto_indent; -} - void TextEdit::cut() { if (readonly) { return; @@ -4652,7 +4341,7 @@ void TextEdit::cut() { _remove_text(cursor.line, 0, cursor.line + 1, 0); } else { _remove_text(cursor.line, 0, cursor.line, text[cursor.line].length()); - backspace_at_cursor(); + backspace(); cursor_set_line(cursor.line + 1); } @@ -5199,7 +4888,6 @@ int TextEdit::get_last_unhidden_line() const { int TextEdit::get_indent_level(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); - // Counts number of tabs and spaces before line starts. int tab_count = 0; int whitespace_count = 0; int line_length = text[p_line].size(); @@ -5212,28 +4900,17 @@ int TextEdit::get_indent_level(int p_line) const { break; } } - return tab_count * indent_size + whitespace_count; + return tab_count * text.get_tab_size() + whitespace_count; } -bool TextEdit::is_line_comment(int p_line) const { - // Checks to see if this line is the start of a comment. - ERR_FAIL_INDEX_V(p_line, text.size(), false); +int TextEdit::get_first_non_whitespace_column(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); - int line_length = text[p_line].size(); - for (int i = 0; i < line_length - 1; i++) { - if (_is_whitespace(text[p_line][i])) { - continue; - } - if (_is_symbol(text[p_line][i])) { - if (text[p_line][i] == '\\') { - i++; // Skip quoted anything. - continue; - } - return text[p_line][i] == '#' || (i + 1 < line_length && text[p_line][i] == '/' && text[p_line][i + 1] == '/'); - } - break; + int col = 0; + while (col < text[p_line].length() && _is_whitespace(text[p_line][col])) { + col++; } - return false; + return col; } int TextEdit::get_line_count() const { @@ -5409,32 +5086,18 @@ void TextEdit::_push_current_op() { } } -void TextEdit::set_indent_using_spaces(const bool p_use_spaces) { - indent_using_spaces = p_use_spaces; -} - -bool TextEdit::is_indent_using_spaces() const { - return indent_using_spaces; -} - -void TextEdit::set_indent_size(const int p_size) { - ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0."); - if (indent_size != p_size) { - indent_size = p_size; - text.set_indent_size(p_size); - text.invalidate_all_lines(); - } - - space_indent = ""; - for (int i = 0; i < p_size; i++) { - space_indent += " "; +void TextEdit::set_tab_size(const int p_size) { + ERR_FAIL_COND_MSG(p_size <= 0, "Tab size must be greater than 0."); + if (p_size == text.get_tab_size()) { + return; } - + text.set_tab_size(p_size); + text.invalidate_all_lines(); update(); } -int TextEdit::get_indent_size() { - return indent_size; +int TextEdit::get_tab_size() const { + return text.get_tab_size(); } void TextEdit::set_draw_tabs(bool p_draw) { @@ -6027,6 +5690,11 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_language", "language"), &TextEdit::set_language); ClassDB::bind_method(D_METHOD("get_language"), &TextEdit::get_language); + ClassDB::bind_method(D_METHOD("get_first_non_whitespace_column", "line"), &TextEdit::get_first_non_whitespace_column); + ClassDB::bind_method(D_METHOD("get_indent_level", "line"), &TextEdit::get_indent_level); + ClassDB::bind_method(D_METHOD("set_tab_size", "size"), &TextEdit::set_tab_size); + ClassDB::bind_method(D_METHOD("get_tab_size"), &TextEdit::get_tab_size); + ClassDB::bind_method(D_METHOD("set_text", "text"), &TextEdit::set_text); ClassDB::bind_method(D_METHOD("insert_text_at_cursor", "text"), &TextEdit::insert_text_at_cursor); @@ -6081,6 +5749,10 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &TextEdit::set_selecting_enabled); ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &TextEdit::is_selecting_enabled); + ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection); + ClassDB::bind_method(D_METHOD("backspace"), &TextEdit::backspace); + BIND_VMETHOD(MethodInfo("_backspace")); + ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut); ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy); ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste); @@ -6136,6 +5808,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_gutter_clickable", "gutter"), &TextEdit::is_gutter_clickable); ClassDB::bind_method(D_METHOD("set_gutter_overwritable", "gutter", "overwritable"), &TextEdit::set_gutter_overwritable); ClassDB::bind_method(D_METHOD("is_gutter_overwritable", "gutter"), &TextEdit::is_gutter_overwritable); + ClassDB::bind_method(D_METHOD("merge_gutters", "from_line", "to_line"), &TextEdit::merge_gutters); ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "object", "callback"), &TextEdit::set_gutter_custom_draw); // Line gutters. @@ -6262,7 +5935,7 @@ TextEdit::TextEdit() { _update_caches(); set_default_cursor_shape(CURSOR_IBEAM); - text.set_indent_size(indent_size); + text.set_tab_size(text.get_tab_size()); text.clear(); h_scroll = memnew(HScrollBar); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index c04d758abb..146de50275 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -96,7 +96,7 @@ private: bool hidden = false; Line() { - data_buf.instance(); + data_buf.instantiate(); } }; @@ -112,11 +112,12 @@ private: int width = -1; - int indent_size = 4; + int tab_size = 4; int gutter_count = 0; public: - void set_indent_size(int p_indent_size); + void set_tab_size(int p_tab_size); + int get_tab_size() const; void set_font(const Ref<Font> &p_font); void set_font_size(int p_font_size); void set_font_features(const Dictionary &p_features); @@ -259,9 +260,6 @@ private: int max_chars = 0; bool readonly = true; // Initialise to opposite first, so we get past the early-out in set_readonly. - bool indent_using_spaces = false; - int indent_size = 4; - String space_indent = " "; Timer *caret_blink_timer; bool caret_blink_enabled = false; @@ -296,7 +294,6 @@ private: bool scroll_past_end_of_file_enabled = false; bool brace_matching_enabled = false; bool highlight_current_line = false; - bool auto_indent = false; String cut_copy_line; bool insert_mode = false; @@ -420,14 +417,9 @@ private: void _clear(); - int _calculate_spaces_till_next_left_indent(int column); - int _calculate_spaces_till_next_right_indent(int column); - // Methods used in shortcuts void _swap_current_input_direction(); void _new_line(bool p_split_current = true, bool p_above = false); - void _indent_right(); - void _indent_left(); void _move_cursor_left(bool p_select, bool p_move_by_word = false); void _move_cursor_right(bool p_select, bool p_move_by_word = false); void _move_cursor_up(bool p_select); @@ -436,9 +428,8 @@ private: void _move_cursor_to_line_end(bool p_select); void _move_cursor_page_up(bool p_select); void _move_cursor_page_down(bool p_select); - void _backspace(bool p_word = false, bool p_all_to_left = false); + void _do_backspace(bool p_word = false, bool p_all_to_left = false); void _delete(bool p_word = false, bool p_all_to_right = false); - void _delete_selection(); void _move_cursor_document_start(bool p_select); void _move_cursor_document_end(bool p_select); void _handle_unicode_character(uint32_t unicode, bool p_had_selection); @@ -522,6 +513,8 @@ public: void set_gutter_overwritable(int p_gutter, bool p_overwritable); bool is_gutter_overwritable(int p_gutter) const; + void merge_gutters(int p_from_line, int p_to_line); + void set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback); // Line gutters. @@ -635,12 +628,9 @@ public: bool has_ime_text() const; void set_line(int line, String new_text); int get_row_height() const; - void backspace_at_cursor(); - void indent_selected_lines_left(); - void indent_selected_lines_right(); int get_indent_level(int p_line) const; - bool is_line_comment(int p_line) const; + int get_first_non_whitespace_column(int p_line) const; inline void set_scroll_pass_end_of_file(bool p_enabled) { scroll_past_end_of_file_enabled = p_enabled; @@ -653,7 +643,6 @@ public: brace_matching_enabled = p_enabled; update(); } - void set_auto_indent(bool p_auto_indent); void center_viewport_to_cursor(); @@ -699,6 +688,9 @@ public: void clear(); + void delete_selection(); + + virtual void backspace(); void cut(); void copy(); void paste(); @@ -730,10 +722,8 @@ public: void redo(); void clear_undo_history(); - void set_indent_using_spaces(const bool p_use_spaces); - bool is_indent_using_spaces() const; - void set_indent_size(const int p_size); - int get_indent_size(); + void set_tab_size(const int p_size); + int get_tab_size() const; void set_draw_tabs(bool p_draw); bool is_drawing_tabs() const; void set_draw_spaces(bool p_draw); @@ -744,9 +734,6 @@ public: void set_insert_mode(bool p_enabled); bool is_insert_mode() const; - void add_keyword(const String &p_keyword); - void clear_keywords(); - double get_v_scroll() const; void set_v_scroll(double p_scroll); diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 46ce9d5ca9..5e5dec3579 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -221,43 +221,87 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu double width_texture = 0.0; double first_section_size = 0.0; double last_section_size = 0.0; - switch (mode) { - case FILL_LEFT_TO_RIGHT: - case FILL_RIGHT_TO_LEFT: { + switch (p_mode) { + case FILL_LEFT_TO_RIGHT: { width_total = dst_rect.size.x; width_texture = texture_size.x; first_section_size = topleft.x; last_section_size = bottomright.x; } break; - case FILL_TOP_TO_BOTTOM: - case FILL_BOTTOM_TO_TOP: { + case FILL_RIGHT_TO_LEFT: { + width_total = dst_rect.size.x; + width_texture = texture_size.x; + // In contrast to `FILL_LEFT_TO_RIGHT`, `first_section_size` and `last_section_size` should switch value. + first_section_size = bottomright.x; + last_section_size = topleft.x; + } break; + case FILL_TOP_TO_BOTTOM: { width_total = dst_rect.size.y; width_texture = texture_size.y; first_section_size = topleft.y; last_section_size = bottomright.y; } break; + case FILL_BOTTOM_TO_TOP: { + width_total = dst_rect.size.y; + width_texture = texture_size.y; + // Similar to `FILL_RIGHT_TO_LEFT`. + first_section_size = bottomright.y; + last_section_size = topleft.y; + } break; case FILL_BILINEAR_LEFT_AND_RIGHT: { - // TODO: Implement + width_total = dst_rect.size.x; + width_texture = texture_size.x; + first_section_size = topleft.x; + last_section_size = bottomright.x; } break; case FILL_BILINEAR_TOP_AND_BOTTOM: { - // TODO: Implement + width_total = dst_rect.size.y; + width_texture = texture_size.y; + first_section_size = topleft.y; + last_section_size = bottomright.y; } break; case FILL_CLOCKWISE: case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: case FILL_COUNTER_CLOCKWISE: { - // Those modes are circular, not relevant for nine patch + // Those modes are circular, not relevant for nine patch. } break; + case FILL_MODE_MAX: + break; } double width_filled = width_total * p_ratio; double middle_section_size = MAX(0.0, width_texture - first_section_size - last_section_size); - middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size))); - last_section_size = MAX(0.0, last_section_size - (width_total - width_filled)); - first_section_size = MIN(first_section_size, width_filled); - width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size); + // Maximum middle texture size. + double max_middle_texture_size = middle_section_size; + + // Maximum real middle texture size. + double max_middle_real_size = MAX(0.0, width_total - (first_section_size + last_section_size)); + + switch (p_mode) { + case FILL_BILINEAR_LEFT_AND_RIGHT: + case FILL_BILINEAR_TOP_AND_BOTTOM: { + last_section_size = MAX(0.0, last_section_size - (width_total - width_filled) * 0.5); + first_section_size = MAX(0.0, first_section_size - (width_total - width_filled) * 0.5); + + // When `width_filled` increases, `middle_section_size` only increases when either of `first_section_size` and `last_section_size` is zero. + // Also, it should always be smaller than or equal to `(width_total - (first_section_size + last_section_size))`. + double real_middle_size = width_filled - first_section_size - last_section_size; + middle_section_size *= MIN(max_middle_real_size, real_middle_size) / max_middle_real_size; + + width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size); + } break; + case FILL_MODE_MAX: + break; + default: { + middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size))); + last_section_size = MAX(0.0, last_section_size - (width_total - width_filled)); + first_section_size = MIN(first_section_size, width_filled); + width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size); + } + } - switch (mode) { + switch (p_mode) { case FILL_LEFT_TO_RIGHT: { src_rect.size.x = width_texture; dst_rect.size.x = width_filled; @@ -287,16 +331,32 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu bottomright.y = first_section_size; } break; case FILL_BILINEAR_LEFT_AND_RIGHT: { - // TODO: Implement + double center_mapped_from_real_width = (width_total * 0.5 - topleft.x) / max_middle_real_size * max_middle_texture_size + topleft.x; + double drift_from_unscaled_center = (src_rect.size.x * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.x - topleft.x); + src_rect.position.x += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5; + src_rect.size.x = width_texture; + dst_rect.position.x += (width_total - width_filled) * 0.5; + dst_rect.size.x = width_filled; + topleft.x = first_section_size; + bottomright.x = last_section_size; } break; case FILL_BILINEAR_TOP_AND_BOTTOM: { - // TODO: Implement + double center_mapped_from_real_width = (width_total * 0.5 - topleft.y) / max_middle_real_size * max_middle_texture_size + topleft.y; + double drift_from_unscaled_center = (src_rect.size.y * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.y - topleft.y); + src_rect.position.y += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5; + src_rect.size.y = width_texture; + dst_rect.position.y += (width_total - width_filled) * 0.5; + dst_rect.size.y = width_filled; + topleft.y = first_section_size; + bottomright.y = last_section_size; } break; case FILL_CLOCKWISE: case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: case FILL_COUNTER_CLOCKWISE: { - // Those modes are circular, not relevant for nine patch + // Those modes are circular, not relevant for nine patch. } break; + case FILL_MODE_MAX: + break; } } @@ -310,19 +370,34 @@ void TextureProgressBar::_notification(int p_what) { const float corners[12] = { -0.125, -0.375, -0.625, -0.875, 0.125, 0.375, 0.625, 0.875, 1.125, 1.375, 1.625, 1.875 }; switch (p_what) { case NOTIFICATION_DRAW: { - if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP)) { + if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP || mode == FILL_BILINEAR_LEFT_AND_RIGHT || mode == FILL_BILINEAR_TOP_AND_BOTTOM)) { if (under.is_valid()) { - draw_nine_patch_stretched(under, FILL_LEFT_TO_RIGHT, 1.0, tint_under); + draw_nine_patch_stretched(under, mode, 1.0, tint_under); } if (progress.is_valid()) { draw_nine_patch_stretched(progress, mode, get_as_ratio(), tint_progress); } if (over.is_valid()) { - draw_nine_patch_stretched(over, FILL_LEFT_TO_RIGHT, 1.0, tint_over); + draw_nine_patch_stretched(over, mode, 1.0, tint_over); } } else { if (under.is_valid()) { - draw_texture(under, Point2(), tint_under); + switch (mode) { + case FILL_CLOCKWISE: + case FILL_COUNTER_CLOCKWISE: + case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: { + if (nine_patch_stretch) { + Rect2 region = Rect2(Point2(), get_size()); + draw_texture_rect(under, region, false, tint_under); + } else { + draw_texture(under, Point2(), tint_under); + } + } break; + case FILL_MODE_MAX: + break; + default: + draw_texture(under, Point2(), tint_under); + } } if (progress.is_valid()) { Size2 s = progress->get_size(); @@ -353,7 +428,7 @@ void TextureProgressBar::_notification(int p_what) { float val = get_as_ratio() * rad_max_degrees / 360; if (val == 1) { Rect2 region = Rect2(Point2(), s); - draw_texture_rect_region(progress, region, region, tint_progress); + draw_texture_rect(progress, region, false, tint_progress); } else if (val != 0) { Array pts; float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1; @@ -416,12 +491,29 @@ void TextureProgressBar::_notification(int p_what) { Rect2 region = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio())); draw_texture_rect_region(progress, region, region, tint_progress); } break; + case FILL_MODE_MAX: + break; default: draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress); } } if (over.is_valid()) { - draw_texture(over, Point2(), tint_over); + switch (mode) { + case FILL_CLOCKWISE: + case FILL_COUNTER_CLOCKWISE: + case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: { + if (nine_patch_stretch) { + Rect2 region = Rect2(Point2(), get_size()); + draw_texture_rect(over, region, false, tint_over); + } else { + draw_texture(over, Point2(), tint_over); + } + } break; + case FILL_MODE_MAX: + break; + default: + draw_texture(over, Point2(), tint_over); + } } } } break; @@ -429,7 +521,7 @@ void TextureProgressBar::_notification(int p_what) { } void TextureProgressBar::set_fill_mode(int p_fill) { - ERR_FAIL_INDEX(p_fill, 9); + ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX); mode = (FillMode)p_fill; update(); } @@ -512,7 +604,7 @@ void TextureProgressBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_under_texture", "get_under_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_over_texture", "get_over_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_progress_texture", "get_progress_texture"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom), Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode"); ADD_GROUP("Tint", "tint_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over"), "set_tint_over", "get_tint_over"); diff --git a/scene/gui/texture_progress_bar.h b/scene/gui/texture_progress_bar.h index a3883a7017..d147c43a26 100644 --- a/scene/gui/texture_progress_bar.h +++ b/scene/gui/texture_progress_bar.h @@ -54,7 +54,8 @@ public: FILL_COUNTER_CLOCKWISE, FILL_BILINEAR_LEFT_AND_RIGHT, FILL_BILINEAR_TOP_AND_BOTTOM, - FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE + FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE, + FILL_MODE_MAX, }; void set_fill_mode(int p_fill); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 26d881955b..aac15cd9a5 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -489,11 +489,11 @@ TreeItem *TreeItem::create_child(int p_idx) { return ti; } -Tree *TreeItem::get_tree() { +Tree *TreeItem::get_tree() const { return tree; } -TreeItem *TreeItem::get_next() { +TreeItem *TreeItem::get_next() const { return next; } @@ -516,11 +516,11 @@ TreeItem *TreeItem::get_prev() { return prev; } -TreeItem *TreeItem::get_parent() { +TreeItem *TreeItem::get_parent() const { return parent; } -TreeItem *TreeItem::get_first_child() { +TreeItem *TreeItem::get_first_child() const { return first_child; } @@ -953,6 +953,53 @@ bool TreeItem::is_folding_disabled() const { return disable_folding; } +Size2 TreeItem::get_minimum_size(int p_column) { + ERR_FAIL_INDEX_V(p_column, cells.size(), Size2()); + Tree *tree = get_tree(); + ERR_FAIL_COND_V(!tree, Size2()); + + Size2 size; + + // 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); + } + Size2 text_size = cell.text_buf->get_size(); + size.width += text_size.width; + size.height = MAX(size.height, text_size.height); + } + + // Icon. + 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); + } + + // 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; + } + + return size; +} + Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (p_argcount < 1) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; @@ -1846,78 +1893,76 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int prev_hl_ofs = base_ofs; while (c) { - if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) { - int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); - int parent_ofs = p_pos.x + cache.item_margin; - Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; + if (htotal >= 0) { + int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c); - if (c->get_first_child() != nullptr) { - root_pos -= Point2i(cache.arrow->get_width(), 0); - } + // Draw relationship lines. + if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) { + int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); + int parent_ofs = p_pos.x + cache.item_margin; + Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; + + if (c->get_first_child() != nullptr) { + root_pos -= Point2i(cache.arrow->get_width(), 0); + } - float line_width = cache.relationship_line_width; - float parent_line_width = cache.parent_hl_line_width; - float children_line_width = cache.children_hl_line_width; + float line_width = cache.relationship_line_width; + float parent_line_width = cache.parent_hl_line_width; + float children_line_width = cache.children_hl_line_width; #ifdef TOOLS_ENABLED - line_width *= Math::round(EDSCALE); - parent_line_width *= Math::round(EDSCALE); - children_line_width *= Math::round(EDSCALE); + line_width *= Math::round(EDSCALE); + parent_line_width *= Math::round(EDSCALE); + children_line_width *= Math::round(EDSCALE); #endif - Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs; + Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs; - int more_prev_ofs = 0; + int more_prev_ofs = 0; - if (root_pos.y + line_width >= 0) { - if (rtl) { - root_pos.x = get_size().width - root_pos.x; - parent_pos.x = get_size().width - parent_pos.x; - } + if (root_pos.y + line_width >= 0) { + if (rtl) { + root_pos.x = get_size().width - root_pos.x; + parent_pos.x = get_size().width - parent_pos.x; + } - // Order of parts on this bend: the horizontal line first, then the vertical line. - if (_is_branch_selected(c)) { - // If this item or one of its children is selected, we draw the line using parent highlight style. - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width); - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); - - more_prev_ofs = cache.parent_hl_line_margin; - prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); - } else if (p_item->is_selected(0)) { - // If parent item is selected (but this item is not), we draw the line using children highlight style. - // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. - if (_is_sibling_branch_selected(c)) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + // Order of parts on this bend: the horizontal line first, then the vertical line. + if (_is_branch_selected(c)) { + // If this item or one of its children is selected, we draw the line using parent highlight style. + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width); RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + more_prev_ofs = cache.parent_hl_line_margin; prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); + } else if (p_item->is_selected(0)) { + // If parent item is selected (but this item is not), we draw the line using children highlight style. + // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. + if (_is_sibling_branch_selected(c)) { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + + prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); + } else { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width); + } } else { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width); - } - } else { - // If nothing of the above is true, we draw the line using normal style. - // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. - if (_is_sibling_branch_selected(c)) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width); - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + // If nothing of the above is true, we draw the line using normal style. + // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. + if (_is_sibling_branch_selected(c)) { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); - prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); - } else { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width); - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width); + prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); + } else { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width); + } } } - } - if (htotal < 0) { - return -1; + prev_ofs = root_pos.y + more_prev_ofs; } - prev_ofs = root_pos.y + more_prev_ofs; - } - - if (htotal >= 0) { - int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c); if (child_h < 0) { if (cache.draw_relationship_lines == 0) { @@ -2092,7 +2137,7 @@ void Tree::_range_click_timeout() { click_handled = false; Ref<InputEventMouseButton> mb; - mb.instance(); + mb.instantiate(); propagate_mouse_activated = false; // done from outside, so signal handler can't clear the tree in the middle of emit (which is a common case) blocked++; @@ -3161,6 +3206,8 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { } } break; + default: + break; } } @@ -3276,7 +3323,7 @@ Size2 Tree::get_internal_min_size() const { size.height += get_item_height(root); } for (int i = 0; i < columns.size(); i++) { - size.width += columns[i].min_width; + size.width += get_column_minimum_width(i); } return size; @@ -3300,26 +3347,38 @@ void Tree::update_scrollbars() { h_scroll->set_begin(Point2(0, size.height - hmin.height)); h_scroll->set_end(Point2(size.width - vmin.width, size.height)); - Size2 min = get_internal_min_size(); + Size2 internal_min_size = get_internal_min_size(); - if (min.height < size.height - hmin.height) { - v_scroll->hide(); - cache.offset.y = 0; - } else { + bool display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) > size.height; + bool display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) > size.width; + for (int i = 0; i < 2; i++) { + // Check twice, as both values are dependent on each other. + if (display_hscroll) { + display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) + hmin.height > size.height; + } + if (display_vscroll) { + display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) + vmin.width > size.width; + } + } + + if (display_vscroll) { v_scroll->show(); - v_scroll->set_max(min.height); + v_scroll->set_max(internal_min_size.height); v_scroll->set_page(size.height - hmin.height - tbh); cache.offset.y = v_scroll->get_value(); + } else { + v_scroll->hide(); + cache.offset.y = 0; } - if (min.width < size.width - vmin.width) { - h_scroll->hide(); - cache.offset.x = 0; - } else { + if (display_hscroll) { h_scroll->show(); - h_scroll->set_max(min.width); + h_scroll->set_max(internal_min_size.width); h_scroll->set_page(size.width - vmin.width); cache.offset.x = h_scroll->get_value(); + } else { + h_scroll->hide(); + cache.offset.x = 0; } } @@ -3445,7 +3504,7 @@ void Tree::_notification(int p_what) { draw_ofs.y += tbh; draw_size.y -= tbh; - if (root) { + if (root && get_size().x > 0 && get_size().y > 0) { draw_item(Point2(), draw_ofs, draw_size, root); } @@ -3513,7 +3572,17 @@ void Tree::_update_all() { } Size2 Tree::get_minimum_size() const { - return Size2(1, 1); + if (h_scroll_enabled && v_scroll_enabled) { + return Size2(); + } else { + Vector2 min_size = get_internal_min_size(); + Ref<StyleBox> bg = cache.bg; + if (bg.is_valid()) { + min_size.x += bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); + min_size.y += bg->get_margin(SIDE_TOP) + bg->get_margin(SIDE_BOTTOM); + } + return Vector2(h_scroll_enabled ? 0 : min_size.x, v_scroll_enabled ? 0 : min_size.y); + } } TreeItem *Tree::create_item(TreeItem *p_parent, int p_idx) { @@ -3541,11 +3610,11 @@ TreeItem *Tree::create_item(TreeItem *p_parent, int p_idx) { return ti; } -TreeItem *Tree::get_root() { +TreeItem *Tree::get_root() const { return root; } -TreeItem *Tree::get_last_item() { +TreeItem *Tree::get_last_item() const { TreeItem *last = root; while (last) { @@ -3675,13 +3744,13 @@ bool Tree::is_root_hidden() const { return hide_root; } -void Tree::set_column_min_width(int p_column, int p_min_width) { +void Tree::set_column_custom_minimum_width(int p_column, int p_min_width) { ERR_FAIL_INDEX(p_column, columns.size()); - if (p_min_width < 1) { + if (p_min_width < 0) { return; } - columns.write[p_column].min_width = p_min_width; + columns.write[p_column].custom_min_width = p_min_width; update(); } @@ -3748,44 +3817,82 @@ TreeItem *Tree::get_next_selected(TreeItem *p_item) { return nullptr; } -int Tree::get_column_width(int p_column) const { +int Tree::get_column_minimum_width(int p_column) const { ERR_FAIL_INDEX_V(p_column, columns.size(), -1); - if (!columns[p_column].expand) { - return columns[p_column].min_width; - } + if (columns[p_column].custom_min_width != 0) { + return columns[p_column].custom_min_width; + } else { + int depth = 0; + int min_width = 0; + TreeItem *next; + for (TreeItem *item = get_root(); item; item = next) { + next = item->get_next_visible(); + // Compute the depth in tree. + if (next && p_column == 0) { + if (next->get_parent() == item) { + depth += 1; + } else { + TreeItem *common_parent = item->get_parent(); + while (common_parent != next->get_parent()) { + common_parent = common_parent->get_parent(); + depth -= 1; + } + } + } - int expand_area = get_size().width; + // Get the item minimum size. + Size2 item_size = item->get_minimum_size(p_column); + if (p_column == 0) { + item_size.width += cache.item_margin * depth; + } + min_width = MAX(min_width, item_size.width); + } + return min_width; + } +} - Ref<StyleBox> bg = cache.bg; +int Tree::get_column_width(int p_column) const { + ERR_FAIL_INDEX_V(p_column, columns.size(), -1); - if (bg.is_valid()) { - expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); - } + if (columns[p_column].expand) { + int expand_area = get_size().width; - if (v_scroll->is_visible_in_tree()) { - expand_area -= v_scroll->get_combined_minimum_size().width; - } + Ref<StyleBox> bg = cache.bg; - int expanding_columns = 0; - int expanding_total = 0; + if (bg.is_valid()) { + expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); + } - for (int i = 0; i < columns.size(); i++) { - if (!columns[i].expand) { - expand_area -= columns[i].min_width; - } else { - expanding_total += columns[i].min_width; - expanding_columns++; + if (v_scroll->is_visible_in_tree()) { + expand_area -= v_scroll->get_combined_minimum_size().width; } - } - if (expand_area < expanding_total) { - return columns[p_column].min_width; - } + int expanding_columns = 0; + int expanding_total = 0; - ERR_FAIL_COND_V(expanding_columns == 0, -1); // shouldn't happen + for (int i = 0; i < columns.size(); i++) { + if (!columns[i].expand) { + expand_area -= get_column_minimum_width(i); + } else { + expanding_total += get_column_minimum_width(i); + expanding_columns++; + } + } - return expand_area * columns[p_column].min_width / expanding_total; + if (expand_area < expanding_total) { + return get_column_minimum_width(p_column); + } + + ERR_FAIL_COND_V(expanding_columns == 0, -1); // shouldn't happen + if (expanding_total == 0) { + return 0; + } else { + return expand_area * get_column_minimum_width(p_column) / expanding_total; + } + } else { + return get_column_minimum_width(p_column); + } } void Tree::propagate_set_columns(TreeItem *p_item) { @@ -4047,6 +4154,24 @@ void Tree::scroll_to_item(TreeItem *p_item) { } } +void Tree::set_h_scroll_enabled(bool p_enable) { + h_scroll_enabled = p_enable; + minimum_size_changed(); +} + +bool Tree::is_h_scroll_enabled() const { + return h_scroll_enabled; +} + +void Tree::set_v_scroll_enabled(bool p_enable) { + v_scroll_enabled = p_enable; + minimum_size_changed(); +} + +bool Tree::is_v_scroll_enabled() const { + return v_scroll_enabled; +} + TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) { TreeItem *from = p_at; TreeItem *loop = nullptr; // Safe-guard against infinite loop. @@ -4422,7 +4547,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("create_item", "parent", "idx"), &Tree::_create_item, DEFVAL(Variant()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_root"), &Tree::get_root); - ClassDB::bind_method(D_METHOD("set_column_min_width", "column", "min_width"), &Tree::set_column_min_width); + ClassDB::bind_method(D_METHOD("set_column_custom_minimum_width", "column", "min_width"), &Tree::set_column_custom_minimum_width); ClassDB::bind_method(D_METHOD("set_column_expand", "column", "expand"), &Tree::set_column_expand); ClassDB::bind_method(D_METHOD("get_column_width", "column"), &Tree::get_column_width); @@ -4468,6 +4593,12 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_scroll"), &Tree::get_scroll); ClassDB::bind_method(D_METHOD("scroll_to_item", "item"), &Tree::_scroll_to_item); + ClassDB::bind_method(D_METHOD("set_h_scroll_enabled", "h_scroll"), &Tree::set_h_scroll_enabled); + ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &Tree::is_h_scroll_enabled); + + ClassDB::bind_method(D_METHOD("set_v_scroll_enabled", "h_scroll"), &Tree::set_v_scroll_enabled); + ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &Tree::is_v_scroll_enabled); + ClassDB::bind_method(D_METHOD("set_hide_folding", "hide"), &Tree::set_hide_folding); ClassDB::bind_method(D_METHOD("is_folding_hidden"), &Tree::is_folding_hidden); @@ -4487,6 +4618,8 @@ void Tree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden"); ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags"); ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_h_scroll_enabled", "is_h_scroll_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_v_scroll_enabled", "is_v_scroll_enabled"); ADD_SIGNAL(MethodInfo("item_selected")); ADD_SIGNAL(MethodInfo("cell_selected")); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 0571a605a5..fd5fcd7838 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -115,7 +115,7 @@ private: Ref<Font> custom_font; Cell() { - text_buf.instance(); + text_buf.instantiate(); } Size2 get_icon_size() const; @@ -315,16 +315,18 @@ public: void set_disable_folding(bool p_disable); bool is_folding_disabled() const; + Size2 get_minimum_size(int p_column); + /* Item manipulation */ TreeItem *create_child(int p_idx = -1); - Tree *get_tree(); + Tree *get_tree() const; TreeItem *get_prev(); - TreeItem *get_next(); - TreeItem *get_parent(); - TreeItem *get_first_child(); + TreeItem *get_next() const; + TreeItem *get_parent() const; + TreeItem *get_first_child() const; TreeItem *get_prev_visible(bool p_wrap = false); TreeItem *get_next_visible(bool p_wrap = false); @@ -408,7 +410,7 @@ private: int drop_mode_flags = 0; struct ColumnInfo { - int min_width = 1; + int custom_min_width = 0; bool expand = true; String title; Ref<TextLine> text_buf; @@ -416,7 +418,7 @@ private: String language; Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED; ColumnInfo() { - text_buf.instance(); + text_buf.instantiate(); } }; @@ -545,6 +547,8 @@ private: void _scroll_moved(float p_value); HScrollBar *h_scroll; VScrollBar *v_scroll; + bool h_scroll_enabled = true; + bool v_scroll_enabled = true; Size2 get_internal_min_size() const; void update_cache(); @@ -623,11 +627,12 @@ public: void clear(); TreeItem *create_item(TreeItem *p_parent = nullptr, int p_idx = -1); - TreeItem *get_root(); - TreeItem *get_last_item(); + TreeItem *get_root() const; + TreeItem *get_last_item() const; - void set_column_min_width(int p_column, int p_min_width); + void set_column_custom_minimum_width(int p_column, int p_min_width); void set_column_expand(int p_column, bool p_expand); + int get_column_minimum_width(int p_column) const; int get_column_width(int p_column) const; void set_hide_root(bool p_enabled); @@ -679,6 +684,10 @@ public: Point2 get_scroll() const; void scroll_to_item(TreeItem *p_item); + void set_h_scroll_enabled(bool p_enable); + bool is_h_scroll_enabled() const; + void set_v_scroll_enabled(bool p_enable); + bool is_v_scroll_enabled() const; void set_cursor_can_exit_tree(bool p_enable); |