Преглед изворни кода

Merge pull request #96592 from bruvzg/alt_u

Add Control+Shift+U (rebindable) action for Unicode input, show hex as it is entered.
Rémi Verschelde пре 1 година
родитељ
комит
b60e16ff62

+ 5 - 0
core/input/input_map.cpp

@@ -400,6 +400,7 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
     { "ui_filedialog_refresh",                         TTRC("Refresh") },
     { "ui_filedialog_show_hidden",                     TTRC("Show Hidden") },
     { "ui_swap_input_direction ",                      TTRC("Swap Input Direction") },
+    { "ui_unicode_start",                              TTRC("Start Unicode Character Input") },
     { "",                                              ""}
 	/* clang-format on */
 };
@@ -754,6 +755,10 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
 	inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER));
 	default_builtin_cache.insert("ui_text_submit", inputs);
 
+	inputs = List<Ref<InputEvent>>();
+	inputs.push_back(InputEventKey::create_reference(Key::U | KeyModifierMask::CTRL | KeyModifierMask::SHIFT));
+	default_builtin_cache.insert("ui_unicode_start", inputs);
+
 	// ///// UI Graph Shortcuts /////
 
 	inputs = List<Ref<InputEvent>>();

+ 18 - 0
doc/classes/LineEdit.xml

@@ -37,6 +37,18 @@
 	<tutorials>
 	</tutorials>
 	<methods>
+		<method name="apply_ime">
+			<return type="void" />
+			<description>
+				Applies text from the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME) and closes the IME if it is open.
+			</description>
+		</method>
+		<method name="cancel_ime">
+			<return type="void" />
+			<description>
+				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.
+			</description>
+		</method>
 		<method name="clear">
 			<return type="void" />
 			<description>
@@ -133,6 +145,12 @@
 				Returns the selection end column.
 			</description>
 		</method>
+		<method name="has_ime_text" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if the user has text in the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME).
+			</description>
+		</method>
 		<method name="has_selection" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 4 - 0
doc/classes/ProjectSettings.xml

@@ -1386,6 +1386,10 @@
 			Default [InputEventAction] to undo the most recent action.
 			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
 		</member>
+		<member name="input/ui_unicode_start" type="Dictionary" setter="" getter="">
+			Default [InputEventAction] to start Unicode character hexadecimal code input in a text field.
+			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
+		</member>
 		<member name="input/ui_up" type="Dictionary" setter="" getter="">
 			Default [InputEventAction] to move up in the UI.
 			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.

+ 172 - 61
scene/gui/line_edit.cpp

@@ -61,16 +61,6 @@ void LineEdit::_edit() {
 	editing = true;
 	_validate_caret_can_draw();
 
-	DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
-	if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
-		DisplayServer::get_singleton()->window_set_ime_active(true, wid);
-		Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position();
-		if (get_window()->get_embedder()) {
-			pos += get_viewport()->get_popup_base_transform().get_origin();
-		}
-		DisplayServer::get_singleton()->window_set_ime_position(pos, wid);
-	}
-
 	show_virtual_keyboard();
 	queue_redraw();
 	emit_signal(SNAME("editing_toggled"), true);
@@ -84,14 +74,7 @@ void LineEdit::_unedit() {
 	editing = false;
 	_validate_caret_can_draw();
 
-	DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
-	if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
-		DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid);
-		DisplayServer::get_singleton()->window_set_ime_active(false, wid);
-	}
-	ime_text = "";
-	ime_selection = Point2();
-	_shape();
+	apply_ime();
 	set_caret_column(caret_column); // Update scroll_offset.
 
 	if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
@@ -109,6 +92,64 @@ bool LineEdit::is_editing() const {
 	return editing;
 }
 
+void LineEdit::_close_ime_window() {
+	DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
+	if (wid == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+		return;
+	}
+	DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid);
+	DisplayServer::get_singleton()->window_set_ime_active(false, wid);
+}
+
+void LineEdit::_update_ime_window_position() {
+	DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
+	if (wid == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+		return;
+	}
+	DisplayServer::get_singleton()->window_set_ime_active(true, wid);
+	Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position();
+	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, wid);
+}
+
+bool LineEdit::has_ime_text() const {
+	return !ime_text.is_empty();
+}
+
+void LineEdit::cancel_ime() {
+	if (!has_ime_text()) {
+		return;
+	}
+	ime_text = String();
+	ime_selection = Vector2i();
+	alt_start = false;
+	alt_start_no_hold = false;
+	_close_ime_window();
+	_shape();
+}
+
+void LineEdit::apply_ime() {
+	if (!has_ime_text()) {
+		return;
+	}
+
+	// Force apply the current IME text.
+	if (alt_start || alt_start_no_hold) {
+		cancel_ime();
+		if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+			char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
+			insert_text_at_caret(ucodestr);
+		}
+	} else {
+		String insert_ime_text = ime_text;
+		cancel_ime();
+		insert_text_at_caret(insert_ime_text);
+	}
+}
+
 void LineEdit::_swap_current_input_direction() {
 	if (input_direction == TEXT_DIRECTION_LTR) {
 		input_direction = TEXT_DIRECTION_RTL;
@@ -333,9 +374,10 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 
 	Ref<InputEventMouseButton> b = p_event;
 
-	// Ignore mouse clicks in IME input mode.
-	if (b.is_valid() && ime_text.is_empty()) {
+	if (b.is_valid()) {
 		if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {
+			apply_ime();
+
 			if (editable && !selection.enabled) {
 				set_caret_at_pixel_pos(b->get_position().x);
 			}
@@ -356,6 +398,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 		}
 
 		if (editable && is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+			apply_ime();
+
 			String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes();
 
 			deselect();
@@ -388,6 +432,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 		}
 
 		if (b->is_pressed()) {
+			apply_ime();
+
 			accept_event(); // Don't pass event further when clicked on text field.
 			if (editable && !text.is_empty() && _is_over_clear_button(b->get_position())) {
 				clear_button_status.press_attempt = true;
@@ -561,46 +607,111 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 		return;
 	}
 
-	if (!k->is_pressed()) {
-		if (alt_start && k->get_keycode() == Key::ALT) {
-			alt_start = false;
-			if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
-				char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
-				insert_text_at_caret(ucodestr);
-			}
-			accept_event();
-			return;
+	// Start Unicode input (hold).
+	if (k->is_alt_pressed() && k->get_keycode() == Key::KP_ADD && !alt_start && !alt_start_no_hold) {
+		if (selection.enabled) {
+			selection_delete();
 		}
+		alt_start = true;
+		alt_code = 0;
+		ime_text = "u";
+		ime_selection = Vector2i(0, -1);
+		_shape();
+		queue_redraw();
+		accept_event();
 		return;
 	}
 
-	// Alt + Unicode input:
-	if (k->is_alt_pressed()) {
-		if (!alt_start) {
-			if (k->get_keycode() == Key::KP_ADD) {
-				alt_start = true;
-				alt_code = 0;
-				accept_event();
-				return;
-			}
+	// Start Unicode input (press).
+	if (k->is_action("ui_unicode_start", true) && !alt_start && !alt_start_no_hold) {
+		if (selection.enabled) {
+			selection_delete();
+		}
+		alt_start_no_hold = true;
+		alt_code = 0;
+		ime_text = "u";
+		ime_selection = Vector2i(0, -1);
+		_shape();
+		queue_redraw();
+		accept_event();
+		return;
+	}
+
+	// Update Unicode input.
+	if (k->is_pressed() && ((k->is_alt_pressed() && alt_start) || alt_start_no_hold)) {
+		if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
+			alt_code = alt_code << 4;
+			alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
+		} else if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
+			alt_code = alt_code << 4;
+			alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
+		} else if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
+			alt_code = alt_code << 4;
+			alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
+		} else if ((Key)k->get_unicode() >= Key::KEY_0 && (Key)k->get_unicode() <= Key::KEY_9) {
+			alt_code = alt_code << 4;
+			alt_code += (uint32_t)((Key)k->get_unicode() - Key::KEY_0);
+		} else if ((Key)k->get_unicode() >= Key::A && (Key)k->get_unicode() <= Key::F) {
+			alt_code = alt_code << 4;
+			alt_code += (uint32_t)((Key)k->get_unicode() - Key::A) + 10;
+		} else if (k->get_physical_keycode() >= Key::KEY_0 && k->get_physical_keycode() <= Key::KEY_9) {
+			alt_code = alt_code << 4;
+			alt_code += (uint32_t)(k->get_physical_keycode() - Key::KEY_0);
+		}
+		if (k->get_keycode() == Key::BACKSPACE) {
+			alt_code = alt_code >> 4;
+		}
+		if (alt_code > 0x10ffff) {
+			alt_code = 0x10ffff;
+		}
+		if (alt_code > 0) {
+			ime_text = vformat("u%s", String::num_int64(alt_code, 16, true));
 		} else {
-			if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
-				alt_code = alt_code << 4;
-				alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
-			}
-			if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
-				alt_code = alt_code << 4;
-				alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
-			}
-			if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
-				alt_code = alt_code << 4;
-				alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
-			}
-			accept_event();
-			return;
+			ime_text = "u";
+		}
+		ime_selection = Vector2i(0, -1);
+		_shape();
+		queue_redraw();
+		accept_event();
+		return;
+	}
+
+	// Submit Unicode input.
+	if ((!k->is_pressed() && alt_start && k->get_keycode() == Key::ALT) || (alt_start_no_hold && (k->is_action("ui_text_submit", true) || k->is_action("ui_accept", true)))) {
+		alt_start = false;
+		alt_start_no_hold = false;
+		if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+			ime_text = String();
+			ime_selection = Vector2i();
+			char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
+			insert_text_at_caret(ucodestr);
+		} else {
+			ime_text = String();
+			ime_selection = Vector2i();
+			_shape();
 		}
+		queue_redraw();
+		accept_event();
+		return;
 	}
 
+	// Cancel Unicode input.
+	if (alt_start_no_hold && k->is_action("ui_cancel", true)) {
+		alt_start = false;
+		alt_start_no_hold = false;
+		ime_text = String();
+		ime_selection = Vector2i();
+		_shape();
+		queue_redraw();
+		accept_event();
+		return;
+	}
+
+	if (!k->is_pressed()) {
+		return;
+	}
+
+	// Open context menu.
 	if (context_menu_enabled) {
 		if (k->is_action("ui_menu", true)) {
 			_update_context_menu();
@@ -830,6 +941,8 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
 	Control::drop_data(p_point, p_data);
 
 	if (p_data.is_string() && is_editable()) {
+		apply_ime();
+
 		set_caret_at_pixel_pos(p_point.x);
 		int caret_column_tmp = caret_column;
 		bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end;
@@ -1213,15 +1326,7 @@ void LineEdit::_notification(int p_what) {
 			}
 
 			if (editing) {
-				DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
-				if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
-					DisplayServer::get_singleton()->window_set_ime_active(true, wid);
-					Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position();
-					if (get_window()->get_embedder()) {
-						pos += get_viewport()->get_popup_base_transform().get_origin();
-					}
-					DisplayServer::get_singleton()->window_set_ime_position(pos, wid);
-				}
+				_update_ime_window_position();
 			}
 		} break;
 
@@ -1745,6 +1850,8 @@ void LineEdit::clear() {
 }
 
 void LineEdit::show_virtual_keyboard() {
+	_update_ime_window_position();
+
 	if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
 		if (selection.enabled) {
 			DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), DisplayServer::VirtualKeyboardType(virtual_keyboard_type), max_length, selection.begin, selection.end);
@@ -2644,6 +2751,10 @@ void LineEdit::_validate_property(PropertyInfo &p_property) const {
 }
 
 void LineEdit::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("has_ime_text"), &LineEdit::has_ime_text);
+	ClassDB::bind_method(D_METHOD("cancel_ime"), &LineEdit::cancel_ime);
+	ClassDB::bind_method(D_METHOD("apply_ime"), &LineEdit::apply_ime);
+
 	ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &LineEdit::set_horizontal_alignment);
 	ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &LineEdit::get_horizontal_alignment);
 

+ 8 - 0
scene/gui/line_edit.h

@@ -92,6 +92,7 @@ private:
 	bool text_changed_dirty = false;
 
 	bool alt_start = false;
+	bool alt_start_no_hold = false;
 	uint32_t alt_code = 0;
 
 	String undo_text;
@@ -209,6 +210,9 @@ private:
 	void _edit();
 	void _unedit();
 
+	void _close_ime_window();
+	void _update_ime_window_position();
+
 	void _clear_undo_stack();
 	void _clear_redo();
 	void _create_undo_state();
@@ -263,6 +267,10 @@ protected:
 public:
 	bool is_editing() const;
 
+	bool has_ime_text() const;
+	void cancel_ime();
+	void apply_ime();
+
 	void set_horizontal_alignment(HorizontalAlignment p_alignment);
 	HorizontalAlignment get_horizontal_alignment() const;
 

+ 99 - 35
scene/gui/text_edit.cpp

@@ -1557,7 +1557,7 @@ void TextEdit::_notification(int p_what) {
 										carets.write[c].draw_pos.x = rect.position.x;
 									}
 								}
-								{
+								if (ime_selection.y > 0) {
 									// IME caret.
 									const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y);
 									for (int j = 0; j < sel.size(); j++) {
@@ -1688,39 +1688,93 @@ void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
 bool TextEdit::alt_input(const Ref<InputEvent> &p_gui_input) {
 	Ref<InputEventKey> k = p_gui_input;
 	if (k.is_valid()) {
-		if (!k->is_pressed()) {
-			if (alt_start && k->get_keycode() == Key::ALT) {
-				alt_start = false;
-				if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
-					handle_unicode_input(alt_code);
-				}
-				return true;
+		// Start Unicode input (hold).
+		if (k->is_alt_pressed() && k->get_keycode() == Key::KP_ADD && !alt_start && !alt_start_no_hold) {
+			if (has_selection()) {
+				delete_selection();
 			}
-			return false;
+			alt_start = true;
+			alt_code = 0;
+			ime_text = "u";
+			ime_selection = Vector2i(0, -1);
+			_update_ime_text();
+			return true;
 		}
 
-		if (k->is_alt_pressed()) {
-			if (!alt_start) {
-				if (k->get_keycode() == Key::KP_ADD) {
-					alt_start = true;
-					alt_code = 0;
-					return true;
-				}
+		// Start Unicode input (press).
+		if (k->is_action("ui_unicode_start", true) && !alt_start && !alt_start_no_hold) {
+			if (has_selection()) {
+				delete_selection();
+			}
+			alt_start_no_hold = true;
+			alt_code = 0;
+			ime_text = "u";
+			ime_selection = Vector2i(0, -1);
+			_update_ime_text();
+			return true;
+		}
+
+		// Update Unicode input.
+		if (k->is_pressed() && ((k->is_alt_pressed() && alt_start) || alt_start_no_hold)) {
+			if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
+				alt_code = alt_code << 4;
+				alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
+			} else if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
+				alt_code = alt_code << 4;
+				alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
+			} else if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
+				alt_code = alt_code << 4;
+				alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
+			} else if ((Key)k->get_unicode() >= Key::KEY_0 && (Key)k->get_unicode() <= Key::KEY_9) {
+				alt_code = alt_code << 4;
+				alt_code += (uint32_t)((Key)k->get_unicode() - Key::KEY_0);
+			} else if ((Key)k->get_unicode() >= Key::A && (Key)k->get_unicode() <= Key::F) {
+				alt_code = alt_code << 4;
+				alt_code += (uint32_t)((Key)k->get_unicode() - Key::A) + 10;
+			} else if (k->get_physical_keycode() >= Key::KEY_0 && k->get_physical_keycode() <= Key::KEY_9) {
+				alt_code = alt_code << 4;
+				alt_code += (uint32_t)(k->get_physical_keycode() - Key::KEY_0);
+			}
+			if (k->get_keycode() == Key::BACKSPACE) {
+				alt_code = alt_code >> 4;
+			}
+			if (alt_code > 0x10ffff) {
+				alt_code = 0x10ffff;
+			}
+			if (alt_code > 0) {
+				ime_text = vformat("u%s", String::num_int64(alt_code, 16, true));
 			} else {
-				if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
-					alt_code = alt_code << 4;
-					alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
-				}
-				if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
-					alt_code = alt_code << 4;
-					alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
-				}
-				if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
-					alt_code = alt_code << 4;
-					alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
-				}
-				return true;
+				ime_text = "u";
 			}
+			ime_selection = Vector2i(0, -1);
+			_update_ime_text();
+			return true;
+		}
+
+		// Submit Unicode input.
+		if ((!k->is_pressed() && alt_start && k->get_keycode() == Key::ALT) || (alt_start_no_hold && (k->is_action("ui_text_submit", true) || k->is_action("ui_accept", true)))) {
+			alt_start = false;
+			alt_start_no_hold = false;
+			if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+				ime_text = String();
+				ime_selection = Vector2i();
+				handle_unicode_input(alt_code);
+			} else {
+				ime_text = String();
+				ime_selection = Vector2i();
+			}
+			_update_ime_text();
+			return true;
+		}
+
+		// Cancel Unicode input.
+		if (alt_start_no_hold && k->is_action("ui_cancel", true)) {
+			alt_start = false;
+			alt_start_no_hold = false;
+			ime_text = String();
+			ime_selection = Vector2i();
+			_update_ime_text();
+			return true;
 		}
 	}
 	return false;
@@ -3117,7 +3171,9 @@ void TextEdit::cancel_ime() {
 		return;
 	}
 	ime_text = String();
-	ime_selection = Point2();
+	ime_selection = Vector2i();
+	alt_start = false;
+	alt_start_no_hold = false;
 	_close_ime_window();
 	_update_ime_text();
 }
@@ -3126,10 +3182,18 @@ 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);
+	if (alt_start || alt_start_no_hold) {
+		cancel_ime();
+		if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+			handle_unicode_input(alt_code);
+		}
+	} else {
+		String insert_ime_text = ime_text;
+		cancel_ime();
+		insert_text_at_caret(insert_ime_text);
+	}
 }
 
 void TextEdit::set_editable(bool p_editable) {
@@ -5966,7 +6030,7 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) {
 
 	// Get position of the end of caret.
 	if (has_ime_text()) {
-		if (ime_selection.y != 0) {
+		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 {
 			caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret));
@@ -6018,7 +6082,7 @@ void TextEdit::center_viewport_to_caret(int p_caret) {
 
 		// Get position of the end of caret.
 		if (has_ime_text()) {
-			if (ime_selection.y != 0) {
+			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 {
 				caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret));

+ 1 - 0
scene/gui/text_edit.h

@@ -276,6 +276,7 @@ private:
 	bool setting_text = false;
 
 	bool alt_start = false;
+	bool alt_start_no_hold = false;
 	uint32_t alt_code = 0;
 
 	// Text properties.