diff options
Diffstat (limited to 'scene/gui/text_edit.cpp')
-rw-r--r-- | scene/gui/text_edit.cpp | 392 |
1 files changed, 319 insertions, 73 deletions
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 74268707ff..817a4453a8 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -39,6 +39,7 @@ #include "core/os/os.h" #include "core/string/string_builder.h" #include "core/string/translation.h" +#include "label.h" #include "scene/main/window.h" @@ -154,30 +155,30 @@ _FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { void TextEdit::Text::_calculate_line_height() { int height = 0; - for (int i = 0; i < text.size(); i++) { + for (const Line &l : text) { // Found another line with the same height...nothing to update. - if (text[i].height == line_height) { + if (l.height == line_height) { height = line_height; break; } - height = MAX(height, text[i].height); + height = MAX(height, l.height); } line_height = height; } void TextEdit::Text::_calculate_max_line_width() { int width = 0; - for (int i = 0; i < text.size(); i++) { - if (is_hidden(i)) { + for (const Line &l : text) { + if (l.hidden) { continue; } // Found another line with the same width...nothing to update. - if (text[i].width == max_width) { + if (l.width == max_width) { width = max_width; break; } - width = MAX(width, text[i].width); + width = MAX(width, l.width); } max_width = width; } @@ -215,7 +216,7 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ // Update height. const int old_height = text.write[p_line].height; const int wrap_amount = get_line_wrap_amount(p_line); - int height = font->get_height(font_size); + int height = font_height; for (int i = 0; i <= wrap_amount; i++) { height = MAX(height, text[p_line].data_buf->get_line_size(i).y); } @@ -266,6 +267,13 @@ void TextEdit::Text::invalidate_all() { return; } + max_width = -1; + line_height = -1; + + if (!font.is_null() && font_size > 0) { + font_height = font->get_height(font_size); + } + for (int i = 0; i < text.size(); i++) { invalidate_cache(i); } @@ -274,7 +282,15 @@ void TextEdit::Text::invalidate_all() { void TextEdit::Text::clear() { text.clear(); - insert(0, "", Array()); + + max_width = -1; + line_height = -1; + + Line line; + line.gutters.resize(gutter_count); + line.data = ""; + text.insert(0, line); + invalidate_cache(0); } int TextEdit::Text::get_max_width() const { @@ -289,30 +305,64 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o invalidate_cache(p_line); } -void TextEdit::Text::insert(int p_at, const String &p_text, const Array &p_bidi_override) { - Line line; - line.gutters.resize(gutter_count); - line.hidden = false; - line.data = p_text; - line.bidi_override = p_bidi_override; - text.insert(p_at, line); +void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) { + int new_line_count = p_text.size() - 1; + if (new_line_count > 0) { + text.resize(text.size() + new_line_count); + for (int i = (text.size() - 1); i > p_at; i--) { + if ((i - new_line_count) <= 0) { + break; + } + text.write[i] = text[i - new_line_count]; + } + } - invalidate_cache(p_at); + for (int i = 0; i < p_text.size(); i++) { + if (i == 0) { + set(p_at + i, p_text[i], p_bidi_override[i]); + continue; + } + Line line; + line.gutters.resize(gutter_count); + line.data = p_text[i]; + line.bidi_override = p_bidi_override[i]; + text.write[p_at + i] = line; + invalidate_cache(p_at + i); + } } -void TextEdit::Text::remove(int p_at) { - int height = text[p_at].height; - int width = text[p_at].width; +void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { + if (p_from_line == p_to_line) { + return; + } + + bool dirty_height = false; + bool dirty_width = false; + for (int i = p_from_line; i < p_to_line; i++) { + if (!dirty_height && text[i].height == line_height) { + dirty_height = true; + } - text.remove(p_at); + if (!dirty_width && text[i].width == max_width) { + dirty_width = true; + } - // If this is the tallest line, we need to get the next tallest. - if (height == line_height) { + if (dirty_height && dirty_width) { + break; + } + } + + int diff = (p_to_line - p_from_line); + for (int i = p_to_line; i < text.size() - 1; i++) { + text.write[(i - diff) + 1] = text[i + 1]; + } + text.resize(text.size() - diff); + + if (dirty_height) { _calculate_line_height(); } - // If this is the longest line, we need to get the next longest. - if (width == max_width) { + if (dirty_width) { _calculate_max_line_width(); } } @@ -330,7 +380,7 @@ void TextEdit::Text::add_gutter(int p_at) { void TextEdit::Text::remove_gutter(int p_gutter) { for (int i = 0; i < text.size(); i++) { - text.write[i].gutters.remove(p_gutter); + text.write[i].gutters.remove_at(p_gutter); } gutter_count--; } @@ -600,7 +650,7 @@ void TextEdit::_notification(int p_what) { String highlighted_text = get_selected_text(); // Check if highlighted words contain only whitespaces (tabs or spaces). - bool only_whitespaces_highlighted = highlighted_text.strip_edges() == String(); + bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty(); const int caret_wrap_index = get_caret_wrap_index(); @@ -608,7 +658,7 @@ void TextEdit::_notification(int p_what) { int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); draw_amount += get_line_wrap_count(first_visible_line + 1); - // minimap + // Draw minimap. if (draw_minimap) { int minimap_visible_lines = get_minimap_visible_lines(); int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); @@ -788,8 +838,9 @@ void TextEdit::_notification(int p_what) { bottom_limit_y -= style_normal->get_margin(SIDE_BOTTOM); } - // draw main text + // Draw main text. caret.visible = false; + line_drawing_cache.clear(); int row_height = get_line_height(); int line = first_visible_line; for (int i = 0; i < draw_amount; i++) { @@ -810,6 +861,8 @@ void TextEdit::_notification(int p_what) { continue; } + LineDrawingCache cache_entry; + Dictionary color_map = _get_line_syntax_highlighting(line); // Ensure we at least use the font color. @@ -899,6 +952,8 @@ void TextEdit::_notification(int p_what) { if (line_wrap_index == 0) { // Only do these if we are on the first wrapped part of a line. + cache_entry.y_offset = ofs_y; + int gutter_offset = style_normal->get_margin(SIDE_LEFT); for (int g = 0; g < gutters.size(); g++) { const GutterInfo gutter = gutters[g]; @@ -910,7 +965,7 @@ void TextEdit::_notification(int p_what) { switch (gutter.type) { case GUTTER_TYPE_STRING: { const String &text = get_line_gutter_text(line, g); - if (text == "") { + if (text.is_empty()) { break; } @@ -1050,7 +1105,7 @@ void TextEdit::_notification(int p_what) { while (highlighted_word_col != -1) { Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + lookup_symbol_word.length() + start); for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (line_spacing / 2), sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { continue; } @@ -1060,9 +1115,9 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - rect.position.y = TS->shaped_text_get_ascent(rid) + font->get_underline_position(font_size); - rect.size.y = font->get_underline_thickness(font_size); - draw_rect(rect, font_selected_color); + rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(font->get_underline_position(font_size)); + rect.size.y = MAX(1, font->get_underline_thickness(font_size)); + draw_rect(rect, color); } highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1); @@ -1076,6 +1131,10 @@ void TextEdit::_notification(int p_what) { int gl_size = TS->shaped_text_get_glyph_count(rid); ofs_y += ldata->get_line_ascent(line_wrap_index); + + int first_visible_char = TS->shaped_text_get_range(rid).y; + int last_visible_char = TS->shaped_text_get_range(rid).x; + int char_ofs = 0; if (outline_size > 0 && outline_color.a > 0) { for (int j = 0; j < gl_size; j++) { @@ -1143,21 +1202,36 @@ void TextEdit::_notification(int p_what) { } } + bool had_glyphs_drawn = false; for (int k = 0; k < glyphs[j].repeat; k++) { if (!clipped && (char_ofs + char_margin) >= xmargin_beg && (char_ofs + glyphs[j].advance + char_margin) <= xmargin_end) { if (glyphs[j].font_rid != RID()) { TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, current_color); + had_glyphs_drawn = true; } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, current_color); + had_glyphs_drawn = true; } } char_ofs += glyphs[j].advance; } + + if (had_glyphs_drawn) { + if (first_visible_char > glyphs[j].start) { + first_visible_char = glyphs[j].start; + } else if (last_visible_char < glyphs[j].end) { + last_visible_char = glyphs[j].end; + } + } + if ((char_ofs + char_margin) >= xmargin_end) { break; } } + cache_entry.first_visible_chars.push_back(first_visible_char); + cache_entry.last_visible_chars.push_back(last_visible_char); + // is_line_folded if (line_wrap_index == line_wrap_amount && line < text.size() - 1 && _is_line_hidden(line + 1)) { int xofs = char_ofs + char_margin + ofs_x + (folded_eol_icon->get_width() / 2); @@ -1200,7 +1274,7 @@ void TextEdit::_notification(int p_what) { if (caret.draw_pos.x >= xmargin_beg && caret.draw_pos.x < xmargin_end) { caret.visible = true; - if (draw_caret) { + if (draw_caret || drag_caret_force_displayed) { if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) { //Block or underline caret, draw trailing carets at full height. int h = font->get_height(font_size); @@ -1308,6 +1382,8 @@ void TextEdit::_notification(int p_what) { } } } + + line_drawing_cache[line] = cache_entry; } if (has_focus()) { @@ -1365,7 +1441,7 @@ void TextEdit::_notification(int p_what) { DisplayServer::get_singleton()->virtual_keyboard_hide(); } - if (deselect_on_focus_loss_enabled) { + if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { deselect(); } } break; @@ -1385,6 +1461,30 @@ void TextEdit::_notification(int p_what) { update(); } } break; + case Control::NOTIFICATION_DRAG_BEGIN: { + selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; + drag_action = true; + dragging_minimap = false; + dragging_selection = false; + can_drag_minimap = false; + click_select_held->stop(); + } break; + case Control::NOTIFICATION_DRAG_END: { + if (is_drag_successful()) { + if (selection.drag_attempt) { + selection.drag_attempt = false; + if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { + delete_selection(); + } else if (deselect_on_focus_loss_enabled) { + deselect(); + } + } + } else { + selection.drag_attempt = false; + } + drag_action = false; + drag_caret_force_displayed = false; + } break; } } @@ -1469,6 +1569,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { set_caret_line(row, false, false); set_caret_column(col); + selection.drag_attempt = false; if (mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) { if (!selection.active) { @@ -1512,6 +1613,9 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { update(); } + } else if (is_mouse_over_selection()) { + selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; + selection.drag_attempt = true; } else { selection.active = false; selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER; @@ -1525,6 +1629,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance) { // Triple-click select line. selection.selecting_mode = SelectionMode::SELECTION_MODE_LINE; + selection.drag_attempt = false; _update_selection_mode_line(); last_dblclk = 0; } else if (mb->is_double_click() && text[caret.line].length()) { @@ -1568,17 +1673,23 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } _generate_context_menu(); - menu->set_position(get_screen_transform().xform(mpos)); - menu->set_size(Vector2(1, 1)); + menu->set_position(get_screen_position() + mpos); + menu->reset_size(); menu->popup(); grab_focus(); } } else { if (mb->get_button_index() == MouseButton::LEFT) { + if (selection.drag_attempt && is_mouse_over_selection()) { + selection.active = false; + } dragging_minimap = false; dragging_selection = false; can_drag_minimap = false; click_select_held->stop(); + if (!drag_action) { + selection.drag_attempt = false; + } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } @@ -1663,6 +1774,14 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { hovered_gutter = current_hovered_gutter; update(); } + + if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) { + drag_caret_force_displayed = true; + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + set_caret_line(pos.y, false); + set_caret_column(pos.x); + dragging_selection = true; + } } if (draw_minimap && !dragging_selection) { @@ -1801,8 +1920,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (context_menu_enabled) { _generate_context_menu(); adjust_viewport_to_caret(); - menu->set_position(get_screen_transform().xform(get_caret_draw_pos())); - menu->set_size(Vector2(1, 1)); + menu->set_position(get_screen_position() + get_caret_draw_pos()); + menu->reset_size(); menu->popup(); menu->grab_focus(); } @@ -2358,7 +2477,7 @@ void TextEdit::_update_caches() { } else { dir = (TextServer::Direction)text_direction; } - text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); text.set_font_features(opentype_features); text.set_draw_control_chars(draw_control_chars); text.set_font(font); @@ -2380,6 +2499,75 @@ bool TextEdit::is_text_field() const { return true; } +Variant TextEdit::get_drag_data(const Point2 &p_point) { + if (selection.active && selection.drag_attempt) { + String t = get_selected_text(); + Label *l = memnew(Label); + l->set_text(t); + set_drag_preview(l); + return t; + } + + return Variant(); +} + +bool TextEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + bool drop_override = Control::can_drop_data(p_point, p_data); // In case user wants to drop custom data. + if (drop_override) { + return drop_override; + } + + return is_editable() && p_data.get_type() == Variant::STRING; +} + +void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { + Control::drop_data(p_point, p_data); + + if (p_data.get_type() == Variant::STRING && is_editable()) { + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + int caret_row_tmp = pos.y; + int caret_column_tmp = pos.x; + if (selection.drag_attempt) { + selection.drag_attempt = false; + if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CTRL))) { + begin_complex_operation(); + if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) { + if (caret_row_tmp > selection.to_line) { + caret_row_tmp = caret_row_tmp - (selection.to_line - selection.from_line); + } else if (caret_row_tmp == selection.to_line && caret_column_tmp >= selection.to_column) { + caret_column_tmp = caret_column_tmp - (selection.to_column - selection.from_column); + } + delete_selection(); + } else { + deselect(); + } + + set_caret_line(caret_row_tmp, true, false); + set_caret_column(caret_column_tmp); + insert_text_at_caret(p_data); + end_complex_operation(); + } + } else if (is_mouse_over_selection()) { + caret_row_tmp = selection.from_line; + caret_column_tmp = selection.from_column; + set_caret_line(caret_row_tmp, true, false); + set_caret_column(caret_column_tmp); + insert_text_at_caret(p_data); + grab_focus(); + } else { + deselect(); + set_caret_line(caret_row_tmp, true, false); + set_caret_column(caret_column_tmp); + insert_text_at_caret(p_data); + grab_focus(); + } + + if (caret_row_tmp != caret.line || caret_column_tmp != caret.column) { + select(caret_row_tmp, caret_column_tmp, caret.line, caret.column); + } + } +} + Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { Point2i pos = get_line_column_at_pos(p_pos); int row = pos.y; @@ -2472,7 +2660,7 @@ void TextEdit::set_text_direction(Control::TextDirection p_text_direction) { } else { dir = (TextServer::Direction)text_direction; } - text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); text.invalidate_all(); if (menu_dir) { @@ -2523,7 +2711,7 @@ void TextEdit::set_language(const String &p_language) { } else { dir = (TextServer::Direction)text_direction; } - text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); text.invalidate_all(); update(); } @@ -3457,6 +3645,49 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ return Point2i(col, row); } +Point2i TextEdit::get_pos_at_line_column(int p_line, int p_column) const { + Rect2i rect = get_rect_at_line_column(p_line, p_column); + return rect.position + Vector2i(0, get_line_height()); +} + +Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const { + ERR_FAIL_INDEX_V(p_line, text.size(), Rect2i(-1, -1, 0, 0)); + ERR_FAIL_COND_V(p_column < 0, Rect2i(-1, -1, 0, 0)); + ERR_FAIL_COND_V(p_column > text[p_line].length(), Rect2i(-1, -1, 0, 0)); + + if (line_drawing_cache.size() == 0 || !line_drawing_cache.has(p_line)) { + // Line is not in the cache, which means it's outside of the viewing area. + return Rect2i(-1, -1, 0, 0); + } + LineDrawingCache cache_entry = line_drawing_cache[p_line]; + + int wrap_index = get_line_wrap_index_at_column(p_line, p_column); + if (wrap_index >= cache_entry.first_visible_chars.size()) { + // Line seems to be wrapped beyond the viewable area. + return Rect2i(-1, -1, 0, 0); + } + + int first_visible_char = cache_entry.first_visible_chars[wrap_index]; + int last_visible_char = cache_entry.last_visible_chars[wrap_index]; + if (p_column < first_visible_char || p_column > last_visible_char) { + // Character is outside of the viewing area, no point calculating its position. + return Rect2i(-1, -1, 0, 0); + } + + Point2i pos, size; + pos.y = cache_entry.y_offset + get_line_height() * wrap_index; + pos.x = get_total_gutter_width() + style_normal->get_margin(SIDE_LEFT) - get_h_scroll(); + + RID text_rid = text.get_line_data(p_line)->get_line_rid(wrap_index); + Vector2 col_bounds = TS->shaped_text_get_grapheme_bounds(text_rid, p_column); + pos.x += col_bounds.x; + size.x = col_bounds.y - col_bounds.x; + + size.y = get_line_height(); + + return Rect2i(pos, size); +} + int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const { float rows = p_pos.y; rows -= style_normal->get_margin(SIDE_TOP); @@ -3511,6 +3742,21 @@ bool TextEdit::is_dragging_cursor() const { return dragging_selection || dragging_minimap; } +bool TextEdit::is_mouse_over_selection(bool p_edges) const { + if (!has_selection()) { + return false; + } + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + int row = pos.y; + int col = pos.x; + if (p_edges) { + if ((row == selection.from_line && col == selection.from_column) || (row == selection.to_line && col == selection.to_column)) { + return true; + } + } + return (row >= selection.from_line && row <= selection.to_line && (row > selection.from_line || col > selection.from_column) && (row < selection.to_line || col < selection.to_column)); +} + /* Caret */ void TextEdit::set_caret_type(CaretType p_type) { caret_type = p_type; @@ -3897,7 +4143,7 @@ void TextEdit::delete_selection() { update(); } -/* line wrapping. */ +/* Line wrapping. */ void TextEdit::set_line_wrapping_mode(LineWrappingMode p_wrapping_mode) { if (line_wrapping_mode != p_wrapping_mode) { line_wrapping_mode = p_wrapping_mode; @@ -4029,7 +4275,10 @@ double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { return p_line; } - double new_line_scroll_pos = get_visible_line_count_in_range(0, CLAMP(p_line, 0, text.size() - 1)); + double new_line_scroll_pos = 0.0; + if (p_line > 0) { + new_line_scroll_pos = get_visible_line_count_in_range(0, MIN(p_line - 1, text.size() - 1)); + } new_line_scroll_pos += p_wrap_index; return new_line_scroll_pos; } @@ -4093,7 +4342,7 @@ int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) co /* Returns the total number of (lines + wrapped - hidden). */ if (!_is_hiding_enabled() && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { - return p_to_line - p_from_line; + return (p_to_line - p_from_line) + 1; } int total_rows = 0; @@ -4278,7 +4527,7 @@ void TextEdit::add_gutter(int p_at) { void TextEdit::remove_gutter(int p_gutter) { ERR_FAIL_INDEX(p_gutter, gutters.size()); - gutters.remove(p_gutter); + gutters.remove_at(p_gutter); for (int i = 0; i < text.size() + 1; i++) { text.remove_gutter(p_gutter); @@ -4701,9 +4950,13 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_word_at_pos", "position"), &TextEdit::get_word_at_pos); ClassDB::bind_method(D_METHOD("get_line_column_at_pos", "position", "allow_out_of_bounds"), &TextEdit::get_line_column_at_pos, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_pos_at_line_column", "line", "column"), &TextEdit::get_pos_at_line_column); + ClassDB::bind_method(D_METHOD("get_rect_at_line_column", "line", "column"), &TextEdit::get_rect_at_line_column); + ClassDB::bind_method(D_METHOD("get_minimap_line_at_pos", "position"), &TextEdit::get_minimap_line_at_pos); ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor); + ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges"), &TextEdit::is_mouse_over_selection); /* Caret. */ BIND_ENUM_CONSTANT(CARET_TYPE_LINE); @@ -4778,7 +5031,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("deselect"), &TextEdit::deselect); ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection); - /* line wrapping. */ + /* Line wrapping. */ BIND_ENUM_CONSTANT(LINE_WRAPPING_NONE); BIND_ENUM_CONSTANT(LINE_WRAPPING_BOUNDARY); @@ -4795,7 +5048,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_wrapped_text", "line"), &TextEdit::get_line_wrapped_text); /* Viewport. */ - // Scolling. + // Scrolling. ClassDB::bind_method(D_METHOD("set_smooth_scroll_enable", "enable"), &TextEdit::set_smooth_scroll_enabled); ClassDB::bind_method(D_METHOD("is_smooth_scroll_enabled"), &TextEdit::is_smooth_scroll_enabled); @@ -5157,7 +5410,7 @@ void TextEdit::_cut_internal() { set_caret_line(get_caret_line() + 1); } - // Correct the visualy perceived caret column taking care of identation level of the lines. + // Correct the visually perceived caret column taking care of indentation level of the lines. int diff_indent = indent_level - get_indent_level(get_caret_line()); cc += diff_indent; if (diff_indent != 0) { @@ -6088,11 +6341,11 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i ERR_FAIL_COND(p_char < 0); /* STEP 1: Remove \r from source text and separate in substrings. */ - - Vector<String> substrings = p_text.replace("\r", "").split("\n"); + const String text_to_insert = p_text.replace("\r", ""); + Vector<String> substrings = text_to_insert.split("\n"); // Is this just a new empty line? - bool shift_first_line = p_char == 0 && p_text.replace("\r", "") == "\n"; + bool shift_first_line = p_char == 0 && substrings.size() == 2 && text_to_insert == "\n"; /* STEP 2: Add spaces if the char is greater than the end of the line. */ while (p_char > text[p_line].length()) { @@ -6100,24 +6353,19 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i } /* STEP 3: Separate dest string in pre and post text. */ - - String preinsert_text = text[p_line].substr(0, p_char); String postinsert_text = text[p_line].substr(p_char, text[p_line].size()); - for (int j = 0; j < substrings.size(); j++) { - // Insert the substrings. + substrings.write[0] = text[p_line].substr(0, p_char) + substrings[0]; + substrings.write[substrings.size() - 1] += postinsert_text; - if (j == 0) { - text.set(p_line, preinsert_text + substrings[j], structured_text_parser(st_parser, st_args, preinsert_text + substrings[j])); - } else { - text.insert(p_line + j, substrings[j], structured_text_parser(st_parser, st_args, substrings[j])); - } - - if (j == substrings.size() - 1) { - text.set(p_line + j, text[p_line + j] + postinsert_text, structured_text_parser(st_parser, st_args, text[p_line + j] + postinsert_text)); - } + Vector<Array> bidi_override; + bidi_override.resize(substrings.size()); + for (int i = 0; i < substrings.size(); i++) { + bidi_override.write[i] = structured_text_parser(st_parser, st_args, substrings[i]); } + text.insert(p_line, substrings, bidi_override); + if (shift_first_line) { text.move_gutters(p_line, p_line + 1); text.set_hidden(p_line + 1, text.is_hidden(p_line)); @@ -6150,7 +6398,7 @@ String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_lin ERR_FAIL_COND_V(p_to_line < p_from_line, String()); // 'from > to'. ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column < p_from_column, String()); // 'from > to'. - String ret; + StringBuilder ret; for (int i = p_from_line; i <= p_to_line; i++) { int begin = (i == p_from_line) ? p_from_column : 0; @@ -6162,7 +6410,7 @@ String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_lin ret += text[i].substr(begin, end - begin); } - return ret; + return ret.as_string(); } void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -6176,9 +6424,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li String pre_text = text[p_from_line].substr(0, p_from_column); String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length()); - for (int i = p_from_line; i < p_to_line; i++) { - text.remove(p_from_line + 1); - } + text.remove_range(p_from_line, p_to_line); text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text)); if (!text_changed_dirty && !setting_text) { |