/*************************************************************************/ /* line_edit.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "line_edit.h" #include "core/input/input_map.h" #include "core/object/message_queue.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/string/print_string.h" #include "core/string/translation.h" #include "label.h" #include "servers/display_server.h" #include "servers/text_server.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" #endif #include "scene/main/window.h" void LineEdit::_swap_current_input_direction() { if (input_direction == TEXT_DIRECTION_LTR) { input_direction = TEXT_DIRECTION_RTL; } else { 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) { if (selection.enabled && !p_select) { set_caret_column(selection.begin); deselect(); return; } shift_selection_check_pre(p_select); if (p_move_by_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); for (int i = words.size() - 2; i >= 0; i = i - 2) { if (words[i] < cc) { cc = words[i]; break; } } set_caret_column(cc); } else { if (caret_mid_grapheme_enabled) { set_caret_column(get_caret_column() - 1); } else { set_caret_column(TS->shaped_text_prev_grapheme_pos(text_rid, get_caret_column())); } } shift_selection_check_post(p_select); } void LineEdit::_move_caret_right(bool p_select, bool p_move_by_word) { if (selection.enabled && !p_select) { set_caret_column(selection.end); deselect(); return; } shift_selection_check_pre(p_select); if (p_move_by_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); for (int i = 1; i < words.size(); i = i + 2) { if (words[i] > cc) { cc = words[i]; break; } } set_caret_column(cc); } else { if (caret_mid_grapheme_enabled) { set_caret_column(get_caret_column() + 1); } else { set_caret_column(TS->shaped_text_next_grapheme_pos(text_rid, get_caret_column())); } } shift_selection_check_post(p_select); } void LineEdit::_move_caret_start(bool p_select) { shift_selection_check_pre(p_select); set_caret_column(0); shift_selection_check_post(p_select); } void LineEdit::_move_caret_end(bool p_select) { shift_selection_check_pre(p_select); set_caret_column(text.length()); shift_selection_check_post(p_select); } void LineEdit::_backspace(bool p_word, bool p_all_to_left) { if (!editable) { return; } if (p_all_to_left) { deselect(); text = text.substr(0, caret_column); _text_changed(); return; } if (selection.enabled) { selection_delete(); return; } if (p_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); for (int i = words.size() - 2; i >= 0; i = i - 2) { if (words[i] < cc) { cc = words[i]; break; } } delete_text(cc, caret_column); set_caret_column(cc); } else { delete_char(); } } void LineEdit::_delete(bool p_word, bool p_all_to_right) { if (!editable) { return; } if (p_all_to_right) { deselect(); text = text.substr(caret_column, text.length() - caret_column); _shape(); set_caret_column(0); _text_changed(); return; } if (selection.enabled) { selection_delete(); return; } int text_len = text.length(); if (caret_column == text_len) { return; // Nothing to do. } if (p_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); for (int i = 1; i < words.size(); i = i + 2) { if (words[i] > cc) { cc = words[i]; break; } } delete_text(caret_column, cc); set_caret_column(caret_column); } else { if (caret_mid_grapheme_enabled) { set_caret_column(caret_column + 1); delete_char(); } else { int cc = caret_column; set_caret_column(TS->shaped_text_next_grapheme_pos(text_rid, caret_column)); delete_text(cc, caret_column); } } } void LineEdit::unhandled_key_input(const Ref &p_event) { Ref k = p_event; if (k.is_valid()) { if (!k->is_pressed()) { return; } // Handle Unicode (with modifiers active, process after shortcuts). if (has_focus() && editable && (k->get_unicode() >= 32)) { 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) { _text_changed(); } accept_event(); } } } void LineEdit::gui_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref 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) { _ensure_menu(); menu->set_position(get_screen_position() + get_local_mouse_position()); menu->reset_size(); menu->popup(); grab_focus(); 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)) { String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes(); deselect(); set_caret_at_pixel_pos(b->get_position().x); if (!paste_buffer.is_empty()) { insert_text_at_caret(paste_buffer); if (!text_changed_dirty) { if (is_inside_tree()) { MessageQueue::get_singleton()->push_call(this, "_text_changed"); } text_changed_dirty = true; } } grab_focus(); return; } if (b->get_button_index() != MouseButton::LEFT) { return; } _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())) { clear_button_status.press_attempt = true; clear_button_status.pressing_inside = true; queue_redraw(); return; } if (b->is_shift_pressed()) { shift_selection_check_pre(true); } set_caret_at_pixel_pos(b->get_position().x); if (b->is_shift_pressed()) { selection_fill_at_caret(); selection.creating = true; } else { if (selecting_enabled) { const int triple_click_timeout = 600; 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()) { // Triple-click select all. selection.enabled = true; selection.begin = 0; selection.end = text.length(); selection.double_click = true; last_dblclk = 0; set_caret_column(selection.begin); if (!pass && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(text); } } else if (b->is_double_click()) { // Double-click select word. last_dblclk = OS::get_singleton()->get_ticks_msec(); last_dblclk_pos = b->get_position(); PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); for (int i = 0; i < words.size(); i = i + 2) { if ((words[i] < caret_column && words[i + 1] > caret_column) || (i == words.size() - 2 && caret_column == words[i + 1])) { selection.enabled = true; selection.begin = words[i]; selection.end = words[i + 1]; selection.double_click = true; set_caret_column(selection.end); break; } } if (!pass && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(text.substr(selection.begin, selection.end - selection.begin)); } } } selection.drag_attempt = false; if ((caret_column < selection.begin) || (caret_column > selection.end) || !selection.enabled) { deselect(); selection.start_column = caret_column; selection.creating = true; } else if (selection.enabled && !selection.double_click) { selection.drag_attempt = true; } } 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(text.substr(selection.begin, selection.end - selection.begin)); } if (!text.is_empty() && is_editable() && 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())) { clear(); return; } } if ((!selection.creating) && (!selection.double_click)) { deselect(); } selection.creating = false; selection.double_click = false; if (!drag_action) { selection.drag_attempt = false; } show_virtual_keyboard(); } queue_redraw(); } Ref m = p_event; if (m.is_valid()) { if (!text.is_empty() && is_editable() && 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) { queue_redraw(); } } if ((m->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { if (selection.creating) { set_caret_at_pixel_pos(m->get_position().x); selection_fill_at_caret(); } } if (drag_action && can_drop_data(m->get_position(), get_viewport()->gui_get_drag_data())) { drag_caret_force_displayed = true; set_caret_at_pixel_pos(m->get_position().x); } } Ref 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 (context_menu_enabled) { if (k->is_action("ui_menu", true)) { _ensure_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(); } } // 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 (is_shortcut_keys_enabled()) { if (k->is_action("ui_copy", true)) { copy_text(); accept_event(); return; } if (k->is_action("ui_text_select_all", true)) { select(); accept_event(); return; } // Cut / Paste if (k->is_action("ui_cut", true)) { cut_text(); accept_event(); return; } if (k->is_action("ui_paste", true)) { paste_text(); accept_event(); return; } // 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; } // 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; } // 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_command_or_control_pressed() || 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 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) { _text_changed(); } accept_event(); } } } void LineEdit::set_horizontal_alignment(HorizontalAlignment p_alignment) { ERR_FAIL_INDEX((int)p_alignment, 4); if (alignment == p_alignment) { return; } alignment = p_alignment; _shape(); queue_redraw(); } HorizontalAlignment LineEdit::get_horizontal_alignment() const { return alignment; } Variant LineEdit::get_drag_data(const Point2 &p_point) { if (selection.drag_attempt && selection.enabled) { String t = text.substr(selection.begin, selection.end - selection.begin); Label *l = memnew(Label); l->set_text(t); set_drag_preview(l); return t; } return Variant(); } bool LineEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const { bool drop_override = Control::can_drop_data(p_point, p_data); // In case user wants to drop custom data. if (drop_override) { return drop_override; } return is_editable() && p_data.get_type() == Variant::STRING; } void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Control::drop_data(p_point, p_data); if (p_data.get_type() == Variant::STRING && is_editable()) { 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; if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { is_inside_sel = selection.enabled && caret_column > selection.begin && caret_column < selection.end; } if (selection.drag_attempt) { selection.drag_attempt = false; if (!is_inside_sel) { if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) { if (caret_column_tmp > selection.end) { caret_column_tmp = caret_column_tmp - (selection.end - selection.begin); } selection_delete(); } set_caret_column(caret_column_tmp); insert_text_at_caret(p_data); } } else if (selection.enabled && caret_column >= selection.begin && caret_column <= selection.end) { caret_column_tmp = selection.begin; selection_delete(); set_caret_column(caret_column_tmp); insert_text_at_caret(p_data); grab_focus(); } else { insert_text_at_caret(p_data); grab_focus(); } select(caret_column_tmp, caret_column); if (!text_changed_dirty) { if (is_inside_tree()) { MessageQueue::get_singleton()->push_call(this, "_text_changed"); } text_changed_dirty = true; } queue_redraw(); } } Control::CursorShape LineEdit::get_cursor_shape(const Point2 &p_pos) const { if ((!text.is_empty() && is_editable() && _is_over_clear_button(p_pos)) || (!is_editable() && (!is_selecting_enabled() || text.is_empty()))) { return CURSOR_ARROW; } return Control::get_cursor_shape(p_pos); } bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const { if (!clear_button_enabled || !has_point(p_pos)) { return false; } Ref icon = theme_cache.clear_icon; int x_ofs = theme_cache.normal->get_margin(SIDE_RIGHT); return p_pos.x > get_size().width - icon->get_width() - x_ofs; } void LineEdit::_update_theme_item_cache() { Control::_update_theme_item_cache(); theme_cache.normal = get_theme_stylebox(SNAME("normal")); theme_cache.read_only = get_theme_stylebox(SNAME("read_only")); theme_cache.focus = get_theme_stylebox(SNAME("focus")); theme_cache.font = get_theme_font(SNAME("font")); theme_cache.font_size = get_theme_font_size(SNAME("font_size")); theme_cache.font_color = get_theme_color(SNAME("font_color")); theme_cache.font_uneditable_color = get_theme_color(SNAME("font_uneditable_color")); theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); theme_cache.font_placeholder_color = get_theme_color(SNAME("font_placeholder_color")); theme_cache.caret_width = get_theme_constant(SNAME("caret_width")); theme_cache.caret_color = get_theme_color(SNAME("caret_color")); theme_cache.minimum_character_width = get_theme_constant(SNAME("minimum_character_width")); theme_cache.selection_color = get_theme_color(SNAME("selection_color")); theme_cache.clear_icon = get_theme_icon(SNAME("clear")); theme_cache.clear_button_color = get_theme_color(SNAME("clear_button_color")); theme_cache.clear_button_color_pressed = get_theme_color(SNAME("clear_button_color_pressed")); theme_cache.base_scale = get_theme_default_base_scale(); } void LineEdit::_notification(int p_what) { switch (p_what) { #ifdef TOOLS_ENABLED case NOTIFICATION_ENTER_TREE: { if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) { set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink")); set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval")); if (!EditorSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed))) { EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed)); } } } break; #endif case NOTIFICATION_RESIZED: { _fit_to_width(); scroll_offset = 0.0; set_caret_column(get_caret_column()); } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { _shape(); queue_redraw(); } break; case NOTIFICATION_TRANSLATION_CHANGED: { placeholder_translated = atr(placeholder); _shape(); queue_redraw(); } break; case NOTIFICATION_WM_WINDOW_FOCUS_IN: { window_has_focus = true; draw_caret = true; queue_redraw(); } break; case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { window_has_focus = false; draw_caret = false; queue_redraw(); } break; case NOTIFICATION_INTERNAL_PROCESS: { if (caret_blinking) { caret_blink_timer += get_process_delta_time(); if (caret_blink_timer >= caret_blink_interval) { caret_blink_timer = 0.0; _toggle_draw_caret(); } } } break; case NOTIFICATION_DRAW: { if ((!has_focus() && !(menu && menu->has_focus()) && !caret_force_displayed) || !window_has_focus) { draw_caret = false; } int width, height; bool rtl = is_layout_rtl(); Size2 size = get_size(); width = size.width; height = size.height; RID ci = get_canvas_item(); Ref style = theme_cache.normal; if (!is_editable()) { style = theme_cache.read_only; draw_caret = false; } Ref font = theme_cache.font; if (!flat) { style->draw(ci, Rect2(Point2(), size)); } if (has_focus()) { theme_cache.focus->draw(ci, Rect2(Point2(), size)); } int x_ofs = 0; bool using_placeholder = text.is_empty() && ime_text.is_empty(); float text_width = TS->shaped_text_get_size(text_rid).x; float text_height = TS->shaped_text_get_size(text_rid).y; switch (alignment) { case HORIZONTAL_ALIGNMENT_FILL: case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - style->get_margin(SIDE_RIGHT) - (text_width))); } else { x_ofs = style->get_offset().x; } } break; case HORIZONTAL_ALIGNMENT_CENTER: { if (!Math::is_zero_approx(scroll_offset)) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - (text_width)) / 2); } } break; case HORIZONTAL_ALIGNMENT_RIGHT: { if (rtl) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - style->get_margin(SIDE_RIGHT) - (text_width))); } } break; } int ofs_max = width - style->get_margin(SIDE_RIGHT); int y_area = height - style->get_minimum_size().height; int y_ofs = style->get_offset().y + (y_area - text_height) / 2; Color selection_color = theme_cache.selection_color; Color font_color; if (is_editable()) { font_color = theme_cache.font_color; } else { font_color = theme_cache.font_uneditable_color; } Color font_selected_color = theme_cache.font_selected_color; Color caret_color = theme_cache.caret_color; // Draw placeholder color. if (using_placeholder) { font_color = theme_cache.font_placeholder_color; } bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { Ref r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; Color color_icon(1, 1, 1, !is_editable() ? .5 * .9 : .9); if (display_clear_icon) { if (clear_button_status.press_attempt && clear_button_status.pressing_inside) { color_icon = theme_cache.clear_button_color_pressed; } else { color_icon = theme_cache.clear_button_color; } } r_icon->draw(ci, Point2(width - r_icon->get_width() - style->get_margin(SIDE_RIGHT), height / 2 - r_icon->get_height() / 2), color_icon); if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); } } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), x_ofs - r_icon->get_width() - style->get_margin(SIDE_RIGHT)); } ofs_max -= r_icon->get_width(); } // Draw selections rects. Vector2 ofs = Point2(x_ofs + scroll_offset, y_ofs); if (selection.enabled) { Vector sel = TS->shaped_text_get_selection(text_rid, selection.begin, selection.end); for (int i = 0; i < sel.size(); i++) { Rect2 rect = Rect2(sel[i].x + ofs.x, ofs.y, sel[i].y - sel[i].x, text_height); if (rect.position.x + rect.size.x <= x_ofs || rect.position.x > ofs_max) { continue; } if (rect.position.x < x_ofs) { rect.size.x -= (x_ofs - rect.position.x); rect.position.x = x_ofs; } else if (rect.position.x + rect.size.x > ofs_max) { rect.size.x = ofs_max - rect.position.x; } RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_color); } } const Glyph *glyphs = TS->shaped_text_get_glyphs(text_rid); int gl_size = TS->shaped_text_get_glyph_count(text_rid); // Draw text. ofs.y += TS->shaped_text_get_ascent(text_rid); Color font_outline_color = theme_cache.font_outline_color; int outline_size = theme_cache.font_outline_size; if (outline_size > 0 && font_outline_color.a > 0) { Vector2 oofs = ofs; for (int i = 0; i < gl_size; i++) { for (int j = 0; j < glyphs[i].repeat; j++) { if (ceil(oofs.x) >= x_ofs && (oofs.x + glyphs[i].advance) <= ofs_max) { if (glyphs[i].font_rid != RID()) { TS->font_draw_glyph_outline(glyphs[i].font_rid, ci, glyphs[i].font_size, outline_size, oofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, font_outline_color); } } oofs.x += glyphs[i].advance; } if (oofs.x >= ofs_max) { break; } } } for (int i = 0; i < gl_size; i++) { bool selected = selection.enabled && glyphs[i].start >= selection.begin && glyphs[i].end <= selection.end; for (int j = 0; j < glyphs[i].repeat; j++) { if (ceil(ofs.x) >= x_ofs && (ofs.x + glyphs[i].advance) <= ofs_max) { if (glyphs[i].font_rid != RID()) { TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color); } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { TS->draw_hex_code_box(ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color); } } ofs.x += glyphs[i].advance; } if (ofs.x >= ofs_max) { break; } } // Draw carets. ofs.x = x_ofs + scroll_offset; if (draw_caret || drag_caret_force_displayed) { // Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1). const int caret_width = theme_cache.caret_width * MAX(1, theme_cache.base_scale); if (ime_text.length() == 0) { // Normal caret. CaretInfo caret = TS->shaped_text_get_carets(text_rid, caret_column); if (caret.l_caret == Rect2() && caret.t_caret == Rect2()) { // No carets, add one at the start. int h = theme_cache.font->get_height(theme_cache.font_size); int y = style->get_offset().y + (y_area - h) / 2; if (rtl) { caret.l_dir = TextServer::DIRECTION_RTL; caret.l_caret = Rect2(Vector2(ofs_max, y), Size2(caret_width, h)); } else { caret.l_dir = TextServer::DIRECTION_LTR; caret.l_caret = Rect2(Vector2(x_ofs, y), Size2(caret_width, h)); } RenderingServer::get_singleton()->canvas_item_add_rect(ci, caret.l_caret, caret_color); } else { if (caret.l_caret != Rect2() && caret.l_dir == TextServer::DIRECTION_AUTO) { // Draw extra marker on top of mid caret. Rect2 trect = Rect2(caret.l_caret.position.x - 3 * caret_width, caret.l_caret.position.y, 6 * caret_width, caret_width); trect.position += ofs; RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); } caret.l_caret.position += ofs; caret.l_caret.size.x = caret_width; RenderingServer::get_singleton()->canvas_item_add_rect(ci, caret.l_caret, caret_color); caret.t_caret.position += ofs; caret.t_caret.size.x = caret_width; RenderingServer::get_singleton()->canvas_item_add_rect(ci, caret.t_caret, caret_color); } } else { { // IME intermediate text range. Vector sel = TS->shaped_text_get_selection(text_rid, caret_column, caret_column + ime_text.length()); for (int i = 0; i < sel.size(); i++) { Rect2 rect = Rect2(sel[i].x + ofs.x, ofs.y, sel[i].y - sel[i].x, text_height); if (rect.position.x + rect.size.x <= x_ofs || rect.position.x > ofs_max) { continue; } if (rect.position.x < x_ofs) { rect.size.x -= (x_ofs - rect.position.x); rect.position.x = x_ofs; } else if (rect.position.x + rect.size.x > ofs_max) { rect.size.x = ofs_max - rect.position.x; } rect.size.y = caret_width; RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, caret_color); } } { // IME caret. Vector sel = TS->shaped_text_get_selection(text_rid, caret_column + ime_selection.x, caret_column + ime_selection.x + ime_selection.y); for (int i = 0; i < sel.size(); i++) { Rect2 rect = Rect2(sel[i].x + ofs.x, ofs.y, sel[i].y - sel[i].x, text_height); if (rect.position.x + rect.size.x <= x_ofs || rect.position.x > ofs_max) { continue; } if (rect.position.x < x_ofs) { rect.size.x -= (x_ofs - rect.position.x); rect.position.x = x_ofs; } else if (rect.position.x + rect.size.x > ofs_max) { rect.size.x = ofs_max - rect.position.x; } rect.size.y = caret_width * 3; RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, caret_color); } } } } if (has_focus()) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + Point2(using_placeholder ? 0 : x_ofs, y_ofs + TS->shaped_text_get_size(text_rid).y), get_viewport()->get_window_id()); } } } break; case NOTIFICATION_FOCUS_ENTER: { if (!caret_force_displayed) { if (caret_blink_enabled) { if (!caret_blinking) { caret_blinking = true; caret_blink_timer = 0.0; } } else { draw_caret = true; } } if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); Point2 caret_column = Point2(get_caret_column(), 1) * get_minimum_size().height; DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + caret_column, get_viewport()->get_window_id()); } show_virtual_keyboard(); } break; case NOTIFICATION_FOCUS_EXIT: { if (caret_blink_enabled && !caret_force_displayed) { caret_blinking = false; } if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id()); DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id()); } 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(); } } break; case MainLoop::NOTIFICATION_OS_IME_UPDATE: { if (has_focus()) { ime_text = DisplayServer::get_singleton()->ime_get_text(); ime_selection = DisplayServer::get_singleton()->ime_get_selection(); _shape(); set_caret_column(caret_column); // Update scroll_offset queue_redraw(); } } break; case NOTIFICATION_DRAG_BEGIN: { drag_action = true; } break; case NOTIFICATION_DRAG_END: { if (is_drag_successful()) { if (selection.drag_attempt) { selection.drag_attempt = false; if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { selection_delete(); } else if (deselect_on_focus_loss_enabled) { deselect(); } } } else { selection.drag_attempt = false; } drag_action = false; drag_caret_force_displayed = false; } break; } } void LineEdit::copy_text() { if (selection.enabled && !pass) { DisplayServer::get_singleton()->clipboard_set(text.substr(selection.begin, selection.end - selection.begin)); } } void LineEdit::cut_text() { if (editable && selection.enabled && !pass) { DisplayServer::get_singleton()->clipboard_set(text.substr(selection.begin, selection.end - selection.begin)); selection_delete(); } } void LineEdit::paste_text() { if (!editable) { return; } // Strip escape characters like \n and \t as they can't be displayed on LineEdit. String paste_buffer = DisplayServer::get_singleton()->clipboard_get().strip_escapes(); if (!paste_buffer.is_empty()) { int prev_len = text.length(); if (selection.enabled) { selection_delete(); } insert_text_at_caret(paste_buffer); if (!text_changed_dirty) { if (is_inside_tree() && text.length() != prev_len) { MessageQueue::get_singleton()->push_call(this, "_text_changed"); } text_changed_dirty = true; } } } bool LineEdit::has_undo() const { if (undo_stack_pos == nullptr) { return undo_stack.size() > 1; } return undo_stack_pos != undo_stack.front(); } bool LineEdit::has_redo() const { return undo_stack_pos != nullptr && undo_stack_pos != undo_stack.back(); } void LineEdit::undo() { if (!editable) { return; } if (undo_stack_pos == nullptr) { if (undo_stack.size() <= 1) { return; } undo_stack_pos = undo_stack.back(); } else if (undo_stack_pos == undo_stack.front()) { return; } deselect(); undo_stack_pos = undo_stack_pos->prev(); TextOperation op = undo_stack_pos->get(); text = op.text; scroll_offset = op.scroll_offset; set_caret_column(op.caret_column); _shape(); _emit_text_change(); } void LineEdit::redo() { if (!editable) { return; } if (undo_stack_pos == nullptr) { return; } if (undo_stack_pos == undo_stack.back()) { return; } deselect(); undo_stack_pos = undo_stack_pos->next(); TextOperation op = undo_stack_pos->get(); text = op.text; scroll_offset = op.scroll_offset; set_caret_column(op.caret_column); _shape(); _emit_text_change(); } void LineEdit::shift_selection_check_pre(bool p_shift) { if (!selection.enabled && p_shift) { selection.start_column = caret_column; } if (!p_shift) { deselect(); } } void LineEdit::shift_selection_check_post(bool p_shift) { if (p_shift) { selection_fill_at_caret(); } } void LineEdit::set_caret_at_pixel_pos(int p_x) { Ref style = theme_cache.normal; bool rtl = is_layout_rtl(); int x_ofs = 0; float text_width = TS->shaped_text_get_size(text_rid).x; switch (alignment) { case HORIZONTAL_ALIGNMENT_FILL: case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - style->get_margin(SIDE_RIGHT) - (text_width))); } else { x_ofs = style->get_offset().x; } } break; case HORIZONTAL_ALIGNMENT_CENTER: { if (!Math::is_zero_approx(scroll_offset)) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - (text_width)) / 2); } } break; case HORIZONTAL_ALIGNMENT_RIGHT: { if (rtl) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - style->get_margin(SIDE_RIGHT) - (text_width))); } } break; } bool using_placeholder = text.is_empty() && ime_text.is_empty(); bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { Ref r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); } } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), x_ofs - r_icon->get_width() - style->get_margin(SIDE_RIGHT)); } } int ofs = ceil(TS->shaped_text_hit_test_position(text_rid, p_x - x_ofs - scroll_offset)); set_caret_column(ofs); } Vector2 LineEdit::get_caret_pixel_pos() { Ref style = theme_cache.normal; bool rtl = is_layout_rtl(); int x_ofs = 0; float text_width = TS->shaped_text_get_size(text_rid).x; switch (alignment) { case HORIZONTAL_ALIGNMENT_FILL: case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - style->get_margin(SIDE_RIGHT) - (text_width))); } else { x_ofs = style->get_offset().x; } } break; case HORIZONTAL_ALIGNMENT_CENTER: { if (!Math::is_zero_approx(scroll_offset)) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - (text_width)) / 2); } } break; case HORIZONTAL_ALIGNMENT_RIGHT: { if (rtl) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - style->get_margin(SIDE_RIGHT) - (text_width))); } } break; } bool using_placeholder = text.is_empty() && ime_text.is_empty(); bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { Ref r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); } } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), x_ofs - r_icon->get_width() - style->get_margin(SIDE_RIGHT)); } } Vector2 ret; CaretInfo caret; // Get position of the start of caret. if (ime_text.length() != 0 && 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); } if ((caret.l_caret != Rect2() && (caret.l_dir == TextServer::DIRECTION_AUTO || caret.l_dir == (TextServer::Direction)input_direction)) || (caret.t_caret == Rect2())) { ret.x = x_ofs + caret.l_caret.position.x + scroll_offset; } else { ret.x = x_ofs + caret.t_caret.position.x + scroll_offset; } // Get position of the end of caret. if (ime_text.length() != 0) { if (ime_selection.y != 0) { caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x + ime_selection.y); } else { caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_text.size()); } if ((caret.l_caret != Rect2() && (caret.l_dir == TextServer::DIRECTION_AUTO || caret.l_dir == (TextServer::Direction)input_direction)) || (caret.t_caret == Rect2())) { ret.y = x_ofs + caret.l_caret.position.x + scroll_offset; } else { ret.y = x_ofs + caret.t_caret.position.x + scroll_offset; } } else { ret.y = ret.x; } return ret; } void LineEdit::set_caret_mid_grapheme_enabled(const bool p_enabled) { caret_mid_grapheme_enabled = p_enabled; } bool LineEdit::is_caret_mid_grapheme_enabled() const { return caret_mid_grapheme_enabled; } bool LineEdit::is_caret_blink_enabled() const { return caret_blink_enabled; } void LineEdit::set_caret_blink_enabled(const bool p_enabled) { caret_blink_enabled = p_enabled; set_process_internal(p_enabled); if (has_focus() || caret_force_displayed) { if (p_enabled) { if (!caret_blinking) { caret_blinking = true; caret_blink_timer = 0.0; } } else { caret_blinking = false; } } draw_caret = true; notify_property_list_changed(); } bool LineEdit::is_caret_force_displayed() const { return caret_force_displayed; } void LineEdit::set_caret_force_displayed(const bool p_enabled) { caret_force_displayed = p_enabled; set_caret_blink_enabled(caret_blink_enabled); queue_redraw(); } float LineEdit::get_caret_blink_interval() const { return caret_blink_interval; } void LineEdit::set_caret_blink_interval(const float p_interval) { ERR_FAIL_COND(p_interval <= 0); caret_blink_interval = p_interval; } void LineEdit::_reset_caret_blink_timer() { if (caret_blink_enabled) { draw_caret = true; if (has_focus()) { caret_blink_timer = 0.0; queue_redraw(); } } } void LineEdit::_toggle_draw_caret() { draw_caret = !draw_caret; if (is_visible_in_tree() && ((has_focus() && window_has_focus) || caret_force_displayed)) { queue_redraw(); } } void LineEdit::delete_char() { if ((text.length() <= 0) || (caret_column == 0)) { return; } text = text.left(caret_column - 1) + text.substr(caret_column); _shape(); set_caret_column(get_caret_column() - 1); _text_changed(); } void LineEdit::delete_text(int p_from_column, int p_to_column) { ERR_FAIL_COND_MSG(p_from_column < 0 || p_from_column > p_to_column || p_to_column > text.length(), vformat("Positional parameters (from: %d, to: %d) are inverted or outside the text length (%d).", p_from_column, p_to_column, text.length())); text = text.left(p_from_column) + text.substr(p_to_column); _shape(); caret_column -= CLAMP(caret_column - p_from_column, 0, p_to_column - p_from_column); if (caret_column >= text.length()) { caret_column = text.length(); } if (!text_changed_dirty) { if (is_inside_tree()) { MessageQueue::get_singleton()->push_call(this, "_text_changed"); } text_changed_dirty = true; } } void LineEdit::set_text(String p_text) { clear_internal(); insert_text_at_caret(p_text); _create_undo_state(); queue_redraw(); caret_column = 0; scroll_offset = 0.0; } void LineEdit::set_text_direction(Control::TextDirection p_text_direction) { ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (text_direction != p_text_direction) { text_direction = p_text_direction; if (text_direction != TEXT_DIRECTION_AUTO && text_direction != TEXT_DIRECTION_INHERITED) { input_direction = text_direction; } _shape(); if (menu_dir) { menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); } queue_redraw(); } } Control::TextDirection LineEdit::get_text_direction() const { return text_direction; } void LineEdit::set_language(const String &p_language) { if (language != p_language) { language = p_language; _shape(); queue_redraw(); } } String LineEdit::get_language() const { return language; } void LineEdit::set_draw_control_chars(bool p_draw_control_chars) { if (draw_control_chars != p_draw_control_chars) { draw_control_chars = p_draw_control_chars; if (menu && menu->get_item_index(MENU_DISPLAY_UCC) >= 0) { menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); } _shape(); queue_redraw(); } } bool LineEdit::get_draw_control_chars() const { return draw_control_chars; } void LineEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { st_parser = p_parser; _shape(); queue_redraw(); } } TextServer::StructuredTextParser LineEdit::get_structured_text_bidi_override() const { return st_parser; } void LineEdit::set_structured_text_bidi_override_options(Array p_args) { st_args = p_args; _shape(); queue_redraw(); } Array LineEdit::get_structured_text_bidi_override_options() const { return st_args; } void LineEdit::clear() { clear_internal(); _text_changed(); // This should reset virtual keyboard state if needed. if (has_focus()) { show_virtual_keyboard(); } } void LineEdit::show_virtual_keyboard() { 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); } else { DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), DisplayServer::VirtualKeyboardType(virtual_keyboard_type), max_length, caret_column); } } } String LineEdit::get_text() const { return text; } void LineEdit::set_placeholder(String p_text) { if (placeholder == p_text) { return; } placeholder = p_text; placeholder_translated = atr(placeholder); _shape(); queue_redraw(); } String LineEdit::get_placeholder() const { return placeholder; } void LineEdit::set_caret_column(int p_column) { if (p_column > (int)text.length()) { p_column = text.length(); } if (p_column < 0) { p_column = 0; } caret_column = p_column; // Fit to window. if (!is_inside_tree()) { scroll_offset = 0.0; return; } Ref style = theme_cache.normal; bool rtl = is_layout_rtl(); int x_ofs = 0; float text_width = TS->shaped_text_get_size(text_rid).x; switch (alignment) { case HORIZONTAL_ALIGNMENT_FILL: case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - style->get_margin(SIDE_RIGHT) - (text_width))); } else { x_ofs = style->get_offset().x; } } break; case HORIZONTAL_ALIGNMENT_CENTER: { if (!Math::is_zero_approx(scroll_offset)) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - (text_width)) / 2); } } break; case HORIZONTAL_ALIGNMENT_RIGHT: { if (rtl) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - style->get_margin(SIDE_RIGHT) - (text_width))); } } break; } int ofs_max = get_size().width - style->get_margin(SIDE_RIGHT); bool using_placeholder = text.is_empty() && ime_text.is_empty(); bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { Ref r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); } } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), x_ofs - r_icon->get_width() - style->get_margin(SIDE_RIGHT)); } ofs_max -= r_icon->get_width(); } // Note: Use two coordinates to fit IME input range. Vector2 primary_caret_offset = get_caret_pixel_pos(); if (MIN(primary_caret_offset.x, primary_caret_offset.y) <= x_ofs) { scroll_offset += x_ofs - MIN(primary_caret_offset.x, primary_caret_offset.y); } else if (MAX(primary_caret_offset.x, primary_caret_offset.y) >= ofs_max) { scroll_offset += ofs_max - MAX(primary_caret_offset.x, primary_caret_offset.y); } scroll_offset = MIN(0, scroll_offset); queue_redraw(); } int LineEdit::get_caret_column() const { return caret_column; } void LineEdit::set_scroll_offset(float p_pos) { scroll_offset = p_pos; if (scroll_offset < 0.0) { scroll_offset = 0.0; } } float LineEdit::get_scroll_offset() const { return scroll_offset; } void LineEdit::insert_text_at_caret(String p_text) { if (max_length > 0) { // Truncate text to append to fit in max_length, if needed. int available_chars = max_length - text.length(); if (p_text.length() > available_chars) { emit_signal(SNAME("text_change_rejected"), p_text.substr(available_chars)); p_text = p_text.substr(0, available_chars); } } String pre = text.substr(0, caret_column); String post = text.substr(caret_column, text.length() - caret_column); text = pre + p_text + post; _shape(); TextServer::Direction dir = TS->shaped_text_get_dominant_direction_in_range(text_rid, caret_column, caret_column + p_text.length()); if (dir != TextServer::DIRECTION_AUTO) { input_direction = (TextDirection)dir; } set_caret_column(caret_column + p_text.length()); } void LineEdit::clear_internal() { deselect(); _clear_undo_stack(); caret_column = 0; scroll_offset = 0.0; undo_text = ""; text = ""; _shape(); queue_redraw(); } Size2 LineEdit::get_minimum_size() const { Ref style = theme_cache.normal; Ref font = theme_cache.font; int font_size = theme_cache.font_size; Size2 min_size; // Minimum size of text. float em_space_size = font->get_char_size('M', font_size).x; min_size.width = theme_cache.minimum_character_width * em_space_size; if (expand_to_text_length) { // Add a space because some fonts are too exact, and because caret needs a bit more when at the end. min_size.width = MAX(min_size.width, full_width + em_space_size); } min_size.height = MAX(TS->shaped_text_get_size(text_rid).y, font->get_height(font_size)); // Take icons into account. int icon_max_width = 0; if (right_icon.is_valid()) { min_size.height = MAX(min_size.height, right_icon->get_height()); icon_max_width = right_icon->get_width(); } if (clear_button_enabled) { min_size.height = MAX(min_size.height, theme_cache.clear_icon->get_height()); icon_max_width = MAX(icon_max_width, theme_cache.clear_icon->get_width()); } min_size.width += icon_max_width; return style->get_minimum_size() + min_size; } void LineEdit::deselect() { selection.begin = 0; selection.end = 0; selection.start_column = 0; selection.enabled = false; selection.creating = false; selection.double_click = false; queue_redraw(); } bool LineEdit::has_selection() const { return selection.enabled; } int LineEdit::get_selection_from_column() const { ERR_FAIL_COND_V(!selection.enabled, -1); return selection.begin; } int LineEdit::get_selection_to_column() const { ERR_FAIL_COND_V(!selection.enabled, -1); return selection.end; } void LineEdit::selection_delete() { if (selection.enabled) { delete_text(selection.begin, selection.end); } deselect(); } void LineEdit::set_max_length(int p_max_length) { ERR_FAIL_COND(p_max_length < 0); max_length = p_max_length; set_text(text); } int LineEdit::get_max_length() const { return max_length; } void LineEdit::selection_fill_at_caret() { if (!selecting_enabled) { return; } selection.begin = caret_column; selection.end = selection.start_column; if (selection.end < selection.begin) { int aux = selection.end; selection.end = selection.begin; selection.begin = aux; } selection.enabled = (selection.begin != selection.end); } void LineEdit::select_all() { if (!selecting_enabled) { return; } if (!text.length()) { return; } selection.begin = 0; selection.end = text.length(); selection.enabled = true; queue_redraw(); } void LineEdit::set_editable(bool p_editable) { if (editable == p_editable) { return; } editable = p_editable; update_minimum_size(); queue_redraw(); } bool LineEdit::is_editable() const { return editable; } void LineEdit::set_secret(bool p_secret) { if (pass == p_secret) { return; } pass = p_secret; _shape(); queue_redraw(); } bool LineEdit::is_secret() const { return pass; } void LineEdit::set_secret_character(const String &p_string) { // An empty string as the secret character would crash the engine. // It also wouldn't make sense to use multiple characters as the secret character. ERR_FAIL_COND_MSG(p_string.length() != 1, "Secret character must be exactly one character long (" + itos(p_string.length()) + " characters given)."); if (secret_character == p_string) { return; } secret_character = p_string; _shape(); queue_redraw(); } String LineEdit::get_secret_character() const { return secret_character; } void LineEdit::select(int p_from, int p_to) { if (!selecting_enabled) { return; } if (p_from == 0 && p_to == 0) { deselect(); return; } int len = text.length(); if (p_from < 0) { p_from = 0; } if (p_from > len) { p_from = len; } if (p_to < 0 || p_to > len) { p_to = len; } if (p_from >= p_to) { return; } selection.enabled = true; selection.begin = p_from; selection.end = p_to; selection.creating = false; selection.double_click = false; queue_redraw(); } bool LineEdit::is_text_field() const { return true; } void LineEdit::menu_option(int p_option) { switch (p_option) { case MENU_CUT: { if (editable) { cut_text(); } } break; case MENU_COPY: { copy_text(); } break; case MENU_PASTE: { if (editable) { paste_text(); } } break; case MENU_CLEAR: { if (editable) { clear(); } } break; case MENU_SELECT_ALL: { select_all(); } break; case MENU_UNDO: { if (editable) { undo(); } } break; case MENU_REDO: { if (editable) { redo(); } } break; case MENU_DIR_INHERITED: { set_text_direction(TEXT_DIRECTION_INHERITED); } break; case MENU_DIR_AUTO: { set_text_direction(TEXT_DIRECTION_AUTO); } break; case MENU_DIR_LTR: { set_text_direction(TEXT_DIRECTION_LTR); } break; case MENU_DIR_RTL: { set_text_direction(TEXT_DIRECTION_RTL); } break; case MENU_DISPLAY_UCC: { set_draw_control_chars(!get_draw_control_chars()); } break; case MENU_INSERT_LRM: { if (editable) { insert_text_at_caret(String::chr(0x200E)); } } break; case MENU_INSERT_RLM: { if (editable) { insert_text_at_caret(String::chr(0x200F)); } } break; case MENU_INSERT_LRE: { if (editable) { insert_text_at_caret(String::chr(0x202A)); } } break; case MENU_INSERT_RLE: { if (editable) { insert_text_at_caret(String::chr(0x202B)); } } break; case MENU_INSERT_LRO: { if (editable) { insert_text_at_caret(String::chr(0x202D)); } } break; case MENU_INSERT_RLO: { if (editable) { insert_text_at_caret(String::chr(0x202E)); } } break; case MENU_INSERT_PDF: { if (editable) { insert_text_at_caret(String::chr(0x202C)); } } break; case MENU_INSERT_ALM: { if (editable) { insert_text_at_caret(String::chr(0x061C)); } } break; case MENU_INSERT_LRI: { if (editable) { insert_text_at_caret(String::chr(0x2066)); } } break; case MENU_INSERT_RLI: { if (editable) { insert_text_at_caret(String::chr(0x2067)); } } break; case MENU_INSERT_FSI: { if (editable) { insert_text_at_caret(String::chr(0x2068)); } } break; case MENU_INSERT_PDI: { if (editable) { insert_text_at_caret(String::chr(0x2069)); } } break; case MENU_INSERT_ZWJ: { if (editable) { insert_text_at_caret(String::chr(0x200D)); } } break; case MENU_INSERT_ZWNJ: { if (editable) { insert_text_at_caret(String::chr(0x200C)); } } break; case MENU_INSERT_WJ: { if (editable) { insert_text_at_caret(String::chr(0x2060)); } } break; case MENU_INSERT_SHY: { if (editable) { insert_text_at_caret(String::chr(0x00AD)); } } } } void LineEdit::set_context_menu_enabled(bool p_enable) { context_menu_enabled = p_enable; } bool LineEdit::is_context_menu_enabled() { return context_menu_enabled; } bool LineEdit::is_menu_visible() const { return menu && menu->is_visible(); } PopupMenu *LineEdit::get_menu() const { const_cast(this)->_ensure_menu(); return menu; } void LineEdit::_editor_settings_changed() { #ifdef TOOLS_ENABLED set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink")); set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval")); #endif } void LineEdit::set_expand_to_text_length_enabled(bool p_enabled) { expand_to_text_length = p_enabled; update_minimum_size(); set_caret_column(caret_column); } bool LineEdit::is_expand_to_text_length_enabled() const { return expand_to_text_length; } void LineEdit::set_clear_button_enabled(bool p_enabled) { if (clear_button_enabled == p_enabled) { return; } clear_button_enabled = p_enabled; _fit_to_width(); update_minimum_size(); queue_redraw(); } bool LineEdit::is_clear_button_enabled() const { return clear_button_enabled; } void LineEdit::set_shortcut_keys_enabled(bool p_enabled) { shortcut_keys_enabled = p_enabled; } bool LineEdit::is_shortcut_keys_enabled() const { return shortcut_keys_enabled; } void LineEdit::set_virtual_keyboard_enabled(bool p_enable) { virtual_keyboard_enabled = p_enable; } bool LineEdit::is_virtual_keyboard_enabled() const { return virtual_keyboard_enabled; } void LineEdit::set_virtual_keyboard_type(VirtualKeyboardType p_type) { virtual_keyboard_type = p_type; } LineEdit::VirtualKeyboardType LineEdit::get_virtual_keyboard_type() const { return virtual_keyboard_type; } void LineEdit::set_middle_mouse_paste_enabled(bool p_enabled) { middle_mouse_paste_enabled = p_enabled; } bool LineEdit::is_middle_mouse_paste_enabled() const { return middle_mouse_paste_enabled; } void LineEdit::set_selecting_enabled(bool p_enabled) { if (selecting_enabled == p_enabled) { return; } selecting_enabled = p_enabled; if (!selecting_enabled) { deselect(); } } bool LineEdit::is_selecting_enabled() const { return selecting_enabled; } void LineEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) { if (deselect_on_focus_loss_enabled == p_enabled) { return; } deselect_on_focus_loss_enabled = p_enabled; if (p_enabled && selection.enabled && !has_focus()) { deselect(); } } bool LineEdit::is_deselect_on_focus_loss_enabled() const { return deselect_on_focus_loss_enabled; } void LineEdit::set_right_icon(const Ref &p_icon) { if (right_icon == p_icon) { return; } right_icon = p_icon; _fit_to_width(); update_minimum_size(); queue_redraw(); } Ref LineEdit::get_right_icon() { return right_icon; } void LineEdit::set_flat(bool p_enabled) { if (flat != p_enabled) { flat = p_enabled; queue_redraw(); } } bool LineEdit::is_flat() const { return flat; } void LineEdit::_text_changed() { _emit_text_change(); _clear_redo(); } void LineEdit::_emit_text_change() { emit_signal(SNAME("text_changed"), text); text_changed_dirty = false; } void LineEdit::_shape() { Size2 old_size = TS->shaped_text_get_size(text_rid); TS->shaped_text_clear(text_rid); String t; if (text.length() == 0 && ime_text.length() == 0) { t = placeholder_translated; } else if (pass) { t = secret_character.repeat(text.length() + ime_text.length()); } else { if (ime_text.length() > 0) { t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length()); } else { t = text; } } if (text_direction == Control::TEXT_DIRECTION_INHERITED) { TS->shaped_text_set_direction(text_rid, is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); } else { TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); } TS->shaped_text_set_preserve_control(text_rid, draw_control_chars); const Ref &font = theme_cache.font; int font_size = theme_cache.font_size; ERR_FAIL_COND(font.is_null()); TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, font->get_opentype_features(), language); for (int i = 0; i < TextServer::SPACING_MAX; i++) { TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); } TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, t)); full_width = TS->shaped_text_get_size(text_rid).x; _fit_to_width(); Size2 size = TS->shaped_text_get_size(text_rid); if ((expand_to_text_length && old_size.x != size.x) || (old_size.y != size.y)) { update_minimum_size(); } } void LineEdit::_fit_to_width() { if (alignment == HORIZONTAL_ALIGNMENT_FILL) { Ref style = theme_cache.normal; int t_width = get_size().width - style->get_margin(SIDE_RIGHT) - style->get_margin(SIDE_LEFT); bool using_placeholder = text.is_empty() && ime_text.is_empty(); bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { Ref r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; t_width -= r_icon->get_width(); } TS->shaped_text_fit_to_width(text_rid, MAX(t_width, full_width)); } } void LineEdit::_clear_redo() { _create_undo_state(); if (undo_stack_pos == nullptr) { return; } undo_stack_pos = undo_stack_pos->next(); while (undo_stack_pos) { List::Element *elem = undo_stack_pos; undo_stack_pos = undo_stack_pos->next(); undo_stack.erase(elem); } _create_undo_state(); } void LineEdit::_clear_undo_stack() { undo_stack.clear(); undo_stack_pos = nullptr; _create_undo_state(); } void LineEdit::_create_undo_state() { TextOperation op; op.text = text; op.caret_column = caret_column; op.scroll_offset = scroll_offset; undo_stack.push_back(op); } Key LineEdit::_get_menu_action_accelerator(const String &p_action) { const List> *events = InputMap::get_singleton()->action_get_events(p_action); if (!events) { return Key::NONE; } // Use first event in the list for the accelerator. const List>::Element *first_event = events->front(); if (!first_event) { return Key::NONE; } const Ref event = first_event->get(); if (event.is_null()) { return Key::NONE; } // Use physical keycode if non-zero if (event->get_physical_keycode() != Key::NONE) { return event->get_physical_keycode_with_modifiers(); } else { return event->get_keycode_with_modifiers(); } } void LineEdit::_validate_property(PropertyInfo &p_property) const { if (!caret_blink_enabled && p_property.name == "caret_blink_interval") { p_property.usage = PROPERTY_USAGE_NO_EDITOR; } } void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_text_changed"), &LineEdit::_text_changed); 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("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); ClassDB::bind_method(D_METHOD("deselect"), &LineEdit::deselect); ClassDB::bind_method(D_METHOD("has_selection"), &LineEdit::has_selection); ClassDB::bind_method(D_METHOD("get_selection_from_column"), &LineEdit::get_selection_from_column); ClassDB::bind_method(D_METHOD("get_selection_to_column"), &LineEdit::get_selection_to_column); ClassDB::bind_method(D_METHOD("set_text", "text"), &LineEdit::set_text); ClassDB::bind_method(D_METHOD("get_text"), &LineEdit::get_text); ClassDB::bind_method(D_METHOD("get_draw_control_chars"), &LineEdit::get_draw_control_chars); ClassDB::bind_method(D_METHOD("set_draw_control_chars", "enable"), &LineEdit::set_draw_control_chars); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &LineEdit::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &LineEdit::get_text_direction); ClassDB::bind_method(D_METHOD("set_language", "language"), &LineEdit::set_language); ClassDB::bind_method(D_METHOD("get_language"), &LineEdit::get_language); ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &LineEdit::set_structured_text_bidi_override); ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &LineEdit::get_structured_text_bidi_override); ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &LineEdit::set_structured_text_bidi_override_options); ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &LineEdit::get_structured_text_bidi_override_options); ClassDB::bind_method(D_METHOD("set_placeholder", "text"), &LineEdit::set_placeholder); ClassDB::bind_method(D_METHOD("get_placeholder"), &LineEdit::get_placeholder); ClassDB::bind_method(D_METHOD("set_caret_column", "position"), &LineEdit::set_caret_column); ClassDB::bind_method(D_METHOD("get_caret_column"), &LineEdit::get_caret_column); ClassDB::bind_method(D_METHOD("get_scroll_offset"), &LineEdit::get_scroll_offset); ClassDB::bind_method(D_METHOD("set_expand_to_text_length_enabled", "enabled"), &LineEdit::set_expand_to_text_length_enabled); ClassDB::bind_method(D_METHOD("is_expand_to_text_length_enabled"), &LineEdit::is_expand_to_text_length_enabled); ClassDB::bind_method(D_METHOD("set_caret_blink_enabled", "enabled"), &LineEdit::set_caret_blink_enabled); ClassDB::bind_method(D_METHOD("is_caret_blink_enabled"), &LineEdit::is_caret_blink_enabled); ClassDB::bind_method(D_METHOD("set_caret_mid_grapheme_enabled", "enabled"), &LineEdit::set_caret_mid_grapheme_enabled); ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &LineEdit::is_caret_mid_grapheme_enabled); ClassDB::bind_method(D_METHOD("set_caret_force_displayed", "enabled"), &LineEdit::set_caret_force_displayed); ClassDB::bind_method(D_METHOD("is_caret_force_displayed"), &LineEdit::is_caret_force_displayed); ClassDB::bind_method(D_METHOD("set_caret_blink_interval", "interval"), &LineEdit::set_caret_blink_interval); ClassDB::bind_method(D_METHOD("get_caret_blink_interval"), &LineEdit::get_caret_blink_interval); ClassDB::bind_method(D_METHOD("set_max_length", "chars"), &LineEdit::set_max_length); ClassDB::bind_method(D_METHOD("get_max_length"), &LineEdit::get_max_length); ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text"), &LineEdit::insert_text_at_caret); ClassDB::bind_method(D_METHOD("delete_char_at_caret"), &LineEdit::delete_char); ClassDB::bind_method(D_METHOD("delete_text", "from_column", "to_column"), &LineEdit::delete_text); ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &LineEdit::set_editable); ClassDB::bind_method(D_METHOD("is_editable"), &LineEdit::is_editable); ClassDB::bind_method(D_METHOD("set_secret", "enabled"), &LineEdit::set_secret); ClassDB::bind_method(D_METHOD("is_secret"), &LineEdit::is_secret); ClassDB::bind_method(D_METHOD("set_secret_character", "character"), &LineEdit::set_secret_character); ClassDB::bind_method(D_METHOD("get_secret_character"), &LineEdit::get_secret_character); ClassDB::bind_method(D_METHOD("menu_option", "option"), &LineEdit::menu_option); ClassDB::bind_method(D_METHOD("get_menu"), &LineEdit::get_menu); ClassDB::bind_method(D_METHOD("is_menu_visible"), &LineEdit::is_menu_visible); ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &LineEdit::set_context_menu_enabled); ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &LineEdit::is_context_menu_enabled); ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enable"), &LineEdit::set_virtual_keyboard_enabled); ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &LineEdit::is_virtual_keyboard_enabled); ClassDB::bind_method(D_METHOD("set_virtual_keyboard_type", "type"), &LineEdit::set_virtual_keyboard_type); ClassDB::bind_method(D_METHOD("get_virtual_keyboard_type"), &LineEdit::get_virtual_keyboard_type); ClassDB::bind_method(D_METHOD("set_clear_button_enabled", "enable"), &LineEdit::set_clear_button_enabled); ClassDB::bind_method(D_METHOD("is_clear_button_enabled"), &LineEdit::is_clear_button_enabled); ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enable"), &LineEdit::set_shortcut_keys_enabled); ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &LineEdit::is_shortcut_keys_enabled); ClassDB::bind_method(D_METHOD("set_middle_mouse_paste_enabled", "enable"), &LineEdit::set_middle_mouse_paste_enabled); ClassDB::bind_method(D_METHOD("is_middle_mouse_paste_enabled"), &LineEdit::is_middle_mouse_paste_enabled); ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &LineEdit::set_selecting_enabled); ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &LineEdit::is_selecting_enabled); ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &LineEdit::set_deselect_on_focus_loss_enabled); ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &LineEdit::is_deselect_on_focus_loss_enabled); ClassDB::bind_method(D_METHOD("set_right_icon", "icon"), &LineEdit::set_right_icon); ClassDB::bind_method(D_METHOD("get_right_icon"), &LineEdit::get_right_icon); ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &LineEdit::set_flat); ClassDB::bind_method(D_METHOD("is_flat"), &LineEdit::is_flat); 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"))); BIND_ENUM_CONSTANT(MENU_CUT); BIND_ENUM_CONSTANT(MENU_COPY); BIND_ENUM_CONSTANT(MENU_PASTE); BIND_ENUM_CONSTANT(MENU_CLEAR); BIND_ENUM_CONSTANT(MENU_SELECT_ALL); BIND_ENUM_CONSTANT(MENU_UNDO); BIND_ENUM_CONSTANT(MENU_REDO); BIND_ENUM_CONSTANT(MENU_DIR_INHERITED); BIND_ENUM_CONSTANT(MENU_DIR_AUTO); BIND_ENUM_CONSTANT(MENU_DIR_LTR); BIND_ENUM_CONSTANT(MENU_DIR_RTL); BIND_ENUM_CONSTANT(MENU_DISPLAY_UCC); BIND_ENUM_CONSTANT(MENU_INSERT_LRM); BIND_ENUM_CONSTANT(MENU_INSERT_RLM); BIND_ENUM_CONSTANT(MENU_INSERT_LRE); BIND_ENUM_CONSTANT(MENU_INSERT_RLE); BIND_ENUM_CONSTANT(MENU_INSERT_LRO); BIND_ENUM_CONSTANT(MENU_INSERT_RLO); BIND_ENUM_CONSTANT(MENU_INSERT_PDF); BIND_ENUM_CONSTANT(MENU_INSERT_ALM); BIND_ENUM_CONSTANT(MENU_INSERT_LRI); BIND_ENUM_CONSTANT(MENU_INSERT_RLI); BIND_ENUM_CONSTANT(MENU_INSERT_FSI); BIND_ENUM_CONSTANT(MENU_INSERT_PDI); BIND_ENUM_CONSTANT(MENU_INSERT_ZWJ); BIND_ENUM_CONSTANT(MENU_INSERT_ZWNJ); BIND_ENUM_CONSTANT(MENU_INSERT_WJ); BIND_ENUM_CONSTANT(MENU_INSERT_SHY); BIND_ENUM_CONSTANT(MENU_MAX); BIND_ENUM_CONSTANT(KEYBOARD_TYPE_DEFAULT); BIND_ENUM_CONSTANT(KEYBOARD_TYPE_MULTILINE); BIND_ENUM_CONSTANT(KEYBOARD_TYPE_NUMBER); BIND_ENUM_CONSTANT(KEYBOARD_TYPE_NUMBER_DECIMAL); BIND_ENUM_CONSTANT(KEYBOARD_TYPE_PHONE); BIND_ENUM_CONSTANT(KEYBOARD_TYPE_EMAIL_ADDRESS); BIND_ENUM_CONSTANT(KEYBOARD_TYPE_PASSWORD); BIND_ENUM_CONSTANT(KEYBOARD_TYPE_URL); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder"); ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_length", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_max_length", "get_max_length"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "secret"), "set_secret", "is_secret"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "secret_character"), "set_secret_character", "get_secret_character"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length_enabled", "is_expand_to_text_length_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "virtual_keyboard_type", PROPERTY_HINT_ENUM, "Default,Multiline,Number,Decimal,Phone,Email,Password,URL"), "set_virtual_keyboard_type", "get_virtual_keyboard_type"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clear_button_enabled"), "set_clear_button_enabled", "is_clear_button_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_interval", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_interval", "get_caret_blink_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_column", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_caret_column", "get_caret_column"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "set_caret_force_displayed", "is_caret_force_displayed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); } void LineEdit::_ensure_menu() { if (!menu) { menu = memnew(PopupMenu); add_child(menu, false, INTERNAL_MODE_FRONT); menu_dir = memnew(PopupMenu); menu_dir->set_name("DirMenu"); menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED); menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO); menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR); menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL); menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT); menu_ctl = memnew(PopupMenu); menu_ctl->set_name("CTLMenu"); menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM); menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM); menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE); menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE); menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO); menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO); menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF); menu_ctl->add_separator(); menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM); menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI); menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI); menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI); menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI); menu_ctl->add_separator(); menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ); menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ); menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ); menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY); menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT); menu->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); menu_ctl->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); } // Reorganize context menu. menu->clear(); if (editable) { menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : Key::NONE); } menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE); if (editable) { menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : Key::NONE); } menu->add_separator(); if (is_selecting_enabled()) { menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE); } if (editable) { menu->add_item(RTR("Clear"), MENU_CLEAR); menu->add_separator(); menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : Key::NONE); menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : Key::NONE); } menu->add_separator(); menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu"); menu->add_separator(); menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC); menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); if (editable) { menu->add_submenu_item(RTR("Insert Control Character"), "CTLMenu"); } menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); if (editable) { menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo()); menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo()); } } LineEdit::LineEdit(const String &p_placeholder) { text_rid = TS->create_shaped_text(); _create_undo_state(); deselect(); set_focus_mode(FOCUS_ALL); set_default_cursor_shape(CURSOR_IBEAM); set_mouse_filter(MOUSE_FILTER_STOP); set_process_unhandled_key_input(true); set_caret_blink_enabled(false); set_placeholder(p_placeholder); set_editable(true); // Initialise to opposite first, so we get past the early-out in set_editable. } LineEdit::~LineEdit() { TS->free_rid(text_rid); }