summaryrefslogtreecommitdiff
path: root/scene/gui/text_edit.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui/text_edit.cpp')
-rw-r--r--scene/gui/text_edit.cpp161
1 files changed, 119 insertions, 42 deletions
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 5295acce8f..86969e3ef4 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -432,32 +432,38 @@ void TextEdit::_notification(int p_what) {
}
_update_wrap_at_column(true);
} break;
+
case NOTIFICATION_RESIZED: {
_update_scrollbars();
_update_wrap_at_column();
} break;
+
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_visible()) {
call_deferred(SNAME("_update_scrollbars"));
call_deferred(SNAME("_update_wrap_at_column"));
}
} break;
+
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
_update_caches();
_update_wrap_at_column(true);
} break;
+
case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
window_has_focus = true;
draw_caret = true;
update();
} break;
+
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
window_has_focus = false;
draw_caret = false;
update();
} break;
+
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (scrolling && get_v_scroll() != target_v_scroll) {
double target_y = target_v_scroll - get_v_scroll();
@@ -465,6 +471,11 @@ void TextEdit::_notification(int p_what) {
// To ensure minimap is responsive override the speed setting.
double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time();
+ // Prevent too small velocity to block scrolling
+ if (Math::abs(vel) < v_scroll->get_step()) {
+ vel = v_scroll->get_step() * SIGN(vel);
+ }
+
if (Math::abs(vel) >= dist) {
set_v_scroll(target_v_scroll);
scrolling = false;
@@ -479,6 +490,7 @@ void TextEdit::_notification(int p_what) {
set_physics_process_internal(false);
}
} break;
+
case NOTIFICATION_DRAW: {
if (first_draw) {
// Size may not be the final one, so attempts to ensure caret was visible may have failed.
@@ -961,7 +973,7 @@ void TextEdit::_notification(int p_what) {
// Give visual indication of empty selected line.
if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) {
- int char_w = font->get_char_size(' ', 0, font_size).width;
+ float char_w = font->get_char_size(' ', 0, font_size).width;
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color);
} else {
@@ -1276,7 +1288,8 @@ void TextEdit::_notification(int p_what) {
}
// Carets.
- const int caret_width = get_theme_constant(SNAME("caret_width")) * get_theme_default_base_scale();
+ // Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1).
+ const int caret_width = get_theme_constant(SNAME("caret_width")) * MAX(1, get_theme_default_base_scale());
if (!clipped && caret.line == line && line_wrap_index == caret_wrap_index) {
caret.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index);
@@ -1427,6 +1440,7 @@ void TextEdit::_notification(int p_what) {
}
}
} break;
+
case NOTIFICATION_FOCUS_ENTER: {
if (caret_blink_enabled) {
caret_blink_timer->start();
@@ -1458,6 +1472,7 @@ void TextEdit::_notification(int p_what) {
DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), true, -1, caret_start, caret_end);
}
} break;
+
case NOTIFICATION_FOCUS_EXIT: {
if (caret_blink_enabled) {
caret_blink_timer->stop();
@@ -1481,6 +1496,7 @@ void TextEdit::_notification(int p_what) {
deselect();
}
} break;
+
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
if (has_focus()) {
ime_text = DisplayServer::get_singleton()->ime_get_text();
@@ -1497,7 +1513,8 @@ void TextEdit::_notification(int p_what) {
update();
}
} break;
- case Control::NOTIFICATION_DRAG_BEGIN: {
+
+ case NOTIFICATION_DRAG_BEGIN: {
selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE;
drag_action = true;
dragging_minimap = false;
@@ -1505,7 +1522,8 @@ void TextEdit::_notification(int p_what) {
can_drag_minimap = false;
click_select_held->stop();
} break;
- case Control::NOTIFICATION_DRAG_END: {
+
+ case NOTIFICATION_DRAG_END: {
if (is_drag_successful()) {
if (selection.drag_attempt) {
selection.drag_attempt = false;
@@ -1524,6 +1542,21 @@ void TextEdit::_notification(int p_what) {
}
}
+void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventKey> 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)) {
+ handle_unicode_input(k->get_unicode());
+ accept_event();
+ }
+ }
+}
+
void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
ERR_FAIL_COND(p_gui_input.is_null());
@@ -2120,16 +2153,21 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
if (p_move_by_word) {
int cc = caret.column;
-
+ // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line.
if (cc == 0 && caret.line > 0) {
set_caret_line(caret.line - 1);
set_caret_column(text[caret.line].length());
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc <= words[0]) {
+ // This solves the scenario where there are no words but glyfs that can be ignored.
+ cc = 0;
+ } else {
+ for (int i = words.size() - 2; i >= 0; i = i - 2) {
+ if (words[i] < cc) {
+ cc = words[i];
+ break;
+ }
}
}
set_caret_column(cc);
@@ -2171,16 +2209,21 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
if (p_move_by_word) {
int cc = caret.column;
-
+ // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line.
if (cc == text[caret.line].length() && caret.line < text.size() - 1) {
set_caret_line(caret.line + 1);
set_caret_column(0);
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- for (int i = 1; i < words.size(); i = i + 2) {
- if (words[i] > cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc >= words[words.size() - 1]) {
+ // This solves the scenario where there are no words but glyfs that can be ignored.
+ cc = text[caret.line].length();
+ } else {
+ for (int i = 1; i < words.size(); i = i + 2) {
+ if (words[i] > cc) {
+ cc = words[i];
+ break;
+ }
}
}
set_caret_column(cc);
@@ -2351,37 +2394,47 @@ void TextEdit::_move_caret_page_down(bool p_select) {
}
void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
- if (!editable) {
+ if (!editable || (caret.column == 0 && caret.line == 0)) {
return;
}
- if (has_selection() || (!p_all_to_left && !p_word)) {
+ if (has_selection() || (!p_all_to_left && !p_word) || caret.column == 0) {
backspace();
return;
}
if (p_all_to_left) {
int caret_current_column = caret.column;
- caret.column = 0;
+ set_caret_column(0);
_remove_text(caret.line, 0, caret.line, caret_current_column);
return;
}
if (p_word) {
- int line = caret.line;
int column = caret.column;
-
- PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < column) {
- column = words[i];
- break;
+ // Check for the case "<word><space><caret>" and ignore the space.
+ // No need to check for column being 0 since it is cheked above.
+ if (is_whitespace(text[caret.line][caret.column - 1])) {
+ column -= 1;
+ }
+ // Get a list with the indices of the word bounds of the given text line.
+ const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
+ if (words.is_empty() || column <= words[0]) {
+ // If "words" is empty, meaning no words are left, we can remove everything until the begining of the line.
+ column = 0;
+ } else {
+ // Otherwise search for the first word break that is smaller than the index from we're currentlu deleteing
+ for (int i = words.size() - 2; i >= 0; i = i - 2) {
+ if (words[i] < column) {
+ column = words[i];
+ break;
+ }
}
}
- _remove_text(line, column, caret.line, caret.column);
+ _remove_text(caret.line, column, caret.line, caret.column);
- set_caret_line(line, false);
+ set_caret_line(caret.line, false);
set_caret_column(column);
return;
}
@@ -2908,15 +2961,20 @@ void TextEdit::_clear() {
end_complex_operation();
return;
}
+ // Cannot merge with above, as we are not part of the tree on creation.
+ int old_text_size = text.size();
+
clear_undo_history();
text.clear();
- caret.column = 0;
- caret.line = 0;
+ set_caret_line(0, false);
+ set_caret_column(0);
caret.x_ofs = 0;
caret.line_ofs = 0;
caret.wrap_ofs = 0;
caret.last_fit_x = 0;
selection.active = false;
+
+ emit_signal(SNAME("lines_edited_from"), old_text_size, 0);
}
void TextEdit::set_text(const String &p_text) {
@@ -2975,14 +3033,16 @@ void TextEdit::set_line(int p_line, const String &p_new_text) {
if (p_line < 0 || p_line >= text.size()) {
return;
}
+ begin_complex_operation();
_remove_text(p_line, 0, p_line, text[p_line].length());
_insert_text(p_line, 0, p_new_text);
- if (caret.line == p_line) {
- caret.column = MIN(caret.column, p_new_text.length());
+ if (caret.line == p_line && caret.column > p_new_text.length()) {
+ set_caret_column(MIN(caret.column, p_new_text.length()), false);
}
if (has_selection() && p_line == selection.to_line && selection.to_column > text[p_line].length()) {
selection.to_column = text[p_line].length();
}
+ end_complex_operation();
}
String TextEdit::get_line(int p_line) const {
@@ -3037,8 +3097,10 @@ void TextEdit::swap_lines(int p_from_line, int p_to_line) {
String tmp = get_line(p_from_line);
String tmp2 = get_line(p_to_line);
+ begin_complex_operation();
set_line(p_to_line, tmp);
set_line(p_from_line, tmp2);
+ end_complex_operation();
}
void TextEdit::insert_line_at(int p_at, const String &p_text) {
@@ -3047,7 +3109,7 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) {
_insert_text(p_at, 0, p_text + "\n");
if (caret.line >= p_at) {
// offset caret when located after inserted line
- ++caret.line;
+ set_caret_line(caret.line + 1, false);
}
if (has_selection()) {
if (selection.from_line >= p_at) {
@@ -3439,9 +3501,6 @@ void TextEdit::undo() {
TextOperation op = undo_stack_pos->get();
_do_text_op(op, true);
- if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) {
- select(op.from_line, op.from_column, op.to_line, op.to_column);
- }
current_op.version = op.prev_version;
if (undo_stack_pos->get().chain_backward) {
@@ -3457,6 +3516,10 @@ void TextEdit::undo() {
}
}
+ if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) {
+ select(op.from_line, op.from_column, op.to_line, op.to_column);
+ }
+
_update_scrollbars();
if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) {
set_caret_line(undo_stack_pos->get().to_line, false);
@@ -3952,6 +4015,7 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
}
}
}
+ bool caret_moved = caret.line != p_line;
caret.line = p_line;
int n_col = _get_char_pos_for_line(caret.last_fit_x, p_line, p_wrap_index);
@@ -3965,15 +4029,16 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
n_col -= 1;
}
}
+ caret_moved = (caret_moved || caret.column != n_col);
caret.column = n_col;
- if (p_adjust_viewport) {
+ if (is_inside_tree() && p_adjust_viewport) {
adjust_viewport_to_caret();
}
setting_caret_line = false;
- if (!caret_pos_dirty) {
+ if (caret_moved && !caret_pos_dirty) {
if (is_inside_tree()) {
MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed");
}
@@ -3990,6 +4055,7 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) {
p_col = 0;
}
+ bool caret_moved = caret.column != p_col;
caret.column = p_col;
if (caret.column > get_line(caret.line).length()) {
caret.column = get_line(caret.line).length();
@@ -3997,11 +4063,11 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) {
caret.last_fit_x = _get_column_x_offset_for_line(caret.column, caret.line);
- if (p_adjust_viewport) {
+ if (is_inside_tree() && p_adjust_viewport) {
adjust_viewport_to_caret();
}
- if (!caret_pos_dirty) {
+ if (caret_moved && !caret_pos_dirty) {
if (is_inside_tree()) {
MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed");
}
@@ -4365,6 +4431,8 @@ int TextEdit::get_h_scroll() const {
}
void TextEdit::set_v_scroll_speed(float p_speed) {
+ // Prevent setting a vertical scroll speed value under 1.0
+ ERR_FAIL_COND(p_speed < 1.0);
v_scroll_speed = p_speed;
}
@@ -4444,7 +4512,11 @@ int TextEdit::get_visible_line_count() const {
int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) const {
ERR_FAIL_INDEX_V(p_from_line, text.size(), 0);
ERR_FAIL_INDEX_V(p_to_line, text.size(), 0);
- ERR_FAIL_COND_V(p_from_line > p_to_line, 0);
+
+ // So we can handle inputs in whatever order
+ if (p_from_line > p_to_line) {
+ SWAP(p_from_line, p_to_line);
+ }
/* Returns the total number of (lines + wrapped - hidden). */
if (!_is_hiding_enabled() && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) {
@@ -5639,8 +5711,10 @@ void TextEdit::_generate_context_menu() {
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()) {
+ if (selecting_enabled || editable) {
+ menu->add_separator();
+ }
+ if (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) {
@@ -6554,13 +6628,14 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li
emit_signal(SNAME("lines_edited_from"), p_to_line, p_from_line);
}
-TextEdit::TextEdit() {
+TextEdit::TextEdit(const String &p_placeholder) {
placeholder_data_buf.instantiate();
clear();
set_focus_mode(FOCUS_ALL);
_update_caches();
set_default_cursor_shape(CURSOR_IBEAM);
+ set_process_unhandled_key_input(true);
text.set_tab_size(text.get_tab_size());
@@ -6596,5 +6671,7 @@ TextEdit::TextEdit() {
undo_stack_max_size = GLOBAL_GET("gui/common/text_edit_undo_stack_max_size");
+ set_placeholder(p_placeholder);
+
set_editable(true);
}