Add Control+Shift+U (rebindabe) action for Unicode input, show hex as it is entered.

This commit is contained in:
bruvzg 2024-09-05 10:52:29 +03:00
parent b214aa8faf
commit b776f55bcb
No known key found for this signature in database
GPG Key ID: 7960FCF39844EC38
7 changed files with 309 additions and 98 deletions

View File

@ -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>>();

View File

@ -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>

View File

@ -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.

View File

@ -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);
// 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;
}
return;
}
// Alt + Unicode input:
if (k->is_alt_pressed()) {
if (!alt_start) {
if (k->get_keycode() == Key::KP_ADD) {
alt_start = true;
// 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;
}
} else {
// 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);
}
if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
} 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);
}
if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
} 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);

View File

@ -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;

View File

@ -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);
// 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 true;
}
return false;
}
if (k->is_alt_pressed()) {
if (!alt_start) {
if (k->get_keycode() == Key::KP_ADD) {
alt_start = true;
alt_code = 0;
ime_text = "u";
ime_selection = Vector2i(0, -1);
_update_ime_text();
return true;
}
} else {
// 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);
}
if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
} 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);
}
if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
} 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.
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));

View File

@ -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.