summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/code_edit.cpp87
-rw-r--r--scene/gui/code_edit.h4
-rw-r--r--scene/gui/color_picker.cpp8
-rw-r--r--scene/gui/container.cpp5
-rw-r--r--scene/gui/graph_edit.cpp16
-rw-r--r--scene/gui/graph_node.cpp2
-rw-r--r--scene/gui/label.cpp161
-rw-r--r--scene/gui/link_button.cpp16
-rw-r--r--scene/gui/link_button.h1
-rw-r--r--scene/gui/menu_button.cpp12
-rw-r--r--scene/gui/option_button.cpp2
-rw-r--r--scene/gui/option_button.h4
-rw-r--r--scene/gui/popup.cpp8
-rw-r--r--scene/gui/popup_menu.h4
-rw-r--r--scene/gui/rich_text_effect.cpp8
-rw-r--r--scene/gui/rich_text_effect.h14
-rw-r--r--scene/gui/rich_text_label.cpp250
-rw-r--r--scene/gui/rich_text_label.h27
-rw-r--r--scene/gui/scroll_bar.cpp28
-rw-r--r--scene/gui/scroll_bar.h3
-rw-r--r--scene/gui/scroll_container.cpp2
-rw-r--r--scene/gui/spin_box.cpp31
-rw-r--r--scene/gui/spin_box.h6
-rw-r--r--scene/gui/tab_container.cpp3
-rw-r--r--scene/gui/text_edit.cpp187
-rw-r--r--scene/gui/text_edit.h24
-rw-r--r--scene/gui/tree.cpp113
-rw-r--r--scene/gui/tree.h3
28 files changed, 709 insertions, 320 deletions
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 5f3ab18cca..5d1106bb41 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -296,7 +296,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
mpos.x = get_size().x - mpos.x;
}
- Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y));
+ Point2i pos = get_line_column_at_pos(mpos);
int line = pos.y;
int col = pos.x;
@@ -321,7 +321,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
mpos.x = get_size().x - mpos.x;
}
- Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y));
+ Point2i pos = get_line_column_at_pos(mpos);
int line = pos.y;
int col = pos.x;
@@ -467,7 +467,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
/* MISC */
- if (k->is_action("ui_cancel", true)) {
+ if (!code_hint.is_empty() && k->is_action("ui_cancel", true)) {
set_code_hint("");
accept_event();
return;
@@ -654,7 +654,7 @@ void CodeEdit::_backspace_internal() {
// 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) {
+ if (get_first_non_whitespace_column(cl) >= cc) {
prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
prev_line = cl;
}
@@ -987,10 +987,10 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
/* 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(1, ins.length() - 2);
+ ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2);
} else {
brace_indent = false;
- ins = "\n" + ins.substr(1, ins.length() - 2);
+ ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2);
}
}
}
@@ -1400,15 +1400,21 @@ void CodeEdit::fold_line(int p_line) {
}
/* Find the last line to be hidden. */
- int end_line = get_line_count();
+ const int line_count = get_line_count() - 1;
+ int end_line = line_count;
int in_comment = is_in_comment(p_line);
int in_string = (in_comment == -1) ? is_in_string(p_line) : -1;
if (in_string != -1 || in_comment != -1) {
end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y;
- /* End line is the same therefore we have a block. */
+ /* End line is the same therefore we have a block of single line delimiters. */
if (end_line == p_line) {
- for (int i = p_line + 1; i < get_line_count(); i++) {
+ for (int i = p_line + 1; i <= line_count; i++) {
+ if (i == line_count) {
+ end_line = line_count;
+ break;
+ }
+
if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) {
end_line = i - 1;
break;
@@ -1417,14 +1423,27 @@ void CodeEdit::fold_line(int p_line) {
}
} else {
int start_indent = get_indent_level(p_line);
- for (int i = p_line + 1; i < get_line_count(); i++) {
+ for (int i = p_line + 1; i <= line_count; i++) {
if (get_line(p_line).strip_edges().size() == 0 || is_in_string(i) != -1 || is_in_comment(i) != -1) {
end_line = i;
continue;
}
- if (get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0) {
+ if (i == line_count) {
+ /* Do not fold empty last line of script if any */
+ end_line = i;
+ if (get_line(i).strip_edges().size() == 0) {
+ end_line--;
+ }
+ break;
+ }
+
+ if ((get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0)) {
+ /* Keep an empty line unfolded if any */
end_line = i - 1;
+ if (get_line(i - 1).strip_edges().size() == 0 && i - 2 > p_line) {
+ end_line = i - 2;
+ }
break;
}
}
@@ -1603,7 +1622,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const {
}
/* Region was found on this line and is not a multiline continuation. */
- if (start_position.x != -1 && start_position.x != get_line(p_line).length() + 1) {
+ int line_length = get_line(p_line).length();
+ if (start_position.x != -1 && line_length > 0 && start_position.x != line_length + 1) {
start_position.y = p_line;
return start_position;
}
@@ -1622,7 +1642,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const {
start_position.x = delimiter_cache[i].back()->key();
/* Make sure it's not a multiline continuation. */
- if (start_position.x != get_line(i).length() + 1) {
+ line_length = get_line(i).length();
+ if (line_length > 0 && start_position.x != line_length + 1) {
break;
}
}
@@ -1704,14 +1725,17 @@ bool CodeEdit::is_code_completion_enabled() const {
void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes) {
code_completion_prefixes.clear();
for (int i = 0; i < p_prefixes.size(); i++) {
- code_completion_prefixes.insert(p_prefixes[i]);
+ const String prefix = p_prefixes[i];
+
+ ERR_CONTINUE_MSG(prefix.is_empty(), "Code completion prefix cannot be empty.");
+ code_completion_prefixes.insert(prefix[0]);
}
}
TypedArray<String> CodeEdit::get_code_completion_prefixes() const {
TypedArray<String> prefixes;
- for (Set<String>::Element *E = code_completion_prefixes.front(); E; E = E->next()) {
- prefixes.push_back(E->get());
+ for (const Set<char32_t>::Element *E = code_completion_prefixes.front(); E; E = E->next()) {
+ prefixes.push_back(String::chr(E->get()));
}
return prefixes;
}
@@ -1774,9 +1798,9 @@ void CodeEdit::request_code_completion(bool p_force) {
String line = get_line(get_caret_line());
int ofs = CLAMP(get_caret_column(), 0, line.length());
- if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(String::chr(line[ofs - 1])))) {
+ if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) {
emit_signal(SNAME("request_code_completion"));
- } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[ofs - 2]))) {
+ } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(line[ofs - 2])) {
emit_signal(SNAME("request_code_completion"));
}
}
@@ -1937,7 +1961,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
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 == _get_auto_brace_pair_close_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);
@@ -1948,7 +1972,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
end_complex_operation();
cancel_code_completion();
- if (code_completion_prefixes.has(String::chr(last_completion_char))) {
+ if (code_completion_prefixes.has(last_completion_char)) {
request_code_completion();
}
}
@@ -2013,7 +2037,9 @@ String CodeEdit::get_text_for_symbol_lookup() {
void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) {
symbol_lookup_word = p_valid ? symbol_lookup_new_word : "";
symbol_lookup_new_word = "";
- _set_symbol_lookup_word(symbol_lookup_word);
+ if (lookup_symbol_word != symbol_lookup_word) {
+ _set_symbol_lookup_word(symbol_lookup_word);
+ }
}
void CodeEdit::_bind_methods() {
@@ -2548,7 +2574,10 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c
}
void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type) {
- if (p_start_key.length() > 0) {
+ // If we are the editor allow "null" as a valid start key, otherwise users cannot add delimiters via the inspector.
+ if (!(Engine::get_singleton()->is_editor_hint() && p_start_key == "null")) {
+ ERR_FAIL_COND_MSG(p_start_key.is_empty(), "delimiter start key cannot be empty");
+
for (int i = 0; i < p_start_key.length(); i++) {
ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "delimiter must start with a symbol");
}
@@ -2613,7 +2642,11 @@ void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, Delimiter
_clear_delimiters(p_type);
for (int i = 0; i < p_delimiters.size(); i++) {
- String key = p_delimiters[i].is_null() ? "" : p_delimiters[i];
+ String key = p_delimiters[i];
+
+ if (key.is_empty()) {
+ continue;
+ }
const String start_key = key.get_slice(" ", 0);
const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String();
@@ -2734,7 +2767,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
bool prev_is_word = false;
/* Cancel if we are at the close of a string. */
- if (in_string == -1 && first_quote_col == cofs - 1) {
+ if (caret_column > 0 && in_string == -1 && first_quote_col == cofs - 1) {
cancel_code_completion();
return;
/* In a string, therefore we are trying to complete the string text. */
@@ -2744,7 +2777,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
/* If we have a space, previous word might be a keyword. eg "func |". */
} else if (cofs > 0 && line[cofs - 1] == ' ') {
int ofs = cofs - 1;
- while (ofs >= 0 && line[ofs] == ' ') {
+ while (ofs > 0 && line[ofs] == ' ') {
ofs--;
}
prev_is_word = _is_char(line[ofs]);
@@ -2760,9 +2793,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
/* If all else fails, check for a prefix. */
/* Single space between caret and prefix is okay. */
bool prev_is_prefix = false;
- if (cofs > 0 && code_completion_prefixes.has(String::chr(line[cofs - 1]))) {
+ if (cofs > 0 && code_completion_prefixes.has(line[cofs - 1])) {
prev_is_prefix = true;
- } else if (cofs > 1 && line[cofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[cofs - 2]))) {
+ } else if (cofs > 1 && line[cofs - 1] == ' ' && code_completion_prefixes.has(line[cofs - 2])) {
prev_is_prefix = true;
}
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 4fbb5194e6..d8eccb7d32 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -214,7 +214,7 @@ private:
int code_completion_longest_line = 0;
Rect2i code_completion_rect;
- Set<String> code_completion_prefixes;
+ Set<char32_t> code_completion_prefixes;
List<ScriptCodeCompletionOption> code_completion_option_submitted;
List<ScriptCodeCompletionOption> code_completion_option_sources;
String code_completion_base;
@@ -248,7 +248,6 @@ private:
void _text_changed();
protected:
- void gui_input(const Ref<InputEvent> &p_gui_input) override;
void _notification(int p_what);
static void _bind_methods();
@@ -265,6 +264,7 @@ protected:
public:
/* General overrides */
+ virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
/* Indent management */
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 1afb0b8e9d..046715e17e 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -996,7 +996,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> mev = p_event;
if (mev.is_valid()) {
Viewport *r = get_tree()->get_root();
- if (!r->get_visible_rect().has_point(Point2(mev->get_global_position().x, mev->get_global_position().y))) {
+ if (!r->get_visible_rect().has_point(mev->get_global_position())) {
return;
}
@@ -1308,6 +1308,8 @@ void ColorPickerButton::_modal_closed() {
void ColorPickerButton::pressed() {
_update_picker();
+ Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
+
popup->set_as_minsize();
picker->_update_presets();
@@ -1319,13 +1321,13 @@ void ColorPickerButton::pressed() {
if (i > 1) {
cp_rect.position.y = get_screen_position().y - cp_rect.size.y;
} else {
- cp_rect.position.y = get_screen_position().y + get_size().height;
+ cp_rect.position.y = get_screen_position().y + size.height;
}
if (i & 1) {
cp_rect.position.x = get_screen_position().x;
} else {
- cp_rect.position.x = get_screen_position().x - MAX(0, (cp_rect.size.x - get_size().x));
+ cp_rect.position.x = get_screen_position().x - MAX(0, (cp_rect.size.x - size.x));
}
if (usable_rect.encloses(cp_rect)) {
diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp
index c97434f69b..11941529cd 100644
--- a/scene/gui/container.cpp
+++ b/scene/gui/container.cpp
@@ -96,17 +96,18 @@ void Container::fit_child_in_rect(Control *p_child, const Rect2 &p_rect) {
ERR_FAIL_COND(!p_child);
ERR_FAIL_COND(p_child->get_parent() != this);
+ bool rtl = is_layout_rtl();
Size2 minsize = p_child->get_combined_minimum_size();
Rect2 r = p_rect;
if (!(p_child->get_h_size_flags() & SIZE_FILL)) {
r.size.x = minsize.width;
if (p_child->get_h_size_flags() & SIZE_SHRINK_END) {
- r.position.x += p_rect.size.width - minsize.width;
+ r.position.x += rtl ? 0 : (p_rect.size.width - minsize.width);
} else if (p_child->get_h_size_flags() & SIZE_SHRINK_CENTER) {
r.position.x += Math::floor((p_rect.size.x - minsize.width) / 2);
} else {
- r.position.x += 0;
+ r.position.x += rtl ? (p_rect.size.width - minsize.width) : 0;
}
}
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index cabae9feb2..bca3b2059d 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -356,16 +356,6 @@ void GraphEdit::_graph_node_raised(Node *p_gn) {
} else {
gn->raise();
}
- int first_not_comment = 0;
- for (int i = 0; i < get_child_count(); i++) {
- GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i));
- if (gn2 && !gn2->is_comment()) {
- first_not_comment = i;
- break;
- }
- }
-
- move_child(connections_layer, first_not_comment);
emit_signal(SNAME("node_selected"), p_gn);
}
@@ -707,7 +697,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
} else if (!just_disconnected) {
String from = connecting_from;
int from_slot = connecting_index;
- Vector2 ofs = Vector2(mb->get_position().x, mb->get_position().y);
+ Vector2 ofs = mb->get_position();
if (!connecting_out) {
emit_signal(SNAME("connection_from_empty"), from, from_slot, ofs);
@@ -2113,7 +2103,7 @@ void GraphEdit::arrange_nodes() {
largest_node_size = 0.0f;
}
- emit_signal("begin_node_move");
+ emit_signal(SNAME("begin_node_move"));
for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) {
GraphNode *gn = Object::cast_to<GraphNode>(node_names[E->get()]);
gn->set_drag(true);
@@ -2126,7 +2116,7 @@ void GraphEdit::arrange_nodes() {
gn->set_position_offset(pos);
gn->set_drag(false);
}
- emit_signal("end_node_move");
+ emit_signal(SNAME("end_node_move"));
arranging_graph = false;
}
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index a20a11b79c..ecf735d7f5 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -895,7 +895,7 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) {
ERR_FAIL_COND_MSG(get_parent_control() == nullptr, "GraphNode must be the child of a GraphEdit node.");
if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- Vector2 mpos = Vector2(mb->get_position().x, mb->get_position().y);
+ Vector2 mpos = mb->get_position();
if (close_rect.size != Size2() && close_rect.has_point(mpos)) {
//send focus to parent
get_parent_control()->grab_focus();
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 3f87003423..18cde25e55 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -92,8 +92,12 @@ void Label::_shape() {
const Ref<Font> &font = get_theme_font(SNAME("font"));
int font_size = get_theme_font_size(SNAME("font_size"));
ERR_FAIL_COND(font.is_null());
- TS->shaped_text_add_string(text_rid, (uppercase) ? xl_text.to_upper() : xl_text, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
- TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, xl_text));
+ String text = (uppercase) ? xl_text.to_upper() : xl_text;
+ if (visible_chars >= 0) {
+ text = text.substr(0, visible_chars);
+ }
+ TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
+ TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text));
dirty = false;
lines_dirty = true;
}
@@ -227,7 +231,15 @@ void Label::_update_visible() {
}
}
-inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) {
+inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Vector2 &p_ofs) {
+ if (p_gl.font_rid != RID()) {
+ TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color);
+ } else {
+ TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color);
+ }
+}
+
+inline void draw_glyph_outline(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) {
if (p_gl.font_rid != RID()) {
if (p_font_shadow_color.a > 0) {
TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color);
@@ -240,9 +252,6 @@ inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const
if (p_font_outline_color.a != 0.0 && p_outline_size > 0) {
TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color);
}
- TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color);
- } else {
- TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color);
}
}
@@ -253,11 +262,18 @@ void Label::_notification(int p_what) {
return; // Nothing new.
}
xl_text = new_text;
+ if (percent_visible < 1) {
+ visible_chars = get_total_character_count() * percent_visible;
+ }
dirty = true;
update();
}
+ if (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED) {
+ update();
+ }
+
if (p_what == NOTIFICATION_DRAW) {
if (clip) {
RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true);
@@ -281,6 +297,7 @@ void Label::_notification(int p_what) {
int outline_size = get_theme_constant(SNAME("outline_size"));
int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size"));
bool rtl = TS->shaped_text_get_direction(text_rid);
+ bool rtl_layout = is_layout_rtl();
style->draw(ci, Rect2(Point2(0, 0), get_size()));
@@ -337,24 +354,6 @@ void Label::_notification(int p_what) {
}
}
- int visible_glyphs = -1;
- int glyhps_drawn = 0;
- if (percent_visible < 1) {
- int total_glyphs = 0;
- for (int i = lines_skipped; i < last_line; i++) {
- const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(lines_rid[i]);
- const TextServer::Glyph *glyphs = visual.ptr();
- int gl_size = visual.size();
- for (int j = 0; j < gl_size; j++) {
- if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
- total_glyphs++;
- }
- }
- }
-
- visible_glyphs = MIN(total_glyphs, visible_chars);
- }
-
Vector2 ofs;
ofs.y = style->get_offset().y + vbegin;
for (int i = lines_skipped; i < last_line; i++) {
@@ -370,13 +369,21 @@ void Label::_notification(int p_what) {
}
break;
case ALIGN_LEFT: {
- ofs.x = style->get_offset().x;
+ if (rtl_layout) {
+ ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
+ } else {
+ ofs.x = style->get_offset().x;
+ }
} break;
case ALIGN_CENTER: {
ofs.x = int(size.width - line_size.width) / 2;
} break;
case ALIGN_RIGHT: {
- ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
+ if (rtl_layout) {
+ ofs.x = style->get_offset().x;
+ } else {
+ ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
+ }
} break;
}
@@ -385,12 +392,61 @@ void Label::_notification(int p_what) {
int gl_size = visual.size();
TextServer::TrimData trim_data = TS->shaped_text_get_trim_data(lines_rid[i]);
+ // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps.
+ if (font_shadow_color.a > 0 || (font_outline_color.a != 0.0 && outline_size > 0)) {
+ Vector2 offset = ofs;
+ // Draw RTL ellipsis string when necessary.
+ if (rtl && trim_data.ellipsis_pos >= 0) {
+ for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) {
+ for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) {
+ //Draw glyph outlines and shadow.
+ draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+ offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance;
+ }
+ }
+ }
+
+ // Draw main text.
+ for (int j = 0; j < gl_size; j++) {
+ for (int k = 0; k < glyphs[j].repeat; k++) {
+ // Trim when necessary.
+ if (trim_data.trim_pos >= 0) {
+ if (rtl) {
+ if (j < trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+ continue;
+ }
+ } else {
+ if (j >= trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+ break;
+ }
+ }
+ }
+
+ // Draw glyph outlines and shadow.
+ draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+ offset.x += glyphs[j].advance;
+ }
+ }
+ // Draw LTR ellipsis string when necessary.
+ if (!rtl && trim_data.ellipsis_pos >= 0) {
+ for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) {
+ for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) {
+ //Draw glyph outlines and shadow.
+ draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+ offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance;
+ }
+ }
+ }
+ }
+
+ // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps.
+
// Draw RTL ellipsis string when necessary.
if (rtl && trim_data.ellipsis_pos >= 0) {
for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) {
for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) {
//Draw glyph outlines and shadow.
- draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs);
+ draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs);
ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance;
}
}
@@ -399,14 +455,6 @@ void Label::_notification(int p_what) {
// Draw main text.
for (int j = 0; j < gl_size; j++) {
for (int k = 0; k < glyphs[j].repeat; k++) {
- if (visible_glyphs != -1) {
- if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
- if (glyhps_drawn >= visible_glyphs) {
- return;
- }
- }
- }
-
// Trim when necessary.
if (trim_data.trim_pos >= 0) {
if (rtl) {
@@ -421,9 +469,8 @@ void Label::_notification(int p_what) {
}
// Draw glyph outlines and shadow.
- draw_glyph(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs);
+ draw_glyph(glyphs[j], ci, font_color, ofs);
ofs.x += glyphs[j].advance;
- glyhps_drawn++;
}
}
// Draw LTR ellipsis string when necessary.
@@ -431,7 +478,7 @@ void Label::_notification(int p_what) {
for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) {
for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) {
//Draw glyph outlines and shadow.
- draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs);
+ draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs);
ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance;
}
}
@@ -646,16 +693,16 @@ String Label::get_text() const {
}
void Label::set_visible_characters(int p_amount) {
- visible_chars = p_amount;
- if (get_total_character_count() > 0) {
- percent_visible = (float)p_amount / (float)get_total_character_count();
- } else {
- percent_visible = 1.0;
- }
- if (p_amount == -1) {
- lines_dirty = true;
+ if (visible_chars != p_amount) {
+ visible_chars = p_amount;
+ if (get_total_character_count() > 0) {
+ percent_visible = (float)p_amount / (float)get_total_character_count();
+ } else {
+ percent_visible = 1.0;
+ }
+ dirty = true;
+ update();
}
- update();
}
int Label::get_visible_characters() const {
@@ -663,15 +710,17 @@ int Label::get_visible_characters() const {
}
void Label::set_percent_visible(float p_percent) {
- if (p_percent < 0 || p_percent >= 1) {
- visible_chars = -1;
- percent_visible = 1;
- lines_dirty = true;
- } else {
- visible_chars = get_total_character_count() * p_percent;
- percent_visible = p_percent;
+ if (percent_visible != p_percent) {
+ if (p_percent < 0 || p_percent >= 1) {
+ visible_chars = -1;
+ percent_visible = 1;
+ } else {
+ visible_chars = get_total_character_count() * p_percent;
+ percent_visible = p_percent;
+ }
+ dirty = true;
+ update();
}
- update();
}
float Label::get_percent_visible() const {
@@ -826,7 +875,7 @@ void Label::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "is_clipping_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1", PROPERTY_USAGE_EDITOR), "set_visible_characters", "get_visible_characters");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
ADD_PROPERTY(PropertyInfo(Variant::INT, "lines_skipped", PROPERTY_HINT_RANGE, "0,999,1"), "set_lines_skipped", "get_lines_skipped");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lines_visible", PROPERTY_HINT_RANGE, "-1,999,1"), "set_max_lines_visible", "get_max_lines_visible");
diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp
index 419d49bccf..925e6f5b97 100644
--- a/scene/gui/link_button.cpp
+++ b/scene/gui/link_button.cpp
@@ -41,12 +41,16 @@ void LinkButton::_shape() {
} else {
text_buf->set_direction((TextServer::Direction)text_direction);
}
- TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, text));
- text_buf->add_string(text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
+ TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text));
+ text_buf->add_string(xl_text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
}
void LinkButton::set_text(const String &p_text) {
+ if (text == p_text) {
+ return;
+ }
text = p_text;
+ xl_text = atr(text);
_shape();
minimum_size_changed();
update();
@@ -141,7 +145,13 @@ Size2 LinkButton::get_minimum_size() const {
void LinkButton::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_TRANSLATION_CHANGED: {
+ xl_text = atr(text);
+ _shape();
+
+ minimum_size_changed();
+ update();
+ } break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
update();
} break;
diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h
index 7eaa9f88b6..231543c63c 100644
--- a/scene/gui/link_button.h
+++ b/scene/gui/link_button.h
@@ -47,6 +47,7 @@ public:
private:
String text;
+ String xl_text;
Ref<TextLine> text_buf;
UnderlineMode underline_mode = UNDERLINE_MODE_ALWAYS;
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 737ba84617..ceb2092e3a 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -87,15 +87,17 @@ void MenuButton::_popup_visibility_changed(bool p_visible) {
void MenuButton::pressed() {
emit_signal(SNAME("about_to_popup"));
- Size2 size = get_size();
+ Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
+ popup->set_size(Size2(size.width, 0));
Point2 gp = get_screen_position();
- gp.y += get_size().y;
-
+ gp.y += size.y;
+ if (is_layout_rtl()) {
+ gp.x += size.width - popup->get_size().width;
+ }
popup->set_position(gp);
+ popup->set_parent_rect(Rect2(Point2(gp - popup->get_position()), size));
- popup->set_size(Size2(size.width, 0));
- popup->set_parent_rect(Rect2(Point2(gp - popup->get_position()), get_size()));
popup->take_mouse_focus();
popup->popup();
}
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index d16e96dbec..2adeb2d947 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -115,7 +115,7 @@ void OptionButton::_selected(int p_which) {
}
void OptionButton::pressed() {
- Size2 size = get_size();
+ 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));
popup->popup();
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index d846e395ad..953337ecce 100644
--- a/scene/gui/option_button.h
+++ b/scene/gui/option_button.h
@@ -56,6 +56,10 @@ protected:
static void _bind_methods();
public:
+ // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes,
+ // this value should be updated to reflect the new size.
+ static const int ITEM_PROPERTY_SIZE = 5;
+
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1);
void add_item(const String &p_label, int p_id = -1);
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index e9414598a2..be05fd5a60 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -194,7 +194,7 @@ Popup::~Popup() {
}
Size2 PopupPanel::_get_contents_minimum_size() const {
- Ref<StyleBox> p = get_theme_stylebox("panel", get_class_name());
+ Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name());
Size2 ms;
@@ -217,7 +217,7 @@ Size2 PopupPanel::_get_contents_minimum_size() const {
}
void PopupPanel::_update_child_rects() {
- Ref<StyleBox> p = get_theme_stylebox("panel", get_class_name());
+ Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name());
Vector2 cpos(p->get_offset());
Vector2 csize(get_size() - p->get_minimum_size());
@@ -244,9 +244,9 @@ void PopupPanel::_update_child_rects() {
void PopupPanel::_notification(int p_what) {
if (p_what == NOTIFICATION_THEME_CHANGED) {
- panel->add_theme_style_override("panel", get_theme_stylebox("panel", get_class_name()));
+ panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name()));
} else if (p_what == NOTIFICATION_READY || p_what == NOTIFICATION_ENTER_TREE) {
- panel->add_theme_style_override("panel", get_theme_stylebox("panel", get_class_name()));
+ panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name()));
_update_child_rects();
} else if (p_what == NOTIFICATION_WM_SIZE_CHANGED) {
_update_child_rects();
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 5c427e43bc..428076c6da 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -144,6 +144,10 @@ protected:
static void _bind_methods();
public:
+ // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes,
+ // this value should be updated to reflect the new size.
+ static const int ITEM_PROPERTY_SIZE = 10;
+
void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0);
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0);
void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0);
diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp
index 236d106af8..076fa132c0 100644
--- a/scene/gui/rich_text_effect.cpp
+++ b/scene/gui/rich_text_effect.cpp
@@ -90,6 +90,12 @@ void CharFXTransform::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_glyph_index"), &CharFXTransform::get_glyph_index);
ClassDB::bind_method(D_METHOD("set_glyph_index", "glyph_index"), &CharFXTransform::set_glyph_index);
+ ClassDB::bind_method(D_METHOD("get_glyph_count"), &CharFXTransform::get_glyph_count);
+ ClassDB::bind_method(D_METHOD("set_glyph_count", "glyph_count"), &CharFXTransform::set_glyph_count);
+
+ ClassDB::bind_method(D_METHOD("get_glyph_flags"), &CharFXTransform::get_glyph_flags);
+ ClassDB::bind_method(D_METHOD("set_glyph_flags", "glyph_flags"), &CharFXTransform::set_glyph_flags);
+
ClassDB::bind_method(D_METHOD("get_font"), &CharFXTransform::get_font);
ClassDB::bind_method(D_METHOD("set_font", "font"), &CharFXTransform::set_font);
@@ -101,5 +107,7 @@ void CharFXTransform::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "env"), "set_environment", "get_environment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_index"), "set_glyph_index", "get_glyph_index");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_count"), "set_glyph_count", "get_glyph_count");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_flags"), "set_glyph_flags", "get_glyph_flags");
ADD_PROPERTY(PropertyInfo(Variant::RID, "font"), "set_font", "get_font");
}
diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h
index f5506542bb..5681f9b193 100644
--- a/scene/gui/rich_text_effect.h
+++ b/scene/gui/rich_text_effect.h
@@ -50,6 +50,8 @@ public:
double elapsed_time = 0.0f;
Dictionary environment;
uint32_t glyph_index = 0;
+ uint16_t glyph_flags = 0;
+ uint8_t glyph_count = 0;
RID font;
CharFXTransform();
@@ -57,19 +59,31 @@ public:
Vector2i get_range() { return range; }
void set_range(const Vector2i &p_range) { range = p_range; }
+
double get_elapsed_time() { return elapsed_time; }
void set_elapsed_time(double p_elapsed_time) { elapsed_time = p_elapsed_time; }
+
bool is_visible() { return visibility; }
void set_visibility(bool p_visibility) { visibility = p_visibility; }
+
bool is_outline() { return outline; }
void set_outline(bool p_outline) { outline = p_outline; }
+
Point2 get_offset() { return offset; }
void set_offset(Point2 p_offset) { offset = p_offset; }
+
Color get_color() { return color; }
void set_color(Color p_color) { color = p_color; }
uint32_t get_glyph_index() const { return glyph_index; };
void set_glyph_index(uint32_t p_glyph_index) { glyph_index = p_glyph_index; };
+
+ uint16_t get_glyph_flags() const { return glyph_index; };
+ void set_glyph_flags(uint16_t p_glyph_flags) { glyph_flags = p_glyph_flags; };
+
+ uint8_t get_glyph_count() const { return glyph_count; };
+ void set_glyph_count(uint8_t p_glyph_count) { glyph_count = p_glyph_count; };
+
RID get_font() const { return font; };
void set_font(RID p_font) { font = p_font; };
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index fbc67d8a24..3eaae5181d 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -333,7 +333,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
} else {
frame->lines.write[i].offset.y = 0;
}
- frame->lines.write[i].offset += Vector2(offset.x, offset.y);
+ frame->lines.write[i].offset += offset;
float h = frame->lines[i].text_buf->get_size().y;
if (frame->min_size_over.y > 0) {
@@ -366,7 +366,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
if (p_line > 0) {
- l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y;
+ l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
} else {
l.offset.y = 0;
}
@@ -399,8 +399,9 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
// Shape current paragraph.
String text;
Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ int remaining_characters = visible_characters - l.char_offset;
for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
- if (visible_characters >= 0 && l.char_offset + l.char_count > visible_characters) {
+ if (visible_characters >= 0 && remaining_characters <= 0) {
break;
}
switch (it->type) {
@@ -427,7 +428,8 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
l.text_buf->add_string("\n", font, font_size, Dictionary(), "");
text += "\n";
- l.char_count += 1;
+ l.char_count++;
+ remaining_characters--;
} break;
case ITEM_TEXT: {
ItemText *t = (ItemText *)it;
@@ -442,9 +444,10 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
Dictionary font_ftr = _find_font_features(it);
String lang = _find_language(it);
String tx = t->text;
- if (visible_characters >= 0 && l.char_offset + l.char_count + tx.length() > visible_characters) {
- tx = tx.substr(0, l.char_offset + l.char_count + tx.length() - visible_characters);
+ if (visible_characters >= 0 && remaining_characters >= 0) {
+ tx = tx.substr(0, remaining_characters);
}
+ remaining_characters -= tx.length();
l.text_buf->add_string(tx, font, font_size, font_ftr, lang);
text += tx;
@@ -454,7 +457,8 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
ItemImage *img = (ItemImage *)it;
l.text_buf->add_object((uint64_t)it, img->image->get_size(), img->inline_align, 1);
text += String::chr(0xfffc);
- l.char_count += 1;
+ l.char_count++;
+ remaining_characters--;
} break;
case ITEM_TABLE: {
ItemTable *table = static_cast<ItemTable *>(it);
@@ -483,6 +487,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
int cell_ch = (char_offset - (l.char_offset + l.char_count));
l.char_count += cell_ch;
t_char_count += cell_ch;
+ remaining_characters -= cell_ch;
table->columns.write[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x));
table->columns.write[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wraped_size().x));
@@ -573,7 +578,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
} else {
frame->lines.write[i].offset.y = 0;
}
- frame->lines.write[i].offset += Vector2(offset.x, offset.y);
+ frame->lines.write[i].offset += offset;
float h = frame->lines[i].text_buf->get_size().y;
if (frame->min_size_over.y > 0) {
@@ -614,7 +619,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
*r_char_offset = l.char_offset + l.char_count;
if (p_line > 0) {
- l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y;
+ l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
} else {
l.offset.y = 0;
}
@@ -847,6 +852,21 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off);
RID frid = glyphs[i].font_rid;
uint32_t gl = glyphs[i].index;
+ uint16_t gl_fl = glyphs[i].flags;
+ uint8_t gl_cn = glyphs[i].count;
+ bool cprev = false;
+ if (gl_cn == 0) { // Parts of the same cluster, always connected.
+ cprev = 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;
+ }
+ } else {
+ if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
+ cprev = true;
+ }
+ }
//Apply fx.
float faded_visibility = 1.0f;
@@ -875,6 +895,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
charfx->outline = true;
charfx->font = frid;
charfx->glyph_index = gl;
+ charfx->glyph_flags = gl_fl;
+ charfx->glyph_count = gl_cn;
charfx->offset = fx_offset;
charfx->color = font_color;
@@ -890,25 +912,34 @@ 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);
- 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 n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
- n_time = (n_time > 1.0) ? 1.0 : n_time;
- fx_offset += 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;
+ if (!cprev) {
+ 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 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;
+ }
+ fx_offset += item_shake->prev_off;
} else if (item_fx->type == ITEM_WAVE) {
ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
- double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f);
- fx_offset += Point2(0, 1) * value;
+ if (!cprev) {
+ 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;
+ }
+ fx_offset += item_wave->prev_off;
} else if (item_fx->type == ITEM_TORNADO) {
ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
- 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);
- fx_offset += Point2(torn_x, torn_y);
+ if (!cprev) {
+ 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);
+ }
+ fx_offset += item_tornado->prev_off;
} else if (item_fx->type == ITEM_RAINBOW) {
ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx);
@@ -999,6 +1030,21 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off);
RID frid = glyphs[i].font_rid;
uint32_t gl = glyphs[i].index;
+ uint16_t gl_fl = glyphs[i].flags;
+ uint8_t gl_cn = glyphs[i].count;
+ bool cprev = false;
+ if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected.
+ cprev = 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;
+ }
+ } else {
+ if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
+ cprev = true;
+ }
+ }
//Apply fx.
float faded_visibility = 1.0f;
@@ -1027,6 +1073,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
charfx->outline = false;
charfx->font = frid;
charfx->glyph_index = gl;
+ charfx->glyph_flags = gl_fl;
+ charfx->glyph_count = gl_cn;
charfx->offset = fx_offset;
charfx->color = font_color;
@@ -1042,25 +1090,34 @@ 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);
- 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 n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
- n_time = (n_time > 1.0) ? 1.0 : n_time;
- fx_offset += 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;
+ if (!cprev) {
+ 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 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;
+ }
+ fx_offset += item_shake->prev_off;
} else if (item_fx->type == ITEM_WAVE) {
ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
- double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f);
- fx_offset += Point2(0, 1) * value;
+ if (!cprev) {
+ 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;
+ }
+ fx_offset += item_wave->prev_off;
} else if (item_fx->type == ITEM_TORNADO) {
ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
- 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);
- fx_offset += Point2(torn_x, torn_y);
+ if (!cprev) {
+ 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);
+ }
+ fx_offset += item_tornado->prev_off;
} else if (item_fx->type == ITEM_RAINBOW) {
ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx);
@@ -1374,8 +1431,8 @@ void RichTextLabel::_notification(int p_what) {
} break;
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_ENTER_TREE: {
- if (bbcode != "") {
- set_bbcode(bbcode);
+ if (text != "") {
+ set_text(text);
}
main->first_invalid_line = 0; //invalidate ALL
@@ -2767,10 +2824,10 @@ bool RichTextLabel::is_scroll_following() const {
Error RichTextLabel::parse_bbcode(const String &p_bbcode) {
clear();
- return append_bbcode(p_bbcode);
+ return append_text(p_bbcode);
}
-Error RichTextLabel::append_bbcode(const String &p_bbcode) {
+Error RichTextLabel::append_text(const String &p_bbcode) {
int pos = 0;
List<String> tag_stack;
@@ -3824,8 +3881,8 @@ int RichTextLabel::get_selection_to() const {
return selection.to_frame->lines[selection.to_line].char_offset + selection.to_char - 1;
}
-void RichTextLabel::set_bbcode(const String &p_bbcode) {
- bbcode = p_bbcode;
+void RichTextLabel::set_text(const String &p_bbcode) {
+ text = p_bbcode;
if (is_inside_tree() && use_bbcode) {
parse_bbcode(p_bbcode);
} else { // raw text
@@ -3834,8 +3891,8 @@ void RichTextLabel::set_bbcode(const String &p_bbcode) {
}
}
-String RichTextLabel::get_bbcode() const {
- return bbcode;
+String RichTextLabel::get_text() const {
+ return text;
}
void RichTextLabel::set_use_bbcode(bool p_enable) {
@@ -3843,19 +3900,24 @@ void RichTextLabel::set_use_bbcode(bool p_enable) {
return;
}
use_bbcode = p_enable;
- set_bbcode(bbcode);
notify_property_list_changed();
+ set_text(text);
}
bool RichTextLabel::is_using_bbcode() const {
return use_bbcode;
}
-String RichTextLabel::get_text() {
+String RichTextLabel::get_parsed_text() const {
String text = "";
Item *it = main;
while (it) {
- if (it->type == ITEM_TEXT) {
+ if (it->type == ITEM_DROPCAP) {
+ const ItemDropcap *dc = (ItemDropcap *)it;
+ if (dc != nullptr) {
+ text += dc->text;
+ }
+ } else if (it->type == ITEM_TEXT) {
ItemText *t = static_cast<ItemText *>(it);
text += t->text;
} else if (it->type == ITEM_NEWLINE) {
@@ -3870,11 +3932,6 @@ String RichTextLabel::get_text() {
return text;
}
-void RichTextLabel::set_text(const String &p_string) {
- clear();
- add_text(p_string);
-}
-
void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
@@ -3931,7 +3988,6 @@ void RichTextLabel::set_percent_visible(float p_percent) {
if (p_percent < 0 || p_percent >= 1) {
visible_characters = -1;
percent_visible = 1;
-
} else {
visible_characters = get_total_character_count() * p_percent;
percent_visible = p_percent;
@@ -3946,24 +4002,15 @@ float RichTextLabel::get_percent_visible() const {
return percent_visible;
}
-void RichTextLabel::set_effects(const Vector<Variant> &effects) {
- custom_effects.clear();
- for (int i = 0; i < effects.size(); i++) {
- Ref<RichTextEffect> effect = Ref<RichTextEffect>(effects[i]);
- custom_effects.push_back(effect);
- }
-
- if ((bbcode != "") && use_bbcode) {
- parse_bbcode(bbcode);
+void RichTextLabel::set_effects(Array p_effects) {
+ custom_effects = p_effects;
+ if ((text != "") && use_bbcode) {
+ parse_bbcode(text);
}
}
-Vector<Variant> RichTextLabel::get_effects() {
- Vector<Variant> r;
- for (int i = 0; i < custom_effects.size(); i++) {
- r.push_back(custom_effects[i]);
- }
- return r;
+Array RichTextLabel::get_effects() {
+ return custom_effects;
}
void RichTextLabel::install_effect(const Variant effect) {
@@ -3972,8 +4019,8 @@ void RichTextLabel::install_effect(const Variant effect) {
if (rteffect.is_valid()) {
custom_effects.push_back(effect);
- if ((bbcode != "") && use_bbcode) {
- parse_bbcode(bbcode);
+ if ((text != "") && use_bbcode) {
+ parse_bbcode(text);
}
}
}
@@ -3986,14 +4033,20 @@ int RichTextLabel::get_content_height() const {
return total_height;
}
-void RichTextLabel::_validate_property(PropertyInfo &property) const {
- if (!use_bbcode && property.name == "bbcode_text") {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+#ifndef DISABLE_DEPRECATED
+// People will be very angry, if their texts get erased, because of #39148. (3.x -> 4.0)
+// Altough some people may not used bbcode_text, so we only overwrite, if bbcode_text is not empty
+bool RichTextLabel::_set(const StringName &p_name, const Variant &p_value) {
+ if (p_name == "bbcode_text" && !((String)p_value).is_empty()) {
+ set_text(p_value);
+ return true;
}
+ return false;
}
+#endif
void RichTextLabel::_bind_methods() {
- ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text);
+ ClassDB::bind_method(D_METHOD("get_parsed_text"), &RichTextLabel::get_parsed_text);
ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text);
ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text);
ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGN_CENTER));
@@ -4071,10 +4124,9 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_selected_text"), &RichTextLabel::get_selected_text);
ClassDB::bind_method(D_METHOD("parse_bbcode", "bbcode"), &RichTextLabel::parse_bbcode);
- ClassDB::bind_method(D_METHOD("append_bbcode", "bbcode"), &RichTextLabel::append_bbcode);
+ ClassDB::bind_method(D_METHOD("append_text", "bbcode"), &RichTextLabel::append_text);
- ClassDB::bind_method(D_METHOD("set_bbcode", "text"), &RichTextLabel::set_bbcode);
- ClassDB::bind_method(D_METHOD("get_bbcode"), &RichTextLabel::get_bbcode);
+ ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text);
ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &RichTextLabel::set_visible_characters);
ClassDB::bind_method(D_METHOD("get_visible_characters"), &RichTextLabel::get_visible_characters);
@@ -4101,16 +4153,13 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_effects"), &RichTextLabel::get_effects);
ClassDB::bind_method(D_METHOD("install_effect", "effect"), &RichTextLabel::install_effect);
- ADD_GROUP("BBCode", "bbcode_");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "bbcode_text", PROPERTY_HINT_MULTILINE_TEXT), "set_bbcode", "get_bbcode");
-
ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content_height"), "set_fit_content_height", "is_fit_content_height_enabled");
@@ -4172,16 +4221,20 @@ void RichTextLabel::_bind_methods() {
}
void RichTextLabel::set_visible_characters(int p_visible) {
- visible_characters = p_visible;
- if (p_visible == -1) {
- percent_visible = 1;
- } else {
- int total_char_count = get_total_character_count();
- if (total_char_count > 0) {
- percent_visible = (float)p_visible / (float)total_char_count;
+ if (visible_characters != p_visible) {
+ visible_characters = p_visible;
+ if (p_visible == -1) {
+ percent_visible = 1;
+ } else {
+ int total_char_count = get_total_character_count();
+ if (total_char_count > 0) {
+ percent_visible = (float)p_visible / (float)total_char_count;
+ }
}
+ main->first_invalid_line = 0; //invalidate ALL
+ _validate_line_caches(main);
+ update();
}
- update();
}
int RichTextLabel::get_visible_characters() const {
@@ -4189,9 +4242,19 @@ int RichTextLabel::get_visible_characters() const {
}
int RichTextLabel::get_total_character_count() const {
+ // Note: Do not use line buffer "char_count", it includes only visible characters.
int tc = 0;
- for (int i = 0; i < current_frame->lines.size(); i++) {
- tc += current_frame->lines[i].char_count;
+ Item *it = main;
+ while (it) {
+ if (it->type == ITEM_TEXT) {
+ ItemText *t = static_cast<ItemText *>(it);
+ tc += t->text.length();
+ } else if (it->type == ITEM_NEWLINE) {
+ tc++;
+ } else if (it->type == ITEM_IMAGE) {
+ tc++;
+ }
+ it = _get_next_item(it, true);
}
return tc;
@@ -4279,12 +4342,13 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item
Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) {
for (int i = 0; i < custom_effects.size(); i++) {
- if (!custom_effects[i].is_valid()) {
+ Ref<RichTextEffect> effect = custom_effects[i];
+ if (!effect.is_valid()) {
continue;
}
- if (custom_effects[i]->get_bbcode() == p_bbcode_identifier) {
- return custom_effects[i];
+ if (effect->get_bbcode() == p_bbcode_identifier) {
+ return effect;
}
}
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index ae04a7e684..94f02a3989 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -85,7 +85,6 @@ public:
protected:
void _notification(int p_what);
static void _bind_methods();
- void _validate_property(PropertyInfo &property) const override;
private:
struct Item;
@@ -268,6 +267,7 @@ private:
float rate = 0.0f;
uint64_t _current_rng = 0;
uint64_t _previous_rng = 0;
+ Vector2 prev_off;
ItemShake() { type = ITEM_SHAKE; }
@@ -290,6 +290,7 @@ private:
struct ItemWave : public ItemFX {
float frequency = 1.0f;
float amplitude = 1.0f;
+ Vector2 prev_off;
ItemWave() { type = ITEM_WAVE; }
};
@@ -297,6 +298,7 @@ private:
struct ItemTornado : public ItemFX {
float radius = 1.0f;
float frequency = 1.0f;
+ Vector2 prev_off;
ItemTornado() { type = ITEM_TORNADO; }
};
@@ -363,7 +365,7 @@ private:
ItemMeta *meta_hovering = nullptr;
Variant current_meta;
- Vector<Ref<RichTextEffect>> custom_effects;
+ Array custom_effects;
void _invalidate_current_line(ItemFrame *p_frame);
void _validate_line_caches(ItemFrame *p_frame);
@@ -452,16 +454,19 @@ private:
virtual Dictionary parse_expressions_for_values(Vector<String> p_expressions);
void _draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag);
-
+#ifndef DISABLE_DEPRECATED
+ // Kept for compatibility from 3.x to 4.0.
+ bool _set(const StringName &p_name, const Variant &p_value);
+#endif
bool use_bbcode = false;
- String bbcode;
+ String text;
int fixed_width = -1;
bool fit_content_height = false;
public:
- String get_text();
+ String get_parsed_text() const;
void add_text(const String &p_text);
void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlign p_align = INLINE_ALIGN_CENTER);
void add_newline();
@@ -548,15 +553,13 @@ public:
void selection_copy();
Error parse_bbcode(const String &p_bbcode);
- Error append_bbcode(const String &p_bbcode);
+ Error append_text(const String &p_bbcode);
void set_use_bbcode(bool p_enable);
bool is_using_bbcode() const;
- void set_bbcode(const String &p_bbcode);
- String get_bbcode() const;
-
- void set_text(const String &p_string);
+ void set_text(const String &p_bbcode);
+ String get_text() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
@@ -577,8 +580,8 @@ public:
void set_percent_visible(float p_percent);
float get_percent_visible() const;
- void set_effects(const Vector<Variant> &effects);
- Vector<Variant> get_effects();
+ void set_effects(Array p_effects);
+ Array get_effects();
void install_effect(const Variant effect);
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 08bcb0bdda..4a3a6837d5 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -80,12 +80,16 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
double total = orientation == VERTICAL ? get_size().height : get_size().width;
if (ofs < decr_size) {
+ decr_active = true;
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
+ update();
return;
}
if (ofs > total - incr_size) {
+ incr_active = true;
set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
+ update();
return;
}
@@ -130,6 +134,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
}
} else {
+ incr_active = false;
+ decr_active = false;
drag.active = false;
update();
}
@@ -215,8 +221,24 @@ void ScrollBar::_notification(int p_what) {
if (p_what == NOTIFICATION_DRAW) {
RID ci = get_canvas_item();
- Ref<Texture2D> decr = highlight == HIGHLIGHT_DECR ? get_theme_icon(SNAME("decrement_highlight")) : get_theme_icon(SNAME("decrement"));
- Ref<Texture2D> incr = highlight == HIGHLIGHT_INCR ? get_theme_icon(SNAME("increment_highlight")) : get_theme_icon(SNAME("increment"));
+ Ref<Texture2D> decr, incr;
+
+ if (decr_active) {
+ decr = get_theme_icon(SNAME("decrement_pressed"));
+ } else if (highlight == HIGHLIGHT_DECR) {
+ decr = get_theme_icon(SNAME("decrement_highlight"));
+ } else {
+ decr = get_theme_icon(SNAME("decrement"));
+ }
+
+ if (incr_active) {
+ incr = get_theme_icon(SNAME("increment_pressed"));
+ } else if (highlight == HIGHLIGHT_INCR) {
+ incr = get_theme_icon(SNAME("increment_highlight"));
+ } else {
+ incr = get_theme_icon(SNAME("increment"));
+ }
+
Ref<StyleBox> bg = has_focus() ? get_theme_stylebox(SNAME("scroll_focus")) : get_theme_stylebox(SNAME("scroll"));
Ref<StyleBox> grabber;
@@ -538,7 +560,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {
if (mm.is_valid()) {
if (drag_node_touching && !drag_node_touching_deaccel) {
- Vector2 motion = Vector2(mm->get_relative().x, mm->get_relative().y);
+ Vector2 motion = mm->get_relative();
drag_node_accum -= motion;
Vector2 diff = drag_node_from + drag_node_accum;
diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h
index fbc035397f..574d17ee20 100644
--- a/scene/gui/scroll_bar.h
+++ b/scene/gui/scroll_bar.h
@@ -51,6 +51,9 @@ class ScrollBar : public Range {
HighlightStatus highlight = HIGHLIGHT_NONE;
+ bool incr_active = false;
+ bool decr_active = false;
+
struct Drag {
bool active = false;
float pos_at_click = 0.0;
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 0c051f61e2..0c0ec39c7f 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -167,7 +167,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
if (mm.is_valid()) {
if (drag_touching && !drag_touching_deaccel) {
- Vector2 motion = Vector2(mm->get_relative().x, mm->get_relative().y);
+ Vector2 motion = mm->get_relative();
drag_accum -= motion;
if (beyond_deadzone || (scroll_h && Math::abs(drag_accum.x) > deadzone) || (scroll_v && Math::abs(drag_accum.y) > deadzone)) {
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 1074d0d8a0..0aec017649 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -68,6 +68,15 @@ void SpinBox::_text_submitted(const String &p_string) {
_value_changed(0);
}
+void SpinBox::_text_changed(const String &p_string) {
+ int cursor_pos = line_edit->get_caret_column();
+
+ _text_submitted(p_string);
+
+ // Line edit 'set_text' method resets the cursor position so we need to undo that.
+ line_edit->set_caret_column(cursor_pos);
+}
+
LineEdit *SpinBox::get_line_edit() {
return line_edit;
}
@@ -244,6 +253,24 @@ String SpinBox::get_prefix() const {
return prefix;
}
+void SpinBox::set_update_on_text_changed(bool p_update) {
+ if (update_on_text_changed == p_update) {
+ return;
+ }
+
+ update_on_text_changed = p_update;
+
+ if (p_update) {
+ line_edit->connect("text_changed", callable_mp(this, &SpinBox::_text_changed), Vector<Variant>(), CONNECT_DEFERRED);
+ } else {
+ line_edit->disconnect("text_changed", callable_mp(this, &SpinBox::_text_changed));
+ }
+}
+
+bool SpinBox::get_update_on_text_changed() const {
+ return update_on_text_changed;
+}
+
void SpinBox::set_editable(bool p_editable) {
line_edit->set_editable(p_editable);
}
@@ -267,11 +294,14 @@ void SpinBox::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_prefix"), &SpinBox::get_prefix);
ClassDB::bind_method(D_METHOD("set_editable", "editable"), &SpinBox::set_editable);
ClassDB::bind_method(D_METHOD("is_editable"), &SpinBox::is_editable);
+ ClassDB::bind_method(D_METHOD("set_update_on_text_changed"), &SpinBox::set_update_on_text_changed);
+ ClassDB::bind_method(D_METHOD("get_update_on_text_changed"), &SpinBox::get_update_on_text_changed);
ClassDB::bind_method(D_METHOD("apply"), &SpinBox::apply);
ClassDB::bind_method(D_METHOD("get_line_edit"), &SpinBox::get_line_edit);
ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "update_on_text_changed"), "set_update_on_text_changed", "get_update_on_text_changed");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "prefix"), "set_prefix", "get_prefix");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "suffix"), "set_suffix", "get_suffix");
}
@@ -284,7 +314,6 @@ SpinBox::SpinBox() {
line_edit->set_mouse_filter(MOUSE_FILTER_PASS);
line_edit->set_align(LineEdit::ALIGN_LEFT);
- //connect("value_changed",this,"_value_changed");
line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), Vector<Variant>(), CONNECT_DEFERRED);
line_edit->connect("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), Vector<Variant>(), CONNECT_DEFERRED);
line_edit->connect("gui_input", callable_mp(this, &SpinBox::_line_edit_input));
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index 9ec3885f1f..9828b894da 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -40,6 +40,7 @@ class SpinBox : public Range {
LineEdit *line_edit;
int last_w = 0;
+ bool update_on_text_changed = false;
Timer *range_click_timer;
void _range_click_timeout();
@@ -47,6 +48,8 @@ class SpinBox : public Range {
void _text_submitted(const String &p_string);
virtual void _value_changed(double) override;
+ void _text_changed(const String &p_string);
+
String prefix;
String suffix;
@@ -88,6 +91,9 @@ public:
void set_prefix(const String &p_prefix);
String get_prefix() const;
+ void set_update_on_text_changed(bool p_update);
+ bool get_update_on_text_changed() const;
+
void apply();
SpinBox();
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 845a2ec6c7..a423dc0173 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -538,7 +538,6 @@ void TabContainer::_notification(int p_what) {
void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) {
Control *control = get_tab_control(p_index);
RID canvas = get_canvas_item();
- Ref<Font> font = get_theme_font(SNAME("font"));
Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
int outline_size = get_theme_constant(SNAME("outline_size"));
int icon_text_distance = get_theme_constant(SNAME("icon_separation"));
@@ -1134,7 +1133,6 @@ Size2 TabContainer::get_minimum_size() const {
Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected"));
Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected"));
Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled"));
- Ref<Font> font = get_theme_font(SNAME("font"));
if (tabs_visible) {
ms.y += MAX(MAX(tab_unselected->get_minimum_size().y, tab_selected->get_minimum_size().y), tab_disabled->get_minimum_size().y);
@@ -1212,6 +1210,7 @@ void TabContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon);
ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled);
ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled);
+ ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabContainer::get_tab_idx_at_point);
ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup);
ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup);
ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled);
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index b9e9a4d450..09899413f2 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -37,6 +37,7 @@
#include "core/object/script_language.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
+#include "core/string/string_builder.h"
#include "core/string/translation.h"
#include "scene/main/window.h"
@@ -78,7 +79,11 @@ void TextEdit::Text::set_font_size(int p_font_size) {
}
void TextEdit::Text::set_tab_size(int p_tab_size) {
+ if (tab_size == p_tab_size) {
+ return;
+ }
tab_size = p_tab_size;
+ tab_size_dirty = true;
}
int TextEdit::Text::get_tab_size() const {
@@ -118,10 +123,8 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const {
return text[p_line].data_buf->get_size().x;
}
-int TextEdit::Text::get_line_height(int p_line, int p_wrap_index) const {
- ERR_FAIL_INDEX_V(p_line, text.size(), 0);
-
- return text[p_line].data_buf->get_line_size(p_wrap_index).y;
+int TextEdit::Text::get_line_height() const {
+ return line_height;
}
void TextEdit::Text::set_width(float p_width) {
@@ -153,6 +156,36 @@ _FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const {
return text[p_line].data;
}
+void TextEdit::Text::_calculate_line_height() {
+ int height = 0;
+ for (int i = 0; i < text.size(); i++) {
+ // Found another line with the same height...nothing to update.
+ if (text[i].height == line_height) {
+ height = line_height;
+ break;
+ }
+ height = MAX(height, text[i].height);
+ }
+ line_height = height;
+}
+
+void TextEdit::Text::_calculate_max_line_width() {
+ int width = 0;
+ for (int i = 0; i < text.size(); i++) {
+ if (is_hidden(i)) {
+ continue;
+ }
+
+ // Found another line with the same width...nothing to update.
+ if (text[i].width == max_width) {
+ width = max_width;
+ break;
+ }
+ width = MAX(width, text[i].width);
+ }
+ max_width = width;
+}
+
void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Vector<Vector2i> &p_bidi_override) {
ERR_FAIL_INDEX(p_line, text.size());
@@ -182,17 +215,54 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_
tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size);
text.write[p_line].data_buf->tab_align(tabs);
}
+
+ // Update height.
+ const int old_height = text.write[p_line].height;
+ const int wrap_amount = get_line_wrap_amount(p_line);
+ int height = font->get_height(font_size);
+ for (int i = 0; i <= wrap_amount; i++) {
+ height = MAX(height, text[p_line].data_buf->get_line_size(i).y);
+ }
+ text.write[p_line].height = height;
+
+ // If this line has shrunk, this may no longer the the tallest line.
+ if (old_height == line_height && height < line_height) {
+ _calculate_line_height();
+ } else {
+ line_height = MAX(height, line_height);
+ }
+
+ // Update width.
+ const int old_width = text.write[p_line].width;
+ int width = get_line_width(p_line);
+ text.write[p_line].width = width;
+
+ // If this line has shrunk, this may no longer the the longest line.
+ if (old_width == max_width && width < max_width) {
+ _calculate_max_line_width();
+ } else if (!is_hidden(p_line)) {
+ max_width = MAX(width, max_width);
+ }
}
void TextEdit::Text::invalidate_all_lines() {
for (int i = 0; i < text.size(); i++) {
text.write[i].data_buf->set_width(width);
- if (tab_size > 0) {
- Vector<float> tabs;
- tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size);
- text.write[i].data_buf->tab_align(tabs);
+ if (tab_size_dirty) {
+ if (tab_size > 0) {
+ Vector<float> tabs;
+ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size);
+ text.write[i].data_buf->tab_align(tabs);
+ }
+ // Tabs have changes, force width update.
+ text.write[i].width = get_line_width(i);
}
}
+
+ if (tab_size_dirty) {
+ _calculate_max_line_width();
+ tab_size_dirty = false;
+ }
}
void TextEdit::Text::invalidate_all() {
@@ -211,16 +281,8 @@ void TextEdit::Text::clear() {
insert(0, "", Vector<Vector2i>());
}
-int TextEdit::Text::get_max_width(bool p_exclude_hidden) const {
- // Quite some work, but should be fast enough.
-
- int max = 0;
- for (int i = 0; i < text.size(); i++) {
- if (!p_exclude_hidden || !is_hidden(i)) {
- max = MAX(max, get_line_width(i));
- }
- }
- return max;
+int TextEdit::Text::get_max_width() const {
+ return max_width;
}
void TextEdit::Text::set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override) {
@@ -243,7 +305,20 @@ void TextEdit::Text::insert(int p_at, const String &p_text, const Vector<Vector2
}
void TextEdit::Text::remove(int p_at) {
+ int height = text[p_at].height;
+ int width = text[p_at].width;
+
text.remove(p_at);
+
+ // If this is the tallest line, we need to get the next tallest.
+ if (height == line_height) {
+ _calculate_line_height();
+ }
+
+ // If this is the longest line, we need to get the next longest.
+ if (width == max_width) {
+ _calculate_max_line_width();
+ }
}
void TextEdit::Text::add_gutter(int p_at) {
@@ -293,7 +368,7 @@ void TextEdit::_notification(int p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_visible()) {
call_deferred(SNAME("_update_scrollbars"));
- call_deferred(SNAME("_update_wrap_at"));
+ call_deferred(SNAME("_update_wrap_at_column"));
}
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
@@ -644,8 +719,9 @@ void TextEdit::_notification(int p_what) {
int characters = 0;
int tabs = 0;
for (int j = 0; j < str.length(); j++) {
- if (color_map.has(last_wrap_column + j)) {
- current_color = color_map[last_wrap_column + j].get("color");
+ const Variant *color_data = color_map.getptr(last_wrap_column + j);
+ if (color_data != nullptr) {
+ current_color = (color_data->operator Dictionary()).get("color", font_color);
if (!editable) {
current_color.a = font_readonly_color.a;
}
@@ -1023,8 +1099,9 @@ void TextEdit::_notification(int p_what) {
char_ofs = 0;
}
for (int j = 0; j < gl_size; j++) {
- if (color_map.has(glyphs[j].start)) {
- current_color = color_map[glyphs[j].start].get("color");
+ const Variant *color_data = color_map.getptr(glyphs[j].start);
+ if (color_data != nullptr) {
+ current_color = (color_data->operator Dictionary()).get("color", font_color);
if (!editable && current_color.a > font_readonly_color.a) {
current_color.a = font_readonly_color.a;
}
@@ -1367,7 +1444,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
_reset_caret_blink_timer();
- Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y));
+ Point2i pos = get_line_column_at_pos(mpos);
int row = pos.y;
int col = pos.x;
@@ -1470,7 +1547,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) {
_reset_caret_blink_timer();
- Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y));
+ Point2i pos = get_line_column_at_pos(mpos);
int row = pos.y;
int col = pos.x;
@@ -2533,10 +2610,10 @@ void TextEdit::set_text(const String &p_text) {
set_caret_column(0);
begin_complex_operation();
+ deselect();
_remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0));
insert_text_at_caret(p_text);
end_complex_operation();
- selection.active = false;
}
set_caret_line(0);
@@ -2548,15 +2625,15 @@ void TextEdit::set_text(const String &p_text) {
}
String TextEdit::get_text() const {
- String longthing;
- int len = text.size();
- for (int i = 0; i < len; i++) {
- longthing += text[i];
- if (i != len - 1) {
- longthing += "\n";
+ StringBuilder ret_text;
+ const int text_size = text.size();
+ for (int i = 0; i < text_size; i++) {
+ ret_text += text[i];
+ if (i != text_size - 1) {
+ ret_text += "\n";
}
}
- return longthing;
+ return ret_text.as_string();
}
int TextEdit::get_line_count() const {
@@ -2592,13 +2669,7 @@ int TextEdit::get_line_width(int p_line, int p_wrap_index) const {
}
int TextEdit::get_line_height() const {
- int height = font->get_height(font_size);
- for (int i = 0; i < text.size(); i++) {
- for (int j = 0; j <= text.get_line_wrap_amount(i); j++) {
- height = MAX(height, text.get_line_height(i, j));
- }
- }
- return height + line_spacing;
+ return text.get_line_height() + line_spacing;
}
int TextEdit::get_indent_level(int p_line) const {
@@ -2660,6 +2731,8 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) {
}
void TextEdit::insert_text_at_caret(const String &p_text) {
+ begin_complex_operation();
+
delete_selection();
int new_column, new_line;
@@ -2669,6 +2742,8 @@ void TextEdit::insert_text_at_caret(const String &p_text) {
set_caret_line(new_line, false);
set_caret_column(new_column);
update();
+
+ end_complex_operation();
}
void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) {
@@ -2953,13 +3028,20 @@ void TextEdit::menu_option(int p_option) {
/* Versioning */
void TextEdit::begin_complex_operation() {
_push_current_op();
- next_operation_is_complex = true;
+ if (complex_operation_count == 0) {
+ next_operation_is_complex = true;
+ }
+ 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;
+ }
if (undo_stack.back()->get().chain_forward) {
undo_stack.back()->get().chain_forward = false;
return;
@@ -4151,6 +4233,9 @@ TextEdit::GutterType TextEdit::get_gutter_type(int p_gutter) const {
void TextEdit::set_gutter_width(int p_gutter, int p_width) {
ERR_FAIL_INDEX(p_gutter, gutters.size());
+ if (gutters[p_gutter].width == p_width) {
+ return;
+ }
gutters.write[p_gutter].width = p_width;
_update_gutter_width();
}
@@ -4166,6 +4251,9 @@ int TextEdit::get_total_gutter_width() const {
void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) {
ERR_FAIL_INDEX(p_gutter, gutters.size());
+ if (gutters[p_gutter].draw == p_draw) {
+ return;
+ }
gutters.write[p_gutter].draw = p_draw;
_update_gutter_width();
}
@@ -5297,7 +5385,7 @@ void TextEdit::_update_selection_mode_pointer() {
dragging_selection = true;
Point2 mp = get_local_mouse_pos();
- Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y));
+ Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
int col = pos.x;
@@ -5314,7 +5402,7 @@ void TextEdit::_update_selection_mode_word() {
dragging_selection = true;
Point2 mp = get_local_mouse_pos();
- Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y));
+ Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
int col = pos.x;
@@ -5362,7 +5450,7 @@ void TextEdit::_update_selection_mode_line() {
dragging_selection = true;
Point2 mp = get_local_mouse_pos();
- Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y));
+ Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
int col = pos.x;
@@ -5458,7 +5546,7 @@ void TextEdit::_update_scrollbars() {
}
int visible_width = size.width - style_normal->get_minimum_size().width;
- int total_width = text.get_max_width(true) + vmin.x + gutters_width + gutter_padding;
+ int total_width = text.get_max_width() + vmin.x + gutters_width + gutter_padding;
if (draw_minimap) {
total_width += minimap_width;
@@ -5677,7 +5765,7 @@ void TextEdit::_update_minimap_hover() {
return;
}
- const int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y));
+ const int row = get_minimap_line_at_pos(mp);
const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line();
if (new_hovering_minimap != hovering_minimap) {
@@ -5698,7 +5786,7 @@ void TextEdit::_update_minimap_click() {
minimap_clicked = true;
dragging_minimap = true;
- int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y));
+ int row = get_minimap_line_at_pos(mp);
if (row >= get_first_visible_line() && (row < get_last_full_visible_line() || row >= (text.size() - 1))) {
minimap_scroll_ratio = v_scroll->get_as_ratio();
@@ -5910,8 +5998,6 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
text.set_hidden(p_line, false);
}
- text.invalidate_cache(p_line);
-
r_end_line = p_line + substrings.size() - 1;
r_end_column = text[r_end_line].length() - postinsert_text.length();
@@ -5968,8 +6054,6 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li
}
text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text));
- text.invalidate_cache(p_from_line);
-
if (!text_changed_dirty && !setting_text) {
if (is_inside_tree()) {
MessageQueue::get_singleton()->push_call(this, "_text_changed_emit");
@@ -5986,7 +6070,6 @@ TextEdit::TextEdit() {
set_default_cursor_shape(CURSOR_IBEAM);
text.set_tab_size(text.get_tab_size());
- text.clear();
h_scroll = memnew(HScrollBar);
v_scroll = memnew(VScrollBar);
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 07999c2442..b1226f2aff 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -144,6 +144,8 @@ private:
Color background_color = Color(0, 0, 0, 0);
bool hidden = false;
+ int height = 0;
+ int width = 0;
Line() {
data_buf.instantiate();
@@ -152,6 +154,7 @@ private:
private:
bool is_dirty = false;
+ bool tab_size_dirty = false;
mutable Vector<Line> text;
Ref<Font> font;
@@ -162,11 +165,16 @@ private:
TextServer::Direction direction = TextServer::DIRECTION_AUTO;
bool draw_control_chars = false;
+ int line_height = -1;
+ int max_width = -1;
int width = -1;
int tab_size = 4;
int gutter_count = 0;
+ void _calculate_line_height();
+ void _calculate_max_line_width();
+
public:
void set_tab_size(int p_tab_size);
int get_tab_size() const;
@@ -176,9 +184,9 @@ private:
void set_direction_and_language(TextServer::Direction p_direction, const String &p_language);
void set_draw_control_chars(bool p_draw_control_chars);
- int get_line_height(int p_line, int p_wrap_index) const;
+ int get_line_height() const;
int get_line_width(int p_line, int p_wrap_index = -1) const;
- int get_max_width(bool p_exclude_hidden = false) const;
+ int get_max_width() const;
void set_width(float p_width);
int get_line_wrap_amount(int p_line) const;
@@ -187,7 +195,14 @@ private:
const Ref<TextParagraph> get_line_data(int p_line) const;
void set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override);
- void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; }
+ void set_hidden(int p_line, bool p_hidden) {
+ text.write[p_line].hidden = p_hidden;
+ if (!p_hidden && text[p_line].width > max_width) {
+ max_width = text[p_line].width;
+ } else if (p_hidden && text[p_line].width == max_width) {
+ _calculate_max_line_width();
+ }
+ }
bool is_hidden(int p_line) const { return text[p_line].hidden; }
void insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override);
void remove(int p_at);
@@ -289,6 +304,7 @@ private:
bool undo_enabled = true;
int undo_stack_max_size = 50;
+ int complex_operation_count = 0;
bool next_operation_is_complex = false;
TextOperation current_op;
@@ -530,7 +546,6 @@ private:
protected:
void _notification(int p_what);
- virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
static void _bind_methods();
@@ -579,6 +594,7 @@ protected:
public:
/* General overrides. */
+ virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
virtual Size2 get_minimum_size() const override;
virtual bool is_text_field() const override;
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index cb990892ed..f62c09925d 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -156,6 +156,7 @@ void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) {
c.dirty = true;
c.icon_max_w = 0;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const {
@@ -169,6 +170,7 @@ void TreeItem::set_checked(int p_column, bool p_checked) {
cells.write[p_column].checked = p_checked;
cells.write[p_column].indeterminate = false;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
void TreeItem::set_indeterminate(int p_column, bool p_indeterminate) {
@@ -180,6 +182,7 @@ void TreeItem::set_indeterminate(int p_column, bool p_indeterminate) {
cells.write[p_column].indeterminate = p_indeterminate;
cells.write[p_column].checked = false;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
bool TreeItem::is_checked(int p_column) const {
@@ -212,6 +215,7 @@ void TreeItem::set_text(int p_column, String p_text) {
cells.write[p_column].step = 0;
}
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
String TreeItem::get_text(int p_column) const {
@@ -227,6 +231,7 @@ void TreeItem::set_text_direction(int p_column, Control::TextDirection p_text_di
cells.write[p_column].dirty = true;
_changed_notify(p_column);
}
+ cached_minimum_size_dirty = true;
}
Control::TextDirection TreeItem::get_text_direction(int p_column) const {
@@ -239,6 +244,7 @@ void TreeItem::clear_opentype_features(int p_column) {
cells.write[p_column].opentype_features.clear();
cells.write[p_column].dirty = true;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_value) {
@@ -248,6 +254,7 @@ void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_va
cells.write[p_column].opentype_features[tag] = p_value;
cells.write[p_column].dirty = true;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
}
@@ -266,6 +273,7 @@ void TreeItem::set_structured_text_bidi_override(int p_column, Control::Structur
cells.write[p_column].st_parser = p_parser;
cells.write[p_column].dirty = true;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
}
@@ -279,6 +287,7 @@ void TreeItem::set_structured_text_bidi_override_options(int p_column, Array p_a
cells.write[p_column].st_args = p_args;
cells.write[p_column].dirty = true;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
Array TreeItem::get_structured_text_bidi_override_options(int p_column) const {
@@ -292,6 +301,7 @@ void TreeItem::set_language(int p_column, const String &p_language) {
cells.write[p_column].language = p_language;
cells.write[p_column].dirty = true;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
}
@@ -305,6 +315,7 @@ void TreeItem::set_suffix(int p_column, String p_suffix) {
cells.write[p_column].suffix = p_suffix;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
String TreeItem::get_suffix(int p_column) const {
@@ -316,6 +327,7 @@ void TreeItem::set_icon(int p_column, const Ref<Texture2D> &p_icon) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].icon = p_icon;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
Ref<Texture2D> TreeItem::get_icon(int p_column) const {
@@ -327,6 +339,7 @@ void TreeItem::set_icon_region(int p_column, const Rect2 &p_icon_region) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].icon_region = p_icon_region;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
Rect2 TreeItem::get_icon_region(int p_column) const {
@@ -349,6 +362,7 @@ void TreeItem::set_icon_max_width(int p_column, int p_max) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].icon_max_w = p_max;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
int TreeItem::get_icon_max_width(int p_column) const {
@@ -461,6 +475,7 @@ void TreeItem::uncollapse_tree() {
void TreeItem::set_custom_minimum_height(int p_height) {
custom_min_height = p_height;
_changed_notify();
+ cached_minimum_size_dirty = true;
}
int TreeItem::get_custom_minimum_height() const {
@@ -785,6 +800,7 @@ void TreeItem::add_button(int p_column, const Ref<Texture2D> &p_button, int p_id
button.tooltip = p_tooltip;
cells.write[p_column].buttons.push_back(button);
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
int TreeItem::get_button_count(int p_column) const {
@@ -828,6 +844,7 @@ void TreeItem::set_button(int p_column, int p_idx, const Ref<Texture2D> &p_butto
ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size());
cells.write[p_column].buttons.write[p_idx].texture = p_button;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
void TreeItem::set_button_color(int p_column, int p_idx, const Color &p_color) {
@@ -843,6 +860,7 @@ void TreeItem::set_button_disabled(int p_column, int p_idx, bool p_disabled) {
cells.write[p_column].buttons.write[p_idx].disabled = p_disabled;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
bool TreeItem::is_button_disabled(int p_column, int p_idx) const {
@@ -856,6 +874,7 @@ void TreeItem::set_editable(int p_column, bool p_editable) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].editable = p_editable;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
bool TreeItem::is_editable(int p_column) {
@@ -888,6 +907,7 @@ void TreeItem::clear_custom_color(int p_column) {
void TreeItem::set_custom_font(int p_column, const Ref<Font> &p_font) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].custom_font = p_font;
+ cached_minimum_size_dirty = true;
}
Ref<Font> TreeItem::get_custom_font(int p_column) const {
@@ -898,6 +918,7 @@ Ref<Font> TreeItem::get_custom_font(int p_column) const {
void TreeItem::set_custom_font_size(int p_column, int p_font_size) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].custom_font_size = p_font_size;
+ cached_minimum_size_dirty = true;
}
int TreeItem::get_custom_font_size(int p_column) const {
@@ -941,6 +962,7 @@ Color TreeItem::get_custom_bg_color(int p_column) const {
void TreeItem::set_custom_as_button(int p_column, bool p_button) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].custom_button = p_button;
+ cached_minimum_size_dirty = true;
}
bool TreeItem::is_custom_set_as_button(int p_column) const {
@@ -952,6 +974,7 @@ void TreeItem::set_text_align(int p_column, TextAlign p_align) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].text_align = p_align;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
TreeItem::TextAlign TreeItem::get_text_align(int p_column) const {
@@ -963,6 +986,7 @@ void TreeItem::set_expand_right(int p_column, bool p_enable) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].expand_right = p_enable;
_changed_notify(p_column);
+ cached_minimum_size_dirty = true;
}
bool TreeItem::get_expand_right(int p_column) const {
@@ -973,6 +997,7 @@ bool TreeItem::get_expand_right(int p_column) const {
void TreeItem::set_disable_folding(bool p_disable) {
disable_folding = p_disable;
_changed_notify(0);
+ cached_minimum_size_dirty = true;
}
bool TreeItem::is_folding_disabled() const {
@@ -984,49 +1009,54 @@ Size2 TreeItem::get_minimum_size(int p_column) {
Tree *tree = get_tree();
ERR_FAIL_COND_V(!tree, Size2());
- Size2 size;
+ if (cached_minimum_size_dirty) {
+ Size2 size;
- // Default offset?
- //size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin;
+ // Default offset?
+ //size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin;
- // Text.
- const TreeItem::Cell &cell = cells[p_column];
- if (!cell.text.is_empty()) {
- if (cell.dirty) {
- tree->update_item_cell(this, p_column);
+ // Text.
+ const TreeItem::Cell &cell = cells[p_column];
+ if (!cell.text.is_empty()) {
+ if (cell.dirty) {
+ tree->update_item_cell(this, p_column);
+ }
+ Size2 text_size = cell.text_buf->get_size();
+ size.width += text_size.width;
+ size.height = MAX(size.height, text_size.height);
}
- Size2 text_size = cell.text_buf->get_size();
- size.width += text_size.width;
- size.height = MAX(size.height, text_size.height);
- }
- // Icon.
- if (cell.mode == CELL_MODE_CHECK) {
- size.width += tree->cache.checked->get_width() + tree->cache.hseparation;
- }
- if (cell.icon.is_valid()) {
- Size2i icon_size = cell.get_icon_size();
- if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) {
- icon_size.width = cell.icon_max_w;
+ // Icon.
+ if (cell.mode == CELL_MODE_CHECK) {
+ size.width += tree->cache.checked->get_width() + tree->cache.hseparation;
+ }
+ if (cell.icon.is_valid()) {
+ Size2i icon_size = cell.get_icon_size();
+ if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) {
+ icon_size.width = cell.icon_max_w;
+ }
+ size.width += icon_size.width + tree->cache.hseparation;
+ size.height = MAX(size.height, icon_size.height);
}
- size.width += icon_size.width + tree->cache.hseparation;
- size.height = MAX(size.height, icon_size.height);
- }
- // Buttons.
- for (int i = 0; i < cell.buttons.size(); i++) {
- Ref<Texture2D> texture = cell.buttons[i].texture;
- if (texture.is_valid()) {
- Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size();
- size.width += button_size.width;
- size.height = MAX(size.height, button_size.height);
+ // Buttons.
+ for (int i = 0; i < cell.buttons.size(); i++) {
+ Ref<Texture2D> texture = cell.buttons[i].texture;
+ if (texture.is_valid()) {
+ Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size();
+ size.width += button_size.width;
+ size.height = MAX(size.height, button_size.height);
+ }
}
- }
- if (cell.buttons.size() >= 2) {
- size.width += (cell.buttons.size() - 1) * tree->cache.button_margin;
+ if (cell.buttons.size() >= 2) {
+ size.width += (cell.buttons.size() - 1) * tree->cache.button_margin;
+ }
+
+ cached_minimum_size = size;
+ cached_minimum_size_dirty = false;
}
- return size;
+ return cached_minimum_size;
}
Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
@@ -1307,6 +1337,10 @@ void Tree::update_cache() {
cache.title_button_color = get_theme_color(SNAME("title_button_color"));
v_scroll->set_custom_step(cache.font->get_height(cache.font_size));
+
+ for (TreeItem *item = get_root(); item; item = item->get_next()) {
+ item->cached_minimum_size_dirty = true;
+ }
}
int Tree::compute_item_height(TreeItem *p_item) const {
@@ -2339,13 +2373,22 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
cache.click_type = Cache::CLICK_NONE;
return -1;
}
+
+ // Make sure the click is correct.
+ Point2 click_pos = get_global_mouse_position() - get_global_position();
+ if (!get_item_at_position(click_pos)) {
+ pressed_button = -1;
+ cache.click_type = Cache::CLICK_NONE;
+ return -1;
+ }
+
pressed_button = j;
cache.click_type = Cache::CLICK_BUTTON;
cache.click_index = j;
cache.click_id = c.buttons[j].id;
cache.click_item = p_item;
cache.click_column = col;
- cache.click_pos = get_global_mouse_position() - get_global_position();
+ cache.click_pos = click_pos;
update();
//emit_signal(SNAME("button_pressed"));
return -1;
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 8b7ddc3faf..85fed941dc 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -130,6 +130,9 @@ private:
bool disable_folding = false;
int custom_min_height = 0;
+ Size2i cached_minimum_size;
+ bool cached_minimum_size_dirty = true;
+
TreeItem *parent = nullptr; // parent item
TreeItem *prev = nullptr; // previous in list
TreeItem *next = nullptr; // next in list