diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index ea8185fff4af..6a1879baa4d4 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -101,6 +101,12 @@
Adjust the viewport so the caret is visible.
+
+
+
+ Applies text from the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME) to each caret and closes the IME if it is open.
+
+
@@ -114,6 +120,12 @@
Starts a multipart edit. All edits will be treated as one action until [method end_complex_operation] is called.
+
+
+
+ Closes the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME) if it is open. Any text in the IME will be lost.
+
+
@@ -611,7 +623,7 @@
- Returns if the user has IME text.
+ Returns [code]true[/code] if the user has text in the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME).
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index d8fb55ca5432..04228c441048 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1270,6 +1270,7 @@ void ScriptTextEditor::_gutter_clicked(int p_line, int p_gutter) {
void ScriptTextEditor::_edit_option(int p_op) {
CodeEdit *tx = code_editor->get_text_editor();
+ tx->apply_ime();
switch (p_op) {
case EDIT_UNDO: {
@@ -1960,6 +1961,8 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref &ev) {
}
if (create_menu) {
+ tx->apply_ime();
+
Point2i pos = tx->get_line_column_at_pos(local_pos);
int row = pos.y;
int col = pos.x;
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index a2364278b6f1..c9a0cbd2de90 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -348,6 +348,7 @@ void TextEditor::set_find_replace_bar(FindReplaceBar *p_bar) {
void TextEditor::_edit_option(int p_op) {
CodeEdit *tx = code_editor->get_text_editor();
+ tx->apply_ime();
switch (p_op) {
case EDIT_UNDO: {
@@ -502,6 +503,8 @@ void TextEditor::_text_edit_gui_input(const Ref &ev) {
if (mb->get_button_index() == MouseButton::RIGHT) {
CodeEdit *tx = code_editor->get_text_editor();
+ tx->apply_ime();
+
Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position());
int row = pos.y;
int col = pos.x;
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
index 98d83b6e955a..e158709a7533 100644
--- a/editor/plugins/text_shader_editor.cpp
+++ b/editor/plugins/text_shader_editor.cpp
@@ -628,6 +628,8 @@ ShaderTextEditor::ShaderTextEditor() {
/*** SCRIPT EDITOR ******/
void TextShaderEditor::_menu_option(int p_option) {
+ shader_editor->get_text_editor()->apply_ime();
+
switch (p_option) {
case EDIT_UNDO: {
shader_editor->get_text_editor()->undo();
@@ -978,6 +980,8 @@ void TextShaderEditor::_text_edit_gui_input(const Ref &ev) {
if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
CodeEdit *tx = shader_editor->get_text_editor();
+ tx->apply_ime();
+
Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position());
int row = pos.y;
int col = pos.x;
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index e63e1ee42a50..f50ac4a0eef5 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -253,8 +253,9 @@ void CodeEdit::_notification(int p_what) {
void CodeEdit::gui_input(const Ref &p_gui_input) {
Ref mb = p_gui_input;
if (mb.is_valid()) {
- /* Ignore mouse clicks in IME input mode. */
+ // Ignore mouse clicks in IME input mode, let TextEdit handle it.
if (has_ime_text()) {
+ TextEdit::gui_input(p_gui_input);
return;
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 540c99913175..81584d9020b4 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1489,14 +1489,7 @@ void TextEdit::_notification(int p_what) {
}
if (has_focus()) {
- if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
- Point2 pos = get_global_position() + get_caret_draw_pos();
- if (get_window()->get_embedder()) {
- pos += get_viewport()->get_popup_base_transform().get_origin();
- }
- DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id());
- }
+ _update_ime_window_position();
}
} break;
@@ -1507,14 +1500,7 @@ void TextEdit::_notification(int p_what) {
draw_caret = true;
}
- if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
- Point2 pos = get_global_position() + get_caret_draw_pos();
- if (get_window()->get_embedder()) {
- pos += get_viewport()->get_popup_base_transform().get_origin();
- }
- DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id());
- }
+ _update_ime_window_position();
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
int caret_start = -1;
@@ -1541,17 +1527,7 @@ void TextEdit::_notification(int p_what) {
caret_blink_timer->stop();
}
- if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
- DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
- }
- if (!ime_text.is_empty()) {
- ime_text = "";
- ime_selection = Point2();
- for (int i = 0; i < carets.size(); i++) {
- text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, ime_text);
- }
- }
+ apply_ime();
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
@@ -1571,15 +1547,8 @@ void TextEdit::_notification(int p_what) {
delete_selection();
}
- for (int i = 0; i < carets.size(); i++) {
- String t;
- if (get_caret_column(i) >= 0) {
- t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length());
- } else {
- t = ime_text;
- }
- text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t));
- }
+ _update_ime_text();
+ adjust_viewport_to_caret(0);
queue_redraw();
}
} break;
@@ -1681,10 +1650,6 @@ void TextEdit::gui_input(const Ref &p_gui_input) {
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 (mb->is_pressed()) {
if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) {
@@ -1718,6 +1683,8 @@ void TextEdit::gui_input(const Ref &p_gui_input) {
if (mb->get_button_index() == MouseButton::LEFT) {
_reset_caret_blink_timer();
+ apply_ime();
+
Point2i pos = get_line_column_at_pos(mpos);
int row = pos.y;
int col = pos.x;
@@ -1865,11 +1832,13 @@ void TextEdit::gui_input(const Ref &p_gui_input) {
}
if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ apply_ime();
paste_primary_clipboard();
}
if (mb->get_button_index() == MouseButton::RIGHT && (context_menu_enabled || is_move_caret_on_right_click_enabled())) {
_reset_caret_blink_timer();
+ apply_ime();
Point2i pos = get_line_column_at_pos(mpos);
int row = pos.y;
@@ -1909,6 +1878,11 @@ void TextEdit::gui_input(const Ref &p_gui_input) {
}
}
} else {
+ if (has_ime_text()) {
+ // Ignore mouse up in IME input mode.
+ return;
+ }
+
if (mb->get_button_index() == MouseButton::LEFT) {
if (selection_drag_attempt && is_mouse_over_selection()) {
remove_secondary_carets();
@@ -1967,7 +1941,7 @@ void TextEdit::gui_input(const Ref &p_gui_input) {
_update_minimap_drag();
}
- if (!dragging_minimap) {
+ if (!dragging_minimap && !has_ime_text()) {
switch (selecting_mode) {
case SelectionMode::SELECTION_MODE_POINTER: {
_update_selection_mode_pointer();
@@ -2012,6 +1986,7 @@ void TextEdit::gui_input(const Ref &p_gui_input) {
}
if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) {
+ apply_ime();
drag_caret_force_displayed = true;
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
set_caret_line(pos.y, false, true, 0, 0);
@@ -3030,6 +3005,43 @@ void TextEdit::_update_caches() {
}
}
+void TextEdit::_close_ime_window() {
+ if (get_viewport()->get_window_id() == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+ return;
+ }
+ DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
+ DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
+}
+
+void TextEdit::_update_ime_window_position() {
+ if (get_viewport()->get_window_id() == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+ return;
+ }
+ DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
+ Point2 pos = get_global_position() + get_caret_draw_pos();
+ if (get_window()->get_embedder()) {
+ pos += get_viewport()->get_popup_base_transform().get_origin();
+ }
+ // The window will move to the updated position the next time the IME is updated, not immediately.
+ DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id());
+}
+
+void TextEdit::_update_ime_text() {
+ if (has_ime_text()) {
+ // Update text to visually include IME text.
+ for (int i = 0; i < get_caret_count(); i++) {
+ String text_with_ime = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length());
+ text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, text_with_ime, structured_text_parser(st_parser, st_args, text_with_ime));
+ }
+ } else {
+ // Reset text.
+ for (int i = 0; i < get_caret_count(); i++) {
+ text.invalidate_cache(get_caret_line(i), get_caret_column(i), true);
+ }
+ }
+ queue_redraw();
+}
+
/* General overrides. */
Size2 TextEdit::get_minimum_size() const {
Size2 size = theme_cache.style_normal->get_minimum_size();
@@ -3189,6 +3201,26 @@ bool TextEdit::has_ime_text() const {
return !ime_text.is_empty();
}
+void TextEdit::cancel_ime() {
+ if (!has_ime_text()) {
+ return;
+ }
+ ime_text = String();
+ ime_selection = Point2();
+ _close_ime_window();
+ _update_ime_text();
+}
+
+void TextEdit::apply_ime() {
+ if (!has_ime_text()) {
+ return;
+ }
+ // Force apply the current IME text.
+ String insert_ime_text = ime_text;
+ cancel_ime();
+ insert_text_at_caret(insert_ime_text);
+}
+
void TextEdit::set_editable(const bool p_editable) {
if (editable == p_editable) {
return;
@@ -3568,16 +3600,8 @@ void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) {
adjust_carets_after_edit(i, new_line, new_column, from_line, from_col);
}
- if (!ime_text.is_empty()) {
- for (int i = 0; i < carets.size(); i++) {
- String t;
- if (get_caret_column(i) >= 0) {
- t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length());
- } else {
- t = ime_text;
- }
- text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t));
- }
+ if (has_ime_text()) {
+ _update_ime_text();
}
end_complex_operation();
@@ -5560,14 +5584,14 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) {
Vector2i caret_pos;
// Get position of the start of caret.
- if (ime_text.length() != 0 && ime_selection.x != 0) {
+ if (has_ime_text() && ime_selection.x != 0) {
caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
}
// Get position of the end of caret.
- if (ime_text.length() != 0) {
+ if (has_ime_text()) {
if (ime_selection.y != 0) {
caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
@@ -5612,14 +5636,14 @@ void TextEdit::center_viewport_to_caret(int p_caret) {
Vector2i caret_pos;
// Get position of the start of caret.
- if (ime_text.length() != 0 && ime_selection.x != 0) {
+ if (has_ime_text() && ime_selection.x != 0) {
caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
}
// Get position of the end of caret.
- if (ime_text.length() != 0) {
+ if (has_ime_text()) {
if (ime_selection.y != 0) {
caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
@@ -6029,6 +6053,8 @@ void TextEdit::_bind_methods() {
/* Text */
// Text properties
ClassDB::bind_method(D_METHOD("has_ime_text"), &TextEdit::has_ime_text);
+ ClassDB::bind_method(D_METHOD("cancel_ime"), &TextEdit::cancel_ime);
+ ClassDB::bind_method(D_METHOD("apply_ime"), &TextEdit::apply_ime);
ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &TextEdit::set_editable);
ClassDB::bind_method(D_METHOD("is_editable"), &TextEdit::is_editable);
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 8a541b623b47..d49be860a9c7 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -293,6 +293,10 @@ class TextEdit : public Control {
void _clear();
void _update_caches();
+ void _close_ime_window();
+ void _update_ime_window_position();
+ void _update_ime_text();
+
// User control.
bool overtype_mode = false;
bool context_menu_enabled = true;
@@ -696,6 +700,8 @@ class TextEdit : public Control {
/* Text */
// Text properties.
bool has_ime_text() const;
+ void cancel_ime();
+ void apply_ime();
void set_editable(const bool p_editable);
bool is_editable() const;