From 93fe9ecb97e5b6fd7d82fea11708ecbef6e7053b Mon Sep 17 00:00:00 2001 From: Yuri Sizov Date: Thu, 18 Nov 2021 18:54:32 +0300 Subject: [PATCH] Add methods to get position from column and line in TextEdit (cherry picked from commit ba747e2a26196bf96b12d61fa9a6d4838995d847) --- doc/classes/TextEdit.xml | 66 ++++++++++++++++++++++ scene/gui/text_edit.cpp | 118 ++++++++++++++++++++++++++++++++++++++- scene/gui/text_edit.h | 39 +++++++++++-- 3 files changed, 217 insertions(+), 6 deletions(-) diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 8556f91281f..f3048759b10 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -137,12 +137,47 @@ Returns the text of a specific line. + + + + + Returns the line and column at the given position. In the returned vector, [code]x[/code] is the column, [code]y[/code] is the line. + + Returns the amount of total lines in the text. + + + + Returns the height of a largest line. + + + + + + + + Returns the width in pixels of the [code]wrap_index[/code] on [code]line[/code]. + + + + + + + Returns the number of times the given line is wrapped. + + + + + + + Returns an array of [String]s representing each wrapped index. + + @@ -150,6 +185,24 @@ [b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member CanvasItem.visible] property. + + + + + + Returns the local position for the given [code]line[/code] and [code]column[/code]. If [code]x[/code] or [code]y[/code] of the returned vector equal [code]-1[/code], the position is outside of the viewable area of the control. + [b]Note:[/b] The Y position corresponds to the bottom side of the line. Use [method get_rect_at_line_column] to get the top side position. + + + + + + + + Returns the local position and size for the grapheme at the given [code]line[/code] and [code]column[/code]. If [code]x[/code] or [code]y[/code] position of the returned rect equal [code]-1[/code], the position is outside of the viewable area of the control. + [b]Note:[/b] The Y position of the returned rect corresponds to the top side of the line, unlike [method get_pos_at_line_column] which returns the bottom side. + + @@ -180,6 +233,12 @@ Returns the selection end line. + + + + Returns the total width of all gutters and internal padding. + + @@ -247,6 +306,13 @@ Returns [code]true[/code] when the specified [code]line[/code] is marked as safe. + + + + + Returns if the given line is wrapped. + + diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index be8b086f5f2..86d73c61ad4 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -922,7 +922,7 @@ void TextEdit::_notification(int p_what) { int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); draw_amount += times_line_wraps(first_visible_line + 1); - // minimap + // Draw minimap. if (draw_minimap) { int minimap_visible_lines = _get_minimap_visible_rows(); int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); @@ -1091,7 +1091,8 @@ void TextEdit::_notification(int p_what) { bottom_limit_y -= cache.style_normal->get_margin(MARGIN_TOP); } - // draw main text + // Draw main text. + line_drawing_cache.clear(); int line = first_visible_line; for (int i = 0; i < draw_amount; i++) { line++; @@ -1112,6 +1113,7 @@ void TextEdit::_notification(int p_what) { } const String &fullstr = text[line]; + LineDrawingCache cache_entry; Map color_map; if (syntax_coloring) { @@ -1125,6 +1127,7 @@ void TextEdit::_notification(int p_what) { Vector wrap_rows = get_wrap_rows_text(line); int line_wrap_amount = times_line_wraps(line); int last_wrap_column = 0; + int wrap_column_offset = 0; for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) { if (line_wrap_index != 0) { @@ -1219,6 +1222,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; + if (text.is_breakpoint(line) && !draw_breakpoint_gutter) { #ifdef TOOLS_ENABLED VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color); @@ -1319,6 +1324,9 @@ void TextEdit::_notification(int p_what) { } } + int first_visible_char = str.length(); + int last_visible_char = 0; + // Loop through characters in one line. int j = 0; for (; j < str.length(); j++) { @@ -1542,6 +1550,13 @@ void TextEdit::_notification(int p_what) { int yofs = (get_row_height() - cache.space_icon->get_height()) / 2; cache.space_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_color_selected : color); } + + if (first_visible_char > j) { + first_visible_char = j; + } + if (last_visible_char < j) { + last_visible_char = j; + } } char_ofs += char_w; @@ -1615,7 +1630,14 @@ void TextEdit::_notification(int p_what) { } } } + + cache_entry.first_visible_char.push_back(wrap_column_offset + first_visible_char); + cache_entry.last_visible_char.push_back(wrap_column_offset + last_visible_char); + + wrap_column_offset += str.length(); } + + line_drawing_cache[line] = cache_entry; } bool completion_below = false; @@ -4782,6 +4804,57 @@ int TextEdit::get_row_height() const { return cache.font->get_height() + cache.line_spacing; } +/* Line and character position. */ +Point2 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()); +} + +Rect2 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_col(p_line, p_column); + if (wrap_index >= cache_entry.first_visible_char.size()) { + // Line seems to be wrapped beyond the viewable area. + return Rect2i(-1, -1, 0, 0); + } + + int first_visible_char = cache_entry.first_visible_char[wrap_index]; + int last_visible_char = cache_entry.last_visible_char[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() + cache.style_normal->get_margin(MARGIN_LEFT) - get_h_scroll(); + + int start_x = get_column_x_offset_for_line(p_column, p_line); + pos.x += start_x; + + String line = text[p_line]; + size.x = cache.font->get_char_size(line[p_column]).width; + size.y = get_line_height(); + + return Rect2i(pos, size); +} + +Point2 TextEdit::get_line_column_at_pos(const Point2 &p_pos) const { + int row, col; + _get_mouse_pos(p_pos, row, col); + + return Point2i(col, row); +} + int TextEdit::get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); @@ -6953,6 +7026,10 @@ int TextEdit::get_info_gutter_width() const { return info_gutter_width; } +int TextEdit::get_total_gutter_width() const { + return cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width; +} + void TextEdit::set_draw_minimap(bool p_draw) { draw_minimap = p_draw; update(); @@ -7080,6 +7157,30 @@ PopupMenu *TextEdit::get_menu() const { return menu; } +int TextEdit::get_line_width(int p_line, int p_wrap_index) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + + if (p_wrap_index >= 0 && line_wraps(p_line)) { + Vector rows = get_wrap_rows_text(p_line); + ERR_FAIL_INDEX_V(p_wrap_index, rows.size(), 0); + + int w = 0; + int len = rows[p_wrap_index].length(); + const CharType *str = rows[p_wrap_index].c_str(); + for (int i = 0; i < len; i++) { + w += text.get_char_width(str[i], str[i + 1], w); + } + + return w; + } + + return text.get_line_width(p_line); +} + +int TextEdit::get_line_height() const { + return get_row_height(); +} + void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &TextEdit::_gui_input); ClassDB::bind_method(D_METHOD("_scroll_moved"), &TextEdit::_scroll_moved); @@ -7110,6 +7211,13 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text); ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line); + ClassDB::bind_method(D_METHOD("get_line_wrapped_text", "line"), &TextEdit::get_wrap_rows_text); + + ClassDB::bind_method(D_METHOD("get_line_width", "line", "wrap_index"), &TextEdit::get_line_width, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_line_height"), &TextEdit::get_line_height); + + ClassDB::bind_method(D_METHOD("is_line_wrapped", "line"), &TextEdit::line_wraps); + ClassDB::bind_method(D_METHOD("get_line_wrap_count", "line"), &TextEdit::times_line_wraps); ClassDB::bind_method(D_METHOD("center_viewport_to_cursor"), &TextEdit::center_viewport_to_cursor); ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true)); @@ -7127,6 +7235,11 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_right_click_moves_caret", "enable"), &TextEdit::set_right_click_moves_caret); ClassDB::bind_method(D_METHOD("is_right_click_moving_caret"), &TextEdit::is_right_click_moving_caret); + /* Line and character position. */ + 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_line_column_at_pos", "position"), &TextEdit::get_line_column_at_pos); + ClassDB::bind_method(D_METHOD("set_readonly", "enable"), &TextEdit::set_readonly); ClassDB::bind_method(D_METHOD("is_readonly"), &TextEdit::is_readonly); @@ -7182,6 +7295,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_breakpoint_gutter_enabled"), &TextEdit::is_breakpoint_gutter_enabled); ClassDB::bind_method(D_METHOD("set_draw_fold_gutter"), &TextEdit::set_draw_fold_gutter); ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &TextEdit::is_drawing_fold_gutter); + ClassDB::bind_method(D_METHOD("get_total_gutter_width"), &TextEdit::get_total_gutter_width); ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled); ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 126850f546f..db6a37ecd6e 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -109,12 +109,14 @@ public: void set_indent_size(int p_indent_size); void set_font(const Ref &p_font); void set_color_regions(const Vector *p_regions) { color_regions = p_regions; } + int get_line_width(int p_line) const; int get_max_width(bool p_exclude_hidden = false) const; int get_char_width(CharType c, CharType next_c, int px) const; void set_line_wrap_amount(int p_line, int p_wrap_amount) const; int get_line_wrap_amount(int p_line) const; const Map &get_color_region_info(int p_line) const; + void set(int p_line, const String &p_text); void set_marked(int p_line, bool p_marked) { text.write[p_line].marked = p_marked; } bool is_marked(int p_line) const { return text[p_line].marked; } @@ -138,14 +140,19 @@ public: bool has_info_icon(int p_line) const { return text[p_line].has_info; } const Ref &get_info_icon(int p_line) const { return text[p_line].info_icon; } const String &get_info(int p_line) const { return text[p_line].info; } + void insert(int p_at, const String &p_text); void remove(int p_at); + int size() const { return text.size(); } + void clear(); void clear_width_cache(); void clear_wrap_cache(); void clear_info_icons(); + _FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; } + Text() { indent_size = 4; } }; @@ -505,6 +512,15 @@ private: void _push_current_op(); + /* Line and character position. */ + struct LineDrawingCache { + int y_offset = 0; + Vector first_visible_char; + Vector last_visible_char; + }; + + Map line_drawing_cache; + /* super internal api, undo/redo builds on it */ void _base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column); @@ -587,22 +603,30 @@ public: void set_text(String p_text); void insert_text_at_cursor(const String &p_text); void insert_at(const String &p_text, int at); + int get_line_count() const; + + int get_line_width(int p_line, int p_wrap_index = -1) const; + int get_line_height() const; + void set_line_as_marked(int p_line, bool p_marked); void set_line_as_bookmark(int p_line, bool p_bookmark); bool is_line_set_as_bookmark(int p_line) const; void get_bookmarks(List *p_bookmarks) const; Array get_bookmarks_array() const; + void set_line_as_breakpoint(int p_line, bool p_breakpoint); bool is_line_set_as_breakpoint(int p_line) const; - void set_executing_line(int p_line); - void clear_executing_line(); - void set_line_as_safe(int p_line, bool p_safe); - bool is_line_set_as_safe(int p_line) const; void get_breakpoints(List *p_breakpoints) const; Array get_breakpoints_array() const; void remove_breakpoints(); + void set_executing_line(int p_line); + void clear_executing_line(); + + void set_line_as_safe(int p_line, bool p_safe); + bool is_line_set_as_safe(int p_line) const; + void set_line_info_icon(int p_line, Ref p_icon, String p_info = ""); void clear_info_icons(); @@ -704,6 +728,11 @@ public: String get_word_under_cursor() const; String get_word_at_pos(const Vector2 &p_pos) const; + /* Line and character position. */ + Point2 get_pos_at_line_column(int p_line, int p_column) const; + Rect2 get_rect_at_line_column(int p_line, int p_column) const; + Point2 get_line_column_at_pos(const Point2 &p_pos) const; + bool search(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column, int &r_line, int &r_column) const; bool has_undo() const; @@ -789,6 +818,8 @@ public: void set_info_gutter_width(int p_gutter_width); int get_info_gutter_width() const; + int get_total_gutter_width() const; + void set_draw_minimap(bool p_draw); bool is_drawing_minimap() const;