diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index ddeee9d765a..9a772c87c9b 100644 --- a/core/input/input_map.cpp +++ b/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>> &InputMap::get_builtins() { inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER)); default_builtin_cache.insert("ui_text_submit", inputs); + inputs = List>(); + 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>(); diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index a37dd479145..d218f720a38 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -37,6 +37,18 @@ + + + + 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. + + + + + + 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. + + @@ -133,6 +145,12 @@ Returns the selection end column. + + + + 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/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 80a4ca9a8a7..b205b862a3f 100644 --- a/doc/classes/ProjectSettings.xml +++ b/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. + + 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. + 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. diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 3b5d4fc33ec..1a066b07286 100644 --- a/scene/gui/line_edit.cpp +++ b/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 &p_event) { Ref 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 &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 &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 &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; - } - } 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; + // 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 { + 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); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 984512745a1..ac7436646b5 100644 --- a/scene/gui/line_edit.h +++ b/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; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index ab0ad2f4b7d..d7799588ead 100644 --- a/scene/gui/text_edit.cpp +++ b/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 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 &p_event) { bool TextEdit::alt_input(const Ref &p_gui_input) { Ref 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; - } - } 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; + // 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 { + 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)); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index a448e185b14..c5f838020bd 100644 --- a/scene/gui/text_edit.h +++ b/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.