From 93a81dd7aa5c85fc15cb7bce082b934343da7fc7 Mon Sep 17 00:00:00 2001 From: kit Date: Wed, 15 May 2024 00:29:35 -0400 Subject: [PATCH] Fix TextEdit caret movement at start of wrapped lines --- scene/gui/text_edit.cpp | 17 ++-------- tests/scene/test_text_edit.h | 66 +++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 5cdb0c3d44f..422783b01bd 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -4328,25 +4328,12 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ return Point2i(text[row].length(), row); } - int col = 0; int colx = p_pos.x - (theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); colx += first_visible_col; if (!editable) { colx -= theme_cache.style_readonly->get_offset().x / 2; colx += theme_cache.style_normal->get_offset().x / 2; } - col = _get_char_pos_for_line(colx, row, wrap_index); - if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { - // Move back one if we are at the end of the row. - Vector rows2 = get_line_wrapped_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); float wrap_indent = (text.is_indent_wrapped_lines() && wrap_index > 0) ? get_indent_level(row) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width : 0.0; @@ -4355,7 +4342,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ } else { colx -= wrap_indent; } - col = TS->shaped_text_hit_test_position(text_rid, colx); + int col = TS->shaped_text_hit_test_position(text_rid, colx); if (!caret_mid_grapheme_enabled) { col = TS->shaped_text_closest_character_pos(text_rid, col); } @@ -7531,7 +7518,7 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column int row = 0; Vector 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)) { + if ((p_char >= rows2[i].x) && (p_char < rows2[i].y || (i == rows2.size() - 1 && p_char == rows2[i].y))) { row = i; break; } diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index b2d9f5100e8..69e27fe7a08 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -1763,6 +1763,28 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 4); + + // Wrapped lines. + text_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); + text_edit->set_text("this is some text\nfor selection"); + text_edit->set_size(Size2(110, 100)); + MessageQueue::get_singleton()->flush(); + + // Line 0 wraps: 'this is ', 'some text'. + // Line 1 wraps: 'for ', 'selection'. + CHECK(text_edit->is_line_wrapped(0)); + + // Select to the first character of a wrapped line. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 11).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 8).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "so"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 10); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 8); + CHECK(text_edit->is_dragging_cursor()); } SUBCASE("[TextEdit] mouse word select") { @@ -5713,6 +5735,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_count() == 2); MessageQueue::get_singleton()->flush(); + // Lines 0 and 4 are wrapped into 2 parts: 'this is ' and 'some'. CHECK(text_edit->is_line_wrapped(0)); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); @@ -5762,9 +5785,9 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - text_edit->set_caret_column(12, false); // Normal up over wrapped line to line 0. + text_edit->set_caret_column(12, false); SEND_GUI_ACTION("ui_text_caret_up"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 0); @@ -5777,6 +5800,23 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Normal up from column 0 to a wrapped line. + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(5); + text_edit->set_caret_column(0); + SEND_GUI_ACTION("ui_text_caret_up"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line() == 4); + CHECK(text_edit->get_caret_column() == 8); + CHECK_FALSE(text_edit->has_selection(0)); + + // Normal up to column 0 of a wrapped line. + SEND_GUI_ACTION("ui_text_caret_up"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line() == 4); + CHECK(text_edit->get_caret_column() == 0); + CHECK_FALSE(text_edit->has_selection(0)); } SUBCASE("[TextEdit] ui_text_caret_down") { @@ -5792,6 +5832,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { MessageQueue::get_singleton()->flush(); + // Lines 3 and 7 are wrapped into 2 parts: 'this is ' and 'some'. CHECK(text_edit->is_line_wrapped(3)); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); @@ -5841,9 +5882,9 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - text_edit->set_caret_column(7, false); // Normal down over wrapped line to last wrapped line. + text_edit->set_caret_column(7, false); SEND_GUI_ACTION("ui_text_caret_down"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 3); @@ -5856,6 +5897,23 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Normal down to column 0 of a wrapped line. + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(3); + text_edit->set_caret_column(0); + SEND_GUI_ACTION("ui_text_caret_down"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line() == 3); + CHECK(text_edit->get_caret_column() == 8); + CHECK_FALSE(text_edit->has_selection(0)); + + // Normal down out of visual column 0 of a wrapped line moves to start of next line. + SEND_GUI_ACTION("ui_text_caret_down"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line() == 4); + CHECK(text_edit->get_caret_column() == 0); + CHECK_FALSE(text_edit->has_selection(0)); } SUBCASE("[TextEdit] ui_text_caret_document_start") { @@ -7162,7 +7220,7 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") { CHECK(text_edit->get_caret_line(0) == 2); CHECK(text_edit->get_caret_column(0) == 5); CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 10); + CHECK(text_edit->get_caret_column(1) == 6); // Cannot add caret below from last line last line wrap. text_edit->add_caret_at_carets(true); @@ -7171,7 +7229,7 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") { CHECK(text_edit->get_caret_line(0) == 2); CHECK(text_edit->get_caret_column(0) == 5); CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 10); + CHECK(text_edit->get_caret_column(1) == 6); // Add caret above from not first line wrap. text_edit->remove_secondary_carets();