소스 검색

Prevent `LineEdit` from losing focus when text is submitted or rejected.

Mounir Tohami 1 년 전
부모
커밋
c36f466a4c
6개의 변경된 파일361개의 추가작업 그리고 260개의 파일을 삭제
  1. 20 1
      doc/classes/LineEdit.xml
  2. 0 1
      editor/editor_properties.cpp
  3. 313 226
      scene/gui/line_edit.cpp
  4. 6 0
      scene/gui/line_edit.h
  5. 21 30
      scene/gui/spin_box.cpp
  6. 1 2
      scene/gui/spin_box.h

+ 20 - 1
doc/classes/LineEdit.xml

@@ -4,7 +4,14 @@
 		An input field for single-line text.
 	</brief_description>
 	<description>
-		[LineEdit] provides an input field for editing a single line of text. It features many built-in shortcuts that are always available ([kbd]Ctrl[/kbd] here maps to [kbd]Cmd[/kbd] on macOS):
+		[LineEdit] provides an input field for editing a single line of text.
+		- When the [LineEdit] control is focused using the keyboard arrow keys, it will only gain focus and not enter edit mode.
+		- To enter edit mode, click on the control with the mouse or press the "ui_text_submit" action (default: [kbd]Enter[/kbd] or [kbd]Kp Enter[/kbd]).
+		- To exit edit mode, press "ui_text_submit" or "ui_cancel" (default: [kbd]Escape[/kbd]) actions.
+		- Check [method is_editing] and [signal editing_toggled] for more information.
+		[b]Important:[/b]
+		- Focusing the [LineEdit] with "ui_focus_next" (default: [kbd]Tab[/kbd]) or "ui_focus_prev" (default: [kbd]Shift + Tab[/kbd]) or [method Control.grab_focus] still enters edit mode (for compatibility).
+		[LineEdit] features many built-in shortcuts that are always available ([kbd]Ctrl[/kbd] here maps to [kbd]Cmd[/kbd] on macOS):
 		- [kbd]Ctrl + C[/kbd]: Copy
 		- [kbd]Ctrl + X[/kbd]: Cut
 		- [kbd]Ctrl + V[/kbd] or [kbd]Ctrl + Y[/kbd]: Paste/"yank"
@@ -139,6 +146,12 @@
 				Inserts [param text] at the caret. If the resulting value is longer than [member max_length], nothing happens.
 			</description>
 		</method>
+		<method name="is_editing" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns whether the [LineEdit] is being edited.
+			</description>
+		</method>
 		<method name="is_menu_visible" qualifiers="const">
 			<return type="bool" />
 			<description>
@@ -301,6 +314,12 @@
 		</member>
 	</members>
 	<signals>
+		<signal name="editing_toggled">
+			<param index="0" name="toggled_on" type="bool" />
+			<description>
+				Emitted when the [LineEdit] switches in or out of edit mode.
+			</description>
+		</signal>
 		<signal name="text_change_rejected">
 			<param index="0" name="rejected_substring" type="String" />
 			<description>

+ 0 - 1
editor/editor_properties.cpp

@@ -81,7 +81,6 @@ void EditorPropertyText::_text_submitted(const String &p_string) {
 	}
 
 	if (text->has_focus()) {
-		text->release_focus();
 		_text_changed(p_string);
 	}
 }

+ 313 - 226
scene/gui/line_edit.cpp

@@ -45,6 +45,70 @@
 #include "editor/editor_settings.h"
 #endif
 
+void LineEdit::_edit() {
+	if (!is_inside_tree()) {
+		return;
+	}
+
+	if (!has_focus()) {
+		grab_focus();
+	}
+
+	if (!editable || editing) {
+		return;
+	}
+
+	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);
+}
+
+void LineEdit::_unedit() {
+	if (!editing) {
+		return;
+	}
+
+	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();
+	set_caret_column(caret_column); // Update scroll_offset.
+
+	if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
+		DisplayServer::get_singleton()->virtual_keyboard_hide();
+	}
+
+	if (deselect_on_focus_loss_enabled && !selection.drag_attempt) {
+		deselect();
+	}
+
+	emit_signal(SNAME("editing_toggled"), false);
+}
+
+bool LineEdit::is_editing() const {
+	return editing;
+}
+
 void LineEdit::_swap_current_input_direction() {
 	if (input_direction == TEXT_DIRECTION_LTR) {
 		input_direction = TEXT_DIRECTION_RTL;
@@ -52,7 +116,6 @@ void LineEdit::_swap_current_input_direction() {
 		input_direction = TEXT_DIRECTION_LTR;
 	}
 	set_caret_column(get_caret_column());
-	queue_redraw();
 }
 
 void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
@@ -240,6 +303,11 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
 }
 
 void LineEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
+	// Return to prevent editing if just focused.
+	if (!editing) {
+		return;
+	}
+
 	Ref<InputEventKey> k = p_event;
 
 	if (k.is_valid()) {
@@ -265,26 +333,38 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 
 	Ref<InputEventMouseButton> b = p_event;
 
-	if (b.is_valid()) {
-		if (ime_text.length() != 0) {
-			// Ignore mouse clicks in IME input mode.
-			return;
-		}
-		if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
-			_update_context_menu();
-			menu->set_position(get_screen_position() + get_local_mouse_position());
-			menu->reset_size();
-			menu->popup();
-			grab_focus();
+	// Ignore mouse clicks in IME input mode.
+	if (b.is_valid() && ime_text.is_empty()) {
+		if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {
+			if (editable && !selection.enabled) {
+				set_caret_at_pixel_pos(b->get_position().x);
+			}
+
+			if (context_menu_enabled) {
+				_update_context_menu();
+				menu->set_position(get_screen_position() + get_local_mouse_position());
+				menu->reset_size();
+				menu->popup();
+			}
+
+			if (editable && !editing) {
+				_edit();
+			}
+
 			accept_event();
 			return;
 		}
 
-		if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+		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)) {
 			String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes();
 
 			deselect();
 			set_caret_at_pixel_pos(b->get_position().x);
+
+			if (!editing) {
+				_edit();
+			}
+
 			if (!paste_buffer.is_empty()) {
 				insert_text_at_caret(paste_buffer);
 
@@ -295,7 +375,6 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 					text_changed_dirty = true;
 				}
 			}
-			grab_focus();
 			accept_event();
 			return;
 		}
@@ -304,10 +383,13 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 			return;
 		}
 
-		_reset_caret_blink_timer();
+		if (editing) {
+			_reset_caret_blink_timer();
+		}
+
 		if (b->is_pressed()) {
 			accept_event(); // Don't pass event further when clicked on text field.
-			if (!text.is_empty() && is_editable() && _is_over_clear_button(b->get_position())) {
+			if (editable && !text.is_empty() && _is_over_clear_button(b->get_position())) {
 				clear_button_status.press_attempt = true;
 				clear_button_status.pressing_inside = true;
 				queue_redraw();
@@ -330,7 +412,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 					const int triple_click_tolerance = 5;
 					const bool is_triple_click = !b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && b->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance;
 
-					if (is_triple_click && text.length()) {
+					if (is_triple_click && !text.is_empty()) {
 						// Triple-click select all.
 						selection.enabled = true;
 						selection.begin = 0;
@@ -377,13 +459,17 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 				}
 			}
 
+			if (editable && !editing) {
+				_edit();
+				return;
+			}
 			queue_redraw();
 
 		} else {
 			if (selection.enabled && !pass && b->get_button_index() == MouseButton::LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
 				DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
 			}
-			if (!text.is_empty() && is_editable() && clear_button_enabled) {
+			if (editable && !text.is_empty() && clear_button_enabled) {
 				bool press_attempt = clear_button_status.press_attempt;
 				clear_button_status.press_attempt = false;
 				if (press_attempt && clear_button_status.pressing_inside && _is_over_clear_button(b->get_position())) {
@@ -416,7 +502,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 	Ref<InputEventMouseMotion> m = p_event;
 
 	if (m.is_valid()) {
-		if (!text.is_empty() && is_editable() && clear_button_enabled) {
+		if (editable && !text.is_empty() && clear_button_enabled) {
 			bool last_press_inside = clear_button_status.pressing_inside;
 			clear_button_status.pressing_inside = clear_button_status.press_attempt && _is_over_clear_button(m->get_position());
 			if (last_press_inside != clear_button_status.pressing_inside) {
@@ -462,221 +548,240 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
 
 	Ref<InputEventKey> k = p_event;
 
-	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)) {
-					char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
-					insert_text_at_caret(ucodestr);
-				}
-				accept_event();
-				return;
-			}
-			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;
-				}
-			} 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;
-			}
-		}
+	if (k.is_null()) {
+		return;
+	}
 
-		if (context_menu_enabled) {
-			if (k->is_action("ui_menu", true)) {
-				_update_context_menu();
-				Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
-				menu->set_position(get_screen_position() + pos);
-				menu->reset_size();
-				menu->popup();
-				menu->grab_focus();
+	if (editable && !editing && k->is_action_pressed("ui_text_submit", false)) {
+		_edit();
+		return;
+	}
 
-				accept_event();
-				return;
-			}
-		}
+	if (!editing) {
+		return;
+	}
 
-		// Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE.
-		if (k->is_action("ui_text_submit", false)) {
-			emit_signal(SNAME("text_submitted"), text);
-			if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
-				DisplayServer::get_singleton()->virtual_keyboard_hide();
+	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;
 		}
+		return;
+	}
 
-		if (k->is_action("ui_cancel")) {
-			callable_mp((Control *)this, &Control::release_focus).call_deferred();
-			accept_event();
-			return;
-		}
-
-		if (is_shortcut_keys_enabled()) {
-			if (k->is_action("ui_copy", true)) {
-				copy_text();
+	// 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;
 			}
-
-			if (k->is_action("ui_text_select_all", true)) {
-				select();
-				accept_event();
-				return;
+		} 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);
 			}
-
-			// Cut / Paste
-			if (k->is_action("ui_cut", true)) {
-				cut_text();
-				accept_event();
-				return;
+			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->is_action("ui_paste", true)) {
-				paste_text();
-				accept_event();
-				return;
+			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;
 			}
-
-			// Undo / Redo
-			if (k->is_action("ui_undo", true)) {
-				undo();
-				accept_event();
-				return;
-			}
-
-			if (k->is_action("ui_redo", true)) {
-				redo();
-				accept_event();
-				return;
-			}
-		}
-
-		// BACKSPACE
-		if (k->is_action("ui_text_backspace_all_to_left", true)) {
-			_backspace(false, true);
-			accept_event();
-			return;
-		}
-		if (k->is_action("ui_text_backspace_word", true)) {
-			_backspace(true);
-			accept_event();
-			return;
-		}
-		if (k->is_action("ui_text_backspace", true)) {
-			_backspace();
 			accept_event();
 			return;
 		}
+	}
+
+	if (context_menu_enabled) {
+		if (k->is_action("ui_menu", true)) {
+			_update_context_menu();
+			Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
+			menu->set_position(get_screen_position() + pos);
+			menu->reset_size();
+			menu->popup();
+			menu->grab_focus();
 
-		// DELETE
-		if (k->is_action("ui_text_delete_all_to_right", true)) {
-			_delete(false, true);
-			accept_event();
-			return;
-		}
-		if (k->is_action("ui_text_delete_word", true)) {
-			_delete(true);
 			accept_event();
 			return;
 		}
-		if (k->is_action("ui_text_delete", true)) {
-			_delete();
-			accept_event();
-			return;
+	}
+
+	// Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE.
+	if (k->is_action_pressed("ui_text_submit")) {
+		emit_signal(SNAME("text_submitted"), text);
+		if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
+			DisplayServer::get_singleton()->virtual_keyboard_hide();
 		}
 
-		// Cursor Movement
+		if (editing) {
+			_unedit();
+		}
 
-		k = k->duplicate();
-		bool shift_pressed = k->is_shift_pressed();
-		// Remove shift or else actions will not match. Use above variable for selection.
-		k->set_shift_pressed(false);
+		accept_event();
+		return;
+	}
 
-		if (k->is_action("ui_text_caret_word_left", true)) {
-			_move_caret_left(shift_pressed, true);
-			accept_event();
-			return;
+	if (k->is_action("ui_cancel")) {
+		if (editing) {
+			_unedit();
 		}
-		if (k->is_action("ui_text_caret_left", true)) {
-			_move_caret_left(shift_pressed);
+
+		accept_event();
+		return;
+	}
+
+	if (is_shortcut_keys_enabled()) {
+		if (k->is_action("ui_copy", true)) {
+			copy_text();
 			accept_event();
 			return;
 		}
-		if (k->is_action("ui_text_caret_word_right", true)) {
-			_move_caret_right(shift_pressed, true);
+
+		if (k->is_action("ui_text_select_all", true)) {
+			select();
 			accept_event();
 			return;
 		}
-		if (k->is_action("ui_text_caret_right", true)) {
-			_move_caret_right(shift_pressed, false);
+
+		// Cut / Paste
+		if (k->is_action("ui_cut", true)) {
+			cut_text();
 			accept_event();
 			return;
 		}
 
-		// Up = Home, Down = End
-		if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) {
-			_move_caret_start(shift_pressed);
+		if (k->is_action("ui_paste", true)) {
+			paste_text();
 			accept_event();
 			return;
 		}
-		if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) {
-			_move_caret_end(shift_pressed);
+
+		// Undo / Redo
+		if (k->is_action("ui_undo", true)) {
+			undo();
 			accept_event();
 			return;
 		}
 
-		// Misc
-		if (k->is_action("ui_swap_input_direction", true)) {
-			_swap_current_input_direction();
+		if (k->is_action("ui_redo", true)) {
+			redo();
 			accept_event();
 			return;
 		}
+	}
 
-		_reset_caret_blink_timer();
+	// BACKSPACE
+	if (k->is_action("ui_text_backspace_all_to_left", true)) {
+		_backspace(false, true);
+		accept_event();
+		return;
+	}
+	if (k->is_action("ui_text_backspace_word", true)) {
+		_backspace(true);
+		accept_event();
+		return;
+	}
+	if (k->is_action("ui_text_backspace", true)) {
+		_backspace();
+		accept_event();
+		return;
+	}
 
-		// Allow unicode handling if:
-		// * No Modifiers are pressed (except shift)
-		bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+	// DELETE
+	if (k->is_action("ui_text_delete_all_to_right", true)) {
+		_delete(false, true);
+		accept_event();
+		return;
+	}
+	if (k->is_action("ui_text_delete_word", true)) {
+		_delete(true);
+		accept_event();
+		return;
+	}
+	if (k->is_action("ui_text_delete", true)) {
+		_delete();
+		accept_event();
+		return;
+	}
 
-		if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
-			// Handle Unicode if no modifiers are active.
-			selection_delete();
-			char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 };
-			int prev_len = text.length();
-			insert_text_at_caret(ucodestr);
-			if (text.length() != prev_len) {
-				if (!text_changed_dirty) {
-					if (is_inside_tree()) {
-						callable_mp(this, &LineEdit::_text_changed).call_deferred();
-					}
-					text_changed_dirty = true;
+	// Cursor Movement
+
+	k = k->duplicate();
+	bool shift_pressed = k->is_shift_pressed();
+	// Remove shift or else actions will not match. Use above variable for selection.
+	k->set_shift_pressed(false);
+
+	if (k->is_action("ui_text_caret_word_left", true)) {
+		_move_caret_left(shift_pressed, true);
+		accept_event();
+		return;
+	}
+	if (k->is_action("ui_text_caret_left", true)) {
+		_move_caret_left(shift_pressed);
+		accept_event();
+		return;
+	}
+	if (k->is_action("ui_text_caret_word_right", true)) {
+		_move_caret_right(shift_pressed, true);
+		accept_event();
+		return;
+	}
+	if (k->is_action("ui_text_caret_right", true)) {
+		_move_caret_right(shift_pressed, false);
+		accept_event();
+		return;
+	}
+
+	// Up = Home, Down = End
+	if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) {
+		_move_caret_start(shift_pressed);
+		accept_event();
+		return;
+	}
+	if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) {
+		_move_caret_end(shift_pressed);
+		accept_event();
+		return;
+	}
+
+	// Misc
+	if (k->is_action("ui_swap_input_direction", true)) {
+		_swap_current_input_direction();
+		accept_event();
+		return;
+	}
+
+	_reset_caret_blink_timer();
+
+	// Allow unicode handling if:
+	// * No Modifiers are pressed (except shift)
+	bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+
+	if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
+		// Handle Unicode if no modifiers are active.
+		selection_delete();
+		char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 };
+		int prev_len = text.length();
+		insert_text_at_caret(ucodestr);
+		if (text.length() != prev_len) {
+			if (!text_changed_dirty) {
+				if (is_inside_tree()) {
+					callable_mp(this, &LineEdit::_text_changed).call_deferred();
 				}
+				text_changed_dirty = true;
 			}
-			accept_event();
-			return;
 		}
+		accept_event();
+		return;
 	}
 }
 
@@ -1107,7 +1212,7 @@ void LineEdit::_notification(int p_what) {
 				}
 			}
 
-			if (has_focus()) {
+			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);
@@ -1121,8 +1226,6 @@ void LineEdit::_notification(int p_what) {
 		} break;
 
 		case NOTIFICATION_FOCUS_ENTER: {
-			_validate_caret_can_draw();
-
 			if (select_all_on_focus) {
 				if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
 					// Select all when the mouse button is up.
@@ -1132,43 +1235,20 @@ void LineEdit::_notification(int p_what) {
 				}
 			}
 
-			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);
+			// Only allow editing if the LineEdit is not focused with arrow keys.
+			if (!(Input::get_singleton()->is_action_pressed("ui_up") || Input::get_singleton()->is_action_pressed("ui_down") || Input::get_singleton()->is_action_pressed("ui_left") || Input::get_singleton()->is_action_pressed("ui_right"))) {
+				_edit();
 			}
-
-			show_virtual_keyboard();
 		} break;
 
 		case NOTIFICATION_FOCUS_EXIT: {
-			_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();
-			set_caret_column(caret_column); // Update scroll_offset.
-
-			if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
-				DisplayServer::get_singleton()->virtual_keyboard_hide();
-			}
-
-			if (deselect_on_focus_loss_enabled && !selection.drag_attempt) {
-				deselect();
+			if (editing) {
+				_unedit();
 			}
 		} break;
 
 		case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
-			if (has_focus()) {
+			if (editing) {
 				ime_text = DisplayServer::get_singleton()->ime_get_text();
 				ime_selection = DisplayServer::get_singleton()->ime_get_selection();
 
@@ -1178,8 +1258,6 @@ void LineEdit::_notification(int p_what) {
 
 				_shape();
 				set_caret_column(caret_column); // Update scroll_offset.
-
-				queue_redraw();
 			}
 		} break;
 
@@ -1419,7 +1497,7 @@ Vector2 LineEdit::get_caret_pixel_pos() {
 	Vector2 ret;
 	CaretInfo caret;
 	// Get position of the start of caret.
-	if (ime_text.length() != 0 && ime_selection.x != 0) {
+	if (!ime_text.is_empty() && ime_selection.x != 0) {
 		caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x);
 	} else {
 		caret = TS->shaped_text_get_carets(text_rid, caret_column);
@@ -1432,7 +1510,7 @@ Vector2 LineEdit::get_caret_pixel_pos() {
 	}
 
 	// Get position of the end of caret.
-	if (ime_text.length() != 0) {
+	if (!ime_text.is_empty()) {
 		if (ime_selection.y != 0) {
 			caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x + ime_selection.y);
 		} else {
@@ -1525,11 +1603,11 @@ void LineEdit::_validate_caret_can_draw() {
 		draw_caret = true;
 		caret_blink_timer = 0.0;
 	}
-	caret_can_draw = editable && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed);
+	caret_can_draw = editing && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed);
 }
 
 void LineEdit::delete_char() {
-	if ((text.length() <= 0) || (caret_column == 0)) {
+	if (text.is_empty() || caret_column == 0) {
 		return;
 	}
 
@@ -1661,7 +1739,7 @@ void LineEdit::clear() {
 	_text_changed();
 
 	// This should reset virtual keyboard state if needed.
-	if (has_focus()) {
+	if (editing) {
 		show_virtual_keyboard();
 	}
 }
@@ -1834,7 +1912,8 @@ Size2 LineEdit::get_minimum_size() const {
 	Size2 min_size;
 
 	// Minimum size of text.
-	float em_space_size = font->get_char_size('M', font_size).x;
+	// W is wider than M in most fonts, Using M may result in hiding the last digit when using float values in SpinBox, ie. ColorPicker RAW values.
+	float em_space_size = font->get_char_size('W', font_size).x;
 	min_size.width = theme_cache.minimum_character_width * em_space_size;
 
 	if (expand_to_text_length) {
@@ -1932,7 +2011,8 @@ void LineEdit::select_all() {
 		return;
 	}
 
-	if (!text.length()) {
+	if (text.is_empty()) {
+		set_caret_column(0);
 		return;
 	}
 
@@ -1948,6 +2028,10 @@ void LineEdit::set_editable(bool p_editable) {
 	}
 
 	editable = p_editable;
+
+	if (!editable && editing) {
+		_unedit();
+	}
 	_validate_caret_can_draw();
 
 	update_minimum_size();
@@ -2328,6 +2412,7 @@ void LineEdit::_emit_text_change() {
 	emit_signal(SceneStringName(text_changed), text);
 	text_changed_dirty = false;
 }
+
 PackedStringArray LineEdit::get_configuration_warnings() const {
 	PackedStringArray warnings = Control::get_configuration_warnings();
 	if (secret_character.length() > 1) {
@@ -2347,13 +2432,13 @@ void LineEdit::_shape() {
 	TS->shaped_text_clear(text_rid);
 
 	String t;
-	if (text.length() == 0 && ime_text.length() == 0) {
+	if (text.is_empty() && ime_text.is_empty()) {
 		t = placeholder_translated;
 	} else if (pass) {
-		String s = (secret_character.length() > 0) ? secret_character.left(1) : U"•";
+		String s = secret_character.is_empty() ? U"•" : secret_character.left(1);
 		t = s.repeat(text.length() + ime_text.length());
 	} else {
-		if (ime_text.length() > 0) {
+		if (!ime_text.is_empty()) {
 			t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length());
 		} else {
 			t = text;
@@ -2562,6 +2647,7 @@ void LineEdit::_bind_methods() {
 	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);
 
+	ClassDB::bind_method(D_METHOD("is_editing"), &LineEdit::is_editing);
 	ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear);
 	ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1));
 	ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all);
@@ -2642,6 +2728,7 @@ void LineEdit::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text")));
 	ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring")));
 	ADD_SIGNAL(MethodInfo("text_submitted", PropertyInfo(Variant::STRING, "new_text")));
+	ADD_SIGNAL(MethodInfo("editing_toggled", PropertyInfo(Variant::BOOL, "toggled_on")));
 
 	BIND_ENUM_CONSTANT(MENU_CUT);
 	BIND_ENUM_CONSTANT(MENU_COPY);

+ 6 - 0
scene/gui/line_edit.h

@@ -86,6 +86,7 @@ public:
 private:
 	HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
 
+	bool editing = false;
 	bool editable = false;
 	bool pass = false;
 	bool text_changed_dirty = false;
@@ -205,6 +206,9 @@ private:
 		float base_scale = 1.0;
 	} theme_cache;
 
+	void _edit();
+	void _unedit();
+
 	void _clear_undo_stack();
 	void _clear_redo();
 	void _create_undo_state();
@@ -257,6 +261,8 @@ protected:
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 
 public:
+	bool is_editing() const;
+
 	void set_horizontal_alignment(HorizontalAlignment p_alignment);
 	HorizontalAlignment get_horizontal_alignment() const;
 

+ 21 - 30
scene/gui/spin_box.cpp

@@ -46,7 +46,7 @@ void SpinBox::_update_text(bool p_keep_line_edit) {
 		value = TS->format_number(value);
 	}
 
-	if (!line_edit->has_focus()) {
+	if (!line_edit->is_editing()) {
 		if (!prefix.is_empty()) {
 			value = prefix + " " + value;
 		}
@@ -197,13 +197,13 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
 				}
 			} break;
 			case MouseButton::WHEEL_UP: {
-				if (line_edit->has_focus()) {
+				if (line_edit->is_editing()) {
 					set_value(get_value() + step * mb->get_factor());
 					accept_event();
 				}
 			} break;
 			case MouseButton::WHEEL_DOWN: {
-				if (line_edit->has_focus()) {
+				if (line_edit->is_editing()) {
 					set_value(get_value() - step * mb->get_factor());
 					accept_event();
 				}
@@ -253,34 +253,26 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
 	}
 }
 
-void SpinBox::_line_edit_focus_enter() {
-	int col = line_edit->get_caret_column();
-	_update_text();
-	line_edit->set_caret_column(col);
+void SpinBox::_line_edit_editing_toggled(bool p_toggled_on) {
+	if (p_toggled_on) {
+		int col = line_edit->get_caret_column();
+		_update_text();
+		line_edit->set_caret_column(col);
 
-	// LineEdit text might change and it clears any selection. Have to re-select here.
-	if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
-		line_edit->select_all();
-	}
-}
+		// LineEdit text might change and it clears any selection. Have to re-select here.
+		if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
+			line_edit->select_all();
+		}
+	} else {
+		// Discontinue because the focus_exit was caused by canceling.
+		if (Input::get_singleton()->is_action_pressed("ui_cancel")) {
+			_update_text();
+			return;
+		}
 
-void SpinBox::_line_edit_focus_exit() {
-	// Discontinue because the focus_exit was caused by left-clicking the arrows.
-	const Viewport *viewport = get_viewport();
-	if (!viewport || viewport->gui_get_focus_owner() == get_line_edit()) {
-		return;
-	}
-	// Discontinue because the focus_exit was caused by right-click context menu.
-	if (line_edit->is_menu_visible()) {
-		return;
-	}
-	// Discontinue because the focus_exit was caused by canceling.
-	if (Input::get_singleton()->is_action_pressed("ui_cancel")) {
-		_update_text();
-		return;
+		line_edit->deselect();
+		_text_submitted(line_edit->get_text());
 	}
-
-	_text_submitted(line_edit->get_text());
 }
 
 inline void SpinBox::_compute_sizes() {
@@ -602,8 +594,7 @@ SpinBox::SpinBox() {
 	line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
 
 	line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED);
-	line_edit->connect(SceneStringName(focus_entered), callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED);
-	line_edit->connect(SceneStringName(focus_exited), callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED);
+	line_edit->connect("editing_toggled", callable_mp(this, &SpinBox::_line_edit_editing_toggled), CONNECT_DEFERRED);
 	line_edit->connect(SceneStringName(gui_input), callable_mp(this, &SpinBox::_line_edit_input));
 
 	range_click_timer = memnew(Timer);

+ 1 - 2
scene/gui/spin_box.h

@@ -86,8 +86,7 @@ class SpinBox : public Range {
 		bool down_button_disabled = false;
 	} state_cache;
 
-	void _line_edit_focus_enter();
-	void _line_edit_focus_exit();
+	void _line_edit_editing_toggled(bool p_toggled_on);
 
 	inline void _compute_sizes();
 	inline int _get_widest_button_icon_width();