summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/base_button.cpp9
-rw-r--r--scene/gui/check_box.cpp4
-rw-r--r--scene/gui/check_box.h2
-rw-r--r--scene/gui/check_button.cpp20
-rw-r--r--scene/gui/check_button.h2
-rw-r--r--scene/gui/code_edit.cpp790
-rw-r--r--scene/gui/code_edit.h4
-rw-r--r--scene/gui/color_picker.cpp9
-rw-r--r--scene/gui/container.cpp4
-rw-r--r--scene/gui/container.h2
-rw-r--r--scene/gui/control.cpp24
-rw-r--r--scene/gui/control.h8
-rw-r--r--scene/gui/dialogs.cpp195
-rw-r--r--scene/gui/dialogs.h17
-rw-r--r--scene/gui/file_dialog.cpp30
-rw-r--r--scene/gui/file_dialog.h6
-rw-r--r--scene/gui/graph_edit.cpp72
-rw-r--r--scene/gui/graph_edit.h6
-rw-r--r--scene/gui/graph_node.cpp146
-rw-r--r--scene/gui/graph_node.h30
-rw-r--r--scene/gui/item_list.cpp58
-rw-r--r--scene/gui/item_list.h8
-rw-r--r--scene/gui/line_edit.cpp39
-rw-r--r--scene/gui/line_edit.h9
-rw-r--r--scene/gui/menu_bar.cpp4
-rw-r--r--scene/gui/menu_button.cpp38
-rw-r--r--scene/gui/menu_button.h4
-rw-r--r--scene/gui/option_button.cpp61
-rw-r--r--scene/gui/option_button.h1
-rw-r--r--scene/gui/popup_menu.cpp21
-rw-r--r--scene/gui/popup_menu.h6
-rw-r--r--scene/gui/progress_bar.cpp46
-rw-r--r--scene/gui/progress_bar.h10
-rw-r--r--scene/gui/range.cpp4
-rw-r--r--scene/gui/range.h2
-rw-r--r--scene/gui/rich_text_label.cpp311
-rw-r--r--scene/gui/rich_text_label.h21
-rw-r--r--scene/gui/scroll_bar.cpp4
-rw-r--r--scene/gui/scroll_container.cpp18
-rw-r--r--scene/gui/scroll_container.h4
-rw-r--r--scene/gui/split_container.cpp251
-rw-r--r--scene/gui/split_container.h30
-rw-r--r--scene/gui/subviewport_container.cpp4
-rw-r--r--scene/gui/subviewport_container.h2
-rw-r--r--scene/gui/tab_bar.cpp4
-rw-r--r--scene/gui/tab_container.cpp15
-rw-r--r--scene/gui/text_edit.cpp2921
-rw-r--r--scene/gui/text_edit.h196
-rw-r--r--scene/gui/texture_button.cpp2
-rw-r--r--scene/gui/texture_rect.cpp8
-rw-r--r--scene/gui/tree.cpp154
-rw-r--r--scene/gui/tree.h16
-rw-r--r--scene/gui/video_stream_player.cpp4
-rw-r--r--scene/gui/video_stream_player.h4
54 files changed, 3455 insertions, 2205 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index cf467ceafb..af6a99ca62 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -264,7 +264,7 @@ bool BaseButton::is_hovered() const {
BaseButton::DrawMode BaseButton::get_draw_mode() const {
if (status.disabled) {
return DRAW_DISABLED;
- };
+ }
if (!status.press_attempt && status.hovering) {
if (status.pressed) {
@@ -273,8 +273,7 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const {
return DRAW_HOVER;
} else {
- /* determine if pressed or not */
-
+ // Determine if pressed or not.
bool pressing;
if (status.press_attempt) {
pressing = (status.pressing_inside || keep_pressed_outside);
@@ -291,8 +290,6 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const {
return DRAW_NORMAL;
}
}
-
- return DRAW_NORMAL;
}
void BaseButton::set_toggle_mode(bool p_on) {
@@ -469,7 +466,7 @@ void BaseButton::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_RESOURCE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context");
BIND_ENUM_CONSTANT(DRAW_NORMAL);
BIND_ENUM_CONSTANT(DRAW_PRESSED);
diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp
index 37db7d53f0..f5eb0b957f 100644
--- a/scene/gui/check_box.cpp
+++ b/scene/gui/check_box.cpp
@@ -77,7 +77,7 @@ void CheckBox::_update_theme_item_cache() {
Button::_update_theme_item_cache();
theme_cache.h_separation = get_theme_constant(SNAME("h_separation"));
- theme_cache.check_v_adjust = get_theme_constant(SNAME("check_v_adjust"));
+ theme_cache.check_v_offset = get_theme_constant(SNAME("check_v_offset"));
theme_cache.normal_style = get_theme_stylebox(SNAME("normal"));
theme_cache.checked = get_theme_icon(SNAME("checked"));
@@ -134,7 +134,7 @@ void CheckBox::_notification(int p_what) {
} else {
ofs.x = theme_cache.normal_style->get_margin(SIDE_LEFT);
}
- ofs.y = int((get_size().height - get_icon_size().height) / 2) + theme_cache.check_v_adjust;
+ ofs.y = int((get_size().height - get_icon_size().height) / 2) + theme_cache.check_v_offset;
if (is_pressed()) {
on_tex->draw(ci, ofs);
diff --git a/scene/gui/check_box.h b/scene/gui/check_box.h
index beafece3dc..8438d0e589 100644
--- a/scene/gui/check_box.h
+++ b/scene/gui/check_box.h
@@ -38,7 +38,7 @@ class CheckBox : public Button {
struct ThemeCache {
int h_separation = 0;
- int check_v_adjust = 0;
+ int check_v_offset = 0;
Ref<StyleBox> normal_style;
Ref<Texture2D> checked;
diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp
index b01081e31b..9466512699 100644
--- a/scene/gui/check_button.cpp
+++ b/scene/gui/check_button.cpp
@@ -82,17 +82,17 @@ void CheckButton::_update_theme_item_cache() {
Button::_update_theme_item_cache();
theme_cache.h_separation = get_theme_constant(SNAME("h_separation"));
- theme_cache.check_v_adjust = get_theme_constant(SNAME("check_v_adjust"));
+ theme_cache.check_v_offset = get_theme_constant(SNAME("check_v_offset"));
theme_cache.normal_style = get_theme_stylebox(SNAME("normal"));
- theme_cache.checked = get_theme_icon(SNAME("on"));
- theme_cache.unchecked = get_theme_icon(SNAME("off"));
- theme_cache.checked_disabled = get_theme_icon(SNAME("on_disabled"));
- theme_cache.unchecked_disabled = get_theme_icon(SNAME("off_disabled"));
- theme_cache.checked_mirrored = get_theme_icon(SNAME("on_mirrored"));
- theme_cache.unchecked_mirrored = get_theme_icon(SNAME("off_mirrored"));
- theme_cache.checked_disabled_mirrored = get_theme_icon(SNAME("on_disabled_mirrored"));
- theme_cache.unchecked_disabled_mirrored = get_theme_icon(SNAME("off_disabled_mirrored"));
+ theme_cache.checked = get_theme_icon(SNAME("checked"));
+ theme_cache.unchecked = get_theme_icon(SNAME("unchecked"));
+ theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled"));
+ theme_cache.unchecked_disabled = get_theme_icon(SNAME("unchecked_disabled"));
+ theme_cache.checked_mirrored = get_theme_icon(SNAME("checked_mirrored"));
+ theme_cache.unchecked_mirrored = get_theme_icon(SNAME("unchecked_mirrored"));
+ theme_cache.checked_disabled_mirrored = get_theme_icon(SNAME("checked_disabled_mirrored"));
+ theme_cache.unchecked_disabled_mirrored = get_theme_icon(SNAME("unchecked_disabled_mirrored"));
}
void CheckButton::_notification(int p_what) {
@@ -142,7 +142,7 @@ void CheckButton::_notification(int p_what) {
} else {
ofs.x = get_size().width - (tex_size.width + theme_cache.normal_style->get_margin(SIDE_RIGHT));
}
- ofs.y = (get_size().height - tex_size.height) / 2 + theme_cache.check_v_adjust;
+ ofs.y = (get_size().height - tex_size.height) / 2 + theme_cache.check_v_offset;
if (is_pressed()) {
on_tex->draw(ci, ofs);
diff --git a/scene/gui/check_button.h b/scene/gui/check_button.h
index e71472c870..3878c9628a 100644
--- a/scene/gui/check_button.h
+++ b/scene/gui/check_button.h
@@ -38,7 +38,7 @@ class CheckButton : public Button {
struct ThemeCache {
int h_separation = 0;
- int check_v_adjust = 0;
+ int check_v_offset = 0;
Ref<StyleBox> normal_style;
Ref<Texture2D> checked;
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 1ef1801457..94d296977b 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -344,7 +344,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
} else {
if (mb->get_button_index() == MouseButton::LEFT) {
- if (mb->is_command_pressed() && !symbol_lookup_word.is_empty()) {
+ if (mb->is_command_or_control_pressed() && !symbol_lookup_word.is_empty()) {
Vector2i mpos = mb->get_position();
if (is_layout_rtl()) {
mpos.x = get_size().x - mpos.x;
@@ -371,7 +371,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (symbol_lookup_on_click_enabled) {
- if (mm->is_command_pressed() && mm->get_button_mask() == MouseButton::NONE && !is_dragging_cursor()) {
+ if (mm->is_command_or_control_pressed() && mm->get_button_mask() == MouseButton::NONE && !is_dragging_cursor()) {
symbol_lookup_new_word = get_word_at_pos(mpos);
if (symbol_lookup_new_word != symbol_lookup_word) {
emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word);
@@ -432,7 +432,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
/* Allow unicode handling if: */
/* No Modifiers are pressed (except shift) */
- bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
/* AUTO-COMPLETE */
if (code_completion_enabled && k->is_action("ui_text_completion_query", true)) {
@@ -609,72 +609,74 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
/* Text manipulation */
// Overridable actions
-void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) {
- bool had_selection = has_selection();
- String selection_text = (had_selection ? get_selected_text() : "");
+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) {
+ if (p_caret != -1 && p_caret != i) {
+ continue;
+ }
- if (had_selection) {
- begin_complex_operation();
- delete_selection();
- }
+ bool had_selection = has_selection(i);
+ String selection_text = (had_selection ? get_selected_text(i) : "");
- // Remove the old character if in overtype mode and no selection.
- if (is_overtype_mode_enabled() && !had_selection) {
- begin_complex_operation();
+ if (had_selection) {
+ delete_selection(i);
+ }
- /* Make sure we don't try and remove empty space. */
- if (get_caret_column() < get_line(get_caret_line()).length()) {
- remove_text(get_caret_line(), get_caret_column(), get_caret_line(), get_caret_column() + 1);
+ // Remove the old character if in overtype mode and no selection.
+ if (is_overtype_mode_enabled() && !had_selection) {
+ // Make sure we don't try and remove empty space.
+ if (get_caret_column(i) < get_line(get_caret_line(i)).length()) {
+ remove_text(get_caret_line(i), get_caret_column(i), get_caret_line(i), get_caret_column(i) + 1);
+ }
}
- }
- const char32_t chr[2] = { (char32_t)p_unicode, 0 };
+ const char32_t chr[2] = { (char32_t)p_unicode, 0 };
- if (auto_brace_completion_enabled) {
- int cl = get_caret_line();
- int cc = get_caret_column();
+ if (auto_brace_completion_enabled) {
+ int cl = get_caret_line(i);
+ int cc = get_caret_column(i);
- if (had_selection) {
- insert_text_at_caret(chr);
+ if (had_selection) {
+ insert_text_at_caret(chr, i);
- String close_key = get_auto_brace_completion_close_key(chr);
- if (!close_key.is_empty()) {
- insert_text_at_caret(selection_text + close_key);
- set_caret_column(get_caret_column() - 1);
- }
- } else {
- int caret_move_offset = 1;
-
- int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1;
-
- if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) {
- insert_text_at_caret(chr);
- } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) {
- insert_text_at_caret(chr);
- } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) {
- caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length();
- } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) {
- insert_text_at_caret(chr);
+ String close_key = get_auto_brace_completion_close_key(chr);
+ if (!close_key.is_empty()) {
+ insert_text_at_caret(selection_text + close_key, i);
+ set_caret_column(get_caret_column(i) - 1, i == 0, i);
+ }
} else {
- insert_text_at_caret(chr);
+ int caret_move_offset = 1;
+
+ int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1;
+
+ if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) {
+ insert_text_at_caret(chr, i);
+ } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) {
+ insert_text_at_caret(chr, i);
+ } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) {
+ caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length();
+ } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) {
+ insert_text_at_caret(chr, i);
+ } else {
+ insert_text_at_caret(chr, i);
- int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1);
- if (pre_brace_pair != -1) {
- insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key);
+ int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1);
+ if (pre_brace_pair != -1) {
+ insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i);
+ }
}
+ set_caret_column(cc + caret_move_offset, i == 0, i);
}
- set_caret_column(cc + caret_move_offset);
+ } else {
+ insert_text_at_caret(chr, i);
}
- } else {
- insert_text_at_caret(chr);
- }
-
- if ((is_overtype_mode_enabled() && !had_selection) || (had_selection)) {
- end_complex_operation();
}
+ end_action();
}
-void CodeEdit::_backspace_internal() {
+void CodeEdit::_backspace_internal(int p_caret) {
if (!is_editable()) {
return;
}
@@ -684,51 +686,65 @@ void CodeEdit::_backspace_internal() {
return;
}
- int cc = get_caret_column();
- int cl = get_caret_line();
+ begin_complex_operation();
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &i : caret_edit_order) {
+ if (p_caret != -1 && p_caret != i) {
+ continue;
+ }
- if (cc == 0 && cl == 0) {
- return;
- }
+ int cc = get_caret_column(i);
+ int cl = get_caret_line(i);
- if (cl > 0 && _is_line_hidden(cl - 1)) {
- unfold_line(get_caret_line() - 1);
- }
+ if (cc == 0 && cl == 0) {
+ continue;
+ }
- int prev_line = cc ? cl : cl - 1;
- int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
+ if (cl > 0 && _is_line_hidden(cl - 1)) {
+ unfold_line(get_caret_line(i) - 1);
+ }
- merge_gutters(prev_line, cl);
+ int prev_line = cc ? cl : cl - 1;
+ int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
- if (auto_brace_completion_enabled && cc > 0) {
- int idx = _get_auto_brace_pair_open_at_pos(cl, cc);
- if (idx != -1) {
- prev_column = cc - auto_brace_completion_pairs[idx].open_key.length();
+ merge_gutters(prev_line, cl);
- if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) {
- remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length());
- } else {
- remove_text(prev_line, prev_column, cl, cc);
+ if (auto_brace_completion_enabled && cc > 0) {
+ int idx = _get_auto_brace_pair_open_at_pos(cl, cc);
+ if (idx != -1) {
+ prev_column = cc - auto_brace_completion_pairs[idx].open_key.length();
+
+ if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) {
+ remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length());
+ } else {
+ 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 + auto_brace_completion_pairs[idx].close_key.length());
+ continue;
}
- set_caret_line(prev_line, false, true);
- set_caret_column(prev_column);
- return;
}
- }
- // For space indentation we need to do a simple unindent if there are no chars to the left, acting in 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;
+ // For space indentation we need to do a simple unindent if there are no chars to the left, acting in 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;
+ }
}
- }
- remove_text(prev_line, prev_column, cl, cc);
+ 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);
- set_caret_line(prev_line, false, true);
- set_caret_column(prev_column);
+ adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
+ }
+ merge_overlapping_carets();
+ end_complex_operation();
}
/* Indent management */
@@ -803,10 +819,15 @@ void CodeEdit::do_indent() {
return;
}
- int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column());
- if (spaces_to_add > 0) {
- insert_text_at_caret(String(" ").repeat(spaces_to_add));
+ begin_complex_operation();
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &i : caret_edit_order) {
+ 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_complex_operation();
}
void CodeEdit::indent_lines() {
@@ -815,48 +836,49 @@ 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);
+
+ // 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--;
+ }
+ }
- /* 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();
- int end_line = start_line;
- if (has_selection()) {
- start_line = get_selection_from_line();
- end_line = get_selection_to_line();
+ for (int i = start_line; i <= end_line; i++) {
+ const String line_text = get_line(i);
+ if (line_text.size() == 0 && has_selection(c)) {
+ continue;
+ }
- /* Ignore the last line if the selection is not past the first column. */
- if (get_selection_to_column() == 0) {
- selection_offset = 0;
- end_line--;
- }
- }
+ if (!indent_using_spaces) {
+ set_line(i, '\t' + line_text);
+ continue;
+ }
- for (int i = start_line; i <= end_line; i++) {
- const String line_text = get_line(i);
- if (line_text.size() == 0 && has_selection()) {
- continue;
+ // 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;
}
- if (!indent_using_spaces) {
- set_line(i, '\t' + line_text);
- continue;
+ // 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);
}
-
- /* 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()) {
- select(start_line, get_selection_from_column() + selection_offset, get_selection_to_line(), get_selection_to_column() + selection_offset);
+ set_caret_column(get_caret_column(c) + selection_offset, false, c);
}
- set_caret_column(get_caret_column() + selection_offset, false);
-
end_complex_operation();
}
@@ -872,30 +894,36 @@ void CodeEdit::do_unindent() {
return;
}
- int cl = get_caret_line();
- const String &line = get_line(cl);
-
- if (line[cc - 1] == '\t') {
- remove_text(cl, cc - 1, cl, cc);
- set_caret_column(MAX(0, cc - 1));
- return;
- }
+ begin_complex_operation();
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &c : caret_edit_order) {
+ int cl = get_caret_line(c);
+ const String &line = get_line(cl);
+
+ if (line[cc - 1] == '\t') {
+ remove_text(cl, cc - 1, cl, cc);
+ set_caret_column(MAX(0, cc - 1), c == 0, c);
+ adjust_carets_after_edit(c, cl, cc, cl, cc - 1);
+ continue;
+ }
- if (line[cc - 1] != ' ') {
- return;
- }
+ if (line[cc - 1] != ' ') {
+ continue;
+ }
- int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
- if (spaces_to_remove > 0) {
- for (int i = 1; i <= spaces_to_remove; i++) {
- if (line[cc - i] != ' ') {
- spaces_to_remove = i - 1;
- break;
+ int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
+ if (spaces_to_remove > 0) {
+ for (int i = 1; i <= spaces_to_remove; i++) {
+ if (line[cc - i] != ' ') {
+ spaces_to_remove = i - 1;
+ break;
+ }
}
+ remove_text(cl, cc - spaces_to_remove, cl, cc);
+ set_caret_column(MAX(0, cc - spaces_to_remove), c == 0, c);
}
- remove_text(cl, cc - spaces_to_remove, cl, cc);
- set_caret_column(MAX(0, cc - spaces_to_remove));
}
+ end_complex_operation();
}
void CodeEdit::unindent_lines() {
@@ -905,71 +933,73 @@ void CodeEdit::unindent_lines() {
begin_complex_operation();
- /* 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();
-
- int start_line = get_caret_line();
- int end_line = start_line;
- if (has_selection()) {
- start_line = get_selection_from_line();
- end_line = get_selection_to_line();
-
- /* Ignore the last line if the selection is not past the first column. */
- initial_selection_end_column = get_selection_to_column();
- if (initial_selection_end_column == 0) {
- end_line--;
+ 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;
+ 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);
+ for (int i = start_line; i <= end_line; i++) {
+ String line_text = get_line(i);
- if (line_text.begins_with("\t")) {
- line_text = line_text.substr(1, line_text.length());
+ if (line_text.begins_with("\t")) {
+ line_text = line_text.substr(1, line_text.length());
- set_line(i, line_text);
- removed_characters = 1;
+ 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;
- }
+ 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. */
- 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());
+ 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.
+ 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;
+ 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;
+ first_line_edited = (i == start_line) ? true : first_line_edited;
+ last_line_edited = (i == end_line) ? true : last_line_edited;
+ }
}
- }
- if (has_selection()) {
- /* Fix selection being off by one on the first line. */
- if (first_line_edited) {
- select(get_selection_from_line(), get_selection_from_column() - removed_characters, get_selection_to_line(), initial_selection_end_column);
- }
+ 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(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters);
+ // 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);
}
- set_caret_column(initial_cursor_column - removed_characters, false);
-
end_complex_operation();
}
@@ -990,106 +1020,108 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
return;
}
- /* 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() : get_line(get_caret_line()).length();
- const int cl = get_caret_line();
-
- const String line = get_line(cl);
+ begin_complex_operation();
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &i : caret_edit_order) {
+ // 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);
- String ins = "\n";
+ const String line = get_line(cl);
- /* Append current indentation. */
- int space_count = 0;
- int line_col = 0;
- for (; line_col < cc; line_col++) {
- if (line[line_col] == '\t') {
- ins += indent_text;
- space_count = 0;
- continue;
- }
+ String ins = "\n";
- if (line[line_col] == ' ') {
- space_count++;
-
- if (space_count == indent_size) {
+ // Append current indentation.
+ int space_count = 0;
+ int line_col = 0;
+ for (; line_col < cc; line_col++) {
+ if (line[line_col] == '\t') {
ins += indent_text;
space_count = 0;
+ continue;
}
- continue;
- }
- break;
- }
- if (is_line_folded(cl)) {
- unfold_line(cl);
- }
+ if (line[line_col] == ' ') {
+ space_count++;
- /* Indent once again if the previous line needs it, ie ':'. */
- /* Then add an addition new line for any closing pairs aka '()'. */
- /* Skip this in comments or if we are going above. */
- bool brace_indent = false;
- if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) {
- bool should_indent = false;
- char32_t indent_char = ' ';
-
- for (; line_col < cc; line_col++) {
- char32_t c = line[line_col];
- if (auto_indent_prefixes.has(c)) {
- should_indent = true;
- indent_char = c;
+ if (space_count == indent_size) {
+ ins += indent_text;
+ space_count = 0;
+ }
continue;
}
+ break;
+ }
- /* Make sure this is the last char, trailing whitespace or comments are okay. */
- /* Increment column for comments because the delimiter (#) should be ignored. */
- if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) {
- should_indent = false;
- }
+ if (is_line_folded(cl)) {
+ unfold_line(cl);
}
- if (should_indent) {
- ins += indent_text;
+ // Indent once again if the previous line needs it, ie ':'.
+ // Then add an addition new line for any closing pairs aka '()'.
+ // Skip this in comments or if we are going above.
+ bool brace_indent = false;
+ if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) {
+ bool should_indent = false;
+ char32_t indent_char = ' ';
- String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char));
- if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) {
- /* No need to move the brace below if we are not taking the text with us. */
- if (p_split_current_line) {
- brace_indent = true;
- ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2);
- } else {
- brace_indent = false;
- ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2);
+ for (; line_col < cc; line_col++) {
+ char32_t c = line[line_col];
+ if (auto_indent_prefixes.has(c)) {
+ should_indent = true;
+ indent_char = c;
+ continue;
+ }
+
+ // Make sure this is the last char, trailing whitespace or comments are okay.
+ // Increment column for comments because the delimiter (#) should be ignored.
+ if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) {
+ should_indent = false;
}
}
- }
- }
- begin_complex_operation();
+ if (should_indent) {
+ ins += indent_text;
+
+ String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char));
+ if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) {
+ // No need to move the brace below if we are not taking the text with us.
+ if (p_split_current_line) {
+ brace_indent = true;
+ ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2);
+ } else {
+ brace_indent = false;
+ ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2);
+ }
+ }
+ }
+ }
- bool first_line = false;
- if (!p_split_current_line) {
- deselect();
+ bool first_line = false;
+ if (!p_split_current_line) {
+ deselect(i);
- if (p_above) {
- if (cl > 0) {
- set_caret_line(cl - 1, false);
- set_caret_column(get_line(get_caret_line()).length());
+ 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(0);
- first_line = true;
+ set_caret_column(line.length(), i == 0, i);
}
- } else {
- set_caret_column(line.length());
}
- }
- insert_text_at_caret(ins);
+ insert_text_at_caret(ins, i);
- if (first_line) {
- set_caret_line(0);
- } else if (brace_indent) {
- set_caret_line(get_caret_line() - 1, false);
- set_caret_column(get_line(get_caret_line()).length());
+ if (first_line) {
+ set_caret_line(0, i == 0, true, 0, i);
+ } else if (brace_indent) {
+ 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_complex_operation();
@@ -1216,30 +1248,39 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const {
}
void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
+ bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
+
if (draw_breakpoints && breakpoint_icon.is_valid()) {
bool hovering = p_region.has_point(get_local_mouse_pos());
bool breakpointed = is_line_breakpointed(p_line);
- if (breakpointed || (hovering && !is_dragging_cursor())) {
+ if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) {
int padding = p_region.size.x / 6;
Rect2 icon_region = p_region;
icon_region.position += Point2(padding, padding);
icon_region.size -= Point2(padding, padding) * 2;
- // Darken icon when hovering & not yet breakpointed.
- Color use_color = hovering && !breakpointed ? breakpoint_color.darkened(0.4) : breakpoint_color;
+ // Darken icon when hovering, shift not pressed & not yet breakpointed.
+ Color use_color = hovering && !breakpointed && !shift_pressed ? breakpoint_color.darkened(0.4) : breakpoint_color;
breakpoint_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
}
}
- if (draw_bookmarks && is_line_bookmarked(p_line) && bookmark_icon.is_valid()) {
- int horizontal_padding = p_region.size.x / 2;
- int vertical_padding = p_region.size.y / 4;
+ if (draw_bookmarks && bookmark_icon.is_valid()) {
+ bool hovering = p_region.has_point(get_local_mouse_pos());
+ bool bookmarked = is_line_bookmarked(p_line);
- Rect2 bookmark_region = p_region;
- bookmark_region.position += Point2(horizontal_padding, 0);
- bookmark_region.size -= Point2(horizontal_padding * 1.1, vertical_padding);
- bookmark_icon->draw_rect(get_canvas_item(), bookmark_region, false, bookmark_color);
+ if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) {
+ int horizontal_padding = p_region.size.x / 2;
+ int vertical_padding = p_region.size.y / 4;
+ Rect2 icon_region = p_region;
+ icon_region.position += Point2(horizontal_padding, 0);
+ icon_region.size -= Point2(horizontal_padding * 1.1, vertical_padding);
+
+ // Darken icon when hovering, shift pressed & not yet bookmarked.
+ Color use_color = hovering && !bookmarked && shift_pressed ? bookmark_color.darkened(0.4) : bookmark_color;
+ bookmark_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
+ }
}
if (draw_executing_lines && is_line_executing(p_line) && executing_line_icon.is_valid()) {
@@ -1513,22 +1554,26 @@ void CodeEdit::fold_line(int p_line) {
_set_line_as_hidden(i, true);
}
- /* Fix selection. */
- if (has_selection()) {
- if (_is_line_hidden(get_selection_from_line()) && _is_line_hidden(get_selection_to_line())) {
- deselect();
- } else if (_is_line_hidden(get_selection_from_line())) {
- select(p_line, 9999, get_selection_to_line(), get_selection_to_column());
- } else if (_is_line_hidden(get_selection_to_line())) {
- select(get_selection_from_line(), get_selection_from_column(), p_line, 9999);
+ 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())) {
- set_caret_line(p_line, false, false);
- set_caret_column(get_line(p_line).length(), false);
+ // 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();
}
@@ -1941,98 +1986,108 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
return;
}
+ char32_t caret_last_completion_char;
begin_complex_operation();
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &i : caret_edit_order) {
+ int caret_line = get_caret_line(i);
+
+ const String &insert_text = code_completion_options[code_completion_current_selected].insert_text;
+ const String &display_text = code_completion_options[code_completion_current_selected].display;
+
+ if (p_replace) {
+ // Find end of current section.
+ const String line = get_line(caret_line);
+ int caret_col = get_caret_column(i);
+ int caret_remove_line = caret_line;
+
+ bool merge_text = true;
+ int in_string = is_in_string(caret_line, caret_col);
+ if (in_string != -1) {
+ Point2 string_end = get_delimiter_end_position(caret_line, caret_col);
+ if (string_end.x != -1) {
+ merge_text = false;
+ caret_remove_line = string_end.y;
+ caret_col = string_end.x - 1;
+ }
+ }
- int caret_line = get_caret_line();
-
- const String &insert_text = code_completion_options[code_completion_current_selected].insert_text;
- const String &display_text = code_completion_options[code_completion_current_selected].display;
-
- if (p_replace) {
- /* Find end of current section */
- const String line = get_line(caret_line);
- int caret_col = get_caret_column();
- int caret_remove_line = caret_line;
-
- bool merge_text = true;
- int in_string = is_in_string(caret_line, caret_col);
- if (in_string != -1) {
- Point2 string_end = get_delimiter_end_position(caret_line, caret_col);
- if (string_end.x != -1) {
- merge_text = false;
- caret_remove_line = string_end.y;
- caret_col = string_end.x - 1;
+ if (merge_text) {
+ for (; caret_col < line.length(); caret_col++) {
+ if (is_symbol(line[caret_col])) {
+ break;
+ }
+ }
}
- }
- if (merge_text) {
- for (; caret_col < line.length(); caret_col++) {
- if (is_symbol(line[caret_col])) {
+ // 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.
+ const String line = get_line(caret_line);
+ int caret_col = get_caret_column(i);
+ int matching_chars = code_completion_base.length();
+ for (; matching_chars <= insert_text.length(); matching_chars++) {
+ if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) {
break;
}
+ caret_col++;
}
+
+ // 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);
+ set_caret_column(caret_col, false, i);
+ insert_text_at_caret(insert_text.substr(matching_chars), i);
}
- /* Replace. */
- remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_remove_line, caret_col);
- set_caret_column(get_caret_column() - code_completion_base.length(), false);
- insert_text_at_caret(insert_text);
- } else {
- /* Get first non-matching char. */
+ //* Handle merging of symbols eg strings, brackets.
const String line = get_line(caret_line);
- int caret_col = get_caret_column();
- int matching_chars = code_completion_base.length();
- for (; matching_chars <= insert_text.length(); matching_chars++) {
- if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) {
- break;
- }
- caret_col++;
+ char32_t next_char = line[get_caret_column(i)];
+ char32_t last_completion_char = insert_text[insert_text.length() - 1];
+ if (i == 0) {
+ caret_last_completion_char = last_completion_char;
}
+ char32_t last_completion_char_display = display_text[display_text.length() - 1];
- /* Remove base completion text. */
- remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_line, get_caret_column());
- set_caret_column(get_caret_column() - code_completion_base.length(), false);
-
- /* Merge with text. */
- insert_text_at_caret(insert_text.substr(0, code_completion_base.length()));
- set_caret_column(caret_col, false);
- insert_text_at_caret(insert_text.substr(matching_chars));
- }
+ int pre_brace_pair = get_caret_column(i) > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i)) : -1;
+ int post_brace_pair = get_caret_column(i) < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i)) : -1;
- /* Handle merging of symbols eg strings, brackets. */
- const String line = get_line(caret_line);
- char32_t next_char = line[get_caret_column()];
- char32_t last_completion_char = insert_text[insert_text.length() - 1];
- char32_t last_completion_char_display = display_text[display_text.length() - 1];
-
- int pre_brace_pair = get_caret_column() > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column()) : -1;
- int post_brace_pair = get_caret_column() < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column()) : -1;
-
- if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) {
- remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1);
- }
+ if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) {
+ 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);
+ }
- if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) {
- remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1);
- } else if (auto_brace_completion_enabled && pre_brace_pair != -1 && post_brace_pair == -1) {
- insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key);
- set_caret_column(get_caret_column() - auto_brace_completion_pairs[pre_brace_pair].close_key.length());
- }
+ if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) {
+ 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 && post_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);
+ }
- if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column() > 0 && get_caret_column() < get_line(caret_line).length()) {
- pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column() + 1);
- if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) {
- remove_text(caret_line, get_caret_column() - 2, caret_line, get_caret_column());
- if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1) != pre_brace_pair) {
- set_caret_column(get_caret_column() - 1);
+ if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column(i) > 0 && get_caret_column(i) < get_line(caret_line).length()) {
+ 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);
+ }
}
}
}
-
end_complex_operation();
cancel_code_completion();
- if (code_completion_prefixes.has(last_completion_char)) {
+ if (code_completion_prefixes.has(caret_last_completion_char)) {
request_code_completion();
}
}
@@ -2378,14 +2433,19 @@ int CodeEdit::_get_auto_brace_pair_close_at_pos(int p_line, int p_col) {
/* Gutters */
void CodeEdit::_gutter_clicked(int p_line, int p_gutter) {
+ bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
+
if (p_gutter == main_gutter) {
- if (draw_breakpoints) {
+ if (draw_breakpoints && !shift_pressed) {
set_line_as_breakpoint(p_line, !is_line_breakpointed(p_line));
+ } else if (draw_bookmarks && shift_pressed) {
+ set_line_as_bookmarked(p_line, !is_line_bookmarked(p_line));
}
return;
}
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);
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 2065f3e681..09c7ef80bf 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -261,8 +261,8 @@ protected:
/* Text manipulation */
// Overridable actions
- virtual void _handle_unicode_input_internal(const uint32_t p_unicode) override;
- virtual void _backspace_internal() override;
+ virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) override;
+ virtual void _backspace_internal(int p_caret) override;
GDVIRTUAL1(_confirm_code_completion, bool)
GDVIRTUAL1(_request_code_completion, bool)
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 2d04132623..36b770408e 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -486,12 +486,15 @@ void ColorPicker::_html_submitted(const String &p_html) {
return;
}
- float last_alpha = color.a;
+ Color previous_color = color;
color = Color::html(p_html);
if (!is_editing_alpha()) {
- color.a = last_alpha;
+ color.a = previous_color.a;
}
+ if (color == previous_color) {
+ return;
+ }
if (!is_inside_tree()) {
return;
}
@@ -1399,7 +1402,7 @@ void ColorPicker::_screen_pick_pressed() {
} else {
screen->show();
}
- screen->raise();
+ screen->move_to_front();
#ifndef _MSC_VER
#warning show modal no longer works, needs to be converted to a popup
#endif
diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp
index 5512c0f1fd..3c29c37479 100644
--- a/scene/gui/container.cpp
+++ b/scene/gui/container.cpp
@@ -192,8 +192,8 @@ void Container::_notification(int p_what) {
}
}
-TypedArray<String> Container::get_configuration_warnings() const {
- TypedArray<String> warnings = Control::get_configuration_warnings();
+PackedStringArray Container::get_configuration_warnings() const {
+ PackedStringArray warnings = Control::get_configuration_warnings();
if (get_class() == "Container" && get_script().is_null()) {
warnings.push_back(RTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."));
diff --git a/scene/gui/container.h b/scene/gui/container.h
index 9ec4ad3200..21bdb95186 100644
--- a/scene/gui/container.h
+++ b/scene/gui/container.h
@@ -63,7 +63,7 @@ public:
virtual Vector<int> get_allowed_size_flags_horizontal() const;
virtual Vector<int> get_allowed_size_flags_vertical() const;
- TypedArray<String> get_configuration_warnings() const override;
+ PackedStringArray get_configuration_warnings() const override;
Container();
};
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index bcf5a8a47f..dc9294df6d 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -213,8 +213,8 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List
}
}
-TypedArray<String> Control::get_configuration_warnings() const {
- TypedArray<String> warnings = Node::get_configuration_warnings();
+PackedStringArray Control::get_configuration_warnings() const {
+ PackedStringArray warnings = Node::get_configuration_warnings();
if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) {
warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\"."));
@@ -1564,7 +1564,7 @@ Size2 Control::get_minimum_size() const {
return Vector2();
}
-void Control::set_custom_minimum_size(const Size2 &p_custom) {
+void Control::set_custom_minimum_size(const Size2i &p_custom) {
if (p_custom == data.custom_minimum_size) {
return;
}
@@ -1572,7 +1572,7 @@ void Control::set_custom_minimum_size(const Size2 &p_custom) {
update_minimum_size();
}
-Size2 Control::get_custom_minimum_size() const {
+Size2i Control::get_custom_minimum_size() const {
return data.custom_minimum_size;
}
@@ -2225,7 +2225,19 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
void Control::set_default_cursor_shape(CursorShape p_shape) {
ERR_FAIL_INDEX(int(p_shape), CURSOR_MAX);
+ if (data.default_cursor == p_shape) {
+ return;
+ }
data.default_cursor = p_shape;
+
+ if (!is_inside_tree()) {
+ return;
+ }
+ if (!get_global_rect().has_point(get_global_mouse_position())) {
+ return;
+ }
+
+ get_viewport()->get_base_window()->update_mouse_cursor_shape();
}
Control::CursorShape Control::get_default_cursor_shape() const {
@@ -2821,7 +2833,7 @@ void Control::_notification(int p_notification) {
case NOTIFICATION_READY: {
#ifdef DEBUG_ENABLED
- connect("ready", callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONESHOT);
+ connect("ready", callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONE_SHOT);
#endif
} break;
@@ -3132,7 +3144,7 @@ void Control::_bind_methods() {
ADD_GROUP("Layout", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction");
ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode");
ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION);
diff --git a/scene/gui/control.h b/scene/gui/control.h
index 3fb1494d66..ee6443c81c 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -200,7 +200,7 @@ private:
int h_size_flags = SIZE_FILL;
int v_size_flags = SIZE_FILL;
real_t expand = 1.0;
- Point2 custom_minimum_size;
+ Point2i custom_minimum_size;
// Input events and rendering.
@@ -387,7 +387,7 @@ public:
// Editor integration.
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
- TypedArray<String> get_configuration_warnings() const override;
+ PackedStringArray get_configuration_warnings() const override;
virtual bool is_text_field() const;
@@ -460,8 +460,8 @@ public:
virtual Size2 get_minimum_size() const;
virtual Size2 get_combined_minimum_size() const;
- void set_custom_minimum_size(const Size2 &p_custom);
- Size2 get_custom_minimum_size() const;
+ void set_custom_minimum_size(const Size2i &p_custom);
+ Size2i get_custom_minimum_size() const;
// Container sizing.
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index 0a1aeeda71..f5edaf02d8 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -54,16 +54,23 @@ void AcceptDialog::_update_theme_item_cache() {
Window::_update_theme_item_cache();
theme_cache.panel_style = get_theme_stylebox(SNAME("panel"));
- theme_cache.margin = get_theme_constant(SNAME("margin"));
- theme_cache.button_margin = get_theme_constant(SNAME("button_margin"));
+ theme_cache.buttons_separation = get_theme_constant(SNAME("buttons_separation"));
}
void AcceptDialog::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_VISIBILITY_CHANGED: {
+ case NOTIFICATION_POST_ENTER_TREE: {
if (is_visible()) {
get_ok_button()->grab_focus();
+ }
+ } break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (is_visible()) {
+ if (get_ok_button()->is_inside_tree()) {
+ get_ok_button()->grab_focus();
+ }
_update_child_rects();
+
parent_visible = get_parent_visible_window();
if (parent_visible) {
parent_visible->connect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused));
@@ -77,10 +84,12 @@ void AcceptDialog::_notification(int p_what) {
} break;
case NOTIFICATION_THEME_CHANGED: {
- bg->add_theme_style_override("panel", theme_cache.panel_style);
+ bg_panel->add_theme_style_override("panel", theme_cache.panel_style);
- label->set_begin(Point2(theme_cache.margin, theme_cache.margin));
- label->set_end(Point2(-theme_cache.margin, -theme_cache.button_margin - 10));
+ child_controls_changed();
+ if (is_visible()) {
+ _update_child_rects();
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -137,14 +146,16 @@ void AcceptDialog::_cancel_pressed() {
}
String AcceptDialog::get_text() const {
- return label->get_text();
+ return message_label->get_text();
}
void AcceptDialog::set_text(String p_text) {
- if (label->get_text() == p_text) {
+ if (message_label->get_text() == p_text) {
return;
}
- label->set_text(p_text);
+
+ message_label->set_text(p_text);
+
child_controls_changed();
if (is_visible()) {
_update_child_rects();
@@ -168,19 +179,24 @@ bool AcceptDialog::get_close_on_escape() const {
}
void AcceptDialog::set_autowrap(bool p_autowrap) {
- label->set_autowrap_mode(p_autowrap ? TextServer::AUTOWRAP_WORD : TextServer::AUTOWRAP_OFF);
+ message_label->set_autowrap_mode(p_autowrap ? TextServer::AUTOWRAP_WORD : TextServer::AUTOWRAP_OFF);
}
bool AcceptDialog::has_autowrap() {
- return label->get_autowrap_mode() != TextServer::AUTOWRAP_OFF;
+ return message_label->get_autowrap_mode() != TextServer::AUTOWRAP_OFF;
}
void AcceptDialog::set_ok_button_text(String p_ok_button_text) {
- ok->set_text(p_ok_button_text);
+ ok_button->set_text(p_ok_button_text);
+
+ child_controls_changed();
+ if (is_visible()) {
+ _update_child_rects();
+ }
}
String AcceptDialog::get_ok_button_text() const {
- return ok->get_text();
+ return ok_button->get_text();
}
void AcceptDialog::register_text_enter(Control *p_line_edit) {
@@ -192,68 +208,79 @@ void AcceptDialog::register_text_enter(Control *p_line_edit) {
}
void AcceptDialog::_update_child_rects() {
- Size2 label_size = label->get_minimum_size();
- if (label->get_text().is_empty()) {
- label_size.height = 0;
- }
-
Size2 size = get_size();
- Size2 hminsize = hbc->get_combined_minimum_size();
+ float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT);
+ float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM);
- Vector2 cpos(theme_cache.margin, theme_cache.margin + label_size.height);
- Vector2 csize(size.x - theme_cache.margin * 2, size.y - theme_cache.margin * 3 - hminsize.y - label_size.height);
+ // Fill the entire size of the window with the background.
+ bg_panel->set_position(Point2());
+ bg_panel->set_size(size);
+
+ // Place the buttons from the bottom edge to their minimum required size.
+ Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size();
+ Size2 buttons_size = Size2(size.x - h_margins, buttons_minsize.y);
+ Point2 buttons_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), size.y - theme_cache.panel_style->get_margin(SIDE_BOTTOM) - buttons_size.y);
+ buttons_hbox->set_position(buttons_position);
+ buttons_hbox->set_size(buttons_size);
+
+ // Place the content from the top to fill the rest of the space (minus the separation).
+ Point2 content_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), theme_cache.panel_style->get_margin(SIDE_TOP));
+ Size2 content_size = Size2(size.x - h_margins, size.y - v_margins - buttons_size.y - theme_cache.buttons_separation);
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
if (!c) {
continue;
}
-
- if (c == hbc || c == label || c == bg || c->is_set_as_top_level()) {
+ if (c == buttons_hbox || c == bg_panel || c->is_set_as_top_level()) {
continue;
}
- c->set_position(cpos);
- c->set_size(csize);
+ c->set_position(content_position);
+ c->set_size(content_size);
}
-
- cpos.y += csize.y + theme_cache.margin;
- csize.y = hminsize.y;
-
- hbc->set_position(cpos);
- hbc->set_size(csize);
-
- bg->set_position(Point2());
- bg->set_size(size);
}
Size2 AcceptDialog::_get_contents_minimum_size() const {
- Size2 minsize = label->get_combined_minimum_size();
-
+ // First, we then iterate over the label and any other custom controls
+ // to try and find the size that encompasses all content.
+ Size2 content_minsize;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
if (!c) {
continue;
}
- if (c == hbc || c == label || c->is_set_as_top_level()) {
+ // Buttons will be included afterwards.
+ // The panel only displays the stylebox and doesn't contribute to the size.
+ if (c == buttons_hbox || c == bg_panel || c->is_set_as_top_level()) {
continue;
}
- Size2 cminsize = c->get_combined_minimum_size();
- minsize.x = MAX(cminsize.x, minsize.x);
- minsize.y = MAX(cminsize.y, minsize.y);
+ Size2 child_minsize = c->get_combined_minimum_size();
+ content_minsize.x = MAX(child_minsize.x, content_minsize.x);
+ content_minsize.y = MAX(child_minsize.y, content_minsize.y);
+ }
+
+ // Then we take the background panel as it provides the offsets,
+ // which are always added to the minimum size.
+ if (theme_cache.panel_style.is_valid()) {
+ content_minsize += theme_cache.panel_style->get_minimum_size();
}
- Size2 hminsize = hbc->get_combined_minimum_size();
- minsize.x = MAX(hminsize.x, minsize.x);
- minsize.y += hminsize.y;
- minsize.x += theme_cache.margin * 2;
- minsize.y += theme_cache.margin * 3; //one as separation between hbc and child
+ // Then we add buttons. Horizontally we're interested in whichever
+ // value is the biggest. Vertically buttons add to the overall size.
+ Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size();
+ content_minsize.x = MAX(buttons_minsize.x, content_minsize.x);
+ content_minsize.y += buttons_minsize.y;
+ // Plus there is a separation size added on top.
+ content_minsize.y += theme_cache.buttons_separation;
- Size2 wmsize = get_min_size();
- minsize.x = MAX(wmsize.x, minsize.x);
- return minsize;
+ // Last, we make sure that we aren't below the minimum window size.
+ Size2 window_minsize = get_min_size();
+ content_minsize.x = MAX(window_minsize.x, content_minsize.x);
+ content_minsize.y = MAX(window_minsize.y, content_minsize.y);
+ return content_minsize;
}
void AcceptDialog::_custom_action(const String &p_action) {
@@ -264,13 +291,19 @@ void AcceptDialog::_custom_action(const String &p_action) {
Button *AcceptDialog::add_button(const String &p_text, bool p_right, const String &p_action) {
Button *button = memnew(Button);
button->set_text(p_text);
+
if (p_right) {
- hbc->add_child(button);
- hbc->add_spacer();
+ buttons_hbox->add_child(button);
+ buttons_hbox->add_spacer();
} else {
- hbc->add_child(button);
- hbc->move_child(button, 0);
- hbc->add_spacer(true);
+ buttons_hbox->add_child(button);
+ buttons_hbox->move_child(button, 0);
+ buttons_hbox->add_spacer(true);
+ }
+
+ child_controls_changed();
+ if (is_visible()) {
+ _update_child_rects();
}
if (!p_action.is_empty()) {
@@ -285,24 +318,19 @@ Button *AcceptDialog::add_cancel_button(const String &p_cancel) {
if (p_cancel.is_empty()) {
c = "Cancel";
}
+
Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c);
+
b->connect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed));
+
return b;
}
void AcceptDialog::remove_button(Control *p_button) {
Button *button = Object::cast_to<Button>(p_button);
ERR_FAIL_NULL(button);
- ERR_FAIL_COND_MSG(button->get_parent() != hbc, vformat("Cannot remove button %s as it does not belong to this dialog.", button->get_name()));
- ERR_FAIL_COND_MSG(button == ok, "Cannot remove dialog's OK button.");
-
- Node *right_spacer = hbc->get_child(button->get_index() + 1);
- // Should always be valid but let's avoid crashing
- if (right_spacer) {
- hbc->remove_child(right_spacer);
- memdelete(right_spacer);
- }
- hbc->remove_child(button);
+ ERR_FAIL_COND_MSG(button->get_parent() != buttons_hbox, vformat("Cannot remove button %s as it does not belong to this dialog.", button->get_name()));
+ ERR_FAIL_COND_MSG(button == ok_button, "Cannot remove dialog's OK button.");
if (button->is_connected("pressed", callable_mp(this, &AcceptDialog::_custom_action))) {
button->disconnect("pressed", callable_mp(this, &AcceptDialog::_custom_action));
@@ -310,6 +338,19 @@ void AcceptDialog::remove_button(Control *p_button) {
if (button->is_connected("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed))) {
button->disconnect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed));
}
+
+ Node *right_spacer = buttons_hbox->get_child(button->get_index() + 1);
+ // Should always be valid but let's avoid crashing.
+ if (right_spacer) {
+ buttons_hbox->remove_child(right_spacer);
+ memdelete(right_spacer);
+ }
+ buttons_hbox->remove_child(button);
+
+ child_controls_changed();
+ if (is_visible()) {
+ _update_child_rects();
+ }
}
void AcceptDialog::_bind_methods() {
@@ -355,25 +396,25 @@ AcceptDialog::AcceptDialog() {
set_exclusive(true);
set_clamp_to_embedder(true);
- bg = memnew(Panel);
- add_child(bg, false, INTERNAL_MODE_FRONT);
+ bg_panel = memnew(Panel);
+ add_child(bg_panel, false, INTERNAL_MODE_FRONT);
- hbc = memnew(HBoxContainer);
+ buttons_hbox = memnew(HBoxContainer);
- label = memnew(Label);
- label->set_anchor(SIDE_RIGHT, Control::ANCHOR_END);
- label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
- add_child(label, false, INTERNAL_MODE_FRONT);
+ message_label = memnew(Label);
+ message_label->set_anchor(SIDE_RIGHT, Control::ANCHOR_END);
+ message_label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
+ add_child(message_label, false, INTERNAL_MODE_FRONT);
- add_child(hbc, false, INTERNAL_MODE_FRONT);
+ add_child(buttons_hbox, false, INTERNAL_MODE_FRONT);
- hbc->add_spacer();
- ok = memnew(Button);
- ok->set_text("OK");
- hbc->add_child(ok);
- hbc->add_spacer();
+ buttons_hbox->add_spacer();
+ ok_button = memnew(Button);
+ ok_button->set_text("OK");
+ buttons_hbox->add_child(ok_button);
+ buttons_hbox->add_spacer();
- ok->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed));
+ ok_button->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed));
set_title(TTRC("Alert!"));
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
index 8ba9c93861..81e82d851e 100644
--- a/scene/gui/dialogs.h
+++ b/scene/gui/dialogs.h
@@ -45,17 +45,18 @@ class AcceptDialog : public Window {
GDCLASS(AcceptDialog, Window);
Window *parent_visible = nullptr;
- Panel *bg = nullptr;
- HBoxContainer *hbc = nullptr;
- Label *label = nullptr;
- Button *ok = nullptr;
+
+ Panel *bg_panel = nullptr;
+ Label *message_label = nullptr;
+ HBoxContainer *buttons_hbox = nullptr;
+ Button *ok_button = nullptr;
+
bool hide_on_ok = true;
bool close_on_escape = true;
struct ThemeCache {
Ref<StyleBox> panel_style;
- int margin = 0;
- int button_margin = 0;
+ int buttons_separation = 0;
} theme_cache;
void _custom_action(const String &p_action);
@@ -82,12 +83,12 @@ protected:
void _cancel_pressed();
public:
- Label *get_label() { return label; }
+ Label *get_label() { return message_label; }
static void set_swap_cancel_ok(bool p_swap);
void register_text_enter(Control *p_line_edit);
- Button *get_ok_button() { return ok; }
+ Button *get_ok_button() { return ok_button; }
Button *add_button(const String &p_text, bool p_right = false, const String &p_action = "");
Button *add_cancel_button(const String &p_cancel = "");
void remove_button(Control *p_button);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index a0cf5f5970..cf7f439aef 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -70,9 +70,9 @@ void FileDialog::_update_theme_item_cache() {
theme_cache.folder = get_theme_icon(SNAME("folder"));
theme_cache.file = get_theme_icon(SNAME("file"));
- theme_cache.folder_icon_modulate = get_theme_color(SNAME("folder_icon_modulate"));
- theme_cache.file_icon_modulate = get_theme_color(SNAME("file_icon_modulate"));
- theme_cache.files_disabled = get_theme_color(SNAME("files_disabled"));
+ theme_cache.folder_icon_color = get_theme_color(SNAME("folder_icon_color"));
+ theme_cache.file_icon_color = get_theme_color(SNAME("file_icon_color"));
+ theme_cache.file_disabled_color = get_theme_color(SNAME("file_disabled_color"));
// TODO: Define own colors?
theme_cache.icon_normal_color = get_theme_color(SNAME("font_color"), SNAME("Button"));
@@ -87,6 +87,8 @@ void FileDialog::_notification(int p_what) {
if (!is_visible()) {
set_process_shortcut_input(false);
}
+
+ invalidate(); // Put it here to preview in the editor.
} break;
case NOTIFICATION_THEME_CHANGED: {
@@ -143,7 +145,7 @@ void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
switch (k->get_keycode()) {
case Key::H: {
- if (k->is_command_pressed()) {
+ if (k->is_command_or_control_pressed()) {
set_show_hidden_files(!show_hidden_files);
} else {
handled = false;
@@ -170,18 +172,20 @@ void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
void FileDialog::set_enable_multiple_selection(bool p_enable) {
tree->set_select_mode(p_enable ? Tree::SELECT_MULTI : Tree::SELECT_SINGLE);
-};
+}
Vector<String> FileDialog::get_selected_files() const {
Vector<String> list;
TreeItem *item = tree->get_root();
- while ((item = tree->get_next_selected(item))) {
+ item = tree->get_next_selected(item);
+ while (item) {
list.push_back(dir_access->get_current_dir().path_join(item->get_text(0)));
- };
+ item = tree->get_next_selected(item);
+ }
return list;
-};
+}
void FileDialog::update_dir() {
if (root_prefix.is_empty()) {
@@ -223,10 +227,6 @@ void FileDialog::_save_confirm_pressed() {
void FileDialog::_post_popup() {
ConfirmationDialog::_post_popup();
- if (invalidated) {
- update_file_list();
- invalidated = false;
- }
if (mode == FILE_MODE_SAVE_FILE) {
file->grab_focus();
} else {
@@ -552,7 +552,7 @@ void FileDialog::update_file_list() {
TreeItem *ti = tree->create_item(root);
ti->set_text(0, dir_name);
ti->set_icon(0, theme_cache.folder);
- ti->set_icon_modulate(0, theme_cache.folder_icon_modulate);
+ ti->set_icon_modulate(0, theme_cache.folder_icon_color);
Dictionary d;
d["name"] = dir_name;
@@ -613,10 +613,10 @@ void FileDialog::update_file_list() {
} else {
ti->set_icon(0, theme_cache.file);
}
- ti->set_icon_modulate(0, theme_cache.file_icon_modulate);
+ ti->set_icon_modulate(0, theme_cache.file_icon_color);
if (mode == FILE_MODE_OPEN_DIR) {
- ti->set_custom_color(0, theme_cache.files_disabled);
+ ti->set_custom_color(0, theme_cache.file_disabled_color);
ti->set_selectable(0, false);
}
Dictionary d;
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 5c892288b5..1add0a9cf5 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -118,9 +118,9 @@ private:
Ref<Texture2D> folder;
Ref<Texture2D> file;
- Color folder_icon_modulate;
- Color file_icon_modulate;
- Color files_disabled;
+ Color folder_icon_color;
+ Color file_icon_color;
+ Color file_disabled_color;
Color icon_normal_color;
Color icon_hover_color;
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 3efd465939..7295ab9e9d 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -190,6 +190,14 @@ void GraphEditMinimap::_adjust_graph_scroll(const Vector2 &p_offset) {
ge->set_scroll_ofs(p_offset + graph_offset - camera_size / 2);
}
+PackedStringArray GraphEdit::get_configuration_warnings() const {
+ PackedStringArray warnings = Control::get_configuration_warnings();
+
+ warnings.push_back(RTR("Please be aware that GraphEdit and GraphNode will undergo extensive refactoring in a future beta version involving compatibility-breaking API changes."));
+
+ return warnings;
+}
+
Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) {
if (is_node_connected(p_from, p_from_port, p_to, p_to_port)) {
return OK;
@@ -351,7 +359,7 @@ void GraphEdit::_graph_node_raised(Node *p_gn) {
if (gn->is_comment()) {
move_child(gn, 0);
} else {
- gn->raise();
+ gn->move_to_front();
}
}
@@ -773,25 +781,25 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
if (connecting_valid) {
if (connecting && connecting_target) {
String from = connecting_from;
- int from_slot = connecting_index;
+ int from_port = connecting_index;
String to = connecting_target_to;
- int to_slot = connecting_target_index;
+ int to_port = connecting_target_index;
if (!connecting_out) {
SWAP(from, to);
- SWAP(from_slot, to_slot);
+ SWAP(from_port, to_port);
}
- emit_signal(SNAME("connection_request"), from, from_slot, to, to_slot);
+ emit_signal(SNAME("connection_request"), from, from_port, to, to_port);
} else if (!just_disconnected) {
String from = connecting_from;
- int from_slot = connecting_index;
+ int from_port = connecting_index;
Vector2 ofs = mb->get_position();
if (!connecting_out) {
- emit_signal(SNAME("connection_from_empty"), from, from_slot, ofs);
+ emit_signal(SNAME("connection_from_empty"), from, from_port, ofs);
} else {
- emit_signal(SNAME("connection_to_empty"), from, from_slot, ofs);
+ emit_signal(SNAME("connection_to_empty"), from, from_port, ofs);
}
}
}
@@ -830,22 +838,22 @@ bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &mpos
}
}
-bool GraphEdit::is_in_input_hotzone(GraphNode *p_graph_node, int p_slot_index, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) {
+bool GraphEdit::is_in_input_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) {
bool success;
- if (GDVIRTUAL_CALL(_is_in_input_hotzone, p_graph_node, p_slot_index, p_mouse_pos, success)) {
+ if (GDVIRTUAL_CALL(_is_in_input_hotzone, p_node, p_port, p_mouse_pos, success)) {
return success;
} else {
- Vector2 pos = p_graph_node->get_connection_input_position(p_slot_index) + p_graph_node->get_position();
+ Vector2 pos = p_node->get_connection_input_position(p_port) + p_node->get_position();
return is_in_port_hotzone(pos / zoom, p_mouse_pos, p_port_size, true);
}
}
-bool GraphEdit::is_in_output_hotzone(GraphNode *p_graph_node, int p_slot_index, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) {
+bool GraphEdit::is_in_output_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) {
bool success;
- if (GDVIRTUAL_CALL(_is_in_output_hotzone, p_graph_node, p_slot_index, p_mouse_pos, success)) {
+ if (GDVIRTUAL_CALL(_is_in_output_hotzone, p_node, p_port, p_mouse_pos, success)) {
return success;
} else {
- Vector2 pos = p_graph_node->get_connection_output_position(p_slot_index) + p_graph_node->get_position();
+ Vector2 pos = p_node->get_connection_output_position(p_port) + p_node->get_position();
return is_in_port_hotzone(pos / zoom, p_mouse_pos, p_port_size, false);
}
}
@@ -1100,11 +1108,11 @@ void GraphEdit::_minimap_draw() {
continue;
}
- Vector2 from_slot_position = gfrom->get_position_offset() * zoom + gfrom->get_connection_output_position(E.from_port);
- Vector2 from_position = minimap->_convert_from_graph_position(from_slot_position - graph_offset) + minimap_offset;
+ Vector2 from_port_position = gfrom->get_position_offset() * zoom + gfrom->get_connection_output_position(E.from_port);
+ Vector2 from_position = minimap->_convert_from_graph_position(from_port_position - graph_offset) + minimap_offset;
Color from_color = gfrom->get_connection_output_color(E.from_port);
- Vector2 to_slot_position = gto->get_position_offset() * zoom + gto->get_connection_input_position(E.to_port);
- Vector2 to_position = minimap->_convert_from_graph_position(to_slot_position - graph_offset) + minimap_offset;
+ Vector2 to_port_position = gto->get_position_offset() * zoom + gto->get_connection_input_position(E.to_port);
+ Vector2 to_position = minimap->_convert_from_graph_position(to_port_position - graph_offset) + minimap_offset;
Color to_color = gto->get_connection_input_color(E.to_port);
if (E.activity > 0) {
@@ -2303,10 +2311,10 @@ void GraphEdit::arrange_nodes() {
}
void GraphEdit::_bind_methods() {
- ClassDB::bind_method(D_METHOD("connect_node", "from", "from_port", "to", "to_port"), &GraphEdit::connect_node);
- ClassDB::bind_method(D_METHOD("is_node_connected", "from", "from_port", "to", "to_port"), &GraphEdit::is_node_connected);
- ClassDB::bind_method(D_METHOD("disconnect_node", "from", "from_port", "to", "to_port"), &GraphEdit::disconnect_node);
- ClassDB::bind_method(D_METHOD("set_connection_activity", "from", "from_port", "to", "to_port", "amount"), &GraphEdit::set_connection_activity);
+ ClassDB::bind_method(D_METHOD("connect_node", "from_node", "from_port", "to_node", "to_port"), &GraphEdit::connect_node);
+ ClassDB::bind_method(D_METHOD("is_node_connected", "from_node", "from_port", "to_node", "to_port"), &GraphEdit::is_node_connected);
+ ClassDB::bind_method(D_METHOD("disconnect_node", "from_node", "from_port", "to_node", "to_port"), &GraphEdit::disconnect_node);
+ ClassDB::bind_method(D_METHOD("set_connection_activity", "from_node", "from_port", "to_node", "to_port", "amount"), &GraphEdit::set_connection_activity);
ClassDB::bind_method(D_METHOD("get_connection_list"), &GraphEdit::_get_connection_list);
ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections);
ClassDB::bind_method(D_METHOD("force_connection_drag_end"), &GraphEdit::force_connection_drag_end);
@@ -2320,7 +2328,7 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_valid_connection_type", "from_type", "to_type"), &GraphEdit::add_valid_connection_type);
ClassDB::bind_method(D_METHOD("remove_valid_connection_type", "from_type", "to_type"), &GraphEdit::remove_valid_connection_type);
ClassDB::bind_method(D_METHOD("is_valid_connection_type", "from_type", "to_type"), &GraphEdit::is_valid_connection_type);
- ClassDB::bind_method(D_METHOD("get_connection_line", "from", "to"), &GraphEdit::get_connection_line);
+ ClassDB::bind_method(D_METHOD("get_connection_line", "from_node", "to_node"), &GraphEdit::get_connection_line);
ClassDB::bind_method(D_METHOD("set_panning_scheme", "scheme"), &GraphEdit::set_panning_scheme);
ClassDB::bind_method(D_METHOD("get_panning_scheme"), &GraphEdit::get_panning_scheme);
@@ -2370,8 +2378,8 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled);
ClassDB::bind_method(D_METHOD("_update_scroll_offset"), &GraphEdit::_update_scroll_offset);
- GDVIRTUAL_BIND(_is_in_input_hotzone, "graph_node", "slot_index", "mouse_position");
- GDVIRTUAL_BIND(_is_in_output_hotzone, "graph_node", "slot_index", "mouse_position");
+ GDVIRTUAL_BIND(_is_in_input_hotzone, "in_node", "in_port", "mouse_position");
+ GDVIRTUAL_BIND(_is_in_output_hotzone, "in_node", "in_port", "mouse_position");
ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox);
@@ -2379,8 +2387,8 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected);
- GDVIRTUAL_BIND(_get_connection_line, "from", "to")
- GDVIRTUAL_BIND(_is_node_hover_valid, "from", "from_slot", "to", "to_slot");
+ GDVIRTUAL_BIND(_get_connection_line, "from_position", "to_position")
+ GDVIRTUAL_BIND(_is_node_hover_valid, "from_node", "from_port", "to_node", "to_port");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_ofs", "get_scroll_ofs");
@@ -2408,21 +2416,21 @@ void GraphEdit::_bind_methods() {
ADD_GROUP("UI", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "arrange_nodes_button_hidden"), "set_arrange_nodes_button_hidden", "is_arrange_nodes_button_hidden");
- ADD_SIGNAL(MethodInfo("connection_request", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot")));
- ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot")));
+ ADD_SIGNAL(MethodInfo("connection_request", PropertyInfo(Variant::STRING_NAME, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::STRING_NAME, "to_node"), PropertyInfo(Variant::INT, "to_port")));
+ ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING_NAME, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::STRING_NAME, "to_node"), PropertyInfo(Variant::INT, "to_port")));
ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "position")));
ADD_SIGNAL(MethodInfo("duplicate_nodes_request"));
ADD_SIGNAL(MethodInfo("copy_nodes_request"));
ADD_SIGNAL(MethodInfo("paste_nodes_request"));
ADD_SIGNAL(MethodInfo("node_selected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("node_deselected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
- ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::VECTOR2, "release_position")));
- ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"), PropertyInfo(Variant::VECTOR2, "release_position")));
+ ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING_NAME, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::VECTOR2, "release_position")));
+ ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING_NAME, "to_node"), PropertyInfo(Variant::INT, "to_port"), PropertyInfo(Variant::VECTOR2, "release_position")));
ADD_SIGNAL(MethodInfo("delete_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName")));
ADD_SIGNAL(MethodInfo("begin_node_move"));
ADD_SIGNAL(MethodInfo("end_node_move"));
ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset")));
- ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "slot"), PropertyInfo(Variant::BOOL, "is_output")));
+ ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::BOOL, "is_output")));
ADD_SIGNAL(MethodInfo("connection_drag_ended"));
BIND_ENUM_CONSTANT(SCROLL_ZOOMS);
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index b6ce575009..101087bdbd 100644
--- a/scene/gui/graph_edit.h
+++ b/scene/gui/graph_edit.h
@@ -201,8 +201,8 @@ private:
GraphEditMinimap *minimap = nullptr;
void _top_layer_input(const Ref<InputEvent> &p_ev);
- bool is_in_input_hotzone(GraphNode *p_graph_node, int p_slot_index, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
- bool is_in_output_hotzone(GraphNode *p_graph_node, int p_slot_index, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
+ bool is_in_input_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
+ bool is_in_output_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
bool is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left);
void _top_layer_draw();
@@ -287,6 +287,8 @@ protected:
GDVIRTUAL4R(bool, _is_node_hover_valid, StringName, int, StringName, int);
public:
+ PackedStringArray get_configuration_warnings() const override;
+
Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index f441144a8e..21c0b5a842 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -779,8 +779,8 @@ void GraphNode::_connpos_update() {
int sep = get_theme_constant(SNAME("separation"));
Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame"));
- conn_input_cache.clear();
- conn_output_cache.clear();
+ left_port_cache.clear();
+ right_port_cache.clear();
int vofs = 0;
int idx = 0;
@@ -801,20 +801,26 @@ void GraphNode::_connpos_update() {
if (slot_info.has(idx)) {
if (slot_info[idx].enable_left) {
- ConnCache cc;
- cc.pos = Point2i(edgeofs, y + h / 2);
+ PortCache cc;
+ cc.position = Point2i(edgeofs, y + h / 2);
+ cc.height = size.height;
+
+ cc.slot_idx = idx;
cc.type = slot_info[idx].type_left;
cc.color = slot_info[idx].color_left;
- cc.height = size.height;
- conn_input_cache.push_back(cc);
+
+ left_port_cache.push_back(cc);
}
if (slot_info[idx].enable_right) {
- ConnCache cc;
- cc.pos = Point2i(get_size().width - edgeofs, y + h / 2);
+ PortCache cc;
+ cc.position = Point2i(get_size().width - edgeofs, y + h / 2);
+ cc.height = size.height;
+
+ cc.slot_idx = idx;
cc.type = slot_info[idx].type_right;
cc.color = slot_info[idx].color_right;
- cc.height = size.height;
- conn_output_cache.push_back(cc);
+
+ right_port_cache.push_back(cc);
}
}
@@ -831,46 +837,55 @@ int GraphNode::get_connection_input_count() {
_connpos_update();
}
- return conn_input_cache.size();
+ return left_port_cache.size();
}
-int GraphNode::get_connection_input_height(int p_idx) {
+int GraphNode::get_connection_input_height(int p_port) {
if (connpos_dirty) {
_connpos_update();
}
- ERR_FAIL_INDEX_V(p_idx, conn_input_cache.size(), 0);
- return conn_input_cache[p_idx].height;
+ ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), 0);
+ return left_port_cache[p_port].height;
}
-Vector2 GraphNode::get_connection_input_position(int p_idx) {
+Vector2 GraphNode::get_connection_input_position(int p_port) {
if (connpos_dirty) {
_connpos_update();
}
- ERR_FAIL_INDEX_V(p_idx, conn_input_cache.size(), Vector2());
- Vector2 pos = conn_input_cache[p_idx].pos;
+ ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), Vector2());
+ Vector2 pos = left_port_cache[p_port].position;
pos.x *= get_scale().x;
pos.y *= get_scale().y;
return pos;
}
-int GraphNode::get_connection_input_type(int p_idx) {
+int GraphNode::get_connection_input_type(int p_port) {
+ if (connpos_dirty) {
+ _connpos_update();
+ }
+
+ ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), 0);
+ return left_port_cache[p_port].type;
+}
+
+Color GraphNode::get_connection_input_color(int p_port) {
if (connpos_dirty) {
_connpos_update();
}
- ERR_FAIL_INDEX_V(p_idx, conn_input_cache.size(), 0);
- return conn_input_cache[p_idx].type;
+ ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), Color());
+ return left_port_cache[p_port].color;
}
-Color GraphNode::get_connection_input_color(int p_idx) {
+int GraphNode::get_connection_input_slot(int p_port) {
if (connpos_dirty) {
_connpos_update();
}
- ERR_FAIL_INDEX_V(p_idx, conn_input_cache.size(), Color());
- return conn_input_cache[p_idx].color;
+ ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), -1);
+ return left_port_cache[p_port].slot_idx;
}
int GraphNode::get_connection_output_count() {
@@ -878,46 +893,55 @@ int GraphNode::get_connection_output_count() {
_connpos_update();
}
- return conn_output_cache.size();
+ return right_port_cache.size();
}
-int GraphNode::get_connection_output_height(int p_idx) {
+int GraphNode::get_connection_output_height(int p_port) {
if (connpos_dirty) {
_connpos_update();
}
- ERR_FAIL_INDEX_V(p_idx, conn_output_cache.size(), 0);
- return conn_output_cache[p_idx].height;
+ ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), 0);
+ return right_port_cache[p_port].height;
}
-Vector2 GraphNode::get_connection_output_position(int p_idx) {
+Vector2 GraphNode::get_connection_output_position(int p_port) {
if (connpos_dirty) {
_connpos_update();
}
- ERR_FAIL_INDEX_V(p_idx, conn_output_cache.size(), Vector2());
- Vector2 pos = conn_output_cache[p_idx].pos;
+ ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), Vector2());
+ Vector2 pos = right_port_cache[p_port].position;
pos.x *= get_scale().x;
pos.y *= get_scale().y;
return pos;
}
-int GraphNode::get_connection_output_type(int p_idx) {
+int GraphNode::get_connection_output_type(int p_port) {
+ if (connpos_dirty) {
+ _connpos_update();
+ }
+
+ ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), 0);
+ return right_port_cache[p_port].type;
+}
+
+Color GraphNode::get_connection_output_color(int p_port) {
if (connpos_dirty) {
_connpos_update();
}
- ERR_FAIL_INDEX_V(p_idx, conn_output_cache.size(), 0);
- return conn_output_cache[p_idx].type;
+ ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), Color());
+ return right_port_cache[p_port].color;
}
-Color GraphNode::get_connection_output_color(int p_idx) {
+int GraphNode::get_connection_output_slot(int p_port) {
if (connpos_dirty) {
_connpos_update();
}
- ERR_FAIL_INDEX_V(p_idx, conn_output_cache.size(), Color());
- return conn_output_cache[p_idx].color;
+ ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), -1);
+ return right_port_cache[p_port].slot_idx;
}
void GraphNode::gui_input(const Ref<InputEvent> &p_ev) {
@@ -1050,30 +1074,30 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language);
- ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right", "enable"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true));
- ClassDB::bind_method(D_METHOD("clear_slot", "idx"), &GraphNode::clear_slot);
+ ClassDB::bind_method(D_METHOD("set_slot", "slot_index", "enable_left_port", "type_left", "color_left", "enable_right_port", "type_right", "color_right", "custom_icon_left", "custom_icon_right", "draw_stylebox"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("clear_slot", "slot_index"), &GraphNode::clear_slot);
ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots);
- ClassDB::bind_method(D_METHOD("is_slot_enabled_left", "idx"), &GraphNode::is_slot_enabled_left);
- ClassDB::bind_method(D_METHOD("set_slot_enabled_left", "idx", "enable_left"), &GraphNode::set_slot_enabled_left);
+ ClassDB::bind_method(D_METHOD("set_slot_enabled_left", "slot_index", "enable"), &GraphNode::set_slot_enabled_left);
+ ClassDB::bind_method(D_METHOD("is_slot_enabled_left", "slot_index"), &GraphNode::is_slot_enabled_left);
- ClassDB::bind_method(D_METHOD("set_slot_type_left", "idx", "type_left"), &GraphNode::set_slot_type_left);
- ClassDB::bind_method(D_METHOD("get_slot_type_left", "idx"), &GraphNode::get_slot_type_left);
+ ClassDB::bind_method(D_METHOD("set_slot_type_left", "slot_index", "type"), &GraphNode::set_slot_type_left);
+ ClassDB::bind_method(D_METHOD("get_slot_type_left", "slot_index"), &GraphNode::get_slot_type_left);
- ClassDB::bind_method(D_METHOD("set_slot_color_left", "idx", "color_left"), &GraphNode::set_slot_color_left);
- ClassDB::bind_method(D_METHOD("get_slot_color_left", "idx"), &GraphNode::get_slot_color_left);
+ ClassDB::bind_method(D_METHOD("set_slot_color_left", "slot_index", "color"), &GraphNode::set_slot_color_left);
+ ClassDB::bind_method(D_METHOD("get_slot_color_left", "slot_index"), &GraphNode::get_slot_color_left);
- ClassDB::bind_method(D_METHOD("is_slot_enabled_right", "idx"), &GraphNode::is_slot_enabled_right);
- ClassDB::bind_method(D_METHOD("set_slot_enabled_right", "idx", "enable_right"), &GraphNode::set_slot_enabled_right);
+ ClassDB::bind_method(D_METHOD("set_slot_enabled_right", "slot_index", "enable"), &GraphNode::set_slot_enabled_right);
+ ClassDB::bind_method(D_METHOD("is_slot_enabled_right", "slot_index"), &GraphNode::is_slot_enabled_right);
- ClassDB::bind_method(D_METHOD("set_slot_type_right", "idx", "type_right"), &GraphNode::set_slot_type_right);
- ClassDB::bind_method(D_METHOD("get_slot_type_right", "idx"), &GraphNode::get_slot_type_right);
+ ClassDB::bind_method(D_METHOD("set_slot_type_right", "slot_index", "type"), &GraphNode::set_slot_type_right);
+ ClassDB::bind_method(D_METHOD("get_slot_type_right", "slot_index"), &GraphNode::get_slot_type_right);
- ClassDB::bind_method(D_METHOD("set_slot_color_right", "idx", "color_right"), &GraphNode::set_slot_color_right);
- ClassDB::bind_method(D_METHOD("get_slot_color_right", "idx"), &GraphNode::get_slot_color_right);
+ ClassDB::bind_method(D_METHOD("set_slot_color_right", "slot_index", "color"), &GraphNode::set_slot_color_right);
+ ClassDB::bind_method(D_METHOD("get_slot_color_right", "slot_index"), &GraphNode::get_slot_color_right);
- ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "idx"), &GraphNode::is_slot_draw_stylebox);
- ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "idx", "draw_stylebox"), &GraphNode::set_slot_draw_stylebox);
+ ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "slot_index"), &GraphNode::is_slot_draw_stylebox);
+ ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "slot_index", "enable"), &GraphNode::set_slot_draw_stylebox);
ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset);
ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphNode::get_position_offset);
@@ -1094,16 +1118,18 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_selected"), &GraphNode::is_selected);
ClassDB::bind_method(D_METHOD("get_connection_input_count"), &GraphNode::get_connection_input_count);
- ClassDB::bind_method(D_METHOD("get_connection_input_height", "idx"), &GraphNode::get_connection_input_height);
- ClassDB::bind_method(D_METHOD("get_connection_input_position", "idx"), &GraphNode::get_connection_input_position);
- ClassDB::bind_method(D_METHOD("get_connection_input_type", "idx"), &GraphNode::get_connection_input_type);
- ClassDB::bind_method(D_METHOD("get_connection_input_color", "idx"), &GraphNode::get_connection_input_color);
+ ClassDB::bind_method(D_METHOD("get_connection_input_height", "port"), &GraphNode::get_connection_input_height);
+ ClassDB::bind_method(D_METHOD("get_connection_input_position", "port"), &GraphNode::get_connection_input_position);
+ ClassDB::bind_method(D_METHOD("get_connection_input_type", "port"), &GraphNode::get_connection_input_type);
+ ClassDB::bind_method(D_METHOD("get_connection_input_color", "port"), &GraphNode::get_connection_input_color);
+ ClassDB::bind_method(D_METHOD("get_connection_input_slot", "port"), &GraphNode::get_connection_input_slot);
ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count);
- ClassDB::bind_method(D_METHOD("get_connection_output_height", "idx"), &GraphNode::get_connection_output_height);
- ClassDB::bind_method(D_METHOD("get_connection_output_position", "idx"), &GraphNode::get_connection_output_position);
- ClassDB::bind_method(D_METHOD("get_connection_output_type", "idx"), &GraphNode::get_connection_output_type);
- ClassDB::bind_method(D_METHOD("get_connection_output_color", "idx"), &GraphNode::get_connection_output_color);
+ ClassDB::bind_method(D_METHOD("get_connection_output_height", "port"), &GraphNode::get_connection_output_height);
+ ClassDB::bind_method(D_METHOD("get_connection_output_position", "port"), &GraphNode::get_connection_output_position);
+ ClassDB::bind_method(D_METHOD("get_connection_output_type", "port"), &GraphNode::get_connection_output_type);
+ ClassDB::bind_method(D_METHOD("get_connection_output_color", "port"), &GraphNode::get_connection_output_color);
+ ClassDB::bind_method(D_METHOD("get_connection_output_slot", "port"), &GraphNode::get_connection_output_slot);
ClassDB::bind_method(D_METHOD("set_show_close_button", "show"), &GraphNode::set_show_close_button);
ClassDB::bind_method(D_METHOD("is_close_button_visible"), &GraphNode::is_close_button_visible);
diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h
index 9c8f926403..e66b0cfc20 100644
--- a/scene/gui/graph_node.h
+++ b/scene/gui/graph_node.h
@@ -78,15 +78,17 @@ private:
Vector<int> cache_y;
- struct ConnCache {
- Vector2 pos;
+ struct PortCache {
+ Vector2 position;
+ int height;
+
+ int slot_idx;
int type = 0;
Color color;
- int height;
};
- Vector<ConnCache> conn_input_cache;
- Vector<ConnCache> conn_output_cache;
+ Vector<PortCache> left_port_cache;
+ Vector<PortCache> right_port_cache;
HashMap<int, Slot> slot_info;
@@ -165,16 +167,18 @@ public:
bool is_close_button_visible() const;
int get_connection_input_count();
- int get_connection_input_height(int p_idx);
- Vector2 get_connection_input_position(int p_idx);
- int get_connection_input_type(int p_idx);
- Color get_connection_input_color(int p_idx);
+ int get_connection_input_height(int p_port);
+ Vector2 get_connection_input_position(int p_port);
+ int get_connection_input_type(int p_port);
+ Color get_connection_input_color(int p_port);
+ int get_connection_input_slot(int p_port);
int get_connection_output_count();
- int get_connection_output_height(int p_idx);
- Vector2 get_connection_output_position(int p_idx);
- int get_connection_output_type(int p_idx);
- Color get_connection_output_color(int p_idx);
+ int get_connection_output_height(int p_port);
+ Vector2 get_connection_output_position(int p_port);
+ int get_connection_output_type(int p_port);
+ Color get_connection_output_color(int p_port);
+ int get_connection_output_slot(int p_port);
void set_overlay(Overlay p_overlay);
Overlay get_overlay() const;
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 8c49353105..357f2480bd 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -596,7 +596,7 @@ ItemList::IconMode ItemList::get_icon_mode() const {
return icon_mode;
}
-void ItemList::set_fixed_icon_size(const Size2 &p_size) {
+void ItemList::set_fixed_icon_size(const Size2i &p_size) {
if (fixed_icon_size == p_size) {
return;
}
@@ -605,7 +605,7 @@ void ItemList::set_fixed_icon_size(const Size2 &p_size) {
queue_redraw();
}
-Size2 ItemList::get_fixed_icon_size() const {
+Size2i ItemList::get_fixed_icon_size() const {
return fixed_icon_size;
}
@@ -655,31 +655,19 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
if (mb.is_valid() && mb->is_pressed()) {
search_string = ""; //any mousepress cancels
Vector2 pos = mb->get_position();
- pos -= theme_cache.bg_style->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
pos.y += scroll_bar->get_value();
if (is_layout_rtl()) {
pos.x = get_size().width - pos.x;
}
- int closest = -1;
-
- for (int i = 0; i < items.size(); i++) {
- Rect2 rc = items[i].rect_cache;
- if (i % current_columns == current_columns - 1) {
- rc.size.width = get_size().width; //not right but works
- }
-
- if (rc.has_point(pos)) {
- closest = i;
- break;
- }
- }
+ int closest = get_item_at_position(mb->get_position(), true);
if (closest != -1 && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT))) {
int i = closest;
- if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_pressed()) {
+ if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_or_control_pressed()) {
deselect(i);
emit_signal(SNAME("multi_selected"), i, false);
@@ -702,13 +690,13 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index());
} else {
- if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MouseButton::LEFT) {
+ if (!mb->is_double_click() && !mb->is_command_or_control_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MouseButton::LEFT) {
defer_select_single = i;
return;
}
if (!items[i].selected || allow_reselect) {
- select(i, select_mode == SELECT_SINGLE || !mb->is_command_pressed());
+ select(i, select_mode == SELECT_SINGLE || !mb->is_command_or_control_pressed());
if (select_mode == SELECT_SINGLE) {
emit_signal(SNAME("item_selected"), i);
@@ -985,8 +973,8 @@ void ItemList::_update_theme_item_cache() {
theme_cache.h_separation = get_theme_constant(SNAME("h_separation"));
theme_cache.v_separation = get_theme_constant(SNAME("v_separation"));
- theme_cache.bg_style = get_theme_stylebox(SNAME("bg"));
- theme_cache.bg_focus_style = get_theme_stylebox(SNAME("bg_focus"));
+ theme_cache.panel_style = get_theme_stylebox(SNAME("panel"));
+ theme_cache.focus_style = get_theme_stylebox(SNAME("focus"));
theme_cache.font = get_theme_font(SNAME("font"));
theme_cache.font_size = get_theme_font_size(SNAME("font_size"));
@@ -1025,13 +1013,13 @@ void ItemList::_notification(int p_what) {
int mw = scroll_bar->get_minimum_size().x;
scroll_bar->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -mw);
scroll_bar->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0);
- scroll_bar->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, theme_cache.bg_style->get_margin(SIDE_TOP));
- scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.bg_style->get_margin(SIDE_BOTTOM));
+ scroll_bar->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, theme_cache.panel_style->get_margin(SIDE_TOP));
+ scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.panel_style->get_margin(SIDE_BOTTOM));
Size2 size = get_size();
- int width = size.width - theme_cache.bg_style->get_minimum_size().width;
+ int width = size.width - theme_cache.panel_style->get_minimum_size().width;
- draw_style_box(theme_cache.bg_style, Rect2(Point2(), size));
+ draw_style_box(theme_cache.panel_style, Rect2(Point2(), size));
Ref<StyleBox> sbsel;
Ref<StyleBox> cursor;
@@ -1047,7 +1035,7 @@ void ItemList::_notification(int p_what) {
if (has_focus()) {
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), true);
- draw_style_box(theme_cache.bg_focus_style, Rect2(Point2(), size));
+ draw_style_box(theme_cache.focus_style, Rect2(Point2(), size));
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), false);
}
@@ -1109,7 +1097,7 @@ void ItemList::_notification(int p_what) {
items.write[i].min_rect_cache.size = minsize;
}
- int fit_size = size.x - theme_cache.bg_style->get_minimum_size().width - mw;
+ int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width - mw;
//2-attempt best fit
current_columns = 0x7FFFFFFF;
@@ -1160,10 +1148,10 @@ void ItemList::_notification(int p_what) {
}
if (all_fit) {
- float page = MAX(0, size.height - theme_cache.bg_style->get_minimum_size().height);
+ float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height);
float max = MAX(page, ofs.y + max_h);
if (auto_height) {
- auto_height_value = ofs.y + max_h + theme_cache.bg_style->get_minimum_size().height;
+ auto_height_value = ofs.y + max_h + theme_cache.panel_style->get_minimum_size().height;
}
scroll_bar->set_max(max);
scroll_bar->set_page(page);
@@ -1204,7 +1192,7 @@ void ItemList::_notification(int p_what) {
ensure_selected_visible = false;
- Vector2 base_ofs = theme_cache.bg_style->get_offset();
+ Vector2 base_ofs = theme_cache.panel_style->get_offset();
base_ofs.y -= int(scroll_bar->get_value());
const Rect2 clip(-base_ofs, size); // visible frame, don't need to draw outside of there
@@ -1442,7 +1430,7 @@ void ItemList::_notification(int p_what) {
}
const int y = base_ofs.y + separators[i];
- draw_line(Vector2(theme_cache.bg_style->get_margin(SIDE_LEFT), y), Vector2(width, y), theme_cache.guide_color);
+ draw_line(Vector2(theme_cache.panel_style->get_margin(SIDE_LEFT), y), Vector2(width, y), theme_cache.guide_color);
}
} break;
}
@@ -1454,7 +1442,7 @@ void ItemList::_scroll_changed(double) {
int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const {
Vector2 pos = p_pos;
- pos -= theme_cache.bg_style->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
pos.y += scroll_bar->get_value();
if (is_layout_rtl()) {
@@ -1467,7 +1455,7 @@ int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const {
for (int i = 0; i < items.size(); i++) {
Rect2 rc = items[i].rect_cache;
if (i % current_columns == current_columns - 1) {
- rc.size.width = get_size().width - rc.position.x; //make sure you can still select the last item when clicking past the column
+ rc.size.width = get_size().width - rc.position.x; // Make sure you can still select the last item when clicking past the column.
}
if (rc.has_point(pos)) {
@@ -1491,7 +1479,7 @@ bool ItemList::is_pos_at_end_of_items(const Point2 &p_pos) const {
}
Vector2 pos = p_pos;
- pos -= theme_cache.bg_style->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
pos.y += scroll_bar->get_value();
if (is_layout_rtl()) {
@@ -1830,7 +1818,7 @@ void ItemList::_bind_methods() {
ADD_GROUP("Icon", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_mode", PROPERTY_HINT_ENUM, "Top,Left"), "set_icon_mode", "get_icon_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "icon_scale"), "set_icon_scale", "get_icon_scale");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fixed_icon_size", PROPERTY_HINT_NONE, "suffix:px"), "set_fixed_icon_size", "get_fixed_icon_size");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "fixed_icon_size", PROPERTY_HINT_NONE, "suffix:px"), "set_fixed_icon_size", "get_fixed_icon_size");
BIND_ENUM_CONSTANT(ICON_MODE_TOP);
BIND_ENUM_CONSTANT(ICON_MODE_LEFT);
diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h
index 63bc771185..4b1b9d9282 100644
--- a/scene/gui/item_list.h
+++ b/scene/gui/item_list.h
@@ -123,8 +123,8 @@ private:
int h_separation = 0;
int v_separation = 0;
- Ref<StyleBox> bg_style;
- Ref<StyleBox> bg_focus_style;
+ Ref<StyleBox> panel_style;
+ Ref<StyleBox> focus_style;
Ref<Font> font;
int font_size = 0;
@@ -244,8 +244,8 @@ public:
void set_icon_mode(IconMode p_mode);
IconMode get_icon_mode() const;
- void set_fixed_icon_size(const Size2 &p_size);
- Size2 get_fixed_icon_size() const;
+ void set_fixed_icon_size(const Size2i &p_size);
+ Size2i get_fixed_icon_size() const;
void set_allow_rmb_select(bool p_allow);
bool get_allow_rmb_select() const;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index c2ce4bdb83..eb34559a14 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -589,7 +589,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
// Allow unicode handling if:
// * No Modifiers are pressed (except shift)
- bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
// Handle Unicode (if no modifiers active)
@@ -734,7 +734,7 @@ void LineEdit::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) {
set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink"));
- set_caret_blink_speed(EDITOR_GET("text_editor/appearance/caret/caret_blink_speed"));
+ set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval"));
if (!EditorSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed))) {
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed));
@@ -777,7 +777,7 @@ void LineEdit::_notification(int p_what) {
if (caret_blinking) {
caret_blink_timer += get_process_delta_time();
- if (caret_blink_timer >= caret_blink_speed) {
+ if (caret_blink_timer >= caret_blink_interval) {
caret_blink_timer = 0.0;
_toggle_draw_caret();
}
@@ -971,7 +971,18 @@ void LineEdit::_notification(int p_what) {
} else {
if (caret.l_caret != Rect2() && caret.l_dir == TextServer::DIRECTION_AUTO) {
// Draw extra marker on top of mid caret.
- Rect2 trect = Rect2(caret.l_caret.position.x - 3 * caret_width, caret.l_caret.position.y, 6 * caret_width, caret_width);
+ Rect2 trect = Rect2(caret.l_caret.position.x - 2.5 * caret_width, caret.l_caret.position.y, 6 * caret_width, caret_width);
+ trect.position += ofs;
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
+ } else if (caret.l_caret != Rect2() && caret.t_caret != Rect2() && caret.l_dir != caret.t_dir) {
+ // Draw extra direction marker on top of split caret.
+ float d = (caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
+ Rect2 trect = Rect2(caret.l_caret.position.x + d * caret_width, caret.l_caret.position.y + caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width);
+ trect.position += ofs;
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
+
+ d = (caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
+ trect = Rect2(caret.t_caret.position.x + d * caret_width, caret.t_caret.position.y, 3 * caret_width, caret_width);
trect.position += ofs;
RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
}
@@ -1392,13 +1403,13 @@ void LineEdit::set_caret_force_displayed(const bool p_enabled) {
queue_redraw();
}
-float LineEdit::get_caret_blink_speed() const {
- return caret_blink_speed;
+float LineEdit::get_caret_blink_interval() const {
+ return caret_blink_interval;
}
-void LineEdit::set_caret_blink_speed(const float p_speed) {
- ERR_FAIL_COND(p_speed <= 0);
- caret_blink_speed = p_speed;
+void LineEdit::set_caret_blink_interval(const float p_interval) {
+ ERR_FAIL_COND(p_interval <= 0);
+ caret_blink_interval = p_interval;
}
void LineEdit::_reset_caret_blink_timer() {
@@ -2037,7 +2048,7 @@ PopupMenu *LineEdit::get_menu() const {
void LineEdit::_editor_settings_changed() {
#ifdef TOOLS_ENABLED
set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink"));
- set_caret_blink_speed(EDITOR_GET("text_editor/appearance/caret/caret_blink_speed"));
+ set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval"));
#endif
}
@@ -2274,7 +2285,7 @@ Key LineEdit::_get_menu_action_accelerator(const String &p_action) {
}
void LineEdit::_validate_property(PropertyInfo &p_property) const {
- if (!caret_blink_enabled && p_property.name == "caret_blink_speed") {
+ if (!caret_blink_enabled && p_property.name == "caret_blink_interval") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
@@ -2317,8 +2328,8 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &LineEdit::is_caret_mid_grapheme_enabled);
ClassDB::bind_method(D_METHOD("set_caret_force_displayed", "enabled"), &LineEdit::set_caret_force_displayed);
ClassDB::bind_method(D_METHOD("is_caret_force_displayed"), &LineEdit::is_caret_force_displayed);
- ClassDB::bind_method(D_METHOD("set_caret_blink_speed", "blink_speed"), &LineEdit::set_caret_blink_speed);
- ClassDB::bind_method(D_METHOD("get_caret_blink_speed"), &LineEdit::get_caret_blink_speed);
+ ClassDB::bind_method(D_METHOD("set_caret_blink_interval", "interval"), &LineEdit::set_caret_blink_interval);
+ ClassDB::bind_method(D_METHOD("get_caret_blink_interval"), &LineEdit::get_caret_blink_interval);
ClassDB::bind_method(D_METHOD("set_max_length", "chars"), &LineEdit::set_max_length);
ClassDB::bind_method(D_METHOD("get_max_length"), &LineEdit::get_max_length);
ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text"), &LineEdit::insert_text_at_caret);
@@ -2419,7 +2430,7 @@ void LineEdit::_bind_methods() {
ADD_GROUP("Caret", "caret_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_speed", "get_caret_blink_speed");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_interval", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_interval", "get_caret_blink_interval");
ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_column", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_caret_column", "get_caret_column");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "set_caret_force_displayed", "is_caret_force_displayed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled");
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 0122a5da69..861c7f273b 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -170,7 +170,7 @@ private:
bool caret_blink_enabled = false;
bool caret_force_displayed = false;
bool draw_caret = true;
- float caret_blink_speed = 0.65;
+ float caret_blink_interval = 0.65;
double caret_blink_timer = 0.0;
bool caret_blinking = false;
@@ -199,8 +199,6 @@ private:
float base_scale = 1.0;
} theme_cache;
- bool _is_over_clear_button(const Point2 &p_pos) const;
-
void _clear_undo_stack();
void _clear_redo();
void _create_undo_state();
@@ -240,6 +238,7 @@ private:
void _ensure_menu();
protected:
+ bool _is_over_clear_button(const Point2 &p_pos) const;
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
static void _bind_methods();
@@ -311,8 +310,8 @@ public:
bool is_caret_blink_enabled() const;
void set_caret_blink_enabled(const bool p_enabled);
- float get_caret_blink_speed() const;
- void set_caret_blink_speed(const float p_speed);
+ float get_caret_blink_interval() const;
+ void set_caret_blink_interval(const float p_interval);
void set_caret_force_displayed(const bool p_enabled);
bool is_caret_force_displayed() const;
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 0613652bdf..75592a1b99 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -137,7 +137,7 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) {
if (p_focus_item) {
for (int i = 0; i < pm->get_item_count(); i++) {
if (!pm->is_item_disabled(i)) {
- pm->set_current_index(i);
+ pm->set_focused_item(i);
break;
}
}
@@ -703,7 +703,7 @@ void MenuBar::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "start_index"), "set_start_index", "get_start_index");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_global_menu"), "set_prefer_global_menu", "is_prefer_global_menu");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_RESOURCE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context");
ADD_GROUP("BiDi", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index f779f87ae7..fa8df48412 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -44,15 +44,12 @@ void MenuButton::shortcut_input(const Ref<InputEvent> &p_event) {
return;
}
- if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) {
- if (!get_parent() || !is_visible_in_tree() || is_disabled()) {
- return;
- }
-
- if (popup->activate_item_by_event(p_event, false)) {
- accept_event();
- }
+ if (p_event->is_pressed() && !p_event->is_echo() && !is_disabled() && is_visible_in_tree() && popup->activate_item_by_event(p_event, false)) {
+ accept_event();
+ return;
}
+
+ Button::shortcut_input(p_event);
}
void MenuButton::_popup_visibility_changed(bool p_visible) {
@@ -91,6 +88,18 @@ void MenuButton::pressed() {
return;
}
+ show_popup();
+}
+
+PopupMenu *MenuButton::get_popup() const {
+ return popup;
+}
+
+void MenuButton::show_popup() {
+ if (!get_viewport()) {
+ return;
+ }
+
emit_signal(SNAME("about_to_popup"));
Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
@@ -107,7 +116,7 @@ void MenuButton::pressed() {
if (!_was_pressed_by_mouse()) {
for (int i = 0; i < popup->get_item_count(); i++) {
if (!popup->is_item_disabled(i)) {
- popup->set_current_index(i);
+ popup->set_focused_item(i);
break;
}
}
@@ -116,14 +125,6 @@ void MenuButton::pressed() {
popup->popup();
}
-void MenuButton::gui_input(const Ref<InputEvent> &p_event) {
- BaseButton::gui_input(p_event);
-}
-
-PopupMenu *MenuButton::get_popup() const {
- return popup;
-}
-
void MenuButton::set_switch_on_hover(bool p_enabled) {
switch_on_hover = p_enabled;
}
@@ -169,7 +170,7 @@ void MenuButton::_notification(int p_what) {
menu_btn_other->pressed();
// As the popup wasn't triggered by a mouse click, the item focus needs to be removed manually.
- menu_btn_other->get_popup()->set_current_index(-1);
+ menu_btn_other->get_popup()->set_focused_item(-1);
}
} break;
}
@@ -226,6 +227,7 @@ void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const {
void MenuButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup);
+ ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup);
ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover);
ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover);
ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuButton::set_disable_shortcuts);
diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h
index 97c0d21f1e..3aacbca3a8 100644
--- a/scene/gui/menu_button.h
+++ b/scene/gui/menu_button.h
@@ -44,8 +44,6 @@ class MenuButton : public Button {
Vector2i mouse_pos_adjusted;
- virtual void gui_input(const Ref<InputEvent> &p_event) override;
-
void _popup_visibility_changed(bool p_visible);
protected:
@@ -60,6 +58,8 @@ public:
virtual void pressed() override;
PopupMenu *get_popup() const;
+ void show_popup();
+
void set_switch_on_hover(bool p_enabled);
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 0dd9666858..2cbece69f2 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -231,32 +231,7 @@ void OptionButton::pressed() {
return;
}
- Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
- popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
- popup->set_size(Size2(size.width, 0));
-
- // If not triggered by the mouse, start the popup with the checked item (or the first enabled one) focused.
- if (current != NONE_SELECTED && !popup->is_item_disabled(current)) {
- if (!_was_pressed_by_mouse()) {
- popup->set_current_index(current);
- } else {
- popup->scroll_to_item(current);
- }
- } else {
- for (int i = 0; i < popup->get_item_count(); i++) {
- if (!popup->is_item_disabled(i)) {
- if (!_was_pressed_by_mouse()) {
- popup->set_current_index(i);
- } else {
- popup->scroll_to_item(i);
- }
-
- break;
- }
- }
- }
-
- popup->popup();
+ show_popup();
}
void OptionButton::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id) {
@@ -511,6 +486,39 @@ PopupMenu *OptionButton::get_popup() const {
return popup;
}
+void OptionButton::show_popup() {
+ if (!get_viewport()) {
+ return;
+ }
+
+ Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
+ popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
+ popup->set_size(Size2(size.width, 0));
+
+ // If not triggered by the mouse, start the popup with the checked item (or the first enabled one) focused.
+ if (current != NONE_SELECTED && !popup->is_item_disabled(current)) {
+ if (!_was_pressed_by_mouse()) {
+ popup->set_focused_item(current);
+ } else {
+ popup->scroll_to_item(current);
+ }
+ } else {
+ for (int i = 0; i < popup->get_item_count(); i++) {
+ if (!popup->is_item_disabled(i)) {
+ if (!_was_pressed_by_mouse()) {
+ popup->set_focused_item(i);
+ } else {
+ popup->scroll_to_item(i);
+ }
+
+ break;
+ }
+ }
+ }
+
+ popup->popup();
+}
+
void OptionButton::get_translatable_strings(List<String> *p_strings) const {
popup->get_translatable_strings(p_strings);
}
@@ -548,6 +556,7 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("_select_int", "idx"), &OptionButton::_select_int);
ClassDB::bind_method(D_METHOD("get_popup"), &OptionButton::get_popup);
+ ClassDB::bind_method(D_METHOD("show_popup"), &OptionButton::show_popup);
ClassDB::bind_method(D_METHOD("set_item_count", "count"), &OptionButton::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count);
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index 2c7e0510f5..b76a31d37e 100644
--- a/scene/gui/option_button.h
+++ b/scene/gui/option_button.h
@@ -123,6 +123,7 @@ public:
void remove_item(int p_idx);
PopupMenu *get_popup() const;
+ void show_popup();
virtual void get_translatable_strings(List<String> *p_strings) const override;
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 9afcd566b9..10e13042a7 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -209,7 +209,7 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
if (p_by_keyboard) {
for (int i = 0; i < submenu_pum->get_item_count(); i++) {
if (!submenu_pum->is_item_disabled(i)) {
- submenu_pum->set_current_index(i);
+ submenu_pum->set_focused_item(i);
break;
}
}
@@ -1547,7 +1547,7 @@ bool PopupMenu::is_item_shortcut_disabled(int p_idx) const {
return items[p_idx].shortcut_is_disabled;
}
-void PopupMenu::set_current_index(int p_idx) {
+void PopupMenu::set_focused_item(int p_idx) {
if (p_idx != -1) {
ERR_FAIL_INDEX(p_idx, items.size());
}
@@ -1564,7 +1564,7 @@ void PopupMenu::set_current_index(int p_idx) {
control->queue_redraw();
}
-int PopupMenu::get_current_index() const {
+int PopupMenu::get_focused_item() const {
return mouse_over;
}
@@ -1762,7 +1762,7 @@ void PopupMenu::clear() {
void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) {
if (!shortcut_refcount.has(p_sc)) {
shortcut_refcount[p_sc] = 1;
- p_sc->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
+ p_sc->connect("changed", callable_mp(this, &PopupMenu::_shortcut_changed));
} else {
shortcut_refcount[p_sc] += 1;
}
@@ -1772,11 +1772,18 @@ void PopupMenu::_unref_shortcut(Ref<Shortcut> p_sc) {
ERR_FAIL_COND(!shortcut_refcount.has(p_sc));
shortcut_refcount[p_sc]--;
if (shortcut_refcount[p_sc] == 0) {
- p_sc->disconnect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
+ p_sc->disconnect("changed", callable_mp(this, &PopupMenu::_shortcut_changed));
shortcut_refcount.erase(p_sc);
}
}
+void PopupMenu::_shortcut_changed() {
+ for (int i = 0; i < items.size(); i++) {
+ items.write[i].dirty = true;
+ }
+ control->queue_redraw();
+}
+
// Hide on item selection determines whether or not the popup will close after item selection
void PopupMenu::set_hide_on_item_selection(bool p_enabled) {
hide_on_item_selection = p_enabled;
@@ -2057,8 +2064,8 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut);
ClassDB::bind_method(D_METHOD("get_item_indent", "index"), &PopupMenu::get_item_indent);
- ClassDB::bind_method(D_METHOD("set_current_index", "index"), &PopupMenu::set_current_index);
- ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index);
+ ClassDB::bind_method(D_METHOD("set_focused_item", "index"), &PopupMenu::set_focused_item);
+ ClassDB::bind_method(D_METHOD("get_focused_item"), &PopupMenu::get_focused_item);
ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index c8c598bd50..89d3904456 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -121,6 +121,8 @@ class PopupMenu : public Popup {
void _ref_shortcut(Ref<Shortcut> p_sc);
void _unref_shortcut(Ref<Shortcut> p_sc);
+ void _shortcut_changed();
+
bool allow_search = true;
uint64_t search_time_msec = 0;
String search_string = "";
@@ -261,8 +263,8 @@ public:
int get_item_max_states(int p_idx) const;
int get_item_state(int p_idx) const;
- void set_current_index(int p_idx);
- int get_current_index() const;
+ void set_focused_item(int p_idx);
+ int get_focused_item() const;
void set_item_count(int p_count);
int get_item_count() const;
diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp
index fe609dd834..8369eaa227 100644
--- a/scene/gui/progress_bar.cpp
+++ b/scene/gui/progress_bar.cpp
@@ -33,13 +33,13 @@
#include "scene/resources/text_line.h"
Size2 ProgressBar::get_minimum_size() const {
- Size2 minimum_size = theme_cache.bg_style->get_minimum_size();
- minimum_size.height = MAX(minimum_size.height, theme_cache.fg_style->get_minimum_size().height);
- minimum_size.width = MAX(minimum_size.width, theme_cache.fg_style->get_minimum_size().width);
- if (percent_visible) {
+ Size2 minimum_size = theme_cache.background_style->get_minimum_size();
+ minimum_size.height = MAX(minimum_size.height, theme_cache.fill_style->get_minimum_size().height);
+ minimum_size.width = MAX(minimum_size.width, theme_cache.fill_style->get_minimum_size().width);
+ if (show_percentage) {
String txt = "100%";
TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size);
- minimum_size.height = MAX(minimum_size.height, theme_cache.bg_style->get_minimum_size().height + tl.get_size().y);
+ 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.width = MAX(minimum_size.width, 1);
minimum_size.height = MAX(minimum_size.height, 1);
@@ -50,8 +50,8 @@ Size2 ProgressBar::get_minimum_size() const {
void ProgressBar::_update_theme_item_cache() {
Range::_update_theme_item_cache();
- theme_cache.bg_style = get_theme_stylebox(SNAME("bg"));
- theme_cache.fg_style = get_theme_stylebox(SNAME("fg"));
+ theme_cache.background_style = get_theme_stylebox(SNAME("background"));
+ theme_cache.fill_style = get_theme_stylebox(SNAME("fill"));
theme_cache.font = get_theme_font(SNAME("font"));
theme_cache.font_size = get_theme_font_size(SNAME("font_size"));
@@ -63,14 +63,14 @@ void ProgressBar::_update_theme_item_cache() {
void ProgressBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
- draw_style_box(theme_cache.bg_style, Rect2(Point2(), get_size()));
+ draw_style_box(theme_cache.background_style, Rect2(Point2(), get_size()));
float r = get_as_ratio();
switch (mode) {
case FILL_BEGIN_TO_END:
case FILL_END_TO_BEGIN: {
- int mp = theme_cache.fg_style->get_minimum_size().width;
+ int mp = theme_cache.fill_style->get_minimum_size().width;
int p = round(r * (get_size().width - mp));
// We want FILL_BEGIN_TO_END to map to right to left when UI layout is RTL,
// and left to right otherwise. And likewise for FILL_END_TO_BEGIN.
@@ -78,23 +78,23 @@ void ProgressBar::_notification(int p_what) {
if (p > 0) {
if (right_to_left) {
int p_remaining = round((1.0 - r) * (get_size().width - mp));
- draw_style_box(theme_cache.fg_style, Rect2(Point2(p_remaining, 0), Size2(p + theme_cache.fg_style->get_minimum_size().width, get_size().height)));
+ draw_style_box(theme_cache.fill_style, Rect2(Point2(p_remaining, 0), Size2(p + theme_cache.fill_style->get_minimum_size().width, get_size().height)));
} else {
- draw_style_box(theme_cache.fg_style, Rect2(Point2(0, 0), Size2(p + theme_cache.fg_style->get_minimum_size().width, get_size().height)));
+ draw_style_box(theme_cache.fill_style, Rect2(Point2(0, 0), Size2(p + theme_cache.fill_style->get_minimum_size().width, get_size().height)));
}
}
} break;
case FILL_TOP_TO_BOTTOM:
case FILL_BOTTOM_TO_TOP: {
- int mp = theme_cache.fg_style->get_minimum_size().height;
+ int mp = theme_cache.fill_style->get_minimum_size().height;
int p = round(r * (get_size().height - mp));
if (p > 0) {
if (mode == FILL_TOP_TO_BOTTOM) {
- draw_style_box(theme_cache.fg_style, Rect2(Point2(0, 0), Size2(get_size().width, p + theme_cache.fg_style->get_minimum_size().height)));
+ draw_style_box(theme_cache.fill_style, Rect2(Point2(0, 0), Size2(get_size().width, p + theme_cache.fill_style->get_minimum_size().height)));
} else {
int p_remaining = round((1.0 - r) * (get_size().height - mp));
- draw_style_box(theme_cache.fg_style, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + theme_cache.fg_style->get_minimum_size().height)));
+ draw_style_box(theme_cache.fill_style, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + theme_cache.fill_style->get_minimum_size().height)));
}
}
} break;
@@ -102,7 +102,7 @@ void ProgressBar::_notification(int p_what) {
break;
}
- if (percent_visible) {
+ if (show_percentage) {
String txt = TS->format_number(itos(int(get_as_ratio() * 100))) + TS->percent_sign();
TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size);
Vector2 text_pos = (Point2(get_size().width - tl.get_size().x, get_size().height - tl.get_size().y) / 2).round();
@@ -127,27 +127,27 @@ int ProgressBar::get_fill_mode() {
return mode;
}
-void ProgressBar::set_percent_visible(bool p_visible) {
- if (percent_visible == p_visible) {
+void ProgressBar::set_show_percentage(bool p_visible) {
+ if (show_percentage == p_visible) {
return;
}
- percent_visible = p_visible;
+ show_percentage = p_visible;
update_minimum_size();
queue_redraw();
}
-bool ProgressBar::is_percent_visible() const {
- return percent_visible;
+bool ProgressBar::is_percentage_shown() const {
+ return show_percentage;
}
void ProgressBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &ProgressBar::set_fill_mode);
ClassDB::bind_method(D_METHOD("get_fill_mode"), &ProgressBar::get_fill_mode);
- ClassDB::bind_method(D_METHOD("set_percent_visible", "visible"), &ProgressBar::set_percent_visible);
- ClassDB::bind_method(D_METHOD("is_percent_visible"), &ProgressBar::is_percent_visible);
+ ClassDB::bind_method(D_METHOD("set_show_percentage", "visible"), &ProgressBar::set_show_percentage);
+ ClassDB::bind_method(D_METHOD("is_percentage_shown"), &ProgressBar::is_percentage_shown);
ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Begin to End,End to Begin,Top to Bottom,Bottom to Top"), "set_fill_mode", "get_fill_mode");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "percent_visible"), "set_percent_visible", "is_percent_visible");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_percentage"), "set_show_percentage", "is_percentage_shown");
BIND_ENUM_CONSTANT(FILL_BEGIN_TO_END);
BIND_ENUM_CONSTANT(FILL_END_TO_BEGIN);
diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h
index c79b901928..b6d7d2c7cf 100644
--- a/scene/gui/progress_bar.h
+++ b/scene/gui/progress_bar.h
@@ -36,11 +36,11 @@
class ProgressBar : public Range {
GDCLASS(ProgressBar, Range);
- bool percent_visible = true;
+ bool show_percentage = true;
struct ThemeCache {
- Ref<StyleBox> bg_style;
- Ref<StyleBox> fg_style;
+ Ref<StyleBox> background_style;
+ Ref<StyleBox> fill_style;
Ref<Font> font;
int font_size = 0;
@@ -67,8 +67,8 @@ public:
void set_fill_mode(int p_fill);
int get_fill_mode();
- void set_percent_visible(bool p_visible);
- bool is_percent_visible() const;
+ void set_show_percentage(bool p_visible);
+ bool is_percentage_shown() const;
Size2 get_minimum_size() const override;
ProgressBar();
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index 1eb412abaf..2d2b3e413d 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -30,8 +30,8 @@
#include "range.h"
-TypedArray<String> Range::get_configuration_warnings() const {
- TypedArray<String> warnings = Node::get_configuration_warnings();
+PackedStringArray Range::get_configuration_warnings() const {
+ PackedStringArray warnings = Node::get_configuration_warnings();
if (shared->exp_ratio && shared->min <= 0) {
warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."));
diff --git a/scene/gui/range.h b/scene/gui/range.h
index 87bd0d88af..19452243cf 100644
--- a/scene/gui/range.h
+++ b/scene/gui/range.h
@@ -100,7 +100,7 @@ public:
void share(Range *p_range);
void unshare();
- TypedArray<String> get_configuration_warnings() const override;
+ PackedStringArray get_configuration_warnings() const override;
Range();
~Range();
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 99cad80cde..7ea46a0b4f 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -52,7 +52,7 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) co
} else if (p_item->E->next()) {
return p_item->E->next()->get();
} else {
- //go up until something with a next is found
+ // Go up until something with a next is found.
while (p_item->parent && !p_item->E->next()) {
p_item = p_item->parent;
}
@@ -72,7 +72,7 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) co
} else if (p_item->E->next()) {
return p_item->E->next()->get();
} else {
- //go up until something with a next is found
+ // Go up until something with a next is found.
while (p_item->type != ITEM_FRAME && !p_item->E->next()) {
p_item = p_item->parent;
}
@@ -84,8 +84,6 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) co
}
}
}
-
- return nullptr;
}
RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) const {
@@ -97,7 +95,7 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co
} else if (p_item->E->prev()) {
return p_item->E->prev()->get();
} else {
- //go back until something with a prev is found
+ // Go back until something with a prev is found.
while (p_item->parent && !p_item->E->prev()) {
p_item = p_item->parent;
}
@@ -117,7 +115,7 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co
} else if (p_item->E->prev()) {
return p_item->E->prev()->get();
} else {
- //go back until something with a prev is found
+ // Go back until something with a prev is found.
while (p_item->type != ITEM_FRAME && !p_item->E->prev()) {
p_item = p_item->parent;
}
@@ -129,8 +127,6 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co
}
}
}
-
- return nullptr;
}
Rect2 RichTextLabel::_get_text_rect() {
@@ -965,17 +961,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
uint32_t gl = glyphs[i].index;
uint16_t gl_fl = glyphs[i].flags;
uint8_t gl_cn = glyphs[i].count;
- bool cprev = false;
+ bool cprev_cluster = false;
+ bool cprev_conn = false;
if (gl_cn == 0) { // Parts of the same cluster, always connected.
- cprev = true;
+ cprev_cluster = true;
}
if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected.
if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) {
- cprev = true;
+ cprev_conn = true;
}
} else {
if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
- cprev = true;
+ cprev_conn = true;
}
}
@@ -994,6 +991,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
for (int j = 0; j < fx_stack.size(); j++) {
ItemFX *item_fx = fx_stack[j];
+ bool cn = cprev_cluster || (cprev_conn && item_fx->connected);
+
if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) {
ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx);
@@ -1024,12 +1023,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
} else if (item_fx->type == ITEM_SHAKE) {
ItemShake *item_shake = static_cast<ItemShake *>(item_fx);
- if (!cprev) {
+ if (!cn) {
uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
uint64_t max_rand = 2147483647;
- double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
- double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+ double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+ double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
n_time = (n_time > 1.0) ? 1.0 : n_time;
item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f;
@@ -1038,7 +1037,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
} else if (item_fx->type == ITEM_WAVE) {
ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
- if (!cprev) {
+ if (!cn) {
double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_wave->amplitude / 10.0f);
item_wave->prev_off = Point2(0, 1) * value;
}
@@ -1046,7 +1045,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
} else if (item_fx->type == ITEM_TORNADO) {
ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
- if (!cprev) {
+ if (!cn) {
double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
item_tornado->prev_off = Point2(torn_x, torn_y);
@@ -1181,17 +1180,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
uint32_t gl = glyphs[i].index;
uint16_t gl_fl = glyphs[i].flags;
uint8_t gl_cn = glyphs[i].count;
- bool cprev = false;
+ bool cprev_cluster = false;
+ bool cprev_conn = false;
if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected.
- cprev = true;
+ cprev_cluster = true;
}
if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected.
if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) {
- cprev = true;
+ cprev_conn = true;
}
} else {
if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
- cprev = true;
+ cprev_conn = true;
}
}
@@ -1209,6 +1209,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
for (int j = 0; j < fx_stack.size(); j++) {
ItemFX *item_fx = fx_stack[j];
+ bool cn = cprev_cluster || (cprev_conn && item_fx->connected);
+
if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) {
ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx);
@@ -1239,12 +1241,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
} else if (item_fx->type == ITEM_SHAKE) {
ItemShake *item_shake = static_cast<ItemShake *>(item_fx);
- if (!cprev) {
+ if (!cn) {
uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
uint64_t max_rand = 2147483647;
- double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
- double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+ double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+ double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
n_time = (n_time > 1.0) ? 1.0 : n_time;
item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f;
@@ -1253,7 +1255,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
} else if (item_fx->type == ITEM_WAVE) {
ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
- if (!cprev) {
+ if (!cn) {
double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f);
item_wave->prev_off = Point2(0, 1) * value;
}
@@ -1261,7 +1263,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
} else if (item_fx->type == ITEM_TORNADO) {
ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
- if (!cprev) {
+ if (!cn) {
double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
item_tornado->prev_off = Point2(torn_x, torn_y);
@@ -1664,7 +1666,7 @@ int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const {
r = m;
}
}
- return l;
+ return MIN(l, (int)main->lines.size() - 1);
}
_FORCE_INLINE_ float RichTextLabel::_calculate_line_vertical_offset(const RichTextLabel::Line &line) const {
@@ -1676,8 +1678,8 @@ void RichTextLabel::_update_theme_item_cache() {
theme_cache.normal_style = get_theme_stylebox(SNAME("normal"));
theme_cache.focus_style = get_theme_stylebox(SNAME("focus"));
- theme_cache.progress_bg_style = get_theme_stylebox(SNAME("bg"), SNAME("ProgressBar"));
- theme_cache.progress_fg_style = get_theme_stylebox(SNAME("fg"), SNAME("ProgressBar"));
+ theme_cache.progress_bg_style = get_theme_stylebox(SNAME("background"), SNAME("ProgressBar"));
+ theme_cache.progress_fg_style = get_theme_stylebox(SNAME("fill"), SNAME("ProgressBar"));
theme_cache.line_separation = get_theme_constant(SNAME("line_separation"));
@@ -1693,7 +1695,6 @@ void RichTextLabel::_update_theme_item_cache() {
theme_cache.shadow_offset_x = get_theme_constant(SNAME("shadow_offset_x"));
theme_cache.shadow_offset_y = get_theme_constant(SNAME("shadow_offset_y"));
theme_cache.outline_size = get_theme_constant(SNAME("outline_size"));
- theme_cache.outline_color = get_theme_color(SNAME("outline_color"));
theme_cache.bold_font = get_theme_font(SNAME("bold_font"));
theme_cache.bold_font_size = get_theme_font_size(SNAME("bold_font_size"));
@@ -2189,6 +2190,75 @@ RichTextLabel::ItemFont *RichTextLabel::_find_font(Item *p_item) {
while (fontitem) {
if (fontitem->type == ITEM_FONT) {
ItemFont *fi = static_cast<ItemFont *>(fontitem);
+ switch (fi->def_font) {
+ case NORMAL_FONT: {
+ if (fi->variation) {
+ Ref<FontVariation> fc = fi->font;
+ if (fc.is_valid()) {
+ fc->set_base_font(theme_cache.normal_font);
+ }
+ } else {
+ fi->font = theme_cache.normal_font;
+ }
+ if (fi->def_size) {
+ fi->font_size = theme_cache.normal_font_size;
+ }
+ } break;
+ case BOLD_FONT: {
+ if (fi->variation) {
+ Ref<FontVariation> fc = fi->font;
+ if (fc.is_valid()) {
+ fc->set_base_font(theme_cache.bold_font);
+ }
+ } else {
+ fi->font = theme_cache.bold_font;
+ }
+ if (fi->def_size) {
+ fi->font_size = theme_cache.bold_font_size;
+ }
+ } break;
+ case ITALICS_FONT: {
+ if (fi->variation) {
+ Ref<FontVariation> fc = fi->font;
+ if (fc.is_valid()) {
+ fc->set_base_font(theme_cache.italics_font);
+ }
+ } else {
+ fi->font = theme_cache.italics_font;
+ }
+ if (fi->def_size) {
+ fi->font_size = theme_cache.italics_font_size;
+ }
+ } break;
+ case BOLD_ITALICS_FONT: {
+ if (fi->variation) {
+ Ref<FontVariation> fc = fi->font;
+ if (fc.is_valid()) {
+ fc->set_base_font(theme_cache.bold_italics_font);
+ }
+ } else {
+ fi->font = theme_cache.bold_italics_font;
+ }
+ if (fi->def_size) {
+ fi->font_size = theme_cache.bold_italics_font_size;
+ }
+ } break;
+ case MONO_FONT: {
+ if (fi->variation) {
+ Ref<FontVariation> fc = fi->font;
+ if (fc.is_valid()) {
+ fc->set_base_font(theme_cache.mono_font);
+ }
+ } else {
+ fi->font = theme_cache.mono_font;
+ }
+ if (fi->def_size) {
+ fi->font_size = theme_cache.mono_font_size;
+ }
+ } break;
+ default: {
+ } break;
+ }
return fi;
}
@@ -2564,7 +2634,9 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) {
void RichTextLabel::_thread_function(void *self) {
RichTextLabel *rtl = reinterpret_cast<RichTextLabel *>(self);
+ rtl->set_physics_process_internal(true);
rtl->_process_line_caches();
+ rtl->set_physics_process_internal(false);
rtl->updating.store(false);
rtl->call_deferred(SNAME("queue_redraw"));
}
@@ -2680,7 +2752,6 @@ bool RichTextLabel::_validate_line_caches() {
loaded.store(true);
thread.start(RichTextLabel::_thread_function, reinterpret_cast<void *>(this));
loading_started = OS::get_singleton()->get_ticks_msec();
- set_physics_process_internal(true);
return false;
} else {
_process_line_caches();
@@ -3004,6 +3075,33 @@ void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font
_add_item(item, false);
}
+void RichTextLabel::_push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
+ ERR_FAIL_COND(current->type == ITEM_TABLE);
+ ItemFont *item = memnew(ItemFont);
+
+ item->def_font = p_def_font;
+ item->variation = true;
+ item->font = p_font;
+ item->font_size = p_size;
+ item->def_size = (p_size <= 0);
+ _add_item(item, true);
+}
+
+void RichTextLabel::_push_def_font(DefaultFont p_def_font) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
+ ERR_FAIL_COND(current->type == ITEM_TABLE);
+ ItemFont *item = memnew(ItemFont);
+
+ item->def_font = p_def_font;
+ item->def_size = true;
+ _add_item(item, true);
+}
+
void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) {
_stop_thread();
MutexLock data_lock(data_mutex);
@@ -3020,31 +3118,31 @@ void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) {
void RichTextLabel::push_normal() {
ERR_FAIL_COND(theme_cache.normal_font.is_null());
- push_font(theme_cache.normal_font, theme_cache.normal_font_size);
+ _push_def_font(NORMAL_FONT);
}
void RichTextLabel::push_bold() {
ERR_FAIL_COND(theme_cache.bold_font.is_null());
- push_font(theme_cache.bold_font, theme_cache.bold_font_size);
+ _push_def_font(BOLD_FONT);
}
void RichTextLabel::push_bold_italics() {
ERR_FAIL_COND(theme_cache.bold_italics_font.is_null());
- push_font(theme_cache.bold_italics_font, theme_cache.bold_italics_font_size);
+ _push_def_font(BOLD_ITALICS_FONT);
}
void RichTextLabel::push_italics() {
ERR_FAIL_COND(theme_cache.italics_font.is_null());
- push_font(theme_cache.italics_font, theme_cache.italics_font_size);
+ _push_def_font(ITALICS_FONT);
}
void RichTextLabel::push_mono() {
ERR_FAIL_COND(theme_cache.mono_font.is_null());
- push_font(theme_cache.mono_font, theme_cache.mono_font_size);
+ _push_def_font(MONO_FONT);
}
void RichTextLabel::push_font_size(int p_font_size) {
@@ -3201,33 +3299,36 @@ void RichTextLabel::push_fade(int p_start_index, int p_length) {
_add_item(item, true);
}
-void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) {
+void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f, bool p_connected = true) {
_stop_thread();
MutexLock data_lock(data_mutex);
ItemShake *item = memnew(ItemShake);
item->strength = p_strength;
item->rate = p_rate;
+ item->connected = p_connected;
_add_item(item, true);
}
-void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f) {
+void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f, bool p_connected = true) {
_stop_thread();
MutexLock data_lock(data_mutex);
ItemWave *item = memnew(ItemWave);
item->frequency = p_frequency;
item->amplitude = p_amplitude;
+ item->connected = p_connected;
_add_item(item, true);
}
-void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f) {
+void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f, bool p_connected = true) {
_stop_thread();
MutexLock data_lock(data_mutex);
ItemTornado *item = memnew(ItemTornado);
item->frequency = p_frequency;
item->radius = p_radius;
+ item->connected = p_connected;
_add_item(item, true);
}
@@ -3635,9 +3736,9 @@ void RichTextLabel::append_text(const String &p_bbcode) {
//use bold font
in_bold = true;
if (in_italics) {
- push_font(theme_cache.bold_italics_font, theme_cache.bold_italics_font_size);
+ _push_def_font(BOLD_ITALICS_FONT);
} else {
- push_font(theme_cache.bold_font, theme_cache.bold_font_size);
+ _push_def_font(BOLD_FONT);
}
pos = brk_end + 1;
tag_stack.push_front(tag);
@@ -3645,15 +3746,15 @@ void RichTextLabel::append_text(const String &p_bbcode) {
//use italics font
in_italics = true;
if (in_bold) {
- push_font(theme_cache.bold_italics_font, theme_cache.bold_italics_font_size);
+ _push_def_font(BOLD_ITALICS_FONT);
} else {
- push_font(theme_cache.italics_font, theme_cache.italics_font_size);
+ _push_def_font(ITALICS_FONT);
}
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "code") {
//use monospace font
- push_font(theme_cache.mono_font, theme_cache.mono_font_size);
+ _push_def_font(MONO_FONT);
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("table=")) {
@@ -3941,7 +4042,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
int fs = theme_cache.normal_font_size * 3;
Ref<Font> f = theme_cache.normal_font;
Color color = theme_cache.default_color;
- Color outline_color = theme_cache.outline_color;
+ Color outline_color = theme_cache.font_outline_color;
int outline_size = theme_cache.outline_size;
Rect2 dropcap_margins = Rect2();
@@ -4078,24 +4179,21 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front("font_size");
- } else if (tag.begins_with("opentype_features=")) {
- String fnt_ftr = tag.substr(18, tag.length());
+ } else if (tag.begins_with("opentype_features=") || tag.begins_with("otf=")) {
+ int value_pos = tag.find("=");
+ String fnt_ftr = tag.substr(value_pos + 1);
Vector<String> subtag = fnt_ftr.split(",");
if (subtag.size() > 0) {
Ref<Font> font = theme_cache.normal_font;
- int font_size = 0;
+ DefaultFont def_font = NORMAL_FONT;
+
ItemFont *font_it = _find_font(current);
if (font_it) {
if (font_it->font.is_valid()) {
font = font_it->font;
- }
- if (font_it->font_size > 0) {
- font_size = font_it->font_size;
+ def_font = font_it->def_font;
}
}
- Ref<FontVariation> fc;
- fc.instantiate();
- fc->set_base_font(font);
Dictionary features;
for (int i = 0; i < subtag.size(); i++) {
Vector<String> subtag_a = subtag[i].split("=");
@@ -4105,11 +4203,21 @@ void RichTextLabel::append_text(const String &p_bbcode) {
features[TS->name_to_tag(subtag_a[0])] = 1;
}
}
+
+ Ref<FontVariation> fc;
+ fc.instantiate();
+
+ fc->set_base_font(font);
fc->set_opentype_features(features);
- push_font(fc, font_size);
+
+ if (def_font != CUSTOM_FONT) {
+ _push_def_font_var(def_font, fc);
+ } else {
+ push_font(fc);
+ }
}
pos = brk_end + 1;
- tag_stack.push_front("opentype_features");
+ tag_stack.push_front(tag.substr(0, value_pos));
} else if (tag.begins_with("font=")) {
String fnt = tag.substr(5, tag.length());
@@ -4125,9 +4233,21 @@ void RichTextLabel::append_text(const String &p_bbcode) {
} else if (tag.begins_with("font ")) {
Vector<String> subtag = tag.substr(2, tag.length()).split(" ");
+ Ref<Font> font = theme_cache.normal_font;
+ DefaultFont def_font = NORMAL_FONT;
+
+ ItemFont *font_it = _find_font(current);
+ if (font_it) {
+ if (font_it->font.is_valid()) {
+ font = font_it->font;
+ def_font = font_it->def_font;
+ }
+ }
+
Ref<FontVariation> fc;
fc.instantiate();
- int fnt_size = 0;
+
+ int fnt_size = -1;
for (int i = 1; i < subtag.size(); i++) {
Vector<String> subtag_a = subtag[i].split("=", true, 2);
if (subtag_a.size() == 2) {
@@ -4135,7 +4255,8 @@ void RichTextLabel::append_text(const String &p_bbcode) {
String fnt = subtag_a[1];
Ref<Font> font_data = ResourceLoader::load(fnt, "Font");
if (font_data.is_valid()) {
- fc->set_base_font(font_data);
+ font = font_data;
+ def_font = CUSTOM_FONT;
}
} else if (subtag_a[0] == "size" || subtag_a[0] == "s") {
fnt_size = subtag_a[1].to_int();
@@ -4189,7 +4310,14 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
}
}
- push_font(fc, fnt_size);
+ fc->set_base_font(font);
+
+ if (def_font != CUSTOM_FONT) {
+ _push_def_font_var(def_font, fc, fnt_size);
+ } else {
+ push_font(fc, fnt_size);
+ }
+
pos = brk_end + 1;
tag_stack.push_front("font");
@@ -4230,7 +4358,13 @@ void RichTextLabel::append_text(const String &p_bbcode) {
rate = rate_option->value.to_float();
}
- push_shake(strength, rate);
+ bool connected = true;
+ OptionMap::Iterator connected_option = bbcode_options.find("connected");
+ if (connected_option) {
+ connected = connected_option->value.to_int();
+ }
+
+ push_shake(strength, rate, connected);
pos = brk_end + 1;
tag_stack.push_front("shake");
set_process_internal(true);
@@ -4247,7 +4381,13 @@ void RichTextLabel::append_text(const String &p_bbcode) {
period = period_option->value.to_float();
}
- push_wave(period, amplitude);
+ bool connected = true;
+ OptionMap::Iterator connected_option = bbcode_options.find("connected");
+ if (connected_option) {
+ connected = connected_option->value.to_int();
+ }
+
+ push_wave(period, amplitude, connected);
pos = brk_end + 1;
tag_stack.push_front("wave");
set_process_internal(true);
@@ -4264,7 +4404,13 @@ void RichTextLabel::append_text(const String &p_bbcode) {
frequency = frequency_option->value.to_float();
}
- push_tornado(frequency, radius);
+ bool connected = true;
+ OptionMap::Iterator connected_option = bbcode_options.find("connected");
+ if (connected_option) {
+ connected = connected_option->value.to_int();
+ }
+
+ push_tornado(frequency, radius, connected);
pos = brk_end + 1;
tag_stack.push_front("tornado");
set_process_internal(true);
@@ -4364,16 +4510,18 @@ int RichTextLabel::get_visible_paragraph_count() const {
if (!is_visible()) {
return 0;
}
+
+ const_cast<RichTextLabel *>(this)->_validate_line_caches();
return visible_paragraph_count;
}
void RichTextLabel::scroll_to_line(int p_line) {
- _validate_line_caches();
-
if (p_line <= 0) {
vscroll->set_value(0);
return;
}
+ _validate_line_caches();
+
int line_count = 0;
int to_line = main->first_invalid_line.load();
for (int i = 0; i < to_line; i++) {
@@ -4392,6 +4540,8 @@ void RichTextLabel::scroll_to_line(int p_line) {
}
float RichTextLabel::get_line_offset(int p_line) {
+ _validate_line_caches();
+
int line_count = 0;
int to_line = main->first_invalid_line.load();
for (int i = 0; i < to_line; i++) {
@@ -4409,6 +4559,8 @@ float RichTextLabel::get_line_offset(int p_line) {
}
float RichTextLabel::get_paragraph_offset(int p_paragraph) {
+ _validate_line_caches();
+
int to_line = main->first_invalid_line.load();
if (0 <= p_paragraph && p_paragraph < to_line) {
return main->lines[p_paragraph].offset.y;
@@ -4417,6 +4569,8 @@ float RichTextLabel::get_paragraph_offset(int p_paragraph) {
}
int RichTextLabel::get_line_count() const {
+ const_cast<RichTextLabel *>(this)->_validate_line_caches();
+
int line_count = 0;
int to_line = main->first_invalid_line.load();
for (int i = 0; i < to_line; i++) {
@@ -4430,6 +4584,8 @@ int RichTextLabel::get_visible_line_count() const {
if (!is_visible()) {
return 0;
}
+ const_cast<RichTextLabel *>(this)->_validate_line_caches();
+
return visible_line_count;
}
@@ -4631,7 +4787,10 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p
queue_redraw();
return true;
}
- p_search_previous ? current_line-- : current_line++;
+
+ if (current_line != ending_line) {
+ p_search_previous ? current_line-- : current_line++;
+ }
}
if (p_from_selection && selection.active) {
@@ -4844,7 +5003,14 @@ void RichTextLabel::set_use_bbcode(bool p_enable) {
}
use_bbcode = p_enable;
notify_property_list_changed();
- set_text(text);
+
+ const String current_text = text;
+ if (use_bbcode) {
+ parse_bbcode(current_text);
+ } else { // raw text
+ clear();
+ add_text(current_text);
+ }
}
bool RichTextLabel::is_using_bbcode() const {
@@ -5005,7 +5171,12 @@ int RichTextLabel::get_content_height() const {
int to_line = main->first_invalid_line.load();
if (to_line) {
MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex());
- total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * theme_cache.line_separation;
+ if (theme_cache.line_separation < 0) {
+ // Do not apply to the last line to avoid cutting text.
+ total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + (main->lines[to_line - 1].text_buf->get_line_count() - 1) * theme_cache.line_separation;
+ } else {
+ total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * theme_cache.line_separation;
+ }
}
return total_height;
}
@@ -5298,6 +5469,8 @@ int RichTextLabel::get_visible_characters() const {
}
int RichTextLabel::get_character_line(int p_char) {
+ _validate_line_caches();
+
int line_count = 0;
int to_line = main->first_invalid_line.load();
for (int i = 0; i < to_line; i++) {
@@ -5318,6 +5491,8 @@ int RichTextLabel::get_character_line(int p_char) {
}
int RichTextLabel::get_character_paragraph(int p_char) {
+ _validate_line_caches();
+
int para_count = 0;
int to_line = main->first_invalid_line.load();
for (int i = 0; i < to_line; i++) {
@@ -5349,6 +5524,8 @@ int RichTextLabel::get_total_character_count() const {
}
int RichTextLabel::get_total_glyph_count() const {
+ const_cast<RichTextLabel *>(this)->_validate_line_caches();
+
int tg = 0;
Item *it = main;
while (it) {
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 8bc28a9ecf..71123602ad 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -82,6 +82,15 @@ public:
MENU_SELECT_ALL,
};
+ enum DefaultFont {
+ NORMAL_FONT,
+ BOLD_FONT,
+ ITALICS_FONT,
+ BOLD_ITALICS_FONT,
+ MONO_FONT,
+ CUSTOM_FONT,
+ };
+
protected:
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
@@ -178,7 +187,10 @@ private:
};
struct ItemFont : public Item {
+ DefaultFont def_font = CUSTOM_FONT;
Ref<Font> font;
+ bool variation = false;
+ bool def_size = false;
int font_size = 0;
ItemFont() { type = ITEM_FONT; }
};
@@ -272,6 +284,7 @@ private:
struct ItemFX : public Item {
double elapsed_time = 0.f;
+ bool connected = true;
};
struct ItemShake : public ItemFX {
@@ -560,6 +573,8 @@ public:
void add_newline();
bool remove_line(const int p_line);
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);
void push_font(const Ref<Font> &p_font, int p_size = 0);
void push_font_size(int p_font_size);
void push_outline_size(int p_font_size);
@@ -579,9 +594,9 @@ public:
void push_hint(const String &p_string);
void push_table(int p_columns, InlineAlignment p_alignment = INLINE_ALIGNMENT_TOP);
void push_fade(int p_start_index, int p_length);
- void push_shake(int p_strength, float p_rate);
- void push_wave(float p_frequency, float p_amplitude);
- void push_tornado(float p_frequency, float p_radius);
+ void push_shake(int p_strength, float p_rate, bool p_connected);
+ void push_wave(float p_frequency, float p_amplitude, bool p_connected);
+ void push_tornado(float p_frequency, float p_radius, bool p_connected);
void push_rainbow(float p_saturation, float p_value, float p_frequency);
void push_bgcolor(const Color &p_color);
void push_fgcolor(const Color &p_color);
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 374ce6feea..6c05b171e3 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -321,7 +321,7 @@ void ScrollBar::_notification(int p_what) {
if (drag_node) {
drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input));
- drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONESHOT);
+ drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT);
}
} break;
@@ -613,7 +613,7 @@ void ScrollBar::set_drag_node(const NodePath &p_path) {
if (drag_node) {
drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input));
- drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONESHOT);
+ drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT);
}
}
}
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index f68cebd657..a6aa4707ff 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -76,14 +76,14 @@ Size2 ScrollContainer::get_minimum_size() const {
min_size.x += v_scroll->get_minimum_size().x;
}
- min_size += theme_cache.bg_style->get_minimum_size();
+ min_size += theme_cache.panel_style->get_minimum_size();
return min_size;
}
void ScrollContainer::_update_theme_item_cache() {
Container::_update_theme_item_cache();
- theme_cache.bg_style = get_theme_stylebox(SNAME("bg"));
+ theme_cache.panel_style = get_theme_stylebox(SNAME("panel"));
}
void ScrollContainer::_cancel_drag() {
@@ -162,7 +162,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
drag_accum = Vector2();
last_drag_accum = Vector2();
drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value());
- drag_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
+ drag_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
drag_touching_deaccel = false;
beyond_deadzone = false;
time_since_motion = 0;
@@ -276,8 +276,8 @@ void ScrollContainer::_reposition_children() {
Size2 size = get_size();
Point2 ofs;
- size -= theme_cache.bg_style->get_minimum_size();
- ofs += theme_cache.bg_style->get_offset();
+ size -= theme_cache.panel_style->get_minimum_size();
+ ofs += theme_cache.panel_style->get_offset();
bool rtl = is_layout_rtl();
if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) { //scrolls may have been moved out for reasons
@@ -341,7 +341,7 @@ void ScrollContainer::_notification(int p_what) {
} break;
case NOTIFICATION_DRAW: {
- draw_style_box(theme_cache.bg_style, Rect2(Vector2(), get_size()));
+ draw_style_box(theme_cache.panel_style, Rect2(Vector2(), get_size()));
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
@@ -416,7 +416,7 @@ void ScrollContainer::_notification(int p_what) {
void ScrollContainer::update_scrollbars() {
Size2 size = get_size();
- size -= theme_cache.bg_style->get_minimum_size();
+ size -= theme_cache.panel_style->get_minimum_size();
Size2 hmin = h_scroll->get_combined_minimum_size();
Size2 vmin = v_scroll->get_combined_minimum_size();
@@ -501,8 +501,8 @@ void ScrollContainer::set_follow_focus(bool p_follow) {
follow_focus = p_follow;
}
-TypedArray<String> ScrollContainer::get_configuration_warnings() const {
- TypedArray<String> warnings = Container::get_configuration_warnings();
+PackedStringArray ScrollContainer::get_configuration_warnings() const {
+ PackedStringArray warnings = Container::get_configuration_warnings();
int found = 0;
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
index fa1f09ab3f..0079358ef7 100644
--- a/scene/gui/scroll_container.h
+++ b/scene/gui/scroll_container.h
@@ -70,7 +70,7 @@ private:
bool follow_focus = false;
struct ThemeCache {
- Ref<StyleBox> bg_style;
+ Ref<StyleBox> panel_style;
} theme_cache;
void _cancel_drag();
@@ -114,7 +114,7 @@ public:
VScrollBar *get_v_scroll_bar();
void ensure_control_visible(Control *p_control);
- TypedArray<String> get_configuration_warnings() const override;
+ PackedStringArray get_configuration_warnings() const override;
ScrollContainer();
};
diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp
index d4fea0d206..2ca1d6239e 100644
--- a/scene/gui/split_container.cpp
+++ b/scene/gui/split_container.cpp
@@ -33,11 +33,99 @@
#include "label.h"
#include "margin_container.h"
+void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
+ SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
+
+ if (sc->collapsed || !sc->_getch(0) || !sc->_getch(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) {
+ return;
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid()) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
+ if (mb->is_pressed()) {
+ sc->_compute_middle_sep(true);
+ dragging = true;
+ drag_ofs = sc->split_offset;
+ if (sc->vertical) {
+ drag_from = get_transform().xform(mb->get_position()).y;
+ } else {
+ drag_from = get_transform().xform(mb->get_position()).x;
+ }
+ } else {
+ dragging = false;
+ queue_redraw();
+ }
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid()) {
+ if (!dragging) {
+ return;
+ }
+
+ Vector2i in_parent_pos = get_transform().xform(mm->get_position());
+ if (!sc->vertical && is_layout_rtl()) {
+ sc->split_offset = drag_ofs - ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from);
+ } else {
+ sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from);
+ }
+ sc->_compute_middle_sep(true);
+ sc->queue_sort();
+ sc->emit_signal(SNAME("dragged"), sc->get_split_offset());
+ }
+}
+
+Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const {
+ SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
+
+ if (!sc->collapsed && sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE) {
+ return (sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT);
+ }
+
+ return Control::get_cursor_shape(p_pos);
+}
+
+void SplitContainerDragger::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_MOUSE_ENTER: {
+ mouse_inside = true;
+ SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
+ if (sc->get_theme_constant(SNAME("autohide"))) {
+ queue_redraw();
+ }
+ } break;
+
+ case NOTIFICATION_MOUSE_EXIT: {
+ mouse_inside = false;
+ SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
+ if (sc->get_theme_constant(SNAME("autohide"))) {
+ queue_redraw();
+ }
+ } break;
+
+ case NOTIFICATION_DRAW: {
+ SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
+ if (!dragging && !mouse_inside && sc->get_theme_constant(SNAME("autohide"))) {
+ return;
+ }
+
+ Ref<Texture2D> tex = sc->get_theme_icon(SNAME("grabber"));
+ draw_texture(tex, (get_size() - tex->get_size()) / 2);
+ } break;
+ }
+}
+
Control *SplitContainer::_getch(int p_idx) const {
int idx = 0;
- for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ 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;
}
@@ -82,9 +170,8 @@ void SplitContainer::_compute_middle_sep(bool p_clamp) {
int ms_second = second->get_combined_minimum_size()[axis];
// Determine the separation between items.
- Ref<Texture2D> g = get_theme_icon(SNAME("grabber"));
- int sep = get_theme_constant(SNAME("separation"));
- sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(sep, vertical ? g->get_height() : g->get_width()) : 0;
+ Ref<Texture2D> g = _get_grabber_icon();
+ int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
// Compute the wished separation_point.
int wished_middle_sep = 0;
@@ -115,22 +202,25 @@ void SplitContainer::_resort() {
Control *first = _getch(0);
Control *second = _getch(1);
- // If we have only one element
+ // If we have only one element.
if (!first || !second) {
if (first) {
fit_child_in_rect(first, Rect2(Point2(), get_size()));
} else if (second) {
fit_child_in_rect(second, Rect2(Point2(), get_size()));
}
+ dragging_area_control->hide();
return;
}
// If we have more that one.
_compute_middle_sep(false);
+ // Determine the separation between items.
Ref<Texture2D> g = _get_grabber_icon();
int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
+ // Move the children, including the dragger.
if (vertical) {
fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, middle_sep)));
int sofs = middle_sep + sep;
@@ -148,7 +238,21 @@ void SplitContainer::_resort() {
}
}
- queue_redraw();
+ // Handle the dragger visibility and position.
+ if (dragger_visibility == DRAGGER_VISIBLE && !collapsed) {
+ dragging_area_control->show();
+
+ int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness);
+ if (vertical) {
+ dragging_area_control->set_rect(Rect2(Point2(0, middle_sep - (dragger_ctrl_size - sep) / 2), Size2(get_size().width, dragger_ctrl_size)));
+ } else {
+ dragging_area_control->set_rect(Rect2(Point2(middle_sep - (dragger_ctrl_size - sep) / 2, 0), Size2(dragger_ctrl_size, get_size().height)));
+ }
+
+ dragging_area_control->queue_redraw();
+ } else {
+ dragging_area_control->hide();
+ }
}
Size2 SplitContainer::get_minimum_size() const {
@@ -183,10 +287,17 @@ Size2 SplitContainer::get_minimum_size() const {
return minimum;
}
+void SplitContainer::_validate_property(PropertyInfo &p_property) const {
+ if (is_fixed && p_property.name == "vertical") {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+}
+
void SplitContainer::_update_theme_item_cache() {
Container::_update_theme_item_cache();
theme_cache.separation = get_theme_constant(SNAME("separation"));
+ theme_cache.minimum_grab_thickness = get_theme_constant(SNAME("minimum_grab_thickness"));
theme_cache.autohide = get_theme_constant(SNAME("autohide"));
theme_cache.grabber_icon = get_theme_icon(SNAME("grabber"));
theme_cache.grabber_icon_h = get_theme_icon(SNAME("h_grabber"));
@@ -204,134 +315,12 @@ void SplitContainer::_notification(int p_what) {
_resort();
} break;
- case NOTIFICATION_MOUSE_EXIT: {
- mouse_inside = false;
- if (theme_cache.autohide) {
- queue_redraw();
- }
- } break;
-
- case NOTIFICATION_DRAW: {
- if (!_getch(0) || !_getch(1)) {
- return;
- }
-
- if (collapsed || (!dragging && !mouse_inside && theme_cache.autohide)) {
- return;
- }
-
- if (dragger_visibility != DRAGGER_VISIBLE) {
- return;
- }
-
- int sep = dragger_visibility != DRAGGER_HIDDEN_COLLAPSED ? theme_cache.separation : 0;
- Ref<Texture2D> tex = theme_cache.grabber_icon;
- Size2 size = get_size();
-
- if (vertical) {
- draw_texture(tex, Point2i((size.x - tex->get_width()) / 2, middle_sep + (sep - tex->get_height()) / 2));
- } else {
- draw_texture(tex, Point2i(middle_sep + (sep - tex->get_width()) / 2, (size.y - tex->get_height()) / 2));
- }
- } break;
-
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
} break;
}
}
-void SplitContainer::_validate_property(PropertyInfo &p_property) const {
- if (is_fixed && p_property.name == "vertical") {
- p_property.usage = PROPERTY_USAGE_NONE;
- }
-}
-
-void SplitContainer::gui_input(const Ref<InputEvent> &p_event) {
- ERR_FAIL_COND(p_event.is_null());
-
- if (collapsed || !_getch(0) || !_getch(1) || dragger_visibility != DRAGGER_VISIBLE) {
- return;
- }
-
- Ref<InputEventMouseButton> mb = p_event;
-
- if (mb.is_valid()) {
- if (mb->get_button_index() == MouseButton::LEFT) {
- if (mb->is_pressed()) {
- if (vertical) {
- if (mb->get_position().y > middle_sep && mb->get_position().y < middle_sep + theme_cache.separation) {
- _compute_middle_sep(true);
- dragging = true;
- drag_from = mb->get_position().y;
- drag_ofs = split_offset;
- }
- } else {
- if (mb->get_position().x > middle_sep && mb->get_position().x < middle_sep + theme_cache.separation) {
- _compute_middle_sep(true);
- dragging = true;
- drag_from = mb->get_position().x;
- drag_ofs = split_offset;
- }
- }
- } else {
- dragging = false;
- }
- }
- }
-
- Ref<InputEventMouseMotion> mm = p_event;
-
- if (mm.is_valid()) {
- bool mouse_inside_state = false;
- if (vertical) {
- mouse_inside_state = mm->get_position().y > middle_sep && mm->get_position().y < middle_sep + theme_cache.separation;
- } else {
- mouse_inside_state = mm->get_position().x > middle_sep && mm->get_position().x < middle_sep + theme_cache.separation;
- }
-
- if (mouse_inside != mouse_inside_state) {
- mouse_inside = mouse_inside_state;
- if (theme_cache.autohide) {
- queue_redraw();
- }
- }
-
- if (!dragging) {
- return;
- }
-
- if (!vertical && is_layout_rtl()) {
- split_offset = drag_ofs - ((vertical ? mm->get_position().y : mm->get_position().x) - drag_from);
- } else {
- split_offset = drag_ofs + ((vertical ? mm->get_position().y : mm->get_position().x) - drag_from);
- }
- _compute_middle_sep(true);
- queue_sort();
- emit_signal(SNAME("dragged"), get_split_offset());
- }
-}
-
-Control::CursorShape SplitContainer::get_cursor_shape(const Point2 &p_pos) const {
- if (dragging) {
- return (vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT);
- }
-
- if (!collapsed && _getch(0) && _getch(1) && dragger_visibility == DRAGGER_VISIBLE) {
- if (vertical) {
- if (p_pos.y > middle_sep && p_pos.y < middle_sep + theme_cache.separation) {
- return CURSOR_VSPLIT;
- }
- } else {
- if (p_pos.x > middle_sep && p_pos.x < middle_sep + theme_cache.separation) {
- return CURSOR_HSPLIT;
- }
- }
- }
-
- return Control::get_cursor_shape(p_pos);
-}
-
void SplitContainer::set_split_offset(int p_offset) {
if (split_offset == p_offset) {
return;
@@ -371,7 +360,6 @@ void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) {
dragger_visibility = p_visibility;
queue_sort();
- queue_redraw();
}
SplitContainer::DraggerVisibility SplitContainer::get_dragger_visibility() const {
@@ -445,4 +433,7 @@ void SplitContainer::_bind_methods() {
SplitContainer::SplitContainer(bool p_vertical) {
vertical = p_vertical;
+
+ dragging_area_control = memnew(SplitContainerDragger);
+ add_child(dragging_area_control, false, Node::INTERNAL_MODE_BACK);
}
diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h
index 598b0ba485..d297e3a3ea 100644
--- a/scene/gui/split_container.h
+++ b/scene/gui/split_container.h
@@ -33,8 +33,26 @@
#include "scene/gui/container.h"
+class SplitContainerDragger : public Control {
+ GDCLASS(SplitContainerDragger, Control);
+
+protected:
+ void _notification(int p_what);
+ virtual void gui_input(const Ref<InputEvent> &p_event) override;
+
+private:
+ bool dragging = false;
+ int drag_from = 0;
+ int drag_ofs = 0;
+ bool mouse_inside = false;
+
+public:
+ virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
+};
+
class SplitContainer : public Container {
GDCLASS(SplitContainer, Container);
+ friend class SplitContainerDragger;
public:
enum DraggerVisibility {
@@ -47,15 +65,14 @@ private:
int split_offset = 0;
int middle_sep = 0;
bool vertical = false;
- bool dragging = false;
- int drag_from = 0;
- int drag_ofs = 0;
bool collapsed = false;
DraggerVisibility dragger_visibility = DRAGGER_VISIBLE;
- bool mouse_inside = false;
+
+ SplitContainerDragger *dragging_area_control = nullptr;
struct ThemeCache {
int separation = 0;
+ int minimum_grab_thickness = 0;
int autohide = 0;
Ref<Texture2D> grabber_icon;
Ref<Texture2D> grabber_icon_h;
@@ -68,10 +85,11 @@ private:
void _compute_middle_sep(bool p_clamp);
void _resort();
+ void _dragging_area_gui_input(const Ref<InputEvent> &p_event);
+
protected:
bool is_fixed = false;
- virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
@@ -92,8 +110,6 @@ public:
void set_vertical(bool p_vertical);
bool is_vertical() const;
- virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
-
virtual Size2 get_minimum_size() const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp
index 88e68ec763..3ad84cbc6d 100644
--- a/scene/gui/subviewport_container.cpp
+++ b/scene/gui/subviewport_container.cpp
@@ -227,8 +227,8 @@ void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) {
}
}
-TypedArray<String> SubViewportContainer::get_configuration_warnings() const {
- TypedArray<String> warnings = Node::get_configuration_warnings();
+PackedStringArray SubViewportContainer::get_configuration_warnings() const {
+ PackedStringArray warnings = Node::get_configuration_warnings();
bool has_viewport = false;
for (int i = 0; i < get_child_count(); i++) {
diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h
index 5b488fb79e..63a58b5f07 100644
--- a/scene/gui/subviewport_container.h
+++ b/scene/gui/subviewport_container.h
@@ -58,7 +58,7 @@ public:
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
- TypedArray<String> get_configuration_warnings() const override;
+ PackedStringArray get_configuration_warnings() const override;
SubViewportContainer();
};
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index 4d18af7743..cf6681f809 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -162,7 +162,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) {
+ if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) {
if (scrolling_enabled && buttons_visible) {
if (offset > 0) {
offset--;
@@ -172,7 +172,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) {
+ if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_or_control_pressed()) {
if (scrolling_enabled && buttons_visible) {
if (missing_right && offset < tabs.size()) {
offset++;
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index f45d132a66..ab4808d312 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -519,12 +519,12 @@ void TabContainer::_refresh_tab_names() {
}
void TabContainer::add_child_notify(Node *p_child) {
+ Container::add_child_notify(p_child);
+
if (p_child == tab_bar) {
return;
}
- Container::add_child_notify(p_child);
-
Control *c = Object::cast_to<Control>(p_child);
if (!c || c->is_set_as_top_level()) {
return;
@@ -838,7 +838,7 @@ Size2 TabContainer::get_minimum_size() const {
}
Vector<Control *> controls = _get_tab_controls();
- int max_control_height = 0;
+ Size2 largest_child_min_size;
for (int i = 0; i < controls.size(); i++) {
Control *c = controls[i];
@@ -847,13 +847,14 @@ Size2 TabContainer::get_minimum_size() const {
}
Size2 cms = c->get_combined_minimum_size();
- ms.x = MAX(ms.x, cms.x);
- max_control_height = MAX(max_control_height, cms.y);
+ largest_child_min_size.x = MAX(largest_child_min_size.x, cms.x);
+ largest_child_min_size.y = MAX(largest_child_min_size.y, cms.y);
}
- ms.y += max_control_height;
+ ms.y += largest_child_min_size.y;
Size2 panel_ms = theme_cache.panel_style->get_minimum_size();
- ms.x = MAX(ms.x, panel_ms.x);
+
+ ms.x = MAX(ms.x, largest_child_min_size.x + panel_ms.x);
ms.y += panel_ms.y;
return ms;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index f93591d8c4..b8973df185 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -536,145 +536,145 @@ void TextEdit::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), background_color);
}
- int brace_open_match_line = -1;
- int brace_open_match_column = -1;
- bool brace_open_matching = false;
- bool brace_open_mismatch = false;
- int brace_close_match_line = -1;
- int brace_close_match_column = -1;
- bool brace_close_matching = false;
- bool brace_close_mismatch = false;
-
- if (highlight_matching_braces_enabled && caret.line >= 0 && caret.line < text.size() && caret.column >= 0) {
- if (caret.column < text[caret.line].length()) {
- // Check for open.
- char32_t c = text[caret.line][caret.column];
- char32_t closec = 0;
-
- if (c == '[') {
- closec = ']';
- } else if (c == '{') {
- closec = '}';
- } else if (c == '(') {
- closec = ')';
+ Vector<BraceMatchingData> brace_matching;
+ if (highlight_matching_braces_enabled) {
+ brace_matching.resize(carets.size());
+
+ for (int caret = 0; caret < carets.size(); caret++) {
+ if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) {
+ continue;
}
- if (closec != 0) {
- int stack = 1;
-
- for (int i = caret.line; i < text.size(); i++) {
- int from = i == caret.line ? caret.column + 1 : 0;
- for (int j = from; j < text[i].length(); j++) {
- char32_t cc = text[i][j];
- // Ignore any brackets inside a string.
- if (cc == '"' || cc == '\'') {
- char32_t quotation = cc;
- do {
- j++;
- if (!(j < text[i].length())) {
- break;
- }
- cc = text[i][j];
- // Skip over escaped quotation marks inside strings.
- if (cc == '\\') {
- bool escaped = true;
- while (j + 1 < text[i].length() && text[i][j + 1] == '\\') {
- escaped = !escaped;
- j++;
+ if (get_caret_column(caret) < text[get_caret_line(caret)].length()) {
+ // Check for open.
+ char32_t c = text[get_caret_line(caret)][get_caret_column(caret)];
+ char32_t closec = 0;
+
+ if (c == '[') {
+ closec = ']';
+ } else if (c == '{') {
+ closec = '}';
+ } else if (c == '(') {
+ closec = ')';
+ }
+
+ if (closec != 0) {
+ int stack = 1;
+
+ for (int i = get_caret_line(caret); i < text.size(); i++) {
+ int from = i == get_caret_line(caret) ? get_caret_column(caret) + 1 : 0;
+ for (int j = from; j < text[i].length(); j++) {
+ char32_t cc = text[i][j];
+ // Ignore any brackets inside a string.
+ if (cc == '"' || cc == '\'') {
+ char32_t quotation = cc;
+ do {
+ j++;
+ if (!(j < text[i].length())) {
+ break;
}
- if (escaped) {
- j++;
- continue;
+ cc = text[i][j];
+ // Skip over escaped quotation marks inside strings.
+ if (cc == '\\') {
+ bool escaped = true;
+ while (j + 1 < text[i].length() && text[i][j + 1] == '\\') {
+ escaped = !escaped;
+ j++;
+ }
+ if (escaped) {
+ j++;
+ continue;
+ }
}
- }
- } while (cc != quotation);
- } else if (cc == c) {
- stack++;
- } else if (cc == closec) {
- stack--;
- }
+ } while (cc != quotation);
+ } else if (cc == c) {
+ stack++;
+ } else if (cc == closec) {
+ stack--;
+ }
- if (stack == 0) {
- brace_open_match_line = i;
- brace_open_match_column = j;
- brace_open_matching = true;
+ if (stack == 0) {
+ brace_matching.write[caret].open_match_line = i;
+ brace_matching.write[caret].open_match_column = j;
+ brace_matching.write[caret].open_matching = true;
+ break;
+ }
+ }
+ if (brace_matching.write[caret].open_match_line != -1) {
break;
}
}
- if (brace_open_match_line != -1) {
- break;
- }
- }
- if (!brace_open_matching) {
- brace_open_mismatch = true;
+ if (!brace_matching.write[caret].open_matching) {
+ brace_matching.write[caret].open_mismatch = true;
+ }
}
}
- }
- if (caret.column > 0) {
- char32_t c = text[caret.line][caret.column - 1];
- char32_t closec = 0;
+ if (get_caret_column(caret) > 0) {
+ char32_t c = text[get_caret_line(caret)][get_caret_column(caret) - 1];
+ char32_t closec = 0;
- if (c == ']') {
- closec = '[';
- } else if (c == '}') {
- closec = '{';
- } else if (c == ')') {
- closec = '(';
- }
+ if (c == ']') {
+ closec = '[';
+ } else if (c == '}') {
+ closec = '{';
+ } else if (c == ')') {
+ closec = '(';
+ }
- if (closec != 0) {
- int stack = 1;
-
- for (int i = caret.line; i >= 0; i--) {
- int from = i == caret.line ? caret.column - 2 : text[i].length() - 1;
- for (int j = from; j >= 0; j--) {
- char32_t cc = text[i][j];
- // Ignore any brackets inside a string.
- if (cc == '"' || cc == '\'') {
- char32_t quotation = cc;
- do {
- j--;
- if (!(j >= 0)) {
- break;
- }
- cc = text[i][j];
- // Skip over escaped quotation marks inside strings.
- if (cc == quotation) {
- bool escaped = false;
- while (j - 1 >= 0 && text[i][j - 1] == '\\') {
- escaped = !escaped;
- j--;
+ if (closec != 0) {
+ int stack = 1;
+
+ for (int i = get_caret_line(caret); i >= 0; i--) {
+ int from = i == get_caret_line(caret) ? get_caret_column(caret) - 2 : text[i].length() - 1;
+ for (int j = from; j >= 0; j--) {
+ char32_t cc = text[i][j];
+ // Ignore any brackets inside a string.
+ if (cc == '"' || cc == '\'') {
+ char32_t quotation = cc;
+ do {
+ j--;
+ if (!(j >= 0)) {
+ break;
}
- if (escaped) {
- cc = '\\';
- continue;
+ cc = text[i][j];
+ // Skip over escaped quotation marks inside strings.
+ if (cc == quotation) {
+ bool escaped = false;
+ while (j - 1 >= 0 && text[i][j - 1] == '\\') {
+ escaped = !escaped;
+ j--;
+ }
+ if (escaped) {
+ cc = '\\';
+ continue;
+ }
}
- }
- } while (cc != quotation);
- } else if (cc == c) {
- stack++;
- } else if (cc == closec) {
- stack--;
- }
+ } while (cc != quotation);
+ } else if (cc == c) {
+ stack++;
+ } else if (cc == closec) {
+ stack--;
+ }
- if (stack == 0) {
- brace_close_match_line = i;
- brace_close_match_column = j;
- brace_close_matching = true;
+ if (stack == 0) {
+ brace_matching.write[caret].close_match_line = i;
+ brace_matching.write[caret].close_match_column = j;
+ brace_matching.write[caret].close_matching = true;
+ break;
+ }
+ }
+ if (brace_matching.write[caret].close_match_line != -1) {
break;
}
}
- if (brace_close_match_line != -1) {
- break;
- }
- }
- if (!brace_close_matching) {
- brace_close_mismatch = true;
+ if (!brace_matching.write[caret].close_matching) {
+ brace_matching.write[caret].close_mismatch = true;
+ }
}
}
}
@@ -683,12 +683,20 @@ void TextEdit::_notification(int p_what) {
bool draw_placeholder = text.size() == 1 && text[0].length() == 0;
// Get the highlighted words.
- String highlighted_text = get_selected_text();
+ String highlighted_text = get_selected_text(0);
// Check if highlighted words contain only whitespaces (tabs or spaces).
bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty();
- const int caret_wrap_index = get_caret_wrap_index();
+ HashMap<int, HashSet<int>> caret_line_wrap_index_map;
+ Vector<int> carets_wrap_index;
+ carets_wrap_index.resize(carets.size());
+ for (int i = 0; i < carets.size(); i++) {
+ carets.write[i].visible = false;
+ int wrap_index = get_caret_wrap_index(i);
+ caret_line_wrap_index_map[get_caret_line(i)].insert(wrap_index);
+ carets_wrap_index.write[i] = wrap_index;
+ }
int first_visible_line = get_first_visible_line() - 1;
int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
@@ -783,7 +791,7 @@ void TextEdit::_notification(int p_what) {
last_wrap_column += wrap_rows[line_wrap_index - 1].length();
}
- if (minimap_line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) {
+ if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) {
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), current_line_color);
} else {
@@ -875,7 +883,6 @@ void TextEdit::_notification(int p_what) {
}
// Draw main text.
- caret.visible = false;
line_drawing_cache.clear();
int row_height = draw_placeholder ? placeholder_line_height + line_spacing : get_line_height();
int line = first_visible_line;
@@ -921,7 +928,7 @@ void TextEdit::_notification(int p_what) {
}
const String &str = wrap_rows[line_wrap_index];
- int char_margin = xmargin_beg - caret.x_ofs;
+ int char_margin = xmargin_beg - first_visible_col;
int ofs_x = 0;
int ofs_y = 0;
@@ -934,7 +941,7 @@ void TextEdit::_notification(int p_what) {
}
ofs_y += i * row_height + line_spacing / 2;
- ofs_y -= caret.wrap_ofs * row_height;
+ ofs_y -= first_visible_line_wrap_ofs * row_height;
ofs_y -= _get_v_scroll_offset() * row_height;
bool clipped = false;
@@ -960,7 +967,7 @@ void TextEdit::_notification(int p_what) {
if (str.length() == 0) {
// Draw line background if empty as we won't loop at all.
- if (line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) {
+ if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) {
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color);
} else {
@@ -969,17 +976,19 @@ void TextEdit::_notification(int p_what) {
}
// Give visual indication of empty selected line.
- if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) {
- float char_w = font->get_char_size(' ', font_size).width;
- if (rtl) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color);
- } else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), selection_color);
+ for (int c = 0; c < carets.size(); c++) {
+ if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c) && char_margin >= xmargin_beg) {
+ float char_w = font->get_char_size(' ', font_size).width;
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), selection_color);
+ }
}
}
} else {
// If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later.
- if (line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) {
+ if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) {
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color);
} else {
@@ -1074,23 +1083,25 @@ void TextEdit::_notification(int p_what) {
char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x;
}
- if (!clipped && selection.active && line >= selection.from_line && line <= selection.to_line) { // Selection
- int sel_from = (line > selection.from_line) ? TS->shaped_text_get_range(rid).x : selection.from_column;
- int sel_to = (line < selection.to_line) ? TS->shaped_text_get_range(rid).y : selection.to_column;
- Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to);
- for (int j = 0; j < sel.size(); j++) {
- Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height);
- if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
- continue;
- }
- if (rect.position.x < xmargin_beg) {
- rect.size.x -= (xmargin_beg - rect.position.x);
- rect.position.x = xmargin_beg;
- }
- if (rect.position.x + rect.size.x > xmargin_end) {
- rect.size.x = xmargin_end - rect.position.x;
+ for (int c = 0; c < carets.size(); c++) {
+ if (!clipped && 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);
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to);
+ for (int j = 0; j < sel.size(); j++) {
+ Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height);
+ if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
+ continue;
+ }
+ if (rect.position.x < xmargin_beg) {
+ rect.size.x -= (xmargin_beg - rect.position.x);
+ rect.position.x = xmargin_beg;
+ }
+ if (rect.position.x + rect.size.x > xmargin_end) {
+ rect.size.x = xmargin_end - rect.position.x;
+ }
+ draw_rect(rect, selection_color, true);
}
- draw_rect(rect, selection_color, true);
}
}
@@ -1201,45 +1212,50 @@ void TextEdit::_notification(int p_what) {
current_color.a = font_readonly_color.a;
}
}
+ Color gl_color = current_color;
- if (selection.active && line >= selection.from_line && line <= selection.to_line) { // Selection
- int sel_from = (line > selection.from_line) ? TS->shaped_text_get_range(rid).x : selection.from_column;
- int sel_to = (line < selection.to_line) ? TS->shaped_text_get_range(rid).y : selection.to_column;
+ for (int c = 0; c < carets.size(); 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);
- if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && override_selected_font_color) {
- current_color = font_selected_color;
+ if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && override_selected_font_color) {
+ gl_color = font_selected_color;
+ }
}
}
float char_pos = char_ofs + char_margin + ofs_x;
if (char_pos >= xmargin_beg) {
if (highlight_matching_braces_enabled) {
- if ((brace_open_match_line == line && brace_open_match_column == glyphs[j].start) ||
- (caret.column == glyphs[j].start && caret.line == line && caret_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) {
- if (brace_open_mismatch) {
- current_color = brace_mismatch_color;
+ for (int c = 0; c < carets.size(); 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) {
+ gl_color = brace_mismatch_color;
+ }
+ Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1));
+ draw_rect(rect, gl_color);
}
- Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1));
- draw_rect(rect, current_color);
- }
- if ((brace_close_match_line == line && brace_close_match_column == glyphs[j].start) ||
- (caret.column == glyphs[j].start + 1 && caret.line == line && caret_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) {
- if (brace_close_mismatch) {
- current_color = brace_mismatch_color;
+ if ((brace_matching[c].close_match_line == line && brace_matching[c].close_match_column == glyphs[j].start) ||
+ (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].close_matching || brace_matching[c].close_mismatch))) {
+ if (brace_matching[c].close_mismatch) {
+ gl_color = brace_mismatch_color;
+ }
+ Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1));
+ draw_rect(rect, gl_color);
}
- Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1));
- draw_rect(rect, current_color);
}
}
if (draw_tabs && ((glyphs[j].flags & TextServer::GRAPHEME_IS_TAB) == TextServer::GRAPHEME_IS_TAB)) {
int yofs = (text_height - tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
- tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), current_color);
+ tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), gl_color);
} else if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE)) {
int yofs = (text_height - space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
int xofs = (glyphs[j].advance * glyphs[j].repeat - space_icon->get_width()) / 2;
- space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), current_color);
+ space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), gl_color);
}
}
@@ -1247,10 +1263,10 @@ void TextEdit::_notification(int p_what) {
for (int k = 0; k < glyphs[j].repeat; k++) {
if (!clipped && (char_ofs + char_margin) >= xmargin_beg && (char_ofs + glyphs[j].advance + char_margin) <= xmargin_end) {
if (glyphs[j].font_rid != RID()) {
- TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, current_color);
+ TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color);
had_glyphs_drawn = true;
} else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
- TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, current_color);
+ TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color);
had_glyphs_drawn = true;
}
}
@@ -1288,137 +1304,155 @@ void TextEdit::_notification(int p_what) {
// Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1).
const int caret_width = get_theme_constant(SNAME("caret_width")) * MAX(1, get_theme_default_base_scale());
- if (!clipped && caret.line == line && line_wrap_index == caret_wrap_index) {
- caret.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index);
+ for (int c = 0; c < carets.size(); c++) {
+ if (!clipped && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index) {
+ carets.write[c].draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index);
- if (ime_text.length() == 0) {
- CaretInfo ts_caret;
- if (str.length() != 0) {
- // Get carets.
- ts_caret = TS->shaped_text_get_carets(rid, caret.column);
- } else {
- // No carets, add one at the start.
- int h = font->get_height(font_size);
- if (rtl) {
- ts_caret.l_dir = TextServer::DIRECTION_RTL;
- ts_caret.l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h));
+ if (ime_text.length() == 0) {
+ CaretInfo ts_caret;
+ if (str.length() != 0) {
+ // Get carets.
+ ts_caret = TS->shaped_text_get_carets(rid, get_caret_column(c));
} else {
- ts_caret.l_dir = TextServer::DIRECTION_LTR;
- ts_caret.l_caret = Rect2(Vector2(char_ofs, -h / 2), Size2(caret_width * 4, h));
+ // No carets, add one at the start.
+ int h = font->get_height(font_size);
+ if (rtl) {
+ ts_caret.l_dir = TextServer::DIRECTION_RTL;
+ ts_caret.l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h));
+ } else {
+ ts_caret.l_dir = TextServer::DIRECTION_LTR;
+ ts_caret.l_caret = Rect2(Vector2(char_ofs, -h / 2), Size2(caret_width * 4, h));
+ }
}
- }
- if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) {
- caret.draw_pos.x = char_margin + ofs_x + ts_caret.l_caret.position.x;
- } else {
- caret.draw_pos.x = char_margin + ofs_x + ts_caret.t_caret.position.x;
- }
+ if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) {
+ carets.write[c].draw_pos.x = char_margin + ofs_x + ts_caret.l_caret.position.x;
+ } else {
+ carets.write[c].draw_pos.x = char_margin + ofs_x + ts_caret.t_caret.position.x;
+ }
- if (caret.draw_pos.x >= xmargin_beg && caret.draw_pos.x < xmargin_end) {
- caret.visible = true;
- if (draw_caret || drag_caret_force_displayed) {
- if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) {
- //Block or underline caret, draw trailing carets at full height.
- int h = font->get_height(font_size);
-
- if (ts_caret.t_caret != Rect2()) {
- if (overtype_mode) {
- ts_caret.t_caret.position.y = TS->shaped_text_get_descent(rid);
- ts_caret.t_caret.size.y = caret_width;
- } else {
- ts_caret.t_caret.position.y = -TS->shaped_text_get_ascent(rid);
- ts_caret.t_caret.size.y = h;
- }
- ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
- draw_rect(ts_caret.t_caret, caret_color, overtype_mode);
+ if (get_caret_draw_pos(c).x >= xmargin_beg && get_caret_draw_pos(c).x < xmargin_end) {
+ carets.write[c].visible = true;
+ if (draw_caret || drag_caret_force_displayed) {
+ if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) {
+ //Block or underline caret, draw trailing carets at full height.
+ int h = font->get_height(font_size);
- if (ts_caret.l_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) {
- ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
- ts_caret.l_caret.size.x = caret_width;
- draw_rect(ts_caret.l_caret, caret_color * Color(1, 1, 1, 0.5));
- }
- } else { // End of the line.
- if (gl_size > 0) {
- // Adjust for actual line dimensions.
+ if (ts_caret.t_caret != Rect2()) {
if (overtype_mode) {
- ts_caret.l_caret.position.y = TS->shaped_text_get_descent(rid);
+ ts_caret.t_caret.position.y = TS->shaped_text_get_descent(rid);
+ ts_caret.t_caret.size.y = caret_width;
+ } else {
+ ts_caret.t_caret.position.y = -TS->shaped_text_get_ascent(rid);
+ ts_caret.t_caret.size.y = h;
+ }
+ ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ draw_rect(ts_caret.t_caret, caret_color, overtype_mode);
+
+ if (ts_caret.l_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) {
+ // Draw split caret (leading part).
+ ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ ts_caret.l_caret.size.x = caret_width;
+ draw_rect(ts_caret.l_caret, caret_color);
+ // Draw extra direction marker on top of split caret.
+ float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
+ Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
+ }
+ } else { // End of the line.
+ if (gl_size > 0) {
+ // Adjust for actual line dimensions.
+ if (overtype_mode) {
+ ts_caret.l_caret.position.y = TS->shaped_text_get_descent(rid);
+ ts_caret.l_caret.size.y = caret_width;
+ } else {
+ ts_caret.l_caret.position.y = -TS->shaped_text_get_ascent(rid);
+ ts_caret.l_caret.size.y = h;
+ }
+ } else if (overtype_mode) {
+ ts_caret.l_caret.position.y += ts_caret.l_caret.size.y;
ts_caret.l_caret.size.y = caret_width;
+ }
+ if (ts_caret.l_caret.position.x >= TS->shaped_text_get_size(rid).x) {
+ ts_caret.l_caret.size.x = font->get_char_size('m', font_size).x;
} else {
- ts_caret.l_caret.position.y = -TS->shaped_text_get_ascent(rid);
- ts_caret.l_caret.size.y = h;
+ ts_caret.l_caret.size.x = 3 * caret_width;
+ }
+ ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ if (ts_caret.l_dir == TextServer::DIRECTION_RTL) {
+ ts_caret.l_caret.position.x -= ts_caret.l_caret.size.x;
}
- } else if (overtype_mode) {
- ts_caret.l_caret.position.y += ts_caret.l_caret.size.y;
- ts_caret.l_caret.size.y = caret_width;
+ draw_rect(ts_caret.l_caret, caret_color, overtype_mode);
}
- if (ts_caret.l_caret.position.x >= TS->shaped_text_get_size(rid).x) {
- ts_caret.l_caret.size.x = font->get_char_size('m', font_size).x;
- } else {
- ts_caret.l_caret.size.x = 3 * caret_width;
+ } else {
+ // Normal caret.
+ if (ts_caret.l_caret != Rect2() && ts_caret.l_dir == TextServer::DIRECTION_AUTO) {
+ // Draw extra marker on top of mid caret.
+ Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width);
+ trect.position += Vector2(char_margin + ofs_x, ofs_y);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
+ } else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) {
+ // Draw extra direction marker on top of split caret.
+ float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
+ Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width);
+ trect.position += Vector2(char_margin + ofs_x, ofs_y);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
+
+ d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
+ trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width);
+ trect.position += Vector2(char_margin + ofs_x, ofs_y);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
}
ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
- if (ts_caret.l_dir == TextServer::DIRECTION_RTL) {
- ts_caret.l_caret.position.x -= ts_caret.l_caret.size.x;
- }
- draw_rect(ts_caret.l_caret, caret_color, overtype_mode);
- }
- } else {
- // Normal caret.
- if (ts_caret.l_caret != Rect2() && ts_caret.l_dir == TextServer::DIRECTION_AUTO) {
- // Draw extra marker on top of mid caret.
- Rect2 trect = Rect2(ts_caret.l_caret.position.x - 3 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width);
- trect.position += Vector2(char_margin + ofs_x, ofs_y);
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
- }
- ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
- ts_caret.l_caret.size.x = caret_width;
+ ts_caret.l_caret.size.x = caret_width;
- draw_rect(ts_caret.l_caret, caret_color);
+ draw_rect(ts_caret.l_caret, caret_color);
- ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
- ts_caret.t_caret.size.x = caret_width;
+ ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ ts_caret.t_caret.size.x = caret_width;
- draw_rect(ts_caret.t_caret, caret_color);
+ draw_rect(ts_caret.t_caret, caret_color);
+ }
}
}
- }
- } else {
- {
- // IME Intermediate text range.
- Vector<Vector2> sel = TS->shaped_text_get_selection(rid, caret.column, caret.column + ime_text.length());
- for (int j = 0; j < sel.size(); j++) {
- Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height);
- if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
- continue;
- }
- if (rect.position.x < xmargin_beg) {
- rect.size.x -= (xmargin_beg - rect.position.x);
- rect.position.x = xmargin_beg;
- } else if (rect.position.x + rect.size.x > xmargin_end) {
- rect.size.x = xmargin_end - rect.position.x;
+ } else {
+ {
+ // IME Intermediate text range.
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length());
+ for (int j = 0; j < sel.size(); j++) {
+ Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height);
+ if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
+ continue;
+ }
+ if (rect.position.x < xmargin_beg) {
+ rect.size.x -= (xmargin_beg - rect.position.x);
+ rect.position.x = xmargin_beg;
+ } else if (rect.position.x + rect.size.x > xmargin_end) {
+ rect.size.x = xmargin_end - rect.position.x;
+ }
+ rect.size.y = caret_width;
+ draw_rect(rect, caret_color);
+ carets.write[c].draw_pos.x = rect.position.x;
}
- rect.size.y = caret_width;
- draw_rect(rect, caret_color);
- caret.draw_pos.x = rect.position.x;
}
- }
- {
- // IME caret.
- Vector<Vector2> sel = TS->shaped_text_get_selection(rid, caret.column + ime_selection.x, caret.column + ime_selection.x + ime_selection.y);
- for (int j = 0; j < sel.size(); j++) {
- Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height);
- if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
- continue;
- }
- if (rect.position.x < xmargin_beg) {
- rect.size.x -= (xmargin_beg - rect.position.x);
- rect.position.x = xmargin_beg;
- } else if (rect.position.x + rect.size.x > xmargin_end) {
- rect.size.x = xmargin_end - rect.position.x;
+ {
+ // IME caret.
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y);
+ for (int j = 0; j < sel.size(); j++) {
+ Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height);
+ if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
+ continue;
+ }
+ if (rect.position.x < xmargin_beg) {
+ rect.size.x -= (xmargin_beg - rect.position.x);
+ rect.position.x = xmargin_beg;
+ } else if (rect.position.x + rect.size.x > xmargin_end) {
+ rect.size.x = xmargin_end - rect.position.x;
+ }
+ rect.size.y = caret_width * 3;
+ draw_rect(rect, caret_color);
+ carets.write[c].draw_pos.x = rect.position.x;
}
- rect.size.y = caret_width * 3;
- draw_rect(rect, caret_color);
- caret.draw_pos.x = rect.position.x;
}
}
}
@@ -1433,7 +1467,7 @@ void TextEdit::_notification(int p_what) {
if (has_focus()) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
- DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + caret.draw_pos, get_viewport()->get_window_id());
+ DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + get_caret_draw_pos(), get_viewport()->get_window_id());
}
}
} break;
@@ -1454,13 +1488,13 @@ void TextEdit::_notification(int p_what) {
int caret_start = -1;
int caret_end = -1;
- if (!selection.active) {
- String full_text = _base_get_text(0, 0, caret.line, caret.column);
+ if (!has_selection(0)) {
+ String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column());
caret_start = full_text.length();
} else {
- String pre_text = _base_get_text(0, 0, selection.from_line, selection.from_column);
- String post_text = get_selected_text();
+ String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column());
+ String post_text = get_selected_text(0);
caret_start = pre_text.length();
caret_end = caret_start + post_text.length();
@@ -1482,14 +1516,16 @@ void TextEdit::_notification(int p_what) {
if (!ime_text.is_empty()) {
ime_text = "";
ime_selection = Point2();
- text.invalidate_cache(caret.line, caret.column, true, ime_text);
+ for (int i = 0; i < carets.size(); i++) {
+ text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, ime_text);
+ }
}
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
- if (deselect_on_focus_loss_enabled && !selection.drag_attempt) {
+ if (deselect_on_focus_loss_enabled && !selection_drag_attempt) {
deselect();
}
} break;
@@ -1499,20 +1535,21 @@ void TextEdit::_notification(int p_what) {
ime_text = DisplayServer::get_singleton()->ime_get_text();
ime_selection = DisplayServer::get_singleton()->ime_get_selection();
- String t;
- if (caret.column >= 0) {
- t = text[caret.line].substr(0, caret.column) + ime_text + text[caret.line].substr(caret.column, text[caret.line].length());
- } else {
- t = ime_text;
+ for (int i = 0; i < carets.size(); i++) {
+ String t;
+ if (get_caret_column(i) >= 0) {
+ t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length());
+ } else {
+ t = ime_text;
+ }
+ text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t));
}
-
- text.invalidate_cache(caret.line, caret.column, true, t, structured_text_parser(st_parser, st_args, t));
queue_redraw();
}
} break;
case NOTIFICATION_DRAG_BEGIN: {
- selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE;
+ selecting_mode = SelectionMode::SELECTION_MODE_NONE;
drag_action = true;
dragging_minimap = false;
dragging_selection = false;
@@ -1522,8 +1559,8 @@ void TextEdit::_notification(int p_what) {
case NOTIFICATION_DRAG_END: {
if (is_drag_successful()) {
- if (selection.drag_attempt) {
- selection.drag_attempt = false;
+ if (selection_drag_attempt) {
+ selection_drag_attempt = false;
if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
delete_selection();
} else if (deselect_on_focus_loss_enabled) {
@@ -1531,7 +1568,7 @@ void TextEdit::_notification(int p_what) {
}
}
} else {
- selection.drag_attempt = false;
+ selection_drag_attempt = false;
}
drag_action = false;
drag_caret_force_displayed = false;
@@ -1614,7 +1651,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (mb->is_pressed()) {
- if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) {
+ if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) {
if (mb->is_shift_pressed()) {
h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
} else if (mb->is_alt_pressed()) {
@@ -1625,7 +1662,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
_scroll_up(3 * mb->get_factor());
}
}
- if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) {
+ if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_or_control_pressed()) {
if (mb->is_shift_pressed()) {
h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
} else if (mb->is_alt_pressed()) {
@@ -1671,77 +1708,103 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
- int prev_col = caret.column;
- int prev_line = caret.line;
-
- set_caret_line(row, false, false);
- set_caret_column(col);
- selection.drag_attempt = false;
-
- if (selecting_enabled && mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) {
- if (!selection.active) {
- selection.active = true;
- selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER;
- selection.from_column = prev_col;
- selection.from_line = prev_line;
- selection.to_column = caret.column;
- selection.to_line = caret.line;
-
- if (selection.from_line > selection.to_line || (selection.from_line == selection.to_line && selection.from_column > selection.to_column)) {
- SWAP(selection.from_column, selection.to_column);
- SWAP(selection.from_line, selection.to_line);
- selection.shiftclick_left = false;
+ int caret = carets.size() - 1;
+ int prev_col = get_caret_column(caret);
+ int prev_line = get_caret_line(caret);
+
+ 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 (!is_mouse_over_selection() && !mb->is_double_click() && !is_triple_click) {
+ if (mb->is_alt_pressed()) {
+ prev_line = row;
+ prev_col = col;
+
+ caret = add_caret(row, 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()) {
+ caret = 0;
+ remove_secondary_carets();
+ }
+ }
+
+ 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 (carets[caret].selection.from_line > carets[caret].selection.to_line || (carets[caret].selection.from_line == carets[caret].selection.to_line && carets[caret].selection.from_column > carets[caret].selection.to_column)) {
+ 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 {
- selection.shiftclick_left = true;
+ carets.write[caret].selection.shiftclick_left = true;
}
- selection.selecting_line = prev_line;
- selection.selecting_column = prev_col;
+ 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 (caret.line < selection.selecting_line || (caret.line == selection.selecting_line && caret.column < selection.selecting_column)) {
- if (selection.shiftclick_left) {
- selection.shiftclick_left = !selection.shiftclick_left;
+ if (carets[caret].line < carets[caret].selection.selecting_line || (carets[caret].line == carets[caret].selection.selecting_line && carets[caret].column < carets[caret].selection.selecting_column)) {
+ if (carets[caret].selection.shiftclick_left) {
+ carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left;
}
- selection.from_column = caret.column;
- selection.from_line = caret.line;
-
- } else if (caret.line > selection.selecting_line || (caret.line == selection.selecting_line && caret.column > selection.selecting_column)) {
- if (!selection.shiftclick_left) {
- SWAP(selection.from_column, selection.to_column);
- SWAP(selection.from_line, selection.to_line);
- selection.shiftclick_left = !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 > carets[caret].selection.selecting_line || (carets[caret].line == carets[caret].selection.selecting_line && carets[caret].column > carets[caret].selection.selecting_column)) {
+ 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;
}
- selection.to_column = caret.column;
- selection.to_line = caret.line;
+ carets.write[caret].selection.to_column = carets[caret].column;
+ carets.write[caret].selection.to_line = carets[caret].line;
} else {
- selection.active = false;
+ deselect(caret);
}
-
+ caret_index_edit_dirty = true;
+ merge_overlapping_carets();
queue_redraw();
}
} else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) {
- selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE;
- selection.drag_attempt = true;
- } else {
- selection.active = false;
- selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER;
- selection.selecting_line = row;
- selection.selecting_column = col;
+ 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);
}
- const int triple_click_timeout = 600;
- const int triple_click_tolerance = 5;
-
- if (!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 (is_triple_click) {
// Triple-click select line.
- selection.selecting_mode = SelectionMode::SELECTION_MODE_LINE;
- selection.drag_attempt = false;
+ selecting_mode = SelectionMode::SELECTION_MODE_LINE;
+ selection_drag_attempt = false;
_update_selection_mode_line();
last_dblclk = 0;
- } else if (mb->is_double_click() && text[caret.line].length()) {
+ } else if (mb->is_double_click() && text[get_caret_line(caret)].length()) {
// Double-click select word.
- selection.selecting_mode = SelectionMode::SELECTION_MODE_WORD;
+ selecting_mode = SelectionMode::SELECTION_MODE_WORD;
_update_selection_mode_word();
last_dblclk = OS::get_singleton()->get_ticks_msec();
last_dblclk_pos = mb->get_position();
@@ -1759,23 +1822,25 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
Point2i pos = get_line_column_at_pos(mpos);
int row = pos.y;
int col = pos.x;
+ int caret = carets.size() - 1;
if (is_move_caret_on_right_click_enabled()) {
- if (has_selection()) {
- int from_line = get_selection_from_line();
- int to_line = get_selection_to_line();
- int from_column = get_selection_from_column();
- int to_column = get_selection_to_column();
+ if (has_selection(caret)) {
+ int from_line = get_selection_from_line(caret);
+ int to_line = get_selection_to_line(caret);
+ int from_column = get_selection_from_column(caret);
+ int to_column = get_selection_to_column(caret);
if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) {
// Right click is outside the selected text.
- deselect();
+ deselect(caret);
}
}
- if (!has_selection()) {
- set_caret_line(row, true, false);
- set_caret_column(col);
+ if (!has_selection(caret)) {
+ set_caret_line(row, true, false, 0, caret);
+ set_caret_column(col, true, caret);
}
+ merge_overlapping_carets();
}
if (context_menu_enabled) {
@@ -1788,15 +1853,15 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
} else {
if (mb->get_button_index() == MouseButton::LEFT) {
- if (selection.drag_attempt && is_mouse_over_selection()) {
- selection.active = false;
+ if (selection_drag_attempt && is_mouse_over_selection()) {
+ deselect();
}
dragging_minimap = false;
dragging_selection = false;
can_drag_minimap = false;
click_select_held->stop();
if (!drag_action) {
- selection.drag_attempt = false;
+ selection_drag_attempt = false;
}
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
@@ -1840,7 +1905,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (!dragging_minimap) {
- switch (selection.selecting_mode) {
+ switch (selecting_mode) {
case SelectionMode::SELECTION_MODE_POINTER: {
_update_selection_mode_pointer();
} break;
@@ -1886,8 +1951,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) {
drag_caret_force_displayed = true;
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- set_caret_line(pos.y, false);
- set_caret_column(pos.x);
+ set_caret_line(pos.y, false, true, 0, 0);
+ set_caret_column(pos.x, true, 0);
dragging_selection = true;
}
}
@@ -1920,9 +1985,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
// Allow unicode handling if:
// * No Modifiers are pressed (except shift)
- bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
-
- selection.selecting_text = false;
+ bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
// Check and handle all built in shortcuts.
@@ -2145,7 +2208,9 @@ void TextEdit::_swap_current_input_direction() {
} else {
input_direction = TEXT_DIRECTION_LTR;
}
- set_caret_column(caret.column);
+ for (int i = 0; i < carets.size(); i++) {
+ set_caret_column(get_caret_column(i), i == 0, i);
+ }
queue_redraw();
}
@@ -2155,325 +2220,429 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) {
}
begin_complex_operation();
-
- bool first_line = false;
- if (!p_split_current_line) {
- deselect();
- if (p_above) {
- if (caret.line > 0) {
- set_caret_line(caret.line - 1, false);
- set_caret_column(text[caret.line].length());
+ 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(0);
- first_line = true;
+ set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
}
- } else {
- set_caret_column(text[caret.line].length());
}
- }
- insert_text_at_caret("\n");
+ insert_text_at_caret("\n", i);
- if (first_line) {
- set_caret_line(0);
+ if (first_line) {
+ set_caret_line(0, i == 0, true, 0, i);
+ }
}
-
end_complex_operation();
}
void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
- // Handle selection
- if (p_select) {
- _pre_shift_selection();
- } else if (selection.active && !p_move_by_word) {
- // If a selection is active, move caret to start of selection
- set_caret_line(selection.from_line);
- set_caret_column(selection.from_column);
- deselect();
- return;
- } else {
- deselect();
- }
-
- if (p_move_by_word) {
- int cc = caret.column;
- // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line.
- if (cc == 0 && caret.line > 0) {
- set_caret_line(caret.line - 1);
- set_caret_column(text[caret.line].length());
+ for (int i = 0; i < carets.size(); 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_column(get_selection_from_column(i), i == 0, i);
+ deselect(i);
+ continue;
} else {
- PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- if (words.is_empty() || cc <= words[0]) {
- // This solves the scenario where there are no words but glyfs that can be ignored.
- cc = 0;
+ deselect(i);
+ }
+
+ if (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_column(text[get_caret_line(i)].length(), i == 0, i);
} else {
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < cc) {
- cc = words[i];
- break;
+ PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
+ if (words.is_empty() || cc <= words[0]) {
+ // This solves the scenario where there are no words but glyfs that can be ignored.
+ cc = 0;
+ } else {
+ for (int j = words.size() - 2; j >= 0; j = j - 2) {
+ if (words[j] < cc) {
+ cc = words[j];
+ break;
+ }
}
}
- }
- set_caret_column(cc);
- }
- } else {
- // 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 (caret.column == 0) {
- if (caret.line > 0) {
- set_caret_line(caret.line - get_next_visible_line_offset_from(CLAMP(caret.line - 1, 0, text.size() - 1), -1));
- set_caret_column(text[caret.line].length());
+ set_caret_column(cc, i == 0, i);
}
} else {
- if (caret_mid_grapheme_enabled) {
- set_caret_column(get_caret_column() - 1);
+ // 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);
+ set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
+ }
} else {
- set_caret_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(caret.line)->get_rid(), get_caret_column()));
+ if (caret_mid_grapheme_enabled) {
+ set_caret_column(get_caret_column(i) - 1, i == 0, i);
+ } else {
+ set_caret_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i);
+ }
}
}
- }
- if (p_select) {
- _post_shift_selection();
+ if (p_select) {
+ _post_shift_selection(i);
+ }
}
+ merge_overlapping_carets();
}
void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
- // Handle selection
- if (p_select) {
- _pre_shift_selection();
- } else if (selection.active && !p_move_by_word) {
- // If a selection is active, move caret to end of selection
- set_caret_line(selection.to_line);
- set_caret_column(selection.to_column);
- deselect();
- return;
- } else {
- deselect();
- }
-
- if (p_move_by_word) {
- int cc = caret.column;
- // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line.
- if (cc == text[caret.line].length() && caret.line < text.size() - 1) {
- set_caret_line(caret.line + 1);
- set_caret_column(0);
+ for (int i = 0; i < carets.size(); 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_column(get_selection_to_column(i), i == 0, i);
+ deselect(i);
+ continue;
} else {
- PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- if (words.is_empty() || cc >= words[words.size() - 1]) {
- // This solves the scenario where there are no words but glyfs that can be ignored.
- cc = text[caret.line].length();
+ deselect(i);
+ }
+
+ if (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_column(0, i == 0, i);
} else {
- for (int i = 1; i < words.size(); i = i + 2) {
- if (words[i] > cc) {
- cc = words[i];
- break;
+ PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
+ if (words.is_empty() || cc >= words[words.size() - 1]) {
+ // This solves the scenario where there are no words but glyfs that can be ignored.
+ cc = text[get_caret_line(i)].length();
+ } else {
+ for (int j = 1; j < words.size(); j = j + 2) {
+ if (words[j] > cc) {
+ cc = words[j];
+ break;
+ }
}
}
- }
- set_caret_column(cc);
- }
- } else {
- // If we are at the end of the line, move the caret to the next line down.
- if (caret.column == text[caret.line].length()) {
- if (caret.line < text.size() - 1) {
- set_caret_line(get_caret_line() + get_next_visible_line_offset_from(CLAMP(caret.line + 1, 0, text.size() - 1), 1), true, false);
- set_caret_column(0);
+ set_caret_column(cc, i == 0, i);
}
} else {
- if (caret_mid_grapheme_enabled) {
- set_caret_column(get_caret_column() + 1);
+ // 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);
+ set_caret_column(0, i == 0, i);
+ }
} else {
- set_caret_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(caret.line)->get_rid(), get_caret_column()));
+ if (caret_mid_grapheme_enabled) {
+ set_caret_column(get_caret_column(i) + 1, i == 0, i);
+ } else {
+ set_caret_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i);
+ }
}
}
- }
- if (p_select) {
- _post_shift_selection();
+ if (p_select) {
+ _post_shift_selection(i);
+ }
}
+ merge_overlapping_carets();
}
void TextEdit::_move_caret_up(bool p_select) {
- if (p_select) {
- _pre_shift_selection();
- } else {
- deselect();
- }
+ for (int i = 0; i < carets.size(); i++) {
+ if (p_select) {
+ _pre_shift_selection(i);
+ } else {
+ deselect(i);
+ }
- int cur_wrap_index = get_caret_wrap_index();
- if (cur_wrap_index > 0) {
- set_caret_line(caret.line, true, false, cur_wrap_index - 1);
- } else if (caret.line == 0) {
- set_caret_column(0);
- } 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, true, false, get_line_wrap_count(new_line));
+ int cur_wrap_index = get_caret_wrap_index(i);
+ if (cur_wrap_index > 0) {
+ set_caret_line(get_caret_line(i), true, false, cur_wrap_index - 1, i);
+ } else if (get_caret_line(i) == 0) {
+ set_caret_column(0, i == 0, i);
} else {
- set_caret_line(new_line, true, false);
+ int new_line = get_caret_line(i) - get_next_visible_line_offset_from(get_caret_line(i) - 1, -1);
+ if (is_line_wrapped(new_line)) {
+ set_caret_line(new_line, i == 0, false, get_line_wrap_count(new_line), i);
+ } else {
+ set_caret_line(new_line, i == 0, false, 0, i);
+ }
}
- }
- if (p_select) {
- _post_shift_selection();
+ if (p_select) {
+ _post_shift_selection(i);
+ }
}
+ merge_overlapping_carets();
}
void TextEdit::_move_caret_down(bool p_select) {
- if (p_select) {
- _pre_shift_selection();
- } else {
- deselect();
- }
+ for (int i = 0; i < carets.size(); i++) {
+ if (p_select) {
+ _pre_shift_selection(i);
+ } else {
+ deselect(i);
+ }
- int cur_wrap_index = get_caret_wrap_index();
- if (cur_wrap_index < get_line_wrap_count(caret.line)) {
- set_caret_line(caret.line, true, false, cur_wrap_index + 1);
- } else if (caret.line == get_last_unhidden_line()) {
- set_caret_column(text[caret.line].length());
- } else {
- 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, true, false, 0);
- }
+ int cur_wrap_index = get_caret_wrap_index(i);
+ if (cur_wrap_index < get_line_wrap_count(get_caret_line(i))) {
+ set_caret_line(get_caret_line(i), i == 0, false, cur_wrap_index + 1, i);
+ } else if (get_caret_line(i) == get_last_unhidden_line()) {
+ set_caret_column(text[get_caret_line(i)].length());
+ } else {
+ 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();
+ if (p_select) {
+ _post_shift_selection(i);
+ }
}
+ merge_overlapping_carets();
}
void TextEdit::_move_caret_to_line_start(bool p_select) {
- if (p_select) {
- _pre_shift_selection();
- } else {
- deselect();
- }
+ for (int i = 0; i < carets.size(); i++) {
+ if (p_select) {
+ _pre_shift_selection(i);
+ } else {
+ deselect(i);
+ }
- // Move caret column to start of wrapped row and then to start of text.
- Vector<String> rows = get_line_wrapped_text(caret.line);
- int wi = get_caret_wrap_index();
- int row_start_col = 0;
- for (int i = 0; i < wi; i++) {
- row_start_col += rows[i].length();
- }
- if (caret.column == row_start_col || wi == 0) {
- // Compute whitespace symbols sequence length.
- int current_line_whitespace_len = get_first_non_whitespace_column(caret.line);
- if (get_caret_column() == current_line_whitespace_len) {
- set_caret_column(0);
+ // Move caret column to start of wrapped row and then to start of text.
+ Vector<String> rows = get_line_wrapped_text(get_caret_line(i));
+ int wi = get_caret_wrap_index(i);
+ int row_start_col = 0;
+ for (int j = 0; j < wi; j++) {
+ row_start_col += rows[j].length();
+ }
+ if (get_caret_column(i) == row_start_col || wi == 0) {
+ // Compute whitespace symbols sequence length.
+ int current_line_whitespace_len = get_first_non_whitespace_column(get_caret_line(i));
+ if (get_caret_column(i) == current_line_whitespace_len) {
+ set_caret_column(0, i == 0, i);
+ } else {
+ set_caret_column(current_line_whitespace_len, i == 0, i);
+ }
} else {
- set_caret_column(current_line_whitespace_len);
+ set_caret_column(row_start_col, i == 0, i);
}
- } else {
- set_caret_column(row_start_col);
- }
- if (p_select) {
- _post_shift_selection();
+ if (p_select) {
+ _post_shift_selection(i);
+ }
}
+ merge_overlapping_carets();
}
void TextEdit::_move_caret_to_line_end(bool p_select) {
- if (p_select) {
- _pre_shift_selection();
- } else {
- deselect();
- }
+ for (int i = 0; i < carets.size(); i++) {
+ if (p_select) {
+ _pre_shift_selection(i);
+ } else {
+ deselect(i);
+ }
- // Move caret column to end of wrapped row and then to end of text.
- Vector<String> rows = get_line_wrapped_text(caret.line);
- int wi = get_caret_wrap_index();
- int row_end_col = -1;
- for (int i = 0; i < wi + 1; i++) {
- row_end_col += rows[i].length();
- }
- if (wi == rows.size() - 1 || caret.column == row_end_col) {
- set_caret_column(text[caret.line].length());
- } else {
- set_caret_column(row_end_col);
- }
+ // Move caret column to end of wrapped row and then to end of text.
+ Vector<String> rows = get_line_wrapped_text(get_caret_line(i));
+ int wi = get_caret_wrap_index(i);
+ int row_end_col = -1;
+ for (int j = 0; j < wi + 1; j++) {
+ row_end_col += rows[j].length();
+ }
+ if (wi == rows.size() - 1 || get_caret_column(i) == row_end_col) {
+ set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
+ } else {
+ set_caret_column(row_end_col, i == 0, i);
+ }
- if (p_select) {
- _post_shift_selection();
+ carets.write[i].last_fit_x = INT_MAX;
+
+ if (p_select) {
+ _post_shift_selection(i);
+ }
}
+ merge_overlapping_carets();
}
void TextEdit::_move_caret_page_up(bool p_select) {
- if (p_select) {
- _pre_shift_selection();
- } else {
- deselect();
- }
+ for (int i = 0; i < carets.size(); i++) {
+ if (p_select) {
+ _pre_shift_selection(i);
+ } else {
+ deselect(i);
+ }
- Point2i next_line = get_next_visible_line_index_offset_from(caret.line, get_caret_wrap_index(), -get_visible_line_count());
- int n_line = caret.line - next_line.x + 1;
- set_caret_line(n_line, true, false, next_line.y);
+ 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();
+ if (p_select) {
+ _post_shift_selection(i);
+ }
}
+ merge_overlapping_carets();
}
void TextEdit::_move_caret_page_down(bool p_select) {
- if (p_select) {
- _pre_shift_selection();
- } else {
- deselect();
- }
+ for (int i = 0; i < carets.size(); i++) {
+ if (p_select) {
+ _pre_shift_selection(i);
+ } else {
+ deselect(i);
+ }
- Point2i next_line = get_next_visible_line_index_offset_from(caret.line, get_caret_wrap_index(), get_visible_line_count());
- int n_line = caret.line + next_line.x - 1;
- set_caret_line(n_line, true, false, next_line.y);
+ 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();
+ if (p_select) {
+ _post_shift_selection(i);
+ }
}
+ merge_overlapping_carets();
}
void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
- if (!editable || (caret.column == 0 && caret.line == 0 && !has_selection())) {
+ if (!editable) {
return;
}
- if (has_selection() || (!p_all_to_left && !p_word) || caret.column == 0) {
- backspace();
- return;
- }
+ start_action(EditAction::ACTION_BACKSPACE);
+ Vector<int> carets_to_remove;
- if (p_all_to_left) {
- int caret_current_column = caret.column;
- set_caret_column(0);
- _remove_text(caret.line, 0, caret.line, caret_current_column);
- return;
- }
+ 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)) {
+ continue;
+ }
- if (p_word) {
- int column = caret.column;
- // 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[caret.line][caret.column - 1])) {
- column -= 1;
+ if (has_selection(caret_idx) || (!p_all_to_left && !p_word) || get_caret_column(caret_idx) == 0) {
+ backspace(caret_idx);
+ continue;
}
- // Get a list with the indices of the word bounds of the given text line.
- const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- if (words.is_empty() || column <= words[0]) {
- // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line.
- column = 0;
- } else {
- // Otherwise search for the first word break that is smaller than the index from which we're currently deleting.
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < column) {
- column = words[i];
+
+ 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;
+ }
+
+ 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;
}
+ continue;
}
- _remove_text(caret.line, column, caret.line, caret.column);
+ 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);
+ // 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])) {
+ 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());
+ 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;
+ } else {
+ // Otherwise search for the first word break that is smaller than the index from we're currently deleting.
+ for (int c = words.size() - 2; c >= 0; c = c - 2) {
+ if (words[c] < column) {
+ column = words[c];
+ break;
+ }
+ }
+ }
- set_caret_line(caret.line, false);
- set_caret_column(column);
- return;
+ // 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);
+
+ // Now we can clean up the overlaping 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;
+ }
}
+
+ // 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_action();
}
void TextEdit::_delete(bool p_word, bool p_all_to_right) {
@@ -2481,82 +2650,147 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
return;
}
- if (has_selection()) {
- delete_selection();
- return;
- }
- int curline_len = text[caret.line].length();
-
- if (caret.line == text.size() - 1 && caret.column == curline_len) {
- return; // Last line, last column: Nothing to do.
- }
+ start_action(EditAction::ACTION_DELETE);
+ Vector<int> carets_to_remove;
- int next_line = caret.column < curline_len ? caret.line : caret.line + 1;
- int next_column;
+ 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);
+ continue;
+ }
+ int curline_len = text[get_caret_line(caret_idx)].length();
- if (p_all_to_right) {
- if (caret.column == curline_len) {
- return;
+ if (get_caret_line(caret_idx) == text.size() - 1 && get_caret_column(caret_idx) == curline_len) {
+ continue; // Last line, last column: Nothing to do.
}
- // Delete everything to right of caret
- next_column = curline_len;
- next_line = caret.line;
- } else if (p_word && caret.column < curline_len - 1) {
- // Delete next word to right of caret
- int line = caret.line;
- int column = caret.column;
+ int next_line = get_caret_column(caret_idx) < curline_len ? get_caret_line(caret_idx) : get_caret_line(caret_idx) + 1;
+ int next_column;
- PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
- for (int i = 1; i < words.size(); i = i + 2) {
- if (words[i] > column) {
- column = words[i];
- break;
+ 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];
+ }
}
- }
- next_line = line;
- next_column = column;
- } else {
- // Delete one character
- if (caret_mid_grapheme_enabled) {
- next_column = caret.column < curline_len ? (caret.column + 1) : 0;
+ if (get_caret_column(caret_idx) == 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) {
+ // Delete next word to right of caret
+ int line = get_caret_line(caret_idx);
+ int column = get_caret_column(caret_idx);
+
+ 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) {
+ if (words[j] > column) {
+ column = words[j];
+ break;
+ }
+ }
+
+ 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 {
- next_column = caret.column < curline_len ? TS->shaped_text_next_grapheme_pos(text.get_line_data(caret.line)->get_rid(), (caret.column)) : 0;
+ // Delete one character
+ if (caret_mid_grapheme_enabled) {
+ next_column = get_caret_column(caret_idx) < curline_len ? (get_caret_column(caret_idx) + 1) : 0;
+ } else {
+ next_column = get_caret_column(caret_idx) < curline_len ? TS->shaped_text_next_grapheme_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);
+ }
+ }
}
+
+ _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(caret.line, caret.column, next_line, next_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]);
+ }
+
+ // 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_action();
queue_redraw();
}
void TextEdit::_move_caret_document_start(bool p_select) {
+ remove_secondary_carets();
if (p_select) {
- _pre_shift_selection();
+ _pre_shift_selection(0);
} else {
deselect();
}
- set_caret_line(0);
+ set_caret_line(0, false);
set_caret_column(0);
if (p_select) {
- _post_shift_selection();
+ _post_shift_selection(0);
}
}
void TextEdit::_move_caret_document_end(bool p_select) {
+ remove_secondary_carets();
if (p_select) {
- _pre_shift_selection();
+ _pre_shift_selection(0);
} else {
deselect();
}
set_caret_line(get_last_unhidden_line(), true, false, 9999);
- set_caret_column(text[caret.line].length());
+ set_caret_column(text[get_caret_line()].length());
if (p_select) {
- _post_shift_selection();
+ _post_shift_selection(0);
}
}
@@ -2676,7 +2910,7 @@ bool TextEdit::is_text_field() const {
}
Variant TextEdit::get_drag_data(const Point2 &p_point) {
- if (selection.active && selection.drag_attempt) {
+ if (has_selection() && selection_drag_attempt) {
String t = get_selected_text();
Label *l = memnew(Label);
l->set_text(t);
@@ -2703,34 +2937,41 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
int caret_row_tmp = pos.y;
int caret_column_tmp = pos.x;
- if (selection.drag_attempt) {
- selection.drag_attempt = false;
+ if (selection_drag_attempt) {
+ selection_drag_attempt = false;
if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::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::CTRL)) {
- if (caret_row_tmp > selection.to_line) {
- caret_row_tmp = caret_row_tmp - (selection.to_line - selection.from_line);
- } else if (caret_row_tmp == selection.to_line && caret_column_tmp >= selection.to_column) {
- caret_column_tmp = caret_column_tmp - (selection.to_column - selection.from_column);
+ 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();
}
+ 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()) {
- caret_row_tmp = selection.from_line;
- caret_column_tmp = selection.from_column;
+ 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);
@@ -2738,8 +2979,8 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
grab_focus();
}
- if (caret_row_tmp != caret.line || caret_column_tmp != caret.column) {
- select(caret_row_tmp, caret_column_tmp, caret.line, caret.column);
+ 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());
}
}
}
@@ -2975,6 +3216,7 @@ void TextEdit::clear() {
void TextEdit::_clear() {
if (editable && undo_enabled) {
+ remove_secondary_carets();
_move_caret_document_start(false);
begin_complex_operation();
@@ -2990,13 +3232,14 @@ void TextEdit::_clear() {
clear_undo_history();
text.clear();
+ remove_secondary_carets();
set_caret_line(0, false);
set_caret_column(0);
- caret.x_ofs = 0;
- caret.line_ofs = 0;
- caret.wrap_ofs = 0;
- caret.last_fit_x = 0;
- selection.active = false;
+ first_visible_col = 0;
+ first_visible_line = 0;
+ first_visible_line_wrap_ofs = 0;
+ carets.write[0].last_fit_x = 0;
+ deselect();
emit_signal(SNAME("lines_edited_from"), old_text_size, 0);
}
@@ -3009,6 +3252,7 @@ void TextEdit::set_text(const String &p_text) {
}
if (undo_enabled) {
+ remove_secondary_carets();
set_caret_line(0);
set_caret_column(0);
@@ -3064,11 +3308,14 @@ void TextEdit::set_line(int p_line, const String &p_new_text) {
begin_complex_operation();
_remove_text(p_line, 0, p_line, text[p_line].length());
_insert_text(p_line, 0, p_new_text);
- if (caret.line == p_line && caret.column > p_new_text.length()) {
- set_caret_column(p_new_text.length(), false);
- }
- if (has_selection() && p_line == selection.to_line && selection.to_column > text[p_line].length()) {
- selection.to_column = text[p_line].length();
+ 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);
+ }
+
+ 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();
+ }
}
end_complex_operation();
}
@@ -3135,42 +3382,54 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) {
ERR_FAIL_INDEX(p_at, text.size());
_insert_text(p_at, 0, p_text + "\n");
- if (caret.line >= p_at) {
- // offset caret when located after inserted line
- set_caret_line(caret.line + 1, false);
- }
- if (has_selection()) {
- if (selection.from_line >= p_at) {
- // offset selection when located after inserted line
- ++selection.from_line;
- ++selection.to_line;
- } else if (selection.to_line >= p_at) {
- // extend selection that includes inserted line
- ++selection.to_line;
+
+ 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);
+ }
}
}
+
+ // Need to apply the above adjustments to the undo / redo carets.
+ current_op.end_carets = carets;
queue_redraw();
}
-void TextEdit::insert_text_at_caret(const String &p_text) {
- bool had_selection = has_selection();
- if (had_selection) {
- begin_complex_operation();
- }
+void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) {
+ ERR_FAIL_COND(p_caret > carets.size());
- delete_selection();
+ begin_complex_operation();
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &i : caret_edit_order) {
+ if (p_caret != -1 && p_caret != i) {
+ continue;
+ }
- int new_column, new_line;
- _insert_text(caret.line, caret.column, p_text, &new_line, &new_column);
- _update_scrollbars();
+ delete_selection(i);
- set_caret_line(new_line, false);
- set_caret_column(new_column);
- queue_redraw();
+ int from_line = get_caret_line(i);
+ int from_col = get_caret_column(i);
- if (had_selection) {
- end_complex_operation();
+ int new_column, new_line;
+ _insert_text(from_line, from_col, p_text, &new_line, &new_column);
+ _update_scrollbars();
+
+ set_caret_line(new_line, false, true, 0, i);
+ set_caret_column(new_column, i == 0, i);
+
+ adjust_carets_after_edit(i, new_line, new_column, from_line, from_col);
}
+ 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) {
@@ -3294,46 +3553,46 @@ Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p
}
// Overridable actions
-void TextEdit::handle_unicode_input(const uint32_t p_unicode) {
- if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode)) {
+void TextEdit::handle_unicode_input(const uint32_t p_unicode, int p_caret) {
+ if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode, p_caret)) {
return;
}
- _handle_unicode_input_internal(p_unicode);
+ _handle_unicode_input_internal(p_unicode, p_caret);
}
-void TextEdit::backspace() {
- if (GDVIRTUAL_CALL(_backspace)) {
+void TextEdit::backspace(int p_caret) {
+ if (GDVIRTUAL_CALL(_backspace, p_caret)) {
return;
}
- _backspace_internal();
+ _backspace_internal(p_caret);
}
-void TextEdit::cut() {
- if (GDVIRTUAL_CALL(_cut)) {
+void TextEdit::cut(int p_caret) {
+ if (GDVIRTUAL_CALL(_cut, p_caret)) {
return;
}
- _cut_internal();
+ _cut_internal(p_caret);
}
-void TextEdit::copy() {
- if (GDVIRTUAL_CALL(_copy)) {
+void TextEdit::copy(int p_caret) {
+ if (GDVIRTUAL_CALL(_copy, p_caret)) {
return;
}
- _copy_internal();
+ _copy_internal(p_caret);
}
-void TextEdit::paste() {
- if (GDVIRTUAL_CALL(_paste)) {
+void TextEdit::paste(int p_caret) {
+ if (GDVIRTUAL_CALL(_paste, p_caret)) {
return;
}
- _paste_internal();
+ _paste_internal(p_caret);
}
-void TextEdit::paste_primary_clipboard() {
- if (GDVIRTUAL_CALL(_paste_primary_clipboard)) {
+void TextEdit::paste_primary_clipboard(int p_caret) {
+ if (GDVIRTUAL_CALL(_paste_primary_clipboard, p_caret)) {
return;
}
- _paste_primary_clipboard_internal();
+ _paste_primary_clipboard_internal(p_caret);
}
// Context menu.
@@ -3470,22 +3729,53 @@ void TextEdit::menu_option(int p_option) {
}
/* Versioning */
+void TextEdit::start_action(EditAction p_action) {
+ if (current_action != p_action) {
+ if (current_action != EditAction::ACTION_NONE) {
+ in_action = false;
+ pending_action_end = false;
+ end_complex_operation();
+ }
+
+ if (p_action != EditAction::ACTION_NONE) {
+ in_action = true;
+ begin_complex_operation();
+ }
+ } else if (current_action != EditAction::ACTION_NONE) {
+ pending_action_end = false;
+ }
+ current_action = p_action;
+}
+
+void TextEdit::end_action() {
+ if (current_action != EditAction::ACTION_NONE) {
+ pending_action_end = true;
+ }
+}
+
+TextEdit::EditAction TextEdit::get_current_action() const {
+ return current_action;
+}
+
void TextEdit::begin_complex_operation() {
_push_current_op();
if (complex_operation_count == 0) {
next_operation_is_complex = true;
+ current_op.start_carets = carets;
}
complex_operation_count++;
}
void TextEdit::end_complex_operation() {
_push_current_op();
- ERR_FAIL_COND(undo_stack.size() == 0);
complex_operation_count = MAX(complex_operation_count - 1, 0);
if (complex_operation_count > 0) {
return;
}
+ ERR_FAIL_COND(undo_stack.size() == 0);
+
+ undo_stack.back()->get().end_carets = carets;
if (undo_stack.back()->get().chain_forward) {
undo_stack.back()->get().chain_forward = false;
return;
@@ -3545,17 +3835,24 @@ void TextEdit::undo() {
}
}
- if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) {
- select(op.from_line, op.from_column, op.to_line, op.to_column);
+ _update_scrollbars();
+ bool dirty_carets = carets.size() != undo_stack_pos->get().start_carets.size();
+ if (!dirty_carets) {
+ for (int i = 0; i < carets.size(); 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;
+ }
+ }
}
- _update_scrollbars();
- if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) {
- set_caret_line(undo_stack_pos->get().to_line, false);
- set_caret_column(undo_stack_pos->get().to_column);
- } else {
- set_caret_line(undo_stack_pos->get().from_line, false);
- set_caret_column(undo_stack_pos->get().from_column);
+ carets = undo_stack_pos->get().start_carets;
+
+ if (dirty_carets && !caret_pos_dirty) {
+ if (is_inside_tree()) {
+ MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed");
+ }
+ caret_pos_dirty = true;
}
queue_redraw();
}
@@ -3564,6 +3861,7 @@ void TextEdit::redo() {
if (!editable) {
return;
}
+
_push_current_op();
if (undo_stack_pos == nullptr) {
@@ -3589,9 +3887,25 @@ void TextEdit::redo() {
}
_update_scrollbars();
- set_caret_line(undo_stack_pos->get().to_line, false);
- set_caret_column(undo_stack_pos->get().to_column);
+ bool dirty_carets = carets.size() != undo_stack_pos->get().end_carets.size();
+ if (!dirty_carets) {
+ for (int i = 0; i < carets.size(); 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;
+ }
+ }
+ }
+
+ carets = undo_stack_pos->get().end_carets;
undo_stack_pos = undo_stack_pos->next();
+
+ if (dirty_carets && !caret_pos_dirty) {
+ if (is_inside_tree()) {
+ MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed");
+ }
+ caret_pos_dirty = true;
+ }
queue_redraw();
}
@@ -3603,7 +3917,7 @@ void TextEdit::clear_undo_history() {
}
bool TextEdit::is_insert_text_operation() const {
- return (current_op.type == TextOperation::TYPE_INSERT);
+ return (current_op.type == TextOperation::TYPE_INSERT || current_action == EditAction::ACTION_TYPING);
}
void TextEdit::tag_saved_version() {
@@ -3792,7 +4106,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_
int wrap_index = 0;
if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) {
- Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SIGN(rows)));
+ Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, rows + (1 * SIGN(rows)));
wrap_index = f_ofs.y;
if (rows < 0) {
@@ -3820,7 +4134,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_
int col = 0;
int colx = p_pos.x - (style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding);
- colx += caret.x_ofs;
+ colx += first_visible_col;
col = _get_char_pos_for_line(colx, row, wrap_index);
if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) {
// Move back one if we are at the end of the row.
@@ -3917,7 +4231,7 @@ int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const {
int row = minimap_line + Math::floor(rows);
if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) {
- int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SIGN(rows))).x - 1;
+ int f_ofs = get_next_visible_line_index_offset_from(minimap_line, first_visible_line_wrap_ofs, rows + (1 * SIGN(rows))).x - 1;
if (rows < 0) {
row = minimap_line - f_ofs;
} else {
@@ -3940,19 +4254,31 @@ bool TextEdit::is_dragging_cursor() const {
return dragging_selection || dragging_minimap;
}
-bool TextEdit::is_mouse_over_selection(bool p_edges) const {
- if (!has_selection()) {
- return false;
- }
- Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- int row = pos.y;
- int col = pos.x;
- if (p_edges) {
- if ((row == selection.from_line && col == selection.from_column) || (row == selection.to_line && col == selection.to_column)) {
+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;
+ }
+ }
+
+ 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;
}
}
- return (row >= selection.from_line && row <= selection.to_line && (row > selection.from_line || col > selection.from_column) && (row < selection.to_line || col < selection.to_column));
+
+ return false;
}
/* Caret */
@@ -3990,13 +4316,13 @@ bool TextEdit::is_caret_blink_enabled() const {
return caret_blink_enabled;
}
-float TextEdit::get_caret_blink_speed() const {
+float TextEdit::get_caret_blink_interval() const {
return caret_blink_timer->get_wait_time();
}
-void TextEdit::set_caret_blink_speed(const float p_speed) {
- ERR_FAIL_COND(p_speed <= 0);
- caret_blink_timer->set_wait_time(p_speed);
+void TextEdit::set_caret_blink_interval(const float p_interval) {
+ ERR_FAIL_COND(p_interval <= 0);
+ caret_blink_timer->set_wait_time(p_interval);
}
void TextEdit::set_move_caret_on_right_click_enabled(const bool p_enabled) {
@@ -4015,15 +4341,233 @@ bool TextEdit::is_caret_mid_grapheme_enabled() const {
return caret_mid_grapheme_enabled;
}
-bool TextEdit::is_caret_visible() const {
- return caret.visible;
+void TextEdit::set_multiple_carets_enabled(bool p_enabled) {
+ multi_carets_enabled = p_enabled;
+ if (!multi_carets_enabled) {
+ remove_secondary_carets();
+ }
+}
+
+bool TextEdit::is_multiple_carets_enabled() const {
+ return multi_carets_enabled;
+}
+
+int TextEdit::add_caret(int p_line, int p_col) {
+ if (!multi_carets_enabled) {
+ return -1;
+ }
+
+ p_line = CLAMP(p_line, 0, text.size() - 1);
+ p_col = CLAMP(p_col, 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) {
+ 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;
+}
+
+void TextEdit::remove_caret(int p_caret) {
+ ERR_FAIL_COND(carets.size() <= 0);
+ ERR_FAIL_INDEX(p_caret, carets.size());
+ carets.remove_at(p_caret);
+ caret_index_edit_dirty = true;
+}
+
+void TextEdit::remove_secondary_carets() {
+ carets.resize(1);
+ caret_index_edit_dirty = true;
+ queue_redraw();
+}
+
+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];
+
+ // 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;
+ }
+
+ 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;
+ }
+
+ if (!should_merge) {
+ 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;
+ }
+
+ 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);
+ }
+ } 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);
+ } else {
+ to_col = get_selection_to_column(caret_to_remove);
+ }
+
+ 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;
+ }
+
+ // 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;
+ }
+
+ // 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--;
+ }
+ continue;
+ }
+
+ // 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);
+ }
+ i--;
+ caret_edit_order = get_caret_index_edit_order();
+ continue;
+ }
+ }
+}
+
+int TextEdit::get_caret_count() const {
+ return carets.size();
+}
+
+Vector<int> TextEdit::get_caret_index_edit_order() {
+ if (!caret_index_edit_dirty) {
+ return caret_index_edit_order;
+ }
+
+ 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 = carets[i].selection.active ? carets[i].selection.to_line : carets[i].line;
+ int col = carets[i].selection.active ? carets[i].selection.to_column : carets[i].column;
+
+ for (; j < caret_index_edit_order.size(); j++) {
+ int idx = caret_index_edit_order[j];
+ int other_line = carets[idx].selection.active ? carets[idx].selection.to_line : carets[idx].line;
+ int other_col = carets[idx].selection.active ? carets[idx].selection.to_column : carets[idx].column;
+ if (line > other_line || (line == other_line && col > other_col)) {
+ break;
+ }
+ }
+ caret_index_edit_order.insert(j, i);
+ }
+ caret_index_edit_dirty = false;
+ return caret_index_edit_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 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]);
+ }
+
+ // 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) == carets[caret_edit_order[j]].selection.from_line) {
+ carets.write[caret_edit_order[j]].selection.from_column += edit_size;
+ }
+ }
+}
+
+bool TextEdit::is_caret_visible(int p_caret) const {
+ ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
+ return carets[p_caret].visible;
}
-Point2 TextEdit::get_caret_draw_pos() const {
- return caret.draw_pos;
+Point2 TextEdit::get_caret_draw_pos(int p_caret) const {
+ ERR_FAIL_INDEX_V(p_caret, carets.size(), Point2(0, 0));
+ return carets[p_caret].draw_pos;
}
-void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index) {
+void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index, int p_caret) {
+ ERR_FAIL_INDEX(p_caret, carets.size());
if (setting_caret_line) {
return;
}
@@ -4052,10 +4596,10 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
}
}
}
- bool caret_moved = caret.line != p_line;
- caret.line = p_line;
+ 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(caret.last_fit_x, p_line, p_wrap_index);
+ 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;
@@ -4066,11 +4610,11 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
n_col -= 1;
}
}
- caret_moved = (caret_moved || caret.column != n_col);
- caret.column = n_col;
+ caret_moved = (caret_moved || get_caret_column(p_caret) != n_col);
+ carets.write[p_caret].column = n_col;
if (is_inside_tree() && p_adjust_viewport) {
- adjust_viewport_to_caret();
+ adjust_viewport_to_caret(p_caret);
}
setting_caret_line = false;
@@ -4083,25 +4627,27 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
}
}
-int TextEdit::get_caret_line() const {
- return caret.line;
+int TextEdit::get_caret_line(int p_caret) const {
+ ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
+ return carets[p_caret].line;
}
-void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) {
+void TextEdit::set_caret_column(int p_col, 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(caret.line).length()) {
- p_col = get_line(caret.line).length();
+ if (p_col > get_line(get_caret_line(p_caret)).length()) {
+ p_col = get_line(get_caret_line(p_caret)).length();
}
- bool caret_moved = caret.column != p_col;
- caret.column = p_col;
+ bool caret_moved = get_caret_column(p_caret) != p_col;
+ carets.write[p_caret].column = p_col;
- caret.last_fit_x = _get_column_x_offset_for_line(caret.column, caret.line);
+ 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 (is_inside_tree() && p_adjust_viewport) {
- adjust_viewport_to_caret();
+ adjust_viewport_to_caret(p_caret);
}
if (caret_moved && !caret_pos_dirty) {
@@ -4112,24 +4658,36 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) {
}
}
-int TextEdit::get_caret_column() const {
- return caret.column;
+int TextEdit::get_caret_column(int p_caret) const {
+ ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
+ return carets[p_caret].column;
}
-int TextEdit::get_caret_wrap_index() const {
- return get_line_wrap_index_at_column(caret.line, caret.column);
+int TextEdit::get_caret_wrap_index(int p_caret) const {
+ ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
+ return get_line_wrap_index_at_column(get_caret_line(p_caret), get_caret_column(p_caret));
}
-String TextEdit::get_word_under_caret() const {
- ERR_FAIL_INDEX_V(caret.line, text.size(), "");
- ERR_FAIL_INDEX_V(caret.column, text[caret.line].length() + 1, "");
- PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- for (int i = 0; i < words.size(); i = i + 2) {
- if (words[i] <= caret.column && words[i + 1] > caret.column) {
- return text[caret.line].substr(words[i], words[i + 1] - words[i]);
+String TextEdit::get_word_under_caret(int p_caret) const {
+ ERR_FAIL_COND_V(p_caret > carets.size(), "");
+
+ StringBuilder selected_text;
+ for (int c = 0; c < carets.size(); c++) {
+ if (p_caret != -1 && p_caret != c) {
+ continue;
+ }
+
+ PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(c))->get_rid());
+ for (int i = 0; i < words.size(); i = i + 2) {
+ if (words[i] <= get_caret_column(c) && words[i + 1] > get_caret_column(c)) {
+ selected_text += text[get_caret_line(c)].substr(words[i], words[i + 1] - words[i]);
+ if (p_caret == -1 && c != carets.size() - 1) {
+ selected_text += "\n";
+ }
+ }
}
}
- return "";
+ return selected_text.as_string();
}
/* Selection. */
@@ -4155,7 +4713,7 @@ void TextEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) {
}
deselect_on_focus_loss_enabled = p_enabled;
- if (p_enabled && selection.active && !has_focus()) {
+ if (p_enabled && has_selection() && !has_focus()) {
deselect();
}
}
@@ -4180,22 +4738,24 @@ bool TextEdit::is_overriding_selected_font_color() const {
return override_selected_font_color;
}
-void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column) {
- selection.selecting_mode = p_mode;
+void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) {
+ ERR_FAIL_INDEX(p_caret, carets.size());
+
+ selecting_mode = p_mode;
if (p_line >= 0) {
ERR_FAIL_INDEX(p_line, text.size());
- selection.selecting_line = p_line;
- selection.selecting_column = CLAMP(selection.selecting_column, 0, text[selection.selecting_line].length());
+ 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(selection.selecting_line, text.size());
- ERR_FAIL_INDEX(p_column, text[selection.selecting_line].length());
- selection.selecting_column = p_column;
+ 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 {
- return selection.selecting_mode;
+ return selecting_mode;
}
void TextEdit::select_all() {
@@ -4206,21 +4766,19 @@ void TextEdit::select_all() {
if (text.size() == 1 && text[0].length() == 0) {
return;
}
- selection.active = true;
- selection.from_line = 0;
- selection.from_column = 0;
- selection.selecting_line = 0;
- selection.selecting_column = 0;
- selection.to_line = text.size() - 1;
- selection.to_column = text[selection.to_line].length();
- selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT;
- selection.shiftclick_left = true;
- set_caret_line(selection.to_line, false);
- set_caret_column(selection.to_column, false);
+
+ remove_secondary_carets();
+ 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() {
+void TextEdit::select_word_under_caret(int p_caret) {
+ ERR_FAIL_COND(p_caret > carets.size());
+
if (!selecting_enabled) {
return;
}
@@ -4229,36 +4787,43 @@ void TextEdit::select_word_under_caret() {
return;
}
- if (selection.active) {
- /* Allow toggling selection by pressing the shortcut a second time. */
- /* This is also usable as a general-purpose "deselect" shortcut after */
- /* selecting anything. */
- deselect();
- return;
- }
+ for (int c = 0; c < carets.size(); c++) {
+ if (p_caret != -1 && p_caret != c) {
+ continue;
+ }
- int begin = 0;
- int end = 0;
- const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- for (int i = 0; i < words.size(); i = i + 2) {
- if ((words[i] <= caret.column && words[i + 1] >= caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) {
- begin = words[i];
- end = words[i + 1];
- break;
+ if (has_selection(c)) {
+ // Allow toggling selection by pressing the shortcut a second time.
+ // This is also usable as a general-purpose "deselect" shortcut after
+ // selecting anything.
+ deselect(c);
+ continue;
}
- }
- // No word found.
- if (begin == 0 && end == 0) {
- return;
- }
+ int begin = 0;
+ int end = 0;
+ const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(c))->get_rid());
+ for (int i = 0; i < words.size(); i = i + 2) {
+ if ((words[i] <= get_caret_column(c) && words[i + 1] >= get_caret_column(c)) || (i == words.size() - 2 && get_caret_column(c) == words[i + 1])) {
+ begin = words[i];
+ end = words[i + 1];
+ break;
+ }
+ }
- select(caret.line, begin, caret.line, end);
- /* Move the caret to the end of the word for easier editing. */
- set_caret_column(end, false);
+ // No word found.
+ if (begin == 0 && end == 0) {
+ continue;
+ }
+
+ select(get_caret_line(c), begin, get_caret_column(c), end, c);
+ // Move the caret to the end of the word for easier editing.
+ set_caret_column(end, false, c);
+ }
}
-void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column) {
+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());
if (!selecting_enabled) {
return;
}
@@ -4287,91 +4852,143 @@ void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_t
p_to_column = 0;
}
- selection.from_line = p_from_line;
- selection.from_column = p_from_column;
- selection.to_line = p_to_line;
- selection.to_column = p_to_column;
+ 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;
- selection.active = true;
+ carets.write[p_caret].selection.active = true;
- if (selection.from_line == selection.to_line) {
- if (selection.from_column == selection.to_column) {
- selection.active = false;
+ if (carets[p_caret].selection.from_line == carets[p_caret].selection.to_line) {
+ if (carets[p_caret].selection.from_column == carets[p_caret].selection.to_column) {
+ carets.write[p_caret].selection.active = false;
- } else if (selection.from_column > selection.to_column) {
- selection.shiftclick_left = false;
- SWAP(selection.from_column, selection.to_column);
+ } else if (carets[p_caret].selection.from_column > carets[p_caret].selection.to_column) {
+ carets.write[p_caret].selection.shiftclick_left = false;
+ SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column);
} else {
- selection.shiftclick_left = true;
+ carets.write[p_caret].selection.shiftclick_left = true;
}
- } else if (selection.from_line > selection.to_line) {
- selection.shiftclick_left = false;
- SWAP(selection.from_line, selection.to_line);
- SWAP(selection.from_column, selection.to_column);
+ } else if (carets[p_caret].selection.from_line > carets[p_caret].selection.to_line) {
+ 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 {
- selection.shiftclick_left = true;
+ carets.write[p_caret].selection.shiftclick_left = true;
}
+ caret_index_edit_dirty = true;
queue_redraw();
}
-bool TextEdit::has_selection() const {
- return selection.active;
+bool TextEdit::has_selection(int p_caret) const {
+ ERR_FAIL_COND_V(p_caret > carets.size(), false);
+ for (int i = 0; i < carets.size(); i++) {
+ if (p_caret != -1 && p_caret != i) {
+ continue;
+ }
+
+ if (carets[i].selection.active) {
+ return true;
+ }
+ }
+ return false;
}
-String TextEdit::get_selected_text() const {
- if (!selection.active) {
- return "";
+String TextEdit::get_selected_text(int p_caret) {
+ ERR_FAIL_COND_V(p_caret > carets.size(), "");
+
+ 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 (!has_selection(caret_idx)) {
+ 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) {
+ selected_text += "\n";
+ }
}
- return _base_get_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
+ return selected_text.as_string();
}
-int TextEdit::get_selection_line() const {
- ERR_FAIL_COND_V(!selection.active, -1);
- return selection.selecting_line;
+int TextEdit::get_selection_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;
}
-int TextEdit::get_selection_column() const {
- ERR_FAIL_COND_V(!selection.active, -1);
- return selection.selecting_column;
+int TextEdit::get_selection_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;
}
-int TextEdit::get_selection_from_line() const {
- ERR_FAIL_COND_V(!selection.active, -1);
- return selection.from_line;
+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;
}
-int TextEdit::get_selection_from_column() const {
- ERR_FAIL_COND_V(!selection.active, -1);
- return selection.from_column;
+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;
}
-int TextEdit::get_selection_to_line() const {
- ERR_FAIL_COND_V(!selection.active, -1);
- return selection.to_line;
+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;
}
-int TextEdit::get_selection_to_column() const {
- ERR_FAIL_COND_V(!selection.active, -1);
- return selection.to_column;
+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;
}
-void TextEdit::deselect() {
- selection.active = false;
+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;
+ }
+ carets.write[i].selection.active = false;
+ }
+ caret_index_edit_dirty = true;
queue_redraw();
}
-void TextEdit::delete_selection() {
- if (!has_selection()) {
- return;
- }
+void TextEdit::delete_selection(int p_caret) {
+ ERR_FAIL_COND(p_caret > carets.size());
+
+ begin_complex_operation();
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &i : caret_edit_order) {
+ if (p_caret != -1 && p_caret != i) {
+ continue;
+ }
+
+ if (!has_selection(i)) {
+ continue;
+ }
- selection.active = false;
- selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE;
- _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
- set_caret_line(selection.from_line, false, false);
- set_caret_column(selection.from_column);
+ selecting_mode = SelectionMode::SELECTION_MODE_NONE;
+ _remove_text(carets[i].selection.from_line, carets[i].selection.from_column, carets[i].selection.to_line, carets[i].selection.to_column);
+ set_caret_line(carets[i].selection.from_line, false, false, 0, i);
+ set_caret_column(carets[i].selection.from_column, i == 0, i);
+ carets.write[i].selection.active = false;
+
+ 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);
+ }
+ end_complex_operation();
queue_redraw();
}
@@ -4542,7 +5159,7 @@ void TextEdit::set_line_as_first_visible(int p_line, int p_wrap_index) {
}
int TextEdit::get_first_visible_line() const {
- return CLAMP(caret.line_ofs, 0, text.size() - 1);
+ return CLAMP(first_visible_line, 0, text.size() - 1);
}
void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) {
@@ -4581,14 +5198,14 @@ void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) {
int TextEdit::get_last_full_visible_line() const {
int first_vis_line = get_first_visible_line();
int last_vis_line = 0;
- last_vis_line = first_vis_line + get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, get_visible_line_count()).x - 1;
+ last_vis_line = first_vis_line + get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, get_visible_line_count()).x - 1;
last_vis_line = CLAMP(last_vis_line, 0, text.size() - 1);
return last_vis_line;
}
int TextEdit::get_last_full_visible_line_wrap_index() const {
int first_vis_line = get_first_visible_line();
- return get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, get_visible_line_count()).y;
+ return get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, get_visible_line_count()).y;
}
int TextEdit::get_visible_line_count() const {
@@ -4624,16 +5241,18 @@ int TextEdit::get_total_visible_line_count() const {
}
// Auto adjust
-void TextEdit::adjust_viewport_to_caret() {
+void TextEdit::adjust_viewport_to_caret(int p_caret) {
+ ERR_FAIL_INDEX(p_caret, carets.size());
+
// Make sure Caret is visible on the screen.
scrolling = false;
minimap_clicked = false;
- int cur_line = caret.line;
- int cur_wrap = get_caret_wrap_index();
+ int cur_line = get_caret_line(p_caret);
+ int cur_wrap = get_caret_wrap_index(p_caret);
int first_vis_line = get_first_visible_line();
- int first_vis_wrap = caret.wrap_ofs;
+ int first_vis_wrap = first_visible_line_wrap_ofs;
int last_vis_line = get_last_full_visible_line();
int last_vis_wrap = get_last_full_visible_line_wrap_index();
@@ -4660,43 +5279,45 @@ void TextEdit::adjust_viewport_to_caret() {
// Get position of the start of caret.
if (ime_text.length() != 0 && ime_selection.x != 0) {
- caret_pos.x = _get_column_x_offset_for_line(caret.column + ime_selection.x, caret.line);
+ caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
- caret_pos.x = _get_column_x_offset_for_line(caret.column, caret.line);
+ caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
}
// Get position of the end of caret.
if (ime_text.length() != 0) {
if (ime_selection.y != 0) {
- caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_selection.x + ime_selection.y, caret.line);
+ caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
- caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_text.size(), caret.line);
+ caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret));
}
} else {
caret_pos.y = caret_pos.x;
}
- if (MAX(caret_pos.x, caret_pos.y) > (caret.x_ofs + visible_width)) {
- caret.x_ofs = MAX(caret_pos.x, caret_pos.y) - visible_width + 1;
+ if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) {
+ first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1;
}
- if (MIN(caret_pos.x, caret_pos.y) < caret.x_ofs) {
- caret.x_ofs = MIN(caret_pos.x, caret_pos.y);
+ if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) {
+ first_visible_col = MIN(caret_pos.x, caret_pos.y);
}
} else {
- caret.x_ofs = 0;
+ first_visible_col = 0;
}
- h_scroll->set_value(caret.x_ofs);
+ h_scroll->set_value(first_visible_col);
queue_redraw();
}
-void TextEdit::center_viewport_to_caret() {
+void TextEdit::center_viewport_to_caret(int p_caret) {
+ ERR_FAIL_INDEX(p_caret, carets.size());
+
// Move viewport so the caret is in the center of the screen.
scrolling = false;
minimap_clicked = false;
- set_line_as_center_visible(caret.line, get_caret_wrap_index());
+ set_line_as_center_visible(get_caret_line(p_caret), get_caret_wrap_index(p_caret));
int visible_width = get_size().width - style_normal->get_minimum_size().width - gutters_width - gutter_padding;
if (draw_minimap) {
visible_width -= minimap_width;
@@ -4713,33 +5334,33 @@ void TextEdit::center_viewport_to_caret() {
// Get position of the start of caret.
if (ime_text.length() != 0 && ime_selection.x != 0) {
- caret_pos.x = _get_column_x_offset_for_line(caret.column + ime_selection.x, caret.line);
+ caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
- caret_pos.x = _get_column_x_offset_for_line(caret.column, caret.line);
+ caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
}
// Get position of the end of caret.
if (ime_text.length() != 0) {
if (ime_selection.y != 0) {
- caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_selection.x + ime_selection.y, caret.line);
+ caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
- caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_text.size(), caret.line);
+ caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret));
}
} else {
caret_pos.y = caret_pos.x;
}
- if (MAX(caret_pos.x, caret_pos.y) > (caret.x_ofs + visible_width)) {
- caret.x_ofs = MAX(caret_pos.x, caret_pos.y) - visible_width + 1;
+ if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) {
+ first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1;
}
- if (MIN(caret_pos.x, caret_pos.y) < caret.x_ofs) {
- caret.x_ofs = MIN(caret_pos.x, caret_pos.y);
+ if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) {
+ first_visible_col = MIN(caret_pos.x, caret_pos.y);
}
} else {
- caret.x_ofs = 0;
+ first_visible_col = 0;
}
- h_scroll->set_value(caret.x_ofs);
+ h_scroll->set_value(first_visible_col);
queue_redraw();
}
@@ -5186,7 +5807,7 @@ 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("insert_text_at_caret", "text"), &TextEdit::insert_text_at_caret);
+ ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text", "caret_index"), &TextEdit::insert_text_at_caret, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("remove_text", "from_line", "from_column", "to_line", "to_column"), &TextEdit::remove_text);
@@ -5195,18 +5816,19 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_next_visible_line_index_offset_from", "line", "wrap_index", "visible_amount"), &TextEdit::get_next_visible_line_index_offset_from);
// Overridable actions
- ClassDB::bind_method(D_METHOD("backspace"), &TextEdit::backspace);
+ ClassDB::bind_method(D_METHOD("backspace", "caret_index"), &TextEdit::backspace, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut);
- ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy);
- ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste);
+ ClassDB::bind_method(D_METHOD("cut", "caret_index"), &TextEdit::cut, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("copy", "caret_index"), &TextEdit::copy, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("paste", "caret_index"), &TextEdit::paste, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("paste_primary_clipboard", "caret_index"), &TextEdit::paste_primary_clipboard, DEFVAL(-1));
- GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char")
- GDVIRTUAL_BIND(_backspace)
- GDVIRTUAL_BIND(_cut)
- GDVIRTUAL_BIND(_copy)
- GDVIRTUAL_BIND(_paste)
- GDVIRTUAL_BIND(_paste_primary_clipboard)
+ GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char", "caret_index")
+ GDVIRTUAL_BIND(_backspace, "caret_index")
+ GDVIRTUAL_BIND(_cut, "caret_index")
+ GDVIRTUAL_BIND(_copy, "caret_index")
+ GDVIRTUAL_BIND(_paste, "caret_index")
+ GDVIRTUAL_BIND(_paste_primary_clipboard, "caret_index")
// Context Menu
BIND_ENUM_CONSTANT(MENU_CUT);
@@ -5240,6 +5862,14 @@ void TextEdit::_bind_methods() {
BIND_ENUM_CONSTANT(MENU_MAX);
/* Versioning */
+ BIND_ENUM_CONSTANT(ACTION_NONE);
+ BIND_ENUM_CONSTANT(ACTION_TYPING);
+ BIND_ENUM_CONSTANT(ACTION_BACKSPACE);
+ BIND_ENUM_CONSTANT(ACTION_DELETE);
+
+ ClassDB::bind_method(D_METHOD("start_action", "action"), &TextEdit::start_action);
+ ClassDB::bind_method(D_METHOD("end_action"), &TextEdit::end_complex_operation);
+
ClassDB::bind_method(D_METHOD("begin_complex_operation"), &TextEdit::begin_complex_operation);
ClassDB::bind_method(D_METHOD("end_complex_operation"), &TextEdit::end_complex_operation);
@@ -5279,7 +5909,7 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_minimap_line_at_pos", "position"), &TextEdit::get_minimap_line_at_pos);
ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor);
- ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges"), &TextEdit::is_mouse_over_selection);
+ ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges", "caret_index"), &TextEdit::is_mouse_over_selection, DEFVAL(-1));
/* Caret. */
BIND_ENUM_CONSTANT(CARET_TYPE_LINE);
@@ -5294,8 +5924,8 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_caret_blink_enabled", "enable"), &TextEdit::set_caret_blink_enabled);
ClassDB::bind_method(D_METHOD("is_caret_blink_enabled"), &TextEdit::is_caret_blink_enabled);
- ClassDB::bind_method(D_METHOD("set_caret_blink_speed", "blink_speed"), &TextEdit::set_caret_blink_speed);
- ClassDB::bind_method(D_METHOD("get_caret_blink_speed"), &TextEdit::get_caret_blink_speed);
+ ClassDB::bind_method(D_METHOD("set_caret_blink_interval", "interval"), &TextEdit::set_caret_blink_interval);
+ ClassDB::bind_method(D_METHOD("get_caret_blink_interval"), &TextEdit::get_caret_blink_interval);
ClassDB::bind_method(D_METHOD("set_move_caret_on_right_click_enabled", "enable"), &TextEdit::set_move_caret_on_right_click_enabled);
ClassDB::bind_method(D_METHOD("is_move_caret_on_right_click_enabled"), &TextEdit::is_move_caret_on_right_click_enabled);
@@ -5303,18 +5933,30 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_caret_mid_grapheme_enabled", "enabled"), &TextEdit::set_caret_mid_grapheme_enabled);
ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &TextEdit::is_caret_mid_grapheme_enabled);
- ClassDB::bind_method(D_METHOD("is_caret_visible"), &TextEdit::is_caret_visible);
- ClassDB::bind_method(D_METHOD("get_caret_draw_pos"), &TextEdit::get_caret_draw_pos);
+ 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("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("set_caret_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::set_caret_line, DEFVAL(true), DEFVAL(true), DEFVAL(0));
- ClassDB::bind_method(D_METHOD("get_caret_line"), &TextEdit::get_caret_line);
+ 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("set_caret_column", "column", "adjust_viewport"), &TextEdit::set_caret_column, DEFVAL(true));
- ClassDB::bind_method(D_METHOD("get_caret_column"), &TextEdit::get_caret_column);
+ 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));
- ClassDB::bind_method(D_METHOD("get_caret_wrap_index"), &TextEdit::get_caret_wrap_index);
+ ClassDB::bind_method(D_METHOD("set_caret_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index", "caret_index"), &TextEdit::set_caret_line, DEFVAL(true), DEFVAL(true), DEFVAL(0), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_caret_line", "caret_index"), &TextEdit::get_caret_line, DEFVAL(0));
- ClassDB::bind_method(D_METHOD("get_word_under_caret"), &TextEdit::get_word_under_caret);
+ ClassDB::bind_method(D_METHOD("set_caret_column", "column", "adjust_viewport", "caret_index"), &TextEdit::set_caret_column, DEFVAL(true), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_caret_column", "caret_index"), &TextEdit::get_caret_column, DEFVAL(0));
+
+ ClassDB::bind_method(D_METHOD("get_caret_wrap_index", "caret_index"), &TextEdit::get_caret_wrap_index, DEFVAL(0));
+
+ ClassDB::bind_method(D_METHOD("get_word_under_caret", "caret_index"), &TextEdit::get_word_under_caret, DEFVAL(-1));
/* Selection. */
BIND_ENUM_CONSTANT(SELECTION_MODE_NONE);
@@ -5335,27 +5977,27 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &TextEdit::set_override_selected_font_color);
ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &TextEdit::is_overriding_selected_font_color);
- ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1));
+ 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("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"), &TextEdit::select_word_under_caret);
- ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column"), &TextEdit::select);
+ ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1));
+ 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("has_selection"), &TextEdit::has_selection);
+ ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_selected_text"), &TextEdit::get_selected_text);
+ ClassDB::bind_method(D_METHOD("get_selected_text", "caret_index"), &TextEdit::get_selected_text, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_selection_line"), &TextEdit::get_selection_line);
- ClassDB::bind_method(D_METHOD("get_selection_column"), &TextEdit::get_selection_column);
+ 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_from_line"), &TextEdit::get_selection_from_line);
- ClassDB::bind_method(D_METHOD("get_selection_from_column"), &TextEdit::get_selection_from_column);
- ClassDB::bind_method(D_METHOD("get_selection_to_line"), &TextEdit::get_selection_to_line);
- ClassDB::bind_method(D_METHOD("get_selection_to_column"), &TextEdit::get_selection_to_column);
+ 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("deselect"), &TextEdit::deselect);
- ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection);
+ 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));
/* Line wrapping. */
BIND_ENUM_CONSTANT(LINE_WRAPPING_NONE);
@@ -5410,8 +6052,8 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_total_visible_line_count"), &TextEdit::get_total_visible_line_count);
// Auto adjust
- ClassDB::bind_method(D_METHOD("adjust_viewport_to_caret"), &TextEdit::adjust_viewport_to_caret);
- ClassDB::bind_method(D_METHOD("center_viewport_to_caret"), &TextEdit::center_viewport_to_caret);
+ ClassDB::bind_method(D_METHOD("adjust_viewport_to_caret", "caret_index"), &TextEdit::adjust_viewport_to_caret, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("center_viewport_to_caret", "caret_index"), &TextEdit::center_viewport_to_caret, DEFVAL(0));
// Minimap
ClassDB::bind_method(D_METHOD("set_draw_minimap", "enabled"), &TextEdit::set_draw_minimap);
@@ -5524,9 +6166,10 @@ void TextEdit::_bind_methods() {
ADD_GROUP("Caret", "caret_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_type", PROPERTY_HINT_ENUM, "Line,Block"), "set_caret_type", "get_caret_type");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01,suffix:s"), "set_caret_blink_speed", "get_caret_blink_speed");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_interval", PROPERTY_HINT_RANGE, "0.1,10,0.01,suffix:s"), "set_caret_blink_interval", "get_caret_blink_interval");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_move_on_right_click"), "set_move_caret_on_right_click_enabled", "is_move_caret_on_right_click_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_multiple"), "set_multiple_carets_enabled", "is_multiple_carets_enabled");
ADD_GROUP("BiDi", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
@@ -5612,157 +6255,238 @@ void TextEdit::_set_symbol_lookup_word(const String &p_symbol) {
/* Text manipulation */
// Overridable actions
-void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode) {
+void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) {
+ ERR_FAIL_COND(p_caret > carets.size());
if (!editable) {
return;
}
- bool had_selection = has_selection();
- if (had_selection) {
- begin_complex_operation();
- delete_selection();
- }
-
- /* Remove the old character if in insert mode and no selection. */
- if (overtype_mode && !had_selection) {
- begin_complex_operation();
-
- /* Make sure we don't try and remove empty space. */
- int cl = get_caret_line();
- int cc = get_caret_column();
- if (cc < get_line(cl).length()) {
- _remove_text(cl, cc, cl, cc + 1);
+ start_action(EditAction::ACTION_TYPING);
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &i : caret_edit_order) {
+ if (p_caret != -1 && p_caret != i) {
+ continue;
}
- }
- const char32_t chr[2] = { (char32_t)p_unicode, 0 };
- insert_text_at_caret(chr);
+ /* Remove the old character if in insert mode and no selection. */
+ if (overtype_mode && !has_selection(i)) {
+ /* Make sure we don't try and remove empty space. */
+ int cl = get_caret_line(i);
+ int cc = get_caret_column(i);
+ if (cc < get_line(cl).length()) {
+ _remove_text(cl, cc, cl, cc + 1);
+ }
+ }
- if ((overtype_mode && !had_selection) || (had_selection)) {
- end_complex_operation();
+ const char32_t chr[2] = { (char32_t)p_unicode, 0 };
+ insert_text_at_caret(chr, i);
}
+ end_action();
}
-void TextEdit::_backspace_internal() {
+void TextEdit::_backspace_internal(int p_caret) {
+ ERR_FAIL_COND(p_caret > carets.size());
if (!editable) {
return;
}
- if (has_selection()) {
- delete_selection();
+ if (has_selection(p_caret)) {
+ delete_selection(p_caret);
return;
}
- int cc = get_caret_column();
- int cl = get_caret_line();
+ begin_complex_operation();
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &i : caret_edit_order) {
+ if (p_caret != -1 && p_caret != i) {
+ continue;
+ }
- if (cc == 0 && cl == 0) {
- return;
- }
+ int cc = get_caret_column(i);
+ int cl = get_caret_line(i);
- int prev_line = cc ? cl : cl - 1;
- int prev_column = cc ? (cc - 1) : (text[cl - 1].length());
+ if (cc == 0 && cl == 0) {
+ continue;
+ }
- merge_gutters(prev_line, cl);
+ int prev_line = cc ? cl : cl - 1;
+ int prev_column = cc ? (cc - 1) : (text[cl - 1].length());
- if (_is_line_hidden(cl)) {
- _set_line_as_hidden(prev_line, true);
- }
- _remove_text(prev_line, prev_column, cl, cc);
+ merge_gutters(prev_line, cl);
+
+ 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);
- set_caret_line(prev_line, false, true);
- set_caret_column(prev_column);
+ adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
+ }
+ merge_overlapping_carets();
+ end_complex_operation();
}
-void TextEdit::_cut_internal() {
+void TextEdit::_cut_internal(int p_caret) {
+ ERR_FAIL_COND(p_caret > carets.size());
if (!editable) {
return;
}
- if (has_selection()) {
- DisplayServer::get_singleton()->clipboard_set(get_selected_text());
- delete_selection();
+ if (has_selection(p_caret)) {
+ DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret));
+ delete_selection(p_caret);
cut_copy_line = "";
return;
}
- int cl = get_caret_line();
- int cc = get_caret_column();
- int indent_level = get_indent_level(cl);
- double hscroll = get_h_scroll();
+ 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();
- String clipboard = text[cl];
- DisplayServer::get_singleton()->clipboard_set(clipboard);
- set_caret_column(0);
+ // Check for overlaping 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;
+ }
+ }
- if (cl == 0 && get_line_count() > 1) {
- _remove_text(cl, 0, cl + 1, 0);
- } else {
- _remove_text(cl, 0, cl, text[cl].length());
- backspace();
- set_caret_line(get_caret_line() + 1);
- }
+ 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());
- cc += diff_indent;
- if (diff_indent != 0) {
- cc += diff_indent > 0 ? -1 : 1;
+ // 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);
}
- // Restore horizontal scroll and caret column modified by the backspace() call.
- set_h_scroll(hscroll);
- set_caret_column(cc);
+ // 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_complex_operation();
- cut_copy_line = clipboard;
+ String clipboard_string = clipboard.as_string();
+ DisplayServer::get_singleton()->clipboard_set(clipboard_string);
+ cut_copy_line = clipboard_string;
}
-void TextEdit::_copy_internal() {
- if (has_selection()) {
- DisplayServer::get_singleton()->clipboard_set(get_selected_text());
+void TextEdit::_copy_internal(int p_caret) {
+ ERR_FAIL_COND(p_caret > carets.size());
+ if (has_selection(p_caret)) {
+ DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret));
cut_copy_line = "";
return;
}
- int cl = get_caret_line();
- if (text[cl].length() != 0) {
- String clipboard = _base_get_text(cl, 0, cl, text[cl].length());
- DisplayServer::get_singleton()->clipboard_set(clipboard);
- cut_copy_line = clipboard;
+ 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";
+ }
+ }
}
+
+ String clipboard_string = clipboard.as_string();
+ DisplayServer::get_singleton()->clipboard_set(clipboard_string);
+ cut_copy_line = clipboard_string;
}
-void TextEdit::_paste_internal() {
+void TextEdit::_paste_internal(int p_caret) {
+ ERR_FAIL_COND(p_caret > carets.size());
if (!editable) {
return;
}
String clipboard = DisplayServer::get_singleton()->clipboard_get();
+ Vector<String> clipboad_lines = clipboard.split("\n");
+ bool insert_line_per_caret = p_caret == -1 && carets.size() > 1 && clipboad_lines.size() == carets.size();
begin_complex_operation();
- if (has_selection()) {
- delete_selection();
- } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) {
- set_caret_column(0);
- String ins = "\n";
- clipboard += ins;
- }
+ 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) {
+ 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 (insert_line_per_caret) {
+ clipboard = clipboad_lines[clipboad_line];
+ }
- insert_text_at_caret(clipboard);
+ insert_text_at_caret(clipboard, i);
+ clipboad_line--;
+ }
end_complex_operation();
}
-void TextEdit::_paste_primary_clipboard_internal() {
+void TextEdit::_paste_primary_clipboard_internal(int p_caret) {
+ ERR_FAIL_COND(p_caret > carets.size());
if (!is_editable() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
return;
}
String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary();
- Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- deselect();
- set_caret_line(pos.y, true, false);
- set_caret_column(pos.x);
+ if (carets.size() == 1) {
+ Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
+ deselect();
+ set_caret_line(pos.y, true, false);
+ set_caret_column(pos.x);
+ }
+
if (!paste_buffer.is_empty()) {
insert_text_at_caret(paste_buffer);
}
@@ -5879,6 +6603,10 @@ Key TextEdit::_get_menu_action_accelerator(const String &p_action) {
/* Versioning */
void TextEdit::_push_current_op() {
+ if (pending_action_end) {
+ start_action(EditAction::ACTION_NONE);
+ return;
+ }
if (current_op.type == TextOperation::TYPE_NONE) {
return; // Nothing to do.
}
@@ -5980,6 +6708,7 @@ int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) con
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() {
@@ -6002,7 +6731,7 @@ void TextEdit::_toggle_draw_caret() {
}
}
-int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const {
+int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column) const {
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
int row = 0;
@@ -6015,7 +6744,7 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const {
}
RID text_rid = text.get_line_data(p_line)->get_line_rid(row);
- CaretInfo ts_caret = TS->shaped_text_get_carets(text_rid, caret.column);
+ CaretInfo ts_caret = TS->shaped_text_get_carets(text_rid, p_column);
if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) {
return ts_caret.l_caret.position.x;
} else {
@@ -6028,8 +6757,8 @@ 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) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) {
- switch (selection.selecting_mode) {
+ 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;
@@ -6055,14 +6784,16 @@ void TextEdit::_update_selection_mode_pointer() {
Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
int col = pos.x;
+ int caret_idx = carets.size() - 1;
- select(selection.selecting_line, selection.selecting_column, line, col);
+ select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx);
- set_caret_line(line, false);
- set_caret_column(col);
+ set_caret_line(line, false, true, 0, caret_idx);
+ set_caret_column(col, true, caret_idx);
queue_redraw();
click_select_held->start();
+ merge_overlapping_carets();
}
void TextEdit::_update_selection_mode_word() {
@@ -6072,6 +6803,7 @@ void TextEdit::_update_selection_mode_word() {
Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
int col = pos.x;
+ int caret_idx = carets.size() - 1;
int caret_pos = CLAMP(col, 0, text[line].length());
int beg = caret_pos;
@@ -6086,25 +6818,25 @@ void TextEdit::_update_selection_mode_word() {
}
/* Initial selection. */
- if (!selection.active) {
- select(line, beg, line, end);
- selection.selecting_column = beg;
- selection.selected_word_beg = beg;
- selection.selected_word_end = end;
- selection.selected_word_origin = beg;
- set_caret_line(line, false);
- set_caret_column(end);
+ 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);
} else {
- if ((col <= selection.selected_word_origin && line == selection.selecting_line) || line < selection.selecting_line) {
- selection.selecting_column = selection.selected_word_end;
- select(line, beg, selection.selecting_line, selection.selected_word_end);
- set_caret_line(selection.from_line, false);
- set_caret_column(selection.from_column);
+ 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(get_selection_from_line(caret_idx), false, true, 0, caret_idx);
+ set_caret_column(get_selection_from_column(caret_idx), true, caret_idx);
} else {
- selection.selecting_column = selection.selected_word_beg;
- select(selection.selecting_line, selection.selected_word_beg, line, end);
- set_caret_line(selection.to_line, false);
- set_caret_column(selection.to_column);
+ 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);
}
}
@@ -6115,6 +6847,7 @@ void TextEdit::_update_selection_mode_word() {
queue_redraw();
click_select_held->start();
+ merge_overlapping_carets();
}
void TextEdit::_update_selection_mode_line() {
@@ -6124,21 +6857,22 @@ void TextEdit::_update_selection_mode_line() {
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 < selection.selecting_line) {
+ if (line < carets[caret_idx].selection.selecting_line) {
/* Caret is above us. */
- set_caret_line(line - 1, false);
- selection.selecting_column = text[selection.selecting_line].length();
+ set_caret_line(line - 1, false, true, 0, caret_idx);
+ carets.write[caret_idx].selection.selecting_column = text[get_selection_line(caret_idx)].length();
} else {
/* Caret is below us. */
- set_caret_line(line + 1, false);
- selection.selecting_column = 0;
+ set_caret_line(line + 1, false, true, 0, caret_idx);
+ carets.write[caret_idx].selection.selecting_column = 0;
col = text[line].length();
}
- set_caret_column(0);
+ set_caret_column(0, false, caret_idx);
- select(selection.selecting_line, selection.selecting_column, line, col);
+ 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());
}
@@ -6146,33 +6880,31 @@ void TextEdit::_update_selection_mode_line() {
queue_redraw();
click_select_held->start();
+ merge_overlapping_carets();
}
-void TextEdit::_pre_shift_selection() {
+void TextEdit::_pre_shift_selection(int p_caret) {
if (!selecting_enabled) {
return;
}
- if (!selection.active || selection.selecting_mode == SelectionMode::SELECTION_MODE_NONE) {
- selection.selecting_line = caret.line;
- selection.selecting_column = caret.column;
- selection.active = true;
+ 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);
+ return;
}
- selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT;
+ set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_selection_line(p_caret), get_selection_column(p_caret), p_caret);
}
-void TextEdit::_post_shift_selection() {
+void TextEdit::_post_shift_selection(int p_caret) {
if (!selecting_enabled) {
return;
}
- if (selection.active && selection.selecting_mode == SelectionMode::SELECTION_MODE_SHIFT) {
- select(selection.selecting_line, selection.selecting_column, caret.line, caret.column);
- queue_redraw();
+ 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);
}
-
- selection.selecting_text = true;
}
/* Line Wrapping */
@@ -6198,17 +6930,14 @@ void TextEdit::_update_wrap_at_column(bool p_force) {
_update_placeholder();
}
- _update_caret_wrap_offset();
-}
-
-void TextEdit::_update_caret_wrap_offset() {
+ // Update viewport.
int first_vis_line = get_first_visible_line();
if (is_line_wrapped(first_vis_line)) {
- caret.wrap_ofs = MIN(caret.wrap_ofs, get_line_wrap_count(first_vis_line));
+ first_visible_line_wrap_ofs = MIN(first_visible_line_wrap_ofs, get_line_wrap_count(first_vis_line));
} else {
- caret.wrap_ofs = 0;
+ first_visible_line_wrap_ofs = 0;
}
- set_line_as_first_visible(caret.line_ofs, caret.wrap_ofs);
+ set_line_as_first_visible(first_visible_line, first_visible_line_wrap_ofs);
}
/* Viewport. */
@@ -6257,8 +6986,8 @@ void TextEdit::_update_scrollbars() {
set_v_scroll(get_v_scroll());
} else {
- caret.line_ofs = 0;
- caret.wrap_ofs = 0;
+ first_visible_line = 0;
+ first_visible_line_wrap_ofs = 0;
v_scroll->set_value(0);
v_scroll->set_max(0);
v_scroll->hide();
@@ -6268,15 +6997,15 @@ void TextEdit::_update_scrollbars() {
h_scroll->show();
h_scroll->set_max(total_width);
h_scroll->set_page(visible_width);
- if (caret.x_ofs > (total_width - visible_width)) {
- caret.x_ofs = (total_width - visible_width);
+ if (first_visible_col > (total_width - visible_width)) {
+ first_visible_col = (total_width - visible_width);
}
- if (fabs(h_scroll->get_value() - (double)caret.x_ofs) >= 1) {
- h_scroll->set_value(caret.x_ofs);
+ if (fabs(h_scroll->get_value() - (double)first_visible_col) >= 1) {
+ h_scroll->set_value(first_visible_col);
}
} else {
- caret.x_ofs = 0;
+ first_visible_col = 0;
h_scroll->set_value(0);
h_scroll->set_max(0);
h_scroll->hide();
@@ -6305,7 +7034,7 @@ void TextEdit::_scroll_moved(double p_to_val) {
}
if (h_scroll->is_visible_in_tree()) {
- caret.x_ofs = h_scroll->get_value();
+ first_visible_col = h_scroll->get_value();
}
if (v_scroll->is_visible_in_tree()) {
// Set line ofs and wrap ofs.
@@ -6328,8 +7057,8 @@ void TextEdit::_scroll_moved(double p_to_val) {
int wi = line_wrap_amount - (sc - v_scroll_i - 1);
wi = CLAMP(wi, 0, line_wrap_amount);
- caret.line_ofs = n_line;
- caret.wrap_ofs = wi;
+ first_visible_line = n_line;
+ first_visible_line_wrap_ofs = wi;
}
queue_redraw();
}
@@ -6410,16 +7139,18 @@ void TextEdit::_scroll_lines_up() {
set_v_scroll(get_v_scroll() - 1);
// Adjust the caret to viewport.
- if (!selection.active) {
- int cur_line = caret.line;
- int cur_wrap = get_caret_wrap_index();
+ for (int i = 0; i < carets.size(); i++) {
+ if (has_selection(i)) {
+ continue;
+ }
+
int last_vis_line = get_last_full_visible_line();
int last_vis_wrap = get_last_full_visible_line_wrap_index();
-
- if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) {
- set_caret_line(last_vis_line, false, false, last_vis_wrap);
+ if (get_caret_line(i) > last_vis_line || (get_caret_line(i) == last_vis_line && get_caret_wrap_index(i) > last_vis_wrap)) {
+ set_caret_line(last_vis_line, false, false, last_vis_wrap, i);
}
}
+ merge_overlapping_carets();
}
void TextEdit::_scroll_lines_down() {
@@ -6430,16 +7161,17 @@ void TextEdit::_scroll_lines_down() {
set_v_scroll(get_v_scroll() + 1);
// Adjust the caret to viewport.
- if (!selection.active) {
- int cur_line = caret.line;
- int cur_wrap = get_caret_wrap_index();
- int first_vis_line = get_first_visible_line();
- int first_vis_wrap = caret.wrap_ofs;
+ for (int i = 0; i < carets.size(); i++) {
+ if (has_selection(i)) {
+ continue;
+ }
- if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) {
- set_caret_line(first_vis_line, false, false, first_vis_wrap);
+ int first_vis_line = get_first_visible_line();
+ if (get_caret_line(i) < first_vis_line || (get_caret_line(i) == first_vis_line && get_caret_wrap_index(i) < first_visible_line_wrap_ofs)) {
+ set_caret_line(first_vis_line, false, false, first_visible_line_wrap_ofs, i);
}
}
+ merge_overlapping_carets();
}
// Minimap
@@ -6576,6 +7308,8 @@ void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r
op.version = ++version;
op.chain_forward = false;
op.chain_backward = false;
+ op.start_carets = carets;
+ op.end_carets = carets;
// See if it should just be set as current op.
if (current_op.type != op.type) {
@@ -6598,6 +7332,7 @@ void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r
current_op.to_column = retchar;
current_op.to_line = retline;
current_op.version = op.version;
+ current_op.end_carets = carets;
}
void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) {
@@ -6628,6 +7363,8 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i
op.version = ++version;
op.chain_forward = false;
op.chain_backward = false;
+ op.start_carets = carets;
+ op.end_carets = carets;
// See if it should just be set as current op.
if (current_op.type != op.type) {
@@ -6642,6 +7379,7 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i
current_op.text = text + current_op.text;
current_op.from_line = p_from_line;
current_op.from_column = p_from_column;
+ current_op.end_carets = carets;
return; // Update current op.
}
@@ -6691,7 +7429,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
r_end_line = p_line + substrings.size() - 1;
r_end_column = text[r_end_line].length() - postinsert_text.length();
- TextServer::Direction dir = TS->shaped_text_get_dominant_direction_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? caret.column : 0, r_end_column);
+ TextServer::Direction dir = TS->shaped_text_get_dominant_direction_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? carets[0].column : 0, r_end_column);
if (dir != TextServer::DIRECTION_AUTO) {
input_direction = (TextDirection)dir;
}
@@ -6753,6 +7491,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li
TextEdit::TextEdit(const String &p_placeholder) {
placeholder_data_buf.instantiate();
+ carets.push_back(Caret());
clear();
set_focus_mode(FOCUS_ALL);
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index f97f99075c..a8e087909e 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -42,6 +42,14 @@ class TextEdit : public Control {
GDCLASS(TextEdit, Control);
public:
+ /* Edit Actions. */
+ enum EditAction {
+ ACTION_NONE,
+ ACTION_TYPING,
+ ACTION_BACKSPACE,
+ ACTION_DELETE,
+ };
+
/* Caret. */
enum CaretType {
CARET_TYPE_LINE,
@@ -299,12 +307,15 @@ private:
Key _get_menu_action_accelerator(const String &p_action);
/* Versioning */
+ struct Caret;
struct TextOperation {
enum Type {
TYPE_NONE,
TYPE_INSERT,
TYPE_REMOVE
};
+ Vector<Caret> start_carets;
+ Vector<Caret> end_carets;
Type type = TYPE_NONE;
int from_line = 0;
@@ -321,6 +332,10 @@ private:
bool undo_enabled = true;
int undo_stack_max_size = 50;
+ EditAction current_action = EditAction::ACTION_NONE;
+ bool pending_action_end = false;
+ bool in_action = false;
+
int complex_operation_count = 0;
bool next_operation_is_complex = false;
@@ -361,19 +376,39 @@ private:
int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const;
/* 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;
+ };
+
struct Caret {
+ Selection selection;
+
Point2 draw_pos;
bool visible = false;
int last_fit_x = 0;
int line = 0;
int column = 0;
- int x_ofs = 0;
- int line_ofs = 0;
- int wrap_ofs = 0;
- } caret;
+ };
+
+ // 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;
Color caret_color = Color(1, 1, 1);
Color caret_background_color = Color(0, 0, 0);
@@ -389,6 +424,8 @@ private:
bool caret_mid_grapheme_enabled = true;
+ bool multi_carets_enabled = true;
+
bool drag_action = false;
bool drag_caret_force_displayed = false;
@@ -397,28 +434,10 @@ private:
void _reset_caret_blink_timer();
void _toggle_draw_caret();
- int _get_column_x_offset_for_line(int p_char, int p_line) const;
+ int _get_column_x_offset_for_line(int p_char, int p_line, int p_column) const;
/* Selection. */
- struct Selection {
- SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE;
- int selecting_line = 0;
- int selecting_column = 0;
- int selected_word_beg = 0;
- int selected_word_end = 0;
- int selected_word_origin = 0;
- bool selecting_text = false;
-
- bool active = false;
-
- int from_line = 0;
- int from_column = 0;
- int to_line = 0;
- int to_column = 0;
-
- bool shiftclick_left = false;
- bool drag_attempt = false;
- } selection;
+ SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE;
bool selecting_enabled = true;
bool deselect_on_focus_loss_enabled = true;
@@ -428,6 +447,7 @@ private:
Color selection_color = Color(1, 1, 1);
bool override_selected_font_color = false;
+ bool selection_drag_attempt = false;
bool dragging_selection = false;
Timer *click_select_held = nullptr;
@@ -439,8 +459,8 @@ private:
void _update_selection_mode_word();
void _update_selection_mode_line();
- void _pre_shift_selection();
- void _post_shift_selection();
+ void _pre_shift_selection(int p_caret);
+ void _post_shift_selection(int p_caret);
/* Line wrapping. */
LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE;
@@ -450,8 +470,6 @@ private:
void _update_wrap_at_column(bool p_force = false);
- void _update_caret_wrap_offset();
-
/* Viewport. */
HScrollBar *h_scroll = nullptr;
VScrollBar *v_scroll = nullptr;
@@ -466,6 +484,10 @@ private:
float v_scroll_speed = 80.0;
// Scrolling.
+ int first_visible_line = 0;
+ int first_visible_line_wrap_ofs = 0;
+ int first_visible_col = 0;
+
bool scrolling = false;
bool updating_scrolls = false;
@@ -583,6 +605,17 @@ protected:
/* Internal API for CodeEdit, pending public API. */
// brace matching
+ struct BraceMatchingData {
+ int open_match_line = -1;
+ int open_match_column = -1;
+ bool open_matching = false;
+ bool open_mismatch = false;
+ int close_match_line = -1;
+ int close_match_column = -1;
+ bool close_matching = false;
+ bool close_mismatch = false;
+ };
+
bool highlight_matching_braces_enabled = false;
Color brace_mismatch_color;
@@ -607,20 +640,20 @@ protected:
/* Text manipulation */
// Overridable actions
- virtual void _handle_unicode_input_internal(const uint32_t p_unicode);
- virtual void _backspace_internal();
+ virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret);
+ virtual void _backspace_internal(int p_caret);
- virtual void _cut_internal();
- virtual void _copy_internal();
- virtual void _paste_internal();
- virtual void _paste_primary_clipboard_internal();
+ virtual void _cut_internal(int p_caret);
+ virtual void _copy_internal(int p_caret);
+ virtual void _paste_internal(int p_caret);
+ virtual void _paste_primary_clipboard_internal(int p_caret);
- GDVIRTUAL1(_handle_unicode_input, int)
- GDVIRTUAL0(_backspace)
- GDVIRTUAL0(_cut)
- GDVIRTUAL0(_copy)
- GDVIRTUAL0(_paste)
- GDVIRTUAL0(_paste_primary_clipboard)
+ GDVIRTUAL2(_handle_unicode_input, int, int)
+ GDVIRTUAL1(_backspace, int)
+ GDVIRTUAL1(_cut, int)
+ GDVIRTUAL1(_copy, int)
+ GDVIRTUAL1(_paste, int)
+ GDVIRTUAL1(_paste_primary_clipboard, int)
public:
/* General overrides. */
@@ -696,7 +729,7 @@ 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);
+ void insert_text_at_caret(const String &p_text, int p_caret = -1);
void remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
@@ -705,13 +738,13 @@ public:
Point2i get_next_visible_line_index_offset_from(int p_line_from, int p_wrap_index_from, int p_visible_amount) const;
// Overridable actions
- void handle_unicode_input(const uint32_t p_unicode);
- void backspace();
+ void handle_unicode_input(const uint32_t p_unicode, int p_caret = -1);
+ void backspace(int p_caret = -1);
- void cut();
- void copy();
- void paste();
- void paste_primary_clipboard();
+ void cut(int p_caret = -1);
+ void copy(int p_caret = -1);
+ void paste(int p_caret = -1);
+ void paste_primary_clipboard(int p_caret = -1);
// Context menu.
PopupMenu *get_menu() const;
@@ -719,6 +752,10 @@ public:
void menu_option(int p_option);
/* Versioning */
+ void start_action(EditAction p_action);
+ void end_action();
+ EditAction get_current_action() const;
+
void begin_complex_operation();
void end_complex_operation();
@@ -753,7 +790,7 @@ public:
int get_minimap_line_at_pos(const Point2i &p_pos) const;
bool is_dragging_cursor() const;
- bool is_mouse_over_selection(bool p_edges = true) const;
+ bool is_mouse_over_selection(bool p_edges = true, int p_caret = -1) const;
/* Caret */
void set_caret_type(CaretType p_type);
@@ -762,8 +799,8 @@ public:
void set_caret_blink_enabled(const bool p_enabled);
bool is_caret_blink_enabled() const;
- void set_caret_blink_speed(const float p_speed);
- float get_caret_blink_speed() const;
+ void set_caret_blink_interval(const float p_interval);
+ float get_caret_blink_interval() const;
void set_move_caret_on_right_click_enabled(const bool p_enabled);
bool is_move_caret_on_right_click_enabled() const;
@@ -771,18 +808,30 @@ public:
void set_caret_mid_grapheme_enabled(const bool p_enabled);
bool is_caret_mid_grapheme_enabled() const;
- bool is_caret_visible() const;
- Point2 get_caret_draw_pos() const;
+ void set_multiple_carets_enabled(bool p_enabled);
+ bool is_multiple_carets_enabled() const;
+
+ int add_caret(int p_line, int p_col);
+ void remove_caret(int p_caret);
+ void remove_secondary_carets();
+ void merge_overlapping_carets();
+ int get_caret_count() const;
+
+ 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);
+
+ bool is_caret_visible(int p_caret = 0) const;
+ Point2 get_caret_draw_pos(int p_caret = 0) const;
- void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0);
- int get_caret_line() const;
+ 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 get_caret_column() const;
+ void set_caret_column(int p_col, bool p_adjust_viewport = true, int p_caret = 0);
+ int get_caret_column(int p_caret = 0) const;
- int get_caret_wrap_index() const;
+ int get_caret_wrap_index(int p_caret = 0) const;
- String get_word_under_caret() const;
+ String get_word_under_caret(int p_caret = -1) const;
/* Selection. */
void set_selecting_enabled(const bool p_enabled);
@@ -797,27 +846,27 @@ public:
void set_override_selected_font_color(bool p_override_selected_font_color);
bool is_overriding_selected_font_color() const;
- void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1);
+ void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0);
SelectionMode get_selection_mode() const;
void select_all();
- void select_word_under_caret();
- void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
+ void select_word_under_caret(int p_caret = -1);
+ void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0);
- bool has_selection() const;
+ bool has_selection(int p_caret = -1) const;
- String get_selected_text() const;
+ String get_selected_text(int p_caret = -1);
- int get_selection_line() const;
- int get_selection_column() const;
+ int get_selection_line(int p_caret = 0) const;
+ int get_selection_column(int p_caret = 0) const;
- int get_selection_from_line() const;
- int get_selection_from_column() const;
- int get_selection_to_line() const;
- int get_selection_to_column() 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;
- void deselect();
- void delete_selection();
+ void deselect(int p_caret = -1);
+ void delete_selection(int p_caret = -1);
/* Line wrapping. */
void set_line_wrapping_mode(LineWrappingMode p_wrapping_mode);
@@ -866,8 +915,8 @@ public:
int get_total_visible_line_count() const;
// Auto Adjust
- void adjust_viewport_to_caret();
- void center_viewport_to_caret();
+ void adjust_viewport_to_caret(int p_caret = 0);
+ void center_viewport_to_caret(int p_caret = 0);
// Minimap
void set_draw_minimap(bool p_enabled);
@@ -949,6 +998,7 @@ public:
TextEdit(const String &p_placeholder = String());
};
+VARIANT_ENUM_CAST(TextEdit::EditAction);
VARIANT_ENUM_CAST(TextEdit::CaretType);
VARIANT_ENUM_CAST(TextEdit::LineWrappingMode);
VARIANT_ENUM_CAST(TextEdit::SelectionMode);
diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp
index 2efb6593d3..d9ab1c2c55 100644
--- a/scene/gui/texture_button.cpp
+++ b/scene/gui/texture_button.cpp
@@ -67,7 +67,7 @@ bool TextureButton::has_point(const Point2 &p_point) const {
Rect2 rect = Rect2();
Size2 mask_size = click_mask->get_size();
- if (_position_rect.has_no_area()) {
+ if (!_position_rect.has_area()) {
rect.size = mask_size;
} else if (_tile) {
// if the stretch mode is tile we offset the point to keep it inside the mask size
diff --git a/scene/gui/texture_rect.cpp b/scene/gui/texture_rect.cpp
index da53da20b0..459e67091d 100644
--- a/scene/gui/texture_rect.cpp
+++ b/scene/gui/texture_rect.cpp
@@ -94,7 +94,7 @@ void TextureRect::_notification(int p_what) {
Ref<AtlasTexture> p_atlas = texture;
- if (p_atlas.is_valid() && region.has_no_area()) {
+ if (p_atlas.is_valid() && !region.has_area()) {
Size2 scale_size(size.width / texture->get_width(), size.height / texture->get_height());
offset.width += hflip ? p_atlas->get_margin().get_position().width * scale_size.width * 2 : 0;
@@ -104,10 +104,10 @@ void TextureRect::_notification(int p_what) {
size.width *= hflip ? -1.0f : 1.0f;
size.height *= vflip ? -1.0f : 1.0f;
- if (region.has_no_area()) {
- draw_texture_rect(texture, Rect2(offset, size), tile);
- } else {
+ if (region.has_area()) {
draw_texture_rect_region(texture, Rect2(offset, size), region);
+ } else {
+ draw_texture_rect(texture, Rect2(offset, size), tile);
}
} break;
}
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index e8164b5728..db7d390b16 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -563,6 +563,57 @@ bool TreeItem::is_collapsed() {
return collapsed;
}
+void TreeItem::set_collapsed_recursive(bool p_collapsed) {
+ if (!tree) {
+ return;
+ }
+
+ set_collapsed(p_collapsed);
+
+ TreeItem *child = get_first_child();
+ while (child) {
+ child->set_collapsed_recursive(p_collapsed);
+ child = child->get_next();
+ }
+}
+
+bool TreeItem::_is_any_collapsed(bool p_only_visible) {
+ TreeItem *child = get_first_child();
+
+ // Check on children directly first (avoid recursing if possible).
+ while (child) {
+ if (child->get_first_child() && child->is_collapsed() && (!p_only_visible || (child->is_visible() && child->get_visible_child_count()))) {
+ return true;
+ }
+ child = child->get_next();
+ }
+
+ child = get_first_child();
+
+ // Otherwise recurse on children.
+ while (child) {
+ if (child->get_first_child() && (!p_only_visible || (child->is_visible() && child->get_visible_child_count())) && child->_is_any_collapsed(p_only_visible)) {
+ return true;
+ }
+ child = child->get_next();
+ }
+
+ return false;
+}
+
+bool TreeItem::is_any_collapsed(bool p_only_visible) {
+ if (p_only_visible && !is_visible()) {
+ return false;
+ }
+
+ // Collapsed if this is collapsed and it has children (only considers visible if only visible is set).
+ if (is_collapsed() && get_first_child() && (!p_only_visible || get_visible_child_count())) {
+ return true;
+ }
+
+ return _is_any_collapsed(p_only_visible);
+}
+
void TreeItem::set_visible(bool p_visible) {
if (visible == p_visible) {
return;
@@ -1112,7 +1163,7 @@ bool TreeItem::is_editable(int p_column) {
void TreeItem::set_custom_color(int p_column, const Color &p_color) {
ERR_FAIL_INDEX(p_column, cells.size());
- if (cells[p_column].custom_color == true) {
+ if (cells[p_column].custom_color && cells[p_column].color == p_color) {
return;
}
@@ -1406,6 +1457,9 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed);
ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed);
+ ClassDB::bind_method(D_METHOD("set_collapsed_recursive", "enable"), &TreeItem::set_collapsed_recursive);
+ ClassDB::bind_method(D_METHOD("is_any_collapsed", "only_visible"), &TreeItem::is_any_collapsed, DEFVAL(false));
+
ClassDB::bind_method(D_METHOD("set_visible", "enable"), &TreeItem::set_visible);
ClassDB::bind_method(D_METHOD("is_visible"), &TreeItem::is_visible);
@@ -1538,12 +1592,14 @@ TreeItem::~TreeItem() {
void Tree::_update_theme_item_cache() {
Control::_update_theme_item_cache();
+ theme_cache.panel_style = get_theme_stylebox(SNAME("panel"));
+ theme_cache.focus_style = get_theme_stylebox(SNAME("focus"));
+
theme_cache.font = get_theme_font(SNAME("font"));
theme_cache.font_size = get_theme_font_size(SNAME("font_size"));
theme_cache.tb_font = get_theme_font(SNAME("title_button_font"));
theme_cache.tb_font_size = get_theme_font_size(SNAME("title_button_font_size"));
- theme_cache.bg = get_theme_stylebox(SNAME("bg"));
- theme_cache.bg_focus = get_theme_stylebox(SNAME("bg_focus"));
+
theme_cache.selected = get_theme_stylebox(SNAME("selected"));
theme_cache.selected_focus = get_theme_stylebox(SNAME("selected_focus"));
theme_cache.cursor = get_theme_stylebox(SNAME("cursor"));
@@ -1955,7 +2011,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (i == 0) {
if (p_item->cells[0].selected && select_mode == SELECT_ROW) {
- Rect2i row_rect = Rect2i(Point2i(theme_cache.bg->get_margin(SIDE_LEFT), item_rect.position.y), Size2i(get_size().width - theme_cache.bg->get_minimum_size().width, item_rect.size.y));
+ Rect2i row_rect = Rect2i(Point2i(theme_cache.panel_style->get_margin(SIDE_LEFT), item_rect.position.y), Size2i(get_size().width - theme_cache.panel_style->get_minimum_size().width, item_rect.size.y));
//Rect2 r = Rect2i(row_rect.pos,row_rect.size);
//r.grow(cache.selected->get_margin(SIDE_LEFT));
if (rtl) {
@@ -2502,7 +2558,7 @@ Rect2 Tree::search_item_rect(TreeItem *p_from, TreeItem *p_item) {
void Tree::_range_click_timeout() {
if (range_item_last && !range_drag_enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
- Point2 pos = get_local_mouse_position() - theme_cache.bg->get_offset();
+ Point2 pos = get_local_mouse_position() - theme_cache.panel_style->get_offset();
if (show_column_titles) {
pos.y -= _get_title_button_height();
@@ -2520,7 +2576,7 @@ void Tree::_range_click_timeout() {
Ref<InputEventMouseButton> mb;
mb.instantiate();
- int x_limit = get_size().width - theme_cache.bg->get_minimum_size().width;
+ int x_limit = get_size().width - theme_cache.panel_style->get_minimum_size().width;
if (h_scroll->is_visible()) {
x_limit -= h_scroll->get_minimum_size().width;
}
@@ -2570,7 +2626,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
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))) {
- p_item->set_collapsed(!p_item->is_collapsed());
+ if (enable_recursive_folding && p_mod->is_shift_pressed()) {
+ p_item->set_collapsed_recursive(!p_item->is_collapsed());
+ } else {
+ p_item->set_collapsed(!p_item->is_collapsed());
+ }
return -1;
}
@@ -2609,7 +2669,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
return -1;
} else if (col == 0) {
int margin = x_ofs + theme_cache.item_margin; //-theme_cache.hseparation;
- //int lm = theme_cache.bg->get_margin(SIDE_LEFT);
+ //int lm = theme_cache.panel_style->get_margin(SIDE_LEFT);
col_width -= margin;
limit_w -= margin;
col_ofs += margin;
@@ -2621,7 +2681,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) {
- p_item->set_collapsed(!p_item->is_collapsed());
+ if (enable_recursive_folding && p_mod->is_shift_pressed()) {
+ p_item->set_collapsed_recursive(!p_item->is_collapsed());
+ } else {
+ p_item->set_collapsed(!p_item->is_collapsed());
+ }
return -1; //collapse/uncollapse because nothing can be done with item
}
@@ -2684,7 +2748,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
return -1;
}
- if (select_mode == SELECT_MULTI && p_mod->is_command_pressed() && c.selectable) {
+ if (select_mode == SELECT_MULTI && p_mod->is_command_or_control_pressed() && c.selectable) {
if (!c.selected || p_button == MouseButton::RIGHT) {
p_item->select(col);
emit_signal(SNAME("multi_selected"), p_item, col, true);
@@ -3125,7 +3189,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
- bool is_command = k.is_valid() && k->is_command_pressed();
+ bool is_command = k.is_valid() && k->is_command_or_control_pressed();
if (p_event->is_action("ui_right") && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
@@ -3283,7 +3347,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
if (!k->is_pressed()) {
return;
}
- if (k->is_command_pressed() || (k->is_shift_pressed() && k->get_unicode() == 0) || k->is_meta_pressed()) {
+ if (k->is_command_or_control_pressed() || (k->is_shift_pressed() && k->get_unicode() == 0) || k->is_meta_pressed()) {
return;
}
if (!root) {
@@ -3308,14 +3372,14 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
- Ref<StyleBox> bg = theme_cache.bg;
+ Ref<StyleBox> bg = theme_cache.panel_style;
bool rtl = is_layout_rtl();
Point2 pos = mm->get_position();
if (rtl) {
pos.x = get_size().width - pos.x;
}
- pos -= theme_cache.bg->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
Cache::ClickType old_hover = cache.hover_type;
int old_index = cache.hover_index;
@@ -3343,7 +3407,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
if (rtl) {
mpos.x = get_size().width - mpos.x;
}
- mpos -= theme_cache.bg->get_offset();
+ mpos -= theme_cache.panel_style->get_offset();
mpos.y -= _get_title_button_height();
if (mpos.y >= 0) {
if (h_scroll->is_visible_in_tree()) {
@@ -3443,7 +3507,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
if (rtl) {
pos.x = get_size().width - pos.x;
}
- pos -= theme_cache.bg->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
if (show_column_titles) {
pos.y -= _get_title_button_height();
@@ -3537,7 +3601,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
switch (mb->get_button_index()) {
case MouseButton::RIGHT:
case MouseButton::LEFT: {
- Ref<StyleBox> bg = theme_cache.bg;
+ Ref<StyleBox> bg = theme_cache.panel_style;
Point2 pos = mb->get_position();
if (rtl) {
@@ -3572,7 +3636,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
pressing_for_editor = false;
propagate_mouse_activated = false;
- int x_limit = get_size().width - theme_cache.bg->get_minimum_size().width;
+ int x_limit = get_size().width - theme_cache.panel_style->get_minimum_size().width;
if (h_scroll->is_visible()) {
x_limit -= h_scroll->get_minimum_size().width;
}
@@ -3613,7 +3677,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
if (mb->get_button_index() == MouseButton::LEFT) {
- if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && !mb->is_command_pressed()) {
+ if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && !mb->is_command_or_control_pressed()) {
emit_signal(SNAME("nothing_selected"));
}
}
@@ -3766,7 +3830,7 @@ bool Tree::is_editing() {
}
Size2 Tree::get_internal_min_size() const {
- Size2i size = theme_cache.bg->get_offset();
+ Size2i size = theme_cache.panel_style->get_offset();
if (root) {
size.height += get_item_height(root);
}
@@ -3789,23 +3853,23 @@ void Tree::update_scrollbars() {
Size2 hmin = h_scroll->get_combined_minimum_size();
Size2 vmin = v_scroll->get_combined_minimum_size();
- v_scroll->set_begin(Point2(size.width - vmin.width, theme_cache.bg->get_margin(SIDE_TOP)));
- v_scroll->set_end(Point2(size.width, size.height - theme_cache.bg->get_margin(SIDE_TOP) - theme_cache.bg->get_margin(SIDE_BOTTOM)));
+ v_scroll->set_begin(Point2(size.width - vmin.width, theme_cache.panel_style->get_margin(SIDE_TOP)));
+ v_scroll->set_end(Point2(size.width, size.height - theme_cache.panel_style->get_margin(SIDE_TOP) - theme_cache.panel_style->get_margin(SIDE_BOTTOM)));
h_scroll->set_begin(Point2(0, size.height - hmin.height));
h_scroll->set_end(Point2(size.width - vmin.width, size.height));
Size2 internal_min_size = get_internal_min_size();
- bool display_vscroll = internal_min_size.height + theme_cache.bg->get_margin(SIDE_TOP) > size.height;
- bool display_hscroll = internal_min_size.width + theme_cache.bg->get_margin(SIDE_LEFT) > size.width;
+ bool display_vscroll = internal_min_size.height + theme_cache.panel_style->get_margin(SIDE_TOP) > size.height;
+ bool display_hscroll = internal_min_size.width + theme_cache.panel_style->get_margin(SIDE_LEFT) > size.width;
for (int i = 0; i < 2; i++) {
// Check twice, as both values are dependent on each other.
if (display_hscroll) {
- display_vscroll = internal_min_size.height + theme_cache.bg->get_margin(SIDE_TOP) + hmin.height > size.height;
+ display_vscroll = internal_min_size.height + theme_cache.panel_style->get_margin(SIDE_TOP) + hmin.height > size.height;
}
if (display_vscroll) {
- display_hscroll = internal_min_size.width + theme_cache.bg->get_margin(SIDE_LEFT) + vmin.width > size.width;
+ display_hscroll = internal_min_size.width + theme_cache.panel_style->get_margin(SIDE_LEFT) + vmin.width > size.width;
}
}
@@ -3941,7 +4005,7 @@ void Tree::_notification(int p_what) {
update_scrollbars();
RID ci = get_canvas_item();
- Ref<StyleBox> bg = theme_cache.bg;
+ Ref<StyleBox> bg = theme_cache.panel_style;
Point2 draw_ofs;
draw_ofs += bg->get_offset();
@@ -3965,7 +4029,7 @@ void Tree::_notification(int p_what) {
if (show_column_titles) {
//title buttons
- int ofs2 = theme_cache.bg->get_margin(SIDE_LEFT);
+ int ofs2 = theme_cache.panel_style->get_margin(SIDE_LEFT);
for (int i = 0; i < columns.size(); i++) {
Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? theme_cache.title_button_hover : theme_cache.title_button);
Ref<Font> f = theme_cache.tb_font;
@@ -3987,11 +4051,11 @@ void Tree::_notification(int p_what) {
}
}
- // Draw the background focus outline last, so that it is drawn in front of the section headings.
+ // Draw the focus outline last, so that it is drawn in front of the section headings.
// Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling.
if (has_focus()) {
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
- theme_cache.bg_focus->draw(ci, Rect2(Point2(), get_size()));
+ theme_cache.focus_style->draw(ci, Rect2(Point2(), get_size()));
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
}
} break;
@@ -4033,7 +4097,7 @@ Size2 Tree::get_minimum_size() const {
return Size2();
} else {
Vector2 min_size = get_internal_min_size();
- Ref<StyleBox> bg = theme_cache.bg;
+ Ref<StyleBox> bg = theme_cache.panel_style;
if (bg.is_valid()) {
min_size.x += bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT);
min_size.y += bg->get_margin(SIDE_TOP) + bg->get_margin(SIDE_BOTTOM);
@@ -4329,7 +4393,7 @@ int Tree::get_column_minimum_width(int p_column) const {
// Check if the visible title of the column is wider.
if (show_column_titles) {
- min_width = MAX(theme_cache.font->get_string_size(columns[p_column].title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + theme_cache.bg->get_margin(SIDE_LEFT) + theme_cache.bg->get_margin(SIDE_RIGHT), min_width);
+ min_width = MAX(theme_cache.font->get_string_size(columns[p_column].title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT), min_width);
}
if (!columns[p_column].clip_content) {
@@ -4374,7 +4438,7 @@ int Tree::get_column_width(int p_column) const {
if (columns[p_column].expand) {
int expand_area = get_size().width;
- Ref<StyleBox> bg = theme_cache.bg;
+ Ref<StyleBox> bg = theme_cache.panel_style;
if (bg.is_valid()) {
expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT);
@@ -4482,7 +4546,7 @@ void Tree::ensure_cursor_is_visible() {
return; // Nothing under cursor.
}
- const Size2 area_size = get_size() - theme_cache.bg->get_minimum_size();
+ const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size();
int y_offset = get_item_offset(selected_item);
if (y_offset != -1) {
@@ -4831,7 +4895,7 @@ int Tree::get_column_at_position(const Point2 &p_pos) const {
if (is_layout_rtl()) {
pos.x = get_size().width - pos.x;
}
- pos -= theme_cache.bg->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
pos.y -= _get_title_button_height();
if (pos.y < 0) {
return -1;
@@ -4861,7 +4925,7 @@ int Tree::get_drop_section_at_position(const Point2 &p_pos) const {
if (is_layout_rtl()) {
pos.x = get_size().width - pos.x;
}
- pos -= theme_cache.bg->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
pos.y -= _get_title_button_height();
if (pos.y < 0) {
return -100;
@@ -4891,7 +4955,7 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const {
if (is_layout_rtl()) {
pos.x = get_size().width - pos.x;
}
- pos -= theme_cache.bg->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
pos.y -= _get_title_button_height();
if (pos.y < 0) {
return nullptr;
@@ -4918,7 +4982,7 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const {
int Tree::get_button_id_at_position(const Point2 &p_pos) const {
if (root) {
Point2 pos = p_pos;
- pos -= theme_cache.bg->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
pos.y -= _get_title_button_height();
if (pos.y < 0) {
return -1;
@@ -4959,7 +5023,7 @@ int Tree::get_button_id_at_position(const Point2 &p_pos) const {
String Tree::get_tooltip(const Point2 &p_pos) const {
if (root) {
Point2 pos = p_pos;
- pos -= theme_cache.bg->get_offset();
+ pos -= theme_cache.panel_style->get_offset();
pos.y -= _get_title_button_height();
if (pos.y < 0) {
return Control::get_tooltip(p_pos);
@@ -5024,6 +5088,14 @@ bool Tree::is_folding_hidden() const {
return hide_folding;
}
+void Tree::set_enable_recursive_folding(bool p_enable) {
+ enable_recursive_folding = p_enable;
+}
+
+bool Tree::is_recursive_folding_enabled() const {
+ return enable_recursive_folding;
+}
+
void Tree::set_drop_mode_flags(int p_flags) {
if (drop_mode_flags == p_flags) {
return;
@@ -5127,6 +5199,9 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_hide_folding", "hide"), &Tree::set_hide_folding);
ClassDB::bind_method(D_METHOD("is_folding_hidden"), &Tree::is_folding_hidden);
+ ClassDB::bind_method(D_METHOD("set_enable_recursive_folding", "enable"), &Tree::set_enable_recursive_folding);
+ ClassDB::bind_method(D_METHOD("is_recursive_folding_enabled"), &Tree::is_recursive_folding_enabled);
+
ClassDB::bind_method(D_METHOD("set_drop_mode_flags", "flags"), &Tree::set_drop_mode_flags);
ClassDB::bind_method(D_METHOD("get_drop_mode_flags"), &Tree::get_drop_mode_flags);
@@ -5141,6 +5216,7 @@ void Tree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_folding"), "set_hide_folding", "is_folding_hidden");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_recursive_folding"), "set_enable_recursive_folding", "is_recursive_folding_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden");
ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags");
ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode");
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index b4ee686bab..f994a5cec1 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -173,6 +173,8 @@ private:
}
}
+ bool _is_any_collapsed(bool p_only_visible);
+
protected:
static void _bind_methods();
@@ -272,6 +274,9 @@ public:
void set_collapsed(bool p_collapsed);
bool is_collapsed();
+ void set_collapsed_recursive(bool p_collapsed);
+ bool is_any_collapsed(bool p_only_visible = false);
+
void set_visible(bool p_visible);
bool is_visible();
@@ -483,12 +488,14 @@ private:
void propagate_set_columns(TreeItem *p_item);
struct ThemeCache {
+ Ref<StyleBox> panel_style;
+ Ref<StyleBox> focus_style;
+
Ref<Font> font;
Ref<Font> tb_font;
int font_size = 0;
int tb_font_size = 0;
- Ref<StyleBox> bg;
- Ref<StyleBox> bg_focus;
+
Ref<StyleBox> selected;
Ref<StyleBox> selected_focus;
Ref<StyleBox> cursor;
@@ -611,6 +618,8 @@ private:
bool hide_folding = false;
+ bool enable_recursive_folding = true;
+
int _count_selected_items(TreeItem *p_from) const;
bool _is_branch_selected(TreeItem *p_from) const;
bool _is_sibling_branch_selected(TreeItem *p_from) const;
@@ -710,6 +719,9 @@ public:
void set_hide_folding(bool p_hide);
bool is_folding_hidden() const;
+ void set_enable_recursive_folding(bool p_enable);
+ bool is_recursive_folding_enabled() const;
+
void set_drop_mode_flags(int p_flags);
int get_drop_mode_flags() const;
diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp
index 1e03ed6e76..0ea49b1645 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.cpp
@@ -381,14 +381,14 @@ String VideoStreamPlayer::get_stream_name() const {
return stream->get_name();
}
-float VideoStreamPlayer::get_stream_position() const {
+double VideoStreamPlayer::get_stream_position() const {
if (playback.is_null()) {
return 0;
}
return playback->get_playback_position();
}
-void VideoStreamPlayer::set_stream_position(float p_position) {
+void VideoStreamPlayer::set_stream_position(double p_position) {
if (playback.is_valid()) {
playback->seek(p_position);
}
diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h
index 9974eb8488..b1ba8a65d7 100644
--- a/scene/gui/video_stream_player.h
+++ b/scene/gui/video_stream_player.h
@@ -105,8 +105,8 @@ public:
float get_volume_db() const;
String get_stream_name() const;
- float get_stream_position() const;
- void set_stream_position(float p_position);
+ double get_stream_position() const;
+ void set_stream_position(double p_position);
void set_autoplay(bool p_enable);
bool has_autoplay() const;