summaryrefslogtreecommitdiff
path: root/scene
diff options
context:
space:
mode:
authorbruvzg <7645683+bruvzg@users.noreply.github.com>2020-09-18 10:36:10 +0300
committerbruvzg <7645683+bruvzg@users.noreply.github.com>2020-11-26 14:25:50 +0200
commit3be31c496089b413c87ca1411726dddc5dd0e7d6 (patch)
tree84b3df317a6da05c63a029f7ab8d0375180caa12 /scene
parentd66eb77c9c4d8906f94180a3ba542a025bc45aae (diff)
[Complex Text Layouts] Refactor TextEdit and CodeEdit controls.
Diffstat (limited to 'scene')
-rw-r--r--scene/gui/code_edit.cpp22
-rw-r--r--scene/gui/text_edit.cpp1786
-rw-r--r--scene/gui/text_edit.h116
3 files changed, 1177 insertions, 747 deletions
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index d088a8cc3b..59cfbccf99 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -34,9 +34,9 @@ void CodeEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_ENTER_TREE: {
- set_gutter_width(main_gutter, cache.row_height);
- set_gutter_width(line_number_gutter, (line_number_digits + 1) * cache.font->get_char_size('0').width);
- set_gutter_width(fold_gutter, cache.row_height / 1.2);
+ set_gutter_width(main_gutter, get_row_height());
+ set_gutter_width(line_number_gutter, (line_number_digits + 1) * cache.font->get_char_size('0', 0, cache.font_size).width);
+ set_gutter_width(fold_gutter, get_row_height() / 1.2);
breakpoint_color = get_theme_color("breakpoint_color");
breakpoint_icon = get_theme_icon("breakpoint");
@@ -234,14 +234,16 @@ 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 = String::num(p_line + 1).lpad(line_number_digits, line_number_padding);
-
- int yofs = p_region.position.y + (cache.row_height - cache.font->get_height()) / 2;
+ String fc = TS->format_number(String::num(p_line + 1).lpad(line_number_digits, line_number_padding));
+ Ref<TextLine> tl;
+ tl.instance();
+ 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);
if (number_color == Color(1, 1, 1)) {
number_color = line_number_color;
}
- cache.font->draw_string(get_canvas_item(), Point2(p_region.position.x, yofs + cache.font->get_ascent()), fc, HALIGN_LEFT, -1, cache.font_size, number_color);
+ tl->draw(get_canvas_item(), Point2(p_region.position.x, yofs), number_color);
}
/* Fold Gutter */
@@ -368,7 +370,7 @@ void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) {
while (lc /= 10) {
line_number_digits++;
}
- set_gutter_width(line_number_gutter, (line_number_digits + 1) * cache.font->get_char_size('0').width);
+ set_gutter_width(line_number_gutter, (line_number_digits + 1) * cache.font->get_char_size('0', 0, cache.font_size).width);
int from_line = MIN(p_from_line, p_to_line);
int line_count = (p_to_line - p_from_line);
@@ -410,6 +412,10 @@ void CodeEdit::_update_gutter_indexes() {
}
CodeEdit::CodeEdit() {
+ /* Text Direction */
+ set_layout_direction(LAYOUT_DIRECTION_LTR);
+ set_text_direction(TEXT_DIRECTION_LTR);
+
/* Gutters */
int gutter_idx = 0;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 18b4e30abf..b9818e139f 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -36,6 +36,8 @@
#include "core/object/script_language.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
+#include "core/string/translation.h"
+
#include "scene/main/window.h"
#ifdef TOOLS_ENABLED
@@ -113,63 +115,118 @@ void TextEdit::Text::set_font(const Ref<Font> &p_font) {
font = p_font;
}
+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::_update_line_cache(int p_line) const {
- int w = 0;
+void TextEdit::Text::set_font_features(const Dictionary &p_features) {
+ opentype_features = p_features;
+}
- int len = text[p_line].data.length();
- const char32_t *str = text[p_line].data.get_data();
+void TextEdit::Text::set_direction_and_language(TextServer::Direction p_direction, String p_language) {
+ direction = p_direction;
+ language = p_language;
+}
- // Update width.
+void TextEdit::Text::set_draw_control_chars(bool p_draw_control_chars) {
+ draw_control_chars = p_draw_control_chars;
+}
- for (int i = 0; i < len; i++) {
- w += get_char_width(str[i], str[i + 1], w);
- }
+int TextEdit::Text::get_line_width(int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, text.size(), 0);
+ return text[p_line].data_buf->get_size().x;
+}
+
+int TextEdit::Text::get_line_height(int p_line, int p_wrap_index) const {
+ ERR_FAIL_INDEX_V(p_line, text.size(), 0);
- text.write[p_line].width_cache = w;
- text.write[p_line].wrap_amount_cache = -1;
+ return text[p_line].data_buf->get_line_size(p_wrap_index).y;
}
-int TextEdit::Text::get_line_width(int p_line) const {
- ERR_FAIL_INDEX_V(p_line, text.size(), -1);
+void TextEdit::Text::set_width(float p_width) {
+ width = p_width;
+}
+
+int TextEdit::Text::get_line_wrap_amount(int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, text.size(), 0);
+
+ return text[p_line].data_buf->get_line_count() - 1;
+}
- if (text[p_line].width_cache == -1) {
- _update_line_cache(p_line);
+Vector<Vector2i> TextEdit::Text::get_line_wrap_ranges(int p_line) const {
+ Vector<Vector2i> ret;
+ ERR_FAIL_INDEX_V(p_line, text.size(), ret);
+
+ for (int i = 0; i < text[p_line].data_buf->get_line_count(); i++) {
+ ret.push_back(text[p_line].data_buf->get_line_range(i));
}
+ return ret;
+}
+
+const Ref<TextParagraph> TextEdit::Text::get_line_data(int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, text.size(), Ref<TextParagraph>());
+ return text[p_line].data_buf;
+}
- return text[p_line].width_cache;
+_FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const {
+ return text[p_line].data;
}
-void TextEdit::Text::set_line_wrap_amount(int p_line, int p_wrap_amount) const {
+void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Vector<Vector2i> &p_bidi_override) {
ERR_FAIL_INDEX(p_line, text.size());
- text.write[p_line].wrap_amount_cache = p_wrap_amount;
-}
+ if (font.is_null() || font_size <= 0) {
+ return; // Not in tree?
+ }
-int TextEdit::Text::get_line_wrap_amount(int p_line) const {
- ERR_FAIL_INDEX_V(p_line, text.size(), -1);
+ text.write[p_line].data_buf->clear();
+ text.write[p_line].data_buf->set_width(width);
+ text.write[p_line].data_buf->set_direction((TextServer::Direction)direction);
+ text.write[p_line].data_buf->set_preserve_control(draw_control_chars);
+ if (p_ime_text.length() > 0) {
+ text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language);
+ if (!p_bidi_override.empty()) {
+ TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override);
+ }
+ } else {
+ text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language);
+ if (!text[p_line].bidi_override.empty()) {
+ TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override);
+ }
+ }
- return text[p_line].wrap_amount_cache;
+ // Apply tab align.
+ if (indent_size > 0) {
+ Vector<float> tabs;
+ tabs.push_back(font->get_char_size('m', 0, font_size).width * indent_size);
+ text.write[p_line].data_buf->tab_align(tabs);
+ }
}
-void TextEdit::Text::clear_width_cache() {
+void TextEdit::Text::invalidate_all_lines() {
for (int i = 0; i < text.size(); i++) {
- text.write[i].width_cache = -1;
+ text.write[i].data_buf->set_width(width);
+ if (indent_size > 0) {
+ Vector<float> tabs;
+ tabs.push_back(font->get_char_size('m', 0, font_size).width * indent_size);
+ text.write[i].data_buf->tab_align(tabs);
+ }
}
}
-void TextEdit::Text::clear_wrap_cache() {
+void TextEdit::Text::invalidate_all() {
for (int i = 0; i < text.size(); i++) {
- text.write[i].wrap_amount_cache = -1;
+ invalidate_cache(i);
}
}
void TextEdit::Text::clear() {
text.clear();
- insert(0, "");
+ insert(0, "", Vector<Vector2i>());
}
int TextEdit::Text::get_max_width(bool p_exclude_hidden) const {
@@ -184,46 +241,30 @@ int TextEdit::Text::get_max_width(bool p_exclude_hidden) const {
return max;
}
-void TextEdit::Text::set(int p_line, const String &p_text) {
+void TextEdit::Text::set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override) {
ERR_FAIL_INDEX(p_line, text.size());
- text.write[p_line].width_cache = -1;
- text.write[p_line].wrap_amount_cache = -1;
text.write[p_line].data = p_text;
+ text.write[p_line].bidi_override = p_bidi_override;
+ invalidate_cache(p_line);
}
-void TextEdit::Text::insert(int p_at, const String &p_text) {
+void TextEdit::Text::insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override) {
Line line;
line.gutters.resize(gutter_count);
line.marked = false;
line.hidden = false;
- line.width_cache = -1;
- line.wrap_amount_cache = -1;
line.data = p_text;
+ line.bidi_override = p_bidi_override;
text.insert(p_at, line);
+
+ invalidate_cache(p_at);
}
void TextEdit::Text::remove(int p_at) {
text.remove(p_at);
}
-int TextEdit::Text::get_char_width(char32_t c, char32_t next_c, int px) const {
- int tab_w = font->get_char_size(' ').width * indent_size;
- int w = 0;
-
- if (c == '\t') {
- int left = px % tab_w;
- if (left == 0) {
- w = tab_w;
- } else {
- w = tab_w - px % tab_w; // Is right.
- }
- } else {
- w = font->get_char_size(c, next_c).width;
- }
- return w;
-}
-
void TextEdit::Text::add_gutter(int p_at) {
for (int i = 0; i < text.size(); i++) {
if (p_at < 0 || p_at > gutter_count) {
@@ -338,9 +379,17 @@ void TextEdit::_click_selection_held() {
}
}
+Point2 TextEdit::_get_local_mouse_pos() const {
+ Point2 mp = get_local_mouse_position();
+ if (is_layout_rtl()) {
+ mp.x = get_size().width - mp.x;
+ }
+ return mp;
+}
+
void TextEdit::_update_selection_mode_pointer() {
dragging_selection = true;
- Point2 mp = get_local_mouse_position();
+ Point2 mp = _get_local_mouse_pos();
int row, col;
_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
@@ -356,7 +405,7 @@ void TextEdit::_update_selection_mode_pointer() {
void TextEdit::_update_selection_mode_word() {
dragging_selection = true;
- Point2 mp = get_local_mouse_position();
+ Point2 mp = _get_local_mouse_pos();
int row, col;
_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
@@ -413,7 +462,7 @@ void TextEdit::_update_selection_mode_word() {
void TextEdit::_update_selection_mode_line() {
dragging_selection = true;
- Point2 mp = get_local_mouse_position();
+ Point2 mp = _get_local_mouse_pos();
int row, col;
_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
@@ -438,7 +487,7 @@ void TextEdit::_update_selection_mode_line() {
}
void TextEdit::_update_minimap_click() {
- Point2 mp = get_local_mouse_position();
+ Point2 mp = _get_local_mouse_pos();
int xmargin_end = get_size().width - cache.style_normal->get_margin(MARGIN_RIGHT);
if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) {
@@ -479,7 +528,8 @@ void TextEdit::_update_minimap_drag() {
control_height = scroll_height;
}
- Point2 mp = get_local_mouse_position();
+ Point2 mp = _get_local_mouse_pos();
+
double diff = (mp.y - minimap_scroll_click_pos) / control_height;
v_scroll->set_as_ratio(minimap_scroll_ratio + diff);
}
@@ -494,7 +544,7 @@ void TextEdit::_notification(int p_what) {
if (text_changed_dirty) {
MessageQueue::get_singleton()->push_call(this, "_text_changed_emit");
}
- _update_wrap_at();
+ _update_wrap_at(true);
} break;
case NOTIFICATION_RESIZED: {
_update_scrollbars();
@@ -506,9 +556,11 @@ void TextEdit::_notification(int p_what) {
call_deferred("_update_wrap_at");
}
} break;
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
+ case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
_update_caches();
- _update_wrap_at();
+ _update_wrap_at(true);
} break;
case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
window_has_focus = true;
@@ -556,6 +608,7 @@ void TextEdit::_notification(int p_what) {
}
Size2 size = get_size();
+ bool rtl = is_layout_rtl();
if ((!has_focus() && !menu->has_focus()) || !window_has_focus) {
draw_caret = false;
}
@@ -582,8 +635,6 @@ void TextEdit::_notification(int p_what) {
cache.style_focus->draw(ci, Rect2(Point2(), size));
}
- int ascent = cache.font->get_ascent();
-
int visible_rows = get_visible_rows() + 1;
Color color = readonly ? cache.font_color_readonly : cache.font_color;
@@ -593,17 +644,25 @@ void TextEdit::_notification(int p_what) {
}
if (line_length_guidelines) {
- const int hard_x = xmargin_beg + (int)cache.font->get_char_size('0').width * line_length_guideline_hard_col - cursor.x_ofs;
+ const int hard_x = xmargin_beg + (int)cache.font->get_char_size('0', 0, cache.font_size).width * line_length_guideline_hard_col - cursor.x_ofs;
if (hard_x > xmargin_beg && hard_x < xmargin_end) {
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(hard_x, 0), Point2(hard_x, size.height), cache.line_length_guideline_color);
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - hard_x, 0), Point2(size.width - hard_x, size.height), cache.line_length_guideline_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(hard_x, 0), Point2(hard_x, size.height), cache.line_length_guideline_color);
+ }
}
// Draw a "Soft" line length guideline, less visible than the hard line length guideline.
// It's usually set to a lower column compared to the hard line length guideline.
// Only drawn if its column differs from the hard line length guideline.
- const int soft_x = xmargin_beg + (int)cache.font->get_char_size('0').width * line_length_guideline_soft_col - cursor.x_ofs;
+ const int soft_x = xmargin_beg + (int)cache.font->get_char_size('0', 0, cache.font_size).width * line_length_guideline_soft_col - cursor.x_ofs;
if (hard_x != soft_x && soft_x > xmargin_beg && soft_x < xmargin_end) {
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(soft_x, 0), Point2(soft_x, size.height), cache.line_length_guideline_color * Color(1, 1, 1, 0.5));
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - soft_x, 0), Point2(size.width - soft_x, size.height), cache.line_length_guideline_color * Color(1, 1, 1, 0.5));
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(soft_x, 0), Point2(soft_x, size.height), cache.line_length_guideline_color * Color(1, 1, 1, 0.5));
+ }
}
}
@@ -791,7 +850,11 @@ void TextEdit::_notification(int p_what) {
// draw the minimap
Color viewport_color = (cache.background_color.get_v() < 0.5) ? Color(1, 1, 1, 0.1) : Color(0, 0, 0, 0.1);
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, cache.minimap_width, viewport_height), viewport_color);
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - cache.minimap_width, viewport_offset_y, cache.minimap_width, viewport_height), viewport_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, cache.minimap_width, viewport_height), viewport_color);
+ }
for (int i = 0; i < minimap_draw_amount; i++) {
minimap_line++;
@@ -841,7 +904,11 @@ void TextEdit::_notification(int p_what) {
}
if (minimap_line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), cache.current_line_color);
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - cache.minimap_width, i * 3, cache.minimap_width, 2), cache.current_line_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), cache.current_line_color);
+ }
}
Color previous_color;
@@ -888,7 +955,11 @@ void TextEdit::_notification(int p_what) {
// take one for zero indexing, and if we hit whitespace / the end of a word.
int chars = MAX(0, (j - (characters - 1)) - (is_whitespace ? 1 : 0)) + 1;
int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs;
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_x_ofs, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color);
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(size.width - char_x_ofs - minimap_char_size.x * characters, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_x_ofs, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color);
+ }
}
if (out_of_bounds) {
@@ -907,6 +978,7 @@ void TextEdit::_notification(int p_what) {
}
// draw main text
+ int row_height = get_row_height();
int line = first_visible_line;
for (int i = 0; i < draw_amount; i++) {
line++;
@@ -926,20 +998,17 @@ void TextEdit::_notification(int p_what) {
continue;
}
- const String &fullstr = text[line];
-
Dictionary color_map = _get_line_syntax_highlighting(line);
// Ensure we at least use the font color.
Color current_color = readonly ? cache.font_color_readonly : cache.font_color;
- bool underlined = false;
+ const Ref<TextParagraph> ldata = text.get_line_data(line);
Vector<String> wrap_rows = get_wrap_rows_text(line);
int line_wrap_amount = times_line_wraps(line);
- int last_wrap_column = 0;
- for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) {
+ for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) {
if (line_wrap_index != 0) {
i++;
if (i >= draw_amount) {
@@ -948,18 +1017,7 @@ void TextEdit::_notification(int p_what) {
}
const String &str = wrap_rows[line_wrap_index];
- int indent_px = line_wrap_index != 0 ? get_indent_level(line) * cache.font->get_char_size(' ').width : 0;
- if (indent_px >= wrap_at) {
- indent_px = 0;
- }
-
- if (line_wrap_index > 0) {
- last_wrap_column += wrap_rows[line_wrap_index - 1].length();
- }
-
int char_margin = xmargin_beg - cursor.x_ofs;
- char_margin += indent_px;
- int char_ofs = 0;
int ofs_readonly = 0;
int ofs_x = 0;
@@ -968,48 +1026,45 @@ void TextEdit::_notification(int p_what) {
ofs_x = cache.style_readonly->get_offset().x / 2;
}
- int ofs_y = (i * get_row_height() + cache.line_spacing / 2) + ofs_readonly;
- ofs_y -= cursor.wrap_ofs * get_row_height();
- ofs_y -= get_v_scroll_offset() * get_row_height();
-
- // Check if line contains highlighted word.
- int highlighted_text_col = -1;
- int search_text_col = -1;
- int highlighted_word_col = -1;
-
- if (!search_text.empty()) {
- search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0);
- }
-
- if (highlighted_text.length() != 0 && highlighted_text != search_text) {
- highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
- }
-
- if (select_identifiers_enabled && highlighted_word.length() != 0) {
- if (_is_char(highlighted_word[0]) || highlighted_word[0] == '.') {
- highlighted_word_col = _get_column_pos_of_word(highlighted_word, fullstr, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
- }
- }
+ int ofs_y = (i * row_height + cache.line_spacing / 2) + ofs_readonly;
+ ofs_y -= cursor.wrap_ofs * row_height;
+ ofs_y -= get_v_scroll_offset() * row_height;
if (text.is_marked(line)) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.mark_color);
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), cache.mark_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), cache.mark_color);
+ }
}
if (str.length() == 0) {
// Draw line background if empty as we won't loop at at all.
if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, get_row_height()), cache.current_line_color);
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), cache.current_line_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), cache.current_line_color);
+ }
}
// Give visual indication of empty selected line.
if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) {
- int char_w = cache.font->get_char_size(' ').width;
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, get_row_height()), cache.selection_color);
+ int char_w = cache.font->get_char_size('m', 0, cache.font_size).width;
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), cache.selection_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), cache.selection_color);
+ }
}
} else {
// If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later.
if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg + ofs_x, get_row_height()), cache.current_line_color);
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), cache.current_line_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), cache.current_line_color);
+ }
}
}
@@ -1031,8 +1086,12 @@ void TextEdit::_notification(int p_what) {
break;
}
- int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2;
- cache.font->draw_string(ci, Point2(gutter_offset + ofs_x, yofs + cache.font->get_ascent()), text, HALIGN_LEFT, -1, cache.font_size, get_line_gutter_item_color(line, g));
+ Ref<TextLine> tl;
+ tl.instance();
+ tl->add_string(text, cache.font, cache.font_size);
+
+ int yofs = ofs_y + (row_height - tl->get_size().y) / 2;
+ tl->draw(ci, Point2(gutter_offset + ofs_x, yofs), get_line_gutter_item_color(line, g));
} break;
case GUTTER_TPYE_ICON: {
const Ref<Texture2D> icon = get_line_gutter_icon(line, g);
@@ -1040,7 +1099,7 @@ void TextEdit::_notification(int p_what) {
break;
}
- Rect2i gutter_rect = Rect2i(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, get_row_height()));
+ Rect2 gutter_rect = Rect2(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, row_height));
int horizontal_padding = gutter_rect.size.x / 6;
int vertical_padding = gutter_rect.size.y / 6;
@@ -1048,13 +1107,28 @@ void TextEdit::_notification(int p_what) {
gutter_rect.position += Point2(horizontal_padding, vertical_padding);
gutter_rect.size -= Point2(horizontal_padding, vertical_padding) * 2;
+ // Correct icon aspect ratio.
+ float icon_ratio = icon->get_width() / icon->get_height();
+ float gutter_ratio = gutter_rect.size.x / gutter_rect.size.y;
+ if (gutter_ratio > icon_ratio) {
+ gutter_rect.size.x = floor(icon->get_width() * (gutter_rect.size.y / icon->get_height()));
+ } else {
+ gutter_rect.size.y = floor(icon->get_height() * (gutter_rect.size.x / icon->get_width()));
+ }
+ if (rtl) {
+ gutter_rect.position.x = size.width - gutter_rect.position.x - gutter_rect.size.x;
+ }
+
icon->draw_rect(ci, gutter_rect, false, get_line_gutter_item_color(line, g));
} break;
case GUTTER_TPYE_CUSTOM: {
if (gutter.custom_draw_obj.is_valid()) {
Object *cdo = ObjectDB::get_instance(gutter.custom_draw_obj);
if (cdo) {
- Rect2i gutter_rect = Rect2i(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, get_row_height()));
+ Rect2i gutter_rect = Rect2i(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, row_height));
+ if (rtl) {
+ gutter_rect.position.x = size.width - gutter_rect.position.x - gutter_rect.size.x;
+ }
cdo->call(gutter.custom_draw_callback, line, g, Rect2(gutter_rect));
}
}
@@ -1065,296 +1139,300 @@ void TextEdit::_notification(int p_what) {
}
}
- // Loop through characters in one line.
- int j = 0;
- for (; j < str.length(); j++) {
- if (color_map.has(last_wrap_column + j)) {
- current_color = color_map[last_wrap_column + j].get("color");
- if (readonly && current_color.a > cache.font_color_readonly.a) {
- current_color.a = cache.font_color_readonly.a;
- }
- }
- color = current_color;
+ // Draw line.
+ RID rid = ldata->get_line_rid(line_wrap_index);
+ float text_height = TS->shaped_text_get_size(rid).y;
- int char_w;
-
- // Handle tabulator.
- char_w = text.get_char_width(str[j], str[j + 1], char_ofs);
-
- if ((char_ofs + char_margin) < xmargin_beg) {
- char_ofs += char_w;
+ if (rtl) {
+ char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x;
+ }
- // Line highlighting handle horizontal clipping.
- if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) {
- if (j == str.length() - 1) {
- // End of line when last char is skipped.
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color);
- } else if ((char_ofs + char_margin) > xmargin_beg) {
- // Char next to margin is skipped.
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, (char_ofs + char_margin) - (xmargin_beg + ofs_x), get_row_height()), cache.current_line_color);
- }
+ if (selection.active && line >= selection.from_line && line <= selection.to_line) { // Selection
+ int sel_from = (line > selection.from_line) ? TS->shaped_text_get_range(rid).x : selection.from_column;
+ int sel_to = (line < selection.to_line) ? TS->shaped_text_get_range(rid).y : selection.to_column;
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to);
+ 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);
+ if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
+ continue;
}
- continue;
- }
-
- if ((char_ofs + char_margin + char_w) >= xmargin_end) {
- break;
+ if (rect.position.x < xmargin_beg) {
+ rect.size.x -= (xmargin_beg - rect.position.x);
+ rect.position.x = xmargin_beg;
+ } else if (rect.position.x + rect.size.x > xmargin_end) {
+ rect.size.x = xmargin_end - rect.position.x;
+ }
+ draw_rect(rect, cache.selection_color, true);
}
+ }
- bool in_search_result = false;
-
- if (search_text_col != -1) {
- // If we are at the end check for new search result on same line.
- if (j >= search_text_col + search_text.length()) {
- search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j);
+ int start = TS->shaped_text_get_range(rid).x;
+ if (!search_text.empty()) { // Search highhlight
+ int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0);
+ while (search_text_col != -1) {
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text.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);
+ if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
+ continue;
+ }
+ if (rect.position.x < xmargin_beg) {
+ rect.size.x -= (xmargin_beg - rect.position.x);
+ rect.position.x = xmargin_beg;
+ } else if (rect.position.x + rect.size.x > xmargin_end) {
+ rect.size.x = xmargin_end - rect.position.x;
+ }
+ draw_rect(rect, cache.search_result_color, true);
+ draw_rect(rect, cache.search_result_border_color, false);
}
- in_search_result = j >= search_text_col && j < search_text_col + search_text.length();
-
- if (in_search_result) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin, ofs_y), Size2i(char_w, get_row_height())), cache.search_result_color);
- }
+ search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + 1);
}
+ }
- // Current line highlighting.
- bool in_selection = (selection.active && line >= selection.from_line && line <= selection.to_line && (line > selection.from_line || last_wrap_column + j >= selection.from_column) && (line < selection.to_line || last_wrap_column + j < selection.to_column));
-
- if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) {
- // Draw the wrap indent offset highlight.
- if (line_wrap_index != 0 && j == 0) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + ofs_x - indent_px, ofs_y, indent_px, get_row_height()), cache.current_line_color);
- }
- // If its the last char draw to end of the line.
- if (j == str.length() - 1) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w + ofs_x, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color);
- }
- // Actual text.
- if (!in_selection) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.current_line_color);
+ if (highlight_all_occurrences && !only_whitespaces_highlighted && !highlighted_text.empty()) { // Highlight
+ int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
+ while (highlighted_text_col != -1) {
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text.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);
+ if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
+ continue;
+ }
+ if (rect.position.x < xmargin_beg) {
+ rect.size.x -= (xmargin_beg - rect.position.x);
+ rect.position.x = xmargin_beg;
+ } else if (rect.position.x + rect.size.x > xmargin_end) {
+ rect.size.x = xmargin_end - rect.position.x;
+ }
+ draw_rect(rect, cache.word_highlighted_color);
}
- }
- if (in_selection) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.selection_color);
+ highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + 1);
}
+ }
- if (in_search_result) {
- Color border_color = (line == search_result_line && j >= search_result_col && j < search_result_col + search_text.length()) ? cache.font_color : cache.search_result_border_color;
-
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, 1)), border_color);
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y + get_row_height() - 1), Size2i(char_w, 1)), border_color);
+ if (select_identifiers_enabled && highlighted_word.length() != 0) { // Highlight word
+ if (_is_char(highlighted_word[0]) || highlighted_word[0] == '.') {
+ int highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
+ while (highlighted_word_col != -1) {
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + highlighted_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);
+ if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
+ continue;
+ }
+ if (rect.position.x < xmargin_beg) {
+ rect.size.x -= (xmargin_beg - rect.position.x);
+ rect.position.x = xmargin_beg;
+ } 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) + cache.font->get_underline_position(cache.font_size);
+ rect.size.y = cache.font->get_underline_thickness(cache.font_size);
+ draw_rect(rect, cache.font_color_selected);
+ }
- if (j == search_text_col) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(1, get_row_height())), border_color);
- }
- if (j == search_text_col + search_text.length() - 1) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + char_w + ofs_x - 1, ofs_y), Size2i(1, get_row_height())), border_color);
+ highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1);
}
}
+ }
- if (highlight_all_occurrences && !only_whitespaces_highlighted) {
- if (highlighted_text_col != -1) {
- // If we are at the end check for new word on same line.
- if (j > highlighted_text_col + highlighted_text.length()) {
- highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j);
- }
-
- bool in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col + highlighted_text.length());
-
- // If this is the original highlighted text we don't want to highlight it again.
- if (cursor.line == line && cursor_wrap_index == line_wrap_index && (cursor.column >= highlighted_text_col && cursor.column <= highlighted_text_col + highlighted_text.length())) {
- in_highlighted_word = false;
- }
+ ofs_y += (row_height - text_height) / 2;
- if (in_highlighted_word) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.word_highlighted_color);
- }
+ const Vector<TextServer::Glyph> glyphs = TS->shaped_text_get_glyphs(rid);
+ ofs_y += ldata->get_line_ascent(line_wrap_index);
+ float char_ofs = 0.f;
+ for (int j = 0; j < glyphs.size(); j++) {
+ if (color_map.has(glyphs[j].start)) {
+ current_color = color_map[glyphs[j].start].get("color");
+ if (readonly && current_color.a > cache.font_color_readonly.a) {
+ current_color.a = cache.font_color_readonly.a;
}
}
- if (highlighted_word_col != -1) {
- if (j + last_wrap_column > highlighted_word_col + highlighted_word.length()) {
- highlighted_word_col = _get_column_pos_of_word(highlighted_word, fullstr, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j + last_wrap_column);
+ if (selection.active && line >= selection.from_line && line <= selection.to_line) { // Selection
+ int sel_from = (line > selection.from_line) ? TS->shaped_text_get_range(rid).x : selection.from_column;
+ int sel_to = (line < selection.to_line) ? TS->shaped_text_get_range(rid).y : selection.to_column;
+
+ if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && override_selected_font_color) {
+ current_color = cache.font_color_selected;
}
- underlined = (j + last_wrap_column >= highlighted_word_col && j + last_wrap_column < highlighted_word_col + highlighted_word.length());
}
if (brace_matching_enabled) {
- int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2;
- if ((brace_open_match_line == line && brace_open_match_column == last_wrap_column + j) ||
- (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) {
+ if ((brace_open_match_line == line && brace_open_match_column == glyphs[j].start) ||
+ (cursor.column == glyphs[j].start && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) {
if (brace_open_mismatch) {
- color = cache.brace_mismatch_color;
+ current_color = cache.brace_mismatch_color;
}
- cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], cache.font_size, in_selection && override_selected_font_color ? cache.font_color_selected : color);
+ Rect2 rect = Rect2(char_ofs + char_margin + ofs_x, ofs_y + cache.font->get_underline_position(cache.font_size), glyphs[j].advance * glyphs[j].repeat, cache.font->get_underline_thickness(cache.font_size));
+ draw_rect(rect, current_color);
}
- if ((brace_close_match_line == line && brace_close_match_column == last_wrap_column + j) ||
- (cursor.column == last_wrap_column + j + 1 && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) {
+ if ((brace_close_match_line == line && brace_close_match_column == glyphs[j].start) ||
+ (cursor.column == glyphs[j].start + 1 && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) {
if (brace_close_mismatch) {
- color = cache.brace_mismatch_color;
+ current_color = cache.brace_mismatch_color;
}
- cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], cache.font_size, in_selection && override_selected_font_color ? cache.font_color_selected : color);
+ Rect2 rect = Rect2(char_ofs + char_margin + ofs_x, ofs_y + cache.font->get_underline_position(cache.font_size), glyphs[j].advance * glyphs[j].repeat, cache.font->get_underline_thickness(cache.font_size));
+ draw_rect(rect, current_color);
}
}
-
- if (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index) {
- cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y);
- cursor_pos.y += (get_row_height() - cache.font->get_height()) / 2;
-
- if (insert_mode) {
- cursor_insert_offset_y = (cache.font->get_height() - 3);
- cursor_pos.y += cursor_insert_offset_y;
+ if (draw_tabs && ((glyphs[j].flags & TextServer::GRAPHEME_IS_TAB) == TextServer::GRAPHEME_IS_TAB)) {
+ int yofs = (text_height - cache.tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
+ cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), current_color);
+ }
+ if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE)) {
+ int yofs = (text_height - cache.space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
+ int xofs = (glyphs[j].advance * glyphs[j].repeat - cache.space_icon->get_width()) / 2;
+ cache.space_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x + xofs, ofs_y + yofs), current_color);
+ }
+ for (int k = 0; k < glyphs[j].repeat; k++) {
+ if ((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);
+ } 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);
+ }
}
+ char_ofs += glyphs[j].advance;
+ }
+ if ((char_ofs + char_margin) >= xmargin_end) {
+ break;
+ }
+ }
- int caret_w = (str[j] == '\t') ? cache.font->get_char_size(' ').width : char_w;
- if (ime_text.length() > 0) {
- int ofs = 0;
- while (true) {
- if (ofs >= ime_text.length()) {
- break;
- }
-
- char32_t cchar = ime_text[ofs];
- char32_t next = ime_text[ofs + 1];
- int im_char_width = cache.font->get_char_size(cchar, next).width;
-
- if ((char_ofs + char_margin + im_char_width) >= xmargin_end) {
- break;
- }
-
- bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y;
- if (selected) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color);
- } else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color);
- }
-
- cache.font->draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, cache.font_size, color);
+ if (line_wrap_index == line_wrap_amount && is_folded(line)) {
+ int yofs = (text_height - cache.folded_eol_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
+ int xofs = cache.folded_eol_icon->get_width() / 2;
+ Color eol_color = cache.code_folding_color;
+ eol_color.a = 1;
+ cache.folded_eol_icon->draw(ci, Point2(char_ofs + char_margin + xofs + ofs_x, ofs_y + yofs), eol_color);
+ }
- char_ofs += im_char_width;
- ofs++;
- }
- }
- if (ime_text.length() == 0) {
- if (draw_caret) {
- if (insert_mode) {
+ // Carets
#ifdef TOOLS_ENABLED
- int caret_h = (block_caret) ? 4 : 2 * EDSCALE;
+ int caret_width = Math::round(EDSCALE);
#else
- int caret_h = (block_caret) ? 4 : 2;
+ int caret_width = 1;
#endif
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, caret_h)), cache.caret_color);
- } else {
-#ifdef TOOLS_ENABLED
- caret_w = (block_caret) ? caret_w : 2 * EDSCALE;
-#else
- caret_w = (block_caret) ? caret_w : 2;
-#endif
-
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, cache.font->get_height())), cache.caret_color);
- }
+ if (cursor.line == line && ((line_wrap_index == line_wrap_amount) || (cursor.column != TS->shaped_text_get_range(rid).y))) {
+ cursor_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index);
+ if (ime_text.length() == 0) {
+ Rect2 l_caret, t_caret;
+ TextServer::Direction l_dir, t_dir;
+ if (str.length() != 0) {
+ // Get carets.
+ TS->shaped_text_get_carets(rid, cursor.column, l_caret, l_dir, t_caret, t_dir);
+ } else {
+ // No carets, add one at the start.
+ int h = cache.font->get_height(cache.font_size);
+ if (rtl) {
+ l_dir = TextServer::DIRECTION_RTL;
+ l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h));
+ } else {
+ l_dir = TextServer::DIRECTION_LTR;
+ l_caret = Rect2(Vector2(char_ofs, -h / 2), Size2(caret_width * 4, h));
}
}
- }
- if (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && block_caret && draw_caret && !insert_mode) {
- color = cache.caret_background_color;
- } else if (block_caret) {
- color = readonly ? cache.font_color_readonly : cache.font_color;
- }
-
- if (str[j] >= 32) {
- int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2;
- int w = cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), str[j], str[j + 1], cache.font_size, in_selection && override_selected_font_color ? cache.font_color_selected : color);
- if (underlined) {
- float line_width = cache.font->get_underline_thickness();
-#ifdef TOOLS_ENABLED
- line_width *= EDSCALE;
-#endif
-
- draw_rect(Rect2(char_ofs + char_margin + ofs_x, yofs + ascent + cache.font->get_underline_position(), w, line_width), in_selection && override_selected_font_color ? cache.font_color_selected : color);
+ if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) {
+ cursor_pos.x = char_margin + ofs_x + l_caret.position.x;
+ } else {
+ cursor_pos.x = char_margin + ofs_x + t_caret.position.x;
}
- } else if (draw_tabs && str[j] == '\t') {
- int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2;
- cache.tab_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 (draw_spaces && str[j] == ' ') {
- 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);
- }
-
- char_ofs += char_w;
-
- if (line_wrap_index == line_wrap_amount && j == str.length() - 1 && is_folded(line)) {
- int yofs = (get_row_height() - cache.folded_eol_icon->get_height()) / 2;
- int xofs = cache.folded_eol_icon->get_width() / 2;
- Color eol_color = cache.code_folding_color;
- eol_color.a = 1;
- cache.folded_eol_icon->draw(ci, Point2(char_ofs + char_margin + xofs + ofs_x, ofs_y + yofs), eol_color);
- }
- }
+ if (draw_caret) {
+ if (block_caret || insert_mode) {
+ //Block or underline caret, draw trailing carets at full height.
+ int h = cache.font->get_height(cache.font_size);
+
+ if (t_caret != Rect2()) {
+ if (insert_mode) {
+ t_caret.position.y = TS->shaped_text_get_descent(rid);
+ t_caret.size.y = caret_width;
+ } else {
+ t_caret.position.y = -TS->shaped_text_get_ascent(rid);
+ t_caret.size.y = h;
+ }
+ t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+
+ draw_rect(t_caret, cache.caret_color, false);
+ } else { // End of the line.
+ if (insert_mode) {
+ l_caret.position.y = TS->shaped_text_get_descent(rid);
+ l_caret.size.y = caret_width;
+ } else {
+ l_caret.position.y = -TS->shaped_text_get_ascent(rid);
+ l_caret.size.y = h;
+ }
+ l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ l_caret.size.x = cache.font->get_char_size('m', 0, cache.font_size).x;
- if (cursor.column == (last_wrap_column + j) && cursor.line == line && cursor_wrap_index == line_wrap_index && (char_ofs + char_margin) >= xmargin_beg) {
- cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y);
- cursor_pos.y += (get_row_height() - cache.font->get_height()) / 2;
+ draw_rect(l_caret, cache.caret_color, false);
+ }
+ } else {
+ // Normal caret.
+ if (l_caret != Rect2() && l_dir == TextServer::DIRECTION_AUTO) {
+ // Draw extra marker on top of mid caret.
+ Rect2 trect = Rect2(l_caret.position.x - 3 * caret_width, l_caret.position.y, 6 * caret_width, caret_width);
+ trect.position += Vector2(char_margin + ofs_x, ofs_y);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, cache.caret_color);
+ }
+ l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ l_caret.size.x = caret_width;
- if (insert_mode) {
- cursor_insert_offset_y = cache.font->get_height() - 3;
- cursor_pos.y += cursor_insert_offset_y;
- }
- if (ime_text.length() > 0) {
- int ofs = 0;
- while (true) {
- if (ofs >= ime_text.length()) {
- break;
- }
+ draw_rect(l_caret, cache.caret_color);
- char32_t cchar = ime_text[ofs];
- char32_t next = ime_text[ofs + 1];
- int im_char_width = cache.font->get_char_size(cchar, next).width;
+ t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ t_caret.size.x = caret_width;
- if ((char_ofs + char_margin + im_char_width) >= xmargin_end) {
- break;
+ draw_rect(t_caret, cache.caret_color);
}
-
- bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y;
- if (selected) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color);
- } else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color);
+ }
+ } else {
+ {
+ // IME intermidiet text range.
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, cursor.column, cursor.column + ime_text.length());
+ 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, text_height);
+ if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
+ continue;
+ }
+ if (rect.position.x < xmargin_beg) {
+ rect.size.x -= (xmargin_beg - rect.position.x);
+ rect.position.x = xmargin_beg;
+ } else if (rect.position.x + rect.size.x > xmargin_end) {
+ rect.size.x = xmargin_end - rect.position.x;
+ }
+ rect.size.y = caret_width;
+ draw_rect(rect, cache.caret_color);
+ cursor_pos.x = rect.position.x;
}
-
- cache.font->draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, cache.font_size, color);
-
- char_ofs += im_char_width;
- ofs++;
}
- }
- if (ime_text.length() == 0) {
- if (draw_caret) {
- if (insert_mode) {
- int char_w = cache.font->get_char_size(' ').width;
-#ifdef TOOLS_ENABLED
- int caret_h = (block_caret) ? 4 : 2 * EDSCALE;
-#else
- int caret_h = (block_caret) ? 4 : 2;
-#endif
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(char_w, caret_h)), cache.caret_color);
- } else {
- int char_w = cache.font->get_char_size(' ').width;
-#ifdef TOOLS_ENABLED
- int caret_w = (block_caret) ? char_w : 2 * EDSCALE;
-#else
- int caret_w = (block_caret) ? char_w : 2;
-#endif
-
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, cache.font->get_height())), cache.caret_color);
+ {
+ // IME caret.
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, cursor.column + ime_selection.x, cursor.column + ime_selection.x + ime_selection.y);
+ 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, text_height);
+ if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
+ continue;
+ }
+ if (rect.position.x < xmargin_beg) {
+ rect.size.x -= (xmargin_beg - rect.position.x);
+ rect.position.x = xmargin_beg;
+ } else if (rect.position.x + rect.size.x > xmargin_end) {
+ rect.size.x = xmargin_end - rect.position.x;
+ }
+ rect.size.y = caret_width * 3;
+ draw_rect(rect, cache.caret_color);
+ cursor_pos.x = rect.position.x;
}
}
}
}
+ ofs_y += ldata->get_line_descent(line_wrap_index);
}
}
@@ -1363,14 +1441,14 @@ void TextEdit::_notification(int p_what) {
// Code completion box.
Ref<StyleBox> csb = get_theme_stylebox("completion");
int maxlines = get_theme_constant("completion_lines");
- int cmax_width = get_theme_constant("completion_max_width") * cache.font->get_char_size('x').x;
+ int cmax_width = get_theme_constant("completion_max_width") * cache.font->get_char_size('x', 0, cache.font_size).x;
int scrollw = get_theme_constant("completion_scroll_width");
Color scrollc = get_theme_color("completion_scroll_color");
const int completion_options_size = completion_options.size();
int lines = MIN(completion_options_size, maxlines);
int w = 0;
- int h = lines * get_row_height();
+ int h = lines * row_height;
int nofs = cache.font->get_string_size(completion_base, cache.font_size).width;
if (completion_options_size < 50) {
@@ -1386,7 +1464,7 @@ void TextEdit::_notification(int p_what) {
// Add space for completion icons.
const int icon_hsep = get_theme_constant("hseparation", "ItemList");
- Size2 icon_area_size(get_row_height(), get_row_height());
+ Size2 icon_area_size(row_height, row_height);
w += icon_area_size.width + icon_hsep;
int line_from = CLAMP(completion_index - lines / 2, 0, completion_options_size - lines);
@@ -1402,10 +1480,10 @@ void TextEdit::_notification(int p_what) {
int th = h + csb->get_minimum_size().y;
- if (cursor_pos.y + get_row_height() + th > get_size().height) {
+ if (cursor_pos.y + row_height + th > get_size().height) {
completion_rect.position.y = cursor_pos.y - th - (cache.line_spacing / 2.0f) - cursor_insert_offset_y;
} else {
- completion_rect.position.y = cursor_pos.y + cache.font->get_height() + (cache.line_spacing / 2.0f) + csb->get_offset().y - cursor_insert_offset_y;
+ completion_rect.position.y = cursor_pos.y + cache.font->get_height(cache.font_size) + (cache.line_spacing / 2.0f) + csb->get_offset().y - cursor_insert_offset_y;
completion_below = true;
}
@@ -1427,17 +1505,23 @@ void TextEdit::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(completion_rect.position, completion_rect.size + Size2(scrollw, 0)), cache.completion_background_color);
}
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(completion_rect.position.x, completion_rect.position.y + (completion_index - line_from) * get_row_height()), Size2(completion_rect.size.width, get_row_height())), cache.completion_selected_color);
+
draw_rect(Rect2(completion_rect.position + Vector2(icon_area_size.x + icon_hsep, 0), Size2(MIN(nofs, completion_rect.size.width - (icon_area_size.x + icon_hsep)), completion_rect.size.height)), cache.completion_existing_color);
for (int i = 0; i < lines; i++) {
int l = line_from + i;
ERR_CONTINUE(l < 0 || l >= completion_options_size);
- int yofs = (get_row_height() - cache.font->get_height()) / 2;
- Point2 title_pos(completion_rect.position.x, completion_rect.position.y + i * get_row_height() + cache.font->get_ascent() + yofs);
+
+ Ref<TextLine> tl;
+ tl.instance();
+ tl->add_string(completion_options[l].display, cache.font, cache.font_size);
+
+ int yofs = (row_height - tl->get_size().y) / 2;
+ Point2 title_pos(completion_rect.position.x, completion_rect.position.y + i * row_height + yofs);
// Draw completion icon if it is valid.
Ref<Texture2D> icon = completion_options[l].icon;
- Rect2 icon_area(completion_rect.position.x, completion_rect.position.y + i * get_row_height(), icon_area_size.width, icon_area_size.height);
+ Rect2 icon_area(completion_rect.position.x, completion_rect.position.y + i * row_height, icon_area_size.width, icon_area_size.height);
if (icon.is_valid()) {
const real_t max_scale = 0.7f;
const real_t side = max_scale * icon_area.size.width;
@@ -1448,11 +1532,20 @@ void TextEdit::_notification(int p_what) {
title_pos.x = icon_area.position.x + icon_area.size.width + icon_hsep;
- if (completion_options[l].default_value.get_type() == Variant::COLOR) {
- draw_rect(Rect2(Point2(completion_rect.position.x + completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)completion_options[l].default_value);
- }
+ tl->set_width(completion_rect.size.width - (icon_area_size.x + icon_hsep));
- draw_string(cache.font, title_pos, completion_options[l].display, HALIGN_LEFT, completion_rect.size.width - (icon_area_size.x + icon_hsep), cache.font_size, completion_options[l].font_color);
+ if (rtl) {
+ if (completion_options[l].default_value.get_type() == Variant::COLOR) {
+ draw_rect(Rect2(Point2(completion_rect.position.x, icon_area.position.y), icon_area_size), (Color)completion_options[l].default_value);
+ }
+ tl->set_align(HALIGN_RIGHT);
+ } else {
+ if (completion_options[l].default_value.get_type() == Variant::COLOR) {
+ draw_rect(Rect2(Point2(completion_rect.position.x + completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)completion_options[l].default_value);
+ }
+ tl->set_align(HALIGN_LEFT);
+ }
+ tl->draw(ci, title_pos, completion_options[l].font_color);
}
if (scrollw) {
@@ -1499,7 +1592,7 @@ void TextEdit::_notification(int p_what) {
}
}
- Size2 size2 = Size2(max_w, sc * font->get_height() + spacing);
+ Size2 size2 = Size2(max_w, sc * font->get_height(cache.font_size) + spacing);
Size2 minsize = size2 + sb->get_minimum_size();
if (completion_hint_offset == -0xFFFF) {
@@ -1509,7 +1602,7 @@ void TextEdit::_notification(int p_what) {
Point2 hint_ofs = Vector2(completion_hint_offset, cursor_pos.y) + callhint_offset;
if (callhint_below) {
- hint_ofs.y += get_row_height() + sb->get_offset().y;
+ hint_ofs.y += row_height + sb->get_offset().y;
} else {
hint_ofs.y -= minsize.y + sb->get_offset().y;
}
@@ -1527,11 +1620,11 @@ void TextEdit::_notification(int p_what) {
end = font->get_string_size(l.substr(0, l.rfind(String::chr(0xFFFF))), cache.font_size).x;
}
- Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent() + font->get_height() * i + spacing);
+ Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(cache.font_size) + font->get_height(cache.font_size) * i + spacing);
round_ofs = round_ofs.round();
draw_string(font, round_ofs, l.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, cache.font_size, font_color);
if (end > 0) {
- Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font->get_height() + font->get_height() * i + spacing - 1);
+ Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font->get_height(cache.font_size) + font->get_height(cache.font_size) * i + spacing - 1);
draw_line(b, b + Vector2(end - begin, 0), font_color);
}
spacing += cache.line_spacing;
@@ -1541,7 +1634,7 @@ void TextEdit::_notification(int p_what) {
if (has_focus()) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
- DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height()), get_viewport()->get_window_id());
+ DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
}
}
} break;
@@ -1554,8 +1647,7 @@ void TextEdit::_notification(int p_what) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
- Point2 cursor_pos = Point2(cursor_get_column(), cursor_get_line()) * get_row_height();
- DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
+ DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + _get_cursor_pixel_pos(), get_viewport()->get_window_id());
}
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
@@ -1585,6 +1677,7 @@ void TextEdit::_notification(int p_what) {
}
ime_text = "";
ime_selection = Point2();
+ text.invalidate_cache(cursor.line, cursor.column, ime_text);
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
@@ -1594,6 +1687,15 @@ void TextEdit::_notification(int p_what) {
if (has_focus()) {
ime_text = DisplayServer::get_singleton()->ime_get_text();
ime_selection = DisplayServer::get_singleton()->ime_get_selection();
+
+ String t;
+ if (cursor.column >= 0) {
+ t = text[cursor.line].substr(0, cursor.column) + ime_text + text[cursor.line].substr(cursor.column, text[cursor.line].length());
+ } else {
+ t = ime_text;
+ }
+
+ text.invalidate_cache(cursor.line, cursor.column, t, structured_text_parser(st_parser, st_args, t));
update();
}
} break;
@@ -1944,7 +2046,7 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co
}
if (row < 0) {
- row = 0; // TODO.
+ row = 0;
}
int col = 0;
@@ -1955,18 +2057,12 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co
} else {
int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + gutters_width + gutter_padding);
colx += cursor.x_ofs;
- col = get_char_pos_for_line(colx, row, wrap_index);
- if (is_wrap_enabled() && wrap_index < times_line_wraps(row)) {
- // Move back one if we are at the end of the row.
- Vector<String> rows2 = get_wrap_rows_text(row);
- int row_end_col = 0;
- for (int i = 0; i < wrap_index + 1; i++) {
- row_end_col += rows2[i].length();
- }
- if (col >= row_end_col) {
- col -= 1;
- }
+
+ RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index);
+ if (is_layout_rtl()) {
+ colx = TS->shaped_text_get_size(text_rid).x - colx;
}
+ col = TS->shaped_text_hit_test_position(text_rid, colx);
}
r_row = row;
@@ -1975,38 +2071,27 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co
Vector2i TextEdit::_get_cursor_pixel_pos() {
adjust_viewport_to_cursor();
- int row = (cursor.line - get_first_visible_line() - cursor.wrap_ofs);
- // Correct for hidden and wrapped lines
+ int row = 1;
for (int i = get_first_visible_line(); i < cursor.line; i++) {
- if (is_line_hidden(i)) {
- row -= 1;
- continue;
- }
- row += times_line_wraps(i);
- }
- // Row might be wrapped. Adjust row and r_column
- Vector<String> rows2 = get_wrap_rows_text(cursor.line);
- while (rows2.size() > 1) {
- if (cursor.column >= rows2[0].length()) {
- cursor.column -= rows2[0].length();
- rows2.remove(0);
- row++;
- } else {
- break;
+ if (!is_line_hidden(i)) {
+ row += times_line_wraps(i) + 1;
}
}
+ row += cursor.wrap_ofs;
// Calculate final pixel position
- int y = (row - get_v_scroll_offset() + 1 /*Bottom of line*/) * get_row_height();
+ int y = (row - get_v_scroll_offset()) * get_row_height();
int x = cache.style_normal->get_margin(MARGIN_LEFT) + gutters_width + gutter_padding - cursor.x_ofs;
- int ix = 0;
- while (ix < rows2[0].size() && ix < cursor.column) {
- if (cache.font != nullptr) {
- x += cache.font->get_char_size(rows2[0].get(ix), cache.font_size).width;
- }
- ix++;
+
+ Rect2 l_caret, t_caret;
+ TextServer::Direction l_dir, t_dir;
+ RID text_rid = text.get_line_data(cursor.line)->get_line_rid(cursor.wrap_ofs);
+ TS->shaped_text_get_carets(text_rid, cursor.column, l_caret, l_dir, t_caret, t_dir);
+ if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) {
+ x += l_caret.position.x;
+ } else {
+ x += t_caret.position.x;
}
- x += get_indent_level(cursor.line) * cache.font->get_char_size(' ', cache.font_size).width;
return Vector2i(x, y);
}
@@ -2071,7 +2156,15 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
Ref<InputEventMouseButton> mb = p_gui_input;
if (mb.is_valid()) {
- if (completion_active && completion_rect.has_point(mb->get_position())) {
+ Vector2i mpos = mb->get_position();
+ if (is_layout_rtl()) {
+ mpos.x = get_size().x - mpos.x;
+ }
+ if (ime_text.length() != 0) {
+ // Ignore mouse clicks in IME input mode.
+ return;
+ }
+ if (completion_active && completion_rect.has_point(mpos)) {
if (!mb->is_pressed()) {
return;
}
@@ -2092,7 +2185,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (mb->get_button_index() == BUTTON_LEFT) {
- completion_index = CLAMP(completion_line_ofs + (mb->get_position().y - completion_rect.position.y) / get_row_height(), 0, completion_options.size() - 1);
+ completion_index = CLAMP(completion_line_ofs + (mpos.y - completion_rect.position.y) / get_row_height(), 0, completion_options.size() - 1);
completion_current = completion_options[completion_index];
update();
@@ -2131,7 +2224,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
_reset_caret_blink_timer();
int row, col;
- _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col);
+ _get_mouse_pos(Point2i(mpos.x, mpos.y), row, col);
int left_margin = cache.style_normal->get_margin(MARGIN_LEFT);
for (int i = 0; i < gutters.size(); i++) {
@@ -2139,7 +2232,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
continue;
}
- if (mb->get_position().x > left_margin && mb->get_position().x <= (left_margin + gutters[i].width) - 3) {
+ if (mpos.x > left_margin && mpos.x <= (left_margin + gutters[i].width) - 3) {
emit_signal("gutter_clicked", row, i);
return;
}
@@ -2150,7 +2243,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
// Unfold on folded icon click.
if (is_folded(row)) {
left_margin += gutter_padding + text.get_line_width(row) - cursor.x_ofs;
- if (mb->get_position().x > left_margin && mb->get_position().x <= left_margin + cache.folded_eol_icon->get_width() + 3) {
+ if (mpos.x > left_margin && mpos.x <= left_margin + cache.folded_eol_icon->get_width() + 3) {
unfold_line(row);
return;
}
@@ -2241,7 +2334,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
_reset_caret_blink_timer();
int row, col;
- _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col);
+ _get_mouse_pos(Point2i(mpos.x, mpos.y), row, col);
if (is_right_click_moving_caret()) {
if (is_selection_active()) {
@@ -2261,7 +2354,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
- menu->set_position(get_screen_transform().xform(get_local_mouse_position()));
+ menu->set_position(get_screen_transform().xform(mpos));
menu->set_size(Vector2(1, 1));
// menu->set_scale(get_global_transform().get_scale());
menu->popup();
@@ -2271,7 +2364,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb->get_button_index() == BUTTON_LEFT) {
if (mb->get_command() && highlighted_word != String()) {
int row, col;
- _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col);
+ _get_mouse_pos(Point2i(mpos.x, mpos.y), row, col);
emit_signal("symbol_lookup", highlighted_word, row, col);
return;
@@ -2307,9 +2400,13 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
Ref<InputEventMouseMotion> mm = p_gui_input;
if (mm.is_valid()) {
+ Vector2i mpos = mm->get_position();
+ if (is_layout_rtl()) {
+ mpos.x = get_size().x - mpos.x;
+ }
if (select_identifiers_enabled) {
if (!dragging_minimap && !dragging_selection && mm->get_command() && mm->get_button_mask() == 0) {
- String new_word = get_word_at_pos(mm->get_position());
+ String new_word = get_word_at_pos(mpos);
if (new_word != highlighted_word) {
emit_signal("symbol_validate", new_word);
}
@@ -2363,7 +2460,8 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
#endif
if (select_identifiers_enabled) {
if (k->is_pressed() && !dragging_minimap && !dragging_selection) {
- emit_signal("symbol_validate", get_word_at_pos(get_local_mouse_position()));
+ Point2 mp = _get_local_mouse_pos();
+ emit_signal("symbol_validate", get_word_at_pos(mp));
} else {
set_highlighted_word(String());
}
@@ -2701,7 +2799,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
continue;
}
- if (indent_char_found && is_line_comment(i)) {
+ if (indent_char_found && is_line_comment(cursor.line)) {
should_indent = true;
break;
} else if (indent_char_found && !_is_whitespace(c)) {
@@ -2844,34 +2942,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
int line = cursor.line;
int column = cursor.column;
- // Check if we are removing a single whitespace, if so remove it and the next char type,
- // else we just remove the whitespace.
- bool only_whitespace = false;
- if (_is_whitespace(text[line][column - 1]) && _is_whitespace(text[line][column - 2])) {
- only_whitespace = true;
- } else if (_is_whitespace(text[line][column - 1])) {
- // Remove the single whitespace.
- column--;
- }
-
- // Check if its a text char.
- bool only_char = (_is_text_char(text[line][column - 1]) && !only_whitespace);
-
- // If its not whitespace or char then symbol.
- bool only_symbols = !(only_whitespace || only_char);
-
- while (column > 0) {
- bool is_whitespace = _is_whitespace(text[line][column - 1]);
- bool is_text_char = _is_text_char(text[line][column - 1]);
-
- if (only_whitespace && !is_whitespace) {
- break;
- } else if (only_char && !is_text_char) {
- break;
- } else if (only_symbols && (is_whitespace || is_text_char)) {
+ Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
+ for (int i = words.size() - 1; i >= 0; i--) {
+ if (words[i].x < column) {
+ column = words[i].x;
break;
}
- column--;
}
_remove_text(line, column, cursor.line, cursor.column);
@@ -2941,17 +3017,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
cursor_set_line(cursor.line - 1);
cursor_set_column(text[cursor.line].length());
} else {
- bool prev_char = false;
-
- while (cc > 0) {
- bool ischar = _is_text_char(text[cursor.line][cc - 1]);
-
- if (prev_char && !ischar) {
+ Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(cursor.line)->get_rid());
+ for (int i = words.size() - 1; i >= 0; i--) {
+ if (words[i].x < cc) {
+ cc = words[i].x;
break;
}
-
- prev_char = ischar;
- cc--;
}
cursor_set_column(cc);
}
@@ -2962,7 +3033,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
cursor_set_column(text[cursor.line].length());
}
} else {
- cursor_set_column(cursor_get_column() - 1);
+ if (mid_grapheme_caret_enabled) {
+ cursor_set_column(cursor_get_column() - 1);
+ } else {
+ cursor_set_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(cursor.line)->get_rid(), cursor_get_column()));
+ }
}
if (k->get_shift()) {
@@ -3004,16 +3079,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
cursor_set_line(cursor.line + 1);
cursor_set_column(0);
} else {
- bool prev_char = false;
-
- while (cc < text[cursor.line].length()) {
- bool ischar = _is_text_char(text[cursor.line][cc]);
-
- if (prev_char && !ischar) {
+ Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(cursor.line)->get_rid());
+ for (int i = 0; i < words.size(); i++) {
+ if (words[i].y > cc) {
+ cc = words[i].y;
break;
}
- prev_char = ischar;
- cc++;
}
cursor_set_column(cc);
}
@@ -3024,7 +3095,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
cursor_set_column(0);
}
} else {
- cursor_set_column(cursor_get_column() + 1);
+ if (mid_grapheme_caret_enabled) {
+ cursor_set_column(cursor_get_column() + 1);
+ } else {
+ cursor_set_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(cursor.line)->get_rid(), cursor_get_column()));
+ }
}
if (k->get_shift()) {
@@ -3163,34 +3238,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
int line = cursor.line;
int column = cursor.column;
- // Check if we are removing a single whitespace, if so remove it and the next char type,
- // else we just remove the whitespace.
- bool only_whitespace = false;
- if (_is_whitespace(text[line][column]) && _is_whitespace(text[line][column + 1])) {
- only_whitespace = true;
- } else if (_is_whitespace(text[line][column])) {
- // Remove the single whitespace.
- column++;
- }
-
- // Check if its a text char.
- bool only_char = (_is_text_char(text[line][column]) && !only_whitespace);
-
- // If its not whitespace or char then symbol.
- bool only_symbols = !(only_whitespace || only_char);
-
- while (column < curline_len) {
- bool is_whitespace = _is_whitespace(text[line][column]);
- bool is_text_char = _is_text_char(text[line][column]);
-
- if (only_whitespace && !is_whitespace) {
- break;
- } else if (only_char && !is_text_char) {
- break;
- } else if (only_symbols && (is_whitespace || is_text_char)) {
+ Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
+ for (int i = 0; i < words.size(); i++) {
+ if (words[i].y > column) {
+ column = words[i].y;
break;
}
- column++;
}
next_line = line;
@@ -3201,7 +3254,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
next_line = cursor.line;
#endif
} else {
- next_column = cursor.column < curline_len ? (cursor.column + 1) : 0;
+ if (mid_grapheme_caret_enabled) {
+ next_column = cursor.column < curline_len ? (cursor.column + 1) : 0;
+ } else {
+ next_column = cursor.column < curline_len ? TS->shaped_text_next_grapheme_pos(text.get_line_data(cursor.line)->get_rid(), (cursor.column)) : 0;
+ }
}
_remove_text(cursor.line, cursor.column, next_line, next_column);
@@ -3431,6 +3488,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
completion_hint = "";
#endif
} break;
+ case (KEY_QUOTELEFT): { // Swap current input direction (primary cursor)
+ if (!k->get_command()) {
+ keycode_handled = false;
+ break;
+ }
+
+ if (input_direction == TEXT_DIRECTION_LTR) {
+ input_direction = TEXT_DIRECTION_RTL;
+ } else {
+ input_direction = TEXT_DIRECTION_LTR;
+ }
+ cursor_set_column(cursor.column);
+ update();
+ } break;
case KEY_X: {
if (readonly) {
break;
@@ -3518,9 +3589,8 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
case KEY_MENU: {
if (context_menu_enabled) {
- menu->set_position(get_global_transform().xform(_get_cursor_pixel_pos()));
+ menu->set_position(get_screen_transform().xform(_get_cursor_pixel_pos()));
menu->set_size(Vector2(1, 1));
- // menu->set_scale(get_global_transform().get_scale());
menu->popup();
menu->grab_focus();
}
@@ -3714,7 +3784,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
/* STEP 2: Add spaces if the char is greater than the end of the line. */
while (p_char > text[p_line].length()) {
- text.set(p_line, text[p_line] + String::chr(' '));
+ text.set(p_line, text[p_line] + String::chr(' '), structured_text_parser(st_parser, st_args, text[p_line] + String::chr(' ')));
}
/* STEP 3: Separate dest string in pre and post text. */
@@ -3726,13 +3796,13 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
// Insert the substrings.
if (j == 0) {
- text.set(p_line, preinsert_text + substrings[j]);
+ 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]);
+ 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);
+ text.set(p_line + j, text[p_line + j] + postinsert_text, structured_text_parser(st_parser, st_args, text[p_line + j] + postinsert_text));
}
}
@@ -3743,11 +3813,16 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
text.set_hidden(p_line, false);
}
- text.set_line_wrap_amount(p_line, -1);
+ text.invalidate_cache(p_line);
r_end_line = p_line + substrings.size() - 1;
r_end_column = text[r_end_line].length() - postinsert_text.length();
+ TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? cursor.column : 0, r_end_column);
+ if (dir != TextServer::DIRECTION_AUTO) {
+ input_direction = (TextDirection)dir;
+ }
+
if (!text_changed_dirty && !setting_text) {
if (is_inside_tree()) {
MessageQueue::get_singleton()->push_call(this, "_text_changed_emit");
@@ -3794,9 +3869,10 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li
for (int i = p_from_line; i < p_to_line; i++) {
text.remove(p_from_line + 1);
}
- text.set(p_from_line, pre_text + post_text);
+ text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text));
- text.set_line_wrap_amount(p_from_line, -1);
+ //text.set_line_wrap_amount(p_from_line, -1);
+ text.invalidate_cache(p_from_line);
if (!text_changed_dirty && !setting_text) {
if (is_inside_tree()) {
@@ -3970,6 +4046,13 @@ void TextEdit::_generate_context_menu() {
menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? KEY_MASK_CMD | KEY_Z : 0);
menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z : 0);
}
+ menu->add_separator();
+ menu->add_submenu_item(RTR("Text writing direction"), "DirMenu");
+ menu->add_separator();
+ menu->add_check_item(RTR("Display control characters"), MENU_DISPLAY_UCC);
+ if (!readonly) {
+ menu->add_submenu_item(RTR("Insert control character"), "CTLMenu");
+ }
}
int TextEdit::get_visible_rows() const {
@@ -3997,19 +4080,27 @@ int TextEdit::get_total_visible_rows() const {
return total_rows;
}
-void TextEdit::_update_wrap_at() {
- wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding - cache.minimap_width - wrap_right_offset;
- update_cursor_wrap_offset();
- text.clear_wrap_cache();
+void TextEdit::_update_wrap_at(bool p_force) {
+ int new_wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding;
+ if (draw_minimap) {
+ new_wrap_at -= minimap_width;
+ }
+ if (v_scroll->is_visible_in_tree()) {
+ new_wrap_at -= v_scroll->get_combined_minimum_size().width;
+ }
+ new_wrap_at -= wrap_right_offset; // Give it a little more space.
- for (int i = 0; i < text.size(); i++) {
- // Update all values that wrap.
- if (!line_wraps(i)) {
- continue;
+ if ((wrap_at != new_wrap_at) || p_force) {
+ wrap_at = new_wrap_at;
+ if (wrap_enabled) {
+ text.set_width(wrap_at);
+ } else {
+ text.set_width(-1);
}
- Vector<String> rows = get_wrap_rows_text(i);
- text.set_line_wrap_amount(i, rows.size() - 1);
+ text.invalidate_all_lines();
}
+
+ update_cursor_wrap_offset();
}
void TextEdit::adjust_viewport_to_cursor() {
@@ -4041,14 +4132,32 @@ void TextEdit::adjust_viewport_to_cursor() {
if (!is_wrap_enabled()) {
// Adjust x offset.
- int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]);
+ Vector2i cursor_pos;
+
+ // Get position of the start of caret.
+ if (ime_text.length() != 0 && ime_selection.x != 0) {
+ cursor_pos.x = get_column_x_offset_for_line(cursor.column + ime_selection.x, cursor.line);
+ } else {
+ cursor_pos.x = get_column_x_offset_for_line(cursor.column, cursor.line);
+ }
- if (cursor_x > (cursor.x_ofs + visible_width)) {
- cursor.x_ofs = cursor_x - visible_width + 1;
+ // Get position of the end of caret.
+ if (ime_text.length() != 0) {
+ if (ime_selection.y != 0) {
+ cursor_pos.y = get_column_x_offset_for_line(cursor.column + ime_selection.x + ime_selection.y, cursor.line);
+ } else {
+ cursor_pos.y = get_column_x_offset_for_line(cursor.column + ime_text.size(), cursor.line);
+ }
+ } else {
+ cursor_pos.y = cursor_pos.x;
}
- if (cursor_x < cursor.x_ofs) {
- cursor.x_ofs = cursor_x;
+ if (MAX(cursor_pos.x, cursor_pos.y) > (cursor.x_ofs + visible_width)) {
+ cursor.x_ofs = MAX(cursor_pos.x, cursor_pos.y) - visible_width + 1;
+ }
+
+ if (MIN(cursor_pos.x, cursor_pos.y) < cursor.x_ofs) {
+ cursor.x_ofs = MIN(cursor_pos.x, cursor_pos.y);
}
} else {
cursor.x_ofs = 0;
@@ -4076,14 +4185,33 @@ void TextEdit::center_viewport_to_cursor() {
if (is_wrap_enabled()) {
// Center x offset.
- int cursor_x = get_column_x_offset_for_line(cursor.column, cursor.line);
- if (cursor_x > (cursor.x_ofs + visible_width)) {
- cursor.x_ofs = cursor_x - visible_width + 1;
+ Vector2i cursor_pos;
+
+ // Get position of the start of caret.
+ if (ime_text.length() != 0 && ime_selection.x != 0) {
+ cursor_pos.x = get_column_x_offset_for_line(cursor.column + ime_selection.x, cursor.line);
+ } else {
+ cursor_pos.x = get_column_x_offset_for_line(cursor.column, cursor.line);
+ }
+
+ // Get position of the end of caret.
+ if (ime_text.length() != 0) {
+ if (ime_selection.y != 0) {
+ cursor_pos.y = get_column_x_offset_for_line(cursor.column + ime_selection.x + ime_selection.y, cursor.line);
+ } else {
+ cursor_pos.y = get_column_x_offset_for_line(cursor.column + ime_text.size(), cursor.line);
+ }
+ } else {
+ cursor_pos.y = cursor_pos.x;
}
- if (cursor_x < cursor.x_ofs) {
- cursor.x_ofs = cursor_x;
+ if (MAX(cursor_pos.x, cursor_pos.y) > (cursor.x_ofs + visible_width)) {
+ cursor.x_ofs = MAX(cursor_pos.x, cursor_pos.y) - visible_width + 1;
+ }
+
+ if (MIN(cursor_pos.x, cursor_pos.y) < cursor.x_ofs) {
+ cursor.x_ofs = MIN(cursor_pos.x, cursor_pos.y);
}
} else {
cursor.x_ofs = 0;
@@ -4108,24 +4236,17 @@ bool TextEdit::line_wraps(int line) const {
if (!is_wrap_enabled()) {
return false;
}
- return text.get_line_width(line) > wrap_at;
+ return text.get_line_wrap_amount(line) > 0;
}
int TextEdit::times_line_wraps(int line) const {
ERR_FAIL_INDEX_V(line, text.size(), 0);
+
if (!line_wraps(line)) {
return 0;
}
- int wrap_amount = text.get_line_wrap_amount(line);
- if (wrap_amount == -1) {
- // Update the value.
- Vector<String> rows = get_wrap_rows_text(line);
- wrap_amount = rows.size() - 1;
- text.set_line_wrap_amount(line, wrap_amount);
- }
-
- return wrap_amount;
+ return text.get_line_wrap_amount(line);
}
Vector<String> TextEdit::get_wrap_rows_text(int p_line) const {
@@ -4137,66 +4258,12 @@ Vector<String> TextEdit::get_wrap_rows_text(int p_line) const {
return lines;
}
- int px = 0;
- int col = 0;
- String line_text = text[p_line];
- String wrap_substring = "";
-
- int word_px = 0;
- String word_str = "";
- int cur_wrap_index = 0;
-
- int tab_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ', cache.font_size).width;
- if (tab_offset_px >= wrap_at) {
- tab_offset_px = 0;
+ const String &line_text = text[p_line];
+ Vector<Vector2i> line_ranges = text.get_line_wrap_ranges(p_line);
+ for (int i = 0; i < line_ranges.size(); i++) {
+ lines.push_back(line_text.substr(line_ranges[i].x, line_ranges[i].y - line_ranges[i].x));
}
- while (col < line_text.length()) {
- char32_t c = line_text[col];
- int w = text.get_char_width(c, line_text[col + 1], px + word_px);
-
- int indent_ofs = (cur_wrap_index != 0 ? tab_offset_px : 0);
-
- if (indent_ofs + word_px + w > wrap_at) {
- // Not enough space to add this char; start next line.
- wrap_substring += word_str;
- lines.push_back(wrap_substring);
- cur_wrap_index++;
- wrap_substring = "";
- px = 0;
-
- word_str = "";
- word_str += c;
- word_px = w;
- } else {
- word_str += c;
- word_px += w;
- if (c == ' ') {
- // End of a word; add this word to the substring.
- wrap_substring += word_str;
- px += word_px;
- word_str = "";
- word_px = 0;
- }
-
- if (indent_ofs + px + word_px > wrap_at) {
- // This word will be moved to the next line.
- lines.push_back(wrap_substring);
- // Reset for next wrap.
- cur_wrap_index++;
- wrap_substring = "";
- px = 0;
- }
- }
- col++;
- }
- // Line ends before hit wrap_at; add this word to the substring.
- wrap_substring += word_str;
- lines.push_back(wrap_substring);
-
- // Update cache.
- text.set_line_wrap_amount(p_line, lines.size() - 1);
-
return lines;
}
@@ -4226,6 +4293,14 @@ int TextEdit::get_line_wrap_index_at_col(int p_line, int p_column) const {
return wrap_index;
}
+void TextEdit::set_mid_grapheme_caret_enabled(const bool p_enabled) {
+ mid_grapheme_caret_enabled = p_enabled;
+}
+
+bool TextEdit::get_mid_grapheme_caret_enabled() const {
+ return mid_grapheme_caret_enabled;
+}
+
void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) {
if (p_col < 0) {
p_col = 0;
@@ -4371,7 +4446,7 @@ void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column
selection.selecting_line = p_line;
}
if (p_column >= 0) {
- ERR_FAIL_INDEX(p_line, text[selection.selecting_line].length());
+ ERR_FAIL_INDEX(p_column, text[selection.selecting_line].length());
selection.selecting_column = p_column;
}
}
@@ -4423,101 +4498,47 @@ void TextEdit::_scroll_moved(double p_to_val) {
}
int TextEdit::get_row_height() const {
- return cache.font->get_height() + cache.line_spacing;
+ int height = cache.font->get_height(cache.font_size);
+ for (int i = 0; i < text.size(); i++) {
+ for (int j = 0; j <= text.get_line_wrap_amount(i); j++) {
+ height = MAX(height, text.get_line_height(i, j));
+ }
+ }
+ return height + cache.line_spacing;
}
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);
+ p_wrap_index = MIN(p_wrap_index, text.get_line_data(p_line)->get_line_count() - 1);
- if (line_wraps(p_line)) {
- int line_wrap_amount = times_line_wraps(p_line);
- int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width;
- if (wrap_offset_px >= wrap_at) {
- wrap_offset_px = 0;
- }
- if (p_wrap_index > line_wrap_amount) {
- p_wrap_index = line_wrap_amount;
- }
- if (p_wrap_index > 0) {
- p_px -= wrap_offset_px;
- } else {
- p_wrap_index = 0;
- }
- Vector<String> rows = get_wrap_rows_text(p_line);
- int c_pos = get_char_pos_for(p_px, rows[p_wrap_index]);
- for (int i = 0; i < p_wrap_index; i++) {
- String s = rows[i];
- c_pos += s.length();
- }
-
- return c_pos;
- } else {
- return get_char_pos_for(p_px, text[p_line]);
+ RID text_rid = text.get_line_data(p_line)->get_line_rid(p_wrap_index);
+ if (is_layout_rtl()) {
+ p_px = TS->shaped_text_get_size(text_rid).x - p_px;
}
+ return TS->shaped_text_hit_test_position(text_rid, p_px);
}
int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const {
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
- if (line_wraps(p_line)) {
- int n_char = p_char;
- int col = 0;
- Vector<String> rows = get_wrap_rows_text(p_line);
- int wrap_index = 0;
- for (int i = 0; i < rows.size(); i++) {
- wrap_index = i;
- String s = rows[wrap_index];
- col += s.length();
- if (col > p_char) {
- break;
- }
- n_char -= s.length();
- }
- int px = get_column_x_offset(n_char, rows[wrap_index]);
-
- int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ', cache.font_size).width;
- if (wrap_offset_px >= wrap_at) {
- wrap_offset_px = 0;
- }
- if (wrap_index != 0) {
- px += wrap_offset_px;
- }
-
- return px;
- } else {
- return get_column_x_offset(p_char, text[p_line]);
- }
-}
-
-int TextEdit::get_char_pos_for(int p_px, String p_str) const {
- int px = 0;
- int c = 0;
-
- while (c < p_str.length()) {
- int w = text.get_char_width(p_str[c], p_str[c + 1], px);
-
- if (p_px < (px + w / 2)) {
+ int row = 0;
+ Vector<Vector2i> rows2 = text.get_line_wrap_ranges(p_line);
+ for (int i = 0; i < rows2.size(); i++) {
+ if ((p_char >= rows2[i].x) && (p_char < rows2[i].y)) {
+ row = i;
break;
}
- px += w;
- c++;
}
- return c;
-}
-
-int TextEdit::get_column_x_offset(int p_char, String p_str) const {
- int px = 0;
-
- for (int i = 0; i < p_char; i++) {
- if (i >= p_str.length()) {
- break;
- }
-
- px += text.get_char_width(p_str[i], p_str[i + 1], px);
+ Rect2 l_caret, t_caret;
+ TextServer::Direction l_dir, t_dir;
+ RID text_rid = text.get_line_data(p_line)->get_line_rid(row);
+ TS->shaped_text_get_carets(text_rid, cursor.column, l_caret, l_dir, t_caret, t_dir);
+ if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) {
+ return l_caret.position.x;
+ } else {
+ return t_caret.position.x;
}
-
- return px;
}
void TextEdit::insert_text_at_cursor(const String &p_text) {
@@ -4603,7 +4624,7 @@ void TextEdit::set_text(String p_text) {
update();
setting_text = false;
-};
+}
String TextEdit::get_text() {
String longthing;
@@ -4616,11 +4637,124 @@ String TextEdit::get_text() {
}
return longthing;
-};
+}
+
+void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+ if (st_parser != p_parser) {
+ st_parser = p_parser;
+ for (int i = 0; i < text.size(); i++) {
+ text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
+ }
+ update();
+ }
+}
+
+Control::StructuredTextParser TextEdit::get_structured_text_bidi_override() const {
+ return st_parser;
+}
+
+void TextEdit::set_structured_text_bidi_override_options(Array p_args) {
+ st_args = p_args;
+ for (int i = 0; i < text.size(); i++) {
+ text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
+ }
+ update();
+}
+
+Array TextEdit::get_structured_text_bidi_override_options() const {
+ return st_args;
+}
+
+void TextEdit::set_text_direction(Control::TextDirection p_text_direction) {
+ ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ if (text_direction != p_text_direction) {
+ text_direction = p_text_direction;
+ if (text_direction != TEXT_DIRECTION_AUTO && text_direction != TEXT_DIRECTION_INHERITED) {
+ input_direction = text_direction;
+ }
+ TextServer::Direction dir;
+ if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR;
+ } else {
+ dir = (TextServer::Direction)text_direction;
+ }
+ text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
+ text.invalidate_all();
+
+ menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED);
+ menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);
+ menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR);
+ menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL);
+ update();
+ }
+}
+
+Control::TextDirection TextEdit::get_text_direction() const {
+ return text_direction;
+}
+
+void TextEdit::clear_opentype_features() {
+ opentype_features.clear();
+ text.set_font_features(opentype_features);
+ text.invalidate_all();
+ update();
+}
+
+void TextEdit::set_opentype_feature(const String &p_name, int p_value) {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) {
+ opentype_features[tag] = p_value;
+ text.set_font_features(opentype_features);
+ text.invalidate_all();
+ update();
+ }
+}
+
+int TextEdit::get_opentype_feature(const String &p_name) const {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag)) {
+ return -1;
+ }
+ return opentype_features[tag];
+}
+
+void TextEdit::set_language(const String &p_language) {
+ if (language != p_language) {
+ language = p_language;
+ TextServer::Direction dir;
+ if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR;
+ } else {
+ dir = (TextServer::Direction)text_direction;
+ }
+ text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
+ text.invalidate_all();
+ update();
+ }
+}
+
+String TextEdit::get_language() const {
+ return language;
+}
+
+void TextEdit::set_draw_control_chars(bool p_draw_control_chars) {
+ if (draw_control_chars != p_draw_control_chars) {
+ draw_control_chars = p_draw_control_chars;
+ menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars);
+ text.set_draw_control_chars(draw_control_chars);
+ text.invalidate_all();
+ update();
+ }
+}
+
+bool TextEdit::get_draw_control_chars() const {
+ return draw_control_chars;
+}
String TextEdit::get_text_for_lookup_completion() {
int row, col;
- _get_mouse_pos(get_local_mouse_position(), row, col);
+ Point2i mp = _get_local_mouse_pos();
+ _get_mouse_pos(mp, row, col);
String longthing;
int len = text.size();
@@ -4695,32 +4829,6 @@ void TextEdit::set_readonly(bool p_readonly) {
readonly = p_readonly;
_generate_context_menu();
- // Reorganize context menu.
- menu->clear();
-
- if (!readonly) {
- menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z);
- menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z);
- }
-
- if (!readonly) {
- menu->add_separator();
- menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X);
- }
-
- menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C);
-
- if (!readonly) {
- menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V);
- }
-
- menu->add_separator();
- menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A);
-
- if (!readonly) {
- menu->add_item(RTR("Clear"), MENU_CLEAR);
- }
-
update();
}
@@ -4729,7 +4837,10 @@ bool TextEdit::is_readonly() const {
}
void TextEdit::set_wrap_enabled(bool p_wrap_enabled) {
- wrap_enabled = p_wrap_enabled;
+ if (wrap_enabled != p_wrap_enabled) {
+ wrap_enabled = p_wrap_enabled;
+ _update_wrap_at(true);
+ }
}
bool TextEdit::is_wrap_enabled() const {
@@ -4792,12 +4903,22 @@ void TextEdit::_update_caches() {
#else
cache.line_spacing = get_theme_constant("line_spacing");
#endif
- cache.row_height = cache.font->get_height() + cache.line_spacing;
cache.tab_icon = get_theme_icon("tab");
cache.space_icon = get_theme_icon("space");
cache.folded_eol_icon = get_theme_icon("GuiEllipsis", "EditorIcons");
+
+ TextServer::Direction dir;
+ if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR;
+ } else {
+ dir = (TextServer::Direction)text_direction;
+ }
+ text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
+ text.set_font_features(opentype_features);
+ text.set_draw_control_chars(draw_control_chars);
text.set_font(cache.font);
- text.clear_width_cache();
+ text.set_font_size(cache.font_size);
+ text.invalidate_all();
if (syntax_highlighter.is_valid()) {
syntax_highlighter->set_text_edit(this);
@@ -5204,27 +5325,13 @@ String TextEdit::get_selection_text() const {
}
String TextEdit::get_word_under_cursor() const {
- int prev_cc = cursor.column;
- while (prev_cc > 0) {
- bool is_char = _is_text_char(text[cursor.line][prev_cc - 1]);
- if (!is_char) {
- break;
+ Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(cursor.line)->get_rid());
+ for (int i = 0; i < words.size(); i++) {
+ if (words[i].x <= cursor.column && words[i].y > cursor.column) {
+ return text[cursor.line].substr(words[i].x, words[i].y - words[i].x);
}
- --prev_cc;
}
-
- int next_cc = cursor.column;
- while (next_cc < text[cursor.line].length()) {
- bool is_char = _is_text_char(text[cursor.line][next_cc]);
- if (!is_char) {
- break;
- }
- ++next_cc;
- }
- if (prev_cc == cursor.column || next_cc == cursor.column) {
- return "";
- }
- return text[cursor.line].substr(prev_cc, next_cc - prev_cc);
+ return "";
}
void TextEdit::set_search_text(const String &p_search_text) {
@@ -5520,7 +5627,7 @@ int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int vi
break;
}
}
- wrap_index = times_line_wraps(MIN(i, text.size() - 1)) - (num_visible - visible_amount);
+ wrap_index = times_line_wraps(MIN(i, text.size() - 1)) - MAX(0, num_visible - visible_amount);
} else {
visible_amount = ABS(visible_amount);
int i;
@@ -5535,7 +5642,7 @@ int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int vi
break;
}
}
- wrap_index = (num_visible - visible_amount);
+ wrap_index = MAX(0, num_visible - visible_amount);
}
wrap_index = MAX(wrap_index, 0);
return num_total;
@@ -5910,8 +6017,11 @@ bool TextEdit::is_indent_using_spaces() const {
void TextEdit::set_indent_size(const int p_size) {
ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0.");
- indent_size = p_size;
- text.set_indent_size(p_size);
+ 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++) {
@@ -5936,6 +6046,7 @@ bool TextEdit::is_drawing_tabs() const {
void TextEdit::set_draw_spaces(bool p_draw) {
draw_spaces = p_draw;
+ update();
}
bool TextEdit::is_drawing_spaces() const {
@@ -6506,6 +6617,9 @@ void TextEdit::set_line_length_guideline_hard_column(int p_column) {
void TextEdit::set_draw_minimap(bool p_draw) {
draw_minimap = p_draw;
+ if (draw_minimap != p_draw) {
+ _update_wrap_at();
+ }
update();
}
@@ -6515,6 +6629,9 @@ bool TextEdit::is_drawing_minimap() const {
void TextEdit::set_minimap_width(int p_minimap_width) {
minimap_width = p_minimap_width;
+ if (minimap_width != p_minimap_width) {
+ _update_wrap_at();
+ }
update();
}
@@ -6575,6 +6692,101 @@ void TextEdit::menu_option(int p_option) {
} break;
case MENU_REDO: {
redo();
+ } break;
+ case MENU_DIR_INHERITED: {
+ set_text_direction(TEXT_DIRECTION_INHERITED);
+ } break;
+ case MENU_DIR_AUTO: {
+ set_text_direction(TEXT_DIRECTION_AUTO);
+ } break;
+ case MENU_DIR_LTR: {
+ set_text_direction(TEXT_DIRECTION_LTR);
+ } break;
+ case MENU_DIR_RTL: {
+ set_text_direction(TEXT_DIRECTION_RTL);
+ } break;
+ case MENU_DISPLAY_UCC: {
+ set_draw_control_chars(!get_draw_control_chars());
+ } break;
+ case MENU_INSERT_LRM: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x200E));
+ }
+ } break;
+ case MENU_INSERT_RLM: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x200F));
+ }
+ } break;
+ case MENU_INSERT_LRE: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x202A));
+ }
+ } break;
+ case MENU_INSERT_RLE: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x202B));
+ }
+ } break;
+ case MENU_INSERT_LRO: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x202D));
+ }
+ } break;
+ case MENU_INSERT_RLO: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x202E));
+ }
+ } break;
+ case MENU_INSERT_PDF: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x202C));
+ }
+ } break;
+ case MENU_INSERT_ALM: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x061C));
+ }
+ } break;
+ case MENU_INSERT_LRI: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x2066));
+ }
+ } break;
+ case MENU_INSERT_RLI: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x2067));
+ }
+ } break;
+ case MENU_INSERT_FSI: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x2068));
+ }
+ } break;
+ case MENU_INSERT_PDI: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x2069));
+ }
+ } break;
+ case MENU_INSERT_ZWJ: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x200D));
+ }
+ } break;
+ case MENU_INSERT_ZWNJ: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x200C));
+ }
+ } break;
+ case MENU_INSERT_WJ: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x2060));
+ }
+ } break;
+ case MENU_INSERT_SHY: {
+ if (!readonly) {
+ insert_text_at_cursor(String::chr(0x00AD));
+ }
}
}
}
@@ -6636,11 +6848,64 @@ PopupMenu *TextEdit::get_menu() const {
return menu;
}
+bool TextEdit::_set(const StringName &p_name, const Variant &p_value) {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ double value = p_value;
+ if (value == -1) {
+ if (opentype_features.has(tag)) {
+ opentype_features.erase(tag);
+ text.set_font_features(opentype_features);
+ text.invalidate_all();
+ update();
+ }
+ } else {
+ if ((double)opentype_features[tag] != value) {
+ opentype_features[tag] = value;
+ text.set_font_features(opentype_features);
+ text.invalidate_all();
+ ;
+ update();
+ }
+ }
+ _change_notify();
+ return true;
+ }
+
+ return false;
+}
+
+bool TextEdit::_get(const StringName &p_name, Variant &r_ret) const {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ if (opentype_features.has(tag)) {
+ r_ret = opentype_features[tag];
+ return true;
+ } else {
+ r_ret = -1;
+ return true;
+ }
+ }
+ return false;
+}
+
+void TextEdit::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) {
+ String name = TS->tag_to_name(*ftr);
+ p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name));
+ }
+ p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+}
+
void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("_gui_input"), &TextEdit::_gui_input);
ClassDB::bind_method(D_METHOD("_cursor_changed_emit"), &TextEdit::_cursor_changed_emit);
ClassDB::bind_method(D_METHOD("_text_changed_emit"), &TextEdit::_text_changed_emit);
- ClassDB::bind_method(D_METHOD("_update_wrap_at"), &TextEdit::_update_wrap_at);
+ ClassDB::bind_method(D_METHOD("_update_wrap_at", "force"), &TextEdit::_update_wrap_at, DEFVAL(false));
BIND_ENUM_CONSTANT(SEARCH_MATCH_CASE);
BIND_ENUM_CONSTANT(SEARCH_WHOLE_WORDS);
@@ -6657,6 +6922,16 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("delete_line"),&TextEdit::delete_line);
*/
+ ClassDB::bind_method(D_METHOD("get_draw_control_chars"), &TextEdit::get_draw_control_chars);
+ ClassDB::bind_method(D_METHOD("set_draw_control_chars", "enable"), &TextEdit::set_draw_control_chars);
+ ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &TextEdit::set_text_direction);
+ ClassDB::bind_method(D_METHOD("get_text_direction"), &TextEdit::get_text_direction);
+ ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &TextEdit::set_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &TextEdit::get_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_opentype_features"), &TextEdit::clear_opentype_features);
+ 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("set_text", "text"), &TextEdit::set_text);
ClassDB::bind_method(D_METHOD("insert_text_at_cursor", "text"), &TextEdit::insert_text_at_cursor);
@@ -6665,6 +6940,11 @@ void TextEdit::_bind_methods() {
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("set_structured_text_bidi_override", "parser"), &TextEdit::set_structured_text_bidi_override);
+ ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &TextEdit::get_structured_text_bidi_override);
+ ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &TextEdit::set_structured_text_bidi_override_options);
+ ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &TextEdit::get_structured_text_bidi_override_options);
+
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));
ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true), DEFVAL(0));
@@ -6678,6 +6958,9 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("cursor_set_block_mode", "enable"), &TextEdit::cursor_set_block_mode);
ClassDB::bind_method(D_METHOD("cursor_is_block_mode"), &TextEdit::cursor_is_block_mode);
+ ClassDB::bind_method(D_METHOD("set_mid_grapheme_caret_enabled", "enabled"), &TextEdit::set_mid_grapheme_caret_enabled);
+ ClassDB::bind_method(D_METHOD("get_mid_grapheme_caret_enabled"), &TextEdit::get_mid_grapheme_caret_enabled);
+
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);
@@ -6802,6 +7085,9 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_minimap_width"), &TextEdit::get_minimap_width);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "readonly"), "set_readonly", "is_readonly");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs");
@@ -6830,6 +7116,11 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "cursor_set_blink_speed", "cursor_get_blink_speed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_moving_by_right_click"), "set_right_click_moves_caret", "is_right_click_moving_caret");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_mid_grapheme_caret_enabled", "get_mid_grapheme_caret_enabled");
+
+ ADD_GROUP("Structured Text", "structured_text_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
ADD_SIGNAL(MethodInfo("cursor_changed"));
ADD_SIGNAL(MethodInfo("text_changed"));
@@ -6848,6 +7139,27 @@ void TextEdit::_bind_methods() {
BIND_ENUM_CONSTANT(MENU_SELECT_ALL);
BIND_ENUM_CONSTANT(MENU_UNDO);
BIND_ENUM_CONSTANT(MENU_REDO);
+ BIND_ENUM_CONSTANT(MENU_DIR_INHERITED);
+ BIND_ENUM_CONSTANT(MENU_DIR_AUTO);
+ BIND_ENUM_CONSTANT(MENU_DIR_LTR);
+ BIND_ENUM_CONSTANT(MENU_DIR_RTL);
+ BIND_ENUM_CONSTANT(MENU_DISPLAY_UCC);
+ BIND_ENUM_CONSTANT(MENU_INSERT_LRM);
+ BIND_ENUM_CONSTANT(MENU_INSERT_RLM);
+ BIND_ENUM_CONSTANT(MENU_INSERT_LRE);
+ BIND_ENUM_CONSTANT(MENU_INSERT_RLE);
+ BIND_ENUM_CONSTANT(MENU_INSERT_LRO);
+ BIND_ENUM_CONSTANT(MENU_INSERT_RLO);
+ BIND_ENUM_CONSTANT(MENU_INSERT_PDF);
+ BIND_ENUM_CONSTANT(MENU_INSERT_ALM);
+ BIND_ENUM_CONSTANT(MENU_INSERT_LRI);
+ BIND_ENUM_CONSTANT(MENU_INSERT_RLI);
+ BIND_ENUM_CONSTANT(MENU_INSERT_FSI);
+ BIND_ENUM_CONSTANT(MENU_INSERT_PDI);
+ BIND_ENUM_CONSTANT(MENU_INSERT_ZWJ);
+ BIND_ENUM_CONSTANT(MENU_INSERT_ZWNJ);
+ BIND_ENUM_CONSTANT(MENU_INSERT_WJ);
+ BIND_ENUM_CONSTANT(MENU_INSERT_SHY);
BIND_ENUM_CONSTANT(MENU_MAX);
GLOBAL_DEF("gui/timers/text_edit_idle_detect_sec", 3);
@@ -6869,8 +7181,8 @@ TextEdit::TextEdit() {
wrap_right_offset = 10;
set_focus_mode(FOCUS_ALL);
_update_caches();
- cache.row_height = 1;
cache.line_spacing = 1;
+ cache.font_size = 16;
set_default_cursor_shape(CURSOR_IBEAM);
indent_size = 4;
@@ -6970,9 +7282,43 @@ TextEdit::TextEdit() {
shortcut_keys_enabled = true;
menu = memnew(PopupMenu);
add_child(menu);
+
+ menu_dir = memnew(PopupMenu);
+ menu_dir->set_name("DirMenu");
+ menu_dir->add_radio_check_item(RTR("Same as layout direction"), MENU_DIR_INHERITED);
+ menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO);
+ menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR);
+ menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL);
+ menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), true);
+ menu->add_child(menu_dir);
+
+ menu_ctl = memnew(PopupMenu);
+ menu_ctl->set_name("CTLMenu");
+ menu_ctl->add_item(RTR("Left-to-right mark (LRM)"), MENU_INSERT_LRM);
+ menu_ctl->add_item(RTR("Right-to-left mark (RLM)"), MENU_INSERT_RLM);
+ menu_ctl->add_item(RTR("Start of left-to-right embedding (LRE)"), MENU_INSERT_LRE);
+ menu_ctl->add_item(RTR("Start of right-to-left embedding (RLE)"), MENU_INSERT_RLE);
+ menu_ctl->add_item(RTR("Start of left-to-right override (LRO)"), MENU_INSERT_LRO);
+ menu_ctl->add_item(RTR("Start of right-to-left override (RLO)"), MENU_INSERT_RLO);
+ menu_ctl->add_item(RTR("Pop direction formatting (PDF)"), MENU_INSERT_PDF);
+ menu_ctl->add_separator();
+ menu_ctl->add_item(RTR("Arabic letter mark (ALM)"), MENU_INSERT_ALM);
+ menu_ctl->add_item(RTR("Left-to-right isolate (LRI)"), MENU_INSERT_LRI);
+ menu_ctl->add_item(RTR("Right-to-left isolate (RLI)"), MENU_INSERT_RLI);
+ menu_ctl->add_item(RTR("First strong isolate (FSI)"), MENU_INSERT_FSI);
+ menu_ctl->add_item(RTR("Pop direction isolate (PDI)"), MENU_INSERT_PDI);
+ menu_ctl->add_separator();
+ menu_ctl->add_item(RTR("Zero width joiner (ZWJ)"), MENU_INSERT_ZWJ);
+ menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
+ menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ);
+ menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY);
+ menu->add_child(menu_ctl);
+
readonly = true; // Initialise to opposite first, so we get past the early-out in set_readonly.
set_readonly(false);
menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
+ menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
+ menu_ctl->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
first_draw = true;
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 9255f853eb..c2fe4afec5 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -36,6 +36,7 @@
#include "scene/gui/scroll_bar.h"
#include "scene/main/timer.h"
#include "scene/resources/syntax_highlighter.h"
+#include "scene/resources/text_paragraph.h"
class TextEdit : public Control {
GDCLASS(TextEdit, Control);
@@ -87,47 +88,68 @@ private:
struct Line {
Vector<Gutter> gutters;
- int32_t width_cache;
+ String data;
+ Vector<Vector2i> bidi_override;
+ Ref<TextParagraph> data_buf;
+
bool marked;
bool hidden;
- int32_t wrap_amount_cache;
- String data;
+
Line() {
- width_cache = 0;
+ data_buf.instance();
+
marked = false;
hidden = false;
- wrap_amount_cache = 0;
}
};
private:
mutable Vector<Line> text;
Ref<Font> font;
+ int font_size = -1;
+
+ Dictionary opentype_features;
+ String language;
+ TextServer::Direction direction = TextServer::DIRECTION_AUTO;
+ bool draw_control_chars = false;
+
+ int width = -1;
+
int indent_size = 4;
int gutter_count = 0;
- void _update_line_cache(int p_line) const;
-
public:
void set_indent_size(int p_indent_size);
void set_font(const Ref<Font> &p_font);
+ void set_font_size(int p_font_size);
+ void set_font_features(const Dictionary &p_features);
+ void set_direction_and_language(TextServer::Direction p_direction, String p_language);
+ void set_draw_control_chars(bool p_draw_control_chars);
+
+ int get_line_height(int p_line, int p_wrap_index) const;
int get_line_width(int p_line) const;
int get_max_width(bool p_exclude_hidden = false) const;
- int get_char_width(char32_t c, char32_t next_c, int px) const;
- void set_line_wrap_amount(int p_line, int p_wrap_amount) const;
+
+ void set_width(float p_width);
int get_line_wrap_amount(int p_line) const;
- void set(int p_line, const String &p_text);
+ Vector<Vector2i> get_line_wrap_ranges(int p_line) const;
+ const Ref<TextParagraph> get_line_data(int p_line) const;
+
+ void set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override);
void set_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; }
void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; }
bool is_hidden(int p_line) const { return text[p_line].hidden; }
- void insert(int p_at, const String &p_text);
+ void insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override);
void remove(int p_at);
int size() const { return text.size(); }
void clear();
- void clear_width_cache();
- void clear_wrap_cache();
- _FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; }
+
+ void invalidate_cache(int p_line, int p_column = -1, const String &p_ime_text = String(), const Vector<Vector2i> &p_bidi_override = Vector<Vector2i>());
+ void invalidate_all();
+ void invalidate_all_lines();
+
+ _FORCE_INLINE_ const String &operator[](int p_line) const;
/* Gutters. */
void add_gutter(int p_at);
@@ -260,6 +282,14 @@ private:
// data
Text text;
+ Dictionary opentype_features;
+ String language;
+ TextDirection text_direction = TEXT_DIRECTION_AUTO;
+ TextDirection input_direction = TEXT_DIRECTION_LTR;
+ Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ Array st_args;
+ bool draw_control_chars = false;
+
uint32_t version;
uint32_t saved_version;
@@ -275,6 +305,7 @@ private:
bool window_has_focus;
bool block_caret;
bool right_click_moves_caret;
+ bool mid_grapheme_caret_enabled = false;
bool wrap_enabled;
int wrap_at;
@@ -356,7 +387,7 @@ private:
int _get_minimap_visible_rows() const;
void update_cursor_wrap_offset();
- void _update_wrap_at();
+ void _update_wrap_at(bool p_force = false);
bool line_wraps(int line) const;
int times_line_wraps(int line) const;
Vector<String> get_wrap_rows_text(int p_line) const;
@@ -376,8 +407,6 @@ private:
int get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const;
int get_column_x_offset_for_line(int p_char, int p_line) const;
- int get_char_pos_for(int p_px, String p_str) const;
- int get_column_x_offset(int p_char, String p_str) const;
void adjust_viewport_to_cursor();
double get_scroll_line_diff() const;
@@ -405,6 +434,8 @@ private:
Size2 get_minimum_size() const override;
int _get_control_height() const;
+ Point2 _get_local_mouse_pos() const;
+
void _reset_caret_blink_timer();
void _toggle_draw_caret();
@@ -425,6 +456,8 @@ private:
Dictionary _search_bind(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column) const;
PopupMenu *menu;
+ PopupMenu *menu_dir;
+ PopupMenu *menu_ctl;
void _clear();
void _cancel_completion();
@@ -465,11 +498,9 @@ protected:
Color search_result_border_color;
Color background_color;
- int row_height;
int line_spacing;
int minimap_width;
Cache() {
- row_height = 0;
line_spacing = 0;
minimap_width = 0;
}
@@ -488,6 +519,10 @@ protected:
static void _bind_methods();
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
public:
/* Syntax Highlighting. */
Ref<SyntaxHighlighter> get_syntax_highlighter();
@@ -542,6 +577,27 @@ public:
MENU_SELECT_ALL,
MENU_UNDO,
MENU_REDO,
+ MENU_DIR_INHERITED,
+ MENU_DIR_AUTO,
+ MENU_DIR_LTR,
+ MENU_DIR_RTL,
+ MENU_DISPLAY_UCC,
+ MENU_INSERT_LRM,
+ MENU_INSERT_RLM,
+ MENU_INSERT_LRE,
+ MENU_INSERT_RLE,
+ MENU_INSERT_LRO,
+ MENU_INSERT_RLO,
+ MENU_INSERT_PDF,
+ MENU_INSERT_ALM,
+ MENU_INSERT_LRI,
+ MENU_INSERT_RLI,
+ MENU_INSERT_FSI,
+ MENU_INSERT_PDI,
+ MENU_INSERT_ZWJ,
+ MENU_INSERT_ZWNJ,
+ MENU_INSERT_WJ,
+ MENU_INSERT_SHY,
MENU_MAX
};
@@ -565,6 +621,25 @@ public:
bool is_insert_text_operation();
+ void set_text_direction(TextDirection p_text_direction);
+ TextDirection get_text_direction() const;
+
+ void set_opentype_feature(const String &p_name, int p_value);
+ int get_opentype_feature(const String &p_name) const;
+ void clear_opentype_features();
+
+ void set_language(const String &p_language);
+ String get_language() const;
+
+ void set_draw_control_chars(bool p_draw_control_chars);
+ bool get_draw_control_chars() const;
+
+ void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
+ Control::StructuredTextParser get_structured_text_bidi_override() const;
+
+ void set_structured_text_bidi_override_options(Array p_args);
+ Array get_structured_text_bidi_override_options() const;
+
void set_highlighted_word(const String &new_word);
void set_text(String p_text);
void insert_text_at_cursor(const String &p_text);
@@ -617,6 +692,9 @@ public:
void center_viewport_to_cursor();
+ void set_mid_grapheme_caret_enabled(const bool p_enabled);
+ bool get_mid_grapheme_caret_enabled() const;
+
void cursor_set_column(int p_col, bool p_adjust_viewport = true);
void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0);