summaryrefslogtreecommitdiff
path: root/scene/gui/line_edit.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui/line_edit.cpp')
-rw-r--r--scene/gui/line_edit.cpp1097
1 files changed, 735 insertions, 362 deletions
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 857c96bea3..2eaa814419 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -37,19 +37,21 @@
#include "core/string/translation.h"
#include "label.h"
#include "servers/display_server.h"
+#include "servers/text_server.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#endif
#include "scene/main/window.h"
-static bool _is_text_char(char32_t c) {
- return !is_symbol(c);
-}
void LineEdit::_gui_input(Ref<InputEvent> p_event) {
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
+ if (ime_text.length() != 0) {
+ // Ignore mouse clicks in IME input mode.
+ return;
+ }
if (b->is_pressed() && b->get_button_index() == BUTTON_RIGHT && context_menu_enabled) {
menu->set_position(get_screen_transform().xform(get_local_mouse_position()));
menu->set_size(Vector2(1, 1));
@@ -200,6 +202,18 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
bool handled = true;
switch (code) {
+ case (KEY_QUOTELEFT): { // Swap current input direction (primary cursor)
+
+ if (input_direction == TEXT_DIRECTION_LTR) {
+ input_direction = TEXT_DIRECTION_RTL;
+ } else {
+ input_direction = TEXT_DIRECTION_LTR;
+ }
+ set_cursor_position(get_cursor_position());
+ update();
+
+ } break;
+
case (KEY_X): { // CUT.
if (editable) {
@@ -237,7 +251,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
if (editable) {
deselect();
text = text.substr(cursor_pos, text.length() - cursor_pos);
- update_cached_width();
+ _shape();
set_cursor_position(0);
_text_changed();
}
@@ -335,17 +349,13 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
} else if (k->get_command()) {
#endif
int cc = cursor_pos;
- bool prev_char = false;
- while (cc > 0) {
- bool ischar = _is_text_char(text[cc - 1]);
-
- if (prev_char && !ischar) {
+ Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid);
+ for (int i = words.size() - 1; i >= 0; i--) {
+ if (words[i].x < cc) {
+ cc = words[i].x;
break;
}
-
- prev_char = ischar;
- cc--;
}
delete_text(cc, cursor_pos);
@@ -390,24 +400,24 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
break;
} else if (k->get_command()) {
#endif
- bool prev_char = false;
int cc = cursor_pos;
- while (cc > 0) {
- bool ischar = _is_text_char(text[cc - 1]);
-
- if (prev_char && !ischar) {
+ Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid);
+ for (int i = words.size() - 1; i >= 0; i--) {
+ if (words[i].x < cc) {
+ cc = words[i].x;
break;
}
-
- prev_char = ischar;
- cc--;
}
set_cursor_position(cc);
} else {
- set_cursor_position(get_cursor_position() - 1);
+ if (mid_grapheme_caret_enabled) {
+ set_cursor_position(get_cursor_position() - 1);
+ } else {
+ set_cursor_position(TS->shaped_text_prev_grapheme_pos(text_rid, get_cursor_position()));
+ }
}
shift_selection_check_post(k->get_shift());
@@ -446,24 +456,24 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
break;
} else if (k->get_command()) {
#endif
- bool prev_char = false;
int cc = cursor_pos;
- while (cc < text.length()) {
- bool ischar = _is_text_char(text[cc]);
-
- if (prev_char && !ischar) {
+ Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid);
+ for (int i = 0; i < words.size(); i++) {
+ if (words[i].y > cc) {
+ cc = words[i].y;
break;
}
-
- prev_char = ischar;
- cc++;
}
set_cursor_position(cc);
} else {
- set_cursor_position(get_cursor_position() + 1);
+ if (mid_grapheme_caret_enabled) {
+ set_cursor_position(get_cursor_position() + 1);
+ } else {
+ set_cursor_position(TS->shaped_text_next_grapheme_pos(text_rid, get_cursor_position()));
+ }
}
shift_selection_check_post(k->get_shift());
@@ -516,23 +526,25 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
#endif
int cc = cursor_pos;
- bool prev_char = false;
-
- while (cc < text.length()) {
- bool ischar = _is_text_char(text[cc]);
-
- if (prev_char && !ischar) {
+ Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid);
+ for (int i = 0; i < words.size(); i++) {
+ if (words[i].y > cc) {
+ cc = words[i].y;
break;
}
- prev_char = ischar;
- cc++;
}
delete_text(cursor_pos, cc);
} else {
- set_cursor_position(cursor_pos + 1);
- delete_char();
+ if (mid_grapheme_caret_enabled) {
+ set_cursor_position(cursor_pos + 1);
+ delete_char();
+ } else {
+ int cc = cursor_pos;
+ set_cursor_position(TS->shaped_text_next_grapheme_pos(text_rid, cursor_pos));
+ delete_text(cc, cursor_pos);
+ }
}
} break;
@@ -562,10 +574,10 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
} break;
case KEY_MENU: {
if (context_menu_enabled) {
- Point2 pos = Point2(get_cursor_pixel_pos(), (get_size().y + get_theme_font("font")->get_height()) / 2);
+ Point2 pos = Point2(get_cursor_pixel_pos().x, (get_size().y + get_theme_font("font")->get_height(get_theme_font_size("font_size"))) / 2);
menu->set_position(get_global_transform().xform(pos));
menu->set_size(Vector2(1, 1));
- // menu->set_scale(get_global_transform().get_scale());
+ //menu->set_scale(get_global_transform().get_scale());
menu->popup();
menu->grab_focus();
}
@@ -605,7 +617,10 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
void LineEdit::set_align(Align p_align) {
ERR_FAIL_INDEX((int)p_align, 4);
- align = p_align;
+ if (align != p_align) {
+ align = p_align;
+ _shape();
+ }
update();
}
@@ -634,14 +649,8 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
set_cursor_at_pixel_pos(p_point.x);
int selected = selection.end - selection.begin;
- Ref<Font> font = get_theme_font("font");
- if (font != nullptr) {
- for (int i = selection.begin; i < selection.end; i++) {
- cached_width -= font->get_char_size(pass ? secret_character[0] : text[i]).width;
- }
- }
-
text.erase(selection.begin, selected);
+ _shape();
append_at_cursor(p_data);
selection.begin = cursor_pos - selected;
@@ -680,13 +689,18 @@ void LineEdit::_notification(int p_what) {
} break;
#endif
case NOTIFICATION_RESIZED: {
+ _fit_to_width();
scroll_offset = 0;
set_cursor_position(get_cursor_position());
-
+ } break;
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
+ case NOTIFICATION_THEME_CHANGED: {
+ _shape();
+ update();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
placeholder_translated = tr(placeholder);
- update_placeholder_width();
+ _shape();
update();
} break;
case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
@@ -705,6 +719,7 @@ void LineEdit::_notification(int p_what) {
}
int width, height;
+ bool rtl = is_layout_rtl();
Size2 size = get_size();
width = size.width;
@@ -718,8 +733,6 @@ void LineEdit::_notification(int p_what) {
draw_caret = false;
}
- Ref<Font> font = get_theme_font("font");
-
style->draw(ci, Rect2(Point2(), size));
if (has_focus()) {
@@ -728,39 +741,44 @@ void LineEdit::_notification(int p_what) {
int x_ofs = 0;
bool using_placeholder = text.empty() && ime_text.empty();
- int cached_text_width = using_placeholder ? cached_placeholder_width : cached_width;
+ float text_width = TS->shaped_text_get_size(text_rid).x;
+ float text_height = TS->shaped_text_get_size(text_rid).y;
switch (align) {
case ALIGN_FILL:
case ALIGN_LEFT: {
- x_ofs = style->get_offset().x;
+ if (rtl) {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - style->get_margin(MARGIN_RIGHT) - (text_width)));
+ } else {
+ x_ofs = style->get_offset().x;
+ }
} break;
case ALIGN_CENTER: {
if (scroll_offset != 0) {
x_ofs = style->get_offset().x;
} else {
- x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - (cached_text_width)) / 2);
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - (text_width)) / 2);
}
} break;
case ALIGN_RIGHT: {
- x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - style->get_margin(MARGIN_RIGHT) - (cached_text_width)));
+ if (rtl) {
+ x_ofs = style->get_offset().x;
+ } else {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - style->get_margin(MARGIN_RIGHT) - (text_width)));
+ }
} break;
}
int ofs_max = width - style->get_margin(MARGIN_RIGHT);
- int char_ofs = scroll_offset;
int y_area = height - style->get_minimum_size().height;
- int y_ofs = style->get_offset().y + (y_area - font->get_height()) / 2;
-
- int font_ascent = font->get_ascent();
+ int y_ofs = style->get_offset().y + (y_area - text_height) / 2;
Color selection_color = get_theme_color("selection_color");
Color font_color = is_editable() ? get_theme_color("font_color") : get_theme_color("font_color_uneditable");
Color font_color_selected = get_theme_color("font_color_selected");
Color cursor_color = get_theme_color("cursor_color");
- const String &t = using_placeholder ? placeholder_translated : text;
// Draw placeholder color.
if (using_placeholder) {
font_color.a *= placeholder_alpha;
@@ -782,7 +800,7 @@ void LineEdit::_notification(int p_what) {
if (align == ALIGN_CENTER) {
if (scroll_offset == 0) {
- x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - cached_text_width - r_icon->get_width() - style->get_margin(MARGIN_RIGHT) * 2) / 2);
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - text_width - r_icon->get_width() - style->get_margin(MARGIN_RIGHT) * 2) / 2);
}
} else {
x_ofs = MAX(style->get_margin(MARGIN_LEFT), x_ofs - r_icon->get_width() - style->get_margin(MARGIN_RIGHT));
@@ -791,137 +809,133 @@ void LineEdit::_notification(int p_what) {
ofs_max -= r_icon->get_width();
}
- int caret_height = font->get_height() > y_area ? y_area : font->get_height();
- FontDrawer drawer(font, Color(1, 1, 1));
- while (true) {
- // End of string, break.
- if (char_ofs >= t.length()) {
- break;
- }
-
- if (char_ofs == cursor_pos) {
- if (ime_text.length() > 0) {
- int ofs = 0;
- while (true) {
- if (ofs >= ime_text.length()) {
- break;
- }
-
- char32_t cchar = (pass && !text.empty()) ? secret_character[0] : ime_text[ofs];
- char32_t next = (pass && !text.empty()) ? secret_character[0] : ime_text[ofs + 1];
- int im_char_width = font->get_char_size(cchar, next).width;
-
- if ((x_ofs + im_char_width) > ofs_max) {
- break;
- }
-
- bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y;
- if (selected) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 3)), font_color);
- } else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color);
- }
-
- drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color);
+#ifdef TOOLS_ENABLED
+ int caret_width = Math::round(EDSCALE);
+#else
+ int caret_width = 1;
+#endif
- x_ofs += im_char_width;
- ofs++;
+ // Draw selections rects.
+ Vector2 ofs = Point2(x_ofs + scroll_offset, y_ofs);
+ if (selection.enabled) {
+ Vector<Vector2> 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 Vector<TextServer::Glyph> glyphs = TS->shaped_text_get_glyphs(text_rid);
+
+ // Draw text.
+ ofs.y += TS->shaped_text_get_ascent(text_rid);
+ for (int i = 0; i < glyphs.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 && floor(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_color_selected : 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_color_selected : font_color);
}
}
+ ofs.x += glyphs[i].advance;
}
-
- char32_t cchar = (pass && !text.empty()) ? secret_character[0] : t[char_ofs];
- char32_t next = (pass && !text.empty()) ? secret_character[0] : t[char_ofs + 1];
- int char_width = font->get_char_size(cchar, next).width;
-
- // End of widget, break.
- if ((x_ofs + char_width) > ofs_max) {
+ if (ofs.x >= ofs_max) {
break;
}
-
- bool selected = selection.enabled && char_ofs >= selection.begin && char_ofs < selection.end;
-
- if (selected) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs), Size2(char_width, caret_height)), selection_color);
- }
-
- int yofs = y_ofs + (caret_height - font->get_height()) / 2;
- drawer.draw_char(ci, Point2(x_ofs, yofs + font_ascent), cchar, next, selected ? font_color_selected : font_color);
-
- if (char_ofs == cursor_pos && draw_caret && !using_placeholder) {
- if (ime_text.length() == 0) {
-#ifdef TOOLS_ENABLED
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs), Size2(Math::round(EDSCALE), caret_height)), cursor_color);
-#else
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs), Size2(1, caret_height)), cursor_color);
-#endif
- }
- }
-
- x_ofs += char_width;
- char_ofs++;
}
- if (char_ofs == cursor_pos) {
- if (ime_text.length() > 0) {
- int ofs = 0;
- while (true) {
- if (ofs >= ime_text.length()) {
- break;
+ // Draw carets.
+ ofs.x = x_ofs + scroll_offset;
+ if (draw_caret) {
+ if (ime_text.length() == 0) {
+ // Normal caret.
+ Rect2 l_caret, t_caret;
+ TextServer::Direction l_dir, t_dir;
+ TS->shaped_text_get_carets(text_rid, cursor_pos, l_caret, l_dir, t_caret, t_dir);
+
+ if (l_caret == Rect2() && t_caret == Rect2()) {
+ // No carets, add one at the start.
+ int h = get_theme_font("font")->get_height(get_theme_font_size("font_size"));
+ int y = style->get_offset().y + (y_area - h) / 2;
+ if (rtl) {
+ l_dir = TextServer::DIRECTION_RTL;
+ l_caret = Rect2(Vector2(ofs_max, y), Size2(caret_width, h));
+ } else {
+ l_dir = TextServer::DIRECTION_LTR;
+ l_caret = Rect2(Vector2(x_ofs, y), Size2(caret_width, h));
}
-
- char32_t cchar = (pass && !text.empty()) ? secret_character[0] : ime_text[ofs];
- char32_t next = (pass && !text.empty()) ? secret_character[0] : ime_text[ofs + 1];
- int im_char_width = font->get_char_size(cchar, next).width;
-
- if ((x_ofs + im_char_width) > ofs_max) {
- break;
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, cursor_color);
+ } else {
+ if (l_caret != Rect2() && l_dir == TextServer::DIRECTION_AUTO) {
+ // Draw extra marker on top of mid caret.
+ Rect2 trect = Rect2(l_caret.position.x - 3 * caret_width, l_caret.position.y, 6 * caret_width, caret_width);
+ trect.position += ofs;
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, cursor_color);
}
- bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y;
- if (selected) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 3)), font_color);
- } else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color);
- }
+ l_caret.position += ofs;
+ l_caret.size.x = caret_width;
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, cursor_color);
- drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color);
+ t_caret.position += ofs;
+ t_caret.size.x = caret_width;
- x_ofs += im_char_width;
- ofs++;
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, t_caret, cursor_color);
}
- }
- }
-
- if ((char_ofs == cursor_pos || using_placeholder) && draw_caret) { // May be at the end, or placeholder.
- if (ime_text.length() == 0) {
- int caret_x_ofs = x_ofs;
- if (using_placeholder) {
- switch (align) {
- case ALIGN_LEFT:
- case ALIGN_FILL: {
- caret_x_ofs = style->get_offset().x;
- } break;
- case ALIGN_CENTER: {
- caret_x_ofs = ofs_max / 2;
- } break;
- case ALIGN_RIGHT: {
- caret_x_ofs = ofs_max;
- } break;
+ } else {
+ {
+ // IME intermidiet text range.
+ Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, cursor_pos, cursor_pos + 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, cursor_color);
+ }
+ }
+ {
+ // IME caret.
+ Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, cursor_pos + ime_selection.x, cursor_pos + 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, cursor_color);
}
}
-#ifdef TOOLS_ENABLED
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(caret_x_ofs, y_ofs), Size2(Math::round(EDSCALE), caret_height)), cursor_color);
-#else
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(caret_x_ofs, y_ofs), Size2(1, caret_height)), cursor_color);
-#endif
}
}
if (has_focus()) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
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 + caret_height), 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;
@@ -962,6 +976,8 @@ void LineEdit::_notification(int p_what) {
}
ime_text = "";
ime_selection = Point2();
+ _shape();
+ set_cursor_position(cursor_pos); // Update scroll_offset
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
@@ -972,6 +988,9 @@ void LineEdit::_notification(int p_what) {
if (has_focus()) {
ime_text = DisplayServer::get_singleton()->ime_get_text();
ime_selection = DisplayServer::get_singleton()->ime_get_selection();
+ _shape();
+ set_cursor_position(cursor_pos); // Update scroll_offset
+
update();
}
} break;
@@ -1023,14 +1042,10 @@ void LineEdit::undo() {
undo_stack_pos = undo_stack_pos->prev();
TextOperation op = undo_stack_pos->get();
text = op.text;
- cached_width = op.cached_width;
scroll_offset = op.scroll_offset;
set_cursor_position(op.cursor_pos);
- if (expand_to_text_length) {
- minimum_size_changed();
- }
-
+ _shape();
_emit_text_change();
}
@@ -1044,14 +1059,10 @@ void LineEdit::redo() {
undo_stack_pos = undo_stack_pos->next();
TextOperation op = undo_stack_pos->get();
text = op.text;
- cached_width = op.cached_width;
scroll_offset = op.scroll_offset;
set_cursor_position(op.cursor_pos);
- if (expand_to_text_length) {
- minimum_size_changed();
- }
-
+ _shape();
_emit_text_change();
}
@@ -1071,98 +1082,138 @@ void LineEdit::shift_selection_check_post(bool p_shift) {
}
void LineEdit::set_cursor_at_pixel_pos(int p_x) {
- Ref<Font> font = get_theme_font("font");
- int ofs = scroll_offset;
Ref<StyleBox> style = get_theme_stylebox("normal");
- int pixel_ofs = 0;
- Size2 size = get_size();
- bool display_clear_icon = !text.empty() && is_editable() && clear_button_enabled;
- int r_icon_width = Control::get_theme_icon("clear")->get_width();
+ bool rtl = is_layout_rtl();
+ int x_ofs = 0;
+ float text_width = TS->shaped_text_get_size(text_rid).x;
switch (align) {
case ALIGN_FILL:
case ALIGN_LEFT: {
- pixel_ofs = int(style->get_offset().x);
+ if (rtl) {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - style->get_margin(MARGIN_RIGHT) - (text_width)));
+ } else {
+ x_ofs = style->get_offset().x;
+ }
} break;
case ALIGN_CENTER: {
if (scroll_offset != 0) {
- pixel_ofs = int(style->get_offset().x);
+ x_ofs = style->get_offset().x;
} else {
- pixel_ofs = int(size.width - (cached_width)) / 2;
- }
-
- if (display_clear_icon) {
- pixel_ofs -= int(r_icon_width / 2 + style->get_margin(MARGIN_RIGHT));
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - (text_width)) / 2);
}
} break;
case ALIGN_RIGHT: {
- pixel_ofs = int(size.width - style->get_margin(MARGIN_RIGHT) - (cached_width));
-
- if (display_clear_icon) {
- pixel_ofs -= int(r_icon_width + style->get_margin(MARGIN_RIGHT));
+ if (rtl) {
+ x_ofs = style->get_offset().x;
+ } else {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - style->get_margin(MARGIN_RIGHT) - (text_width)));
}
} break;
}
- while (ofs < text.length()) {
- int char_w = 0;
- if (font != nullptr) {
- char_w = font->get_char_size(pass ? secret_character[0] : text[ofs]).width;
- }
- pixel_ofs += char_w;
-
- if (pixel_ofs > p_x) { // Found what we look for.
- break;
+ bool using_placeholder = text.empty() && ime_text.empty();
+ bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled;
+ if (right_icon.is_valid() || display_clear_icon) {
+ Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon("clear") : right_icon;
+ if (align == ALIGN_CENTER) {
+ if (scroll_offset == 0) {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(MARGIN_RIGHT) * 2) / 2);
+ }
+ } else {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), x_ofs - r_icon->get_width() - style->get_margin(MARGIN_RIGHT));
}
-
- ofs++;
}
+ int ofs = TS->shaped_text_hit_test_position(text_rid, p_x - x_ofs - scroll_offset);
set_cursor_position(ofs);
}
-int LineEdit::get_cursor_pixel_pos() {
- Ref<Font> font = get_theme_font("font");
- int ofs = scroll_offset;
+Vector2i LineEdit::get_cursor_pixel_pos() {
Ref<StyleBox> style = get_theme_stylebox("normal");
- int pixel_ofs = 0;
- Size2 size = get_size();
- bool display_clear_icon = !text.empty() && is_editable() && clear_button_enabled;
- int r_icon_width = Control::get_theme_icon("clear")->get_width();
+ bool rtl = is_layout_rtl();
+ int x_ofs = 0;
+ float text_width = TS->shaped_text_get_size(text_rid).x;
switch (align) {
case ALIGN_FILL:
case ALIGN_LEFT: {
- pixel_ofs = int(style->get_offset().x);
+ if (rtl) {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - style->get_margin(MARGIN_RIGHT) - (text_width)));
+ } else {
+ x_ofs = style->get_offset().x;
+ }
} break;
case ALIGN_CENTER: {
if (scroll_offset != 0) {
- pixel_ofs = int(style->get_offset().x);
+ x_ofs = style->get_offset().x;
} else {
- pixel_ofs = int(size.width - (cached_width)) / 2;
- }
-
- if (display_clear_icon) {
- pixel_ofs -= int(r_icon_width / 2 + style->get_margin(MARGIN_RIGHT));
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - (text_width)) / 2);
}
} break;
case ALIGN_RIGHT: {
- pixel_ofs = int(size.width - style->get_margin(MARGIN_RIGHT) - (cached_width));
-
- if (display_clear_icon) {
- pixel_ofs -= int(r_icon_width + style->get_margin(MARGIN_RIGHT));
+ if (rtl) {
+ x_ofs = style->get_offset().x;
+ } else {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - style->get_margin(MARGIN_RIGHT) - (text_width)));
}
} break;
}
- while (ofs < cursor_pos) {
- if (font != nullptr) {
- pixel_ofs += font->get_char_size(pass ? secret_character[0] : text[ofs]).width;
+ bool using_placeholder = text.empty() && ime_text.empty();
+ bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled;
+ if (right_icon.is_valid() || display_clear_icon) {
+ Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon("clear") : right_icon;
+ if (align == ALIGN_CENTER) {
+ if (scroll_offset == 0) {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(MARGIN_RIGHT) * 2) / 2);
+ }
+ } else {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), x_ofs - r_icon->get_width() - style->get_margin(MARGIN_RIGHT));
+ }
+ }
+
+ Vector2i ret;
+ Rect2 l_caret, t_caret;
+ TextServer::Direction l_dir, t_dir;
+ // Get position of the start of caret.
+ if (ime_text.length() != 0 && ime_selection.x != 0) {
+ TS->shaped_text_get_carets(text_rid, cursor_pos + ime_selection.x, l_caret, l_dir, t_caret, t_dir);
+ } else {
+ TS->shaped_text_get_carets(text_rid, cursor_pos, l_caret, l_dir, t_caret, t_dir);
+ }
+
+ if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) {
+ ret.x = x_ofs + l_caret.position.x + scroll_offset;
+ } else {
+ ret.x = x_ofs + t_caret.position.x + scroll_offset;
+ }
+
+ // Get position of the end of caret.
+ if (ime_text.length() != 0) {
+ if (ime_selection.y != 0) {
+ TS->shaped_text_get_carets(text_rid, cursor_pos + ime_selection.x + ime_selection.y, l_caret, l_dir, t_caret, t_dir);
+ } else {
+ TS->shaped_text_get_carets(text_rid, cursor_pos + ime_text.size(), l_caret, l_dir, t_caret, t_dir);
}
- ofs++;
+ if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) {
+ ret.y = x_ofs + l_caret.position.x + scroll_offset;
+ } else {
+ ret.y = x_ofs + t_caret.position.x + scroll_offset;
+ }
+ } else {
+ ret.y = ret.x;
}
- return pixel_ofs;
+ return ret;
+}
+
+void LineEdit::set_mid_grapheme_caret_enabled(const bool p_enabled) {
+ mid_grapheme_caret_enabled = p_enabled;
+}
+
+bool LineEdit::get_mid_grapheme_caret_enabled() const {
+ return mid_grapheme_caret_enabled;
}
bool LineEdit::cursor_get_blink_enabled() const {
@@ -1227,49 +1278,26 @@ void LineEdit::delete_char() {
return;
}
- Ref<Font> font = get_theme_font("font");
- if (font != nullptr) {
- cached_width -= font->get_char_size(pass ? secret_character[0] : text[cursor_pos - 1]).width;
- }
-
text.erase(cursor_pos - 1, 1);
+ _shape();
set_cursor_position(get_cursor_position() - 1);
- if (align == ALIGN_CENTER || align == ALIGN_RIGHT) {
- scroll_offset = CLAMP(scroll_offset - 1, 0, MAX(text.length() - 1, 0));
- }
-
_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()));
- if (text.size() > 0) {
- Ref<Font> font = get_theme_font("font");
- if (font != nullptr) {
- for (int i = p_from_column; i < p_to_column; i++) {
- cached_width -= font->get_char_size(pass ? secret_character[0] : text[i]).width;
- }
- }
- } else {
- cached_width = 0;
- }
text.erase(p_from_column, p_to_column - p_from_column);
+ _shape();
+
cursor_pos -= CLAMP(cursor_pos - p_from_column, 0, p_to_column - p_from_column);
if (cursor_pos >= text.length()) {
cursor_pos = text.length();
}
- if (scroll_offset > cursor_pos) {
- scroll_offset = cursor_pos;
- }
-
- if (align == ALIGN_CENTER || align == ALIGN_RIGHT) {
- scroll_offset = CLAMP(scroll_offset - (p_to_column - p_from_column), 0, MAX(text.length() - 1, 0));
- }
if (!text_changed_dirty) {
if (is_inside_tree()) {
@@ -1283,15 +1311,102 @@ void LineEdit::set_text(String p_text) {
clear_internal();
append_at_cursor(p_text);
- if (expand_to_text_length) {
- minimum_size_changed();
- }
-
update();
cursor_pos = 0;
scroll_offset = 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();
+
+ 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);
+ update();
+ }
+}
+
+Control::TextDirection LineEdit::get_text_direction() const {
+ return text_direction;
+}
+
+void LineEdit::clear_opentype_features() {
+ opentype_features.clear();
+ _shape();
+ update();
+}
+
+void LineEdit::set_opentype_feature(const String &p_name, int p_value) {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) {
+ opentype_features[tag] = p_value;
+ _shape();
+ update();
+ }
+}
+
+int LineEdit::get_opentype_feature(const String &p_name) const {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag)) {
+ return -1;
+ }
+ return opentype_features[tag];
+}
+
+void LineEdit::set_language(const String &p_language) {
+ if (language != p_language) {
+ language = p_language;
+ _shape();
+ update();
+ }
+}
+
+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;
+ menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars);
+ _shape();
+ update();
+ }
+}
+
+bool LineEdit::get_draw_control_chars() const {
+ return draw_control_chars;
+}
+
+void LineEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+ if (st_parser != p_parser) {
+ st_parser = p_parser;
+ _shape();
+ update();
+ }
+}
+
+Control::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();
+ update();
+}
+
+Array LineEdit::get_structured_text_bidi_override_options() const {
+ return st_args;
+}
+
void LineEdit::clear() {
clear_internal();
_text_changed();
@@ -1304,7 +1419,7 @@ String LineEdit::get_text() const {
void LineEdit::set_placeholder(String p_text) {
placeholder = p_text;
placeholder_translated = tr(placeholder);
- update_placeholder_width();
+ _shape();
update();
}
@@ -1332,57 +1447,68 @@ void LineEdit::set_cursor_position(int p_pos) {
cursor_pos = p_pos;
+ // Fit to window.
+
if (!is_inside_tree()) {
- scroll_offset = cursor_pos;
+ scroll_offset = 0;
return;
}
Ref<StyleBox> style = get_theme_stylebox("normal");
- Ref<Font> font = get_theme_font("font");
+ bool rtl = is_layout_rtl();
- if (cursor_pos <= scroll_offset) {
- // Adjust window if cursor goes too much to the left.
- set_scroll_offset(MAX(0, cursor_pos - 1));
- } else {
- // Adjust window if cursor goes too much to the right.
- int window_width = get_size().width - style->get_minimum_size().width;
- bool display_clear_icon = !text.empty() && is_editable() && clear_button_enabled;
- if (right_icon.is_valid() || display_clear_icon) {
- Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon("clear") : right_icon;
- window_width -= r_icon->get_width();
- }
-
- if (window_width < 0) {
- return;
- }
- int wp = scroll_offset;
-
- if (font.is_valid()) {
- int accum_width = 0;
-
- for (int i = cursor_pos; i >= scroll_offset; i--) {
- if (i >= text.length()) {
- // Do not do this, because if the cursor is at the end, its just fine that it takes no space.
- // accum_width = font->get_char_size(' ').width;
- } else {
- if (pass) {
- accum_width += font->get_char_size(secret_character[0], i + 1 < text.length() ? secret_character[0] : 0).width;
- } else {
- accum_width += font->get_char_size(text[i], i + 1 < text.length() ? text[i + 1] : 0).width; // Anything should do.
- }
- }
- if (accum_width > window_width) {
- break;
- }
+ int x_ofs = 0;
+ float text_width = TS->shaped_text_get_size(text_rid).x;
+ switch (align) {
+ case ALIGN_FILL:
+ case ALIGN_LEFT: {
+ if (rtl) {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - style->get_margin(MARGIN_RIGHT) - (text_width)));
+ } else {
+ x_ofs = style->get_offset().x;
+ }
+ } break;
+ case ALIGN_CENTER: {
+ if (scroll_offset != 0) {
+ x_ofs = style->get_offset().x;
+ } else {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - (text_width)) / 2);
+ }
+ } break;
+ case ALIGN_RIGHT: {
+ if (rtl) {
+ x_ofs = style->get_offset().x;
+ } else {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - style->get_margin(MARGIN_RIGHT) - (text_width)));
+ }
+ } break;
+ }
- wp = i;
+ int ofs_max = get_size().width - style->get_margin(MARGIN_RIGHT);
+ bool using_placeholder = text.empty() && ime_text.empty();
+ bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled;
+ if (right_icon.is_valid() || display_clear_icon) {
+ Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon("clear") : right_icon;
+ if (align == ALIGN_CENTER) {
+ if (scroll_offset == 0) {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(MARGIN_RIGHT) * 2) / 2);
}
+ } else {
+ x_ofs = MAX(style->get_margin(MARGIN_LEFT), x_ofs - r_icon->get_width() - style->get_margin(MARGIN_RIGHT));
}
+ ofs_max -= r_icon->get_width();
+ }
- if (wp != scroll_offset) {
- set_scroll_offset(wp);
- }
+ // Note: Use too coordinates to fit IME input range.
+ Vector2i primary_catret_offset = get_cursor_pixel_pos();
+
+ if (MIN(primary_catret_offset.x, primary_catret_offset.y) <= x_ofs) {
+ scroll_offset += (x_ofs - MIN(primary_catret_offset.x, primary_catret_offset.y));
+ } else if (MAX(primary_catret_offset.x, primary_catret_offset.y) >= ofs_max) {
+ scroll_offset += (ofs_max - MAX(primary_catret_offset.x, primary_catret_offset.y));
}
+ scroll_offset = MIN(0, scroll_offset);
+
update();
}
@@ -1406,7 +1532,11 @@ void LineEdit::append_at_cursor(String p_text) {
String pre = text.substr(0, cursor_pos);
String post = text.substr(cursor_pos, text.length() - cursor_pos);
text = pre + p_text + post;
- update_cached_width();
+ _shape();
+ TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text_rid, cursor_pos, cursor_pos + p_text.length());
+ if (dir != TextServer::DIRECTION_AUTO) {
+ input_direction = (TextDirection)dir;
+ }
set_cursor_position(cursor_pos + p_text.length());
} else {
emit_signal("text_change_rejected");
@@ -1416,39 +1546,39 @@ void LineEdit::append_at_cursor(String p_text) {
void LineEdit::clear_internal() {
deselect();
_clear_undo_stack();
- cached_width = 0;
cursor_pos = 0;
scroll_offset = 0;
undo_text = "";
text = "";
+ _shape();
update();
}
Size2 LineEdit::get_minimum_size() const {
Ref<StyleBox> style = get_theme_stylebox("normal");
Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
Size2 min_size;
// Minimum size of text.
- int space_size = font->get_char_size(' ').x;
+ int space_size = font->get_char_size('m', 0, font_size).x;
min_size.width = get_theme_constant("minimum_spaces") * space_size;
if (expand_to_text_length) {
// Add a space because some fonts are too exact, and because cursor needs a bit more when at the end.
- min_size.width = MAX(min_size.width, font->get_string_size(text).x + space_size);
+ min_size.width = MAX(min_size.width, full_width + space_size);
}
- min_size.height = font->get_height();
+ min_size.height = MAX(TS->shaped_text_get_size(text_rid).y, font->get_height(font_size));
// Take icons into account.
- if (!text.empty() && is_editable() && clear_button_enabled) {
- min_size.width = MAX(min_size.width, Control::get_theme_icon("clear")->get_width());
- min_size.height = MAX(min_size.height, Control::get_theme_icon("clear")->get_height());
- }
- if (right_icon.is_valid()) {
- min_size.width = MAX(min_size.width, right_icon->get_width());
- min_size.height = MAX(min_size.height, right_icon->get_height());
+ bool using_placeholder = text.empty() && ime_text.empty();
+ bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled;
+ if (right_icon.is_valid() || display_clear_icon) {
+ Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon("clear") : right_icon;
+ min_size.width += r_icon->get_width();
+ min_size.height = MAX(min_size.height, r_icon->get_height());
}
return style->get_minimum_size() + min_size;
@@ -1531,8 +1661,10 @@ bool LineEdit::is_editable() const {
}
void LineEdit::set_secret(bool p_secret) {
- pass = p_secret;
- update_cached_width();
+ if (pass != p_secret) {
+ pass = p_secret;
+ _shape();
+ }
update();
}
@@ -1545,8 +1677,10 @@ void LineEdit::set_secret_character(const String &p_string) {
// 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).");
- secret_character = p_string;
- update_cached_width();
+ if (secret_character != p_string) {
+ secret_character = p_string;
+ _shape();
+ }
update();
}
@@ -1623,6 +1757,101 @@ void LineEdit::menu_option(int p_option) {
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) {
+ append_at_cursor(String::chr(0x200E));
+ }
+ } break;
+ case MENU_INSERT_RLM: {
+ if (editable) {
+ append_at_cursor(String::chr(0x200F));
+ }
+ } break;
+ case MENU_INSERT_LRE: {
+ if (editable) {
+ append_at_cursor(String::chr(0x202A));
+ }
+ } break;
+ case MENU_INSERT_RLE: {
+ if (editable) {
+ append_at_cursor(String::chr(0x202B));
+ }
+ } break;
+ case MENU_INSERT_LRO: {
+ if (editable) {
+ append_at_cursor(String::chr(0x202D));
+ }
+ } break;
+ case MENU_INSERT_RLO: {
+ if (editable) {
+ append_at_cursor(String::chr(0x202E));
+ }
+ } break;
+ case MENU_INSERT_PDF: {
+ if (editable) {
+ append_at_cursor(String::chr(0x202C));
+ }
+ } break;
+ case MENU_INSERT_ALM: {
+ if (editable) {
+ append_at_cursor(String::chr(0x061C));
+ }
+ } break;
+ case MENU_INSERT_LRI: {
+ if (editable) {
+ append_at_cursor(String::chr(0x2066));
+ }
+ } break;
+ case MENU_INSERT_RLI: {
+ if (editable) {
+ append_at_cursor(String::chr(0x2067));
+ }
+ } break;
+ case MENU_INSERT_FSI: {
+ if (editable) {
+ append_at_cursor(String::chr(0x2068));
+ }
+ } break;
+ case MENU_INSERT_PDI: {
+ if (editable) {
+ append_at_cursor(String::chr(0x2069));
+ }
+ } break;
+ case MENU_INSERT_ZWJ: {
+ if (editable) {
+ append_at_cursor(String::chr(0x200D));
+ }
+ } break;
+ case MENU_INSERT_ZWNJ: {
+ if (editable) {
+ append_at_cursor(String::chr(0x200C));
+ }
+ } break;
+ case MENU_INSERT_WJ: {
+ if (editable) {
+ append_at_cursor(String::chr(0x2060));
+ }
+ } break;
+ case MENU_INSERT_SHY: {
+ if (editable) {
+ append_at_cursor(String::chr(0x00AD));
+ }
}
}
}
@@ -1649,7 +1878,7 @@ void LineEdit::_editor_settings_changed() {
void LineEdit::set_expand_to_text_length(bool p_enabled) {
expand_to_text_length = p_enabled;
minimum_size_changed();
- set_scroll_offset(0);
+ set_cursor_position(cursor_pos);
}
bool LineEdit::get_expand_to_text_length() const {
@@ -1661,6 +1890,7 @@ void LineEdit::set_clear_button_enabled(bool p_enabled) {
return;
}
clear_button_enabled = p_enabled;
+ _fit_to_width();
minimum_size_changed();
update();
}
@@ -1706,6 +1936,7 @@ void LineEdit::set_right_icon(const Ref<Texture2D> &p_icon) {
return;
}
right_icon = p_icon;
+ _fit_to_width();
minimum_size_changed();
update();
}
@@ -1715,10 +1946,6 @@ Ref<Texture2D> LineEdit::get_right_icon() {
}
void LineEdit::_text_changed() {
- if (expand_to_text_length) {
- minimum_size_changed();
- }
-
_emit_text_change();
_clear_redo();
}
@@ -1729,24 +1956,55 @@ void LineEdit::_emit_text_change() {
text_changed_dirty = false;
}
-void LineEdit::update_cached_width() {
- Ref<Font> font = get_theme_font("font");
- cached_width = 0;
- if (font != nullptr) {
- String text = get_text();
- for (int i = 0; i < text.length(); i++) {
- cached_width += font->get_char_size(pass ? secret_character[0] : text[i]).width;
+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) {
+ 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, cursor_pos) + ime_text + text.substr(cursor_pos, 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> &font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
+ TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
+ 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)) {
+ minimum_size_changed();
+ }
}
-void LineEdit::update_placeholder_width() {
- Ref<Font> font = get_theme_font("font");
- cached_placeholder_width = 0;
- if (font != nullptr) {
- for (int i = 0; i < placeholder_translated.length(); i++) {
- cached_placeholder_width += font->get_char_size(placeholder_translated[i]).width;
+void LineEdit::_fit_to_width() {
+ if (align == ALIGN_FILL) {
+ Ref<StyleBox> style = get_theme_stylebox("normal");
+ int t_width = get_size().width - style->get_margin(MARGIN_RIGHT) - style->get_margin(MARGIN_LEFT);
+ bool using_placeholder = text.empty() && ime_text.empty();
+ bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled;
+ if (right_icon.is_valid() || display_clear_icon) {
+ Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon("clear") : right_icon;
+ t_width -= r_icon->get_width();
}
+ TS->shaped_text_fit_to_width(text_rid, MAX(t_width, full_width));
}
}
@@ -1774,7 +2032,6 @@ void LineEdit::_clear_undo_stack() {
void LineEdit::_create_undo_state() {
TextOperation op;
op.text = text;
- op.cached_width = cached_width;
op.cursor_pos = cursor_pos;
op.scroll_offset = scroll_offset;
undo_stack.push_back(op);
@@ -1800,6 +2057,63 @@ void LineEdit::_generate_context_menu() {
menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? KEY_MASK_CMD | KEY_Z : 0);
menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z : 0);
}
+ 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);
+ if (editable) {
+ menu->add_submenu_item(RTR("Insert control character"), "CTLMenu");
+ }
+}
+
+bool LineEdit::_set(const StringName &p_name, const Variant &p_value) {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ double value = p_value;
+ if (value == -1) {
+ if (opentype_features.has(tag)) {
+ opentype_features.erase(tag);
+ _shape();
+ update();
+ }
+ } else {
+ if ((double)opentype_features[tag] != value) {
+ opentype_features[tag] = value;
+ _shape();
+ update();
+ }
+ }
+ _change_notify();
+ return true;
+ }
+
+ return false;
+}
+
+bool LineEdit::_get(const StringName &p_name, Variant &r_ret) const {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ if (opentype_features.has(tag)) {
+ r_ret = opentype_features[tag];
+ return true;
+ } else {
+ r_ret = -1;
+ return true;
+ }
+ }
+ return false;
+}
+
+void LineEdit::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) {
+ String name = TS->tag_to_name(*ftr);
+ p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name));
+ }
+ p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
}
void LineEdit::_bind_methods() {
@@ -1815,6 +2129,19 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("deselect"), &LineEdit::deselect);
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_opentype_feature", "tag", "value"), &LineEdit::set_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &LineEdit::get_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_opentype_features"), &LineEdit::clear_opentype_features);
+ 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_placeholder_alpha", "alpha"), &LineEdit::set_placeholder_alpha);
@@ -1826,6 +2153,8 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_expand_to_text_length"), &LineEdit::get_expand_to_text_length);
ClassDB::bind_method(D_METHOD("cursor_set_blink_enabled", "enabled"), &LineEdit::cursor_set_blink_enabled);
ClassDB::bind_method(D_METHOD("cursor_get_blink_enabled"), &LineEdit::cursor_get_blink_enabled);
+ ClassDB::bind_method(D_METHOD("set_mid_grapheme_caret_enabled", "enabled"), &LineEdit::set_mid_grapheme_caret_enabled);
+ ClassDB::bind_method(D_METHOD("get_mid_grapheme_caret_enabled"), &LineEdit::get_mid_grapheme_caret_enabled);
ClassDB::bind_method(D_METHOD("cursor_set_force_displayed", "enabled"), &LineEdit::cursor_set_force_displayed);
ClassDB::bind_method(D_METHOD("cursor_get_force_displayed"), &LineEdit::cursor_get_force_displayed);
ClassDB::bind_method(D_METHOD("cursor_set_blink_speed", "blink_speed"), &LineEdit::cursor_set_blink_speed);
@@ -1872,6 +2201,27 @@ void LineEdit::_bind_methods() {
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);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text");
@@ -1887,6 +2237,12 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
+ ADD_GROUP("Structured Text", "structured_text_");
+ 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");
ADD_GROUP("Placeholder", "placeholder_");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "placeholder_alpha", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_placeholder_alpha", "get_placeholder_alpha");
@@ -1895,50 +2251,67 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "cursor_set_blink_speed", "cursor_get_blink_speed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_position"), "set_cursor_position", "get_cursor_position");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "cursor_set_force_displayed", "cursor_get_force_displayed");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_mid_grapheme_caret_enabled", "get_mid_grapheme_caret_enabled");
}
LineEdit::LineEdit() {
- undo_stack_pos = nullptr;
+ text_rid = TS->create_shaped_text();
_create_undo_state();
- align = ALIGN_LEFT;
- cached_width = 0;
- cached_placeholder_width = 0;
- cursor_pos = 0;
- scroll_offset = 0;
- window_has_focus = true;
- max_length = 0;
- pass = false;
- secret_character = "*";
- text_changed_dirty = false;
- placeholder_alpha = 0.6;
- clear_button_enabled = false;
+
clear_button_status.press_attempt = false;
clear_button_status.pressing_inside = false;
- shortcut_keys_enabled = true;
- selecting_enabled = true;
deselect();
set_focus_mode(FOCUS_ALL);
set_default_cursor_shape(CURSOR_IBEAM);
set_mouse_filter(MOUSE_FILTER_STOP);
- draw_caret = true;
- caret_blink_enabled = false;
- caret_force_displayed = false;
caret_blink_timer = memnew(Timer);
add_child(caret_blink_timer);
caret_blink_timer->set_wait_time(0.65);
caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret));
cursor_set_blink_enabled(false);
- context_menu_enabled = true;
menu = memnew(PopupMenu);
add_child(menu);
- editable = false; // Initialise to opposite first, so we get past the early-out in set_editable.
- set_editable(true);
+
+ 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_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), true);
+ menu->add_child(menu_dir);
+
+ 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);
+
+ set_editable(true); // Initialise to opposite first, so we get past the early-out in set_editable.
menu->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
- expand_to_text_length = false;
+ menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
+ menu_ctl->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
}
LineEdit::~LineEdit() {
+ TS->free(text_rid);
}