Skip to content

Commit d912220

Browse files
committed
Merge pull request #105656 from Koyper/fix_line_edit_text_edit_composite_character_backspace_delete
[LineEdit/TextEdit] Add composite character backspace delete and get composite character positions
2 parents 5d697c3 + b668f45 commit d912220

File tree

7 files changed

+139
-5
lines changed

7 files changed

+139
-5
lines changed

doc/classes/LineEdit.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,22 @@
133133
[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member Window.visible] property.
134134
</description>
135135
</method>
136+
<method name="get_next_composite_character_column" qualifiers="const">
137+
<return type="int" />
138+
<param index="0" name="column" type="int" />
139+
<description>
140+
Returns the correct column at the end of a composite character like ❤️‍🩹 (mending heart; Unicode: [code]U+2764 U+FE0F U+200D U+1FA79[/code]) which is comprised of more than one Unicode code point, if the caret is at the start of the composite character. Also returns the correct column with the caret at mid grapheme and for non-composite characters.
141+
[b]Note:[/b] To check at caret location use [code]get_next_composite_character_column(get_caret_column())[/code]
142+
</description>
143+
</method>
144+
<method name="get_previous_composite_character_column" qualifiers="const">
145+
<return type="int" />
146+
<param index="0" name="column" type="int" />
147+
<description>
148+
Returns the correct column at the start of a composite character like ❤️‍🩹 (mending heart; Unicode: [code]U+2764 U+FE0F U+200D U+1FA79[/code]) which is comprised of more than one Unicode code point, if the caret is at the end of the composite character. Also returns the correct column with the caret at mid grapheme and for non-composite characters.
149+
[b]Note:[/b] To check at caret location use [code]get_previous_composite_character_column(get_caret_column())[/code]
150+
</description>
151+
</method>
136152
<method name="get_scroll_offset" qualifiers="const">
137153
<return type="float" />
138154
<description>
@@ -246,6 +262,9 @@
246262
<member name="alignment" type="int" setter="set_horizontal_alignment" getter="get_horizontal_alignment" enum="HorizontalAlignment" default="0">
247263
Text alignment as defined in the [enum HorizontalAlignment] enum.
248264
</member>
265+
<member name="backspace_deletes_composite_character_enabled" type="bool" setter="set_backspace_deletes_composite_character_enabled" getter="is_backspace_deletes_composite_character_enabled" default="false">
266+
If [code]true[/code] and [member caret_mid_grapheme] is [code]false[/code], backspace deletes an entire composite character such as ❤️‍🩹, instead of deleting part of the composite character.
267+
</member>
249268
<member name="caret_blink" type="bool" setter="set_caret_blink_enabled" getter="is_caret_blink_enabled" default="false">
250269
If [code]true[/code], makes the caret blink.
251270
</member>

doc/classes/TextEdit.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,15 @@
517517
Returns the number of lines that may be drawn on the minimap.
518518
</description>
519519
</method>
520+
<method name="get_next_composite_character_column" qualifiers="const">
521+
<return type="int" />
522+
<param index="0" name="line" type="int" />
523+
<param index="1" name="column" type="int" />
524+
<description>
525+
Returns the correct column at the end of a composite character like ❤️‍🩹 (mending heart; Unicode: [code]U+2764 U+FE0F U+200D U+1FA79[/code]) which is comprised of more than one Unicode code point, if the caret is at the start of the composite character. Also returns the correct column with the caret at mid grapheme and for non-composite characters.
526+
[b]Note:[/b] To check at caret location use [code]get_next_composite_character_column(get_caret_line(), get_caret_column())[/code]
527+
</description>
528+
</method>
520529
<method name="get_next_visible_line_index_offset_from" qualifiers="const">
521530
<return type="Vector2i" />
522531
<param index="0" name="line" type="int" />
@@ -543,6 +552,15 @@
543552
[b]Note:[/b] The Y position corresponds to the bottom side of the line. Use [method get_rect_at_line_column] to get the top side position.
544553
</description>
545554
</method>
555+
<method name="get_previous_composite_character_column" qualifiers="const">
556+
<return type="int" />
557+
<param index="0" name="line" type="int" />
558+
<param index="1" name="column" type="int" />
559+
<description>
560+
Returns the correct column at the start of a composite character like ❤️‍🩹 (mending heart; Unicode: [code]U+2764 U+FE0F U+200D U+1FA79[/code]) which is comprised of more than one Unicode code point, if the caret is at the end of the composite character. Also returns the correct column with the caret at mid grapheme and for non-composite characters.
561+
[b]Note:[/b] To check at caret location use [code]get_previous_composite_character_column(get_caret_line(), get_caret_column())[/code]
562+
</description>
563+
</method>
546564
<method name="get_rect_at_line_column" qualifiers="const">
547565
<return type="Rect2i" />
548566
<param index="0" name="line" type="int" />
@@ -1265,6 +1283,9 @@
12651283
<member name="autowrap_mode" type="int" setter="set_autowrap_mode" getter="get_autowrap_mode" enum="TextServer.AutowrapMode" default="3">
12661284
If [member wrap_mode] is set to [constant LINE_WRAPPING_BOUNDARY], sets text wrapping mode. To see how each mode behaves, see [enum TextServer.AutowrapMode].
12671285
</member>
1286+
<member name="backspace_deletes_composite_character_enabled" type="bool" setter="set_backspace_deletes_composite_character_enabled" getter="is_backspace_deletes_composite_character_enabled" default="false">
1287+
If [code]true[/code] and [member caret_mid_grapheme] is [code]false[/code], backspace deletes an entire composite character such as ❤️‍🩹, instead of deleting part of the composite character.
1288+
</member>
12681289
<member name="caret_blink" type="bool" setter="set_caret_blink_enabled" getter="is_caret_blink_enabled" default="false">
12691290
If [code]true[/code], makes the caret blink.
12701291
</member>

scene/gui/code_edit.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,14 @@ void CodeEdit::_backspace_internal(int p_caret) {
808808
}
809809

810810
int from_line = to_column > 0 ? to_line : to_line - 1;
811-
int from_column = to_column > 0 ? (to_column - 1) : (get_line(to_line - 1).length());
811+
int from_column = 0;
812+
if (to_column == 0) {
813+
from_column = get_line(to_line - 1).length();
814+
} else if (TextEdit::is_caret_mid_grapheme_enabled() || !TextEdit::is_backspace_deletes_composite_character_enabled()) {
815+
from_column = to_column - 1;
816+
} else {
817+
from_column = TextEdit::get_previous_composite_character_column(to_line, to_column);
818+
}
812819

813820
merge_gutters(from_line, to_line);
814821

scene/gui/line_edit.cpp

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,11 +1956,14 @@ void LineEdit::delete_char() {
19561956
if (text.is_empty() || caret_column == 0) {
19571957
return;
19581958
}
1959-
1960-
text = text.left(caret_column - 1) + text.substr(caret_column);
1959+
int delete_char_offset = 1;
1960+
if (!caret_mid_grapheme_enabled && backspace_deletes_composite_character_enabled) {
1961+
delete_char_offset = caret_column - get_previous_composite_character_column(caret_column);
1962+
}
1963+
text = text.left(caret_column - delete_char_offset) + text.substr(caret_column);
19611964
_shape();
19621965

1963-
set_caret_column(get_caret_column() - 1);
1966+
set_caret_column(get_caret_column() - delete_char_offset);
19641967

19651968
_text_changed();
19661969
}
@@ -2214,6 +2217,24 @@ int LineEdit::get_caret_column() const {
22142217
return caret_column;
22152218
}
22162219

2220+
int LineEdit::get_next_composite_character_column(int p_column) const {
2221+
ERR_FAIL_INDEX_V(p_column, text.length() + 1, -1);
2222+
if (p_column == text.length()) {
2223+
return p_column;
2224+
} else {
2225+
return TS->shaped_text_next_character_pos(text_rid, p_column);
2226+
}
2227+
}
2228+
2229+
int LineEdit::get_previous_composite_character_column(int p_column) const {
2230+
ERR_FAIL_INDEX_V(p_column, text.length() + 1, -1);
2231+
if (p_column == 0) {
2232+
return 0;
2233+
} else {
2234+
return TS->shaped_text_prev_character_pos(text_rid, p_column);
2235+
}
2236+
}
2237+
22172238
void LineEdit::set_scroll_offset(float p_pos) {
22182239
scroll_offset = p_pos;
22192240
if (scroll_offset < 0.0) {
@@ -2630,6 +2651,14 @@ bool LineEdit::is_emoji_menu_enabled() const {
26302651
return emoji_menu_enabled;
26312652
}
26322653

2654+
void LineEdit::set_backspace_deletes_composite_character_enabled(bool p_enabled) {
2655+
backspace_deletes_composite_character_enabled = p_enabled;
2656+
}
2657+
2658+
bool LineEdit::is_backspace_deletes_composite_character_enabled() const {
2659+
return backspace_deletes_composite_character_enabled;
2660+
}
2661+
26332662
bool LineEdit::is_menu_visible() const {
26342663
return menu && menu->is_visible();
26352664
}
@@ -3093,6 +3122,8 @@ void LineEdit::_bind_methods() {
30933122
ClassDB::bind_method(D_METHOD("get_placeholder"), &LineEdit::get_placeholder);
30943123
ClassDB::bind_method(D_METHOD("set_caret_column", "position"), &LineEdit::set_caret_column);
30953124
ClassDB::bind_method(D_METHOD("get_caret_column"), &LineEdit::get_caret_column);
3125+
ClassDB::bind_method(D_METHOD("get_next_composite_character_column", "column"), &LineEdit::get_next_composite_character_column);
3126+
ClassDB::bind_method(D_METHOD("get_previous_composite_character_column", "column"), &LineEdit::get_previous_composite_character_column);
30963127
ClassDB::bind_method(D_METHOD("get_scroll_offset"), &LineEdit::get_scroll_offset);
30973128
ClassDB::bind_method(D_METHOD("set_expand_to_text_length_enabled", "enabled"), &LineEdit::set_expand_to_text_length_enabled);
30983129
ClassDB::bind_method(D_METHOD("is_expand_to_text_length_enabled"), &LineEdit::is_expand_to_text_length_enabled);
@@ -3125,6 +3156,8 @@ void LineEdit::_bind_methods() {
31253156
ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &LineEdit::is_context_menu_enabled);
31263157
ClassDB::bind_method(D_METHOD("set_emoji_menu_enabled", "enable"), &LineEdit::set_emoji_menu_enabled);
31273158
ClassDB::bind_method(D_METHOD("is_emoji_menu_enabled"), &LineEdit::is_emoji_menu_enabled);
3159+
ClassDB::bind_method(D_METHOD("set_backspace_deletes_composite_character_enabled", "enable"), &LineEdit::set_backspace_deletes_composite_character_enabled);
3160+
ClassDB::bind_method(D_METHOD("is_backspace_deletes_composite_character_enabled"), &LineEdit::is_backspace_deletes_composite_character_enabled);
31283161
ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enable"), &LineEdit::set_virtual_keyboard_enabled);
31293162
ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &LineEdit::is_virtual_keyboard_enabled);
31303163
ClassDB::bind_method(D_METHOD("set_virtual_keyboard_type", "type"), &LineEdit::set_virtual_keyboard_type);
@@ -3204,6 +3237,7 @@ void LineEdit::_bind_methods() {
32043237
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length_enabled", "is_expand_to_text_length_enabled");
32053238
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
32063239
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emoji_menu_enabled"), "set_emoji_menu_enabled", "is_emoji_menu_enabled");
3240+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "backspace_deletes_composite_character_enabled"), "set_backspace_deletes_composite_character_enabled", "is_backspace_deletes_composite_character_enabled");
32073241
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled");
32083242
ADD_PROPERTY(PropertyInfo(Variant::INT, "virtual_keyboard_type", PROPERTY_HINT_ENUM, "Default,Multiline,Number,Decimal,Phone,Email,Password,URL"), "set_virtual_keyboard_type", "get_virtual_keyboard_type");
32093243
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clear_button_enabled"), "set_clear_button_enabled", "is_clear_button_enabled");

scene/gui/line_edit.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ class LineEdit : public Control {
122122

123123
bool context_menu_enabled = true;
124124
bool emoji_menu_enabled = true;
125+
bool backspace_deletes_composite_character_enabled = false;
125126
PopupMenu *menu = nullptr;
126127
PopupMenu *menu_dir = nullptr;
127128
PopupMenu *menu_ctl = nullptr;
@@ -309,6 +310,9 @@ class LineEdit : public Control {
309310
void set_emoji_menu_enabled(bool p_enabled);
310311
bool is_emoji_menu_enabled() const;
311312

313+
void set_backspace_deletes_composite_character_enabled(bool p_enabled);
314+
bool is_backspace_deletes_composite_character_enabled() const;
315+
312316
void select(int p_from = 0, int p_to = -1);
313317
void select_all();
314318
void selection_delete();
@@ -345,6 +349,8 @@ class LineEdit : public Control {
345349

346350
void set_caret_column(int p_column);
347351
int get_caret_column() const;
352+
int get_next_composite_character_column(int p_column) const;
353+
int get_previous_composite_character_column(int p_column) const;
348354

349355
void set_max_length(int p_max_length);
350356
int get_max_length() const;

scene/gui/text_edit.cpp

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3724,6 +3724,14 @@ bool TextEdit::is_emoji_menu_enabled() const {
37243724
return emoji_menu_enabled;
37253725
}
37263726

3727+
void TextEdit::set_backspace_deletes_composite_character_enabled(bool p_enabled) {
3728+
backspace_deletes_composite_character_enabled = p_enabled;
3729+
}
3730+
3731+
bool TextEdit::is_backspace_deletes_composite_character_enabled() const {
3732+
return backspace_deletes_composite_character_enabled;
3733+
}
3734+
37273735
void TextEdit::set_shortcut_keys_enabled(bool p_enabled) {
37283736
shortcut_keys_enabled = p_enabled;
37293737
}
@@ -6057,6 +6065,26 @@ int TextEdit::get_selection_origin_column(int p_caret) const {
60576065
return carets[p_caret].selection.origin_column;
60586066
}
60596067

6068+
int TextEdit::get_next_composite_character_column(int p_line, int p_column) const {
6069+
ERR_FAIL_INDEX_V(p_line, text.size(), -1);
6070+
ERR_FAIL_INDEX_V(p_column, text[p_line].length() + 1, -1);
6071+
if (p_column == text[p_line].length()) {
6072+
return p_column;
6073+
} else {
6074+
return TS->shaped_text_next_character_pos(text.get_line_data(p_line)->get_rid(), (p_column));
6075+
}
6076+
}
6077+
6078+
int TextEdit::get_previous_composite_character_column(int p_line, int p_column) const {
6079+
ERR_FAIL_INDEX_V(p_line, text.size(), -1);
6080+
ERR_FAIL_INDEX_V(p_column, text[p_line].length() + 1, -1);
6081+
if (p_column == 0) {
6082+
return 0;
6083+
} else {
6084+
return TS->shaped_text_prev_character_pos(text.get_line_data(p_line)->get_rid(), p_column);
6085+
}
6086+
}
6087+
60606088
int TextEdit::get_selection_from_line(int p_caret) const {
60616089
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
60626090
if (!has_selection(p_caret)) {
@@ -6958,6 +6986,9 @@ void TextEdit::_bind_methods() {
69586986
ClassDB::bind_method(D_METHOD("set_emoji_menu_enabled", "enable"), &TextEdit::set_emoji_menu_enabled);
69596987
ClassDB::bind_method(D_METHOD("is_emoji_menu_enabled"), &TextEdit::is_emoji_menu_enabled);
69606988

6989+
ClassDB::bind_method(D_METHOD("set_backspace_deletes_composite_character_enabled", "enable"), &TextEdit::set_backspace_deletes_composite_character_enabled);
6990+
ClassDB::bind_method(D_METHOD("is_backspace_deletes_composite_character_enabled"), &TextEdit::is_backspace_deletes_composite_character_enabled);
6991+
69616992
ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enabled"), &TextEdit::set_shortcut_keys_enabled);
69626993
ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &TextEdit::is_shortcut_keys_enabled);
69636994

@@ -7151,6 +7182,8 @@ void TextEdit::_bind_methods() {
71517182

71527183
ClassDB::bind_method(D_METHOD("set_caret_column", "column", "adjust_viewport", "caret_index"), &TextEdit::set_caret_column, DEFVAL(true), DEFVAL(0));
71537184
ClassDB::bind_method(D_METHOD("get_caret_column", "caret_index"), &TextEdit::get_caret_column, DEFVAL(0));
7185+
ClassDB::bind_method(D_METHOD("get_next_composite_character_column", "line", "column"), &TextEdit::get_next_composite_character_column);
7186+
ClassDB::bind_method(D_METHOD("get_previous_composite_character_column", "line", "column"), &TextEdit::get_previous_composite_character_column);
71547187

71557188
ClassDB::bind_method(D_METHOD("get_caret_wrap_index", "caret_index"), &TextEdit::get_caret_wrap_index, DEFVAL(0));
71567189

@@ -7360,6 +7393,7 @@ void TextEdit::_bind_methods() {
73607393
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
73617394
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
73627395
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emoji_menu_enabled"), "set_emoji_menu_enabled", "is_emoji_menu_enabled");
7396+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "backspace_deletes_composite_character_enabled"), "set_backspace_deletes_composite_character_enabled", "is_backspace_deletes_composite_character_enabled");
73637397
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
73647398
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
73657399
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
@@ -7596,7 +7630,14 @@ void TextEdit::_backspace_internal(int p_caret) {
75967630
}
75977631

75987632
int from_line = to_column > 0 ? to_line : to_line - 1;
7599-
int from_column = to_column > 0 ? (to_column - 1) : (text[to_line - 1].length());
7633+
int from_column = 0;
7634+
if (to_column == 0) {
7635+
from_column = text[to_line - 1].length();
7636+
} else if (caret_mid_grapheme_enabled || !backspace_deletes_composite_character_enabled) {
7637+
from_column = to_column - 1;
7638+
} else {
7639+
from_column = get_previous_composite_character_column(to_line, to_column);
7640+
}
76007641

76017642
merge_gutters(from_line, to_line);
76027643

scene/gui/text_edit.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ class TextEdit : public Control {
340340
bool overtype_mode = false;
341341
bool context_menu_enabled = true;
342342
bool emoji_menu_enabled = true;
343+
bool backspace_deletes_composite_character_enabled = false;
343344
bool shortcut_keys_enabled = true;
344345
bool virtual_keyboard_enabled = true;
345346
bool middle_mouse_paste_enabled = true;
@@ -813,6 +814,9 @@ class TextEdit : public Control {
813814
void set_emoji_menu_enabled(bool p_enabled);
814815
bool is_emoji_menu_enabled() const;
815816

817+
void set_backspace_deletes_composite_character_enabled(bool p_enabled);
818+
bool is_backspace_deletes_composite_character_enabled() const;
819+
816820
void set_shortcut_keys_enabled(bool p_enabled);
817821
bool is_shortcut_keys_enabled() const;
818822

@@ -960,6 +964,8 @@ class TextEdit : public Control {
960964

961965
void set_caret_column(int p_column, bool p_adjust_viewport = true, int p_caret = 0);
962966
int get_caret_column(int p_caret = 0) const;
967+
int get_next_composite_character_column(int p_line, int p_column) const;
968+
int get_previous_composite_character_column(int p_line, int p_column) const;
963969

964970
int get_caret_wrap_index(int p_caret = 0) const;
965971

0 commit comments

Comments
 (0)