summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/check_box.cpp18
-rw-r--r--scene/gui/code_edit.cpp50
-rw-r--r--scene/gui/code_edit.h2
-rw-r--r--scene/gui/control.cpp29
-rw-r--r--scene/gui/control.h3
-rw-r--r--scene/gui/label.cpp77
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/link_button.cpp16
-rw-r--r--scene/gui/link_button.h1
-rw-r--r--scene/gui/option_button.h4
-rw-r--r--scene/gui/popup_menu.h4
-rw-r--r--scene/gui/rich_text_label.cpp24
-rw-r--r--scene/gui/rich_text_label.h6
-rw-r--r--scene/gui/scroll_bar.cpp26
-rw-r--r--scene/gui/scroll_bar.h3
-rw-r--r--scene/gui/tab_container.cpp1
-rw-r--r--scene/gui/text_edit.cpp205
-rw-r--r--scene/gui/text_edit.h25
-rw-r--r--scene/gui/tree.cpp11
19 files changed, 411 insertions, 96 deletions
diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp
index d93107df2d..411fb2e1f0 100644
--- a/scene/gui/check_box.cpp
+++ b/scene/gui/check_box.cpp
@@ -34,11 +34,13 @@
Size2 CheckBox::get_icon_size() const {
Ref<Texture2D> checked = Control::get_theme_icon(SNAME("checked"));
- Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled"));
Ref<Texture2D> unchecked = Control::get_theme_icon(SNAME("unchecked"));
- Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled"));
Ref<Texture2D> radio_checked = Control::get_theme_icon(SNAME("radio_checked"));
Ref<Texture2D> radio_unchecked = Control::get_theme_icon(SNAME("radio_unchecked"));
+ Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled"));
+ Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled"));
+ Ref<Texture2D> radio_checked_disabled = Control::get_theme_icon(SNAME("radio_checked_disabled"));
+ Ref<Texture2D> radio_unchecked_disabled = Control::get_theme_icon(SNAME("radio_unchecked_disabled"));
Size2 tex_size = Size2(0, 0);
if (!checked.is_null()) {
@@ -53,6 +55,18 @@ Size2 CheckBox::get_icon_size() const {
if (!radio_unchecked.is_null()) {
tex_size = Size2(MAX(tex_size.width, radio_unchecked->get_width()), MAX(tex_size.height, radio_unchecked->get_height()));
}
+ if (!checked_disabled.is_null()) {
+ tex_size = Size2(MAX(tex_size.width, checked_disabled->get_width()), MAX(tex_size.height, checked_disabled->get_height()));
+ }
+ if (!unchecked_disabled.is_null()) {
+ tex_size = Size2(MAX(tex_size.width, unchecked_disabled->get_width()), MAX(tex_size.height, unchecked_disabled->get_height()));
+ }
+ if (!radio_checked_disabled.is_null()) {
+ tex_size = Size2(MAX(tex_size.width, radio_checked_disabled->get_width()), MAX(tex_size.height, radio_checked_disabled->get_height()));
+ }
+ if (!radio_unchecked_disabled.is_null()) {
+ tex_size = Size2(MAX(tex_size.width, radio_unchecked_disabled->get_width()), MAX(tex_size.height, radio_unchecked_disabled->get_height()));
+ }
return tex_size;
}
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index ca884f56e4..3beff57027 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -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;
}
}
@@ -1937,7 +1958,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);
@@ -2548,7 +2569,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 +2637,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();
@@ -2744,7 +2772,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]);
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 4fbb5194e6..740548d559 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -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/control.cpp b/scene/gui/control.cpp
index 4ac6a58d36..81411d5844 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -30,6 +30,7 @@
#include "control.h"
+#include "container.h"
#include "core/config/project_settings.h"
#include "core/math/geometry_2d.h"
#include "core/object/message_queue.h"
@@ -168,6 +169,20 @@ Size2 Control::_edit_get_minimum_size() const {
}
#endif
+String Control::properties_managed_by_container[] = {
+ "offset_left",
+ "offset_top",
+ "offset_right",
+ "offset_bottom",
+ "anchor_left",
+ "anchor_top",
+ "anchor_right",
+ "anchor_bottom",
+ "rect_position",
+ "rect_scale",
+ "rect_size"
+};
+
void Control::accept_event() {
if (is_inside_tree()) {
get_viewport()->_gui_accept_event();
@@ -442,6 +457,20 @@ void Control::_validate_property(PropertyInfo &property) const {
property.hint_string = hint_string;
}
+ if (!Object::cast_to<Container>(get_parent())) {
+ return;
+ }
+ // Disable the property if it's managed by the parent container.
+ bool property_is_managed_by_container = false;
+ for (unsigned i = 0; i < properties_managed_by_container_count; i++) {
+ property_is_managed_by_container = properties_managed_by_container[i] == property.name;
+ if (property_is_managed_by_container) {
+ break;
+ }
+ }
+ if (property_is_managed_by_container) {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
}
Control *Control::get_parent_control() const {
diff --git a/scene/gui/control.h b/scene/gui/control.h
index 0faa617f8d..9cec5d6e8d 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -230,6 +230,9 @@ private:
} data;
+ static constexpr unsigned properties_managed_by_container_count = 11;
+ static String properties_managed_by_container[properties_managed_by_container_count];
+
// used internally
Control *_find_control_at_pos(CanvasItem *p_node, const Point2 &p_pos, const Transform2D &p_xform, Transform2D &r_inv_xform);
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 3f87003423..5600816b2d 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -227,7 +227,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 +248,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);
}
}
@@ -385,12 +390,70 @@ 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++) {
+ 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) {
+ 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;
+ glyhps_drawn++;
+ }
+ }
+ // 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;
}
}
@@ -421,7 +484,7 @@ 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++;
}
@@ -431,7 +494,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;
}
}
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 3605842224..d9acbeb828 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -674,7 +674,7 @@ void LineEdit::_notification(int p_what) {
int y_ofs = style->get_offset().y + (y_area - text_height) / 2;
Color selection_color = get_theme_color(SNAME("selection_color"));
- Color font_color = is_editable() ? get_theme_color(SNAME("font_color")) : get_theme_color(SNAME("font_uneditable_color"));
+ Color font_color = get_theme_color(is_editable() ? SNAME("font_color") : SNAME("font_uneditable_color"));
Color font_selected_color = get_theme_color(SNAME("font_selected_color"));
Color caret_color = get_theme_color(SNAME("caret_color"));
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/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_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_label.cpp b/scene/gui/rich_text_label.cpp
index fbc67d8a24..aeadfd78ee 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -3946,24 +3946,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);
- }
-
+void RichTextLabel::set_effects(Array p_effects) {
+ custom_effects = p_effects;
if ((bbcode != "") && use_bbcode) {
parse_bbcode(bbcode);
}
}
-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) {
@@ -4279,12 +4270,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..f25a8bf193 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -363,7 +363,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);
@@ -577,8 +577,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..4edf373fbf 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;
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/tab_container.cpp b/scene/gui/tab_container.cpp
index 845a2ec6c7..137ce7e96f 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -1212,6 +1212,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 e5ba12ec8d..6bcbedfceb 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:
@@ -557,13 +632,25 @@ void TextEdit::_notification(int p_what) {
}
int minimap_draw_amount = minimap_visible_lines + get_line_wrap_count(minimap_line + 1);
- // draw the minimap
- Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, 0.1) : Color(0, 0, 0, 0.1);
+ // Draw the minimap.
+
+ // Add visual feedback when dragging or hovering the the visible area rectangle.
+ float viewport_alpha;
+ if (dragging_minimap) {
+ viewport_alpha = 0.25;
+ } else if (hovering_minimap) {
+ viewport_alpha = 0.175;
+ } else {
+ viewport_alpha = 0.1;
+ }
+
+ const Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha);
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color);
} else {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, minimap_width, viewport_height), viewport_color);
}
+
for (int i = 0; i < minimap_draw_amount; i++) {
minimap_line++;
@@ -632,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;
}
@@ -1011,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;
}
@@ -1549,6 +1638,10 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
+ if (draw_minimap && !dragging_selection) {
+ _update_minimap_hover();
+ }
+
if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
accept_event(); // Accept event if scroll changed.
}
@@ -2532,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 {
@@ -2576,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 {
@@ -4135,6 +4222,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();
}
@@ -4150,6 +4240,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();
}
@@ -5442,7 +5535,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;
@@ -5644,6 +5737,33 @@ void TextEdit::_scroll_lines_down() {
}
// Minimap
+
+void TextEdit::_update_minimap_hover() {
+ const Point2 mp = get_local_mouse_pos();
+ const int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT);
+
+ const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end;
+ if (!hovering_sidebar) {
+ if (hovering_minimap) {
+ // Only redraw if the hovering status changed.
+ hovering_minimap = false;
+ update();
+ }
+
+ // Return early to avoid running the operations below when not needed.
+ return;
+ }
+
+ const int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y));
+
+ const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line();
+ if (new_hovering_minimap != hovering_minimap) {
+ // Only redraw if the hovering status changed.
+ hovering_minimap = new_hovering_minimap;
+ update();
+ }
+}
+
void TextEdit::_update_minimap_click() {
Point2 mp = get_local_mouse_pos();
@@ -5867,8 +5987,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();
@@ -5925,8 +6043,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");
@@ -5943,7 +6059,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 ced03e19d0..525464c302 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);
@@ -446,12 +461,14 @@ private:
// minimap scroll
bool minimap_clicked = false;
+ bool hovering_minimap = false;
bool dragging_minimap = false;
bool can_drag_minimap = false;
double minimap_scroll_ratio = 0.0;
double minimap_scroll_click_pos = 0.0;
+ void _update_minimap_hover();
void _update_minimap_click();
void _update_minimap_drag();
@@ -528,7 +545,6 @@ private:
protected:
void _notification(int p_what);
- virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
static void _bind_methods();
@@ -577,6 +593,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..c4dfbc0d4e 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -2339,13 +2339,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;