Browse Source

Merge pull request #101824 from bruvzg/wrap_align

[TextEdit] Use actual indentation offset instead of space width for wrapped lines.
Rémi Verschelde 6 months ago
parent
commit
3b8385a6be
3 changed files with 51 additions and 22 deletions
  1. 46 19
      scene/gui/text_edit.cpp
  2. 2 0
      scene/gui/text_edit.h
  3. 3 3
      tests/scene/test_text_edit.h

+ 46 - 19
scene/gui/text_edit.cpp

@@ -181,6 +181,36 @@ const Ref<TextParagraph> TextEdit::Text::get_line_data(int p_line) const {
 	return text[p_line].data_buf;
 }
 
+float TextEdit::Text::get_indent_offset(int p_line, bool p_rtl) const {
+	ERR_FAIL_INDEX_V(p_line, text.size(), 0);
+	Line &text_line = text.write[p_line];
+	if (text_line.indent_ofs < 0.0) {
+		int char_count = 0;
+		int line_length = text_line.data.size();
+		for (int i = 0; i < line_length - 1; i++) {
+			if (text_line.data[i] == '\t') {
+				char_count++;
+			} else if (text_line.data[i] == ' ') {
+				char_count++;
+			} else {
+				break;
+			}
+		}
+		RID text_rid = text_line.data_buf->get_line_rid(0);
+		float offset = (p_rtl) ? TS->shaped_text_get_size(text_rid).x : 0;
+		Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, 0, char_count);
+		for (const Vector2 v : sel) {
+			if (p_rtl) {
+				offset = MIN(v.x, MIN(v.y, offset));
+			} else {
+				offset = MAX(v.x, MAX(v.y, offset));
+			}
+		}
+		text_line.indent_ofs = (p_rtl) ? TS->shaped_text_get_size(text_rid).x - offset : offset;
+	}
+	return text_line.indent_ofs;
+}
+
 _FORCE_INLINE_ String TextEdit::Text::operator[](int p_line) const {
 	ERR_FAIL_INDEX_V(p_line, text.size(), "");
 	return text[p_line].data;
@@ -216,6 +246,7 @@ void TextEdit::Text::invalidate_cache(int p_line, bool p_text_changed) {
 	text_line.data_buf->set_break_flags(flags);
 	text_line.data_buf->set_preserve_control(draw_control_chars);
 	text_line.data_buf->set_custom_punctuation(get_enabled_word_separators());
+	text_line.indent_ofs = -1.0;
 
 	const String &text_with_ime = (!text_line.ime_data.is_empty()) ? text_line.ime_data : text_line.data;
 	const Array &bidi_override_with_ime = (!text_line.ime_data.is_empty()) ? text_line.ime_bidi_override : text_line.bidi_override;
@@ -1059,11 +1090,10 @@ void TextEdit::_notification(int p_what) {
 				int line_wrap_amount = draw_placeholder ? placeholder_wrapped_rows.size() - 1 : get_line_wrap_count(line);
 
 				int first_indent_line = 0;
-				float wrap_indent_line = 0.0;
 				if (text.is_indent_wrapped_lines()) {
-					wrap_indent_line = _get_wrapped_indent_level(line, first_indent_line) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
-					wrap_indent_line = MIN(wrap_indent_line, wrap_at_column * 0.6);
+					_get_wrapped_indent_level(line, first_indent_line);
 				}
+				float indent_ofs = MIN(text.get_indent_offset(line, rtl), wrap_at_column * 0.6);
 				for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) {
 					if (line_wrap_index != 0) {
 						i++;
@@ -1195,7 +1225,7 @@ void TextEdit::_notification(int p_what) {
 					// Draw line.
 					RID rid = ldata->get_line_rid(line_wrap_index);
 					float text_height = TS->shaped_text_get_size(rid).y;
-					float wrap_indent = line_wrap_index > first_indent_line ? wrap_indent_line : 0.0;
+					float wrap_indent = line_wrap_index > first_indent_line ? indent_ofs : 0.0;
 
 					if (rtl) {
 						char_margin = size.width - char_margin - (TS->shaped_text_get_size(rid).x + wrap_indent);
@@ -4526,14 +4556,14 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_clamp_line
 	}
 
 	RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index);
+
+	bool rtl = is_layout_rtl();
 	int first_indent_line = 0;
-	float wrap_indent_line = 0.0;
 	if (text.is_indent_wrapped_lines()) {
-		wrap_indent_line = _get_wrapped_indent_level(row, first_indent_line) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
-		wrap_indent_line = MIN(wrap_indent_line, wrap_at_column * 0.6);
+		_get_wrapped_indent_level(row, first_indent_line);
 	}
-	float wrap_indent = wrap_index > first_indent_line ? wrap_indent_line : 0.0;
-	if (is_layout_rtl()) {
+	float wrap_indent = wrap_index > first_indent_line ? MIN(text.get_indent_offset(row, rtl), wrap_at_column * 0.6) : 0.0;
+	if (rtl) {
 		colx = TS->shaped_text_get_size(text_rid).x - colx + wrap_indent;
 	} else {
 		colx -= wrap_indent;
@@ -7634,12 +7664,10 @@ int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) con
 
 	RID text_rid = text.get_line_data(p_line)->get_line_rid(p_wrap_index);
 	int first_indent_line = 0;
-	float wrap_indent_line = 0.0;
 	if (text.is_indent_wrapped_lines()) {
-		wrap_indent_line = _get_wrapped_indent_level(p_line, first_indent_line) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
-		wrap_indent_line = MIN(wrap_indent_line, wrap_at_column * 0.6);
+		_get_wrapped_indent_level(p_line, first_indent_line);
 	}
-	float wrap_indent = p_wrap_index > first_indent_line ? wrap_indent_line : 0.0;
+	float wrap_indent = p_wrap_index > first_indent_line ? MIN(text.get_indent_offset(p_line, is_layout_rtl()), wrap_at_column * 0.6) : 0.0;
 	if (is_layout_rtl()) {
 		p_px = TS->shaped_text_get_size(text_rid).x - p_px + wrap_indent;
 	} else {
@@ -7708,18 +7736,17 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column
 	}
 
 	RID text_rid = text.get_line_data(p_line)->get_line_rid(row);
+	bool rtl = is_layout_rtl();
 	int first_indent_line = 0;
-	float wrap_indent_line = 0.0;
 	if (text.is_indent_wrapped_lines()) {
-		wrap_indent_line = _get_wrapped_indent_level(p_line, first_indent_line) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
-		wrap_indent_line = MIN(wrap_indent_line, wrap_at_column * 0.6);
+		_get_wrapped_indent_level(p_line, first_indent_line);
 	}
-	float wrap_indent = row > first_indent_line ? wrap_indent_line : 0.0;
+	float wrap_indent = row > first_indent_line ? MIN(text.get_indent_offset(p_line, rtl), wrap_at_column * 0.6) : 0.0;
 	CaretInfo ts_caret = TS->shaped_text_get_carets(text_rid, p_column);
 	if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) {
-		return ts_caret.l_caret.position.x + (is_layout_rtl() ? -wrap_indent : wrap_indent);
+		return ts_caret.l_caret.position.x + (rtl ? -wrap_indent : wrap_indent);
 	} else {
-		return ts_caret.t_caret.position.x + (is_layout_rtl() ? -wrap_indent : wrap_indent);
+		return ts_caret.t_caret.position.x + (rtl ? -wrap_indent : wrap_indent);
 	}
 }
 

+ 2 - 0
scene/gui/text_edit.h

@@ -159,6 +159,7 @@ private:
 			int line_count = 0;
 			int height = 0;
 			int width = 0;
+			float indent_ofs = -1.0;
 
 			Line() {
 				data_buf.instantiate();
@@ -228,6 +229,7 @@ private:
 
 		Vector<Vector2i> get_line_wrap_ranges(int p_line) const;
 		const Ref<TextParagraph> get_line_data(int p_line) const;
+		float get_indent_offset(int p_line, bool p_rtl) const;
 
 		void set(int p_line, const String &p_text, const Array &p_bidi_override);
 		void set_ime(int p_line, const String &p_text, const Array &p_bidi_override);

+ 3 - 3
tests/scene/test_text_edit.h

@@ -7344,7 +7344,7 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") {
 		CHECK(text_edit->get_caret_line(1) == 1);
 		CHECK(text_edit->get_caret_column(1) == 1);
 		CHECK(text_edit->get_caret_line(2) == 0);
-		CHECK(text_edit->get_caret_column(2) == 8);
+		CHECK(text_edit->get_caret_column(2) == 7);
 
 		// Add caret above from first line and not first line wrap.
 		text_edit->add_caret_at_carets(false);
@@ -7355,9 +7355,9 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") {
 		CHECK(text_edit->get_caret_line(1) == 1);
 		CHECK(text_edit->get_caret_column(1) == 1);
 		CHECK(text_edit->get_caret_line(2) == 0);
-		CHECK(text_edit->get_caret_column(2) == 8);
+		CHECK(text_edit->get_caret_column(2) == 7);
 		CHECK(text_edit->get_caret_line(3) == 0);
-		CHECK(text_edit->get_caret_column(3) == 4);
+		CHECK(text_edit->get_caret_column(3) == 2);
 
 		// Cannot add caret above from first line first line wrap.
 		text_edit->remove_secondary_carets();