summaryrefslogtreecommitdiffstats
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/aspect_ratio_container.cpp13
-rw-r--r--scene/gui/box_container.cpp30
-rw-r--r--scene/gui/center_container.cpp14
-rw-r--r--scene/gui/code_edit.cpp630
-rw-r--r--scene/gui/code_edit.h8
-rw-r--r--scene/gui/color_picker.cpp30
-rw-r--r--scene/gui/container.cpp8
-rw-r--r--scene/gui/container.h2
-rw-r--r--scene/gui/control.cpp69
-rw-r--r--scene/gui/control.h4
-rw-r--r--scene/gui/flow_container.cpp95
-rw-r--r--scene/gui/flow_container.h11
-rw-r--r--scene/gui/graph_edit.cpp6
-rw-r--r--scene/gui/graph_edit_arranger.cpp2
-rw-r--r--scene/gui/graph_element.cpp13
-rw-r--r--scene/gui/graph_frame.cpp12
-rw-r--r--scene/gui/graph_node.cpp17
-rw-r--r--scene/gui/grid_container.cpp15
-rw-r--r--scene/gui/line_edit.cpp4
-rw-r--r--scene/gui/margin_container.cpp13
-rw-r--r--scene/gui/panel_container.cpp14
-rw-r--r--scene/gui/popup.cpp6
-rw-r--r--scene/gui/popup.h12
-rw-r--r--scene/gui/progress_bar.cpp2
-rw-r--r--scene/gui/rich_text_label.compat.inc5
-rw-r--r--scene/gui/rich_text_label.cpp43
-rw-r--r--scene/gui/rich_text_label.h4
-rw-r--r--scene/gui/scroll_container.cpp19
-rw-r--r--scene/gui/split_container.cpp5
-rw-r--r--scene/gui/tab_container.cpp22
-rw-r--r--scene/gui/text_edit.compat.inc41
-rw-r--r--scene/gui/text_edit.cpp2455
-rw-r--r--scene/gui/text_edit.h98
-rw-r--r--scene/gui/texture_button.cpp2
-rw-r--r--scene/gui/texture_progress_bar.cpp2
-rw-r--r--scene/gui/tree.cpp8
36 files changed, 2098 insertions, 1636 deletions
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp
index 711dd13781..1663a7d602 100644
--- a/scene/gui/aspect_ratio_container.cpp
+++ b/scene/gui/aspect_ratio_container.cpp
@@ -35,16 +35,10 @@
Size2 AspectRatioContainer::get_minimum_size() const {
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
- if (!c->is_visible()) {
- continue;
- }
Size2 minsize = c->get_combined_minimum_size();
ms = ms.max(minsize);
}
@@ -107,13 +101,10 @@ void AspectRatioContainer::_notification(int p_what) {
bool rtl = is_layout_rtl();
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
// Temporary fix for editor crash.
TextureRect *trect = Object::cast_to<TextureRect>(c);
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index 2728126e64..88f65ca1bc 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -55,11 +55,8 @@ void BoxContainer::_resort() {
HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -109,11 +106,8 @@ void BoxContainer::_resort() {
float error = 0.0; // Keep track of accumulated error in pixels
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -201,11 +195,8 @@ void BoxContainer::_resort() {
}
for (int i = start; i != end; i += delta) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -252,17 +243,10 @@ Size2 BoxContainer::get_minimum_size() const {
bool first = true;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
-
- if (!c->is_visible()) {
- continue;
- }
Size2i size = c->get_combined_minimum_size();
diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp
index acdeae289b..1af33d2814 100644
--- a/scene/gui/center_container.cpp
+++ b/scene/gui/center_container.cpp
@@ -36,16 +36,10 @@ Size2 CenterContainer::get_minimum_size() const {
}
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
- if (!c->is_visible()) {
- continue;
- }
Size2 minsize = c->get_combined_minimum_size();
ms = ms.max(minsize);
}
@@ -81,14 +75,10 @@ void CenterContainer::_notification(int p_what) {
case NOTIFICATION_SORT_CHILDREN: {
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
-
Size2 minsize = c->get_combined_minimum_size();
Point2 ofs = use_top_left ? (-minsize * 0.5).floor() : ((size - minsize) / 2.0).floor();
fit_child_in_rect(c, Rect2(ofs, minsize));
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 4f90504e35..8131fe7aaa 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -624,16 +624,31 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
return TextEdit::get_cursor_shape(p_pos);
}
+void CodeEdit::_unhide_carets() {
+ // Unfold caret and selection origin.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (_is_line_hidden(get_caret_line(i))) {
+ unfold_line(get_caret_line(i));
+ }
+ if (has_selection(i) && _is_line_hidden(get_selection_origin_line(i))) {
+ unfold_line(get_selection_origin_line(i));
+ }
+ }
+}
+
/* Text manipulation */
// Overridable actions
void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) {
start_action(EditAction::ACTION_TYPING);
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_caret != -1 && p_caret != i) {
continue;
}
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
bool had_selection = has_selection(i);
String selection_text = (had_selection ? get_selected_text(i) : "");
@@ -691,6 +706,7 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_ca
insert_text_at_caret(chr, i);
}
}
+ end_multicaret_edit();
end_action();
}
@@ -705,66 +721,80 @@ void CodeEdit::_backspace_internal(int p_caret) {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_caret != -1 && p_caret != i) {
continue;
}
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
- int cc = get_caret_column(i);
- int cl = get_caret_line(i);
+ int to_line = get_caret_line(i);
+ int to_column = get_caret_column(i);
- if (cc == 0 && cl == 0) {
+ if (to_column == 0 && to_line == 0) {
continue;
}
- if (cl > 0 && _is_line_hidden(cl - 1)) {
- unfold_line(get_caret_line(i) - 1);
+ if (to_line > 0 && _is_line_hidden(to_line - 1)) {
+ unfold_line(to_line - 1);
}
- int prev_line = cc ? cl : cl - 1;
- int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
+ int from_line = to_column > 0 ? to_line : to_line - 1;
+ int from_column = to_column > 0 ? (to_column - 1) : (get_line(to_line - 1).length());
- merge_gutters(prev_line, cl);
+ merge_gutters(from_line, to_line);
- if (auto_brace_completion_enabled && cc > 0) {
- int idx = _get_auto_brace_pair_open_at_pos(cl, cc);
+ if (auto_brace_completion_enabled && to_column > 0) {
+ int idx = _get_auto_brace_pair_open_at_pos(to_line, to_column);
if (idx != -1) {
- prev_column = cc - auto_brace_completion_pairs[idx].open_key.length();
+ from_column = to_column - auto_brace_completion_pairs[idx].open_key.length();
- if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) {
- cc += auto_brace_completion_pairs[idx].close_key.length();
+ if (_get_auto_brace_pair_close_at_pos(to_line, to_column) == idx) {
+ to_column += auto_brace_completion_pairs[idx].close_key.length();
}
-
- remove_text(prev_line, prev_column, cl, cc);
-
- set_caret_line(prev_line, false, true, 0, i);
- set_caret_column(prev_column, i == 0, i);
-
- adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
- continue;
}
}
// For space indentation we need to do a basic unindent if there are no chars to the left, acting the same way as tabs.
- if (indent_using_spaces && cc != 0) {
- if (get_first_non_whitespace_column(cl) >= cc) {
- prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
- prev_line = cl;
+ if (indent_using_spaces && to_column != 0) {
+ if (get_first_non_whitespace_column(to_line) >= to_column) {
+ from_column = to_column - _calculate_spaces_till_next_left_indent(to_column);
+ from_line = to_line;
}
}
- remove_text(prev_line, prev_column, cl, cc);
-
- set_caret_line(prev_line, false, true, 0, i);
- set_caret_column(prev_column, i == 0, i);
+ remove_text(from_line, from_column, to_line, to_column);
- adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
+ set_caret_line(from_line, false, true, -1, i);
+ set_caret_column(from_column, i == 0, i);
}
- merge_overlapping_carets();
+
+ end_multicaret_edit();
end_complex_operation();
}
+void CodeEdit::_cut_internal(int p_caret) {
+ // Overridden to unfold lines.
+ _copy_internal(p_caret);
+
+ if (!is_editable()) {
+ return;
+ }
+
+ if (has_selection(p_caret)) {
+ delete_selection(p_caret);
+ return;
+ }
+ if (p_caret == -1) {
+ delete_lines();
+ } else {
+ unfold_line(get_caret_line(p_caret));
+ remove_line_at(get_caret_line(p_caret));
+ }
+}
+
/* Indent management */
void CodeEdit::set_indent_size(const int p_size) {
ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0.");
@@ -838,13 +868,17 @@ void CodeEdit::do_indent() {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column(i));
if (spaces_to_add > 0) {
insert_text_at_caret(String(" ").repeat(spaces_to_add), i);
}
}
+ end_multicaret_edit();
end_complex_operation();
}
@@ -854,51 +888,28 @@ void CodeEdit::indent_lines() {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &c : caret_edit_order) {
- // This value informs us by how much we changed selection position by indenting right.
- // Default is 1 for tab indentation.
- int selection_offset = 1;
-
- int start_line = get_caret_line(c);
- int end_line = start_line;
- if (has_selection(c)) {
- start_line = get_selection_from_line(c);
- end_line = get_selection_to_line(c);
+ begin_multicaret_edit();
- // Ignore the last line if the selection is not past the first column.
- if (get_selection_to_column(c) == 0) {
- selection_offset = 0;
- end_line--;
- }
- }
-
- for (int i = start_line; i <= end_line; i++) {
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
+ for (Point2i line_range : line_ranges) {
+ for (int i = line_range.x; i <= line_range.y; i++) {
const String line_text = get_line(i);
- if (line_text.size() == 0 && has_selection(c)) {
+ if (line_text.size() == 0) {
+ // Ignore empty lines.
continue;
}
- if (!indent_using_spaces) {
- set_line(i, '\t' + line_text);
- continue;
+ if (indent_using_spaces) {
+ int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i));
+ insert_text(String(" ").repeat(spaces_to_add), i, 0, false);
+ } else {
+ insert_text("\t", i, 0, false);
}
-
- // We don't really care where selection is - we just need to know indentation level at the beginning of the line.
- // Since we will add this many spaces, we want to move the whole selection and caret by this much.
- int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i));
- set_line(i, String(" ").repeat(spaces_to_add) + line_text);
- selection_offset = spaces_to_add;
}
-
- // Fix selection and caret being off after shifting selection right.
- if (has_selection(c)) {
- select(start_line, get_selection_from_column(c) + selection_offset, get_selection_to_line(c), get_selection_to_column(c) + selection_offset, c);
- }
- set_caret_column(get_caret_column(c) + selection_offset, false, c);
}
+
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
void CodeEdit::unindent_lines() {
@@ -907,76 +918,25 @@ void CodeEdit::unindent_lines() {
}
begin_complex_operation();
+ begin_multicaret_edit();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &c : caret_edit_order) {
- // Moving caret and selection after unindenting can get tricky because
- // changing content of line can move caret and selection on its own (if new line ends before previous position of either)
- // therefore we just remember initial values and at the end of the operation offset them by number of removed characters.
- int removed_characters = 0;
- int initial_selection_end_column = 0;
- int initial_cursor_column = get_caret_column(c);
-
- int start_line = get_caret_line(c);
- int end_line = start_line;
- if (has_selection(c)) {
- start_line = get_selection_from_line(c);
- end_line = get_selection_to_line(c);
-
- // Ignore the last line if the selection is not past the first column.
- initial_selection_end_column = get_selection_to_column(c);
- if (initial_selection_end_column == 0) {
- end_line--;
- }
- }
-
- bool first_line_edited = false;
- bool last_line_edited = false;
-
- for (int i = start_line; i <= end_line; i++) {
- String line_text = get_line(i);
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
+ for (Point2i line_range : line_ranges) {
+ for (int i = line_range.x; i <= line_range.y; i++) {
+ const String line_text = get_line(i);
if (line_text.begins_with("\t")) {
- line_text = line_text.substr(1, line_text.length());
-
- set_line(i, line_text);
- removed_characters = 1;
-
- first_line_edited = (i == start_line) ? true : first_line_edited;
- last_line_edited = (i == end_line) ? true : last_line_edited;
- continue;
- }
-
- if (line_text.begins_with(" ")) {
- // When unindenting we aim to remove spaces before line that has selection no matter what is selected.
- // Here we remove only enough spaces to align text to nearest full multiple of indentation_size.
- // In case where selection begins at the start of indentation_size multiple we remove whole indentation level.
+ remove_text(i, 0, i, 1);
+ } else if (line_text.begins_with(" ")) {
+ // Remove only enough spaces to align text to nearest full multiple of indentation_size.
int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i));
- line_text = line_text.substr(spaces_to_remove, line_text.length());
-
- set_line(i, line_text);
- removed_characters = spaces_to_remove;
-
- first_line_edited = (i == start_line) ? true : first_line_edited;
- last_line_edited = (i == end_line) ? true : last_line_edited;
+ remove_text(i, 0, i, spaces_to_remove);
}
}
-
- if (has_selection(c)) {
- // Fix selection being off by one on the first line.
- if (first_line_edited) {
- select(get_selection_from_line(c), get_selection_from_column(c) - removed_characters, get_selection_to_line(c), initial_selection_end_column, c);
- }
-
- // Fix selection being off by one on the last line.
- if (last_line_edited) {
- select(get_selection_from_line(c), get_selection_from_column(c), get_selection_to_line(c), initial_selection_end_column - removed_characters, c);
- }
- }
- set_caret_column(initial_cursor_column - removed_characters, false, c);
}
+
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
@@ -992,27 +952,6 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
ERR_FAIL_COND(p_to_line >= get_line_count());
ERR_FAIL_COND(p_to_line < p_from_line);
- // Store caret states.
- Vector<int> caret_columns;
- Vector<Pair<int, int>> from_selections;
- Vector<Pair<int, int>> to_selections;
- caret_columns.resize(get_caret_count());
- from_selections.resize(get_caret_count());
- to_selections.resize(get_caret_count());
- for (int c = 0; c < get_caret_count(); c++) {
- caret_columns.write[c] = get_caret_column(c);
-
- // Set "selection_from_line" to -1 to allow checking if there was a selection later.
- if (!has_selection(c)) {
- from_selections.write[c].first = -1;
- continue;
- }
- from_selections.write[c].first = get_selection_from_line(c);
- from_selections.write[c].second = get_selection_from_column(c);
- to_selections.write[c].first = get_selection_to_line(c);
- to_selections.write[c].second = get_selection_to_column(c);
- }
-
// Check lines within range.
const char32_t from_indent_char = indent_using_spaces ? '\t' : ' ';
int size_diff = indent_using_spaces ? indent_size - 1 : -(indent_size - 1);
@@ -1044,23 +983,10 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
line_changed = true;
if (!changed_indentation) {
begin_complex_operation();
+ begin_multicaret_edit();
changed_indentation = true;
}
- // Calculate new caret state.
- for (int c = 0; c < get_caret_count(); c++) {
- if (get_caret_line(c) != i || caret_columns[c] <= j) {
- continue;
- }
- caret_columns.write[c] += size_diff;
-
- if (from_selections.write[c].first == -1) {
- continue;
- }
- from_selections.write[c].second = from_selections[c].first == i ? from_selections[c].second + size_diff : from_selections[c].second;
- to_selections.write[c].second = to_selections[c].first == i ? to_selections[c].second + size_diff : to_selections[c].second;
- }
-
// Calculate new line.
line = line.left(j + ((size_diff < 0) ? size_diff : 0)) + indent_text + line.substr(j + 1);
@@ -1069,6 +995,7 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
}
if (line_changed) {
+ // Use set line to preserve carets visual position.
set_line(i, line);
}
}
@@ -1077,16 +1004,9 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
return;
}
- // Restore caret states.
- for (int c = 0; c < get_caret_count(); c++) {
- set_caret_column(caret_columns[c], c == 0, c);
- if (from_selections.write[c].first != -1) {
- select(from_selections.write[c].first, from_selections.write[c].second, to_selections.write[c].first, to_selections.write[c].second, c);
- }
- }
merge_overlapping_carets();
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const {
@@ -1107,15 +1027,22 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
// When not splitting the line, we need to factor in indentation from the end of the current line.
const int cc = p_split_current_line ? get_caret_column(i) : get_line(get_caret_line(i)).length();
const int cl = get_caret_line(i);
const String line = get_line(cl);
- String ins = "\n";
+ String ins = "";
+ if (!p_above) {
+ ins = "\n";
+ }
// Append current indentation.
int space_count = 0;
@@ -1138,6 +1065,9 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
}
break;
}
+ if (p_above) {
+ ins += "\n";
+ }
if (is_line_folded(cl)) {
unfold_line(cl);
@@ -1183,33 +1113,22 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
}
}
- bool first_line = false;
- if (!p_split_current_line) {
+ if (p_split_current_line) {
+ insert_text_at_caret(ins, i);
+ } else {
+ insert_text(ins, cl, p_above ? 0 : get_line(cl).length(), p_above, p_above);
deselect(i);
-
- if (p_above) {
- if (cl > 0) {
- set_caret_line(cl - 1, false, true, 0, i);
- set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
- } else {
- set_caret_column(0, i == 0, i);
- first_line = true;
- }
- } else {
- set_caret_column(line.length(), i == 0, i);
- }
+ set_caret_line(p_above ? cl : cl + 1, false, true, -1, i);
+ set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
}
-
- insert_text_at_caret(ins, i);
-
- if (first_line) {
- set_caret_line(0, i == 0, true, 0, i);
- } else if (brace_indent) {
+ if (brace_indent) {
+ // Move to inner indented line.
set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
}
}
+ end_multicaret_edit();
end_complex_operation();
}
@@ -1700,27 +1619,8 @@ void CodeEdit::fold_line(int p_line) {
_set_line_as_hidden(i, true);
}
- for (int i = 0; i < get_caret_count(); i++) {
- // Fix selection.
- if (has_selection(i)) {
- if (_is_line_hidden(get_selection_from_line(i)) && _is_line_hidden(get_selection_to_line(i))) {
- deselect(i);
- } else if (_is_line_hidden(get_selection_from_line(i))) {
- select(p_line, 9999, get_selection_to_line(i), get_selection_to_column(i), i);
- } else if (_is_line_hidden(get_selection_to_line(i))) {
- select(get_selection_from_line(i), get_selection_from_column(i), p_line, 9999, i);
- }
- }
-
- // Reset caret.
- if (_is_line_hidden(get_caret_line(i))) {
- set_caret_line(p_line, false, false, 0, i);
- set_caret_column(get_line(p_line).length(), false, i);
- }
- }
-
- merge_overlapping_carets();
- queue_redraw();
+ // Collapse any carets in the hidden area.
+ collapse_carets(p_line, get_line(p_line).length(), end_line, get_line(end_line).length(), true);
}
void CodeEdit::unfold_line(int p_line) {
@@ -1769,6 +1669,23 @@ void CodeEdit::toggle_foldable_line(int p_line) {
fold_line(p_line);
}
+void CodeEdit::toggle_foldable_lines_at_carets() {
+ begin_multicaret_edit();
+ int previous_line = -1;
+ Vector<int> sorted = get_sorted_carets();
+ for (int caret_idx : sorted) {
+ if (multicaret_edit_ignore_caret(caret_idx)) {
+ continue;
+ }
+ int line_idx = get_caret_line(caret_idx);
+ if (line_idx != previous_line) {
+ toggle_foldable_line(line_idx);
+ previous_line = line_idx;
+ }
+ }
+ end_multicaret_edit();
+}
+
bool CodeEdit::is_line_folded(int p_line) const {
ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
return p_line + 1 < get_line_count() && !_is_line_hidden(p_line) && _is_line_hidden(p_line + 1);
@@ -1795,49 +1712,29 @@ void CodeEdit::create_code_region() {
WARN_PRINT_ONCE("Cannot create code region without any one line comment delimiters");
return;
}
+ String region_name = atr(ETR("New Code Region"));
+
begin_complex_operation();
- // Merge selections if selection starts on the same line the previous one ends.
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- Vector<int> carets_to_remove;
- for (int i = 1; i < caret_edit_order.size(); i++) {
- int current_caret = caret_edit_order[i - 1];
- int next_caret = caret_edit_order[i];
- if (get_selection_from_line(current_caret) == get_selection_to_line(next_caret)) {
- select(get_selection_from_line(next_caret), get_selection_from_column(next_caret), get_selection_to_line(current_caret), get_selection_to_column(current_caret), next_caret);
- carets_to_remove.append(current_caret);
- }
- }
- // Sort and remove backwards to preserve indices.
- carets_to_remove.sort();
- for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
- remove_caret(carets_to_remove[i]);
- }
-
- // Adding start and end region tags.
- int first_region_start = -1;
- for (int caret_idx : get_caret_index_edit_order()) {
- if (!has_selection(caret_idx)) {
- continue;
- }
- int from_line = get_selection_from_line(caret_idx);
- if (first_region_start == -1 || from_line < first_region_start) {
- first_region_start = from_line;
- }
- int to_line = get_selection_to_line(caret_idx);
- set_line(to_line, get_line(to_line) + "\n" + code_region_end_string);
- insert_line_at(from_line, code_region_start_string + " " + atr(ETR("New Code Region")));
- fold_line(from_line);
+ begin_multicaret_edit();
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets(true, false);
+
+ // Add start and end region tags.
+ int line_offset = 0;
+ for (Point2i line_range : line_ranges) {
+ insert_text("\n" + code_region_end_string, line_range.y + line_offset, get_line(line_range.y + line_offset).length());
+ insert_line_at(line_range.x + line_offset, code_region_start_string + " " + region_name);
+ fold_line(line_range.x + line_offset);
+ line_offset += 2;
}
+ int first_region_start = line_ranges[0].x;
// Select name of the first region to allow quick edit.
remove_secondary_carets();
- set_caret_line(first_region_start);
- int tag_length = code_region_start_string.length() + atr(ETR("New Code Region")).length() + 1;
- set_caret_column(tag_length);
+ int tag_length = code_region_start_string.length() + region_name.length() + 1;
select(first_region_start, code_region_start_string.length() + 1, first_region_start, tag_length);
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
String CodeEdit::get_code_region_start_tag() const {
@@ -2236,8 +2133,12 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
char32_t caret_last_completion_char = 0;
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
int caret_line = get_caret_line(i);
const String &insert_text = code_completion_options[code_completion_current_selected].insert_text;
@@ -2270,8 +2171,6 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
// Replace.
remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_remove_line, caret_col);
- adjust_carets_after_edit(i, caret_line, caret_col - code_completion_base.length(), caret_remove_line, caret_col);
- set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i);
insert_text_at_caret(insert_text, i);
} else {
// Get first non-matching char.
@@ -2287,8 +2186,6 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
// Remove base completion text.
remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i));
- adjust_carets_after_edit(i, caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i));
- set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i);
// Merge with text.
insert_text_at_caret(insert_text.substr(0, code_completion_base.length()), i);
@@ -2313,12 +2210,10 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
if (has_string_delimiter(String::chr(last_completion_char))) {
if (post_brace_pair != -1 && last_char_matches) {
remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
- adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
}
} else {
if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && last_char_matches) {
remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
- adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
} else if (auto_brace_completion_enabled && pre_brace_pair != -1) {
insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i);
set_caret_column(get_caret_column(i) - auto_brace_completion_pairs[pre_brace_pair].close_key.length(), i == 0, i);
@@ -2329,13 +2224,16 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i) + 1);
if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1)) {
remove_text(caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i));
- adjust_carets_after_edit(i, caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i));
- if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1) != pre_brace_pair) {
- set_caret_column(get_caret_column(i) - 1, i == 0, i);
+ if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) + 1) != pre_brace_pair) {
+ set_caret_column(get_caret_column(i) + 1, i == 0, i);
+ } else {
+ set_caret_column(get_caret_column(i) + 2, i == 0, i);
}
}
}
}
+
+ end_multicaret_edit();
end_complex_operation();
cancel_code_completion();
@@ -2418,65 +2316,154 @@ void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) {
}
/* Text manipulation */
-void CodeEdit::duplicate_lines() {
+void CodeEdit::move_lines_up() {
begin_complex_operation();
+ begin_multicaret_edit();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &caret_index : caret_edit_order) {
- // The text that will be inserted. All lines in one string.
- String insert_text;
-
- // The new line position of the caret after the operation.
- int new_caret_line = get_caret_line(caret_index);
- // The new column position of the caret after the operation.
- int new_caret_column = get_caret_column(caret_index);
- // The caret positions of the selection. Stays -1 if there is no selection.
- int select_from_line = -1;
- int select_to_line = -1;
- int select_from_column = -1;
- int select_to_column = -1;
- // Number of lines of the selection.
- int select_num_lines = -1;
-
- if (has_selection(caret_index)) {
- select_from_line = get_selection_from_line(caret_index);
- select_to_line = get_selection_to_line(caret_index);
- select_from_column = get_selection_from_column(caret_index);
- select_to_column = get_selection_to_column(caret_index);
- select_num_lines = select_to_line - select_from_line + 1;
-
- for (int i = select_from_line; i <= select_to_line; i++) {
- insert_text += "\n" + get_line(i);
- unfold_line(i);
- }
- new_caret_line = select_to_line + select_num_lines;
- } else {
- insert_text = "\n" + get_line(new_caret_line);
- new_caret_line++;
+ // Move lines up by swapping each line with the one above it.
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
+ for (Point2i line_range : line_ranges) {
+ if (line_range.x == 0) {
+ continue;
+ }
+ unfold_line(line_range.x - 1);
+ for (int line = line_range.x; line <= line_range.y; line++) {
+ unfold_line(line);
+ swap_lines(line - 1, line);
+ }
+ }
- unfold_line(get_caret_line(caret_index));
+ // Fix selection if it ended at column 0, since it wasn't moved.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (has_selection(i) && get_selection_to_column(i) == 0 && get_selection_to_line(i) != 0) {
+ if (is_caret_after_selection_origin(i)) {
+ set_caret_line(get_caret_line(i) - 1, false, true, -1, i);
+ } else {
+ set_selection_origin_line(get_selection_origin_line(i) - 1, true, -1, i);
+ }
}
+ }
- // The text will be inserted at the end of the current line.
- set_caret_column(get_line(get_caret_line(caret_index)).length(), false, caret_index);
+ end_multicaret_edit();
+ end_complex_operation();
+}
- deselect(caret_index);
+void CodeEdit::move_lines_down() {
+ begin_complex_operation();
+ begin_multicaret_edit();
- insert_text_at_caret(insert_text, caret_index);
- set_caret_line(new_caret_line, false, true, 0, caret_index);
- set_caret_column(new_caret_column, true, caret_index);
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
- if (select_from_line != -1) {
- // Advance the selection by the number of duplicated lines.
- select_from_line += select_num_lines;
- select_to_line += select_num_lines;
+ // Fix selection if it ended at column 0, since it won't be moved.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (has_selection(i) && get_selection_to_column(i) == 0 && get_selection_to_line(i) != get_line_count() - 1) {
+ if (is_caret_after_selection_origin(i)) {
+ set_caret_line(get_caret_line(i) + 1, false, true, -1, i);
+ } else {
+ set_selection_origin_line(get_selection_origin_line(i) + 1, true, -1, i);
+ }
+ }
+ }
- select(select_from_line, select_from_column, select_to_line, select_to_column, caret_index);
+ // Move lines down by swapping each line with the one below it.
+ for (Point2i line_range : line_ranges) {
+ if (line_range.y == get_line_count() - 1) {
+ continue;
+ }
+ unfold_line(line_range.y + 1);
+ for (int line = line_range.y; line >= line_range.x; line--) {
+ unfold_line(line);
+ swap_lines(line + 1, line);
}
}
+ end_multicaret_edit();
+ end_complex_operation();
+}
+
+void CodeEdit::delete_lines() {
+ begin_complex_operation();
+ begin_multicaret_edit();
+
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
+ int line_offset = 0;
+ for (Point2i line_range : line_ranges) {
+ // Remove last line of range separately to preserve carets.
+ unfold_line(line_range.y + line_offset);
+ remove_line_at(line_range.y + line_offset);
+ if (line_range.x != line_range.y) {
+ remove_text(line_range.x + line_offset, 0, line_range.y + line_offset, 0);
+ }
+ line_offset += line_range.x - line_range.y - 1;
+ }
+
+ // Deselect all.
+ deselect();
+
+ end_multicaret_edit();
+ end_complex_operation();
+}
+
+void CodeEdit::duplicate_selection() {
+ begin_complex_operation();
+ begin_multicaret_edit();
+
+ // Duplicate lines from carets without selections first.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
+ for (int l = get_selection_from_line(i); l <= get_selection_to_line(i); l++) {
+ unfold_line(l);
+ }
+ if (has_selection(i)) {
+ continue;
+ }
+
+ String text_to_insert = get_line(get_caret_line(i)) + "\n";
+ // Insert new text before the line, so the caret is on the second one.
+ insert_text(text_to_insert, get_caret_line(i), 0);
+ }
+
+ // Duplicate selections.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
+ if (!has_selection(i)) {
+ continue;
+ }
+
+ // Insert new text before the selection, so the caret is on the second one.
+ insert_text(get_selected_text(i), get_selection_from_line(i), get_selection_from_column(i));
+ }
+
+ end_multicaret_edit();
+ end_complex_operation();
+}
+
+void CodeEdit::duplicate_lines() {
+ begin_complex_operation();
+ begin_multicaret_edit();
+
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets(false, false);
+ int line_offset = 0;
+ for (Point2i line_range : line_ranges) {
+ // The text that will be inserted. All lines in one string.
+ String text_to_insert;
+
+ for (int i = line_range.x + line_offset; i <= line_range.y + line_offset; i++) {
+ text_to_insert += get_line(i) + "\n";
+ unfold_line(i);
+ }
+
+ // Insert new text before the line.
+ insert_text(text_to_insert, line_range.x + line_offset, 0);
+ line_offset += line_range.y - line_range.x + 1;
+ }
+
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
/* Visual */
@@ -2578,6 +2565,7 @@ void CodeEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("fold_all_lines"), &CodeEdit::fold_all_lines);
ClassDB::bind_method(D_METHOD("unfold_all_lines"), &CodeEdit::unfold_all_lines);
ClassDB::bind_method(D_METHOD("toggle_foldable_line", "line"), &CodeEdit::toggle_foldable_line);
+ ClassDB::bind_method(D_METHOD("toggle_foldable_lines_at_carets"), &CodeEdit::toggle_foldable_lines_at_carets);
ClassDB::bind_method(D_METHOD("is_line_folded", "line"), &CodeEdit::is_line_folded);
ClassDB::bind_method(D_METHOD("get_folded_lines"), &CodeEdit::get_folded_lines);
@@ -2679,6 +2667,10 @@ void CodeEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid);
/* Text manipulation */
+ ClassDB::bind_method(D_METHOD("move_lines_up"), &CodeEdit::move_lines_up);
+ ClassDB::bind_method(D_METHOD("move_lines_down"), &CodeEdit::move_lines_down);
+ ClassDB::bind_method(D_METHOD("delete_lines"), &CodeEdit::delete_lines);
+ ClassDB::bind_method(D_METHOD("duplicate_selection"), &CodeEdit::duplicate_selection);
ClassDB::bind_method(D_METHOD("duplicate_lines"), &CodeEdit::duplicate_lines);
/* Inspector */
@@ -2846,10 +2838,12 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) {
if (p_gutter == line_number_gutter) {
remove_secondary_carets();
- set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0);
- select(p_line, 0, p_line + 1, 0);
- set_caret_line(p_line + 1);
- set_caret_column(0);
+ set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE);
+ if (p_line == get_line_count() - 1) {
+ select(p_line, 0, p_line, INT_MAX);
+ } else {
+ select(p_line, 0, p_line + 1, 0);
+ }
return;
}
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 1770d4f4d8..56f8cce548 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -309,11 +309,14 @@ protected:
static void _bind_compatibility_methods();
#endif
+ virtual void _unhide_carets() override;
+
/* Text manipulation */
// Overridable actions
virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) override;
virtual void _backspace_internal(int p_caret) override;
+ virtual void _cut_internal(int p_caret) override;
GDVIRTUAL1(_confirm_code_completion, bool)
GDVIRTUAL1(_request_code_completion, bool)
@@ -409,6 +412,7 @@ public:
void fold_all_lines();
void unfold_all_lines();
void toggle_foldable_line(int p_line);
+ void toggle_foldable_lines_at_carets();
bool is_line_folded(int p_line) const;
TypedArray<int> get_folded_lines() const;
@@ -489,6 +493,10 @@ public:
void set_symbol_lookup_word_as_valid(bool p_valid);
/* Text manipulation */
+ void move_lines_up();
+ void move_lines_down();
+ void delete_lines();
+ void duplicate_selection();
void duplicate_lines();
CodeEdit();
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 848a598ebb..aeaaac1efc 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -458,8 +458,8 @@ void ColorPicker::set_editor_settings(Object *p_editor_settings) {
}
}
- for (int i = 0; i < preset_cache.size(); i++) {
- presets.push_back(preset_cache[i]);
+ for (const Color &preset : preset_cache) {
+ presets.push_back(preset);
}
if (recent_preset_cache.is_empty()) {
@@ -469,8 +469,8 @@ void ColorPicker::set_editor_settings(Object *p_editor_settings) {
}
}
- for (int i = 0; i < recent_preset_cache.size(); i++) {
- recent_presets.push_back(recent_preset_cache[i]);
+ for (const Color &preset : recent_preset_cache) {
+ recent_presets.push_back(preset);
}
_update_presets();
@@ -660,8 +660,8 @@ void ColorPicker::_update_presets() {
for (int i = 1; i < preset_container->get_child_count(); i++) {
preset_container->get_child(i)->queue_free();
}
- for (int i = 0; i < preset_cache.size(); i++) {
- _add_preset_button(preset_size, preset_cache[i]);
+ for (const Color &preset : preset_cache) {
+ _add_preset_button(preset_size, preset);
}
_notification(NOTIFICATION_VISIBILITY_CHANGED);
}
@@ -677,13 +677,13 @@ void ColorPicker::_update_recent_presets() {
}
recent_presets.clear();
- for (int i = 0; i < recent_preset_cache.size(); i++) {
- recent_presets.push_back(recent_preset_cache[i]);
+ for (const Color &preset : recent_preset_cache) {
+ recent_presets.push_back(preset);
}
int preset_size = _get_preset_size();
- for (int i = 0; i < recent_presets.size(); i++) {
- _add_recent_preset_button(preset_size, recent_presets[i]);
+ for (const Color &preset : recent_presets) {
+ _add_recent_preset_button(preset_size, preset);
}
_notification(NOTIFICATION_VISIBILITY_CHANGED);
@@ -937,8 +937,9 @@ void ColorPicker::erase_recent_preset(const Color &p_color) {
PackedColorArray ColorPicker::get_presets() const {
PackedColorArray arr;
arr.resize(presets.size());
- for (int i = 0; i < presets.size(); i++) {
- arr.set(i, presets[i]);
+ int i = 0;
+ for (List<Color>::ConstIterator itr = presets.begin(); itr != presets.end(); ++itr, ++i) {
+ arr.set(i, *itr);
}
return arr;
}
@@ -946,8 +947,9 @@ PackedColorArray ColorPicker::get_presets() const {
PackedColorArray ColorPicker::get_recent_presets() const {
PackedColorArray arr;
arr.resize(recent_presets.size());
- for (int i = 0; i < recent_presets.size(); i++) {
- arr.set(i, recent_presets[i]);
+ int i = 0;
+ for (List<Color>::ConstIterator itr = recent_presets.begin(); itr != recent_presets.end(); ++itr, ++i) {
+ arr.set(i, *itr);
}
return arr;
}
diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp
index c6e66c95c6..5db8d69eef 100644
--- a/scene/gui/container.cpp
+++ b/scene/gui/container.cpp
@@ -141,6 +141,14 @@ void Container::queue_sort() {
pending_sort = true;
}
+Control *Container::as_sortable_control(Node *p_node) const {
+ Control *c = Object::cast_to<Control>(p_node);
+ if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) {
+ return nullptr;
+ }
+ return c;
+}
+
Vector<int> Container::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
if (GDVIRTUAL_CALL(_get_allowed_size_flags_horizontal, flags)) {
diff --git a/scene/gui/container.h b/scene/gui/container.h
index 94c3c540d7..405220cee6 100644
--- a/scene/gui/container.h
+++ b/scene/gui/container.h
@@ -42,6 +42,8 @@ class Container : public Control {
protected:
void queue_sort();
+ Control *as_sortable_control(Node *p_node) const;
+
virtual void add_child_notify(Node *p_child) override;
virtual void move_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index d430fe9bfc..2dd12b92f3 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -142,8 +142,8 @@ Size2 Control::_edit_get_scale() const {
void Control::_edit_set_rect(const Rect2 &p_edit_rect) {
ERR_FAIL_COND_MSG(!Engine::get_singleton()->is_editor_hint(), "This function can only be used from editor plugins.");
- set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled());
- set_size(p_edit_rect.size.snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled());
+ set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snappedf(1), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled());
+ set_size(p_edit_rect.size.snappedf(1), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled());
}
Rect2 Control::_edit_get_rect() const {
@@ -208,28 +208,42 @@ void Control::set_root_layout_direction(int p_root_dir) {
#ifdef TOOLS_ENABLED
void Control::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
ERR_READ_THREAD_GUARD;
- CanvasItem::get_argument_options(p_function, p_idx, r_options);
-
if (p_idx == 0) {
- List<StringName> sn;
const String pf = p_function;
+ Theme::DataType type = Theme::DATA_TYPE_MAX;
+
if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color") {
- ThemeDB::get_singleton()->get_default_theme()->get_color_list(get_class(), &sn);
- } else if (pf == "add_theme_style_override" || pf == "has_theme_style" || pf == "has_theme_style_override" || pf == "get_theme_style") {
- ThemeDB::get_singleton()->get_default_theme()->get_stylebox_list(get_class(), &sn);
+ type = Theme::DATA_TYPE_COLOR;
+ } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant") {
+ type = Theme::DATA_TYPE_CONSTANT;
} else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font") {
- ThemeDB::get_singleton()->get_default_theme()->get_font_list(get_class(), &sn);
+ type = Theme::DATA_TYPE_FONT;
} else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size") {
- ThemeDB::get_singleton()->get_default_theme()->get_font_size_list(get_class(), &sn);
- } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant") {
- ThemeDB::get_singleton()->get_default_theme()->get_constant_list(get_class(), &sn);
+ type = Theme::DATA_TYPE_FONT_SIZE;
+ } else if (pf == "add_theme_icon_override" || pf == "has_theme_icon" || pf == "has_theme_icon_override" || pf == "get_theme_icon") {
+ type = Theme::DATA_TYPE_ICON;
+ } else if (pf == "add_theme_style_override" || pf == "has_theme_style" || pf == "has_theme_style_override" || pf == "get_theme_style") {
+ type = Theme::DATA_TYPE_STYLEBOX;
}
- sn.sort_custom<StringName::AlphCompare>();
- for (const StringName &name : sn) {
- r_options->push_back(String(name).quote());
+ if (type != Theme::DATA_TYPE_MAX) {
+ List<ThemeDB::ThemeItemBind> theme_items;
+ ThemeDB::get_singleton()->get_class_items(get_class_name(), &theme_items, true, type);
+
+ List<StringName> sn;
+ for (const ThemeDB::ThemeItemBind &E : theme_items) {
+ if (E.data_type == type) {
+ sn.push_back(E.item_name);
+ }
+ }
+
+ sn.sort_custom<StringName::AlphCompare>();
+ for (const StringName &name : sn) {
+ r_options->push_back(String(name).quote());
+ }
}
}
+ CanvasItem::get_argument_options(p_function, p_idx, r_options);
}
#endif
@@ -1383,6 +1397,15 @@ void Control::_set_position(const Point2 &p_point) {
void Control::set_position(const Point2 &p_point, bool p_keep_offsets) {
ERR_MAIN_THREAD_GUARD;
+
+#ifdef TOOLS_ENABLED
+ // Can't compute anchors, set position directly and return immediately.
+ if (saving && !is_inside_tree()) {
+ data.pos_cache = p_point;
+ return;
+ }
+#endif
+
if (p_keep_offsets) {
_compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor);
} else {
@@ -1441,6 +1464,14 @@ void Control::set_size(const Size2 &p_size, bool p_keep_offsets) {
new_size.y = min.y;
}
+#ifdef TOOLS_ENABLED
+ // Can't compute anchors, set size directly and return immediately.
+ if (saving && !is_inside_tree()) {
+ data.size_cache = new_size;
+ return;
+ }
+#endif
+
if (p_keep_offsets) {
_compute_anchors(Rect2(data.pos_cache, new_size), data.offset, data.anchor);
} else {
@@ -3140,6 +3171,14 @@ Control *Control::make_custom_tooltip(const String &p_text) const {
void Control::_notification(int p_notification) {
ERR_MAIN_THREAD_GUARD;
switch (p_notification) {
+#ifdef TOOLS_ENABLED
+ case NOTIFICATION_EDITOR_PRE_SAVE: {
+ saving = true;
+ } break;
+ case NOTIFICATION_EDITOR_POST_SAVE: {
+ saving = false;
+ } break;
+#endif
case NOTIFICATION_POSTINITIALIZE: {
data.initialized = true;
diff --git a/scene/gui/control.h b/scene/gui/control.h
index bdb908e089..c784d4330d 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -47,6 +47,10 @@ class ThemeContext;
class Control : public CanvasItem {
GDCLASS(Control, CanvasItem);
+#ifdef TOOLS_ENABLED
+ bool saving = false;
+#endif
+
public:
enum Anchor {
ANCHOR_BEGIN = 0,
diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp
index 9f79da2905..ceffd9d103 100644
--- a/scene/gui/flow_container.cpp
+++ b/scene/gui/flow_container.cpp
@@ -38,6 +38,7 @@ struct _LineData {
int min_line_length = 0;
int stretch_avail = 0;
float stretch_ratio_total = 0;
+ bool is_filled = false;
};
void FlowContainer::_resort() {
@@ -58,14 +59,12 @@ void FlowContainer::_resort() {
float line_stretch_ratio_total = 0;
int current_container_size = vertical ? get_rect().size.y : get_rect().size.x;
int children_in_current_line = 0;
+ Control *last_child = nullptr;
// First pass for line wrapping and minimum size calculation.
for (int i = 0; i < get_child_count(); i++) {
- Control *child = Object::cast_to<Control>(get_child(i));
- if (!child || !child->is_visible()) {
- continue;
- }
- if (child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i));
+ if (!child) {
continue;
}
@@ -77,7 +76,7 @@ void FlowContainer::_resort() {
}
if (ofs.y + child_msc.y > current_container_size) {
line_length = ofs.y - theme_cache.v_separation;
- lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
+ lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true });
// Move in new column (vertical line).
ofs.x += line_height + theme_cache.h_separation;
@@ -99,7 +98,7 @@ void FlowContainer::_resort() {
}
if (ofs.x + child_msc.x > current_container_size) {
line_length = ofs.x - theme_cache.h_separation;
- lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
+ lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true });
// Move in new line.
ofs.y += line_height + theme_cache.v_separation;
@@ -116,11 +115,16 @@ void FlowContainer::_resort() {
ofs.x += child_msc.x;
}
+ last_child = child;
children_minsize_cache[child] = child_msc;
children_in_current_line++;
}
line_length = vertical ? (ofs.y) : (ofs.x);
- lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
+ bool is_filled = false;
+ if (last_child != nullptr) {
+ is_filled = vertical ? (ofs.y + last_child->get_combined_minimum_size().y > current_container_size ? true : false) : (ofs.x + last_child->get_combined_minimum_size().x > current_container_size ? true : false);
+ }
+ lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, is_filled });
// Second pass for in-line expansion and alignment.
@@ -131,11 +135,8 @@ void FlowContainer::_resort() {
ofs.y = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *child = Object::cast_to<Control>(get_child(i));
- if (!child || !child->is_visible()) {
- continue;
- }
- if (child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i));
+ if (!child) {
continue;
}
Size2i child_size = children_minsize_cache[child];
@@ -158,17 +159,43 @@ void FlowContainer::_resort() {
// but only if the line doesn't contain a child that expands.
if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) {
int alignment_ofs = 0;
+ bool is_not_first_line_and_not_filled = current_line_idx != 0 && !line_data.is_filled;
+ float prior_stretch_avail = is_not_first_line_and_not_filled ? lines_data[current_line_idx - 1].stretch_avail : 0.0;
switch (alignment) {
- case ALIGNMENT_CENTER:
- alignment_ofs = line_data.stretch_avail / 2;
- break;
- case ALIGNMENT_END:
- alignment_ofs = line_data.stretch_avail;
- break;
+ case ALIGNMENT_BEGIN: {
+ if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && is_not_first_line_and_not_filled) {
+ if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) {
+ alignment_ofs = line_data.stretch_avail - prior_stretch_avail;
+ } else if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_CENTER) {
+ alignment_ofs = (line_data.stretch_avail - prior_stretch_avail) * 0.5;
+ }
+ }
+ } break;
+ case ALIGNMENT_CENTER: {
+ if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_CENTER && is_not_first_line_and_not_filled) {
+ if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) {
+ alignment_ofs = line_data.stretch_avail - (prior_stretch_avail * 0.5);
+ } else { // Is LAST_WRAP_ALIGNMENT_BEGIN
+ alignment_ofs = prior_stretch_avail * 0.5;
+ }
+ } else {
+ alignment_ofs = line_data.stretch_avail * 0.5;
+ }
+ } break;
+ case ALIGNMENT_END: {
+ if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_END && is_not_first_line_and_not_filled) {
+ if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_BEGIN) {
+ alignment_ofs = prior_stretch_avail;
+ } else { // Is LAST_WRAP_ALIGNMENT_CENTER
+ alignment_ofs = prior_stretch_avail + (line_data.stretch_avail - prior_stretch_avail) * 0.5;
+ }
+ } else {
+ alignment_ofs = line_data.stretch_avail;
+ }
+ } break;
default:
break;
}
-
if (vertical) { /* VERTICAL */
ofs.y += alignment_ofs;
} else { /* HORIZONTAL */
@@ -223,17 +250,10 @@ Size2 FlowContainer::get_minimum_size() const {
Size2i minimum;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
-
- if (!c->is_visible()) {
- continue;
- }
Size2i size = c->get_combined_minimum_size();
@@ -314,6 +334,18 @@ FlowContainer::AlignmentMode FlowContainer::get_alignment() const {
return alignment;
}
+void FlowContainer::set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment) {
+ if (last_wrap_alignment == p_last_wrap_alignment) {
+ return;
+ }
+ last_wrap_alignment = p_last_wrap_alignment;
+ _resort();
+}
+
+FlowContainer::LastWrapAlignmentMode FlowContainer::get_last_wrap_alignment() const {
+ return last_wrap_alignment;
+}
+
void FlowContainer::set_vertical(bool p_vertical) {
ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
vertical = p_vertical;
@@ -346,6 +378,8 @@ void FlowContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment);
ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment);
+ ClassDB::bind_method(D_METHOD("set_last_wrap_alignment", "last_wrap_alignment"), &FlowContainer::set_last_wrap_alignment);
+ ClassDB::bind_method(D_METHOD("get_last_wrap_alignment"), &FlowContainer::get_last_wrap_alignment);
ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical);
ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical);
ClassDB::bind_method(D_METHOD("set_reverse_fill", "reverse_fill"), &FlowContainer::set_reverse_fill);
@@ -354,8 +388,13 @@ void FlowContainer::_bind_methods() {
BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN);
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
BIND_ENUM_CONSTANT(ALIGNMENT_END);
+ BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_INHERIT);
+ BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_BEGIN);
+ BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_CENTER);
+ BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_END);
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "last_wrap_alignment", PROPERTY_HINT_ENUM, "Inherit,Begin,Center,End"), "set_last_wrap_alignment", "get_last_wrap_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverse_fill"), "set_reverse_fill", "is_reverse_fill");
diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h
index 90da73aaab..65ebc89c78 100644
--- a/scene/gui/flow_container.h
+++ b/scene/gui/flow_container.h
@@ -42,6 +42,12 @@ public:
ALIGNMENT_CENTER,
ALIGNMENT_END
};
+ enum LastWrapAlignmentMode {
+ LAST_WRAP_ALIGNMENT_INHERIT,
+ LAST_WRAP_ALIGNMENT_BEGIN,
+ LAST_WRAP_ALIGNMENT_CENTER,
+ LAST_WRAP_ALIGNMENT_END
+ };
private:
int cached_size = 0;
@@ -50,6 +56,7 @@ private:
bool vertical = false;
bool reverse_fill = false;
AlignmentMode alignment = ALIGNMENT_BEGIN;
+ LastWrapAlignmentMode last_wrap_alignment = LAST_WRAP_ALIGNMENT_INHERIT;
struct ThemeCache {
int h_separation = 0;
@@ -71,6 +78,9 @@ public:
void set_alignment(AlignmentMode p_alignment);
AlignmentMode get_alignment() const;
+ void set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment);
+ LastWrapAlignmentMode get_last_wrap_alignment() const;
+
void set_vertical(bool p_vertical);
bool is_vertical() const;
@@ -102,5 +112,6 @@ public:
};
VARIANT_ENUM_CAST(FlowContainer::AlignmentMode);
+VARIANT_ENUM_CAST(FlowContainer::LastWrapAlignmentMode);
#endif // FLOW_CONTAINER_H
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index ef9c7e35ed..646e45b27a 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -501,7 +501,7 @@ void GraphEdit::_graph_element_resize_request(const Vector2 &p_new_minsize, Node
// Snap the new size to the grid if snapping is enabled.
Vector2 new_size = p_new_minsize;
if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) {
- new_size = new_size.snapped(Vector2(snapping_distance, snapping_distance));
+ new_size = new_size.snappedf(snapping_distance);
}
// Disallow resizing the frame to a size smaller than the minimum size of the attached nodes.
@@ -851,7 +851,7 @@ void GraphEdit::_set_position_of_frame_attached_nodes(GraphFrame *p_frame, const
Vector2 pos = (attached_node->get_drag_from() * zoom + drag_accum) / zoom;
if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) {
- pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
+ pos = pos.snappedf(snapping_distance);
}
// Recursively move graph frames.
@@ -1678,7 +1678,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
// Snapping can be toggled temporarily by holding down Ctrl.
// This is done here as to not toggle the grid when holding down Ctrl.
if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
- pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
+ pos = pos.snappedf(snapping_distance);
}
graph_element->set_position_offset(pos);
diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp
index 49998beb42..fa1059c667 100644
--- a/scene/gui/graph_edit_arranger.cpp
+++ b/scene/gui/graph_edit_arranger.cpp
@@ -180,7 +180,7 @@ void GraphEditArranger::arrange_nodes() {
if (graph_edit->is_snapping_enabled()) {
float snapping_distance = graph_edit->get_snapping_distance();
- pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
+ pos = pos.snappedf(snapping_distance);
}
graph_node->set_position_offset(pos);
graph_node->set_drag(false);
diff --git a/scene/gui/graph_element.cpp b/scene/gui/graph_element.cpp
index 9c13a3ad9e..e231b05d7f 100644
--- a/scene/gui/graph_element.cpp
+++ b/scene/gui/graph_element.cpp
@@ -49,14 +49,10 @@ void GraphElement::_resort() {
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
- Control *child = Object::cast_to<Control>(get_child(i));
- if (!child || !child->is_visible_in_tree()) {
- continue;
- }
- if (child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i));
+ if (!child) {
continue;
}
-
fit_child_in_rect(child, Rect2(Point2(), size));
}
}
@@ -65,10 +61,7 @@ Size2 GraphElement::get_minimum_size() const {
Size2 minsize;
for (int i = 0; i < get_child_count(); i++) {
Control *child = Object::cast_to<Control>(get_child(i));
- if (!child) {
- continue;
- }
- if (child->is_set_as_top_level()) {
+ if (!child || child->is_set_as_top_level()) {
continue;
}
diff --git a/scene/gui/graph_frame.cpp b/scene/gui/graph_frame.cpp
index ca9f7e6fcf..8cd7dbbeb5 100644
--- a/scene/gui/graph_frame.cpp
+++ b/scene/gui/graph_frame.cpp
@@ -160,14 +160,10 @@ void GraphFrame::_resort() {
Point2 offset = Point2(sb_panel->get_margin(SIDE_LEFT), sb_panel->get_margin(SIDE_TOP) + titlebar_min_size.height + sb_titlebar->get_minimum_size().height);
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible_in_tree()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
- if (child->is_set_as_top_level()) {
- continue;
- }
-
fit_child_in_rect(child, Rect2(offset, size));
}
}
@@ -325,8 +321,8 @@ Size2 GraphFrame::get_minimum_size() const {
Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size();
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index d035515b51..75ac5c5135 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -175,9 +175,8 @@ void GraphNode::_resort() {
HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
-
- if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
@@ -218,8 +217,8 @@ void GraphNode::_resort() {
bool refit_successful = true;
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
@@ -256,8 +255,8 @@ void GraphNode::_resort() {
int width = new_size.width - sb_panel->get_minimum_size().width;
int valid_children_idx = 0;
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
@@ -614,8 +613,8 @@ Size2 GraphNode::get_minimum_size() const {
Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size();
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp
index a4baf3bb8d..a67bba786b 100644
--- a/scene/gui/grid_container.cpp
+++ b/scene/gui/grid_container.cpp
@@ -44,11 +44,8 @@ void GridContainer::_notification(int p_what) {
// Compute the per-column/per-row data.
int valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -186,8 +183,8 @@ void GridContainer::_notification(int p_what) {
valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
int row = valid_controls_index / columns;
@@ -282,8 +279,8 @@ Size2 GridContainer::get_minimum_size() const {
int valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
int row = valid_controls_index / columns;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index ddfe202c13..c263e14b8a 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -1805,7 +1805,6 @@ void LineEdit::clear_internal() {
}
Size2 LineEdit::get_minimum_size() const {
- Ref<StyleBox> style = theme_cache.normal;
Ref<Font> font = theme_cache.font;
int font_size = theme_cache.font_size;
@@ -1834,7 +1833,8 @@ Size2 LineEdit::get_minimum_size() const {
}
min_size.width += icon_max_width;
- return style->get_minimum_size() + min_size;
+ Size2 style_min_size = theme_cache.normal->get_minimum_size().max(theme_cache.read_only->get_minimum_size());
+ return style_min_size + min_size;
}
void LineEdit::deselect() {
diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp
index 6d331afbb5..91e6c1f092 100644
--- a/scene/gui/margin_container.cpp
+++ b/scene/gui/margin_container.cpp
@@ -36,16 +36,10 @@ Size2 MarginContainer::get_minimum_size() const {
Size2 max;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
- if (!c->is_visible()) {
- continue;
- }
Size2 s = c->get_combined_minimum_size();
if (s.width > max.width) {
@@ -103,13 +97,10 @@ void MarginContainer::_notification(int p_what) {
Size2 s = get_size();
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
int w = s.width - theme_cache.margin_left - theme_cache.margin_right;
int h = s.height - theme_cache.margin_top - theme_cache.margin_bottom;
diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp
index ef85919859..76fde26b26 100644
--- a/scene/gui/panel_container.cpp
+++ b/scene/gui/panel_container.cpp
@@ -35,11 +35,8 @@
Size2 PanelContainer::get_minimum_size() const {
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -87,11 +84,8 @@ void PanelContainer::_notification(int p_what) {
}
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index 87383283fd..97b33c4f74 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -77,6 +77,9 @@ void Popup::_notification(int p_what) {
_initialize_visible_parents();
} else {
_deinitialize_visible_parents();
+ if (hide_reason == HIDE_REASON_NONE) {
+ hide_reason = HIDE_REASON_CANCELED;
+ }
emit_signal(SNAME("popup_hide"));
popped_up = false;
}
@@ -87,6 +90,7 @@ void Popup::_notification(int p_what) {
if (!is_in_edited_scene_root()) {
if (has_focus()) {
popped_up = true;
+ hide_reason = HIDE_REASON_NONE;
}
}
} break;
@@ -100,6 +104,7 @@ void Popup::_notification(int p_what) {
case NOTIFICATION_WM_CLOSE_REQUEST: {
if (!is_in_edited_scene_root()) {
+ hide_reason = HIDE_REASON_UNFOCUSED;
_close_pressed();
}
} break;
@@ -114,6 +119,7 @@ void Popup::_notification(int p_what) {
void Popup::_parent_focused() {
if (popped_up && get_flag(FLAG_POPUP)) {
+ hide_reason = HIDE_REASON_UNFOCUSED;
_close_pressed();
}
}
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
index 48818686f7..69a81ad98c 100644
--- a/scene/gui/popup.h
+++ b/scene/gui/popup.h
@@ -43,6 +43,16 @@ class Popup : public Window {
LocalVector<Window *> visible_parents;
bool popped_up = false;
+public:
+ enum HideReason {
+ HIDE_REASON_NONE,
+ HIDE_REASON_CANCELED, // E.g., because of rupture of UI flow (app unfocused). Includes closed programmatically.
+ HIDE_REASON_UNFOCUSED, // E.g., user clicked outside.
+ };
+
+private:
+ HideReason hide_reason = HIDE_REASON_NONE;
+
void _initialize_visible_parents();
void _deinitialize_visible_parents();
@@ -60,6 +70,8 @@ protected:
virtual void _post_popup() override;
public:
+ HideReason get_hide_reason() const { return hide_reason; }
+
Popup();
~Popup();
};
diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp
index b2617e6fc7..90ce01e383 100644
--- a/scene/gui/progress_bar.cpp
+++ b/scene/gui/progress_bar.cpp
@@ -41,7 +41,7 @@ Size2 ProgressBar::get_minimum_size() const {
TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size);
minimum_size.height = MAX(minimum_size.height, theme_cache.background_style->get_minimum_size().height + tl.get_size().y);
} else { // this is needed, else the progressbar will collapse
- minimum_size = minimum_size.max(Size2(1, 1));
+ minimum_size = minimum_size.maxf(1);
}
return minimum_size;
}
diff --git a/scene/gui/rich_text_label.compat.inc b/scene/gui/rich_text_label.compat.inc
index 626278a405..97739c4b79 100644
--- a/scene/gui/rich_text_label.compat.inc
+++ b/scene/gui/rich_text_label.compat.inc
@@ -38,9 +38,14 @@ void RichTextLabel::_add_image_bind_compat_80410(const Ref<Texture2D> &p_image,
add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false);
}
+bool RichTextLabel::_remove_paragraph_bind_compat_91098(int p_paragraph) {
+ return remove_paragraph(p_paragraph, false);
+}
+
void RichTextLabel::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data"), &RichTextLabel::_push_meta_bind_compat_89024);
ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::_add_image_bind_compat_80410, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()));
+ ClassDB::bind_compatibility_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::_remove_paragraph_bind_compat_91098);
}
#endif // DISABLE_DEPRECATED
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 0773181239..19b02f33c6 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -3342,7 +3342,7 @@ void RichTextLabel::_remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_fr
}
}
-bool RichTextLabel::remove_paragraph(const int p_paragraph) {
+bool RichTextLabel::remove_paragraph(int p_paragraph, bool p_no_invalidate) {
_stop_thread();
MutexLock data_lock(data_mutex);
@@ -3391,8 +3391,44 @@ bool RichTextLabel::remove_paragraph(const int p_paragraph) {
selection.click_frame = nullptr;
selection.click_item = nullptr;
- deselect();
+ selection.active = false;
+
+ if (p_no_invalidate) {
+ // Do not invalidate cache, only update vertical offsets of the paragraphs after deleted one and scrollbar.
+ int to_line = main->first_invalid_line.load() - 1;
+ float total_height = (p_paragraph == 0) ? 0 : _calculate_line_vertical_offset(main->lines[p_paragraph - 1]);
+ for (int i = p_paragraph; i < to_line; i++) {
+ MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex());
+ main->lines[i].offset.y = total_height;
+ total_height = _calculate_line_vertical_offset(main->lines[i]);
+ }
+ updating_scroll = true;
+ vscroll->set_max(total_height);
+ updating_scroll = false;
+
+ main->first_invalid_line.store(MAX(main->first_invalid_line.load() - 1, 0));
+ main->first_resized_line.store(MAX(main->first_resized_line.load() - 1, 0));
+ main->first_invalid_font_line.store(MAX(main->first_invalid_font_line.load() - 1, 0));
+ } else {
+ // Invalidate cache after the deleted paragraph.
+ main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph));
+ main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph));
+ main->first_invalid_font_line.store(MIN(main->first_invalid_font_line.load(), p_paragraph));
+ }
+ queue_redraw();
+
+ return true;
+}
+
+bool RichTextLabel::invalidate_paragraph(int p_paragraph) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
+ if (p_paragraph >= (int)main->lines.size() || p_paragraph < 0) {
+ return false;
+ }
+ // Invalidate cache.
main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph));
main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph));
main->first_invalid_font_line.store(MIN(main->first_invalid_font_line.load(), p_paragraph));
@@ -5851,7 +5887,8 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region", "key", "pad", "tooltip", "size_in_percent"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(Variant()), DEFVAL(false), DEFVAL(String()), DEFVAL(false));
ClassDB::bind_method(D_METHOD("update_image", "key", "mask", "image", "width", "height", "color", "inline_align", "region", "pad", "tooltip", "size_in_percent"), &RichTextLabel::update_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(false), DEFVAL(String()), DEFVAL(false));
ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline);
- ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::remove_paragraph);
+ ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph", "no_invalidate"), &RichTextLabel::remove_paragraph, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("invalidate_paragraph", "paragraph"), &RichTextLabel::invalidate_paragraph);
ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font, DEFVAL(0));
ClassDB::bind_method(D_METHOD("push_font_size", "font_size"), &RichTextLabel::push_font_size);
ClassDB::bind_method(D_METHOD("push_normal"), &RichTextLabel::push_normal);
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 371f6724d7..189ee1da6e 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -134,6 +134,7 @@ protected:
#ifndef DISABLE_DEPRECATED
void _push_meta_bind_compat_89024(const Variant &p_meta);
void _add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region);
+ bool _remove_paragraph_bind_compat_91098(int p_paragraph);
static void _bind_compatibility_methods();
#endif
@@ -664,7 +665,8 @@ public:
void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
void update_image(const Variant &p_key, BitField<ImageUpdateMask> p_mask, const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
void add_newline();
- bool remove_paragraph(const int p_paragraph);
+ bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false);
+ bool invalidate_paragraph(int p_paragraph);
void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0));
void _push_def_font(DefaultFont p_def_font);
void _push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size = -1);
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 4f49f60d70..6f5d0cdcfb 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -43,11 +43,8 @@ Size2 ScrollContainer::get_minimum_size() const {
largest_child_min_size = Size2();
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
if (c == h_scroll || c == v_scroll) {
@@ -308,11 +305,8 @@ void ScrollContainer::_reposition_children() {
}
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
if (c == h_scroll || c == v_scroll) {
@@ -542,13 +536,10 @@ PackedStringArray ScrollContainer::get_configuration_warnings() const {
int found = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
if (c == h_scroll || c == v_scroll) {
continue;
}
diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp
index 5f4586a6d5..98b1db4a96 100644
--- a/scene/gui/split_container.cpp
+++ b/scene/gui/split_container.cpp
@@ -127,10 +127,7 @@ Control *SplitContainer::get_containable_child(int p_idx) const {
for (int i = 0; i < get_child_count(false); i++) {
Control *c = Object::cast_to<Control>(get_child(i, false));
- if (!c || !c->is_visible()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ if (!c || !c->is_visible() || c->is_set_as_top_level()) {
continue;
}
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index e2feb59a8c..dc53cf82e6 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -147,9 +147,7 @@ void TabContainer::_notification(int p_what) {
if (get_tab_count() > 0) {
_refresh_tab_names();
}
- } break;
- case NOTIFICATION_POST_ENTER_TREE: {
if (setup_current_tab >= -1) {
set_current_tab(setup_current_tab);
setup_current_tab = -2;
@@ -191,6 +189,25 @@ void TabContainer::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (!is_visible() || setup_current_tab > -2) {
+ return;
+ }
+
+ updating_visibility = true;
+
+ // As the visibility change notification will be triggered for all children soon after,
+ // beat it to the punch and make sure that the correct node is the only one visible first.
+ // Otherwise, it can prevent a tab change done right before this container was made visible.
+ Vector<Control *> controls = _get_tab_controls();
+ int current = get_current_tab();
+ for (int i = 0; i < controls.size(); i++) {
+ controls[i]->set_visible(i == current);
+ }
+
+ updating_visibility = false;
+ } break;
+
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
@@ -611,6 +628,7 @@ void TabContainer::set_current_tab(int p_current) {
setup_current_tab = p_current;
return;
}
+
tab_bar->set_current_tab(p_current);
}
diff --git a/scene/gui/text_edit.compat.inc b/scene/gui/text_edit.compat.inc
new file mode 100644
index 0000000000..bf73229868
--- /dev/null
+++ b/scene/gui/text_edit.compat.inc
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* text_edit.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+void TextEdit::_set_selection_mode_compat_86978(SelectionMode p_mode, int p_line, int p_column, int p_caret) {
+ set_selection_mode(p_mode);
+}
+
+void TextEdit::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::_set_selection_mode_compat_86978, DEFVAL(-1), DEFVAL(-1), DEFVAL(0));
+}
+
+#endif
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 38b4ffc8ae..49cfa8a030 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "text_edit.h"
+#include "text_edit.compat.inc"
#include "core/config/project_settings.h"
#include "core/input/input.h"
@@ -451,7 +452,7 @@ void TextEdit::_notification(int p_what) {
callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
}
if (text_changed_dirty) {
- callable_mp(this, &TextEdit::_text_changed_emit).call_deferred();
+ callable_mp(this, &TextEdit::_emit_text_changed).call_deferred();
}
_update_wrap_at_column(true);
} break;
@@ -565,9 +566,9 @@ void TextEdit::_notification(int p_what) {
Vector<BraceMatchingData> brace_matching;
if (highlight_matching_braces_enabled) {
- brace_matching.resize(carets.size());
+ brace_matching.resize(get_caret_count());
- for (int caret = 0; caret < carets.size(); caret++) {
+ for (int caret = 0; caret < get_caret_count(); caret++) {
if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) {
continue;
}
@@ -1104,7 +1105,7 @@ void TextEdit::_notification(int p_what) {
// Draw selections.
float char_w = theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
- for (int c = 0; c < carets.size(); c++) {
+ for (int c = 0; c < get_caret_count(); c++) {
if (!clipped && has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) {
int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c);
int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c);
@@ -1257,7 +1258,7 @@ void TextEdit::_notification(int p_what) {
}
Color gl_color = current_color;
- for (int c = 0; c < carets.size(); c++) {
+ for (int c = 0; c < get_caret_count(); c++) {
if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection
int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c);
int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c);
@@ -1271,7 +1272,7 @@ void TextEdit::_notification(int p_what) {
float char_pos = char_ofs + char_margin + ofs_x;
if (char_pos >= xmargin_beg) {
if (highlight_matching_braces_enabled) {
- for (int c = 0; c < carets.size(); c++) {
+ for (int c = 0; c < get_caret_count(); c++) {
if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) ||
(get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) {
if (brace_matching[c].open_mismatch) {
@@ -1562,10 +1563,15 @@ void TextEdit::_notification(int p_what) {
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
if (has_focus()) {
+ bool had_ime_text = has_ime_text();
ime_text = DisplayServer::get_singleton()->ime_get_text();
ime_selection = DisplayServer::get_singleton()->ime_get_selection();
- if (!ime_text.is_empty() && has_selection()) {
+ if (!had_ime_text && has_ime_text()) {
+ _cancel_drag_and_drop_text();
+ }
+
+ if (has_ime_text() && has_selection()) {
delete_selection();
}
@@ -1576,7 +1582,7 @@ void TextEdit::_notification(int p_what) {
} break;
case NOTIFICATION_DRAG_BEGIN: {
- selecting_mode = SelectionMode::SELECTION_MODE_NONE;
+ set_selection_mode(SelectionMode::SELECTION_MODE_NONE);
drag_action = true;
dragging_minimap = false;
dragging_selection = false;
@@ -1587,19 +1593,31 @@ void TextEdit::_notification(int p_what) {
case NOTIFICATION_DRAG_END: {
if (is_drag_successful()) {
if (selection_drag_attempt) {
- selection_drag_attempt = false;
+ // Dropped elsewhere.
if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
delete_selection();
} else if (deselect_on_focus_loss_enabled) {
deselect();
}
}
- } else {
- selection_drag_attempt = false;
}
+ if (drag_caret_index >= 0) {
+ if (drag_caret_index < carets.size()) {
+ remove_caret(drag_caret_index);
+ }
+ drag_caret_index = -1;
+ }
+ selection_drag_attempt = false;
drag_action = false;
drag_caret_force_displayed = false;
} break;
+
+ case NOTIFICATION_MOUSE_EXIT_SELF: {
+ if (drag_caret_force_displayed) {
+ drag_caret_force_displayed = false;
+ queue_redraw();
+ }
+ } break;
}
}
@@ -1702,15 +1720,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
}
+
if (mb->get_button_index() == MouseButton::LEFT) {
_reset_caret_blink_timer();
apply_ime();
Point2i pos = get_line_column_at_pos(mpos);
- int row = pos.y;
+ int line = pos.y;
int col = pos.x;
+ // Gutters.
int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
for (int i = 0; i < gutters.size(); i++) {
if (!gutters[i].draw || gutters[i].width <= 0) {
@@ -1718,14 +1738,14 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) {
- emit_signal(SNAME("gutter_clicked"), row, i);
+ emit_signal(SNAME("gutter_clicked"), line, i);
return;
}
left_margin += gutters[i].width;
}
- // Minimap
+ // Minimap.
if (draw_minimap) {
_update_minimap_click();
if (dragging_minimap) {
@@ -1733,121 +1753,86 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
+ // Update caret.
+
int caret = carets.size() - 1;
int prev_col = get_caret_column(caret);
int prev_line = get_caret_line(caret);
+ int mouse_over_selection_caret = get_selection_at_line_column(line, col, true);
+
const int triple_click_timeout = 600;
const int triple_click_tolerance = 5;
bool is_triple_click = (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance);
if (!mb->is_double_click() && !is_triple_click) {
if (mb->is_alt_pressed()) {
- prev_line = row;
+ prev_line = line;
prev_col = col;
// Remove caret at clicked location.
- if (carets.size() > 1) {
- for (int i = 0; i < carets.size(); i++) {
- // Deselect if clicked on caret or its selection.
- if ((get_caret_column(i) == col && get_caret_line(i) == row) || is_mouse_over_selection(true, i)) {
- remove_caret(i);
- last_dblclk = 0;
- return;
- }
+ if (get_caret_count() > 1) {
+ // Deselect if clicked on caret or its selection.
+ int clicked_caret = get_selection_at_line_column(line, col, true, false);
+ if (clicked_caret != -1) {
+ remove_caret(clicked_caret);
+ last_dblclk = 0;
+ return;
}
}
- if (is_mouse_over_selection()) {
+ if (mouse_over_selection_caret >= 0) {
+ // Did not remove selection under mouse, don't add a new caret.
return;
}
- caret = add_caret(row, col);
+ // Create new caret at clicked location.
+ caret = add_caret(line, col);
if (caret == -1) {
return;
}
- carets.write[caret].selection.selecting_line = row;
- carets.write[caret].selection.selecting_column = col;
-
last_dblclk = 0;
- } else if (!mb->is_shift_pressed() && !is_mouse_over_selection()) {
- caret = 0;
- remove_secondary_carets();
- }
- }
-
- _push_current_op();
- set_caret_line(row, false, true, 0, caret);
- set_caret_column(col, false, caret);
- selection_drag_attempt = false;
-
- if (selecting_enabled && mb->is_shift_pressed() && (get_caret_column(caret) != prev_col || get_caret_line(caret) != prev_line)) {
- if (!has_selection(caret)) {
- carets.write[caret].selection.active = true;
- selecting_mode = SelectionMode::SELECTION_MODE_POINTER;
- carets.write[caret].selection.from_column = prev_col;
- carets.write[caret].selection.from_line = prev_line;
- carets.write[caret].selection.to_column = carets[caret].column;
- carets.write[caret].selection.to_line = carets[caret].line;
-
- if (get_selection_from_line(caret) > get_selection_to_line(caret) || (get_selection_from_line(caret) == get_selection_to_line(caret) && get_selection_from_column(caret) > get_selection_to_column(caret))) {
- SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column);
- SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line);
- carets.write[caret].selection.shiftclick_left = false;
- } else {
- carets.write[caret].selection.shiftclick_left = true;
- }
- carets.write[caret].selection.selecting_line = prev_line;
- carets.write[caret].selection.selecting_column = prev_col;
- caret_index_edit_dirty = true;
- merge_overlapping_carets();
- queue_redraw();
- } else {
- if (carets[caret].line < get_selection_line(caret) || (carets[caret].line == get_selection_line(caret) && carets[caret].column < get_selection_column(caret))) {
- if (carets[caret].selection.shiftclick_left) {
- carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left;
- }
- carets.write[caret].selection.from_column = carets[caret].column;
- carets.write[caret].selection.from_line = carets[caret].line;
-
- } else if (carets[caret].line > get_selection_line(caret) || (carets[caret].line == get_selection_line(caret) && carets[caret].column > get_selection_column(caret))) {
- if (!carets[caret].selection.shiftclick_left) {
- SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column);
- SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line);
- carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left;
- }
- carets.write[caret].selection.to_column = carets[caret].column;
- carets.write[caret].selection.to_line = carets[caret].line;
-
+ } else if (!mb->is_shift_pressed()) {
+ if (drag_and_drop_selection_enabled && mouse_over_selection_caret >= 0) {
+ // Try to drag and drop.
+ set_selection_mode(SelectionMode::SELECTION_MODE_NONE);
+ selection_drag_attempt = true;
+ drag_and_drop_origin_caret_index = mouse_over_selection_caret;
+ last_dblclk = 0;
+ // Don't update caret until we know if it is not drag and drop.
+ return;
} else {
- deselect(caret);
+ // A regular click clears all other carets.
+ caret = 0;
+ remove_secondary_carets();
+ deselect();
}
- caret_index_edit_dirty = true;
- merge_overlapping_carets();
- queue_redraw();
}
- } else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) {
- set_selection_mode(SelectionMode::SELECTION_MODE_NONE, get_selection_line(caret), get_selection_column(caret), caret);
- // We use the main caret for dragging, so reset this one.
- set_caret_line(prev_line, false, true, 0, caret);
- set_caret_column(prev_col, false, caret);
- selection_drag_attempt = true;
- } else if (caret == 0) {
- deselect();
- set_selection_mode(SelectionMode::SELECTION_MODE_POINTER, row, col);
- }
- if (is_triple_click) {
- // Triple-click select line.
- selecting_mode = SelectionMode::SELECTION_MODE_LINE;
+ _push_current_op();
+ set_caret_line(line, false, true, -1, caret);
+ set_caret_column(col, false, caret);
selection_drag_attempt = false;
- _update_selection_mode_line();
+ bool caret_moved = get_caret_column(caret) != prev_col || get_caret_line(caret) != prev_line;
+
+ if (selecting_enabled && mb->is_shift_pressed() && !has_selection(caret) && caret_moved) {
+ // Select from the previous caret position.
+ select(prev_line, prev_col, line, col, caret);
+ }
+
+ // Start regular select mode.
+ set_selection_mode(SelectionMode::SELECTION_MODE_POINTER);
+ _update_selection_mode_pointer(true);
+ } else if (is_triple_click) {
+ // Start triple-click select line mode.
+ set_selection_mode(SelectionMode::SELECTION_MODE_LINE);
+ _update_selection_mode_line(true);
last_dblclk = 0;
- } else if (mb->is_double_click() && text[get_caret_line(caret)].length()) {
- // Double-click select word.
- selecting_mode = SelectionMode::SELECTION_MODE_WORD;
- _update_selection_mode_word();
+ } else if (mb->is_double_click()) {
+ // Start double-click select word mode.
+ set_selection_mode(SelectionMode::SELECTION_MODE_WORD);
+ _update_selection_mode_word(true);
last_dblclk = OS::get_singleton()->get_ticks_msec();
last_dblclk_pos = mb->get_position();
}
@@ -1863,34 +1848,20 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
_push_current_op();
_reset_caret_blink_timer();
apply_ime();
+ _cancel_drag_and_drop_text();
Point2i pos = get_line_column_at_pos(mpos);
- int row = pos.y;
- int col = pos.x;
+ int mouse_line = pos.y;
+ int mouse_column = pos.x;
- bool selection_clicked = false;
if (is_move_caret_on_right_click_enabled()) {
- if (has_selection()) {
- for (int i = 0; i < get_caret_count(); i++) {
- int from_line = get_selection_from_line(i);
- int to_line = get_selection_to_line(i);
- int from_column = get_selection_from_column(i);
- int to_column = get_selection_to_column(i);
-
- if (row >= from_line && row <= to_line && (row != from_line || col >= from_column) && (row != to_line || col <= to_column)) {
- // Right click in one of the selected text
- selection_clicked = true;
- break;
- }
- }
- }
+ bool selection_clicked = get_selection_at_line_column(mouse_line, mouse_column, true) >= 0;
if (!selection_clicked) {
deselect();
remove_secondary_carets();
- set_caret_line(row, false, false);
- set_caret_column(col);
+ set_caret_line(mouse_line, false, false, -1);
+ set_caret_column(mouse_column);
}
- merge_overlapping_carets();
}
if (context_menu_enabled) {
@@ -1908,22 +1879,20 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (mb->get_button_index() == MouseButton::LEFT) {
- if (selection_drag_attempt && is_mouse_over_selection()) {
+ if (!drag_action && selection_drag_attempt && is_mouse_over_selection()) {
+ // This is not a drag and drop attempt, update the caret.
+ selection_drag_attempt = false;
remove_secondary_carets();
+ deselect();
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- set_caret_line(pos.y, false, true, 0, 0);
+ set_caret_line(pos.y, false, true, -1, 0);
set_caret_column(pos.x, true, 0);
-
- deselect();
}
dragging_minimap = false;
dragging_selection = false;
can_drag_minimap = false;
click_select_held->stop();
- if (!drag_action) {
- selection_drag_attempt = false;
- }
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
@@ -1958,7 +1927,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
mpos.x = get_size().x - mpos.x;
}
- if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging.
+ if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && get_viewport()->gui_get_drag_data() == Variant()) {
+ // Update if not in drag and drop.
_reset_caret_blink_timer();
if (draw_minimap && !dragging_selection) {
@@ -2011,10 +1981,19 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) {
apply_ime();
+ // Update drag and drop caret.
drag_caret_force_displayed = true;
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- set_caret_line(pos.y, false, true, 0, 0);
- set_caret_column(pos.x, true, 0);
+
+ if (drag_caret_index == -1) {
+ // Force create a new caret for drag and drop.
+ carets.push_back(Caret());
+ drag_caret_index = carets.size() - 1;
+ }
+
+ drag_caret_force_displayed = true;
+ set_caret_line(pos.y, false, true, -1, drag_caret_index);
+ set_caret_column(pos.x, true, drag_caret_index);
dragging_selection = true;
}
}
@@ -2043,6 +2022,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
+ _cancel_drag_and_drop_text();
+
_reset_caret_blink_timer();
// Allow unicode handling if:
@@ -2321,42 +2302,36 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
- bool first_line = false;
- if (!p_split_current_line) {
- deselect(i);
- if (p_above) {
- if (get_caret_line(i) > 0) {
- set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
- set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
- } else {
- set_caret_column(0, i == 0, i);
- first_line = true;
- }
- } else {
- set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
- }
- }
+ begin_multicaret_edit();
- insert_text_at_caret("\n", i);
-
- if (first_line) {
- set_caret_line(0, i == 0, true, 0, i);
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
+ if (p_split_current_line) {
+ insert_text_at_caret("\n", i);
+ } else {
+ int line = get_caret_line(i);
+ insert_text("\n", line, p_above ? 0 : text[line].length(), p_above, p_above);
+ deselect(i);
+ set_caret_line(p_above ? line : line + 1, false, true, -1, i);
+ set_caret_column(0, i == 0, i);
}
}
+
+ end_multicaret_edit();
end_complex_operation();
}
void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
// Handle selection.
if (p_select) {
_pre_shift_selection(i);
} else if (has_selection(i) && !p_move_by_word) {
// If a selection is active, move caret to start of selection.
- set_caret_line(get_selection_from_line(i), false, true, 0, i);
+ set_caret_line(get_selection_from_line(i), false, true, -1, i);
set_caret_column(get_selection_from_column(i), i == 0, i);
deselect(i);
continue;
@@ -2368,7 +2343,7 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
int cc = get_caret_column(i);
// 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 && get_caret_line(i) > 0) {
- set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
+ set_caret_line(get_caret_line(i) - 1, false, true, -1, i);
set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
@@ -2389,7 +2364,8 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
// 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 (get_caret_column(i) == 0) {
if (get_caret_line(i) > 0) {
- set_caret_line(get_caret_line(i) - get_next_visible_line_offset_from(CLAMP(get_caret_line(i) - 1, 0, text.size() - 1), -1), false, true, 0, i);
+ int new_caret_line = get_caret_line(i) - get_next_visible_line_offset_from(CLAMP(get_caret_line(i) - 1, 0, text.size() - 1), -1);
+ set_caret_line(new_caret_line, false, true, -1, i);
set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
}
} else {
@@ -2400,23 +2376,19 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
}
}
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
// Handle selection.
if (p_select) {
_pre_shift_selection(i);
} else if (has_selection(i) && !p_move_by_word) {
// If a selection is active, move caret to end of selection.
- set_caret_line(get_selection_to_line(i), false, true, 0, i);
+ set_caret_line(get_selection_to_line(i), false, true, -1, i);
set_caret_column(get_selection_to_column(i), i == 0, i);
deselect(i);
continue;
@@ -2428,7 +2400,7 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
int cc = get_caret_column(i);
// 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[get_caret_line(i)].length() && get_caret_line(i) < text.size() - 1) {
- set_caret_line(get_caret_line(i) + 1, false, true, 0, i);
+ set_caret_line(get_caret_line(i) + 1, false, true, -1, i);
set_caret_column(0, i == 0, i);
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
@@ -2449,7 +2421,8 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
// If we are at the end of the line, move the caret to the next line down.
if (get_caret_column(i) == text[get_caret_line(i)].length()) {
if (get_caret_line(i) < text.size() - 1) {
- set_caret_line(get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1), false, false, 0, i);
+ int new_caret_line = get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1);
+ set_caret_line(new_caret_line, false, false, -1, i);
set_caret_column(0, i == 0, i);
}
} else {
@@ -2460,17 +2433,13 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
}
}
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_up(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2490,17 +2459,13 @@ void TextEdit::_move_caret_up(bool p_select) {
set_caret_line(new_line, i == 0, false, 0, i);
}
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_down(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2516,17 +2481,13 @@ void TextEdit::_move_caret_down(bool p_select) {
int new_line = get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1);
set_caret_line(new_line, i == 0, false, 0, i);
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_to_line_start(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2551,17 +2512,13 @@ void TextEdit::_move_caret_to_line_start(bool p_select) {
} else {
set_caret_column(row_start_col, i == 0, i);
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_to_line_end(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2580,17 +2537,13 @@ void TextEdit::_move_caret_to_line_end(bool p_select) {
} else {
set_caret_column(row_end_col, i == 0, i);
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_page_up(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2600,17 +2553,13 @@ void TextEdit::_move_caret_page_up(bool p_select) {
Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), -get_visible_line_count());
int n_line = get_caret_line(i) - next_line.x + 1;
set_caret_line(n_line, i == 0, false, next_line.y, i);
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_page_down(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2620,10 +2569,6 @@ void TextEdit::_move_caret_page_down(bool p_select) {
Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), get_visible_line_count());
int n_line = get_caret_line(i) + next_line.x - 1;
set_caret_line(n_line, i == 0, false, next_line.y, i);
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
@@ -2634,58 +2579,47 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
}
start_action(EditAction::ACTION_BACKSPACE);
- Vector<int> carets_to_remove;
+ begin_multicaret_edit();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = 0; i < caret_edit_order.size(); i++) {
- int caret_idx = caret_edit_order[i];
- if (get_caret_column(caret_idx) == 0 && get_caret_line(caret_idx) == 0 && !has_selection(caret_idx)) {
+ Vector<int> sorted_carets = get_sorted_carets();
+ sorted_carets.reverse();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
+ if (multicaret_edit_ignore_caret(caret_index)) {
continue;
}
- if (has_selection(caret_idx) || (!p_all_to_left && !p_word) || get_caret_column(caret_idx) == 0) {
- backspace(caret_idx);
+ if (get_caret_column(caret_index) == 0 && get_caret_line(caret_index) == 0 && !has_selection(caret_index)) {
continue;
}
- if (p_all_to_left) {
- int caret_current_column = get_caret_column(caret_idx);
- set_caret_column(0, caret_idx == 0, caret_idx);
- _remove_text(get_caret_line(caret_idx), 0, get_caret_line(caret_idx), caret_current_column);
- adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), caret_current_column, get_caret_line(caret_idx), get_caret_column(caret_idx));
-
- // Check for any overlapping carets since we removed the entire line.
- for (int j = i + 1; j < caret_edit_order.size(); j++) {
- // Selection only end on this line, only the one as carets cannot overlap.
- if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx) && get_selection_to_line(caret_edit_order[j]) == get_caret_line(caret_idx)) {
- carets.write[caret_edit_order[j]].selection.to_column = 0;
- break;
- }
-
- // Check for caret.
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) || (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx))) {
- break;
- }
+ if (has_selection(caret_index) || (!p_all_to_left && !p_word) || get_caret_column(caret_index) == 0) {
+ backspace(caret_index);
+ continue;
+ }
- deselect(caret_edit_order[j]);
- carets_to_remove.push_back(caret_edit_order[j]);
- set_caret_column(0, caret_idx == 0, caret_idx);
- i = j;
- }
+ if (p_all_to_left) {
+ // Remove everything to left of caret to the start of the line.
+ int caret_current_column = get_caret_column(caret_index);
+ _remove_text(get_caret_line(caret_index), 0, get_caret_line(caret_index), caret_current_column);
+ collapse_carets(get_caret_line(caret_index), 0, get_caret_line(caret_index), caret_current_column);
+ set_caret_column(0, caret_index == 0, caret_index);
+ _offset_carets_after(get_caret_line(caret_index), caret_current_column, get_caret_line(caret_index), 0);
continue;
}
if (p_word) {
- // Save here as the caret may change when resolving overlaps.
- int from_column = get_caret_column(caret_idx);
- int column = get_caret_column(caret_idx);
+ // Remove text to the start of the word left of the caret.
+ int from_column = get_caret_column(caret_index);
+ int column = get_caret_column(caret_index);
// Check for the case "<word><space><caret>" and ignore the space.
// No need to check for column being 0 since it is checked above.
- if (is_whitespace(text[get_caret_line(caret_idx)][get_caret_column(caret_idx) - 1])) {
+ if (is_whitespace(text[get_caret_line(caret_index)][get_caret_column(caret_index) - 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(get_caret_line(caret_idx))->get_rid());
+ const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_index))->get_rid());
if (words.is_empty() || column <= words[0]) {
// If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line.
column = 0;
@@ -2699,57 +2633,14 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
}
}
- // Check for any other carets in this range.
- int overlapping_caret_index = -1;
- for (int j = i + 1; j < caret_edit_order.size(); j++) {
- // Check caret and selection in on the right line.
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) && (!has_selection(caret_edit_order[j]) || get_selection_to_line(caret_edit_order[j]) != get_caret_line(caret_idx))) {
- break;
- }
-
- // If it has a selection, check it ends with in the range.
- if ((has_selection(caret_edit_order[j]) && get_selection_to_column(caret_edit_order[j]) < column)) {
- break;
- }
-
- // If it has a selection and it starts outside our word, we need to adjust the selection, and handle it later to prevent overlap.
- if ((has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) < column)) {
- carets.write[caret_edit_order[j]].selection.to_column = column;
- overlapping_caret_index = caret_edit_order[j];
- break;
- }
-
- // Otherwise we can remove it.
- if (get_caret_column(caret_edit_order[j]) > column || (has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) > column)) {
- deselect(caret_edit_order[j]);
- carets_to_remove.push_back(caret_edit_order[j]);
- set_caret_column(0, caret_idx == 0, caret_idx);
- i = j;
- }
- }
-
- _remove_text(get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column);
-
- set_caret_line(get_caret_line(caret_idx), false, true, 0, caret_idx);
- set_caret_column(column, caret_idx == 0, caret_idx);
- adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column);
-
- // Now we can clean up the overlapping caret.
- if (overlapping_caret_index != -1) {
- backspace(overlapping_caret_index);
- i++;
- carets_to_remove.push_back(overlapping_caret_index);
- set_caret_column(get_caret_column(overlapping_caret_index), caret_idx == 0, caret_idx);
- }
- continue;
+ _remove_text(get_caret_line(caret_index), column, get_caret_line(caret_index), from_column);
+ collapse_carets(get_caret_line(caret_index), column, get_caret_line(caret_index), from_column);
+ set_caret_column(column, caret_index == 0, caret_index);
+ _offset_carets_after(get_caret_line(caret_index), from_column, get_caret_line(caret_index), column);
}
}
- // Sort and remove backwards to preserve indexes.
- carets_to_remove.sort();
- for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
- remove_caret(carets_to_remove[i]);
- }
+ end_multicaret_edit();
end_action();
}
@@ -2759,61 +2650,40 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
}
start_action(EditAction::ACTION_DELETE);
- Vector<int> carets_to_remove;
+ begin_multicaret_edit();
+
+ Vector<int> sorted_carets = get_sorted_carets();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
+ if (multicaret_edit_ignore_caret(caret_index)) {
+ continue;
+ }
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = 0; i < caret_edit_order.size(); i++) {
- int caret_idx = caret_edit_order[i];
- if (has_selection(caret_idx)) {
- delete_selection(caret_idx);
+ if (has_selection(caret_index)) {
+ delete_selection(caret_index);
continue;
}
- int curline_len = text[get_caret_line(caret_idx)].length();
- if (get_caret_line(caret_idx) == text.size() - 1 && get_caret_column(caret_idx) == curline_len) {
+ int curline_len = text[get_caret_line(caret_index)].length();
+ if (get_caret_line(caret_index) == text.size() - 1 && get_caret_column(caret_index) == curline_len) {
continue; // Last line, last column: Nothing to do.
}
- int next_line = get_caret_column(caret_idx) < curline_len ? get_caret_line(caret_idx) : get_caret_line(caret_idx) + 1;
+ int next_line = get_caret_column(caret_index) < curline_len ? get_caret_line(caret_index) : get_caret_line(caret_index) + 1;
int next_column;
if (p_all_to_right) {
- // Get caret furthest to the left.
- for (int j = i + 1; j < caret_edit_order.size(); j++) {
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
- break;
- }
-
- if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
- break;
- }
-
- if (!has_selection(caret_edit_order[j])) {
- i = j;
- caret_idx = caret_edit_order[i];
- }
- }
-
- if (get_caret_column(caret_idx) == curline_len) {
+ if (get_caret_column(caret_index) == curline_len) {
continue;
}
// Delete everything to right of caret.
next_column = curline_len;
- next_line = get_caret_line(caret_idx);
-
- // Remove overlapping carets.
- for (int j = i - 1; j >= 0; j--) {
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
- break;
- }
- carets_to_remove.push_back(caret_edit_order[j]);
- }
-
- } else if (p_word && get_caret_column(caret_idx) < curline_len - 1) {
+ next_line = get_caret_line(caret_index);
+ } else if (p_word && get_caret_column(caret_index) < curline_len - 1) {
// Delete next word to right of caret.
- int line = get_caret_line(caret_idx);
- int column = get_caret_column(caret_idx);
+ int line = get_caret_line(caret_index);
+ int column = get_caret_column(caret_index);
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
for (int j = 1; j < words.size(); j = j + 2) {
@@ -2825,49 +2695,22 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
next_line = line;
next_column = column;
-
- // Remove overlapping carets.
- for (int j = i - 1; j >= 0; j--) {
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
- break;
- }
-
- if (get_caret_column(caret_edit_order[j]) > column) {
- break;
- }
- carets_to_remove.push_back(caret_edit_order[j]);
- }
} else {
// Delete one character.
if (caret_mid_grapheme_enabled) {
- next_column = get_caret_column(caret_idx) < curline_len ? (get_caret_column(caret_idx) + 1) : 0;
+ next_column = get_caret_column(caret_index) < curline_len ? (get_caret_column(caret_index) + 1) : 0;
} else {
- next_column = get_caret_column(caret_idx) < curline_len ? TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(caret_idx))->get_rid(), (get_caret_column(caret_idx))) : 0;
- }
-
- // Remove overlapping carets.
- if (i > 0) {
- int prev_caret_idx = caret_edit_order[i - 1];
- if (get_caret_line(prev_caret_idx) == next_line && get_caret_column(prev_caret_idx) == next_column) {
- carets_to_remove.push_back(prev_caret_idx);
- }
+ next_column = get_caret_column(caret_index) < curline_len ? TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(caret_index))->get_rid(), (get_caret_column(caret_index))) : 0;
}
}
- _remove_text(get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column);
- adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column);
+ _remove_text(get_caret_line(caret_index), get_caret_column(caret_index), next_line, next_column);
+ collapse_carets(get_caret_line(caret_index), get_caret_column(caret_index), next_line, next_column);
+ _offset_carets_after(next_line, next_column, get_caret_line(caret_index), get_caret_column(caret_index));
}
- // Sort and remove backwards to preserve indexes.
- carets_to_remove.sort();
- for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
- remove_caret(carets_to_remove[i]);
- }
-
- // If we are deleting from the end of a line, due to column preservation we could still overlap with another caret.
- merge_overlapping_carets();
+ end_multicaret_edit();
end_action();
- queue_redraw();
}
void TextEdit::_move_caret_document_start(bool p_select) {
@@ -2878,12 +2721,8 @@ void TextEdit::_move_caret_document_start(bool p_select) {
deselect();
}
- set_caret_line(0, false);
+ set_caret_line(0, false, true, -1);
set_caret_column(0);
-
- if (p_select) {
- _post_shift_selection(0);
- }
}
void TextEdit::_move_caret_document_end(bool p_select) {
@@ -2894,12 +2733,8 @@ void TextEdit::_move_caret_document_end(bool p_select) {
deselect();
}
- set_caret_line(get_last_unhidden_line(), true, false, 9999);
+ set_caret_line(get_last_unhidden_line(), true, false, -1);
set_caret_column(text[get_caret_line()].length());
-
- if (p_select) {
- _post_shift_selection(0);
- }
}
bool TextEdit::_clear_carets_and_selection() {
@@ -2917,51 +2752,6 @@ bool TextEdit::_clear_carets_and_selection() {
return false;
}
-void TextEdit::_get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x) const {
- if (p_last_fit_x == -1) {
- p_last_fit_x = _get_column_x_offset_for_line(p_old_column, p_old_line, p_old_column);
- }
-
- // Calculate the new line and wrap index.
- p_new_line = p_old_line;
- int caret_wrap_index = p_old_wrap_index;
- if (p_below) {
- if (caret_wrap_index < get_line_wrap_count(p_new_line)) {
- caret_wrap_index++;
- } else {
- p_new_line++;
- caret_wrap_index = 0;
- }
- } else {
- if (caret_wrap_index == 0) {
- p_new_line--;
- caret_wrap_index = get_line_wrap_count(p_new_line);
- } else {
- caret_wrap_index--;
- }
- }
-
- // Boundary checks.
- if (p_new_line < 0) {
- p_new_line = 0;
- }
- if (p_new_line >= text.size()) {
- p_new_line = text.size() - 1;
- }
-
- p_new_column = _get_char_pos_for_line(p_last_fit_x, p_new_line, caret_wrap_index);
- if (p_new_column != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && caret_wrap_index < get_line_wrap_count(p_new_line)) {
- Vector<String> rows = get_line_wrapped_text(p_new_line);
- int row_end_col = 0;
- for (int i = 0; i < caret_wrap_index + 1; i++) {
- row_end_col += rows[i].length();
- }
- if (p_new_column >= row_end_col) {
- p_new_column -= 1;
- }
- }
-}
-
void TextEdit::_update_placeholder() {
if (theme_cache.font.is_null() || theme_cache.font_size <= 0) {
return; // Not in tree?
@@ -3127,53 +2917,48 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
if (p_data.get_type() == Variant::STRING && is_editable()) {
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- int caret_row_tmp = pos.y;
- int caret_column_tmp = pos.x;
+ int drop_at_line = pos.y;
+ int drop_at_column = pos.x;
+ int selection_index = get_selection_at_line_column(drop_at_line, drop_at_column, !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL));
+
+ // Remove drag caret before the complex operation starts so it won't appear in undo.
+ remove_caret(drag_caret_index);
+
+ if (selection_drag_attempt && selection_index >= 0 && selection_index == drag_and_drop_origin_caret_index) {
+ // Dropped onto original selection, do nothing.
+ selection_drag_attempt = false;
+ return;
+ }
+
+ begin_complex_operation();
+ begin_multicaret_edit();
if (selection_drag_attempt) {
+ // Drop from self.
selection_drag_attempt = false;
- if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL))) {
- // Set caret back at selection for undo / redo.
- set_caret_line(get_selection_to_line(), false, false);
- set_caret_column(get_selection_to_column());
-
- begin_complex_operation();
- if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
- if (caret_row_tmp > get_selection_to_line()) {
- caret_row_tmp = caret_row_tmp - (get_selection_to_line() - get_selection_from_line());
- } else if (caret_row_tmp == get_selection_to_line() && caret_column_tmp >= get_selection_to_column()) {
- caret_column_tmp = caret_column_tmp - (get_selection_to_column() - get_selection_from_column());
- }
- delete_selection();
- } else {
- deselect();
- }
+ if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
+ // Delete all selections.
+ int temp_caret = add_caret(drop_at_line, drop_at_column);
- remove_secondary_carets();
- set_caret_line(caret_row_tmp, true, false);
- set_caret_column(caret_column_tmp);
- insert_text_at_caret(p_data);
- end_complex_operation();
- }
- } else if (is_mouse_over_selection()) {
- remove_secondary_carets();
- caret_row_tmp = get_selection_from_line();
- caret_column_tmp = get_selection_from_column();
- set_caret_line(caret_row_tmp, true, false);
- set_caret_column(caret_column_tmp);
- insert_text_at_caret(p_data);
- grab_focus();
- } else {
- remove_secondary_carets();
- deselect();
- set_caret_line(caret_row_tmp, true, false);
- set_caret_column(caret_column_tmp);
- insert_text_at_caret(p_data);
- grab_focus();
- }
+ delete_selection();
- if (caret_row_tmp != get_caret_line() || caret_column_tmp != get_caret_column()) {
- select(caret_row_tmp, caret_column_tmp, get_caret_line(), get_caret_column());
+ // Use a temporary caret to update the drop at position.
+ drop_at_line = get_caret_line(temp_caret);
+ drop_at_column = get_caret_column(temp_caret);
+ }
}
+ remove_secondary_carets();
+ deselect();
+
+ // Insert the dragged text.
+ set_caret_line(drop_at_line, true, false, -1);
+ set_caret_column(drop_at_column);
+ insert_text_at_caret(p_data);
+
+ select(drop_at_line, drop_at_column, get_caret_line(), get_caret_column());
+ grab_focus();
+ adjust_viewport_to_caret();
+ end_multicaret_edit();
+ end_complex_operation();
}
}
@@ -3459,7 +3244,7 @@ void TextEdit::_clear() {
clear_undo_history();
text.clear();
remove_secondary_carets();
- set_caret_line(0, false);
+ set_caret_line(0, false, true, -1);
set_caret_column(0);
first_visible_col = 0;
first_visible_line = 0;
@@ -3532,17 +3317,36 @@ void TextEdit::set_line(int p_line, const String &p_new_text) {
return;
}
begin_complex_operation();
- _remove_text(p_line, 0, p_line, text[p_line].length());
- _insert_text(p_line, 0, p_new_text);
- for (int i = 0; i < carets.size(); i++) {
- if (get_caret_line(i) == p_line && get_caret_column(i) > p_new_text.length()) {
- set_caret_column(p_new_text.length(), false, i);
+
+ int old_column = text[p_line].length();
+
+ // Set the affected carets column to update their last offset x.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (_is_line_col_in_range(get_caret_line(i), get_caret_column(i), p_line, 0, p_line, old_column)) {
+ set_caret_column(get_caret_column(i), false, i);
+ }
+ if (has_selection(i) && _is_line_col_in_range(get_selection_origin_line(i), get_selection_origin_column(i), p_line, 0, p_line, old_column)) {
+ set_selection_origin_column(get_selection_origin_column(i), i);
}
+ }
- if (has_selection(i) && p_line == get_selection_to_line(i) && get_selection_to_column(i) > text[p_line].length()) {
- carets.write[i].selection.to_column = text[p_line].length();
+ _remove_text(p_line, 0, p_line, old_column);
+ int new_line, new_column;
+ _insert_text(p_line, 0, p_new_text, &new_line, &new_column);
+
+ // Don't offset carets that were on the old line.
+ _offset_carets_after(p_line, old_column, new_line, new_column, false, false);
+
+ // Set the caret lines to update the column to match visually.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (_is_line_col_in_range(get_caret_line(i), get_caret_column(i), p_line, 0, p_line, old_column)) {
+ set_caret_line(get_caret_line(i), false, true, 0, i);
+ }
+ if (has_selection(i) && _is_line_col_in_range(get_selection_origin_line(i), get_selection_origin_column(i), p_line, 0, p_line, old_column)) {
+ set_selection_origin_line(get_selection_origin_line(i), true, 0, i);
}
}
+ merge_overlapping_carets();
end_complex_operation();
}
@@ -3596,71 +3400,163 @@ void TextEdit::swap_lines(int p_from_line, int p_to_line) {
ERR_FAIL_INDEX(p_from_line, text.size());
ERR_FAIL_INDEX(p_to_line, text.size());
- String tmp = get_line(p_from_line);
- String tmp2 = get_line(p_to_line);
+ if (p_from_line == p_to_line) {
+ return;
+ }
+
+ String from_line_text = get_line(p_from_line);
+ String to_line_text = get_line(p_to_line);
begin_complex_operation();
- set_line(p_to_line, tmp);
- set_line(p_from_line, tmp2);
+ begin_multicaret_edit();
+ // Don't use set_line to avoid clamping and updating carets.
+ _remove_text(p_to_line, 0, p_to_line, text[p_to_line].length());
+ _insert_text(p_to_line, 0, from_line_text);
+ _remove_text(p_from_line, 0, p_from_line, text[p_from_line].length());
+ _insert_text(p_from_line, 0, to_line_text);
+
+ // Swap carets.
+ for (int i = 0; i < get_caret_count(); i++) {
+ bool selected = has_selection(i);
+ if (get_caret_line(i) == p_from_line || get_caret_line(i) == p_to_line) {
+ int caret_new_line = get_caret_line(i) == p_from_line ? p_to_line : p_from_line;
+ int caret_column = get_caret_column(i);
+ set_caret_line(caret_new_line, false, true, -1, i);
+ set_caret_column(caret_column, false, i);
+ }
+ if (selected && (get_selection_origin_line(i) == p_from_line || get_selection_origin_line(i) == p_to_line)) {
+ int origin_new_line = get_selection_origin_line(i) == p_from_line ? p_to_line : p_from_line;
+ int origin_column = get_selection_origin_column(i);
+ select(origin_new_line, origin_column, get_caret_line(i), get_caret_column(i), i);
+ }
+ }
+ // If only part of a selection was changed, it may now overlap.
+ merge_overlapping_carets();
+
+ end_multicaret_edit();
end_complex_operation();
}
-void TextEdit::insert_line_at(int p_at, const String &p_text) {
- ERR_FAIL_INDEX(p_at, text.size());
+void TextEdit::insert_line_at(int p_line, const String &p_text) {
+ ERR_FAIL_INDEX(p_line, text.size());
+
+ // Use a complex operation so subsequent calls aren't merged together.
+ begin_complex_operation();
- _insert_text(p_at, 0, p_text + "\n");
+ int new_line, new_column;
+ _insert_text(p_line, 0, p_text + "\n", &new_line, &new_column);
+ _offset_carets_after(p_line, 0, new_line, new_column);
- for (int i = 0; i < carets.size(); i++) {
- if (get_caret_line(i) >= p_at) {
- // Offset caret when located after inserted line.
- set_caret_line(get_caret_line(i) + 1, false, true, 0, i);
- }
- if (has_selection(i)) {
- if (get_selection_from_line(i) >= p_at) {
- // Offset selection when located after inserted line.
- select(get_selection_from_line(i) + 1, get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i);
- } else if (get_selection_to_line(i) >= p_at) {
- // Extend selection that includes inserted line.
- select(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i);
+ end_complex_operation();
+}
+
+void TextEdit::remove_line_at(int p_line, bool p_move_carets_down) {
+ ERR_FAIL_INDEX(p_line, text.size());
+
+ if (get_line_count() == 1) {
+ // Only one line, just remove contents.
+ begin_complex_operation();
+ int line_length = get_line(p_line).length();
+ _remove_text(p_line, 0, p_line, line_length);
+ collapse_carets(p_line, 0, p_line, line_length, true);
+ end_complex_operation();
+ return;
+ }
+
+ begin_complex_operation();
+
+ bool is_last_line = p_line == get_line_count() - 1;
+ int from_line = is_last_line ? p_line - 1 : p_line;
+ int next_line = is_last_line ? p_line : p_line + 1;
+ int from_column = is_last_line ? get_line(from_line).length() : 0;
+ int next_column = is_last_line ? get_line(next_line).length() : 0;
+
+ if ((!is_last_line && p_move_carets_down) || (p_line != 0 && !p_move_carets_down)) {
+ // Set the carets column to update their last offset x.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (get_caret_line(i) == p_line) {
+ set_caret_column(get_caret_column(i), false, i);
+ }
+ if (has_selection(i) && get_selection_origin_line(i) == p_line) {
+ set_selection_origin_column(get_selection_origin_column(i), i);
}
}
}
- // Need to apply the above adjustments to the undo / redo carets.
- current_op.end_carets = carets;
- queue_redraw();
+ // Remove line.
+ _remove_text(from_line, from_column, next_line, next_column);
+
+ begin_multicaret_edit();
+ if ((is_last_line && p_move_carets_down) || (p_line == 0 && !p_move_carets_down)) {
+ // Collapse carets.
+ collapse_carets(from_line, from_column, next_line, next_column, true);
+ } else {
+ // Move carets to visually line up.
+ int target_line = p_move_carets_down ? p_line : p_line - 1;
+ for (int i = 0; i < get_caret_count(); i++) {
+ bool selected = has_selection(i);
+ if (get_caret_line(i) == p_line) {
+ set_caret_line(target_line, i == 0, true, 0, i);
+ }
+ if (selected && get_selection_origin_line(i) == p_line) {
+ set_selection_origin_line(target_line, true, 0, i);
+ select(get_selection_origin_line(i), get_selection_origin_column(i), get_caret_line(i), get_caret_column(i), i);
+ }
+ }
+
+ merge_overlapping_carets();
+ }
+ _offset_carets_after(next_line, next_column, from_line, from_column);
+ end_multicaret_edit();
+ end_complex_operation();
}
void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_caret != -1 && p_caret != i) {
continue;
}
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
delete_selection(i);
int from_line = get_caret_line(i);
int from_col = get_caret_column(i);
- int new_column, new_line;
+ int new_line, new_column;
_insert_text(from_line, from_col, p_text, &new_line, &new_column);
_update_scrollbars();
+ _offset_carets_after(from_line, from_col, new_line, new_column);
- set_caret_line(new_line, false, true, 0, i);
+ set_caret_line(new_line, false, true, -1, i);
set_caret_column(new_column, i == 0, i);
-
- adjust_carets_after_edit(i, new_line, new_column, from_line, from_col);
}
if (has_ime_text()) {
_update_ime_text();
}
+ end_multicaret_edit();
+ end_complex_operation();
+}
+
+void TextEdit::insert_text(const String &p_text, int p_line, int p_column, bool p_before_selection_begin, bool p_before_selection_end) {
+ ERR_FAIL_INDEX(p_line, text.size());
+ ERR_FAIL_INDEX(p_column, text[p_line].length() + 1);
+
+ begin_complex_operation();
+
+ int new_line, new_column;
+ _insert_text(p_line, p_column, p_text, &new_line, &new_column);
+
+ _offset_carets_after(p_line, p_column, new_line, new_column, p_before_selection_begin, p_before_selection_end);
+
end_complex_operation();
- queue_redraw();
}
void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) {
@@ -3671,7 +3567,13 @@ void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, in
ERR_FAIL_COND(p_to_line < p_from_line);
ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column);
+ begin_complex_operation();
+
_remove_text(p_from_line, p_from_column, p_to_line, p_to_column);
+ collapse_carets(p_from_line, p_from_column, p_to_line, p_to_column);
+ _offset_carets_after(p_to_line, p_to_column, p_from_line, p_from_column);
+
+ end_complex_operation();
}
int TextEdit::get_last_unhidden_line() const {
@@ -4040,7 +3942,7 @@ void TextEdit::undo() {
_push_current_op();
if (undo_stack_pos == nullptr) {
- if (!undo_stack.size()) {
+ if (undo_stack.is_empty()) {
return; // Nothing to undo.
}
@@ -4059,6 +3961,7 @@ void TextEdit::undo() {
current_op.version = op.prev_version;
if (undo_stack_pos->get().chain_backward) {
+ // This was part of a complex operation, undo until the chain forward at the start of the complex operation.
while (true) {
ERR_BREAK(!undo_stack_pos->prev());
undo_stack_pos = undo_stack_pos->prev();
@@ -4072,9 +3975,9 @@ void TextEdit::undo() {
}
_update_scrollbars();
- bool dirty_carets = carets.size() != undo_stack_pos->get().start_carets.size();
+ bool dirty_carets = get_caret_count() != undo_stack_pos->get().start_carets.size();
if (!dirty_carets) {
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (carets[i].line != undo_stack_pos->get().start_carets[i].line || carets[i].column != undo_stack_pos->get().start_carets[i].column) {
dirty_carets = true;
break;
@@ -4084,11 +3987,11 @@ void TextEdit::undo() {
carets = undo_stack_pos->get().start_carets;
- if (dirty_carets && !caret_pos_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
- }
- caret_pos_dirty = true;
+ _unhide_carets();
+
+ if (dirty_carets) {
+ _caret_changed();
+ _selection_changed();
}
adjust_viewport_to_caret();
}
@@ -4113,6 +4016,7 @@ void TextEdit::redo() {
_do_text_op(op, false);
current_op.version = op.version;
if (undo_stack_pos->get().chain_forward) {
+ // This was part of a complex operation, redo until the chain backward at the end of the complex operation.
while (true) {
ERR_BREAK(!undo_stack_pos->next());
undo_stack_pos = undo_stack_pos->next();
@@ -4126,9 +4030,9 @@ void TextEdit::redo() {
}
_update_scrollbars();
- bool dirty_carets = carets.size() != undo_stack_pos->get().end_carets.size();
+ bool dirty_carets = get_caret_count() != undo_stack_pos->get().end_carets.size();
if (!dirty_carets) {
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (carets[i].line != undo_stack_pos->get().end_carets[i].line || carets[i].column != undo_stack_pos->get().end_carets[i].column) {
dirty_carets = true;
break;
@@ -4139,11 +4043,11 @@ void TextEdit::redo() {
carets = undo_stack_pos->get().end_carets;
undo_stack_pos = undo_stack_pos->next();
- if (dirty_carets && !caret_pos_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
- }
- caret_pos_dirty = true;
+ _unhide_carets();
+
+ if (dirty_carets) {
+ _caret_changed();
+ _selection_changed();
}
adjust_viewport_to_caret();
}
@@ -4358,13 +4262,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_
}
}
- if (row < 0) {
- row = 0;
- }
-
- if (row >= text.size()) {
- row = text.size() - 1;
- }
+ row = CLAMP(row, 0, text.size() - 1);
int visible_lines = get_visible_line_count_in_range(first_vis_line, row);
if (rows > visible_lines) {
@@ -4510,29 +4408,13 @@ bool TextEdit::is_dragging_cursor() const {
}
bool TextEdit::is_mouse_over_selection(bool p_edges, int p_caret) const {
- for (int i = 0; i < carets.size(); i++) {
- if (p_caret != -1 && p_caret != i) {
- continue;
- }
-
- if (!has_selection(i)) {
- continue;
- }
-
- Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- int row = pos.y;
- int col = pos.x;
- if (p_edges) {
- if ((row == get_selection_from_line(i) && col == get_selection_from_column(i)) || (row == get_selection_to_line(i) && col == get_selection_to_column(i))) {
- return true;
- }
- }
+ Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
+ int line = pos.y;
+ int column = pos.x;
- if (row >= get_selection_from_line(i) && row <= get_selection_to_line(i) && (row > get_selection_from_line(i) || col > get_selection_from_column(i)) && (row < get_selection_to_line(i) || col < get_selection_to_column(i))) {
- return true;
- }
+ if ((p_caret == -1 && get_selection_at_line_column(line, column, p_edges) != -1) || (p_caret != -1 && _selection_contains(p_caret, line, column, p_edges))) {
+ return true;
}
-
return false;
}
@@ -4612,6 +4494,9 @@ void TextEdit::set_multiple_carets_enabled(bool p_enabled) {
multi_carets_enabled = p_enabled;
if (!multi_carets_enabled) {
remove_secondary_carets();
+ multicaret_edit_count = 0;
+ multicaret_edit_ignore_carets.clear();
+ multicaret_edit_merge_queued = false;
}
}
@@ -4619,270 +4504,411 @@ bool TextEdit::is_multiple_carets_enabled() const {
return multi_carets_enabled;
}
-int TextEdit::add_caret(int p_line, int p_col) {
+int TextEdit::add_caret(int p_line, int p_column) {
if (!multi_carets_enabled) {
return -1;
}
+ _cancel_drag_and_drop_text();
p_line = CLAMP(p_line, 0, text.size() - 1);
- p_col = CLAMP(p_col, 0, get_line(p_line).length());
+ p_column = CLAMP(p_column, 0, get_line(p_line).length());
- for (int i = 0; i < carets.size(); i++) {
- if (get_caret_line(i) == p_line && get_caret_column(i) == p_col) {
+ if (!is_in_mulitcaret_edit()) {
+ // Carets cannot overlap.
+ if (get_selection_at_line_column(p_line, p_column, true, false) != -1) {
return -1;
}
-
- if (has_selection(i)) {
- if (p_line >= get_selection_from_line(i) && p_line <= get_selection_to_line(i) && (p_line > get_selection_from_line(i) || p_col >= get_selection_from_column(i)) && (p_line < get_selection_to_line(i) || p_col <= get_selection_to_column(i))) {
- return -1;
- }
- }
}
carets.push_back(Caret());
- set_caret_line(p_line, false, false, 0, carets.size() - 1);
- set_caret_column(p_col, false, carets.size() - 1);
- caret_index_edit_dirty = true;
- return carets.size() - 1;
+ int new_index = carets.size() - 1;
+ set_caret_line(p_line, false, false, -1, new_index);
+ set_caret_column(p_column, false, new_index);
+ _caret_changed(new_index);
+
+ if (is_in_mulitcaret_edit()) {
+ multicaret_edit_ignore_carets.insert(new_index);
+ merge_overlapping_carets();
+ }
+ return new_index;
}
void TextEdit::remove_caret(int p_caret) {
ERR_FAIL_COND_MSG(carets.size() <= 1, "The main caret should not be removed.");
ERR_FAIL_INDEX(p_caret, carets.size());
+
+ _caret_changed(p_caret);
carets.remove_at(p_caret);
- caret_index_edit_dirty = true;
+
+ if (drag_caret_index >= 0) {
+ if (p_caret == drag_caret_index) {
+ drag_caret_index = -1;
+ } else if (p_caret < drag_caret_index) {
+ drag_caret_index -= 1;
+ }
+ }
}
void TextEdit::remove_secondary_carets() {
+ if (carets.size() == 1) {
+ return;
+ }
+
+ _caret_changed();
carets.resize(1);
- caret_index_edit_dirty = true;
- queue_redraw();
+
+ if (drag_caret_index >= 0) {
+ drag_caret_index = -1;
+ }
}
-void TextEdit::merge_overlapping_carets() {
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = 0; i < caret_edit_order.size() - 1; i++) {
- int first_caret = caret_edit_order[i];
- int second_caret = caret_edit_order[i + 1];
+int TextEdit::get_caret_count() const {
+ // Don't include drag caret.
+ if (drag_caret_index >= 0) {
+ return carets.size() - 1;
+ }
+ return carets.size();
+}
- // Both have selection.
- if (has_selection(first_caret) && has_selection(second_caret)) {
- bool should_merge = false;
- if (get_selection_from_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_from_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_from_line(first_caret) > get_selection_from_line(second_caret) || get_selection_from_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_from_line(first_caret) < get_selection_to_line(second_caret) || get_selection_from_column(first_caret) <= get_selection_to_column(second_caret))) {
- should_merge = true;
- }
+void TextEdit::add_caret_at_carets(bool p_below) {
+ if (!multi_carets_enabled) {
+ return;
+ }
+ const int last_line_max_wrap = get_line_wrap_count(text.size() - 1);
+
+ begin_multicaret_edit();
+ int view_target_caret = -1;
+ int view_line = p_below ? -1 : INT_MAX;
+ int num_carets = get_caret_count();
+ for (int i = 0; i < num_carets; i++) {
+ const int caret_line = get_caret_line(i);
+ const int caret_column = get_caret_column(i);
+ const bool is_selected = has_selection(i) || carets[i].last_fit_x != carets[i].selection.origin_last_fit_x;
+ const int selection_origin_line = get_selection_origin_line(i);
+ const int selection_origin_column = get_selection_origin_column(i);
+ const int caret_wrap_index = get_caret_wrap_index(i);
+ const int selection_origin_wrap_index = !is_selected ? -1 : get_line_wrap_index_at_column(selection_origin_line, selection_origin_column);
+
+ if (caret_line == 0 && !p_below && (caret_wrap_index == 0 || selection_origin_wrap_index == 0)) {
+ // Can't add above the first line.
+ continue;
+ }
+ if (caret_line == text.size() - 1 && p_below && (caret_wrap_index == last_line_max_wrap || selection_origin_wrap_index == last_line_max_wrap)) {
+ // Can't add below the last line.
+ continue;
+ }
- if (get_selection_to_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_to_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_to_line(first_caret) > get_selection_from_line(second_caret) || get_selection_to_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_to_line(first_caret) < get_selection_to_line(second_caret) || get_selection_to_column(first_caret) <= get_selection_to_column(second_caret))) {
- should_merge = true;
- }
+ // Add a new caret.
+ int new_caret_index = add_caret(caret_line, caret_column);
+ ERR_FAIL_COND_MSG(new_caret_index < 0, "Failed to add a caret.");
- if (!should_merge) {
- continue;
- }
+ // Copy the selection origin and last fit.
+ set_selection_origin_line(selection_origin_line, true, -1, new_caret_index);
+ set_selection_origin_column(selection_origin_column, new_caret_index);
+ carets.write[new_caret_index].last_fit_x = carets[i].last_fit_x;
+ carets.write[new_caret_index].selection.origin_last_fit_x = carets[i].selection.origin_last_fit_x;
- // Save the newest one for Click + Drag.
- int caret_to_save = first_caret;
- int caret_to_remove = second_caret;
- if (first_caret < second_caret) {
- caret_to_save = second_caret;
- caret_to_remove = first_caret;
+ // Move the caret up or down one visible line.
+ if (!p_below) {
+ // Move caret up.
+ if (caret_wrap_index > 0) {
+ set_caret_line(caret_line, false, false, caret_wrap_index - 1, new_caret_index);
+ } else {
+ int new_line = caret_line - get_next_visible_line_offset_from(caret_line - 1, -1);
+ if (is_line_wrapped(new_line)) {
+ set_caret_line(new_line, false, false, get_line_wrap_count(new_line), new_caret_index);
+ } else {
+ set_caret_line(new_line, false, false, 0, new_caret_index);
+ }
}
-
- int from_line = MIN(get_selection_from_line(caret_to_save), get_selection_from_line(caret_to_remove));
- int to_line = MAX(get_selection_to_line(caret_to_save), get_selection_to_line(caret_to_remove));
- int from_col = get_selection_from_column(caret_to_save);
- int to_col = get_selection_to_column(caret_to_save);
- int selection_line = get_selection_line(caret_to_save);
- int selection_col = get_selection_column(caret_to_save);
-
- bool at_from = (get_caret_line(caret_to_save) == get_selection_from_line(caret_to_save) && get_caret_column(caret_to_save) == get_selection_from_column(caret_to_save));
-
- if (at_from) {
- if (get_selection_line(caret_to_remove) > get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) >= get_selection_column(caret_to_save))) {
- selection_line = get_selection_line(caret_to_remove);
- selection_col = get_selection_column(caret_to_remove);
+ // Move selection origin up.
+ if (is_selected) {
+ if (selection_origin_wrap_index > 0) {
+ set_selection_origin_line(caret_line, false, selection_origin_wrap_index - 1, new_caret_index);
+ } else {
+ int new_line = selection_origin_line - get_next_visible_line_offset_from(selection_origin_line - 1, -1);
+ if (is_line_wrapped(new_line)) {
+ set_selection_origin_line(new_line, false, get_line_wrap_count(new_line), new_caret_index);
+ } else {
+ set_selection_origin_line(new_line, false, 0, new_caret_index);
+ }
}
- } else if (get_selection_line(caret_to_remove) < get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) <= get_selection_column(caret_to_save))) {
- selection_line = get_selection_line(caret_to_remove);
- selection_col = get_selection_column(caret_to_remove);
}
-
- if (get_selection_from_line(caret_to_remove) < get_selection_from_line(caret_to_save) || (get_selection_from_line(caret_to_remove) == get_selection_from_line(caret_to_save) && get_selection_from_column(caret_to_remove) <= get_selection_from_column(caret_to_save))) {
- from_col = get_selection_from_column(caret_to_remove);
+ if (get_caret_line(new_caret_index) < view_line) {
+ view_line = get_caret_line(new_caret_index);
+ view_target_caret = new_caret_index;
+ }
+ } else {
+ // Move caret down.
+ if (caret_wrap_index < get_line_wrap_count(caret_line)) {
+ set_caret_line(caret_line, false, false, caret_wrap_index + 1, new_caret_index);
} else {
- to_col = get_selection_to_column(caret_to_remove);
+ int new_line = caret_line + get_next_visible_line_offset_from(CLAMP(caret_line + 1, 0, text.size() - 1), 1);
+ set_caret_line(new_line, false, false, 0, new_caret_index);
+ }
+ // Move selection origin down.
+ if (is_selected) {
+ if (selection_origin_wrap_index < get_line_wrap_count(selection_origin_line)) {
+ set_selection_origin_line(selection_origin_line, false, selection_origin_wrap_index + 1, new_caret_index);
+ } else {
+ int new_line = selection_origin_line + get_next_visible_line_offset_from(CLAMP(selection_origin_line + 1, 0, text.size() - 1), 1);
+ set_selection_origin_line(new_line, false, 0, new_caret_index);
+ }
}
+ if (get_caret_line(new_caret_index) > view_line) {
+ view_line = get_caret_line(new_caret_index);
+ view_target_caret = new_caret_index;
+ }
+ }
+ if (is_selected) {
+ // Make sure selection is active.
+ select(get_selection_origin_line(new_caret_index), get_selection_origin_column(new_caret_index), get_caret_line(new_caret_index), get_caret_column(new_caret_index), new_caret_index);
+ carets.write[new_caret_index].last_fit_x = carets[i].last_fit_x;
+ carets.write[new_caret_index].selection.origin_last_fit_x = carets[i].selection.origin_last_fit_x;
+ }
- select(from_line, from_col, to_line, to_col, caret_to_save);
- set_selection_mode(selecting_mode, selection_line, selection_col, caret_to_save);
- set_caret_line((at_from ? from_line : to_line), caret_to_save == 0, true, 0, caret_to_save);
- set_caret_column((at_from ? from_col : to_col), caret_to_save == 0, caret_to_save);
- remove_caret(caret_to_remove);
- i--;
- caret_edit_order = get_caret_index_edit_order();
- continue;
+ bool check_edges = !has_selection(0) || !has_selection(new_caret_index);
+ bool will_merge_with_main_caret = _selection_contains(0, get_caret_line(new_caret_index), get_caret_column(new_caret_index), check_edges, false) || _selection_contains(new_caret_index, get_caret_line(0), get_caret_column(0), check_edges, false);
+ if (will_merge_with_main_caret) {
+ // Move next to the main caret so it stays the main caret after merging.
+ Caret new_caret = carets[new_caret_index];
+ carets.remove_at(new_caret_index);
+ carets.insert(0, new_caret);
+ i++;
}
+ }
- // Only first has selection.
- if (has_selection(first_caret)) {
- if (get_caret_line(second_caret) >= get_selection_from_line(first_caret) && get_caret_line(second_caret) <= get_selection_to_line(first_caret) && (get_caret_line(second_caret) > get_selection_from_line(first_caret) || get_caret_column(second_caret) >= get_selection_from_column(first_caret)) && (get_caret_line(second_caret) < get_selection_to_line(first_caret) || get_caret_column(second_caret) <= get_selection_to_column(first_caret))) {
- remove_caret(second_caret);
- caret_edit_order = get_caret_index_edit_order();
- i--;
- }
- continue;
+ // Show the topmost caret if added above or bottommost caret if added below.
+ if (view_target_caret >= 0 && view_target_caret < get_caret_count()) {
+ adjust_viewport_to_caret(view_target_caret);
+ }
+
+ merge_overlapping_carets();
+ end_multicaret_edit();
+}
+
+struct _CaretSortComparator {
+ _FORCE_INLINE_ bool operator()(const Vector3i &a, const Vector3i &b) const {
+ // x is column, y is line, z is caret index.
+ if (a.y == b.y) {
+ return a.x < b.x;
}
+ return a.y < b.y;
+ }
+};
- // Only second has selection.
- if (has_selection(second_caret)) {
- if (get_caret_line(first_caret) >= get_selection_from_line(second_caret) && get_caret_line(first_caret) <= get_selection_to_line(second_caret) && (get_caret_line(first_caret) > get_selection_from_line(second_caret) || get_caret_column(first_caret) >= get_selection_from_column(second_caret)) && (get_caret_line(first_caret) < get_selection_to_line(second_caret) || get_caret_column(first_caret) <= get_selection_to_column(second_caret))) {
- remove_caret(first_caret);
- caret_edit_order = get_caret_index_edit_order();
- i--;
- }
+Vector<int> TextEdit::get_sorted_carets(bool p_include_ignored_carets) const {
+ // Returns caret indexes sorted by selection start or caret position from top to bottom of text.
+ Vector<Vector3i> caret_line_col_indexes;
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (!p_include_ignored_carets && multicaret_edit_ignore_caret(i)) {
continue;
}
+ caret_line_col_indexes.push_back(Vector3i(get_selection_from_column(i), get_selection_from_line(i), i));
+ }
+ caret_line_col_indexes.sort_custom<_CaretSortComparator>();
+ Vector<int> sorted;
+ sorted.resize(caret_line_col_indexes.size());
+ for (int i = 0; i < caret_line_col_indexes.size(); i++) {
+ sorted.set(i, caret_line_col_indexes[i].z);
+ }
+ return sorted;
+}
- // Both have no selection.
- if (get_caret_line(first_caret) == get_caret_line(second_caret) && get_caret_column(first_caret) == get_caret_column(second_caret)) {
- // Save the newest one for Click + Drag.
- if (first_caret < second_caret) {
- remove_caret(first_caret);
- } else {
- remove_caret(second_caret);
+void TextEdit::collapse_carets(int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_inclusive) {
+ // Collapse carets in the selected range to the from position.
+
+ // Clamp the collapse target position.
+ int collapse_line = CLAMP(p_from_line, 0, text.size() - 1);
+ int collapse_column = CLAMP(p_from_column, 0, text[collapse_line].length());
+
+ // Swap the lines if they are in the wrong order.
+ if (p_from_line > p_to_line) {
+ SWAP(p_from_line, p_to_line);
+ SWAP(p_from_column, p_to_column);
+ }
+ if (p_from_line == p_to_line && p_from_column > p_to_column) {
+ SWAP(p_from_column, p_to_column);
+ }
+ bool any_collapsed = false;
+
+ // Intentionally includes carets in the multicaret_edit_ignore list so that they are moved together.
+ for (int i = 0; i < get_caret_count(); i++) {
+ bool is_caret_in = _is_line_col_in_range(get_caret_line(i), get_caret_column(i), p_from_line, p_from_column, p_to_line, p_to_column, p_inclusive);
+ if (!has_selection(i)) {
+ if (is_caret_in) {
+ // Caret was in the collapsed area.
+ set_caret_line(collapse_line, false, true, -1, i);
+ set_caret_column(collapse_column, false, i);
+ if (is_in_mulitcaret_edit() && get_caret_count() > 1) {
+ multicaret_edit_ignore_carets.insert(i);
+ }
+ any_collapsed = true;
+ }
+ } else {
+ bool is_origin_in = _is_line_col_in_range(get_selection_origin_line(i), get_selection_origin_column(i), p_from_line, p_from_column, p_to_line, p_to_column, p_inclusive);
+
+ if (is_caret_in && is_origin_in) {
+ // Selection was completely encapsulated.
+ deselect(i);
+ set_caret_line(collapse_line, false, true, -1, i);
+ set_caret_column(collapse_column, false, i);
+ if (is_in_mulitcaret_edit() && get_caret_count() > 1) {
+ multicaret_edit_ignore_carets.insert(i);
+ }
+ any_collapsed = true;
+ } else if (is_caret_in) {
+ // Only caret was inside.
+ set_caret_line(collapse_line, false, true, -1, i);
+ set_caret_column(collapse_column, false, i);
+ any_collapsed = true;
+ } else if (is_origin_in) {
+ // Only selection origin was inside.
+ set_selection_origin_line(collapse_line, true, -1, i);
+ set_selection_origin_column(collapse_column, i);
+ any_collapsed = true;
+ }
+ }
+ if (!p_inclusive && !any_collapsed) {
+ if ((get_caret_line(i) == collapse_line && get_caret_column(i) == collapse_column) || (get_selection_origin_line(i) == collapse_line && get_selection_origin_column(i) == collapse_column)) {
+ // Make sure to queue a merge, even if we didn't include it.
+ any_collapsed = true;
}
- i--;
- caret_edit_order = get_caret_index_edit_order();
- continue;
}
}
+ if (any_collapsed) {
+ merge_overlapping_carets();
+ }
}
-int TextEdit::get_caret_count() const {
- return carets.size();
-}
+void TextEdit::merge_overlapping_carets() {
+ if (is_in_mulitcaret_edit()) {
+ // Queue merge to be performed the end of the multicaret edit.
+ multicaret_edit_merge_queued = true;
+ return;
+ }
-void TextEdit::add_caret_at_carets(bool p_below) {
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &caret_index : caret_edit_order) {
- const int caret_line = get_caret_line(caret_index);
- const int caret_column = get_caret_column(caret_index);
-
- // The last fit x will be cleared if the caret has a selection,
- // but if it does not have a selection the last fit x will be
- // transferred to the new caret.
- int caret_from_column = 0, caret_to_column = 0, caret_last_fit_x = carets[caret_index].last_fit_x;
- if (has_selection(caret_index)) {
- // If the selection goes over multiple lines, deselect it.
- if (get_selection_from_line(caret_index) != get_selection_to_line(caret_index)) {
- deselect(caret_index);
+ multicaret_edit_merge_queued = false;
+ multicaret_edit_ignore_carets.clear();
+
+ if (get_caret_count() == 1) {
+ return;
+ }
+
+ Vector<int> sorted_carets = get_sorted_carets(true);
+ for (int i = 0; i < sorted_carets.size() - 1; i++) {
+ int first_caret = sorted_carets[i];
+ int second_caret = sorted_carets[i + 1];
+
+ bool merge_carets;
+ if (!has_selection(first_caret) || !has_selection(second_caret)) {
+ // Merge if touching.
+ merge_carets = get_selection_from_line(second_caret) < get_selection_to_line(first_caret) || (get_selection_from_line(second_caret) == get_selection_to_line(first_caret) && get_selection_from_column(second_caret) <= get_selection_to_column(first_caret));
+ } else {
+ // Merge two selections if overlapping.
+ merge_carets = get_selection_from_line(second_caret) < get_selection_to_line(first_caret) || (get_selection_from_line(second_caret) == get_selection_to_line(first_caret) && get_selection_from_column(second_caret) < get_selection_to_column(first_caret));
+ }
+
+ if (!merge_carets) {
+ continue;
+ }
+
+ // Save the newest one for Click + Drag.
+ int caret_to_save = first_caret;
+ int caret_to_remove = second_caret;
+ if (first_caret < second_caret) {
+ caret_to_save = second_caret;
+ caret_to_remove = first_caret;
+ }
+
+ if (get_selection_from_line(caret_to_save) != get_selection_from_line(caret_to_remove) || get_selection_to_line(caret_to_save) != get_selection_to_line(caret_to_remove) || get_selection_from_column(caret_to_save) != get_selection_from_column(caret_to_remove) || get_selection_to_column(caret_to_save) != get_selection_to_column(caret_to_remove)) {
+ // Selections are not the same, merge them into one bigger selection.
+ int new_from_line = MIN(get_selection_from_line(caret_to_remove), get_selection_from_line(caret_to_save));
+ int new_to_line = MAX(get_selection_to_line(caret_to_remove), get_selection_to_line(caret_to_save));
+ int new_from_col;
+ int new_to_col;
+ if (get_selection_from_line(caret_to_remove) < get_selection_from_line(caret_to_save)) {
+ new_from_col = get_selection_from_column(caret_to_remove);
+ } else if (get_selection_from_line(caret_to_remove) > get_selection_from_line(caret_to_save)) {
+ new_from_col = get_selection_from_column(caret_to_save);
} else {
- caret_from_column = get_selection_from_column(caret_index);
- caret_to_column = get_selection_to_column(caret_index);
- caret_last_fit_x = -1;
- carets.write[caret_index].last_fit_x = _get_column_x_offset_for_line(caret_column, caret_line, caret_column);
+ new_from_col = MIN(get_selection_from_column(caret_to_remove), get_selection_from_column(caret_to_save));
+ }
+ if (get_selection_to_line(caret_to_remove) < get_selection_to_line(caret_to_save)) {
+ new_to_col = get_selection_to_column(caret_to_save);
+ } else if (get_selection_to_line(caret_to_remove) > get_selection_to_line(caret_to_save)) {
+ new_to_col = get_selection_to_column(caret_to_remove);
+ } else {
+ new_to_col = MAX(get_selection_to_column(caret_to_remove), get_selection_to_column(caret_to_save));
}
- }
- // Get the line and column of the new caret as if you would move the caret by pressing the arrow keys.
- int new_caret_line, new_caret_column, new_caret_from_column = 0, new_caret_to_column = 0;
- _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_column, p_below, new_caret_line, new_caret_column, caret_last_fit_x);
+ // Use the direction from the last caret or the saved one.
+ int caret_dir_to_copy;
+ if (has_selection(caret_to_remove) && has_selection(caret_to_save)) {
+ caret_dir_to_copy = caret_to_remove == get_caret_count() - 1 ? caret_to_remove : caret_to_save;
+ } else {
+ caret_dir_to_copy = !has_selection(caret_to_remove) ? caret_to_save : caret_to_remove;
+ }
- // If the caret does have a selection calculate the new from and to columns.
- if (caret_from_column != caret_to_column) {
- // We only need to calculate the selection columns if the column of the caret changed.
- if (caret_column != new_caret_column) {
- int _; // Unused placeholder for p_new_line.
- _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_from_column, p_below, _, new_caret_from_column);
- _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_to_column, p_below, _, new_caret_to_column);
+ if (is_caret_after_selection_origin(caret_dir_to_copy)) {
+ select(new_from_line, new_from_col, new_to_line, new_to_col, caret_to_save);
} else {
- new_caret_from_column = caret_from_column;
- new_caret_to_column = caret_to_column;
+ select(new_to_line, new_to_col, new_from_line, new_from_col, caret_to_save);
}
}
- // Add the new caret.
- const int new_caret_index = add_caret(new_caret_line, new_caret_column);
-
- if (new_caret_index == -1) {
- continue;
+ if (caret_to_save == 0) {
+ adjust_viewport_to_caret(caret_to_save);
}
- // Also add the selection if there should be one.
- if (new_caret_from_column != new_caret_to_column) {
- select(new_caret_line, new_caret_from_column, new_caret_line, new_caret_to_column, new_caret_index);
- // Necessary to properly modify the selection after adding the new caret.
- carets.write[new_caret_index].selection.selecting_line = new_caret_line;
- carets.write[new_caret_index].selection.selecting_column = new_caret_column == new_caret_from_column ? new_caret_to_column : new_caret_from_column;
- continue;
+ remove_caret(caret_to_remove);
+
+ // Update the rest of the sorted list.
+ for (int j = i; j < sorted_carets.size(); j++) {
+ if (sorted_carets[j] > caret_to_remove) {
+ // Shift the index since a caret before it was removed.
+ sorted_carets.write[j] -= 1;
+ }
}
+ // Remove the caret from the sorted array.
+ sorted_carets.remove_at(caret_to_remove == first_caret ? i : i + 1);
- // Copy the last fit x over.
- carets.write[new_caret_index].last_fit_x = carets[caret_index].last_fit_x;
+ // Process the caret again, since it and the next caret might also overlap.
+ i--;
}
-
- merge_overlapping_carets();
- queue_redraw();
}
-Vector<int> TextEdit::get_caret_index_edit_order() {
- if (!caret_index_edit_dirty) {
- return caret_index_edit_order;
+// Starts a multicaret edit operation. Call this before iterating over the carets and call [end_multicaret_edit] afterwards.
+void TextEdit::begin_multicaret_edit() {
+ if (!multi_carets_enabled) {
+ return;
}
+ multicaret_edit_count++;
+}
- caret_index_edit_order.clear();
- caret_index_edit_order.push_back(0);
- for (int i = 1; i < carets.size(); i++) {
- int j = 0;
-
- int line = has_selection(i) ? get_selection_to_line(i) : carets[i].line;
- int col = has_selection(i) ? get_selection_to_column(i) : carets[i].column;
+void TextEdit::end_multicaret_edit() {
+ if (!multi_carets_enabled) {
+ return;
+ }
+ if (multicaret_edit_count > 0) {
+ multicaret_edit_count--;
+ }
+ if (multicaret_edit_count != 0) {
+ return;
+ }
- for (; j < caret_index_edit_order.size(); j++) {
- int idx = caret_index_edit_order[j];
- int other_line = has_selection(idx) ? get_selection_to_line(idx) : carets[idx].line;
- int other_col = has_selection(idx) ? get_selection_to_column(idx) : carets[idx].column;
- if (line > other_line || (line == other_line && col > other_col)) {
- break;
- }
- }
- caret_index_edit_order.insert(j, i);
+ // This was the last multicaret edit operation.
+ if (multicaret_edit_merge_queued) {
+ merge_overlapping_carets();
}
- caret_index_edit_dirty = false;
- return caret_index_edit_order;
+ multicaret_edit_ignore_carets.clear();
}
-void TextEdit::adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col) {
- int edit_height = p_from_line - p_to_line;
- int edit_size = ((edit_height == 0) ? p_from_col : 0) - p_to_col;
-
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int j = 0; j < caret_edit_order.size(); j++) {
- if (caret_edit_order[j] == p_caret) {
- return;
- }
-
- // Adjust caret.
- // set_caret_line could adjust the column, so save here.
- int cc = get_caret_column(caret_edit_order[j]);
- if (edit_height != 0) {
- set_caret_line(get_caret_line(caret_edit_order[j]) + edit_height, false, true, 0, caret_edit_order[j]);
- }
- if (get_caret_line(p_caret) == get_caret_line(caret_edit_order[j])) {
- set_caret_column(cc + edit_size, false, caret_edit_order[j]);
- }
+bool TextEdit::is_in_mulitcaret_edit() const {
+ return multicaret_edit_count > 0;
+}
- // Adjust selection.
- if (!has_selection(caret_edit_order[j])) {
- continue;
- }
- if (edit_height != 0) {
- carets.write[caret_edit_order[j]].selection.from_line += edit_height;
- carets.write[caret_edit_order[j]].selection.to_line += edit_height;
- }
- if (get_caret_line(p_caret) == get_selection_from_line(caret_edit_order[j])) {
- carets.write[caret_edit_order[j]].selection.from_column += edit_size;
- }
- }
+bool TextEdit::multicaret_edit_ignore_caret(int p_caret) const {
+ return multicaret_edit_ignore_carets.has(p_caret);
}
bool TextEdit::is_caret_visible(int p_caret) const {
@@ -4902,16 +4928,10 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
}
setting_caret_line = true;
- if (p_line < 0) {
- p_line = 0;
- }
-
- if (p_line >= text.size()) {
- p_line = text.size() - 1;
- }
+ p_line = CLAMP(p_line, 0, text.size() - 1);
if (!p_can_be_hidden) {
- if (_is_line_hidden(CLAMP(p_line, 0, text.size() - 1))) {
+ if (_is_line_hidden(p_line)) {
int move_down = get_next_visible_line_offset_from(p_line, 1) - 1;
if (p_line + move_down <= text.size() - 1 && !_is_line_hidden(p_line + move_down)) {
p_line += move_down;
@@ -4920,7 +4940,7 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
if (p_line - move_up > 0 && !_is_line_hidden(p_line - move_up)) {
p_line -= move_up;
} else {
- WARN_PRINT(("Caret set to hidden line " + itos(p_line) + " and there are no nonhidden lines."));
+ WARN_PRINT("Caret set to hidden line " + itos(p_line) + " and there are no nonhidden lines.");
}
}
}
@@ -4928,31 +4948,36 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
bool caret_moved = get_caret_line(p_caret) != p_line;
carets.write[p_caret].line = p_line;
- int n_col = _get_char_pos_for_line(carets[p_caret].last_fit_x, p_line, p_wrap_index);
- if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) {
- Vector<String> rows = get_line_wrapped_text(p_line);
- int row_end_col = 0;
- for (int i = 0; i < p_wrap_index + 1; i++) {
- row_end_col += rows[i].length();
- }
- if (n_col >= row_end_col) {
- n_col -= 1;
+ int n_col;
+ if (p_wrap_index >= 0) {
+ // Keep caret in same visual x position it was at previously.
+ n_col = _get_char_pos_for_line(carets[p_caret].last_fit_x, p_line, p_wrap_index);
+ if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) {
+ // Offset by one to not go past the end of the wrapped line.
+ if (n_col >= text.get_line_wrap_ranges(p_line)[p_wrap_index].y) {
+ n_col -= 1;
+ }
}
+ } else {
+ // Clamp the column.
+ n_col = MIN(get_caret_column(p_caret), get_line(p_line).length());
}
caret_moved = (caret_moved || get_caret_column(p_caret) != n_col);
carets.write[p_caret].column = n_col;
+ // Unselect if the caret moved to the selection origin.
+ if (p_wrap_index >= 0 && has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) {
+ deselect(p_caret);
+ }
+
if (is_inside_tree() && p_adjust_viewport) {
adjust_viewport_to_caret(p_caret);
}
setting_caret_line = false;
- if (caret_moved && !caret_pos_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
- }
- caret_pos_dirty = true;
+ if (caret_moved) {
+ _caret_changed(p_caret);
}
}
@@ -4961,29 +4986,32 @@ int TextEdit::get_caret_line(int p_caret) const {
return carets[p_caret].line;
}
-void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport, int p_caret) {
+void TextEdit::set_caret_column(int p_column, bool p_adjust_viewport, int p_caret) {
ERR_FAIL_INDEX(p_caret, carets.size());
- if (p_col < 0) {
- p_col = 0;
- }
- if (p_col > get_line(get_caret_line(p_caret)).length()) {
- p_col = get_line(get_caret_line(p_caret)).length();
- }
- bool caret_moved = get_caret_column(p_caret) != p_col;
- carets.write[p_caret].column = p_col;
+ p_column = CLAMP(p_column, 0, get_line(get_caret_line(p_caret)).length());
+
+ bool caret_moved = get_caret_column(p_caret) != p_column;
+ carets.write[p_caret].column = p_column;
carets.write[p_caret].last_fit_x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
+ if (!has_selection(p_caret)) {
+ // Set the selection origin last fit x to be the same, so we can tell if there was a selection.
+ carets.write[p_caret].selection.origin_last_fit_x = carets[p_caret].last_fit_x;
+ }
+
+ // Unselect if the caret moved to the selection origin.
+ if (has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) {
+ deselect(p_caret);
+ }
+
if (is_inside_tree() && p_adjust_viewport) {
adjust_viewport_to_caret(p_caret);
}
- if (caret_moved && !caret_pos_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
- }
- caret_pos_dirty = true;
+ if (caret_moved) {
+ _caret_changed(p_caret);
}
}
@@ -4998,7 +5026,7 @@ int TextEdit::get_caret_wrap_index(int p_caret) const {
}
String TextEdit::get_word_under_caret(int p_caret) const {
- ERR_FAIL_COND_V(p_caret > carets.size(), "");
+ ERR_FAIL_COND_V(p_caret >= carets.size() || p_caret < -1, "");
StringBuilder selected_text;
for (int c = 0; c < carets.size(); c++) {
@@ -5059,20 +5087,8 @@ bool TextEdit::is_drag_and_drop_selection_enabled() const {
return drag_and_drop_selection_enabled;
}
-void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) {
- ERR_FAIL_INDEX(p_caret, carets.size());
-
+void TextEdit::set_selection_mode(SelectionMode p_mode) {
selecting_mode = p_mode;
- if (p_line >= 0) {
- ERR_FAIL_INDEX(p_line, text.size());
- carets.write[p_caret].selection.selecting_line = p_line;
- carets.write[p_caret].selection.selecting_column = CLAMP(carets[p_caret].selection.selecting_column, 0, text[carets[p_caret].selection.selecting_line].length());
- }
- if (p_column >= 0) {
- ERR_FAIL_INDEX(carets[p_caret].selection.selecting_line, text.size());
- ERR_FAIL_INDEX(p_column, text[carets[p_caret].selection.selecting_line].length() + 1);
- carets.write[p_caret].selection.selecting_column = p_column;
- }
}
TextEdit::SelectionMode TextEdit::get_selection_mode() const {
@@ -5090,16 +5106,12 @@ void TextEdit::select_all() {
}
remove_secondary_carets();
+ set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT);
select(0, 0, text.size() - 1, text[text.size() - 1].length());
- set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, 0, 0);
- carets.write[0].selection.shiftclick_left = true;
- set_caret_line(get_selection_to_line(), false);
- set_caret_column(get_selection_to_column(), false);
- queue_redraw();
}
void TextEdit::select_word_under_caret(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= carets.size() || p_caret < -1);
_push_current_op();
if (!selecting_enabled) {
@@ -5140,8 +5152,6 @@ void TextEdit::select_word_under_caret(int p_caret) {
}
select(get_caret_line(c), begin, get_caret_line(c), end, c);
- // Move the caret to the end of the word for easier editing.
- set_caret_column(end, false, c);
}
merge_overlapping_carets();
}
@@ -5234,53 +5244,37 @@ void TextEdit::skip_selection_for_next_occurrence() {
}
}
-void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) {
- ERR_FAIL_INDEX(p_caret, carets.size());
+void TextEdit::select(int p_origin_line, int p_origin_column, int p_caret_line, int p_caret_column, int p_caret) {
+ ERR_FAIL_INDEX(p_caret, get_caret_count());
+
+ p_caret_line = CLAMP(p_caret_line, 0, text.size() - 1);
+ p_caret_column = CLAMP(p_caret_column, 0, text[p_caret_line].length());
+ set_caret_line(p_caret_line, false, true, -1, p_caret);
+ set_caret_column(p_caret_column, false, p_caret);
+
if (!selecting_enabled) {
return;
}
- p_from_line = CLAMP(p_from_line, 0, text.size() - 1);
- p_from_column = CLAMP(p_from_column, 0, text[p_from_line].length());
- p_to_line = CLAMP(p_to_line, 0, text.size() - 1);
- p_to_column = CLAMP(p_to_column, 0, text[p_to_line].length());
-
- carets.write[p_caret].selection.from_line = p_from_line;
- carets.write[p_caret].selection.from_column = p_from_column;
- carets.write[p_caret].selection.to_line = p_to_line;
- carets.write[p_caret].selection.to_column = p_to_column;
-
- carets.write[p_caret].selection.active = true;
-
- if (get_selection_from_line(p_caret) == get_selection_to_line(p_caret)) {
- if (get_selection_from_column(p_caret) == get_selection_to_column(p_caret)) {
- carets.write[p_caret].selection.active = false;
+ p_origin_line = CLAMP(p_origin_line, 0, text.size() - 1);
+ p_origin_column = CLAMP(p_origin_column, 0, text[p_origin_line].length());
+ set_selection_origin_line(p_origin_line, true, -1, p_caret);
+ set_selection_origin_column(p_origin_column, p_caret);
- } else if (get_selection_from_column(p_caret) > get_selection_to_column(p_caret)) {
- carets.write[p_caret].selection.shiftclick_left = false;
- SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column);
- } else {
- carets.write[p_caret].selection.shiftclick_left = true;
- }
- } else if (get_selection_from_line(p_caret) > get_selection_to_line(p_caret)) {
- carets.write[p_caret].selection.shiftclick_left = false;
- SWAP(carets.write[p_caret].selection.from_line, carets.write[p_caret].selection.to_line);
- SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column);
- } else {
- carets.write[p_caret].selection.shiftclick_left = true;
+ bool had_selection = has_selection(p_caret);
+ bool activate = p_origin_line != p_caret_line || p_origin_column != p_caret_column;
+ carets.write[p_caret].selection.active = activate;
+ if (had_selection != activate) {
+ _selection_changed(p_caret);
}
-
- caret_index_edit_dirty = true;
- queue_redraw();
}
bool TextEdit::has_selection(int p_caret) const {
- ERR_FAIL_COND_V(p_caret > carets.size(), false);
+ ERR_FAIL_COND_V(p_caret >= carets.size() || p_caret < -1, false);
+ if (p_caret >= 0) {
+ return carets[p_caret].selection.active;
+ }
for (int i = 0; i < carets.size(); i++) {
- if (p_caret != -1 && p_caret != i) {
- continue;
- }
-
if (carets[i].selection.active) {
return true;
}
@@ -5289,100 +5283,268 @@ bool TextEdit::has_selection(int p_caret) const {
}
String TextEdit::get_selected_text(int p_caret) {
- ERR_FAIL_COND_V(p_caret > carets.size(), "");
+ ERR_FAIL_COND_V(p_caret >= carets.size() || p_caret < -1, "");
- StringBuilder selected_text;
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = caret_edit_order.size() - 1; i >= 0; i--) {
- int caret_idx = caret_edit_order[i];
- if (p_caret != -1 && p_caret != caret_idx) {
- continue;
+ if (p_caret >= 0) {
+ if (!has_selection(p_caret)) {
+ return "";
}
+ return _base_get_text(get_selection_from_line(p_caret), get_selection_from_column(p_caret), get_selection_to_line(p_caret), get_selection_to_column(p_caret));
+ }
- if (!has_selection(caret_idx)) {
+ StringBuilder selected_text;
+ Vector<int> sorted_carets = get_sorted_carets();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
+
+ if (!has_selection(caret_index)) {
continue;
}
- selected_text += _base_get_text(get_selection_from_line(caret_idx), get_selection_from_column(caret_idx), get_selection_to_line(caret_idx), get_selection_to_column(caret_idx));
- if (p_caret == -1 && i != 0) {
+ if (selected_text.get_string_length() != 0) {
selected_text += "\n";
}
+ selected_text += _base_get_text(get_selection_from_line(caret_index), get_selection_from_column(caret_index), get_selection_to_line(caret_index), get_selection_to_column(caret_index));
}
return selected_text.as_string();
}
-int TextEdit::get_selection_line(int p_caret) const {
+int TextEdit::get_selection_at_line_column(int p_line, int p_column, bool p_include_edges, bool p_only_selections) const {
+ // Return the caret index of the found selection, or -1.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (_selection_contains(i, p_line, p_column, p_include_edges, p_only_selections)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+Vector<Point2i> TextEdit::get_line_ranges_from_carets(bool p_only_selections, bool p_merge_adjacent) const {
+ // Get a series of line ranges that cover all lines that have a caret or selection.
+ // For each Point2i range, x is the first line and y is the last line.
+ Vector<Point2i> ret;
+ int last_to_line = INT_MIN;
+
+ Vector<int> sorted_carets = get_sorted_carets();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
+ if (p_only_selections && !has_selection(caret_index)) {
+ continue;
+ }
+ Point2i range = Point2i(get_selection_from_line(caret_index), get_selection_to_line(caret_index));
+ if (has_selection(caret_index) && get_selection_to_column(caret_index) == 0) {
+ // Dont include selection end line if it ends at column 0.
+ range.y--;
+ }
+ if (range.x == last_to_line || (p_merge_adjacent && range.x - 1 == last_to_line)) {
+ // Merge if starts on the same line or adjacent line.
+ ret.write[ret.size() - 1].y = range.y;
+ } else {
+ ret.append(range);
+ }
+ last_to_line = range.y;
+ }
+ return ret;
+}
+
+TypedArray<Vector2i> TextEdit::get_line_ranges_from_carets_typed_array(bool p_only_selections, bool p_merge_adjacent) const {
+ // Wrapper for `get_line_ranges_from_carets` to return a datatype that can be exposed.
+ TypedArray<Vector2i> ret;
+ Vector<Point2i> ranges = get_line_ranges_from_carets(p_only_selections, p_merge_adjacent);
+ for (const Point2i &range : ranges) {
+ ret.push_back(range);
+ }
+ return ret;
+}
+
+void TextEdit::set_selection_origin_line(int p_line, bool p_can_be_hidden, int p_wrap_index, int p_caret) {
+ if (!selecting_enabled) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_caret, carets.size());
+ p_line = CLAMP(p_line, 0, text.size() - 1);
+
+ if (!p_can_be_hidden) {
+ if (_is_line_hidden(p_line)) {
+ int move_down = get_next_visible_line_offset_from(p_line, 1) - 1;
+ if (p_line + move_down <= text.size() - 1 && !_is_line_hidden(p_line + move_down)) {
+ p_line += move_down;
+ } else {
+ int move_up = get_next_visible_line_offset_from(p_line, -1) - 1;
+ if (p_line - move_up > 0 && !_is_line_hidden(p_line - move_up)) {
+ p_line -= move_up;
+ } else {
+ WARN_PRINT("Selection origin set to hidden line " + itos(p_line) + " and there are no nonhidden lines.");
+ }
+ }
+ }
+ }
+
+ bool selection_moved = get_selection_origin_line(p_caret) != p_line;
+ carets.write[p_caret].selection.origin_line = p_line;
+
+ int n_col;
+ if (p_wrap_index >= 0) {
+ // Keep selection origin in same visual x position it was at previously.
+ n_col = _get_char_pos_for_line(carets[p_caret].selection.origin_last_fit_x, p_line, p_wrap_index);
+ if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) {
+ // Offset by one to not go past the end of the wrapped line.
+ if (n_col >= text.get_line_wrap_ranges(p_line)[p_wrap_index].y) {
+ n_col -= 1;
+ }
+ }
+ } else {
+ // Clamp the column.
+ n_col = MIN(get_selection_origin_column(p_caret), get_line(p_line).length());
+ }
+ selection_moved = (selection_moved || get_selection_origin_column(p_caret) != n_col);
+ carets.write[p_caret].selection.origin_column = n_col;
+
+ // Unselect if the selection origin moved to the caret.
+ if (p_wrap_index >= 0 && has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) {
+ deselect(p_caret);
+ }
+
+ if (selection_moved && has_selection(p_caret)) {
+ _selection_changed(p_caret);
+ }
+}
+
+void TextEdit::set_selection_origin_column(int p_column, int p_caret) {
+ if (!selecting_enabled) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_caret, carets.size());
+
+ p_column = CLAMP(p_column, 0, get_line(get_selection_origin_line(p_caret)).length());
+
+ bool selection_moved = get_selection_origin_column(p_caret) != p_column;
+
+ carets.write[p_caret].selection.origin_column = p_column;
+
+ carets.write[p_caret].selection.origin_last_fit_x = _get_column_x_offset_for_line(get_selection_origin_column(p_caret), get_selection_origin_line(p_caret), get_selection_origin_column(p_caret));
+
+ // Unselect if the selection origin moved to the caret.
+ if (has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) {
+ deselect(p_caret);
+ }
+
+ if (selection_moved && has_selection(p_caret)) {
+ _selection_changed(p_caret);
+ }
+}
+
+int TextEdit::get_selection_origin_line(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.selecting_line;
+ return carets[p_caret].selection.origin_line;
}
-int TextEdit::get_selection_column(int p_caret) const {
+int TextEdit::get_selection_origin_column(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.selecting_column;
+ return carets[p_caret].selection.origin_column;
}
int TextEdit::get_selection_from_line(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.from_line;
+ if (!has_selection(p_caret)) {
+ return carets[p_caret].line;
+ }
+ return MIN(carets[p_caret].selection.origin_line, carets[p_caret].line);
}
int TextEdit::get_selection_from_column(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.from_column;
+ if (!has_selection(p_caret)) {
+ return carets[p_caret].column;
+ }
+ if (carets[p_caret].selection.origin_line < carets[p_caret].line) {
+ return carets[p_caret].selection.origin_column;
+ } else if (carets[p_caret].selection.origin_line > carets[p_caret].line) {
+ return carets[p_caret].column;
+ } else {
+ return MIN(carets[p_caret].selection.origin_column, carets[p_caret].column);
+ }
}
int TextEdit::get_selection_to_line(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.to_line;
+ if (!has_selection(p_caret)) {
+ return carets[p_caret].line;
+ }
+ return MAX(carets[p_caret].selection.origin_line, carets[p_caret].line);
}
int TextEdit::get_selection_to_column(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.to_column;
+ if (!has_selection(p_caret)) {
+ return carets[p_caret].column;
+ }
+ if (carets[p_caret].selection.origin_line < carets[p_caret].line) {
+ return carets[p_caret].column;
+ } else if (carets[p_caret].selection.origin_line > carets[p_caret].line) {
+ return carets[p_caret].selection.origin_column;
+ } else {
+ return MAX(carets[p_caret].selection.origin_column, carets[p_caret].column);
+ }
+}
+
+bool TextEdit::is_caret_after_selection_origin(int p_caret) const {
+ ERR_FAIL_INDEX_V(p_caret, carets.size(), false);
+ if (!has_selection(p_caret)) {
+ return true;
+ }
+ return carets[p_caret].line > carets[p_caret].selection.origin_line || (carets[p_caret].line == carets[p_caret].selection.origin_line && carets[p_caret].column >= carets[p_caret].selection.origin_column);
}
void TextEdit::deselect(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
- for (int i = 0; i < carets.size(); i++) {
- if (p_caret != -1 && p_caret != i) {
- continue;
+ ERR_FAIL_COND(p_caret >= carets.size() || p_caret < -1);
+ bool selection_changed = false;
+ if (p_caret >= 0) {
+ selection_changed = carets.write[p_caret].selection.active;
+ carets.write[p_caret].selection.active = false;
+ } else {
+ for (int i = 0; i < carets.size(); i++) {
+ selection_changed |= carets.write[i].selection.active;
+ carets.write[i].selection.active = false;
}
- carets.write[i].selection.active = false;
}
- caret_index_edit_dirty = true;
- queue_redraw();
+ if (selection_changed) {
+ _selection_changed(p_caret);
+ }
}
void TextEdit::delete_selection(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_caret != -1 && p_caret != i) {
continue;
}
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
if (!has_selection(i)) {
continue;
}
- selecting_mode = SelectionMode::SELECTION_MODE_NONE;
- _remove_text(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i), get_selection_to_column(i));
- set_caret_line(get_selection_from_line(i), false, false, 0, i);
- set_caret_column(get_selection_from_column(i), i == 0, i);
- carets.write[i].selection.active = false;
+ int selection_from_line = get_selection_from_line(i);
+ int selection_from_column = get_selection_from_column(i);
+ int selection_to_line = get_selection_to_line(i);
+ int selection_to_column = get_selection_to_column(i);
+
+ _remove_text(selection_from_line, selection_from_column, selection_to_line, selection_to_column);
+ _offset_carets_after(selection_to_line, selection_to_column, selection_from_line, selection_from_column);
+ merge_overlapping_carets();
- adjust_carets_after_edit(i, carets[i].selection.from_line, carets[i].selection.from_column, carets[i].selection.to_line, carets[i].selection.to_column);
+ deselect(i);
+ set_caret_line(selection_from_line, false, false, -1, i);
+ set_caret_column(selection_from_column, i == 0, i);
}
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
/* Line wrapping. */
@@ -6224,8 +6386,10 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("swap_lines", "from_line", "to_line"), &TextEdit::swap_lines);
ClassDB::bind_method(D_METHOD("insert_line_at", "line", "text"), &TextEdit::insert_line_at);
+ ClassDB::bind_method(D_METHOD("remove_line_at", "line", "move_carets_down"), &TextEdit::remove_line_at, DEFVAL(true));
ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text", "caret_index"), &TextEdit::insert_text_at_caret, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("insert_text", "text", "line", "column", "before_selection_begin", "before_selection_end"), &TextEdit::insert_text, DEFVAL(true), DEFVAL(false));
ClassDB::bind_method(D_METHOD("remove_text", "from_line", "from_column", "to_line", "to_column"), &TextEdit::remove_text);
ClassDB::bind_method(D_METHOD("get_last_unhidden_line"), &TextEdit::get_last_unhidden_line);
@@ -6311,7 +6475,7 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_search_text", "search_text"), &TextEdit::set_search_text);
ClassDB::bind_method(D_METHOD("set_search_flags", "flags"), &TextEdit::set_search_flags);
- ClassDB::bind_method(D_METHOD("search", "text", "flags", "from_line", "from_colum"), &TextEdit::search);
+ ClassDB::bind_method(D_METHOD("search", "text", "flags", "from_line", "from_column"), &TextEdit::search);
/* Tooltip */
ClassDB::bind_method(D_METHOD("set_tooltip_request_func", "callback"), &TextEdit::set_tooltip_request_func);
@@ -6355,15 +6519,20 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_multiple_carets_enabled", "enabled"), &TextEdit::set_multiple_carets_enabled);
ClassDB::bind_method(D_METHOD("is_multiple_carets_enabled"), &TextEdit::is_multiple_carets_enabled);
- ClassDB::bind_method(D_METHOD("add_caret", "line", "col"), &TextEdit::add_caret);
+ ClassDB::bind_method(D_METHOD("add_caret", "line", "column"), &TextEdit::add_caret);
ClassDB::bind_method(D_METHOD("remove_caret", "caret"), &TextEdit::remove_caret);
ClassDB::bind_method(D_METHOD("remove_secondary_carets"), &TextEdit::remove_secondary_carets);
- ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets);
ClassDB::bind_method(D_METHOD("get_caret_count"), &TextEdit::get_caret_count);
ClassDB::bind_method(D_METHOD("add_caret_at_carets", "below"), &TextEdit::add_caret_at_carets);
- ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order);
- ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit);
+ ClassDB::bind_method(D_METHOD("get_sorted_carets", "include_ignored_carets"), &TextEdit::get_sorted_carets, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("collapse_carets", "from_line", "from_column", "to_line", "to_column", "inclusive"), &TextEdit::collapse_carets, DEFVAL(false));
+
+ ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets);
+ ClassDB::bind_method(D_METHOD("begin_multicaret_edit"), &TextEdit::begin_multicaret_edit);
+ ClassDB::bind_method(D_METHOD("end_multicaret_edit"), &TextEdit::end_multicaret_edit);
+ ClassDB::bind_method(D_METHOD("is_in_mulitcaret_edit"), &TextEdit::is_in_mulitcaret_edit);
+ ClassDB::bind_method(D_METHOD("multicaret_edit_ignore_caret", "caret_index"), &TextEdit::multicaret_edit_ignore_caret);
ClassDB::bind_method(D_METHOD("is_caret_visible", "caret_index"), &TextEdit::is_caret_visible, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_caret_draw_pos", "caret_index"), &TextEdit::get_caret_draw_pos, DEFVAL(0));
@@ -6394,27 +6563,33 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &TextEdit::set_drag_and_drop_selection_enabled);
ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &TextEdit::is_drag_and_drop_selection_enabled);
- ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("set_selection_mode", "mode"), &TextEdit::set_selection_mode);
ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode);
ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all);
ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("add_selection_for_next_occurrence"), &TextEdit::add_selection_for_next_occurrence);
ClassDB::bind_method(D_METHOD("skip_selection_for_next_occurrence"), &TextEdit::skip_selection_for_next_occurrence);
- ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("select", "origin_line", "origin_column", "caret_line", "caret_column", "caret_index"), &TextEdit::select, DEFVAL(0));
ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_selected_text", "caret_index"), &TextEdit::get_selected_text, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_selection_at_line_column", "line", "column", "include_edges", "only_selections"), &TextEdit::get_selection_at_line_column, DEFVAL(true), DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("get_line_ranges_from_carets", "only_selections", "merge_adjacent"), &TextEdit::get_line_ranges_from_carets_typed_array, DEFVAL(false), DEFVAL(true));
- ClassDB::bind_method(D_METHOD("get_selection_line", "caret_index"), &TextEdit::get_selection_line, DEFVAL(0));
- ClassDB::bind_method(D_METHOD("get_selection_column", "caret_index"), &TextEdit::get_selection_column, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_selection_origin_line", "caret_index"), &TextEdit::get_selection_origin_line, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_selection_origin_column", "caret_index"), &TextEdit::get_selection_origin_column, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("set_selection_origin_line", "line", "can_be_hidden", "wrap_index", "caret_index"), &TextEdit::set_selection_origin_line, DEFVAL(true), DEFVAL(-1), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("set_selection_origin_column", "column", "caret_index"), &TextEdit::set_selection_origin_column, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_selection_from_line", "caret_index"), &TextEdit::get_selection_from_line, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_selection_from_column", "caret_index"), &TextEdit::get_selection_from_column, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_selection_to_line", "caret_index"), &TextEdit::get_selection_to_line, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_selection_to_column", "caret_index"), &TextEdit::get_selection_to_column, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("is_caret_after_selection_origin", "caret_index"), &TextEdit::is_caret_after_selection_origin, DEFVAL(0));
+
ClassDB::bind_method(D_METHOD("deselect", "caret_index"), &TextEdit::deselect, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("delete_selection", "caret_index"), &TextEdit::delete_selection, DEFVAL(-1));
@@ -6550,6 +6725,14 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_menu_visible"), &TextEdit::is_menu_visible);
ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option);
+ /* Deprecated */
+#ifndef DISABLE_DEPRECATED
+ ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit);
+ ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order);
+ ClassDB::bind_method(D_METHOD("get_selection_line", "caret_index"), &TextEdit::get_selection_line, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_selection_column", "caret_index"), &TextEdit::get_selection_column, DEFVAL(0));
+#endif
+
/* Inspector */
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text", PROPERTY_HINT_MULTILINE_TEXT), "set_placeholder", "get_placeholder");
@@ -6617,7 +6800,7 @@ void TextEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("gutter_added"));
ADD_SIGNAL(MethodInfo("gutter_removed"));
- /* Theme items */
+ // Theme items
/* Search */
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, search_result_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, search_result_border_color);
@@ -6690,6 +6873,10 @@ void TextEdit::_unhide_all_lines() {
queue_redraw();
}
+void TextEdit::_unhide_carets() {
+ // Override for functionality.
+}
+
void TextEdit::_set_line_as_hidden(int p_line, bool p_hidden) {
ERR_FAIL_INDEX(p_line, text.size());
@@ -6717,14 +6904,17 @@ void TextEdit::_set_symbol_lookup_word(const String &p_symbol) {
// Overridable actions
void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (!editable) {
return;
}
start_action(EditAction::ACTION_TYPING);
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
if (p_caret != -1 && p_caret != i) {
continue;
}
@@ -6742,11 +6932,12 @@ void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_ca
const char32_t chr[2] = { (char32_t)p_unicode, 0 };
insert_text_at_caret(chr, i);
}
+ end_multicaret_edit();
end_action();
}
void TextEdit::_backspace_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (!editable) {
return;
}
@@ -6757,194 +6948,163 @@ void TextEdit::_backspace_internal(int p_caret) {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
if (p_caret != -1 && p_caret != i) {
continue;
}
- int cc = get_caret_column(i);
- int cl = get_caret_line(i);
+ int to_line = get_caret_line(i);
+ int to_column = get_caret_column(i);
- if (cc == 0 && cl == 0) {
+ if (to_column == 0 && to_line == 0) {
continue;
}
- int prev_line = cc ? cl : cl - 1;
- int prev_column = cc ? (cc - 1) : (text[cl - 1].length());
+ int from_line = to_column > 0 ? to_line : to_line - 1;
+ int from_column = to_column > 0 ? (to_column - 1) : (text[to_line - 1].length());
- merge_gutters(prev_line, cl);
+ merge_gutters(from_line, to_line);
- if (_is_line_hidden(cl)) {
- _set_line_as_hidden(prev_line, true);
- }
- _remove_text(prev_line, prev_column, cl, cc);
-
- set_caret_line(prev_line, false, true, 0, i);
- set_caret_column(prev_column, i == 0, i);
+ _remove_text(from_line, from_column, to_line, to_column);
+ collapse_carets(from_line, from_column, to_line, to_column);
+ _offset_carets_after(to_line, to_column, from_line, from_column);
- adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
+ set_caret_line(from_line, false, true, -1, i);
+ set_caret_column(from_column, i == 0, i);
}
- merge_overlapping_carets();
+ end_multicaret_edit();
end_complex_operation();
}
void TextEdit::_cut_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
+
+ _copy_internal(p_caret);
+
if (!editable) {
return;
}
if (has_selection(p_caret)) {
- DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret));
delete_selection(p_caret);
- cut_copy_line = "";
return;
}
+ // Remove full lines.
begin_complex_operation();
- Vector<int> carets_to_remove;
-
- StringBuilder clipboard;
- // This is the exception and has to edit in reverse order else the string copied to the clipboard will be backwards.
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = caret_edit_order.size() - 1; i >= 0; i--) {
- int caret_idx = caret_edit_order[i];
- if (p_caret != -1 && p_caret != caret_idx) {
- continue;
- }
-
- int cl = get_caret_line(caret_idx);
- int cc = get_caret_column(caret_idx);
- int indent_level = get_indent_level(cl);
- double hscroll = get_h_scroll();
-
- // Check for overlapping carets.
- // We don't need to worry about selections as that is caught before this entire section.
- for (int j = i - 1; j >= 0; j--) {
- if (get_caret_line(caret_edit_order[j]) == cl) {
- carets_to_remove.push_back(caret_edit_order[j]);
- i = j;
- }
- }
-
- clipboard += text[cl];
- if (p_caret == -1 && caret_idx != 0) {
- clipboard += "\n";
- }
-
- if (cl == 0 && get_line_count() > 1) {
- _remove_text(cl, 0, cl + 1, 0);
- adjust_carets_after_edit(caret_idx, cl, 0, cl + 1, text[cl].length());
- } else {
- _remove_text(cl, 0, cl, text[cl].length());
- set_caret_column(0, false, caret_idx);
- backspace(caret_idx);
- set_caret_line(get_caret_line(caret_idx) + 1, caret_idx == 0, 0, 0, caret_idx);
- }
-
- // Correct the visually perceived caret column taking care of indentation level of the lines.
- int diff_indent = indent_level - get_indent_level(get_caret_line(caret_idx));
- cc += diff_indent;
- if (diff_indent != 0) {
- cc += diff_indent > 0 ? -1 : 1;
- }
-
- // Restore horizontal scroll and caret column modified by the backspace() call.
- set_h_scroll(hscroll);
- set_caret_column(cc, caret_idx == 0, caret_idx);
+ begin_multicaret_edit();
+ Vector<Point2i> line_ranges;
+ if (p_caret == -1) {
+ line_ranges = get_line_ranges_from_carets();
+ } else {
+ line_ranges.push_back(Point2i(get_caret_line(p_caret), get_caret_line(p_caret)));
}
-
- // Sort and remove backwards to preserve indexes.
- carets_to_remove.sort();
- for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
- remove_caret(carets_to_remove[i]);
+ int line_offset = 0;
+ for (Point2i line_range : line_ranges) {
+ // Preserve carets on the last line.
+ remove_line_at(line_range.y + line_offset);
+ if (line_range.x != line_range.y) {
+ remove_text(line_range.x + line_offset, 0, line_range.y + line_offset, 0);
+ }
+ line_offset += line_range.x - line_range.y - 1;
}
+ end_multicaret_edit();
end_complex_operation();
-
- String clipboard_string = clipboard.as_string();
- DisplayServer::get_singleton()->clipboard_set(clipboard_string);
- cut_copy_line = clipboard_string;
}
void TextEdit::_copy_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (has_selection(p_caret)) {
DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret));
cut_copy_line = "";
return;
}
+ // Copy full lines.
StringBuilder clipboard;
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = caret_edit_order.size() - 1; i >= 0; i--) {
- int caret_idx = caret_edit_order[i];
- if (p_caret != -1 && p_caret != caret_idx) {
- continue;
- }
-
- int cl = get_caret_line(caret_idx);
- if (text[cl].length() != 0) {
- clipboard += _base_get_text(cl, 0, cl, text[cl].length());
- if (p_caret == -1 && i != 0) {
- clipboard += "\n";
+ Vector<Point2i> line_ranges;
+ if (p_caret == -1) {
+ // When there are multiple carets on a line, only copy it once.
+ line_ranges = get_line_ranges_from_carets(false, true);
+ } else {
+ line_ranges.push_back(Point2i(get_caret_line(p_caret), get_caret_line(p_caret)));
+ }
+ for (Point2i line_range : line_ranges) {
+ for (int i = line_range.x; i <= line_range.y; i++) {
+ if (text[i].length() != 0) {
+ clipboard += _base_get_text(i, 0, i, text[i].length());
}
+ clipboard += "\n";
}
}
String clipboard_string = clipboard.as_string();
DisplayServer::get_singleton()->clipboard_set(clipboard_string);
- cut_copy_line = clipboard_string;
+ // Set the cut copy line so we know to paste as a line.
+ if (get_caret_count() == 1) {
+ cut_copy_line = clipboard_string;
+ } else {
+ cut_copy_line = "";
+ }
}
void TextEdit::_paste_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (!editable) {
return;
}
String clipboard = DisplayServer::get_singleton()->clipboard_get();
+
+ // Paste a full line. Ignore '\r' characters that may have been added to the clipboard by the OS.
+ if (get_caret_count() == 1 && !has_selection(0) && !cut_copy_line.is_empty() && cut_copy_line == clipboard.replace("\r", "")) {
+ insert_text(clipboard, get_caret_line(), 0);
+ return;
+ }
+
+ // Paste text at each caret or one line per caret.
Vector<String> clipboad_lines = clipboard.split("\n");
- bool insert_line_per_caret = p_caret == -1 && carets.size() > 1 && clipboad_lines.size() == carets.size();
+ bool insert_line_per_caret = p_caret == -1 && get_caret_count() > 1 && clipboad_lines.size() == get_caret_count();
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- int clipboad_line = clipboad_lines.size() - 1;
- for (const int &i : caret_edit_order) {
- if (p_caret != -1 && p_caret != i) {
+ begin_multicaret_edit();
+ Vector<int> sorted_carets = get_sorted_carets();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
+ if (p_caret != -1 && p_caret != caret_index) {
continue;
}
- if (has_selection(i)) {
- delete_selection(i);
- } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) {
- set_caret_column(0, i == 0, i);
- String ins = "\n";
- clipboard += ins;
+ if (has_selection(caret_index)) {
+ delete_selection(caret_index);
}
if (insert_line_per_caret) {
- clipboard = clipboad_lines[clipboad_line];
+ clipboard = clipboad_lines[i];
}
- insert_text_at_caret(clipboard, i);
- clipboad_line--;
+ insert_text_at_caret(clipboard, caret_index);
}
+ end_multicaret_edit();
end_complex_operation();
}
void TextEdit::_paste_primary_clipboard_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (!is_editable() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
return;
}
String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary();
- if (carets.size() == 1) {
+ if (get_caret_count() == 1) {
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
deselect();
- set_caret_line(pos.y, true, false);
+ set_caret_line(pos.y, true, false, -1);
set_caret_column(pos.x);
}
@@ -7203,10 +7363,26 @@ int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) con
}
/* Caret */
+void TextEdit::_caret_changed(int p_caret) {
+ queue_redraw();
+
+ if (has_selection(p_caret)) {
+ _selection_changed(p_caret);
+ }
+
+ if (caret_pos_dirty) {
+ return;
+ }
+
+ if (is_inside_tree()) {
+ callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
+ }
+ caret_pos_dirty = true;
+}
+
void TextEdit::_emit_caret_changed() {
emit_signal(SNAME("caret_changed"));
caret_pos_dirty = false;
- caret_index_edit_dirty = true;
}
void TextEdit::_reset_caret_blink_timer() {
@@ -7251,60 +7427,152 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column
}
}
-/* Selection */
-void TextEdit::_click_selection_held() {
- // Warning: is_mouse_button_pressed(MouseButton::LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD
- // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem.
- // I'm unsure if there's an actual fix that doesn't have a ton of side effects.
- if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && get_selection_mode() != SelectionMode::SELECTION_MODE_NONE) {
- switch (get_selection_mode()) {
- case SelectionMode::SELECTION_MODE_POINTER: {
- _update_selection_mode_pointer();
- } break;
- case SelectionMode::SELECTION_MODE_WORD: {
- _update_selection_mode_word();
- } break;
- case SelectionMode::SELECTION_MODE_LINE: {
- _update_selection_mode_line();
- } break;
- default: {
- break;
+bool TextEdit::_is_line_col_in_range(int p_line, int p_column, int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_include_edges) const {
+ if (p_line >= p_from_line && p_line <= p_to_line && (p_line > p_from_line || p_column > p_from_column) && (p_line < p_to_line || p_column < p_to_column)) {
+ return true;
+ }
+ if (p_include_edges) {
+ if ((p_line == p_from_line && p_column == p_from_column) || (p_line == p_to_line && p_column == p_to_column)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void TextEdit::_offset_carets_after(int p_old_line, int p_old_column, int p_new_line, int p_new_column, bool p_include_selection_begin, bool p_include_selection_end) {
+ // Moves all carets at or after old_line and old_column.
+ // Called after deleting or inserting text so that the carets stay with the text they are at.
+
+ int edit_height = p_new_line - p_old_line;
+ int edit_size = p_new_column - p_old_column;
+ if (edit_height == 0 && edit_size == 0) {
+ return;
+ }
+
+ // Intentionally includes carets in the multicaret_edit_ignore list so that they are moved together.
+ for (int i = 0; i < get_caret_count(); i++) {
+ bool selected = has_selection(i);
+ bool caret_at_end = selected && is_caret_after_selection_origin(i);
+ bool include_caret_at = caret_at_end ? p_include_selection_end : p_include_selection_begin;
+
+ // Move caret.
+ int caret_line = get_caret_line(i);
+ int caret_column = get_caret_column(i);
+ bool caret_after = caret_line > p_old_line || (caret_line == p_old_line && caret_column > p_old_column);
+ bool caret_at = caret_line == p_old_line && caret_column == p_old_column;
+ if (caret_after || (caret_at && include_caret_at)) {
+ caret_line += edit_height;
+ if (caret_line == p_new_line) {
+ caret_column += edit_size;
+ }
+
+ if (edit_height != 0) {
+ set_caret_line(caret_line, false, true, -1, i);
}
+ set_caret_column(caret_column, false, i);
}
- } else {
+
+ // Move selection origin.
+ if (!selected) {
+ continue;
+ }
+ bool include_selection_origin_at = !caret_at_end ? p_include_selection_end : p_include_selection_begin;
+
+ int selection_origin_line = get_selection_origin_line(i);
+ int selection_origin_column = get_selection_origin_column(i);
+ bool selection_origin_after = selection_origin_line > p_old_line || (selection_origin_line == p_old_line && selection_origin_column > p_old_column);
+ bool selection_origin_at = selection_origin_line == p_old_line && selection_origin_column == p_old_column;
+ if (selection_origin_after || (selection_origin_at && include_selection_origin_at)) {
+ selection_origin_line += edit_height;
+ if (selection_origin_line == p_new_line) {
+ selection_origin_column += edit_size;
+ }
+ select(selection_origin_line, selection_origin_column, caret_line, caret_column, i);
+ }
+ }
+ if (!p_include_selection_begin && p_include_selection_end && has_selection()) {
+ // It is possible that two adjacent selections now overlap.
+ merge_overlapping_carets();
+ }
+}
+
+void TextEdit::_cancel_drag_and_drop_text() {
+ // Cancel the drag operation if drag originated from here.
+ if (selection_drag_attempt && get_viewport()) {
+ get_viewport()->gui_cancel_drag();
+ }
+}
+
+/* Selection */
+void TextEdit::_selection_changed(int p_caret) {
+ if (!selecting_enabled) {
+ return;
+ }
+
+ _cancel_drag_and_drop_text();
+ queue_redraw();
+}
+
+void TextEdit::_click_selection_held() {
+ // Update the selection mode on a timer so it is updated when the view scrolls even if the mouse isn't moving.
+ if (!Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || get_selection_mode() == SelectionMode::SELECTION_MODE_NONE) {
click_select_held->stop();
+ return;
+ }
+ switch (get_selection_mode()) {
+ case SelectionMode::SELECTION_MODE_POINTER: {
+ _update_selection_mode_pointer();
+ } break;
+ case SelectionMode::SELECTION_MODE_WORD: {
+ _update_selection_mode_word();
+ } break;
+ case SelectionMode::SELECTION_MODE_LINE: {
+ _update_selection_mode_line();
+ } break;
+ default: {
+ break;
+ }
}
}
-void TextEdit::_update_selection_mode_pointer() {
- dragging_selection = true;
+void TextEdit::_update_selection_mode_pointer(bool p_initial) {
Point2 mp = get_local_mouse_pos();
Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
- int col = pos.x;
- int caret_idx = carets.size() - 1;
-
- select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx);
+ int column = pos.x;
+ int caret_index = get_caret_count() - 1;
+
+ if (p_initial && !has_selection(caret_index)) {
+ set_selection_origin_line(line, true, -1, caret_index);
+ set_selection_origin_column(column, caret_index);
+ // Set the word begin and end to the column in case the mode changes later.
+ carets.write[caret_index].selection.word_begin_column = column;
+ carets.write[caret_index].selection.word_end_column = column;
+ } else {
+ select(get_selection_origin_line(caret_index), get_selection_origin_column(caret_index), line, column, caret_index);
+ }
+ adjust_viewport_to_caret(caret_index);
- set_caret_line(line, false, true, 0, caret_idx);
- set_caret_column(col, true, caret_idx);
- queue_redraw();
+ if (has_selection(caret_index)) {
+ // Only set to true if any selection has been made.
+ dragging_selection = true;
+ }
click_select_held->start();
merge_overlapping_carets();
}
-void TextEdit::_update_selection_mode_word() {
+void TextEdit::_update_selection_mode_word(bool p_initial) {
dragging_selection = true;
Point2 mp = get_local_mouse_pos();
Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
- int col = pos.x;
- int caret_idx = carets.size() - 1;
+ int column = pos.x;
+ int caret_index = get_caret_count() - 1;
- int caret_pos = CLAMP(col, 0, text[line].length());
+ int caret_pos = CLAMP(column, 0, text[line].length());
int beg = caret_pos;
int end = beg;
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
@@ -7316,70 +7584,57 @@ void TextEdit::_update_selection_mode_word() {
}
}
- /* Initial selection. */
- if (!has_selection(caret_idx)) {
- select(line, beg, line, end, caret_idx);
- carets.write[caret_idx].selection.selecting_column = beg;
- carets.write[caret_idx].selection.selected_word_beg = beg;
- carets.write[caret_idx].selection.selected_word_end = end;
- carets.write[caret_idx].selection.selected_word_origin = beg;
- set_caret_line(line, false, true, 0, caret_idx);
- set_caret_column(end, true, caret_idx);
+ if (p_initial && !has_selection(caret_index)) {
+ // Set the selection origin if there is no existing selection.
+ select(line, beg, line, end, caret_index);
+ carets.write[caret_index].selection.word_begin_column = beg;
+ carets.write[caret_index].selection.word_end_column = end;
} else {
- if ((col <= carets[caret_idx].selection.selected_word_origin && line == get_selection_line(caret_idx)) || line < get_selection_line(caret_idx)) {
- carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_end;
- select(line, beg, get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_end, caret_idx);
- set_caret_line(line, false, true, 0, caret_idx);
- set_caret_column(beg, true, caret_idx);
- } else {
- carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_beg;
- select(get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_beg, line, end, caret_idx);
- set_caret_line(get_selection_to_line(caret_idx), false, true, 0, caret_idx);
- set_caret_column(get_selection_to_column(caret_idx), true, caret_idx);
- }
+ // Expand the word selection to the mouse.
+ int origin_line = get_selection_origin_line(caret_index);
+ bool is_new_selection_dir_right = line > origin_line || (line == origin_line && column >= carets[caret_index].selection.word_begin_column);
+ int origin_col = is_new_selection_dir_right ? carets[caret_index].selection.word_begin_column : carets[caret_index].selection.word_end_column;
+ int caret_col = is_new_selection_dir_right ? end : beg;
+
+ select(origin_line, origin_col, line, caret_col, caret_index);
}
+ adjust_viewport_to_caret(caret_index);
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
- queue_redraw();
-
click_select_held->start();
merge_overlapping_carets();
}
-void TextEdit::_update_selection_mode_line() {
+void TextEdit::_update_selection_mode_line(bool p_initial) {
dragging_selection = true;
Point2 mp = get_local_mouse_pos();
Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
- int col = pos.x;
- int caret_idx = carets.size() - 1;
-
- col = 0;
- if (line < carets[caret_idx].selection.selecting_line) {
- // Caret is above us.
- set_caret_line(line - 1, false, true, 0, caret_idx);
- carets.write[caret_idx].selection.selecting_column = has_selection(caret_idx)
- ? text[get_selection_line(caret_idx)].length()
- : 0;
- } else {
- // Caret is below us.
- set_caret_line(line + 1, false, true, 0, caret_idx);
- carets.write[caret_idx].selection.selecting_column = 0;
- col = text[line].length();
+ int caret_index = get_caret_count() - 1;
+
+ int origin_line = p_initial && !has_selection(caret_index) ? line : get_selection_origin_line();
+ bool line_below = line >= origin_line;
+ int origin_col = line_below ? 0 : get_line(origin_line).length();
+ int caret_line = line_below ? line + 1 : line;
+ int caret_col = caret_line < text.size() ? 0 : get_line(text.size() - 1).length();
+
+ select(origin_line, origin_col, caret_line, caret_col, caret_index);
+ adjust_viewport_to_caret(caret_index);
+
+ if (p_initial) {
+ // Set the word begin and end to the start and end of the origin line in case the mode changes later.
+ carets.write[caret_index].selection.word_begin_column = 0;
+ carets.write[caret_index].selection.word_end_column = get_line(origin_line).length();
}
- set_caret_column(0, false, caret_idx);
- select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx);
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
- queue_redraw();
-
click_select_held->start();
merge_overlapping_carets();
}
@@ -7389,23 +7644,23 @@ void TextEdit::_pre_shift_selection(int p_caret) {
return;
}
- if (!has_selection(p_caret) || get_selection_mode() == SelectionMode::SELECTION_MODE_NONE) {
- carets.write[p_caret].selection.active = true;
- set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_caret_line(p_caret), get_caret_column(p_caret), p_caret);
+ set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT);
+ if (has_selection(p_caret)) {
return;
}
-
- set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_selection_line(p_caret), get_selection_column(p_caret), p_caret);
+ // Prepare selection to start at current caret position.
+ set_selection_origin_line(get_caret_line(p_caret), true, -1, p_caret);
+ set_selection_origin_column(get_caret_column(p_caret), p_caret);
+ carets.write[p_caret].selection.active = true;
+ carets.write[p_caret].selection.word_begin_column = get_caret_column(p_caret);
+ carets.write[p_caret].selection.word_end_column = get_caret_column(p_caret);
}
-void TextEdit::_post_shift_selection(int p_caret) {
- if (!selecting_enabled) {
- return;
- }
-
- if (has_selection(p_caret) && get_selection_mode() == SelectionMode::SELECTION_MODE_SHIFT) {
- select(get_selection_line(p_caret), get_selection_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret), p_caret);
+bool TextEdit::_selection_contains(int p_caret, int p_line, int p_column, bool p_include_edges, bool p_only_selections) const {
+ if (!has_selection(p_caret)) {
+ return !p_only_selections && p_line == get_caret_line(p_caret) && p_column == get_caret_column(p_caret);
}
+ return _is_line_col_in_range(p_line, p_column, get_selection_from_line(p_caret), get_selection_from_column(p_caret), get_selection_to_line(p_caret), get_selection_to_column(p_caret), p_include_edges);
}
/* Line Wrapping */
@@ -7780,9 +8035,43 @@ Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) {
return syntax_highlighter.is_null() && !setting_text ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line);
}
+/* Deprecated. */
+#ifndef DISABLE_DEPRECATED
+Vector<int> TextEdit::get_caret_index_edit_order() {
+ Vector<int> carets_order = get_sorted_carets();
+ carets_order.reverse();
+ return carets_order;
+}
+
+void TextEdit::adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col) {
+}
+
+int TextEdit::get_selection_line(int p_caret) const {
+ return get_selection_origin_line(p_caret);
+}
+
+int TextEdit::get_selection_column(int p_caret) const {
+ return get_selection_origin_column(p_caret);
+}
+#endif
+
/*** Super internal Core API. Everything builds on it. ***/
-void TextEdit::_text_changed_emit() {
+void TextEdit::_text_changed() {
+ _cancel_drag_and_drop_text();
+ queue_redraw();
+
+ if (text_changed_dirty || setting_text) {
+ return;
+ }
+
+ if (is_inside_tree()) {
+ callable_mp(this, &TextEdit::_emit_text_changed).call_deferred();
+ }
+ text_changed_dirty = true;
+}
+
+void TextEdit::_emit_text_changed() {
emit_signal(SNAME("text_changed"));
text_changed_dirty = false;
}
@@ -7918,12 +8207,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
input_direction = (TextDirection)dir;
}
- if (!text_changed_dirty && !setting_text) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_text_changed_emit).call_deferred();
- }
- text_changed_dirty = true;
- }
+ _text_changed();
emit_signal(SNAME("lines_edited_from"), p_line, r_end_line);
}
@@ -7964,12 +8248,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li
text.remove_range(p_from_line, p_to_line);
text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text));
- if (!text_changed_dirty && !setting_text) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_text_changed_emit).call_deferred();
- }
- text_changed_dirty = true;
- }
+ _text_changed();
emit_signal(SNAME("lines_edited_from"), p_to_line, p_from_line);
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 1099295d3b..efade39876 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -389,18 +389,12 @@ private:
/* Caret. */
struct Selection {
bool active = false;
- bool shiftclick_left = false;
- int selecting_line = 0;
- int selecting_column = 0;
- int selected_word_beg = 0;
- int selected_word_end = 0;
- int selected_word_origin = 0;
-
- int from_line = 0;
- int from_column = 0;
- int to_line = 0;
- int to_column = 0;
+ int origin_line = 0;
+ int origin_column = 0;
+ int origin_last_fit_x = 0;
+ int word_begin_column = 0;
+ int word_end_column = 0;
};
struct Caret {
@@ -415,11 +409,13 @@ private:
// Vector containing all the carets, index '0' is the "main caret" and should never be removed.
Vector<Caret> carets;
- Vector<int> caret_index_edit_order;
bool setting_caret_line = false;
bool caret_pos_dirty = false;
- bool caret_index_edit_dirty = true;
+
+ int multicaret_edit_count = 0;
+ bool multicaret_edit_merge_queued = false;
+ HashSet<int> multicaret_edit_ignore_carets;
CaretType caret_type = CaretType::CARET_TYPE_LINE;
@@ -438,12 +434,18 @@ private:
bool drag_action = false;
bool drag_caret_force_displayed = false;
+ void _caret_changed(int p_caret = -1);
void _emit_caret_changed();
void _reset_caret_blink_timer();
void _toggle_draw_caret();
int _get_column_x_offset_for_line(int p_char, int p_line, int p_column) const;
+ bool _is_line_col_in_range(int p_line, int p_column, int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_include_edges = true) const;
+
+ void _offset_carets_after(int p_old_line, int p_old_column, int p_new_line, int p_new_column, bool p_include_selection_begin = true, bool p_include_selection_end = true);
+
+ void _cancel_drag_and_drop_text();
/* Selection. */
SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE;
@@ -456,18 +458,23 @@ private:
bool selection_drag_attempt = false;
bool dragging_selection = false;
+ int drag_and_drop_origin_caret_index = -1;
+ int drag_caret_index = -1;
Timer *click_select_held = nullptr;
uint64_t last_dblclk = 0;
Vector2 last_dblclk_pos;
+
+ void _selection_changed(int p_caret = -1);
void _click_selection_held();
- void _update_selection_mode_pointer();
- void _update_selection_mode_word();
- void _update_selection_mode_line();
+ void _update_selection_mode_pointer(bool p_initial = false);
+ void _update_selection_mode_word(bool p_initial = false);
+ void _update_selection_mode_line(bool p_initial = false);
void _pre_shift_selection(int p_caret);
- void _post_shift_selection(int p_caret);
+
+ bool _selection_contains(int p_caret, int p_line, int p_column, bool p_include_edges = true, bool p_only_selections = true) const;
/* Line wrapping. */
LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE;
@@ -599,7 +606,8 @@ private:
/*** Super internal Core API. Everything builds on it. ***/
bool text_changed_dirty = false;
- void _text_changed_emit();
+ void _text_changed();
+ void _emit_text_changed();
void _insert_text(int p_line, int p_char, const String &p_text, int *r_end_line = nullptr, int *r_end_char = nullptr);
void _remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
@@ -625,13 +633,15 @@ private:
void _move_caret_document_end(bool p_select);
bool _clear_carets_and_selection();
- // Used in add_caret_at_carets
- void _get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x = -1) const;
-
protected:
void _notification(int p_what);
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ void _set_selection_mode_compat_86978(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0);
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
+
virtual void _update_theme_item_cache() override;
/* Internal API for CodeEdit, pending public API. */
@@ -659,6 +669,7 @@ protected:
bool _is_line_hidden(int p_line) const;
void _unhide_all_lines();
+ virtual void _unhide_carets();
// Symbol lookup.
String lookup_symbol_word;
@@ -765,9 +776,11 @@ public:
void swap_lines(int p_from_line, int p_to_line);
- void insert_line_at(int p_at, const String &p_text);
- void insert_text_at_caret(const String &p_text, int p_caret = -1);
+ void insert_line_at(int p_line, const String &p_text);
+ void remove_line_at(int p_line, bool p_move_carets_down = true);
+ void insert_text_at_caret(const String &p_text, int p_caret = -1);
+ void insert_text(const String &p_text, int p_line, int p_column, bool p_before_selection_begin = true, bool p_before_selection_end = false);
void remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
int get_last_unhidden_line() const;
@@ -851,15 +864,20 @@ public:
void set_multiple_carets_enabled(bool p_enabled);
bool is_multiple_carets_enabled() const;
- int add_caret(int p_line, int p_col);
+ int add_caret(int p_line, int p_column);
void remove_caret(int p_caret);
void remove_secondary_carets();
- void merge_overlapping_carets();
int get_caret_count() const;
void add_caret_at_carets(bool p_below);
- Vector<int> get_caret_index_edit_order();
- void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col);
+ Vector<int> get_sorted_carets(bool p_include_ignored_carets = false) const;
+ void collapse_carets(int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_inclusive = false);
+
+ void merge_overlapping_carets();
+ void begin_multicaret_edit();
+ void end_multicaret_edit();
+ bool is_in_mulitcaret_edit() const;
+ bool multicaret_edit_ignore_caret(int p_caret) const;
bool is_caret_visible(int p_caret = 0) const;
Point2 get_caret_draw_pos(int p_caret = 0) const;
@@ -867,7 +885,7 @@ public:
void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0, int p_caret = 0);
int get_caret_line(int p_caret = 0) const;
- void set_caret_column(int p_col, bool p_adjust_viewport = true, int p_caret = 0);
+ void set_caret_column(int p_column, bool p_adjust_viewport = true, int p_caret = 0);
int get_caret_column(int p_caret = 0) const;
int get_caret_wrap_index(int p_caret = 0) const;
@@ -884,27 +902,34 @@ public:
void set_drag_and_drop_selection_enabled(const bool p_enabled);
bool is_drag_and_drop_selection_enabled() const;
- void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0);
+ void set_selection_mode(SelectionMode p_mode);
SelectionMode get_selection_mode() const;
void select_all();
void select_word_under_caret(int p_caret = -1);
void add_selection_for_next_occurrence();
void skip_selection_for_next_occurrence();
- void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0);
+ void select(int p_origin_line, int p_origin_column, int p_caret_line, int p_caret_column, int p_caret = 0);
bool has_selection(int p_caret = -1) const;
String get_selected_text(int p_caret = -1);
+ int get_selection_at_line_column(int p_line, int p_column, bool p_include_edges = true, bool p_only_selections = true) const;
+ Vector<Point2i> get_line_ranges_from_carets(bool p_only_selections = false, bool p_merge_adjacent = true) const;
+ TypedArray<Vector2i> get_line_ranges_from_carets_typed_array(bool p_only_selections = false, bool p_merge_adjacent = true) const;
- int get_selection_line(int p_caret = 0) const;
- int get_selection_column(int p_caret = 0) const;
+ void set_selection_origin_line(int p_line, bool p_can_be_hidden = true, int p_wrap_index = -1, int p_caret = 0);
+ void set_selection_origin_column(int p_column, int p_caret = 0);
+ int get_selection_origin_line(int p_caret = 0) const;
+ int get_selection_origin_column(int p_caret = 0) const;
int get_selection_from_line(int p_caret = 0) const;
int get_selection_from_column(int p_caret = 0) const;
int get_selection_to_line(int p_caret = 0) const;
int get_selection_to_column(int p_caret = 0) const;
+ bool is_caret_after_selection_origin(int p_caret = 0) const;
+
void deselect(int p_caret = -1);
void delete_selection(int p_caret = -1);
@@ -1043,6 +1068,15 @@ public:
Color get_font_color() const;
+ /* Deprecated. */
+#ifndef DISABLE_DEPRECATED
+ Vector<int> get_caret_index_edit_order();
+ void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col);
+
+ int get_selection_line(int p_caret = 0) const;
+ int get_selection_column(int p_caret = 0) const;
+#endif
+
TextEdit(const String &p_placeholder = String());
};
diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp
index 0b197c8c02..df90257e03 100644
--- a/scene/gui/texture_button.cpp
+++ b/scene/gui/texture_button.cpp
@@ -103,7 +103,7 @@ bool TextureButton::has_point(const Point2 &p_point) const {
point *= scale;
// finally, we need to check if the point is inside a rectangle with a position >= 0,0 and a size <= mask_size
- rect.position = Point2().max(_texture_region.position);
+ rect.position = _texture_region.position.maxf(0);
rect.size = mask_size.min(_texture_region.size);
}
diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index fc9ac2ab18..bbe5ddf1c3 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -249,7 +249,7 @@ Point2 TextureProgressBar::get_relative_center() {
p += rad_center_off;
p.x /= progress->get_width();
p.y /= progress->get_height();
- p = p.clamp(Point2(), Point2(1, 1));
+ p = p.clampf(0, 1);
return p;
}
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index b17d345f1f..e2de585a69 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -2828,7 +2828,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
return -1;
}
- if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + theme_cache.item_margin))) {
+ if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x < (x_ofs + theme_cache.item_margin))) {
if (enable_recursive_folding && p_mod->is_shift_pressed()) {
p_item->set_collapsed_recursive(!p_item->is_collapsed());
} else {
@@ -3152,9 +3152,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
void Tree::_text_editor_popup_modal_close() {
- if (Input::get_singleton()->is_key_pressed(Key::ESCAPE) ||
- Input::get_singleton()->is_key_pressed(Key::KP_ENTER) ||
- Input::get_singleton()->is_key_pressed(Key::ENTER)) {
+ if (popup_editor->get_hide_reason() == Popup::HIDE_REASON_CANCELED) {
return;
}
@@ -3429,7 +3427,7 @@ Rect2 Tree::_get_content_rect() const {
const real_t v_size = v_scroll->is_visible() ? (v_scroll->get_combined_minimum_size().x + theme_cache.scrollbar_h_separation) : 0;
const real_t h_size = h_scroll->is_visible() ? (h_scroll->get_combined_minimum_size().y + theme_cache.scrollbar_v_separation) : 0;
const Point2 scroll_begin = _get_scrollbar_layout_rect().get_end() - Vector2(v_size, h_size);
- const Size2 offset = (content_rect.get_end() - scroll_begin).max(Vector2(0, 0));
+ const Size2 offset = (content_rect.get_end() - scroll_begin).maxf(0);
return content_rect.grow_individual(0, 0, -offset.x, -offset.y);
}