summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/base_button.cpp20
-rw-r--r--scene/gui/base_button.h2
-rw-r--r--scene/gui/box_container.cpp2
-rw-r--r--scene/gui/button.cpp13
-rw-r--r--scene/gui/code_edit.cpp23
-rw-r--r--scene/gui/code_edit.h6
-rw-r--r--scene/gui/control.cpp69
-rw-r--r--scene/gui/control.h16
-rw-r--r--scene/gui/file_dialog.cpp2
-rw-r--r--scene/gui/flow_container.cpp2
-rw-r--r--scene/gui/graph_edit.cpp251
-rw-r--r--scene/gui/graph_edit.h33
-rw-r--r--scene/gui/graph_node.cpp117
-rw-r--r--scene/gui/graph_node.h18
-rw-r--r--scene/gui/grid_container.cpp84
-rw-r--r--scene/gui/line_edit.cpp38
-rw-r--r--scene/gui/line_edit.h3
-rw-r--r--scene/gui/menu_button.cpp2
-rw-r--r--scene/gui/option_button.cpp2
-rw-r--r--scene/gui/popup.cpp16
-rw-r--r--scene/gui/popup.h4
-rw-r--r--scene/gui/popup_menu.h2
-rw-r--r--scene/gui/progress_bar.cpp61
-rw-r--r--scene/gui/progress_bar.h16
-rw-r--r--scene/gui/range.cpp8
-rw-r--r--scene/gui/range.h2
-rw-r--r--scene/gui/rich_text_label.cpp890
-rw-r--r--scene/gui/rich_text_label.h50
-rw-r--r--scene/gui/tab_bar.cpp1
-rw-r--r--scene/gui/text_edit.cpp63
-rw-r--r--scene/gui/text_edit.h7
-rw-r--r--scene/gui/texture_progress_bar.cpp4
-rw-r--r--scene/gui/tree.cpp211
-rw-r--r--scene/gui/tree.h10
-rw-r--r--scene/gui/video_stream_player.cpp2
35 files changed, 1398 insertions, 652 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index 789c01adf3..776623f7ce 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -43,12 +43,12 @@ void BaseButton::_unpress_group() {
status.pressed = true;
}
- for (Set<BaseButton *>::Element *E = button_group->buttons.front(); E; E = E->next()) {
- if (E->get() == this) {
+ for (BaseButton *E : button_group->buttons) {
+ if (E == this) {
continue;
}
- E->get()->set_pressed(false);
+ E->set_pressed(false);
}
}
@@ -485,24 +485,24 @@ BaseButton::~BaseButton() {
}
void ButtonGroup::get_buttons(List<BaseButton *> *r_buttons) {
- for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) {
- r_buttons->push_back(E->get());
+ for (BaseButton *E : buttons) {
+ r_buttons->push_back(E);
}
}
Array ButtonGroup::_get_buttons() {
Array btns;
- for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) {
- btns.push_back(E->get());
+ for (const BaseButton *E : buttons) {
+ btns.push_back(E);
}
return btns;
}
BaseButton *ButtonGroup::get_pressed_button() {
- for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) {
- if (E->get()->is_pressed()) {
- return E->get();
+ for (BaseButton *E : buttons) {
+ if (E->is_pressed()) {
+ return E;
}
}
diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h
index f4f9b88868..ba3852ec98 100644
--- a/scene/gui/base_button.h
+++ b/scene/gui/base_button.h
@@ -143,7 +143,7 @@ VARIANT_ENUM_CAST(BaseButton::ActionMode)
class ButtonGroup : public Resource {
GDCLASS(ButtonGroup, Resource);
friend class BaseButton;
- Set<BaseButton *> buttons;
+ HashSet<BaseButton *> buttons;
protected:
static void _bind_methods();
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index 251648da69..df695feba8 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -52,7 +52,7 @@ void BoxContainer::_resort() {
int stretch_min = 0;
int stretch_avail = 0;
float stretch_ratio_total = 0.0;
- Map<Control *, _MinSizeCache> min_size_cache;
+ HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index b7c1e674dd..c54897035a 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -60,11 +60,11 @@ Size2 Button::get_minimum_size() const {
}
}
}
-
- Ref<Font> font = get_theme_font(SNAME("font"));
- float font_height = font->get_height(get_theme_font_size(SNAME("font_size")));
-
- minsize.height = MAX(font_height, minsize.height);
+ if (!xl_text.is_empty()) {
+ Ref<Font> font = get_theme_font(SNAME("font"));
+ float font_height = font->get_height(get_theme_font_size(SNAME("font_size")));
+ minsize.height = MAX(font_height, minsize.height);
+ }
return get_theme_stylebox(SNAME("normal"))->get_minimum_size() + minsize;
}
@@ -258,7 +258,8 @@ void Button::_notification(int p_what) {
if (expand_icon) {
Size2 _size = get_size() - style->get_offset() * 2;
- _size.width -= get_theme_constant(SNAME("h_separation")) + icon_ofs_region;
+ int icon_text_separation = text.is_empty() ? 0 : get_theme_constant(SNAME("h_separation"));
+ _size.width -= icon_text_separation + icon_ofs_region;
if (!clip_text && icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER) {
_size.width -= text_buf->get_size().width;
}
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 05bb30f7e0..22e9763929 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -357,6 +357,11 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
Ref<InputEventKey> k = p_gui_input;
+ if (TextEdit::alt_input(p_gui_input)) {
+ accept_event();
+ return;
+ }
+
bool update_code_completion = false;
if (!k.is_valid()) {
TextEdit::gui_input(p_gui_input);
@@ -735,8 +740,8 @@ void CodeEdit::set_auto_indent_prefixes(const TypedArray<String> &p_prefixes) {
TypedArray<String> CodeEdit::get_auto_indent_prefixes() const {
TypedArray<String> prefixes;
- for (const Set<char32_t>::Element *E = auto_indent_prefixes.front(); E; E = E->next()) {
- prefixes.push_back(String::chr(E->get()));
+ for (const char32_t &E : auto_indent_prefixes) {
+ prefixes.push_back(String::chr(E));
}
return prefixes;
}
@@ -1623,7 +1628,7 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const {
start_position.y = -1;
start_position.x = -1;
- bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value()) != -1;
+ bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->get()) != -1;
/* Check the keys for this line. */
for (const KeyValue<int, int> &E : delimiter_cache[p_line]) {
@@ -1747,8 +1752,8 @@ void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes
TypedArray<String> CodeEdit::get_code_completion_prefixes() const {
TypedArray<String> prefixes;
- for (const Set<char32_t>::Element *E = code_completion_prefixes.front(); E; E = E->next()) {
- prefixes.push_back(String::chr(E->get()));
+ for (const char32_t &E : code_completion_prefixes) {
+ prefixes.push_back(String::chr(E));
}
return prefixes;
}
@@ -2397,7 +2402,7 @@ void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) {
}
} else {
for (int i = start_line; i < end_line; i++) {
- delimiter_cache.insert(i, Map<int, int>());
+ delimiter_cache.insert(i, RBMap<int, int>());
}
}
}
@@ -2534,7 +2539,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c
int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value();
bool in_region = region != -1 && delimiters[region].type == p_type;
- for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) {
+ for (RBMap<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) {
/* If column is specified, loop until the key is larger then the column. */
if (p_column != -1) {
if (E->key() > p_column) {
@@ -3036,7 +3041,9 @@ void CodeEdit::_text_changed() {
lc = get_line_count();
List<int> breakpoints;
- breakpointed_lines.get_key_list(&breakpoints);
+ for (const KeyValue<int, bool> &E : breakpointed_lines) {
+ breakpoints.push_back(E.key);
+ }
for (const int &line : breakpoints) {
if (line < lines_edited_from || (line < lc && is_line_breakpointed(line))) {
continue;
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 135dd32780..ccf046c612 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -58,7 +58,7 @@ private:
String indent_text = "\t";
bool auto_indent = false;
- Set<char32_t> auto_indent_prefixes;
+ HashSet<char32_t> auto_indent_prefixes;
bool indent_using_spaces = false;
int _calculate_spaces_till_next_left_indent(int p_column) const;
@@ -176,7 +176,7 @@ private:
* ]
* ]
*/
- Vector<Map<int, int>> delimiter_cache;
+ Vector<RBMap<int, int>> delimiter_cache;
void _update_delimiter_cache(int p_from_line = 0, int p_to_line = -1);
int _is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const;
@@ -214,7 +214,7 @@ private:
int code_completion_longest_line = 0;
Rect2i code_completion_rect;
- Set<char32_t> code_completion_prefixes;
+ HashSet<char32_t> code_completion_prefixes;
List<ScriptLanguage::CodeCompletionOption> code_completion_option_submitted;
List<ScriptLanguage::CodeCompletionOption> code_completion_option_sources;
String code_completion_base;
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index dddeec5610..db78b4adb6 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -471,6 +471,13 @@ void Control::_validate_property(PropertyInfo &property) const {
property.hint_string = hint_string;
}
+ if (property.name == "mouse_force_pass_scroll_events") {
+ // Disable force pass if the control is not stopping the event.
+ if (data.mouse_filter != MOUSE_FILTER_STOP) {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+ }
+
// Validate which positioning properties should be displayed depending on the parent and the layout mode.
Node *parent_node = get_parent_control();
if (!parent_node) {
@@ -712,35 +719,25 @@ void Control::_notification(int p_notification) {
data.parent_window = Object::cast_to<Window>(get_parent());
data.is_rtl_dirty = true;
- Node *parent = this; //meh
+ CanvasItem *node = this;
Control *parent_control = nullptr;
- bool subwindow = false;
-
- while (parent) {
- parent = parent->get_parent();
+ while (!node->is_set_as_top_level()) {
+ CanvasItem *parent = Object::cast_to<CanvasItem>(node->get_parent());
if (!parent) {
break;
}
- CanvasItem *ci = Object::cast_to<CanvasItem>(parent);
- if (ci && ci->is_set_as_top_level()) {
- subwindow = true;
- break;
- }
-
parent_control = Object::cast_to<Control>(parent);
-
if (parent_control) {
break;
- } else if (ci) {
- } else {
- break;
}
+
+ node = parent;
}
- if (parent_control && !subwindow) {
- //do nothing, has a parent control and not top_level
+ if (parent_control) {
+ // Do nothing, has a parent control.
if (data.theme.is_null() && parent_control->data.theme_owner) {
data.theme_owner = parent_control->data.theme_owner;
notification(NOTIFICATION_THEME_CHANGED);
@@ -839,6 +836,7 @@ void Control::_notification(int p_notification) {
}
} else {
data.minimum_size_valid = false;
+ _update_minimum_size();
_size_changed();
}
} break;
@@ -1590,22 +1588,22 @@ void Control::set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos,
void Control::_set_anchors_layout_preset(int p_preset) {
bool list_changed = false;
- if (has_meta("_edit_layout_mode") && (int)get_meta("_edit_layout_mode") != (int)LayoutMode::LAYOUT_MODE_ANCHORS) {
+ if (get_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS).operator int() != LayoutMode::LAYOUT_MODE_ANCHORS) {
list_changed = true;
- set_meta("_edit_layout_mode", (int)LayoutMode::LAYOUT_MODE_ANCHORS);
+ set_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS);
}
if (p_preset == -1) {
- if (!has_meta("_edit_use_custom_anchors") || !(bool)get_meta("_edit_use_custom_anchors")) {
+ if (!get_meta("_edit_use_custom_anchors", false)) {
set_meta("_edit_use_custom_anchors", true);
notify_property_list_changed();
}
return; // Keep settings as is.
}
- if (!has_meta("_edit_use_custom_anchors") || (bool)get_meta("_edit_use_custom_anchors")) {
+ if (get_meta("_edit_use_custom_anchors", true)) {
list_changed = true;
- set_meta("_edit_use_custom_anchors", false);
+ remove_meta("_edit_use_custom_anchors");
}
LayoutPreset preset = (LayoutPreset)p_preset;
@@ -2931,6 +2929,7 @@ int Control::get_v_size_flags() const {
void Control::set_mouse_filter(MouseFilter p_filter) {
ERR_FAIL_INDEX(p_filter, 3);
data.mouse_filter = p_filter;
+ notify_property_list_changed();
update_configuration_warnings();
}
@@ -2938,6 +2937,14 @@ Control::MouseFilter Control::get_mouse_filter() const {
return data.mouse_filter;
}
+void Control::set_force_pass_scroll_events(bool p_force_pass_scroll_events) {
+ data.force_pass_scroll_events = p_force_pass_scroll_events;
+}
+
+bool Control::is_force_pass_scroll_events() const {
+ return data.force_pass_scroll_events;
+}
+
void Control::warp_mouse(const Point2 &p_position) {
ERR_FAIL_COND(!is_inside_tree());
get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position));
@@ -3250,6 +3257,9 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mouse_filter", "filter"), &Control::set_mouse_filter);
ClassDB::bind_method(D_METHOD("get_mouse_filter"), &Control::get_mouse_filter);
+ ClassDB::bind_method(D_METHOD("set_force_pass_scroll_events", "force_pass_scroll_events"), &Control::set_force_pass_scroll_events);
+ ClassDB::bind_method(D_METHOD("is_force_pass_scroll_events"), &Control::is_force_pass_scroll_events);
+
ClassDB::bind_method(D_METHOD("set_clip_contents", "enable"), &Control::set_clip_contents);
ClassDB::bind_method(D_METHOD("is_clipping_contents"), &Control::is_clipping_contents);
@@ -3292,19 +3302,19 @@ void Control::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_BOTTOM);
ADD_SUBGROUP_INDENT("Anchor Offsets", "offset_", 1);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_LEFT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_top", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_TOP);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_right", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_RIGHT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_bottom", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_BOTTOM);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_LEFT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_top", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_TOP);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_right", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_RIGHT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_bottom", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_BOTTOM);
ADD_SUBGROUP_INDENT("Grow Direction", "grow_", 1);
ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Left,Right,Both"), "set_h_grow_direction", "get_h_grow_direction");
ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Top,Bottom,Both"), "set_v_grow_direction", "get_v_grow_direction");
ADD_SUBGROUP("Transform", "");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_size", "get_size");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "_set_size", "get_size");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "_set_position", "get_position");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale"), "set_scale", "get_scale");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "pivot_offset"), "set_pivot_offset", "get_pivot_offset");
@@ -3331,6 +3341,7 @@ void Control::_bind_methods() {
ADD_GROUP("Mouse", "mouse_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape");
ADD_GROUP("Theme", "theme_");
diff --git a/scene/gui/control.h b/scene/gui/control.h
index 65b71d74f8..f18dd99bff 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -190,6 +190,7 @@ private:
Point2 custom_minimum_size;
MouseFilter mouse_filter = MOUSE_FILTER_STOP;
+ bool force_pass_scroll_events = true;
bool clip_contents = false;
@@ -216,12 +217,12 @@ private:
NodePath focus_prev;
bool bulk_theme_override = false;
- HashMap<StringName, Ref<Texture2D>> icon_override;
- HashMap<StringName, Ref<StyleBox>> style_override;
- HashMap<StringName, Ref<Font>> font_override;
- HashMap<StringName, int> font_size_override;
- HashMap<StringName, Color> color_override;
- HashMap<StringName, int> constant_override;
+ Theme::ThemeIconMap icon_override;
+ Theme::ThemeStyleMap style_override;
+ Theme::ThemeFontMap font_override;
+ Theme::ThemeFontSizeMap font_size_override;
+ Theme::ThemeColorMap color_override;
+ Theme::ThemeConstantMap constant_override;
} data;
@@ -468,6 +469,9 @@ public:
void set_mouse_filter(MouseFilter p_filter);
MouseFilter get_mouse_filter() const;
+ void set_force_pass_scroll_events(bool p_force_pass_scroll_events);
+ bool is_force_pass_scroll_events() const;
+
/* SKINNING */
void begin_bulk_theme_override();
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 6da5340ca4..1725816c31 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -915,7 +915,7 @@ void FileDialog::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_overrides_title"), "set_mode_overrides_title", "is_mode_overriding_title");
ADD_PROPERTY(PropertyInfo(Variant::INT, "file_mode", PROPERTY_HINT_ENUM, "Open File,Open Files,Open Folder,Open Any,Save"), "set_file_mode", "get_file_mode");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User data,File system"), "set_access", "get_access");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User Data,File System"), "set_access", "get_access");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir");
diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp
index 1e5863b845..30b694da76 100644
--- a/scene/gui/flow_container.cpp
+++ b/scene/gui/flow_container.cpp
@@ -49,7 +49,7 @@ void FlowContainer::_resort() {
bool rtl = is_layout_rtl();
- Map<Control *, Size2i> children_minsize_cache;
+ HashMap<Control *, Size2i> children_minsize_cache;
Vector<_LineData> lines_data;
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index f2b724fa39..8ad55fc6ef 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -426,8 +426,8 @@ void GraphEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
- port_grab_distance_horizontal = get_theme_constant(SNAME("port_grab_distance_horizontal"));
- port_grab_distance_vertical = get_theme_constant(SNAME("port_grab_distance_vertical"));
+ port_hotzone_inner_extent = get_theme_constant("port_hotzone_inner_extent");
+ port_hotzone_outer_extent = get_theme_constant("port_hotzone_outer_extent");
zoom_minus->set_icon(get_theme_icon(SNAME("minus")));
zoom_reset->set_icon(get_theme_icon(SNAME("reset")));
@@ -508,8 +508,9 @@ void GraphEdit::_notification(int p_what) {
void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes) {
Rect2 comment_node_rect = p_node->get_rect();
- Vector<GraphNode *> enclosed_nodes;
+ comment_node_rect.size *= zoom;
+ Vector<GraphNode *> enclosed_nodes;
for (int i = 0; i < get_child_count(); i++) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
if (!gn || gn->is_selected()) {
@@ -517,13 +518,15 @@ void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<S
}
Rect2 node_rect = gn->get_rect();
+ node_rect.size *= zoom;
+
bool included = comment_node_rect.encloses(node_rect);
if (included) {
enclosed_nodes.push_back(gn);
}
}
- p_comment_enclosed_nodes.set(p_node->get_name(), enclosed_nodes);
+ p_comment_enclosed_nodes.insert(p_node->get_name(), enclosed_nodes);
}
void GraphEdit::_set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag) {
@@ -544,8 +547,7 @@ void GraphEdit::_set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashM
}
bool GraphEdit::_filter_input(const Point2 &p_point) {
- Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
- Vector2i port_size = Vector2i(port->get_width(), port->get_height());
+ Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
@@ -553,14 +555,18 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
continue;
}
- for (int j = 0; j < gn->get_connection_output_count(); j++) {
- if (is_in_output_hotzone(gn, j, p_point / zoom, port_size)) {
+ for (int j = 0; j < gn->get_connection_input_count(); j++) {
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_input_height(j));
+ if (is_in_input_hotzone(gn, j, p_point / zoom, port_size)) {
return true;
}
}
- for (int j = 0; j < gn->get_connection_input_count(); j++) {
- if (is_in_input_hotzone(gn, j, p_point / zoom, port_size)) {
+ for (int j = 0; j < gn->get_connection_output_count(); j++) {
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_output_height(j));
+ if (is_in_output_hotzone(gn, j, p_point / zoom, port_size)) {
return true;
}
}
@@ -572,8 +578,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
- Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
- Vector2i port_size = Vector2i(port->get_width(), port->get_height());
+ Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
connecting_valid = false;
click_pos = mb->get_position() / zoom;
@@ -585,6 +590,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
for (int j = 0; j < gn->get_connection_output_count(); j++) {
Vector2 pos = gn->get_connection_output_position(j) + gn->get_position();
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_output_height(j));
+
if (is_in_output_hotzone(gn, j, click_pos, port_size)) {
if (valid_left_disconnect_types.has(gn->get_connection_output_type(j))) {
//check disconnect
@@ -629,6 +637,10 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
for (int j = 0; j < gn->get_connection_input_count(); j++) {
Vector2 pos = gn->get_connection_input_position(j) + gn->get_position();
+
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_input_height(j));
+
if (is_in_input_hotzone(gn, j, click_pos, port_size)) {
if (right_disconnects || valid_right_disconnect_types.has(gn->get_connection_input_type(j))) {
//check disconnect
@@ -682,11 +694,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0;
if (connecting_valid) {
- Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
- Vector2i port_size = Vector2i(port->get_width(), port->get_height());
-
Vector2 mpos = mm->get_position() / zoom;
for (int i = get_child_count() - 1; i >= 0; i--) {
+ Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
if (!gn) {
continue;
@@ -695,6 +705,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
if (!connecting_out) {
for (int j = 0; j < gn->get_connection_output_count(); j++) {
Vector2 pos = gn->get_connection_output_position(j) + gn->get_position();
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_output_height(j));
+
int type = gn->get_connection_output_type(j);
if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_output_hotzone(gn, j, mpos, port_size)) {
connecting_target = true;
@@ -707,6 +720,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
} else {
for (int j = 0; j < gn->get_connection_input_count(); j++) {
Vector2 pos = gn->get_connection_input_position(j) + gn->get_position();
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_input_height(j));
+
int type = gn->get_connection_input_type(j);
if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_input_hotzone(gn, j, mpos, port_size)) {
connecting_target = true;
@@ -754,19 +770,24 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
}
}
-bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &pos) {
- if (p_control->is_set_as_top_level() || !p_control->is_visible()) {
+bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &mpos, const Vector2 &p_offset) {
+ if (p_control->is_set_as_top_level() || !p_control->is_visible() || !p_control->is_inside_tree()) {
return false;
}
- if (!p_control->has_point(pos) || p_control->get_mouse_filter() == MOUSE_FILTER_IGNORE) {
- //test children
+ Rect2 control_rect = p_control->get_rect();
+ control_rect.size *= zoom;
+ control_rect.position *= zoom;
+ control_rect.position += p_offset;
+
+ if (!control_rect.has_point(mpos) || p_control->get_mouse_filter() == MOUSE_FILTER_IGNORE) {
+ // Test children.
for (int i = 0; i < p_control->get_child_count(); i++) {
- Control *subchild = Object::cast_to<Control>(p_control->get_child(i));
- if (!subchild) {
+ Control *child_rect = Object::cast_to<Control>(p_control->get_child(i));
+ if (!child_rect) {
continue;
}
- if (_check_clickable_control(subchild, pos - subchild->get_position())) {
+ if (_check_clickable_control(child_rect, mpos, control_rect.position)) {
return true;
}
}
@@ -798,7 +819,13 @@ bool GraphEdit::is_in_output_hotzone(GraphNode *p_graph_node, int p_slot_index,
}
bool GraphEdit::is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left) {
- if (!Rect2(pos.x - port_grab_distance_horizontal, pos.y - port_grab_distance_vertical, port_grab_distance_horizontal * 2, port_grab_distance_vertical * 2).has_point(p_mouse_pos)) {
+ Rect2 hotzone = Rect2(
+ pos.x - (p_left ? port_hotzone_outer_extent : port_hotzone_inner_extent),
+ pos.y - p_port_size.height / 2.0,
+ port_hotzone_inner_extent + port_hotzone_outer_extent,
+ p_port_size.height);
+
+ if (!hotzone.has_point(p_mouse_pos)) {
return false;
}
@@ -807,23 +834,17 @@ bool GraphEdit::is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_po
if (!child) {
continue;
}
- Rect2 rect = child->get_rect();
-
- // To prevent intersections with other nodes.
- rect.position *= zoom;
- rect.size *= zoom;
-
- if (rect.has_point(p_mouse_pos)) {
- //check sub-controls
- Vector2 subpos = p_mouse_pos - rect.position;
+ Rect2 child_rect = child->get_rect();
+ child_rect.size *= zoom;
+ if (child_rect.has_point(p_mouse_pos * zoom)) {
for (int j = 0; j < child->get_child_count(); j++) {
Control *subchild = Object::cast_to<Control>(child->get_child(j));
if (!subchild) {
continue;
}
- if (_check_clickable_control(subchild, subpos - subchild->get_position())) {
+ if (_check_clickable_control(subchild, p_mouse_pos * zoom, child_rect.position)) {
return false;
}
}
@@ -839,13 +860,23 @@ PackedVector2Array GraphEdit::get_connection_line(const Vector2 &p_from, const V
return ret;
}
+ float x_diff = (p_to.x - p_from.x);
+ float cp_offset = x_diff * lines_curvature;
+ if (x_diff < 0) {
+ cp_offset *= -1;
+ }
+
Curve2D curve;
- Vector<Color> colors;
curve.add_point(p_from);
- curve.set_point_out(0, Vector2(60, 0));
+ curve.set_point_out(0, Vector2(cp_offset, 0));
curve.add_point(p_to);
- curve.set_point_in(1, Vector2(-60, 0));
- return curve.tessellate();
+ curve.set_point_in(1, Vector2(-cp_offset, 0));
+
+ if (lines_curvature > 0) {
+ return curve.tessellate(5, 2.0);
+ } else {
+ return curve.tessellate(1);
+ }
}
void GraphEdit::_draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom) {
@@ -1343,7 +1374,19 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
emit_signal(SNAME("paste_nodes_request"));
accept_event();
} else if (p_ev->is_action("ui_graph_delete")) {
- emit_signal(SNAME("delete_nodes_request"));
+ TypedArray<StringName> nodes;
+
+ for (int i = 0; i < get_child_count(); i++) {
+ GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
+ if (!gn) {
+ continue;
+ }
+ if (gn->is_selected() && gn->is_close_button_visible()) {
+ nodes.push_back(gn->get_name());
+ }
+ }
+
+ emit_signal(SNAME("delete_nodes_request"), nodes);
accept_event();
}
}
@@ -1654,6 +1697,15 @@ void GraphEdit::_minimap_toggled() {
}
}
+void GraphEdit::set_connection_lines_curvature(float p_curvature) {
+ lines_curvature = p_curvature;
+ update();
+}
+
+float GraphEdit::get_connection_lines_curvature() const {
+ return lines_curvature;
+}
+
void GraphEdit::set_connection_lines_thickness(float p_thickness) {
lines_thickness = p_thickness;
update();
@@ -1684,11 +1736,11 @@ void GraphEdit::set_warped_panning(bool p_warped) {
warped_panning = p_warped;
}
-int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v) {
+int GraphEdit::_set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v) {
switch (p_operation) {
case GraphEdit::IS_EQUAL: {
- for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) {
- if (!r_v.has(E->get())) {
+ for (const StringName &E : r_u) {
+ if (!r_v.has(E)) {
return 0;
}
}
@@ -1698,25 +1750,28 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u,
if (r_u.size() == r_v.size() && !r_u.size()) {
return 1;
}
- for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) {
- if (!r_v.has(E->get())) {
+ for (const StringName &E : r_u) {
+ if (!r_v.has(E)) {
return 0;
}
}
return 1;
} break;
case GraphEdit::DIFFERENCE: {
- for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) {
- if (r_v.has(E->get())) {
- r_u.erase(E->get());
+ for (HashSet<StringName>::Iterator E = r_u.begin(); E;) {
+ HashSet<StringName>::Iterator N = E;
+ ++N;
+ if (r_v.has(*E)) {
+ r_u.remove(E);
}
+ E = N;
}
return r_u.size();
} break;
case GraphEdit::UNION: {
- for (Set<StringName>::Element *E = r_v.front(); E; E = E->next()) {
- if (!r_u.has(E->get())) {
- r_u.insert(E->get());
+ for (const StringName &E : r_v) {
+ if (!r_u.has(E)) {
+ r_u.insert(E);
}
}
return r_u.size();
@@ -1727,28 +1782,28 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u,
return -1;
}
-HashMap<int, Vector<StringName>> GraphEdit::_layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) {
+HashMap<int, Vector<StringName>> GraphEdit::_layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
HashMap<int, Vector<StringName>> l;
- Set<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z;
+ HashSet<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z;
int current_layer = 0;
bool selected = false;
while (!_set_operations(GraphEdit::IS_EQUAL, q, u)) {
_set_operations(GraphEdit::DIFFERENCE, p, u);
- for (const Set<StringName>::Element *E = p.front(); E; E = E->next()) {
- Set<StringName> n = r_upper_neighbours[E->get()];
+ for (const StringName &E : p) {
+ HashSet<StringName> n = r_upper_neighbours[E];
if (_set_operations(GraphEdit::IS_SUBSET, n, z)) {
Vector<StringName> t;
- t.push_back(E->get());
+ t.push_back(E);
if (!l.has(current_layer)) {
- l.set(current_layer, Vector<StringName>{});
+ l.insert(current_layer, Vector<StringName>{});
}
selected = true;
t.append_array(l[current_layer]);
- l.set(current_layer, t);
- Set<StringName> V;
- V.insert(E->get());
+ l.insert(current_layer, t);
+ HashSet<StringName> V;
+ V.insert(E);
_set_operations(GraphEdit::UNION, u, V);
}
}
@@ -1789,10 +1844,10 @@ Vector<StringName> GraphEdit::_split(const Vector<StringName> &r_layer, const Ha
return left;
}
-void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes) {
- for (const Set<StringName>::Element *E = r_selected_nodes.front(); E; E = E->next()) {
- r_root[E->get()] = E->get();
- r_align[E->get()] = E->get();
+void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes) {
+ for (const StringName &E : r_selected_nodes) {
+ r_root[E] = E;
+ r_align[E] = E;
}
if (r_layers.size() == 1) {
@@ -1829,7 +1884,7 @@ void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, c
}
}
-void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) {
+void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
if (r_layers.size() == 1) {
return;
}
@@ -1860,17 +1915,17 @@ void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layer
}
d[q] = crossings;
}
- c.set(p, d);
+ c.insert(p, d);
}
- r_layers.set(i, _split(lower_layer, c));
+ r_layers.insert(i, _split(lower_layer, c));
}
}
-void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) {
- for (const Set<StringName>::Element *E = r_block_heads.front(); E; E = E->next()) {
+void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) {
+ for (const StringName &E : r_block_heads) {
real_t left = 0;
- StringName u = E->get();
+ StringName u = E;
StringName v = r_align[u];
while (u != v && (StringName)r_root[u] != v) {
String _connection = String(u) + " " + String(v);
@@ -1891,11 +1946,11 @@ void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictio
v = (StringName)r_align[v];
}
- u = E->get();
+ u = E;
do {
r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left;
u = (StringName)r_align[u];
- } while (u != E->get());
+ } while (u != E);
}
}
@@ -2026,7 +2081,7 @@ void GraphEdit::_place_block(StringName p_v, float p_delta, const HashMap<int, V
threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions);
w = r_align[w];
} while (w != p_v);
- r_node_positions.set(p_v, pos);
+ r_node_positions.insert(p_v, pos);
}
#undef PRED
@@ -2040,7 +2095,7 @@ void GraphEdit::arrange_nodes() {
}
Dictionary node_names;
- Set<StringName> selected_nodes;
+ HashSet<StringName> selected_nodes;
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
@@ -2051,7 +2106,7 @@ void GraphEdit::arrange_nodes() {
node_names[gn->get_name()] = gn;
}
- HashMap<StringName, Set<StringName>> upper_neighbours;
+ HashMap<StringName, HashSet<StringName>> upper_neighbours;
HashMap<StringName, Pair<int, int>> port_info;
Vector2 origin(FLT_MAX, FLT_MAX);
@@ -2066,7 +2121,7 @@ void GraphEdit::arrange_nodes() {
if (gn->is_selected()) {
selected_nodes.insert(gn->get_name());
- Set<StringName> s;
+ HashSet<StringName> s;
for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from]);
if (E->get().to == gn->get_name() && p_from->is_selected()) {
@@ -2082,10 +2137,10 @@ void GraphEdit::arrange_nodes() {
ports = p_ports;
}
}
- port_info.set(_connection, ports);
+ port_info.insert(_connection, ports);
}
}
- upper_neighbours.set(gn->get_name(), s);
+ upper_neighbours.insert(gn->get_name(), s);
}
}
@@ -2103,35 +2158,35 @@ void GraphEdit::arrange_nodes() {
HashMap<StringName, Vector2> new_positions;
Vector2 default_position(FLT_MAX, FLT_MAX);
Dictionary inner_shift;
- Set<StringName> block_heads;
+ HashSet<StringName> block_heads;
- for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) {
- inner_shift[E->get()] = 0.0f;
- sink[E->get()] = E->get();
- shift[E->get()] = FLT_MAX;
- new_positions.set(E->get(), default_position);
- if ((StringName)root[E->get()] == E->get()) {
- block_heads.insert(E->get());
+ for (const StringName &E : selected_nodes) {
+ inner_shift[E] = 0.0f;
+ sink[E] = E;
+ shift[E] = FLT_MAX;
+ new_positions.insert(E, default_position);
+ if ((StringName)root[E] == E) {
+ block_heads.insert(E);
}
}
_calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info);
- for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) {
- _place_block(E->get(), gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions);
+ for (const StringName &E : block_heads) {
+ _place_block(E, gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions);
}
origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]);
origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x;
- for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) {
- StringName u = E->get();
- float start_from = origin.y + new_positions[E->get()].y;
+ for (const StringName &E : block_heads) {
+ StringName u = E;
+ float start_from = origin.y + new_positions[E].y;
do {
Vector2 cal_pos;
cal_pos.y = start_from + (real_t)inner_shift[u];
- new_positions.set(u, cal_pos);
+ new_positions.insert(u, cal_pos);
u = align[u];
- } while (u != E->get());
+ } while (u != E);
}
// Compute horizontal coordinates individually for layers to get uniform gap.
@@ -2161,7 +2216,7 @@ void GraphEdit::arrange_nodes() {
}
cal_pos.x = current_node_start_pos;
}
- new_positions.set(layer[j], cal_pos);
+ new_positions.insert(layer[j], cal_pos);
}
start_from += largest_node_size + gap_h;
@@ -2169,10 +2224,10 @@ void GraphEdit::arrange_nodes() {
}
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()]);
+ for (const StringName &E : selected_nodes) {
+ GraphNode *gn = Object::cast_to<GraphNode>(node_names[E]);
gn->set_drag(true);
- Vector2 pos = (new_positions[E->get()]);
+ Vector2 pos = (new_positions[E]);
if (is_using_snap()) {
const int snap = get_snap();
@@ -2229,6 +2284,9 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_snap", "enable"), &GraphEdit::set_use_snap);
ClassDB::bind_method(D_METHOD("is_using_snap"), &GraphEdit::is_using_snap);
+ ClassDB::bind_method(D_METHOD("set_connection_lines_curvature", "curvature"), &GraphEdit::set_connection_lines_curvature);
+ ClassDB::bind_method(D_METHOD("get_connection_lines_curvature"), &GraphEdit::get_connection_lines_curvature);
+
ClassDB::bind_method(D_METHOD("set_connection_lines_thickness", "pixels"), &GraphEdit::set_connection_lines_thickness);
ClassDB::bind_method(D_METHOD("get_connection_lines_thickness"), &GraphEdit::get_connection_lines_thickness);
@@ -2265,6 +2323,7 @@ void GraphEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans"), "set_panning_scheme", "get_panning_scheme");
ADD_GROUP("Connection Lines", "connection_lines");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_curvature"), "set_connection_lines_curvature", "get_connection_lines_curvature");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_thickness"), "set_connection_lines_thickness", "get_connection_lines_thickness");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "connection_lines_antialiased"), "set_connection_lines_antialiased", "is_connection_lines_antialiased");
@@ -2290,7 +2349,7 @@ void GraphEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("node_deselected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::VECTOR2, "release_position")));
ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"), PropertyInfo(Variant::VECTOR2, "release_position")));
- ADD_SIGNAL(MethodInfo("delete_nodes_request"));
+ ADD_SIGNAL(MethodInfo("delete_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName")));
ADD_SIGNAL(MethodInfo("begin_node_move"));
ADD_SIGNAL(MethodInfo("end_node_move"));
ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset")));
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index f556fcdd23..02e90e4717 100644
--- a/scene/gui/graph_edit.h
+++ b/scene/gui/graph_edit.h
@@ -124,8 +124,8 @@ private:
HScrollBar *h_scroll = nullptr;
VScrollBar *v_scroll = nullptr;
- float port_grab_distance_horizontal = 0.0;
- float port_grab_distance_vertical = 0.0;
+ float port_hotzone_inner_extent = 0.0;
+ float port_hotzone_outer_extent = 0.0;
Ref<ViewPanner> panner;
bool warped_panning = true;
@@ -178,6 +178,7 @@ private:
List<Connection> connections;
float lines_thickness = 2.0f;
+ float lines_curvature = 0.5f;
bool lines_antialiased = true;
PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to);
@@ -218,8 +219,11 @@ private:
uint64_t key = 0;
};
- bool operator<(const ConnType &p_type) const {
- return key < p_type.key;
+ static uint32_t hash(const ConnType &p_conn) {
+ return hash_one_uint64(p_conn.key);
+ }
+ bool operator==(const ConnType &p_type) const {
+ return key == p_type.key;
}
ConnType(uint32_t a = 0, uint32_t b = 0) {
@@ -228,9 +232,9 @@ private:
}
};
- Set<ConnType> valid_connection_types;
- Set<int> valid_left_disconnect_types;
- Set<int> valid_right_disconnect_types;
+ HashSet<ConnType, ConnType> valid_connection_types;
+ HashSet<int> valid_left_disconnect_types;
+ HashSet<int> valid_right_disconnect_types;
HashMap<StringName, Vector<GraphNode *>> comment_enclosed_nodes;
void _update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes);
@@ -247,7 +251,7 @@ private:
friend class GraphEditMinimap;
void _minimap_toggled();
- bool _check_clickable_control(Control *p_control, const Vector2 &pos);
+ bool _check_clickable_control(Control *p_control, const Vector2 &r_mouse_pos, const Vector2 &p_offset);
bool arranging_graph = false;
@@ -258,12 +262,12 @@ private:
UNION,
};
- int _set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v);
- HashMap<int, Vector<StringName>> _layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours);
+ int _set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v);
+ HashMap<int, Vector<StringName>> _layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings);
- void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes);
- void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours);
- void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info);
+ void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes);
+ void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
+ void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info);
float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions);
void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions);
@@ -341,6 +345,9 @@ public:
int get_snap() const;
void set_snap(int p_snap);
+ void set_connection_lines_curvature(float p_curvature);
+ float get_connection_lines_curvature() const;
+
void set_connection_lines_thickness(float p_thickness);
float get_connection_lines_thickness() const;
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index e3ecd17ed8..5cb28a30e8 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -93,11 +93,13 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) {
si.color_right = p_value;
} else if (what == "right_icon") {
si.custom_slot_right = p_value;
+ } else if (what == "draw_stylebox") {
+ si.draw_stylebox = p_value;
} else {
return false;
}
- set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right);
+ set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right, si.draw_stylebox);
update();
return true;
}
@@ -144,6 +146,8 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = si.color_right;
} else if (what == "right_icon") {
r_ret = si.custom_slot_right;
+ } else if (what == "draw_stylebox") {
+ r_ret = si.draw_stylebox;
} else {
return false;
}
@@ -175,7 +179,7 @@ void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::INT, base + "right_type"));
p_list->push_back(PropertyInfo(Variant::COLOR, base + "right_color"));
p_list->push_back(PropertyInfo(Variant::OBJECT, base + "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL));
-
+ p_list->push_back(PropertyInfo(Variant::BOOL, base + "draw_stylebox"));
idx++;
}
}
@@ -185,6 +189,7 @@ void GraphNode::_resort() {
Size2i new_size = get_size();
Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame"));
+ Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot"));
int sep = get_theme_constant(SNAME("separation"));
@@ -193,7 +198,7 @@ void GraphNode::_resort() {
int stretch_min = 0;
int stretch_avail = 0;
float stretch_ratio_total = 0;
- Map<Control *, _MinSizeCache> min_size_cache;
+ HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -204,7 +209,7 @@ void GraphNode::_resort() {
continue;
}
- Size2i size = c->get_combined_minimum_size();
+ Size2i size = c->get_combined_minimum_size() + (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2());
_MinSizeCache msc;
stretch_min += size.height;
@@ -312,7 +317,9 @@ void GraphNode::_resort() {
int size = to - from;
- Rect2 rect(sb->get_margin(SIDE_LEFT), from, w, size);
+ float margin = sb->get_margin(SIDE_LEFT) + (slot_info[i].draw_stylebox ? sb_slot->get_margin(SIDE_LEFT) : 0);
+ float width = w - (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size().x : 0);
+ Rect2 rect(margin, from, width, size);
fit_child_in_rect(c, rect);
cache_y.push_back(from - sb->get_margin(SIDE_TOP) + size * 0.5);
@@ -351,14 +358,14 @@ void GraphNode::_notification(int p_what) {
Ref<StyleBox> sb;
if (comment) {
- sb = get_theme_stylebox(selected ? "comment_focus" : "comment");
+ sb = get_theme_stylebox(selected ? SNAME("comment_focus") : SNAME("comment"));
} else {
- sb = get_theme_stylebox(selected ? "selected_frame" : "frame");
+ sb = get_theme_stylebox(selected ? SNAME("selected_frame") : SNAME("frame"));
}
- //sb=sb->duplicate();
- //sb->call("set_modulate",modulate);
+ Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot"));
+
Ref<Texture2D> port = get_theme_icon(SNAME("port"));
Ref<Texture2D> close = get_theme_icon(SNAME("close"));
Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer"));
@@ -389,13 +396,9 @@ void GraphNode::_notification(int p_what) {
int w = get_size().width - sb->get_minimum_size().x;
- if (show_close) {
- w -= close->get_width();
- }
-
title_buf->draw(get_canvas_item(), Point2(sb->get_margin(SIDE_LEFT) + title_h_offset, -title_buf->get_size().y + title_offset), title_color);
if (show_close) {
- Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset, -close->get_height() + close_offset);
+ Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset - close->get_width(), -close->get_height() + close_offset);
draw_texture(close, cpos, close_color);
close_rect.position = cpos;
close_rect.size = close->get_size();
@@ -411,7 +414,7 @@ void GraphNode::_notification(int p_what) {
continue;
}
const Slot &s = slot_info[E.key];
- //left
+ // Left port.
if (s.enable_left) {
Ref<Texture2D> p = port;
if (s.custom_slot_left.is_valid()) {
@@ -419,6 +422,7 @@ void GraphNode::_notification(int p_what) {
}
p->draw(get_canvas_item(), icofs + Point2(edgeofs, cache_y[E.key]), s.color_left);
}
+ // Right port.
if (s.enable_right) {
Ref<Texture2D> p = port;
if (s.custom_slot_right.is_valid()) {
@@ -426,6 +430,15 @@ void GraphNode::_notification(int p_what) {
}
p->draw(get_canvas_item(), icofs + Point2(get_size().x - edgeofs, cache_y[E.key]), s.color_right);
}
+
+ // Draw slot stylebox.
+ if (s.draw_stylebox) {
+ Control *c = Object::cast_to<Control>(get_child(E.key));
+ Rect2 c_rect = c->get_rect();
+ c_rect.position.x = sb->get_margin(SIDE_LEFT);
+ c_rect.size.width = w;
+ draw_style_box(sb_slot, c_rect);
+ }
}
if (resizable) {
@@ -482,7 +495,7 @@ void GraphNode::_validate_property(PropertyInfo &property) const {
}
#endif
-void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right) {
+void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right, bool p_draw_stylebox) {
ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx));
if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) &&
@@ -501,6 +514,7 @@ void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const C
s.color_right = p_color_right;
s.custom_slot_left = p_custom_left;
s.custom_slot_right = p_custom_right;
+ s.draw_stylebox = p_draw_stylebox;
slot_info[p_idx] = s;
update();
connpos_dirty = true;
@@ -622,16 +636,39 @@ Color GraphNode::get_slot_color_right(int p_idx) const {
return slot_info[p_idx].color_right;
}
+bool GraphNode::is_slot_draw_stylebox(int p_idx) const {
+ if (!slot_info.has(p_idx)) {
+ return false;
+ }
+ return slot_info[p_idx].draw_stylebox;
+}
+
+void GraphNode::set_slot_draw_stylebox(int p_idx, bool p_enable) {
+ ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set draw_stylebox for the slot with p_idx (%d) lesser than zero.", p_idx));
+
+ slot_info[p_idx].draw_stylebox = p_enable;
+ update();
+ connpos_dirty = true;
+
+ emit_signal(SNAME("slot_updated"), p_idx);
+}
+
Size2 GraphNode::get_minimum_size() const {
- int sep = get_theme_constant(SNAME("separation"));
Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame"));
+ Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot"));
+
+ int sep = get_theme_constant(SNAME("separation"));
+ int title_h_offset = get_theme_constant(SNAME("title_h_offset"));
+
bool first = true;
Size2 minsize;
- minsize.x = title_buf->get_size().x;
+ minsize.x = title_buf->get_size().x + title_h_offset;
if (show_close) {
+ int close_h_offset = get_theme_constant(SNAME("close_h_offset"));
Ref<Texture2D> close = get_theme_icon(SNAME("close"));
- minsize.x += sep + close->get_width();
+ //TODO: Remove this magic number after GraphNode rework.
+ minsize.x += 12 + close->get_width() + close_h_offset;
}
for (int i = 0; i < get_child_count(); i++) {
@@ -644,6 +681,9 @@ Size2 GraphNode::get_minimum_size() const {
}
Size2i size = c->get_combined_minimum_size();
+ if (slot_info.has(i)) {
+ size += slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2();
+ }
minsize.y += size.y;
minsize.x = MAX(minsize.x, size.x);
@@ -792,6 +832,7 @@ void GraphNode::_connpos_update() {
cc.pos = Point2i(edgeofs, y + h / 2);
cc.type = slot_info[idx].type_left;
cc.color = slot_info[idx].color_left;
+ cc.height = size.height;
conn_input_cache.push_back(cc);
}
if (slot_info[idx].enable_right) {
@@ -799,6 +840,7 @@ void GraphNode::_connpos_update() {
cc.pos = Point2i(get_size().width - edgeofs, y + h / 2);
cc.type = slot_info[idx].type_right;
cc.color = slot_info[idx].color_right;
+ cc.height = size.height;
conn_output_cache.push_back(cc);
}
}
@@ -819,12 +861,13 @@ int GraphNode::get_connection_input_count() {
return conn_input_cache.size();
}
-int GraphNode::get_connection_output_count() {
+int GraphNode::get_connection_input_height(int p_idx) {
if (connpos_dirty) {
_connpos_update();
}
- return conn_output_cache.size();
+ ERR_FAIL_INDEX_V(p_idx, conn_input_cache.size(), 0);
+ return conn_input_cache[p_idx].height;
}
Vector2 GraphNode::get_connection_input_position(int p_idx) {
@@ -857,6 +900,23 @@ Color GraphNode::get_connection_input_color(int p_idx) {
return conn_input_cache[p_idx].color;
}
+int GraphNode::get_connection_output_count() {
+ if (connpos_dirty) {
+ _connpos_update();
+ }
+
+ return conn_output_cache.size();
+}
+
+int GraphNode::get_connection_output_height(int p_idx) {
+ if (connpos_dirty) {
+ _connpos_update();
+ }
+
+ ERR_FAIL_INDEX_V(p_idx, conn_output_cache.size(), 0);
+ return conn_output_cache[p_idx].height;
+}
+
Vector2 GraphNode::get_connection_output_position(int p_idx) {
if (connpos_dirty) {
_connpos_update();
@@ -989,7 +1049,7 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language);
- ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()));
+ ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right", "enable"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true));
ClassDB::bind_method(D_METHOD("clear_slot", "idx"), &GraphNode::clear_slot);
ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots);
@@ -1011,6 +1071,9 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_slot_color_right", "idx", "color_right"), &GraphNode::set_slot_color_right);
ClassDB::bind_method(D_METHOD("get_slot_color_right", "idx"), &GraphNode::get_slot_color_right);
+ ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "idx"), &GraphNode::is_slot_draw_stylebox);
+ ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "idx", "draw_stylebox"), &GraphNode::set_slot_draw_stylebox);
+
ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset);
ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphNode::get_position_offset);
@@ -1023,15 +1086,17 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_selected", "selected"), &GraphNode::set_selected);
ClassDB::bind_method(D_METHOD("is_selected"), &GraphNode::is_selected);
- ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count);
ClassDB::bind_method(D_METHOD("get_connection_input_count"), &GraphNode::get_connection_input_count);
+ ClassDB::bind_method(D_METHOD("get_connection_input_height", "idx"), &GraphNode::get_connection_input_height);
+ ClassDB::bind_method(D_METHOD("get_connection_input_position", "idx"), &GraphNode::get_connection_input_position);
+ ClassDB::bind_method(D_METHOD("get_connection_input_type", "idx"), &GraphNode::get_connection_input_type);
+ ClassDB::bind_method(D_METHOD("get_connection_input_color", "idx"), &GraphNode::get_connection_input_color);
+ ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count);
+ ClassDB::bind_method(D_METHOD("get_connection_output_height", "idx"), &GraphNode::get_connection_output_height);
ClassDB::bind_method(D_METHOD("get_connection_output_position", "idx"), &GraphNode::get_connection_output_position);
ClassDB::bind_method(D_METHOD("get_connection_output_type", "idx"), &GraphNode::get_connection_output_type);
ClassDB::bind_method(D_METHOD("get_connection_output_color", "idx"), &GraphNode::get_connection_output_color);
- ClassDB::bind_method(D_METHOD("get_connection_input_position", "idx"), &GraphNode::get_connection_input_position);
- ClassDB::bind_method(D_METHOD("get_connection_input_type", "idx"), &GraphNode::get_connection_input_type);
- ClassDB::bind_method(D_METHOD("get_connection_input_color", "idx"), &GraphNode::get_connection_input_color);
ClassDB::bind_method(D_METHOD("set_show_close_button", "show"), &GraphNode::set_show_close_button);
ClassDB::bind_method(D_METHOD("is_close_button_visible"), &GraphNode::is_close_button_visible);
diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h
index 7eb5f27cff..f6c943dc89 100644
--- a/scene/gui/graph_node.h
+++ b/scene/gui/graph_node.h
@@ -54,6 +54,7 @@ private:
Color color_right = Color(1, 1, 1, 1);
Ref<Texture2D> custom_slot_left;
Ref<Texture2D> custom_slot_right;
+ bool draw_stylebox = true;
};
String title;
@@ -80,12 +81,13 @@ private:
Vector2 pos;
int type = 0;
Color color;
+ int height;
};
Vector<ConnCache> conn_input_cache;
Vector<ConnCache> conn_output_cache;
- Map<int, Slot> slot_info;
+ HashMap<int, Slot> slot_info;
bool connpos_dirty = true;
@@ -115,7 +117,7 @@ protected:
public:
bool has_point(const Point2 &p_point) const override;
- void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>());
+ void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>(), bool p_draw_stylebox = true);
void clear_slot(int p_idx);
void clear_all_slots();
@@ -137,6 +139,9 @@ public:
void set_slot_color_right(int p_idx, const Color &p_color_right);
Color get_slot_color_right(int p_idx) const;
+ bool is_slot_draw_stylebox(int p_idx) const;
+ void set_slot_draw_stylebox(int p_idx, bool p_enable);
+
void set_title(const String &p_title);
String get_title() const;
@@ -163,10 +168,13 @@ public:
bool is_close_button_visible() const;
int get_connection_input_count();
- int get_connection_output_count();
+ int get_connection_input_height(int p_idx);
Vector2 get_connection_input_position(int p_idx);
int get_connection_input_type(int p_idx);
Color get_connection_input_color(int p_idx);
+
+ int get_connection_output_count();
+ int get_connection_output_height(int p_idx);
Vector2 get_connection_output_position(int p_idx);
int get_connection_output_type(int p_idx);
Color get_connection_output_color(int p_idx);
@@ -185,7 +193,9 @@ public:
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
- bool is_resizing() const { return resizing; }
+ bool is_resizing() const {
+ return resizing;
+ }
GraphNode();
};
diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp
index b58bb4d74a..6f8518a7b0 100644
--- a/scene/gui/grid_container.cpp
+++ b/scene/gui/grid_container.cpp
@@ -29,14 +29,15 @@
/*************************************************************************/
#include "grid_container.h"
+#include "core/templates/rb_set.h"
void GridContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
- Map<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col).
- Map<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row).
- Set<int> col_expanded; // Columns which have the SIZE_EXPAND flag set.
- Set<int> row_expanded; // Rows which have the SIZE_EXPAND flag set.
+ RBMap<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col).
+ RBMap<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row).
+ RBSet<int> col_expanded; // Columns which have the SIZE_EXPAND flag set.
+ RBSet<int> row_expanded; // Rows which have the SIZE_EXPAND flag set.
int hsep = get_theme_constant(SNAME("h_separation"));
int vsep = get_theme_constant(SNAME("v_separation"));
@@ -104,11 +105,11 @@ void GridContainer::_notification(int p_what) {
// Check if all minwidth constraints are OK if we use the remaining space.
can_fit = true;
int max_index = col_expanded.front()->get();
- for (Set<int>::Element *E = col_expanded.front(); E; E = E->next()) {
- if (col_minw[E->get()] > col_minw[max_index]) {
- max_index = E->get();
+ for (const int &E : col_expanded) {
+ if (col_minw[E] > col_minw[max_index]) {
+ max_index = E;
}
- if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E->get()]) {
+ if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E]) {
can_fit = false;
}
}
@@ -125,11 +126,11 @@ void GridContainer::_notification(int p_what) {
// Check if all minheight constraints are OK if we use the remaining space.
can_fit = true;
int max_index = row_expanded.front()->get();
- for (Set<int>::Element *E = row_expanded.front(); E; E = E->next()) {
- if (row_minh[E->get()] > row_minh[max_index]) {
- max_index = E->get();
+ for (const int &E : row_expanded) {
+ if (row_minh[E] > row_minh[max_index]) {
+ max_index = E;
}
- if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E->get()]) {
+ if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E]) {
can_fit = false;
}
}
@@ -142,13 +143,47 @@ void GridContainer::_notification(int p_what) {
}
// Finally, fit the nodes.
- int col_expand = col_expanded.size() > 0 ? remaining_space.width / col_expanded.size() : 0;
- int row_expand = row_expanded.size() > 0 ? remaining_space.height / row_expanded.size() : 0;
+ int col_remaining_pixel = 0;
+ int col_expand = 0;
+ if (col_expanded.size() > 0) {
+ col_expand = remaining_space.width / col_expanded.size();
+ col_remaining_pixel = remaining_space.width - col_expanded.size() * col_expand;
+ }
+
+ int row_remaining_pixel = 0;
+ int row_expand = 0;
+ if (row_expanded.size() > 0) {
+ row_expand = remaining_space.height / row_expanded.size();
+ row_remaining_pixel = remaining_space.height - row_expanded.size() * row_expand;
+ }
+
bool rtl = is_layout_rtl();
int col_ofs = 0;
int row_ofs = 0;
+ // Calculate the index of rows and columns that receive the remaining pixel.
+ int col_remaining_pixel_index = 0;
+ for (int i = 0; i < max_col; i++) {
+ if (col_remaining_pixel == 0) {
+ break;
+ }
+ if (col_expanded.has(i)) {
+ col_remaining_pixel_index = i + 1;
+ col_remaining_pixel--;
+ }
+ }
+ int row_remaining_pixel_index = 0;
+ for (int i = 0; i < max_row; i++) {
+ if (row_remaining_pixel == 0) {
+ break;
+ }
+ if (row_expanded.has(i)) {
+ row_remaining_pixel_index = i + 1;
+ row_remaining_pixel--;
+ }
+ }
+
valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -167,17 +202,30 @@ void GridContainer::_notification(int p_what) {
}
if (row > 0) {
row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + vsep;
+
+ if (row_expanded.has(row - 1) && row - 1 < row_remaining_pixel_index) {
+ // Apply the remaining pixel of the previous row.
+ row_ofs++;
+ }
}
}
+ Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
+
+ // Add the remaining pixel to the expanding columns and rows, starting from left and top.
+ if (col_expanded.has(col) && col < col_remaining_pixel_index) {
+ s.x++;
+ }
+ if (row_expanded.has(row) && row < row_remaining_pixel_index) {
+ s.y++;
+ }
+
if (rtl) {
- Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
Point2 p(col_ofs - s.width, row_ofs);
fit_child_in_rect(c, Rect2(p, s));
col_ofs -= s.width + hsep;
} else {
Point2 p(col_ofs, row_ofs);
- Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
fit_child_in_rect(c, Rect2(p, s));
col_ofs += s.width + hsep;
}
@@ -214,8 +262,8 @@ void GridContainer::_bind_methods() {
}
Size2 GridContainer::get_minimum_size() const {
- Map<int, int> col_minw;
- Map<int, int> row_minh;
+ RBMap<int, int> col_minw;
+ RBMap<int, int> row_minh;
int hsep = get_theme_constant(SNAME("h_separation"));
int vsep = get_theme_constant(SNAME("v_separation"));
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index e5b58a7cc8..fd9db4ea1b 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -406,9 +406,45 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if (k.is_valid()) {
if (!k->is_pressed()) {
+ if (alt_start && k->get_keycode() == Key::ALT) {
+ alt_start = false;
+ if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+ char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
+ insert_text_at_caret(ucodestr);
+ }
+ accept_event();
+ return;
+ }
return;
}
+ // Alt+ Unicode input:
+ if (k->is_alt_pressed()) {
+ if (!alt_start) {
+ if (k->get_keycode() == Key::KP_ADD) {
+ alt_start = true;
+ alt_code = 0;
+ accept_event();
+ return;
+ }
+ } else {
+ if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
+ }
+ if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
+ }
+ if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
+ }
+ accept_event();
+ return;
+ }
+ }
+
if (context_menu_enabled) {
if (k->is_action("ui_menu", true)) {
_ensure_menu();
@@ -659,7 +695,7 @@ bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const {
return false;
}
Ref<Texture2D> icon = Control::get_theme_icon(SNAME("clear"));
- int x_ofs = get_theme_stylebox(SNAME("normal"))->get_offset().x;
+ int x_ofs = get_theme_stylebox(SNAME("normal"))->get_margin(SIDE_RIGHT);
return p_pos.x > get_size().width - icon->get_width() - x_ofs;
}
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 50aa2f4460..0fb178fca4 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -77,6 +77,9 @@ private:
bool pass = false;
bool text_changed_dirty = false;
+ bool alt_start = false;
+ uint32_t alt_code = 0;
+
String undo_text;
String text;
String placeholder;
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 1feee017c2..316fee53fe 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -187,7 +187,7 @@ void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const {
pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
p_list->push_back(pi);
- pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button");
+ pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button");
pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0);
p_list->push_back(pi);
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 4b79d79846..a86f2bdbc1 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -320,7 +320,7 @@ int OptionButton::get_selectable_item(bool p_from_last) const {
}
}
} else {
- for (int i = get_item_count() - 1; i >= 0; i++) {
+ for (int i = get_item_count() - 1; i >= 0; i--) {
if (!is_item_disabled(i) && !is_item_separator(i)) {
return i;
}
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index b9e3e7814e..6532fc5934 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -108,11 +108,25 @@ void Popup::_close_pressed() {
_deinitialize_visible_parents();
- call_deferred(SNAME("hide"));
+ // Hide after returning to process events, but only if we don't
+ // get popped up in the interim.
+ call_deferred(SNAME("_popup_conditional_hide"));
+}
+
+void Popup::_post_popup() {
+ Window::_post_popup();
+ popped_up = true;
+}
+
+void Popup::_popup_conditional_hide() {
+ if (!popped_up) {
+ hide();
+ }
}
void Popup::_bind_methods() {
ADD_SIGNAL(MethodInfo("popup_hide"));
+ ClassDB::bind_method(D_METHOD("_popup_conditional_hide"), &Popup::_popup_conditional_hide);
}
Rect2i Popup::_popup_adjust_rect() const {
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
index 6211af4d20..27f46d4a97 100644
--- a/scene/gui/popup.h
+++ b/scene/gui/popup.h
@@ -57,6 +57,10 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+ void _popup_conditional_hide();
+
+ virtual void _post_popup() override;
+
public:
Popup();
~Popup();
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 98d76875cb..12587b7e73 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -117,7 +117,7 @@ class PopupMenu : public Popup {
bool hide_on_multistate_item_selection = false;
Vector2 moved;
- Map<Ref<Shortcut>, int> shortcut_refcount;
+ HashMap<Ref<Shortcut>, int> shortcut_refcount;
void _ref_shortcut(Ref<Shortcut> p_sc);
void _unref_shortcut(Ref<Shortcut> p_sc);
diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp
index 50ffb3ca67..f36682942f 100644
--- a/scene/gui/progress_bar.cpp
+++ b/scene/gui/progress_bar.cpp
@@ -62,15 +62,42 @@ void ProgressBar::_notification(int p_what) {
Color font_color = get_theme_color(SNAME("font_color"));
draw_style_box(bg, Rect2(Point2(), get_size()));
+
float r = get_as_ratio();
- int mp = fg->get_minimum_size().width;
- int p = r * (get_size().width - mp);
- if (p > 0) {
- if (is_layout_rtl()) {
- draw_style_box(fg, Rect2(Point2(p, 0), Size2(fg->get_minimum_size().width, get_size().height)));
- } else {
- draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height)));
- }
+
+ switch (mode) {
+ case FILL_BEGIN_TO_END:
+ case FILL_END_TO_BEGIN: {
+ int mp = fg->get_minimum_size().width;
+ int p = round(r * (get_size().width - mp));
+ // We want FILL_BEGIN_TO_END to map to right to left when UI layout is RTL,
+ // and left to right otherwise. And likewise for FILL_END_TO_BEGIN.
+ bool right_to_left = is_layout_rtl() ? (mode == FILL_BEGIN_TO_END) : (mode == FILL_END_TO_BEGIN);
+ if (p > 0) {
+ if (right_to_left) {
+ int p_remaining = round((1.0 - r) * (get_size().width - mp));
+ draw_style_box(fg, Rect2(Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, get_size().height)));
+ } else {
+ draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height)));
+ }
+ }
+ } break;
+ case FILL_TOP_TO_BOTTOM:
+ case FILL_BOTTOM_TO_TOP: {
+ int mp = fg->get_minimum_size().height;
+ int p = round(r * (get_size().height - mp));
+
+ if (p > 0) {
+ if (mode == FILL_TOP_TO_BOTTOM) {
+ draw_style_box(fg, Rect2(Point2(0, 0), Size2(get_size().width, p + fg->get_minimum_size().height)));
+ } else {
+ int p_remaining = round((1.0 - r) * (get_size().height - mp));
+ draw_style_box(fg, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + fg->get_minimum_size().height)));
+ }
+ }
+ } break;
+ case FILL_MODE_MAX:
+ break;
}
if (percent_visible) {
@@ -88,6 +115,16 @@ void ProgressBar::_notification(int p_what) {
}
}
+void ProgressBar::set_fill_mode(int p_fill) {
+ ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX);
+ mode = (FillMode)p_fill;
+ update();
+}
+
+int ProgressBar::get_fill_mode() {
+ return mode;
+}
+
void ProgressBar::set_percent_visible(bool p_visible) {
percent_visible = p_visible;
update();
@@ -98,10 +135,18 @@ bool ProgressBar::is_percent_visible() const {
}
void ProgressBar::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &ProgressBar::set_fill_mode);
+ ClassDB::bind_method(D_METHOD("get_fill_mode"), &ProgressBar::get_fill_mode);
ClassDB::bind_method(D_METHOD("set_percent_visible", "visible"), &ProgressBar::set_percent_visible);
ClassDB::bind_method(D_METHOD("is_percent_visible"), &ProgressBar::is_percent_visible);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Begin to End,End to Begin,Top to Bottom,Bottom to Top"), "set_fill_mode", "get_fill_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "percent_visible"), "set_percent_visible", "is_percent_visible");
+
+ BIND_ENUM_CONSTANT(FILL_BEGIN_TO_END);
+ BIND_ENUM_CONSTANT(FILL_END_TO_BEGIN);
+ BIND_ENUM_CONSTANT(FILL_TOP_TO_BOTTOM);
+ BIND_ENUM_CONSTANT(FILL_BOTTOM_TO_TOP);
}
ProgressBar::ProgressBar() {
diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h
index 2d89163f78..5ba21ad7d5 100644
--- a/scene/gui/progress_bar.h
+++ b/scene/gui/progress_bar.h
@@ -43,11 +43,27 @@ protected:
static void _bind_methods();
public:
+ enum FillMode {
+ FILL_BEGIN_TO_END,
+ FILL_END_TO_BEGIN,
+ FILL_TOP_TO_BOTTOM,
+ FILL_BOTTOM_TO_TOP,
+ FILL_MODE_MAX
+ };
+
+ void set_fill_mode(int p_fill);
+ int get_fill_mode();
+
void set_percent_visible(bool p_visible);
bool is_percent_visible() const;
Size2 get_minimum_size() const override;
ProgressBar();
+
+private:
+ FillMode mode = FILL_BEGIN_TO_END;
};
+VARIANT_ENUM_CAST(ProgressBar::FillMode);
+
#endif // PROGRESS_BAR_H
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index 8e66826e9d..fae6688452 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -50,8 +50,8 @@ void Range::_value_changed_notify() {
}
void Range::Shared::emit_value_changed() {
- for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) {
- Range *r = E->get();
+ for (Range *E : owners) {
+ Range *r = E;
if (!r->is_inside_tree()) {
continue;
}
@@ -70,8 +70,8 @@ void Range::_validate_values() {
}
void Range::Shared::emit_changed(const char *p_what) {
- for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) {
- Range *r = E->get();
+ for (Range *E : owners) {
+ Range *r = E;
if (!r->is_inside_tree()) {
continue;
}
diff --git a/scene/gui/range.h b/scene/gui/range.h
index 46b0d39202..1274821bd1 100644
--- a/scene/gui/range.h
+++ b/scene/gui/range.h
@@ -45,7 +45,7 @@ class Range : public Control {
bool exp_ratio = false;
bool allow_greater = false;
bool allow_lesser = false;
- Set<Range *> owners;
+ HashSet<Range *> owners;
void emit_value_changed();
void emit_changed(const char *p_what = "");
};
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 7ed28ac3c8..94c2a9e64b 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -209,9 +209,10 @@ String RichTextLabel::_letters(int p_num, bool p_capitalize) const {
void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
ERR_FAIL_COND(p_frame == nullptr);
- ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
+ ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size());
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
RID t = l.text_buf->get_rid();
int spans = TS->shaped_get_span_count(t);
@@ -231,7 +232,7 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<
}
}
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
switch (it->type) {
case ITEM_TABLE: {
@@ -239,7 +240,7 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<
for (Item *E : table->subitems) {
ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E);
- for (int i = 0; i < frame->lines.size(); i++) {
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
_update_line_font(frame, i, p_base_font, p_base_font_size);
}
}
@@ -250,11 +251,12 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<
}
}
-void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width) {
- ERR_FAIL_COND(p_frame == nullptr);
- ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
+float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h) {
+ ERR_FAIL_COND_V(p_frame == nullptr, p_h);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), p_h);
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size);
l.text_buf->set_width(p_width - l.offset.x);
@@ -265,7 +267,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
l.text_buf->tab_align(tabs);
}
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
switch (it->type) {
case ITEM_TABLE: {
@@ -275,16 +277,18 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
int col_count = table->columns.size();
for (int i = 0; i < col_count; i++) {
- table->columns.write[i].width = 0;
+ table->columns[i].width = 0;
}
int idx = 0;
for (Item *E : table->subitems) {
ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E);
- for (int i = 0; i < frame->lines.size(); i++) {
+ float prev_h = 0;
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1;
- _resize_line(frame, i, p_base_font, p_base_font_size, w);
+ prev_h = _resize_line(frame, i, p_base_font, p_base_font_size, w, prev_h);
}
idx++;
}
@@ -300,7 +304,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
for (int i = 0; i < col_count; i++) {
remaining_width -= table->columns[i].min_width;
if (table->columns[i].max_width > table->columns[i].min_width) {
- table->columns.write[i].expand = true;
+ table->columns[i].expand = true;
}
if (table->columns[i].expand) {
total_ratio += table->columns[i].expand_ratio;
@@ -309,9 +313,9 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
// Assign actual widths.
for (int i = 0; i < col_count; i++) {
- table->columns.write[i].width = table->columns[i].min_width;
+ table->columns[i].width = table->columns[i].min_width;
if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) {
- table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
+ table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
}
table->total_width += table->columns[i].width + hseparation;
}
@@ -328,7 +332,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
int dif = table->columns[i].width - table->columns[i].max_width;
if (dif > 0) {
table_need_fit = true;
- table->columns.write[i].width = table->columns[i].max_width;
+ table->columns[i].width = table->columns[i].max_width;
table->total_width -= dif;
total_ratio -= table->columns[i].expand_ratio;
}
@@ -342,7 +346,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
if (dif > 0) {
int slice = table->columns[i].expand_ratio * remaining_width / total_ratio;
int incr = MIN(dif, slice);
- table->columns.write[i].width += incr;
+ table->columns[i].width += incr;
table->total_width += incr;
}
}
@@ -366,16 +370,14 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
offset.x += frame->padding.position.x;
float yofs = frame->padding.position.y;
- for (int i = 0; i < frame->lines.size(); i++) {
- frame->lines.write[i].text_buf->set_width(table->columns[column].width);
- table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x));
+ float prev_h = 0;
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
+ frame->lines[i].text_buf->set_width(table->columns[column].width);
+ table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x));
- if (i > 0) {
- frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + frame->lines[i - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
- } else {
- frame->lines.write[i].offset.y = 0;
- }
- frame->lines.write[i].offset += offset;
+ frame->lines[i].offset.y = prev_h;
+ frame->lines[i].offset += offset;
float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation"));
if (i > 0) {
@@ -388,6 +390,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
h = MIN(h, frame->max_size_over.y);
}
yofs += h;
+ prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
yofs += frame->padding.size.y;
offset.x += table->columns[column].width + hseparation + frame->padding.size.x;
@@ -410,18 +413,16 @@ 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 + p_frame->lines[p_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
- } else {
- l.offset.y = 0;
- }
+ l.offset.y = p_h;
+ return l.offset.y + l.text_buf->get_size().y + l.text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
-void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset) {
- ERR_FAIL_COND(p_frame == nullptr);
- ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
+float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset) {
+ ERR_FAIL_COND_V(p_frame == nullptr, p_h);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), p_h);
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
uint16_t autowrap_flags = TextServer::BREAK_MANDATORY;
switch (autowrap_mode) {
@@ -458,7 +459,7 @@ 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;
+ Item *it_to = (p_line + 1 < (int)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_chars_behavior == VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters <= 0) {
@@ -524,9 +525,9 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
int t_char_count = 0;
// Set minimums to zero.
for (int i = 0; i < col_count; i++) {
- table->columns.write[i].min_width = 0;
- table->columns.write[i].max_width = 0;
- table->columns.write[i].width = 0;
+ table->columns[i].min_width = 0;
+ table->columns[i].max_width = 0;
+ table->columns[i].width = 0;
}
// Compute minimum width for each cell.
const int available_width = p_width - hseparation * (col_count - 1);
@@ -537,17 +538,20 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
ItemFrame *frame = static_cast<ItemFrame *>(E);
int column = idx % col_count;
- for (int i = 0; i < frame->lines.size(); i++) {
+ float prev_h = 0;
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
+
int char_offset = l.char_offset + l.char_count;
int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1;
- _shape_line(frame, i, p_base_font, p_base_font_size, w, &char_offset);
+ prev_h = _shape_line(frame, i, p_base_font, p_base_font_size, w, prev_h, &char_offset);
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_wrapped_size().x));
+ table->columns[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x));
+ table->columns[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wrapped_size().x));
}
idx++;
}
@@ -560,7 +564,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
for (int i = 0; i < col_count; i++) {
remaining_width -= table->columns[i].min_width;
if (table->columns[i].max_width > table->columns[i].min_width) {
- table->columns.write[i].expand = true;
+ table->columns[i].expand = true;
}
if (table->columns[i].expand) {
total_ratio += table->columns[i].expand_ratio;
@@ -569,9 +573,9 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
// Assign actual widths.
for (int i = 0; i < col_count; i++) {
- table->columns.write[i].width = table->columns[i].min_width;
+ table->columns[i].width = table->columns[i].min_width;
if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) {
- table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
+ table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
}
table->total_width += table->columns[i].width + hseparation;
}
@@ -588,7 +592,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
int dif = table->columns[i].width - table->columns[i].max_width;
if (dif > 0) {
table_need_fit = true;
- table->columns.write[i].width = table->columns[i].max_width;
+ table->columns[i].width = table->columns[i].max_width;
table->total_width -= dif;
total_ratio -= table->columns[i].expand_ratio;
}
@@ -602,7 +606,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
if (dif > 0) {
int slice = table->columns[i].expand_ratio * remaining_width / total_ratio;
int incr = MIN(dif, slice);
- table->columns.write[i].width += incr;
+ table->columns[i].width += incr;
table->total_width += incr;
}
}
@@ -626,16 +630,15 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
offset.x += frame->padding.position.x;
float yofs = frame->padding.position.y;
- for (int i = 0; i < frame->lines.size(); i++) {
- frame->lines.write[i].text_buf->set_width(table->columns[column].width);
- table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x));
+ float prev_h = 0;
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
- if (i > 0) {
- frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + frame->lines[i - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
- } else {
- frame->lines.write[i].offset.y = 0;
- }
- frame->lines.write[i].offset += offset;
+ frame->lines[i].text_buf->set_width(table->columns[column].width);
+ table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x));
+
+ frame->lines[i].offset.y = prev_h;
+ frame->lines[i].offset += offset;
float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation"));
if (i > 0) {
@@ -648,6 +651,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
h = MIN(h, frame->max_size_over.y);
}
yofs += h;
+ prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
yofs += frame->padding.size.y;
offset.x += table->columns[column].width + hseparation + frame->padding.size.x;
@@ -678,24 +682,22 @@ 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 + p_frame->lines[p_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
- } else {
- l.offset.y = 0;
- }
+ l.offset.y = p_h;
+ return l.offset.y + l.text_buf->get_size().y + l.text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs) {
ERR_FAIL_COND_V(p_frame == nullptr, 0);
- ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), 0);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), 0);
Vector2 off;
int line_spacing = get_theme_constant(SNAME("line_separation"));
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
Item *it_from = l.from;
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
if (it_from == nullptr) {
return 0;
@@ -877,7 +879,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + hseparation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->border != Color(0, 0, 0, 0) ? frame->border : border), false);
}
- for (int j = 0; j < frame->lines.size(); j++) {
+ for (int j = 0; j < (int)frame->lines.size(); j++) {
_draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs);
}
idx++;
@@ -1299,22 +1301,12 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item
int vofs = vscroll->get_value();
// Search for the first line.
- int from_line = 0;
-
- //TODO, change to binary search ?
- while (from_line < main->lines.size()) {
- if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")) >= vofs) {
- break;
- }
- from_line++;
- }
-
- if (from_line >= main->lines.size()) {
- return;
- }
+ int to_line = main->first_invalid_line.load();
+ int from_line = _find_first_line(0, to_line, vofs);
Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
- while (ofs.y < size.height && from_line < main->lines.size()) {
+ while (ofs.y < size.height && from_line < to_line) {
+ MutexLock lock(main->lines[from_line].text_buf->get_mutex());
_find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char);
ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) {
@@ -1331,7 +1323,9 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
Vector2 off;
int char_pos = -1;
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
+
bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL);
bool lrtl = is_layout_rtl();
@@ -1420,14 +1414,14 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
}
}
if (crect.has_point(p_click)) {
- for (int j = 0; j < frame->lines.size(); j++) {
+ for (int j = 0; j < (int)frame->lines.size(); j++) {
_find_click_in_line(frame, j, rect.position + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_click, &table_click_frame, &table_click_line, &table_click_item, &table_click_char, true);
if (table_click_frame && table_click_item) {
// Save cell detected cell hit data.
table_range = Vector2i(INT32_MAX, 0);
for (Item *F : table->subitems) {
ItemFrame *sub_frame = static_cast<ItemFrame *>(F);
- for (int k = 0; k < sub_frame->lines.size(); k++) {
+ for (int k = 0; k < (int)sub_frame->lines.size(); k++) {
table_range.x = MIN(table_range.x, sub_frame->lines[k].char_offset);
table_range.y = MAX(table_range.y, sub_frame->lines[k].char_offset + sub_frame->lines[k].char_count);
}
@@ -1484,7 +1478,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
// Find item.
if (r_click_item != nullptr) {
Item *it = p_frame->lines[p_line].from;
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
if (char_pos == p_frame->lines[p_line].char_count) {
// Selection after the end of line, select last item.
if (it_to != nullptr) {
@@ -1532,28 +1526,6 @@ void RichTextLabel::_scroll_changed(double) {
update();
}
-void RichTextLabel::_update_scroll() {
- int total_height = get_content_height();
-
- bool exceeds = total_height > get_size().height && scroll_active;
-
- if (exceeds != scroll_visible) {
- if (exceeds) {
- scroll_visible = true;
- scroll_w = vscroll->get_combined_minimum_size().width;
- vscroll->show();
- vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w);
- } else {
- scroll_visible = false;
- scroll_w = 0;
- vscroll->hide();
- }
-
- main->first_resized_line = 0; //invalidate ALL
- _validate_line_caches(main);
- }
-}
-
void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta_time) {
Item *it = p_frame;
while (it) {
@@ -1588,6 +1560,22 @@ void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta
}
}
+int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const {
+ int l = p_from;
+ int r = p_to;
+ while (l < r) {
+ int m = Math::floor(double(l + r) / 2.0);
+ MutexLock lock(main->lines[m].text_buf->get_mutex());
+ int ofs = main->lines[m].offset.y + main->lines[m].text_buf->get_size().y + main->lines[m].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
+ if (ofs < p_vofs) {
+ l = m + 1;
+ } else {
+ r = m;
+ }
+ }
+ return l;
+}
+
void RichTextLabel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_EXIT: {
@@ -1600,38 +1588,45 @@ void RichTextLabel::_notification(int p_what) {
} break;
case NOTIFICATION_RESIZED: {
- main->first_resized_line = 0; //invalidate ALL
+ _stop_thread();
+ main->first_resized_line.store(0); //invalidate ALL
update();
} break;
case NOTIFICATION_THEME_CHANGED: {
- main->first_invalid_font_line = 0; //invalidate ALL
+ _stop_thread();
+ main->first_invalid_font_line.store(0); //invalidate ALL
update();
} break;
case NOTIFICATION_ENTER_TREE: {
+ _stop_thread();
if (!text.is_empty()) {
set_text(text);
}
- main->first_invalid_line = 0; //invalidate ALL
+ main->first_invalid_line.store(0); //invalidate ALL
update();
} break;
+ case NOTIFICATION_EXIT_TREE: {
+ _stop_thread();
+ } break;
+
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
- main->first_invalid_line = 0; //invalidate ALL
+ _stop_thread();
+ main->first_invalid_line.store(0); //invalidate ALL
update();
} break;
- case NOTIFICATION_DRAW: {
- _validate_line_caches(main);
- _update_scroll();
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ update();
+ } break;
+ case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
-
Size2 size = get_size();
- Rect2 text_rect = _get_text_rect();
draw_style_box(get_theme_stylebox(SNAME("normal")), Rect2(Point2(), size));
@@ -1641,22 +1636,42 @@ void RichTextLabel::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
}
+ // Start text shaping.
+ if (_validate_line_caches()) {
+ set_physics_process_internal(false); // Disable auto refresh, if text is fully processed.
+ } else {
+ // Draw loading progress bar.
+ if ((progress_delay > 0) && (OS::get_singleton()->get_ticks_msec() - loading_started >= (uint64_t)progress_delay)) {
+ Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg"), SNAME("ProgressBar"));
+ Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg"), SNAME("ProgressBar"));
+ Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"));
+
+ Vector2 p_size = Vector2(size.width - (style->get_offset().x + vscroll->get_combined_minimum_size().width) * 2, vscroll->get_combined_minimum_size().width);
+ Vector2 p_pos = Vector2(style->get_offset().x, size.height - style->get_offset().y - vscroll->get_combined_minimum_size().width);
+
+ draw_style_box(bg, Rect2(p_pos, p_size));
+
+ bool right_to_left = is_layout_rtl();
+ double r = loaded.load();
+ int mp = fg->get_minimum_size().width;
+ int p = round(r * (p_size.width - mp));
+ if (right_to_left) {
+ int p_remaining = round((1.0 - r) * (p_size.width - mp));
+ draw_style_box(fg, Rect2(p_pos + Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, p_size.height)));
+ } else {
+ draw_style_box(fg, Rect2(p_pos, Size2(p + fg->get_minimum_size().width, p_size.height)));
+ }
+ }
+ }
+
+ // Draw main text.
+ Rect2 text_rect = _get_text_rect();
float vofs = vscroll->get_value();
// Search for the first line.
- int from_line = 0;
+ int to_line = main->first_invalid_line.load();
+ int from_line = _find_first_line(0, to_line, vofs);
- //TODO, change to binary search ?
- while (from_line < main->lines.size()) {
- if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")) >= vofs) {
- break;
- }
- from_line++;
- }
-
- if (from_line >= main->lines.size()) {
- break; //nothing to draw
- }
Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
Color base_color = get_theme_color(SNAME("default_color"));
Color outline_color = get_theme_color(SNAME("font_outline_color"));
@@ -1671,7 +1686,9 @@ void RichTextLabel::_notification(int p_what) {
// New cache draw.
Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
int processed_glyphs = 0;
- while (ofs.y < size.height && from_line < main->lines.size()) {
+ while (ofs.y < size.height && from_line < to_line) {
+ MutexLock lock(main->lines[from_line].text_buf->get_mutex());
+
visible_paragraph_count++;
visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, shadow_outline_size, shadow_ofs, processed_glyphs);
ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
@@ -1681,6 +1698,9 @@ void RichTextLabel::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
if (is_visible_in_tree()) {
+ if (!is_ready()) {
+ return;
+ }
double dt = get_process_delta_time();
_update_fx(main, dt);
update();
@@ -1708,18 +1728,6 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const
return CURSOR_IBEAM;
}
- if (main->first_invalid_line < main->lines.size()) {
- return get_default_cursor_shape(); //invalid
- }
-
- if (main->first_invalid_font_line < main->lines.size()) {
- return get_default_cursor_shape(); //invalid
- }
-
- if (main->first_resized_line < main->lines.size()) {
- return get_default_cursor_shape(); //invalid
- }
-
Item *item = nullptr;
bool outside = true;
const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &item, nullptr, &outside);
@@ -1727,7 +1735,6 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const
if (item && !outside && const_cast<RichTextLabel *>(this)->_find_meta(item, nullptr)) {
return CURSOR_POINTING_HAND;
}
-
return get_default_cursor_shape();
}
@@ -1737,16 +1744,6 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
- if (main->first_invalid_line < main->lines.size()) {
- return;
- }
- if (main->first_invalid_font_line < main->lines.size()) {
- return;
- }
- if (main->first_resized_line < main->lines.size()) {
- return;
- }
-
if (b->get_button_index() == MouseButton::LEFT) {
if (b->is_pressed() && !b->is_double_click()) {
scroll_updated = false;
@@ -1800,6 +1797,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
if (c_frame) {
const Line &l = c_frame->lines[c_line];
+ MutexLock lock(l.text_buf->get_mutex());
PackedInt32Array words = TS->shaped_text_get_word_breaks(l.text_buf->get_rid());
for (int i = 0; i < words.size(); i = i + 2) {
if (c_index >= words[i] && c_index < words[i + 1]) {
@@ -1841,8 +1839,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
deselect();
}
}
-
- if (!b->is_double_click() && !scroll_updated) {
+ if (!b->is_double_click() && !scroll_updated && !selection.active) {
Item *c_item = nullptr;
bool outside = true;
@@ -1945,18 +1942,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
}
Ref<InputEventMouseMotion> m = p_event;
-
if (m.is_valid()) {
- if (main->first_invalid_line < main->lines.size()) {
- return;
- }
- if (main->first_invalid_font_line < main->lines.size()) {
- return;
- }
- if (main->first_resized_line < main->lines.size()) {
- return;
- }
-
ItemFrame *c_frame = nullptr;
int c_line = 0;
Item *c_item = nullptr;
@@ -2434,93 +2420,215 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) {
return false;
}
-void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
- if (p_frame->first_invalid_line == p_frame->lines.size()) {
+void RichTextLabel::_thread_function(void *self) {
+ RichTextLabel *rtl = reinterpret_cast<RichTextLabel *>(self);
+ rtl->_process_line_caches();
+ rtl->updating.store(false);
+ rtl->call_deferred(SNAME("update"));
+}
+
+void RichTextLabel::_stop_thread() {
+ if (threaded) {
+ stop_thread.store(true);
+ thread.wait_to_finish();
+ }
+}
+
+bool RichTextLabel::is_ready() const {
+ if (updating.load()) {
+ return false;
+ }
+ return (main->first_invalid_line.load() == (int)main->lines.size() && main->first_resized_line.load() == (int)main->lines.size() && main->first_invalid_font_line.load() == (int)main->lines.size());
+}
+
+void RichTextLabel::set_threaded(bool p_threaded) {
+ if (threaded != p_threaded) {
+ _stop_thread();
+ threaded = p_threaded;
+ update();
+ }
+}
+
+bool RichTextLabel::is_threaded() const {
+ return threaded;
+}
+
+void RichTextLabel::set_progress_bar_delay(int p_delay_ms) {
+ progress_delay = p_delay_ms;
+}
+
+int RichTextLabel::get_progress_bar_delay() const {
+ return progress_delay;
+}
+
+bool RichTextLabel::_validate_line_caches() {
+ if (updating.load()) {
+ return false;
+ }
+ if (main->first_invalid_line.load() == (int)main->lines.size()) {
+ MutexLock data_lock(data_mutex);
+ Rect2 text_rect = _get_text_rect();
+
Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
+ int ctrl_height = get_size().height;
// Update fonts.
- if (p_frame->first_invalid_font_line != p_frame->lines.size()) {
- for (int i = p_frame->first_invalid_font_line; i < p_frame->lines.size(); i++) {
- _update_line_font(p_frame, i, base_font, base_font_size);
+ if (main->first_invalid_font_line.load() != (int)main->lines.size()) {
+ for (int i = main->first_invalid_font_line.load(); i < (int)main->lines.size(); i++) {
+ _update_line_font(main, i, base_font, base_font_size);
}
- p_frame->first_resized_line = p_frame->first_invalid_font_line;
- p_frame->first_invalid_font_line = p_frame->lines.size();
+ main->first_resized_line.store(main->first_invalid_font_line.load());
+ main->first_invalid_font_line.store(main->lines.size());
}
- if (p_frame->first_resized_line == p_frame->lines.size()) {
- return;
+ if (main->first_resized_line.load() == (int)main->lines.size()) {
+ return true;
}
// Resize lines without reshaping.
- Rect2 text_rect = _get_text_rect();
+ int fi = main->first_resized_line.load();
+
+ float total_height = 0;
+ for (int i = fi; i < (int)main->lines.size(); i++) {
+ total_height = _resize_line(main, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height);
+
+ updating_scroll = true;
+ bool exceeds = total_height > ctrl_height && scroll_active;
+ if (exceeds != scroll_visible) {
+ if (exceeds) {
+ scroll_visible = true;
+ scroll_w = vscroll->get_combined_minimum_size().width;
+ vscroll->show();
+ vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w);
+ } else {
+ scroll_visible = false;
+ scroll_w = 0;
+ vscroll->hide();
+ }
- for (int i = p_frame->first_resized_line; i < p_frame->lines.size(); i++) {
- _resize_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w);
- }
+ main->first_resized_line.store(0);
- int total_height = 0;
- if (p_frame->lines.size()) {
- total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
- }
+ total_height = 0;
+ for (int j = 0; j <= i; j++) {
+ total_height = _resize_line(main, j, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height);
- p_frame->first_resized_line = p_frame->lines.size();
+ main->first_resized_line.store(j);
+ }
+ }
- updating_scroll = true;
- vscroll->set_max(total_height);
- vscroll->set_page(text_rect.size.height);
- if (scroll_follow && scroll_following) {
- vscroll->set_value(total_height);
+ vscroll->set_max(total_height);
+ vscroll->set_page(text_rect.size.height);
+ if (scroll_follow && scroll_following) {
+ vscroll->set_value(total_height);
+ }
+ updating_scroll = false;
+
+ main->first_resized_line.store(i);
}
- updating_scroll = false;
+
+ main->first_resized_line.store(main->lines.size());
if (fit_content_height) {
update_minimum_size();
}
- return;
+ return true;
}
+ stop_thread.store(false);
+ if (threaded) {
+ updating.store(true);
+ loaded.store(true);
+ thread.start(RichTextLabel::_thread_function, reinterpret_cast<void *>(this));
+ loading_started = OS::get_singleton()->get_ticks_msec();
+ set_physics_process_internal(true);
+ return false;
+ } else {
+ _process_line_caches();
+ update();
+ return true;
+ }
+}
+void RichTextLabel::_process_line_caches() {
// Shape invalid lines.
+ MutexLock data_lock(data_mutex);
Rect2 text_rect = _get_text_rect();
Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
+ int ctrl_height = get_size().height;
+ int fi = main->first_invalid_line.load();
+ int total_chars = (fi == 0) ? 0 : (main->lines[fi].char_offset + main->lines[fi].char_count);
- int total_chars = (p_frame->first_invalid_line == 0) ? 0 : (p_frame->lines[p_frame->first_invalid_line].char_offset + p_frame->lines[p_frame->first_invalid_line].char_count);
- for (int i = p_frame->first_invalid_line; i < p_frame->lines.size(); i++) {
- _shape_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, &total_chars);
- }
+ float total_height = 0;
+ for (int i = fi; i < (int)main->lines.size(); i++) {
+ total_height = _shape_line(main, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height, &total_chars);
- int total_height = 0;
- if (p_frame->lines.size()) {
- total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
- }
+ updating_scroll = true;
+ bool exceeds = total_height > ctrl_height && scroll_active;
+ if (exceeds != scroll_visible) {
+ if (exceeds) {
+ scroll_visible = true;
+ scroll_w = vscroll->get_combined_minimum_size().width;
+ vscroll->show();
+ vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w);
+ } else {
+ scroll_visible = false;
+ scroll_w = 0;
+ vscroll->hide();
+ }
- p_frame->first_invalid_line = p_frame->lines.size();
- p_frame->first_resized_line = p_frame->lines.size();
- p_frame->first_invalid_font_line = p_frame->lines.size();
+ main->first_invalid_line.store(0);
+ main->first_resized_line.store(0);
+ main->first_invalid_font_line.store(0);
- updating_scroll = true;
- vscroll->set_max(total_height);
- vscroll->set_page(text_rect.size.height);
- if (scroll_follow && scroll_following) {
- vscroll->set_value(total_height);
+ total_height = 0;
+ for (int j = 0; j <= i; j++) {
+ total_height = _resize_line(main, j, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height);
+
+ main->first_invalid_line.store(j);
+ main->first_resized_line.store(j);
+ main->first_invalid_font_line.store(j);
+ }
+ }
+
+ vscroll->set_max(total_height);
+ vscroll->set_page(text_rect.size.height);
+ if (scroll_follow && scroll_following) {
+ vscroll->set_value(total_height);
+ }
+ updating_scroll = false;
+
+ main->first_invalid_line.store(i);
+ main->first_resized_line.store(i);
+ main->first_invalid_font_line.store(i);
+
+ if (stop_thread.load()) {
+ return;
+ }
+ loaded.store(double(i) / double(main->lines.size()));
}
- updating_scroll = false;
+
+ main->first_invalid_line.store(main->lines.size());
+ main->first_resized_line.store(main->lines.size());
+ main->first_invalid_font_line.store(main->lines.size());
if (fit_content_height) {
update_minimum_size();
}
+ emit_signal(SNAME("finished"));
}
void RichTextLabel::_invalidate_current_line(ItemFrame *p_frame) {
- if (p_frame->lines.size() - 1 <= p_frame->first_invalid_line) {
- p_frame->first_invalid_line = p_frame->lines.size() - 1;
- update();
+ if ((int)p_frame->lines.size() - 1 <= p_frame->first_invalid_line) {
+ p_frame->first_invalid_line = (int)p_frame->lines.size() - 1;
}
}
void RichTextLabel::add_text(const String &p_text) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
if (current->type == ITEM_TABLE) {
return; //can't add anything here
}
@@ -2564,13 +2672,14 @@ void RichTextLabel::add_text(const String &p_text) {
_add_item(item, false);
current_frame->lines.resize(current_frame->lines.size() + 1);
if (item->type != ITEM_NEWLINE) {
- current_frame->lines.write[current_frame->lines.size() - 1].from = item;
+ current_frame->lines[current_frame->lines.size() - 1].from = item;
}
_invalidate_current_line(current_frame);
}
pos = end + 1;
}
+ update();
}
void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) {
@@ -2599,7 +2708,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
}
if (current_frame->lines[current_frame->lines.size() - 1].from == nullptr) {
- current_frame->lines.write[current_frame->lines.size() - 1].from = p_item;
+ current_frame->lines[current_frame->lines.size() - 1].from = p_item;
}
p_item->line = current_frame->lines.size() - 1;
@@ -2608,6 +2717,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
if (fixed_width != -1) {
update_minimum_size();
}
+ update();
}
void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_subitem_line) {
@@ -2635,6 +2745,9 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub
}
void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
if (current->type == ITEM_TABLE) {
return;
}
@@ -2674,6 +2787,9 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width,
}
void RichTextLabel::add_newline() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
if (current->type == ITEM_TABLE) {
return;
}
@@ -2682,10 +2798,14 @@ void RichTextLabel::add_newline() {
_add_item(item, false);
current_frame->lines.resize(current_frame->lines.size() + 1);
_invalidate_current_line(current_frame);
+ update();
}
bool RichTextLabel::remove_line(const int p_line) {
- if (p_line >= current_frame->lines.size() || p_line < 0) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
+ if (p_line >= (int)current_frame->lines.size() || p_line < 0) {
return false;
}
@@ -2713,16 +2833,19 @@ bool RichTextLabel::remove_line(const int p_line) {
}
if (p_line == 0 && current->subitems.size() > 0) {
- main->lines.write[0].from = main;
+ main->lines[0].from = main;
}
- main->first_invalid_line = 0; // p_line ???
+ main->first_invalid_line.store(0);
update();
return true;
}
void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins, const Color &p_color, int p_ol_size, const Color &p_ol_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ERR_FAIL_COND(p_string.is_empty());
ERR_FAIL_COND(p_font.is_null());
@@ -2741,6 +2864,9 @@ void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font
}
void RichTextLabel::push_font(const Ref<Font> &p_font) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ERR_FAIL_COND(p_font.is_null());
ItemFont *item = memnew(ItemFont);
@@ -2785,6 +2911,9 @@ void RichTextLabel::push_mono() {
}
void RichTextLabel::push_font_size(int p_font_size) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemFontSize *item = memnew(ItemFontSize);
@@ -2793,6 +2922,9 @@ void RichTextLabel::push_font_size(int p_font_size) {
}
void RichTextLabel::push_font_features(const Dictionary &p_features) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemFontFeatures *item = memnew(ItemFontFeatures);
@@ -2801,6 +2933,9 @@ void RichTextLabel::push_font_features(const Dictionary &p_features) {
}
void RichTextLabel::push_outline_size(int p_font_size) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemOutlineSize *item = memnew(ItemOutlineSize);
@@ -2809,6 +2944,9 @@ void RichTextLabel::push_outline_size(int p_font_size) {
}
void RichTextLabel::push_color(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemColor *item = memnew(ItemColor);
@@ -2817,6 +2955,9 @@ void RichTextLabel::push_color(const Color &p_color) {
}
void RichTextLabel::push_outline_color(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemOutlineColor *item = memnew(ItemOutlineColor);
@@ -2825,6 +2966,9 @@ void RichTextLabel::push_outline_color(const Color &p_color) {
}
void RichTextLabel::push_underline() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemUnderline *item = memnew(ItemUnderline);
@@ -2832,6 +2976,9 @@ void RichTextLabel::push_underline() {
}
void RichTextLabel::push_strikethrough() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemStrikethrough *item = memnew(ItemStrikethrough);
@@ -2839,6 +2986,9 @@ void RichTextLabel::push_strikethrough() {
}
void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, TextServer::StructuredTextParser p_st_parser) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemParagraph *item = memnew(ItemParagraph);
@@ -2850,6 +3000,9 @@ void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::Tex
}
void RichTextLabel::push_indent(int p_level) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ERR_FAIL_COND(p_level < 0);
@@ -2859,6 +3012,9 @@ void RichTextLabel::push_indent(int p_level) {
}
void RichTextLabel::push_list(int p_level, ListType p_list, bool p_capitalize) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ERR_FAIL_COND(p_level < 0);
@@ -2871,6 +3027,9 @@ void RichTextLabel::push_list(int p_level, ListType p_list, bool p_capitalize) {
}
void RichTextLabel::push_meta(const Variant &p_meta) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemMeta *item = memnew(ItemMeta);
@@ -2879,6 +3038,9 @@ void RichTextLabel::push_meta(const Variant &p_meta) {
}
void RichTextLabel::push_hint(const String &p_string) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemHint *item = memnew(ItemHint);
@@ -2887,20 +3049,26 @@ void RichTextLabel::push_hint(const String &p_string) {
}
void RichTextLabel::push_table(int p_columns, InlineAlignment p_alignment) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(p_columns < 1);
ItemTable *item = memnew(ItemTable);
item->columns.resize(p_columns);
item->total_width = 0;
item->inline_align = p_alignment;
- for (int i = 0; i < item->columns.size(); i++) {
- item->columns.write[i].expand = false;
- item->columns.write[i].expand_ratio = 1;
+ for (int i = 0; i < (int)item->columns.size(); i++) {
+ item->columns[i].expand = false;
+ item->columns[i].expand_ratio = 1;
}
_add_item(item, true, false);
}
void RichTextLabel::push_fade(int p_start_index, int p_length) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemFade *item = memnew(ItemFade);
item->starting_index = p_start_index;
item->length = p_length;
@@ -2908,6 +3076,9 @@ void RichTextLabel::push_fade(int p_start_index, int p_length) {
}
void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemShake *item = memnew(ItemShake);
item->strength = p_strength;
item->rate = p_rate;
@@ -2915,6 +3086,9 @@ void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) {
}
void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemWave *item = memnew(ItemWave);
item->frequency = p_frequency;
item->amplitude = p_amplitude;
@@ -2922,6 +3096,9 @@ void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0
}
void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemTornado *item = memnew(ItemTornado);
item->frequency = p_frequency;
item->radius = p_radius;
@@ -2929,6 +3106,9 @@ void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0
}
void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_frequency) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemRainbow *item = memnew(ItemRainbow);
item->frequency = p_frequency;
item->saturation = p_saturation;
@@ -2937,6 +3117,9 @@ void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_freq
}
void RichTextLabel::push_bgcolor(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemBGColor *item = memnew(ItemBGColor);
@@ -2945,6 +3128,9 @@ void RichTextLabel::push_bgcolor(const Color &p_color) {
}
void RichTextLabel::push_fgcolor(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemFGColor *item = memnew(ItemFGColor);
@@ -2953,6 +3139,9 @@ void RichTextLabel::push_fgcolor(const Color &p_color) {
}
void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemCustomFX *item = memnew(ItemCustomFX);
item->custom_effect = p_custom_effect;
item->char_fx_transform->environment = p_environment;
@@ -2960,15 +3149,23 @@ void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionar
}
void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_ratio) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_TABLE);
+
ItemTable *table = static_cast<ItemTable *>(current);
- ERR_FAIL_INDEX(p_column, table->columns.size());
- table->columns.write[p_column].expand = p_expand;
- table->columns.write[p_column].expand_ratio = p_ratio;
+ ERR_FAIL_INDEX(p_column, (int)table->columns.size());
+ table->columns[p_column].expand = p_expand;
+ table->columns[p_column].expand_ratio = p_ratio;
}
void RichTextLabel::set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_FRAME);
+
ItemFrame *cell = static_cast<ItemFrame *>(current);
ERR_FAIL_COND(!cell->cell);
cell->odd_row_bg = p_odd_row_bg;
@@ -2976,14 +3173,22 @@ void RichTextLabel::set_cell_row_background_color(const Color &p_odd_row_bg, con
}
void RichTextLabel::set_cell_border_color(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_FRAME);
+
ItemFrame *cell = static_cast<ItemFrame *>(current);
ERR_FAIL_COND(!cell->cell);
cell->border = p_color;
}
void RichTextLabel::set_cell_size_override(const Size2 &p_min_size, const Size2 &p_max_size) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_FRAME);
+
ItemFrame *cell = static_cast<ItemFrame *>(current);
ERR_FAIL_COND(!cell->cell);
cell->min_size_over = p_min_size;
@@ -2991,13 +3196,20 @@ void RichTextLabel::set_cell_size_override(const Size2 &p_min_size, const Size2
}
void RichTextLabel::set_cell_padding(const Rect2 &p_padding) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_FRAME);
+
ItemFrame *cell = static_cast<ItemFrame *>(current);
ERR_FAIL_COND(!cell->cell);
cell->padding = p_padding;
}
void RichTextLabel::push_cell() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_TABLE);
ItemFrame *item = memnew(ItemFrame);
@@ -3006,20 +3218,23 @@ void RichTextLabel::push_cell() {
current_frame = item;
item->cell = true;
item->lines.resize(1);
- item->lines.write[0].from = nullptr;
- item->first_invalid_line = 0; // parent frame last line ???
+ item->lines[0].from = nullptr;
+ item->first_invalid_line.store(0); // parent frame last line ???
}
int RichTextLabel::get_current_table_column() const {
ERR_FAIL_COND_V(current->type != ITEM_TABLE, -1);
ItemTable *table = static_cast<ItemTable *>(current);
-
return table->subitems.size() % table->columns.size();
}
void RichTextLabel::pop() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(!current->parent);
+
if (current->type == ITEM_FRAME) {
current_frame = static_cast<ItemFrame *>(current)->parent_frame;
}
@@ -3027,12 +3242,15 @@ void RichTextLabel::pop() {
}
void RichTextLabel::clear() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
main->_clear_children();
current = main;
current_frame = main;
main->lines.clear();
main->lines.resize(1);
- main->first_invalid_line = 0;
+ main->first_invalid_line.store(0);
selection.click_frame = nullptr;
selection.click_item = nullptr;
@@ -3050,8 +3268,10 @@ void RichTextLabel::clear() {
}
void RichTextLabel::set_tab_size(int p_spaces) {
+ _stop_thread();
+
tab_size = p_spaces;
- main->first_resized_line = 0;
+ main->first_resized_line.store(0);
update();
}
@@ -3131,6 +3351,9 @@ void RichTextLabel::parse_bbcode(const String &p_bbcode) {
}
void RichTextLabel::append_text(const String &p_bbcode) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
int pos = 0;
List<String> tag_stack;
@@ -3177,7 +3400,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
// Find optional parameters.
String bbcode_name;
- typedef Map<String, String> OptionMap;
+ typedef HashMap<String, String> OptionMap;
OptionMap bbcode_options;
if (!split_tag_block.is_empty()) {
bbcode_name = split_tag_block[0];
@@ -3620,9 +3843,9 @@ void RichTextLabel::append_text(const String &p_bbcode) {
Ref<Texture2D> texture = ResourceLoader::load(image, "Texture2D");
if (texture.is_valid()) {
Color color = Color(1.0, 1.0, 1.0);
- OptionMap::Element *color_option = bbcode_options.find("color");
+ OptionMap::Iterator color_option = bbcode_options.find("color");
if (color_option) {
- color = Color::from_string(color_option->value(), color);
+ color = Color::from_string(color_option->value, color);
}
int width = 0;
@@ -3636,14 +3859,14 @@ void RichTextLabel::append_text(const String &p_bbcode) {
height = bbcode_value.substr(sep + 1).to_int();
}
} else {
- OptionMap::Element *width_option = bbcode_options.find("width");
+ OptionMap::Iterator width_option = bbcode_options.find("width");
if (width_option) {
- width = width_option->value().to_int();
+ width = width_option->value.to_int();
}
- OptionMap::Element *height_option = bbcode_options.find("height");
+ OptionMap::Iterator height_option = bbcode_options.find("height");
if (height_option) {
- height = height_option->value().to_int();
+ height = height_option->value.to_int();
}
}
@@ -3729,15 +3952,15 @@ void RichTextLabel::append_text(const String &p_bbcode) {
} else if (bbcode_name == "fade") {
int start_index = 0;
- OptionMap::Element *start_option = bbcode_options.find("start");
+ OptionMap::Iterator start_option = bbcode_options.find("start");
if (start_option) {
- start_index = start_option->value().to_int();
+ start_index = start_option->value.to_int();
}
int length = 10;
- OptionMap::Element *length_option = bbcode_options.find("length");
+ OptionMap::Iterator length_option = bbcode_options.find("length");
if (length_option) {
- length = length_option->value().to_int();
+ length = length_option->value.to_int();
}
push_fade(start_index, length);
@@ -3745,15 +3968,15 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("fade");
} else if (bbcode_name == "shake") {
int strength = 5;
- OptionMap::Element *strength_option = bbcode_options.find("level");
+ OptionMap::Iterator strength_option = bbcode_options.find("level");
if (strength_option) {
- strength = strength_option->value().to_int();
+ strength = strength_option->value.to_int();
}
float rate = 20.0f;
- OptionMap::Element *rate_option = bbcode_options.find("rate");
+ OptionMap::Iterator rate_option = bbcode_options.find("rate");
if (rate_option) {
- rate = rate_option->value().to_float();
+ rate = rate_option->value.to_float();
}
push_shake(strength, rate);
@@ -3762,15 +3985,15 @@ void RichTextLabel::append_text(const String &p_bbcode) {
set_process_internal(true);
} else if (bbcode_name == "wave") {
float amplitude = 20.0f;
- OptionMap::Element *amplitude_option = bbcode_options.find("amp");
+ OptionMap::Iterator amplitude_option = bbcode_options.find("amp");
if (amplitude_option) {
- amplitude = amplitude_option->value().to_float();
+ amplitude = amplitude_option->value.to_float();
}
float period = 5.0f;
- OptionMap::Element *period_option = bbcode_options.find("freq");
+ OptionMap::Iterator period_option = bbcode_options.find("freq");
if (period_option) {
- period = period_option->value().to_float();
+ period = period_option->value.to_float();
}
push_wave(period, amplitude);
@@ -3779,15 +4002,15 @@ void RichTextLabel::append_text(const String &p_bbcode) {
set_process_internal(true);
} else if (bbcode_name == "tornado") {
float radius = 10.0f;
- OptionMap::Element *radius_option = bbcode_options.find("radius");
+ OptionMap::Iterator radius_option = bbcode_options.find("radius");
if (radius_option) {
- radius = radius_option->value().to_float();
+ radius = radius_option->value.to_float();
}
float frequency = 1.0f;
- OptionMap::Element *frequency_option = bbcode_options.find("freq");
+ OptionMap::Iterator frequency_option = bbcode_options.find("freq");
if (frequency_option) {
- frequency = frequency_option->value().to_float();
+ frequency = frequency_option->value.to_float();
}
push_tornado(frequency, radius);
@@ -3796,21 +4019,21 @@ void RichTextLabel::append_text(const String &p_bbcode) {
set_process_internal(true);
} else if (bbcode_name == "rainbow") {
float saturation = 0.8f;
- OptionMap::Element *saturation_option = bbcode_options.find("sat");
+ OptionMap::Iterator saturation_option = bbcode_options.find("sat");
if (saturation_option) {
- saturation = saturation_option->value().to_float();
+ saturation = saturation_option->value.to_float();
}
float value = 0.8f;
- OptionMap::Element *value_option = bbcode_options.find("val");
+ OptionMap::Iterator value_option = bbcode_options.find("val");
if (value_option) {
- value = value_option->value().to_float();
+ value = value_option->value.to_float();
}
float frequency = 1.0f;
- OptionMap::Element *frequency_option = bbcode_options.find("freq");
+ OptionMap::Iterator frequency_option = bbcode_options.find("freq");
if (frequency_option) {
- frequency = frequency_option->value().to_float();
+ frequency = frequency_option->value.to_float();
}
push_rainbow(saturation, value, frequency);
@@ -3871,9 +4094,13 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
void RichTextLabel::scroll_to_paragraph(int p_paragraph) {
- ERR_FAIL_INDEX(p_paragraph, main->lines.size());
- _validate_line_caches(main);
- vscroll->set_value(main->lines[p_paragraph].offset.y);
+ if (p_paragraph <= 0) {
+ vscroll->set_value(0);
+ } else if (p_paragraph >= main->first_invalid_line.load()) {
+ vscroll->set_value(vscroll->get_max());
+ } else {
+ vscroll->set_value(main->lines[p_paragraph].offset.y);
+ }
}
int RichTextLabel::get_paragraph_count() const {
@@ -3888,10 +4115,14 @@ int RichTextLabel::get_visible_paragraph_count() const {
}
void RichTextLabel::scroll_to_line(int p_line) {
- _validate_line_caches(main);
-
+ if (p_line <= 0) {
+ vscroll->set_value(0);
+ return;
+ }
int line_count = 0;
- for (int i = 0; i < main->lines.size(); i++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
if ((line_count <= p_line) && (line_count + main->lines[i].text_buf->get_line_count() >= p_line)) {
float line_offset = 0.f;
for (int j = 0; j < p_line - line_count; j++) {
@@ -3902,11 +4133,14 @@ void RichTextLabel::scroll_to_line(int p_line) {
}
line_count += main->lines[i].text_buf->get_line_count();
}
+ vscroll->set_value(vscroll->get_max());
}
float RichTextLabel::get_line_offset(int p_line) {
int line_count = 0;
- for (int i = 0; i < main->lines.size(); i++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
if ((line_count <= p_line) && (p_line <= line_count + main->lines[i].text_buf->get_line_count())) {
float line_offset = 0.f;
for (int j = 0; j < p_line - line_count; j++) {
@@ -3920,7 +4154,8 @@ float RichTextLabel::get_line_offset(int p_line) {
}
float RichTextLabel::get_paragraph_offset(int p_paragraph) {
- if (0 <= p_paragraph && p_paragraph < main->lines.size()) {
+ int to_line = main->first_invalid_line.load();
+ if (0 <= p_paragraph && p_paragraph < to_line) {
return main->lines[p_paragraph].offset.y;
}
return 0;
@@ -3928,7 +4163,9 @@ float RichTextLabel::get_paragraph_offset(int p_paragraph) {
int RichTextLabel::get_line_count() const {
int line_count = 0;
- for (int i = 0; i < main->lines.size(); i++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
line_count += main->lines[i].text_buf->get_line_count();
}
return line_count;
@@ -3989,13 +4226,13 @@ bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_f
ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E->get());
if (p_reverse_search) {
- for (int i = frame->lines.size() - 1; i >= 0; i--) {
+ for (int i = (int)frame->lines.size() - 1; i >= 0; i--) {
if (_search_line(frame, i, p_string, -1, p_reverse_search)) {
return true;
}
}
} else {
- for (int i = 0; i < frame->lines.size(); i++) {
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
if (_search_line(frame, i, p_string, 0, p_reverse_search)) {
return true;
}
@@ -4008,12 +4245,12 @@ bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_f
bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search) {
ERR_FAIL_COND_V(p_frame == nullptr, false);
- ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), false);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), false);
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
String text;
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
switch (it->type) {
case ITEM_NEWLINE: {
@@ -4071,7 +4308,8 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p
int char_idx = p_search_previous ? -1 : 0;
int current_line = 0;
- int ending_line = main->lines.size() - 1;
+ int to_line = main->first_invalid_line.load();
+ int ending_line = to_line - 1;
if (p_from_selection && selection.active) {
// First check to see if other results exist in current line
char_idx = p_search_previous ? selection.from_char - 1 : selection.to_char;
@@ -4120,8 +4358,8 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p
while (current_line != ending_line) {
// Wrap around
if (current_line < 0) {
- current_line = main->lines.size() - 1;
- } else if (current_line >= main->lines.size()) {
+ current_line = to_line - 1;
+ } else if (current_line >= to_line) {
current_line = 0;
}
@@ -4143,12 +4381,13 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p
String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const {
String text;
+
ERR_FAIL_COND_V(p_frame == nullptr, text);
- ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), text);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), text);
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
int end_idx = 0;
if (it_to != nullptr) {
end_idx = it_to->index;
@@ -4163,7 +4402,7 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p
for (Item *E : table->subitems) {
ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E);
- for (int i = 0; i < frame->lines.size(); i++) {
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
text += _get_line_text(frame, i, p_selection);
}
}
@@ -4227,7 +4466,8 @@ String RichTextLabel::get_selected_text() const {
}
String text;
- for (int i = 0; i < main->lines.size(); i++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
text += _get_line_text(main, i, selection);
}
return text;
@@ -4368,19 +4608,23 @@ String RichTextLabel::get_parsed_text() const {
void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ _stop_thread();
+
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
update();
}
}
void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
+ _stop_thread();
+
st_parser = p_parser;
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
update();
}
}
@@ -4390,10 +4634,14 @@ TextServer::StructuredTextParser RichTextLabel::get_structured_text_bidi_overrid
}
void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) {
- st_args = p_args;
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
- update();
+ if (st_args != p_args) {
+ _stop_thread();
+
+ st_args = p_args;
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
+ update();
+ }
}
Array RichTextLabel::get_structured_text_bidi_override_options() const {
@@ -4406,9 +4654,11 @@ Control::TextDirection RichTextLabel::get_text_direction() const {
void RichTextLabel::set_language(const String &p_language) {
if (language != p_language) {
+ _stop_thread();
+
language = p_language;
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
update();
}
}
@@ -4419,9 +4669,11 @@ String RichTextLabel::get_language() const {
void RichTextLabel::set_autowrap_mode(RichTextLabel::AutowrapMode p_mode) {
if (autowrap_mode != p_mode) {
+ _stop_thread();
+
autowrap_mode = p_mode;
main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ _validate_line_caches();
update();
}
}
@@ -4432,6 +4684,8 @@ RichTextLabel::AutowrapMode RichTextLabel::get_autowrap_mode() const {
void RichTextLabel::set_percent_visible(float p_percent) {
if (percent_visible != p_percent) {
+ _stop_thread();
+
if (p_percent < 0 || p_percent >= 1) {
visible_characters = -1;
percent_visible = 1;
@@ -4440,8 +4694,8 @@ void RichTextLabel::set_percent_visible(float p_percent) {
percent_visible = p_percent;
}
if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) {
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
}
update();
}
@@ -4476,15 +4730,19 @@ void RichTextLabel::install_effect(const Variant effect) {
int RichTextLabel::get_content_height() const {
int total_height = 0;
- if (main->lines.size()) {
- total_height = main->lines[main->lines.size() - 1].offset.y + main->lines[main->lines.size() - 1].text_buf->get_size().y + main->lines[main->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
+ int to_line = main->first_invalid_line.load();
+ if (to_line) {
+ MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex());
+ total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
return total_height;
}
int RichTextLabel::get_content_width() const {
int total_width = 0;
- for (int i = 0; i < main->lines.size(); i++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
total_width = MAX(total_width, main->lines[i].offset.x + main->lines[i].text_buf->get_size().x);
}
return total_width;
@@ -4603,6 +4861,14 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text);
+ ClassDB::bind_method(D_METHOD("is_ready"), &RichTextLabel::is_ready);
+
+ ClassDB::bind_method(D_METHOD("set_threaded", "threaded"), &RichTextLabel::set_threaded);
+ ClassDB::bind_method(D_METHOD("is_threaded"), &RichTextLabel::is_threaded);
+
+ ClassDB::bind_method(D_METHOD("set_progress_bar_delay", "delay_ms"), &RichTextLabel::set_progress_bar_delay);
+ ClassDB::bind_method(D_METHOD("get_progress_bar_delay"), &RichTextLabel::get_progress_bar_delay);
+
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);
@@ -4643,6 +4909,9 @@ void RichTextLabel::_bind_methods() {
// Note: set "bbcode_enabled" first, to avoid unnecessary "text" resets.
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "threaded"), "set_threaded", "is_threaded");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "progress_bar_delay"), "set_progress_bar_delay", "get_progress_bar_delay");
+
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, "fit_content_height"), "set_fit_content_height", "is_fit_content_height_enabled");
@@ -4676,6 +4945,8 @@ void RichTextLabel::_bind_methods() {
ADD_SIGNAL(MethodInfo("meta_hover_started", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
ADD_SIGNAL(MethodInfo("meta_hover_ended", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
+ ADD_SIGNAL(MethodInfo("finished"));
+
BIND_ENUM_CONSTANT(AUTOWRAP_OFF);
BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY);
BIND_ENUM_CONSTANT(AUTOWRAP_WORD);
@@ -4727,15 +4998,19 @@ RichTextLabel::VisibleCharactersBehavior RichTextLabel::get_visible_characters_b
void RichTextLabel::set_visible_characters_behavior(RichTextLabel::VisibleCharactersBehavior p_behavior) {
if (visible_chars_behavior != p_behavior) {
+ _stop_thread();
+
visible_chars_behavior = p_behavior;
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
update();
}
}
void RichTextLabel::set_visible_characters(int p_visible) {
if (visible_characters != p_visible) {
+ _stop_thread();
+
visible_characters = p_visible;
if (p_visible == -1) {
percent_visible = 1;
@@ -4746,8 +5021,8 @@ void RichTextLabel::set_visible_characters(int p_visible) {
}
}
if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) {
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
}
update();
}
@@ -4759,7 +5034,9 @@ int RichTextLabel::get_visible_characters() const {
int RichTextLabel::get_character_line(int p_char) {
int line_count = 0;
- for (int i = 0; i < main->lines.size(); i++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
if (main->lines[i].char_offset < p_char && p_char <= main->lines[i].char_offset + main->lines[i].char_count) {
for (int j = 0; j < main->lines[i].text_buf->get_line_count(); j++) {
Vector2i range = main->lines[i].text_buf->get_line_range(j);
@@ -4777,7 +5054,8 @@ int RichTextLabel::get_character_line(int p_char) {
int RichTextLabel::get_character_paragraph(int p_char) {
int para_count = 0;
- for (int i = 0; i < main->lines.size(); i++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
if (main->lines[i].char_offset < p_char && p_char <= main->lines[i].char_offset + main->lines[i].char_count) {
return para_count;
} else {
@@ -4802,7 +5080,6 @@ int RichTextLabel::get_total_character_count() const {
}
it = _get_next_item(it, true);
}
-
return tc;
}
@@ -4812,7 +5089,8 @@ int RichTextLabel::get_total_glyph_count() const {
while (it) {
if (it->type == ITEM_FRAME) {
ItemFrame *f = static_cast<ItemFrame *>(it);
- for (int i = 0; i < f->lines.size(); i++) {
+ for (int i = 0; i < (int)f->lines.size(); i++) {
+ MutexLock lock(f->lines[i].text_buf->get_mutex());
tg += TS->shaped_text_get_glyph_count(f->lines[i].text_buf->get_rid());
}
}
@@ -4836,7 +5114,6 @@ Size2 RichTextLabel::get_minimum_size() const {
}
if (fit_content_height) {
- const_cast<RichTextLabel *>(this)->_validate_line_caches(main);
size.y += get_content_height();
}
@@ -5034,10 +5311,10 @@ RichTextLabel::RichTextLabel(const String &p_text) {
main->index = 0;
current = main;
main->lines.resize(1);
- main->lines.write[0].from = main;
- main->first_invalid_line = 0;
- main->first_resized_line = 0;
- main->first_invalid_font_line = 0;
+ main->lines[0].from = main;
+ main->first_invalid_line.store(0);
+ main->first_resized_line.store(0);
+ main->first_invalid_font_line.store(0);
current_frame = main;
vscroll = memnew(VScrollBar);
@@ -5052,10 +5329,13 @@ RichTextLabel::RichTextLabel(const String &p_text) {
vscroll->hide();
set_text(p_text);
+ updating.store(false);
+ stop_thread.store(false);
set_clip_contents(true);
}
RichTextLabel::~RichTextLabel() {
+ _stop_thread();
memdelete(main);
}
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index c6d0d0875d..7fbd5f1745 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -141,10 +141,10 @@ private:
struct ItemFrame : public Item {
bool cell = false;
- Vector<Line> lines;
- int first_invalid_line = 0;
- int first_invalid_font_line = 0;
- int first_resized_line = 0;
+ LocalVector<Line> lines;
+ std::atomic<int> first_invalid_line;
+ std::atomic<int> first_invalid_font_line;
+ std::atomic<int> first_resized_line;
ItemFrame *parent_frame = nullptr;
@@ -155,7 +155,12 @@ private:
Size2 max_size_over = Size2(-1, -1);
Rect2 padding;
- ItemFrame() { type = ITEM_FRAME; }
+ ItemFrame() {
+ type = ITEM_FRAME;
+ first_invalid_line.store(0);
+ first_invalid_font_line.store(0);
+ first_resized_line.store(0);
+ }
};
struct ItemText : public Item {
@@ -263,8 +268,8 @@ private:
int width = 0;
};
- Vector<Column> columns;
- Vector<float> rows;
+ LocalVector<Column> columns;
+ LocalVector<float> rows;
int total_width = 0;
int total_height = 0;
@@ -363,6 +368,16 @@ private:
Item *current = nullptr;
ItemFrame *current_frame = nullptr;
+ Thread thread;
+ Mutex data_mutex;
+ bool threaded = false;
+ std::atomic<bool> stop_thread;
+ std::atomic<bool> updating;
+ std::atomic<double> loaded;
+
+ uint64_t loading_started = 0;
+ int progress_delay = 1000;
+
VScrollBar *vscroll = nullptr;
AutowrapMode autowrap_mode = AUTOWRAP_WORD_SMART;
@@ -392,7 +407,11 @@ private:
Array custom_effects;
void _invalidate_current_line(ItemFrame *p_frame);
- void _validate_line_caches(ItemFrame *p_frame);
+
+ static void _thread_function(void *self);
+ void _stop_thread();
+ bool _validate_line_caches();
+ void _process_line_caches();
void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false);
void _remove_item(Item *p_item, const int p_line, const int p_subitem_line);
@@ -446,8 +465,9 @@ private:
bool _search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search);
bool _search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search);
- void _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset);
- void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width);
+ float _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset);
+ float _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h);
+
void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false);
@@ -480,9 +500,9 @@ private:
bool _find_layout_subitem(Item *from, Item *to);
void _fetch_item_fx_stack(Item *p_item, Vector<ItemFX *> &r_stack);
- void _update_scroll();
void _update_fx(ItemFrame *p_frame, double p_delta_time);
void _scroll_changed(double);
+ int _find_first_line(int p_from, int p_to, int p_vofs) const;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual String get_tooltip(const Point2 &p_pos) const override;
@@ -611,6 +631,14 @@ public:
bool is_deselect_on_focus_loss_enabled() const;
void deselect();
+ bool is_ready() const;
+
+ void set_threaded(bool p_threaded);
+ bool is_threaded() const;
+
+ void set_progress_bar_delay(int p_delay_ms);
+ int get_progress_bar_delay() const;
+
// Context menu.
PopupMenu *get_menu() const;
bool is_menu_visible() const;
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index b96ba0ebf9..fec8d11bef 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -862,6 +862,7 @@ void TabBar::_update_hover() {
void TabBar::_update_cache() {
if (tabs.is_empty()) {
+ buttons_visible = false;
return;
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 1a439a5c1d..8e948203f1 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1557,6 +1557,47 @@ void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
}
}
+bool TextEdit::alt_input(const Ref<InputEvent> &p_gui_input) {
+ Ref<InputEventKey> k = p_gui_input;
+ if (k.is_valid()) {
+ if (!k->is_pressed()) {
+ if (alt_start && k->get_keycode() == Key::ALT) {
+ alt_start = false;
+ if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+ handle_unicode_input(alt_code);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ if (k->is_alt_pressed()) {
+ if (!alt_start) {
+ if (k->get_keycode() == Key::KP_ADD) {
+ alt_start = true;
+ alt_code = 0;
+ return true;
+ }
+ } else {
+ if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
+ }
+ if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
+ }
+ if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
ERR_FAIL_COND(p_gui_input.is_null());
@@ -1865,6 +1906,10 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
Ref<InputEventKey> k = p_gui_input;
if (k.is_valid()) {
+ if (alt_input(p_gui_input)) {
+ accept_event();
+ return;
+ }
if (!k->is_pressed()) {
return;
}
@@ -2388,7 +2433,7 @@ void TextEdit::_move_caret_page_down(bool p_select) {
}
void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
- if (!editable || (caret.column == 0 && caret.line == 0)) {
+ if (!editable || (caret.column == 0 && caret.line == 0 && !has_selection())) {
return;
}
@@ -2407,17 +2452,17 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
if (p_word) {
int column = caret.column;
// Check for the case "<word><space><caret>" and ignore the space.
- // No need to check for column being 0 since it is cheked above.
+ // No need to check for column being 0 since it is checked above.
if (is_whitespace(text[caret.line][caret.column - 1])) {
column -= 1;
}
// Get a list with the indices of the word bounds of the given text line.
const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
if (words.is_empty() || column <= words[0]) {
- // If "words" is empty, meaning no words are left, we can remove everything until the begining of the line.
+ // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line.
column = 0;
} else {
- // Otherwise search for the first word break that is smaller than the index from we're currentlu deleteing
+ // Otherwise search for the first word break that is smaller than the index from which we're currently deleting.
for (int i = words.size() - 2; i >= 0; i = i - 2) {
if (words[i] < column) {
column = words[i];
@@ -3035,7 +3080,7 @@ void TextEdit::set_line(int p_line, const String &p_new_text) {
_remove_text(p_line, 0, p_line, text[p_line].length());
_insert_text(p_line, 0, p_new_text);
if (caret.line == p_line && caret.column > p_new_text.length()) {
- set_caret_column(MIN(caret.column, p_new_text.length()), false);
+ set_caret_column(p_new_text.length(), false);
}
if (has_selection() && p_line == selection.to_line && selection.to_column > text[p_line].length()) {
selection.to_column = text[p_line].length();
@@ -4711,9 +4756,7 @@ void TextEdit::add_gutter(int p_at) {
gutters.insert(p_at, GutterInfo());
}
- for (int i = 0; i < text.size() + 1; i++) {
- text.add_gutter(p_at);
- }
+ text.add_gutter(p_at);
emit_signal(SNAME("gutter_added"));
update();
}
@@ -4723,9 +4766,7 @@ void TextEdit::remove_gutter(int p_gutter) {
gutters.remove_at(p_gutter);
- for (int i = 0; i < text.size() + 1; i++) {
- text.remove_gutter(p_gutter);
- }
+ text.remove_gutter(p_gutter);
emit_signal(SNAME("gutter_removed"));
update();
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index a0ae9631f9..993203bee6 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -247,6 +247,9 @@ private:
bool setting_text = false;
+ bool alt_start = false;
+ uint32_t alt_code = 0;
+
// Text properties.
String ime_text = "";
Point2 ime_selection;
@@ -353,7 +356,7 @@ private:
Vector<int> last_visible_chars;
};
- Map<int, LineDrawingCache> line_drawing_cache;
+ HashMap<int, LineDrawingCache> line_drawing_cache;
int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const;
@@ -508,7 +511,6 @@ private:
/* Syntax highlighting. */
Ref<SyntaxHighlighter> syntax_highlighter;
- Map<int, Dictionary> syntax_highlighting_cache;
Dictionary _get_line_syntax_highlighting(int p_line);
@@ -625,6 +627,7 @@ public:
/* General overrides. */
virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
+ bool alt_input(const Ref<InputEvent> &p_gui_input);
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/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index f79c68671c..081d065efe 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -650,8 +650,8 @@ void TextureProgressBar::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_progress"), "set_tint_progress", "get_tint_progress");
ADD_GROUP("Radial Fill", "radial_");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_radial_initial_angle", "get_radial_initial_angle");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_fill_degrees", "get_fill_degrees");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider,degrees"), "set_radial_initial_angle", "get_radial_initial_angle");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider,degrees"), "set_fill_degrees", "get_fill_degrees");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "radial_center_offset"), "set_radial_center_offset", "get_radial_center_offset");
BIND_ENUM_CONSTANT(FILL_LEFT_TO_RIGHT);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 89807dbe95..d3e7540790 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -544,6 +544,21 @@ bool TreeItem::is_collapsed() {
return collapsed;
}
+void TreeItem::set_visible(bool p_visible) {
+ if (visible == p_visible) {
+ return;
+ }
+ visible = p_visible;
+ if (tree) {
+ tree->update();
+ _changed_notify();
+ }
+}
+
+bool TreeItem::is_visible() {
+ return visible;
+}
+
void TreeItem::uncollapse_tree() {
TreeItem *t = this;
while (t) {
@@ -646,7 +661,7 @@ TreeItem *TreeItem::get_first_child() const {
return first_child;
}
-TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
+TreeItem *TreeItem::_get_prev_visible(bool p_wrap) {
TreeItem *current = this;
TreeItem *prev = current->get_prev();
@@ -682,7 +697,21 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
return current;
}
-TreeItem *TreeItem::get_next_visible(bool p_wrap) {
+TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
+ TreeItem *loop = this;
+ TreeItem *prev = this->_get_prev_visible(p_wrap);
+ while (prev && !prev->is_visible()) {
+ prev = prev->_get_prev_visible(p_wrap);
+ if (prev == loop) {
+ // Check that we haven't looped all the way around to the start.
+ prev = nullptr;
+ break;
+ }
+ }
+ return prev;
+}
+
+TreeItem *TreeItem::_get_next_visible(bool p_wrap) {
TreeItem *current = this;
if (!current->collapsed && current->first_child) {
@@ -709,12 +738,37 @@ TreeItem *TreeItem::get_next_visible(bool p_wrap) {
return current;
}
+TreeItem *TreeItem::get_next_visible(bool p_wrap) {
+ TreeItem *loop = this;
+ TreeItem *next = this->_get_next_visible(p_wrap);
+ while (next && !next->is_visible()) {
+ next = next->_get_next_visible(p_wrap);
+ if (next == loop) {
+ // Check that we haven't looped all the way around to the start.
+ next = nullptr;
+ break;
+ }
+ }
+ return next;
+}
+
TreeItem *TreeItem::get_child(int p_idx) {
_create_children_cache();
ERR_FAIL_INDEX_V(p_idx, children_cache.size(), nullptr);
return children_cache.get(p_idx);
}
+int TreeItem::get_visible_child_count() {
+ _create_children_cache();
+ int visible_count = 0;
+ for (int i = 0; i < children_cache.size(); i++) {
+ if (children_cache[i]->is_visible()) {
+ visible_count += 1;
+ }
+ }
+ return visible_count;
+}
+
int TreeItem::get_child_count() {
_create_children_cache();
return children_cache.size();
@@ -1256,6 +1310,9 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed);
ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed);
+ ClassDB::bind_method(D_METHOD("set_visible", "enable"), &TreeItem::set_visible);
+ ClassDB::bind_method(D_METHOD("is_visible"), &TreeItem::is_visible);
+
ClassDB::bind_method(D_METHOD("uncollapse_tree"), &TreeItem::uncollapse_tree);
ClassDB::bind_method(D_METHOD("set_custom_minimum_height", "height"), &TreeItem::set_custom_minimum_height);
@@ -1340,6 +1397,7 @@ void TreeItem::_bind_methods() {
}
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_folding"), "set_disable_folding", "is_folding_disabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_minimum_height", PROPERTY_HINT_RANGE, "0,1000,1"), "set_custom_minimum_height", "get_custom_minimum_height");
@@ -1445,7 +1503,7 @@ void Tree::update_cache() {
}
int Tree::compute_item_height(TreeItem *p_item) const {
- if (p_item == root && hide_root) {
+ if ((p_item == root && hide_root) || !p_item->is_visible()) {
return 0;
}
@@ -1506,6 +1564,9 @@ int Tree::compute_item_height(TreeItem *p_item) const {
}
int Tree::get_item_height(TreeItem *p_item) const {
+ if (!p_item->is_visible()) {
+ return 0;
+ }
int height = compute_item_height(p_item);
height += cache.vseparation;
@@ -1686,6 +1747,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
return -1; //draw no more!
}
+ if (!p_item->is_visible()) {
+ return 0;
+ }
+
RID ci = get_canvas_item();
int htotal = 0;
@@ -1754,19 +1819,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = p_item->cells[i].buttons[j].texture;
Size2 s = b->get_size() + cache.button_pressed->get_minimum_size();
- if (s.height < label_h) {
- s.height = label_h;
- }
Point2i o = Point2i(ofs + w - s.width, p_pos.y) - cache.offset + p_draw_ofs;
if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) {
- //being pressed
+ // Being pressed.
Point2 od = o;
if (rtl) {
od.x = get_size().width - od.x - s.x;
}
- cache.button_pressed->draw(get_canvas_item(), Rect2(od, s));
+ cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, s.width, MAX(s.height, label_h)));
}
o.y += (label_h - s.height) / 2;
@@ -2059,7 +2121,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
- if (!p_item->disable_folding && !hide_folding && p_item->first_child) { //has children, draw the guide box
+ if (!p_item->disable_folding && !hide_folding && p_item->first_child && p_item->get_visible_child_count() != 0) { //has visible children, draw the guide box
Ref<Texture2D> arrow;
@@ -2102,12 +2164,12 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
// Draw relationship lines.
- if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) {
+ if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible()) {
int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
int parent_ofs = p_pos.x + cache.item_margin;
Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs;
- if (c->get_first_child() != nullptr) {
+ if (c->get_visible_child_count() > 0) {
root_pos -= Point2i(cache.arrow->get_width(), 0);
}
@@ -2385,6 +2447,11 @@ void Tree::_range_click_timeout() {
}
int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod) {
+ if (p_item && !p_item->is_visible()) {
+ // Skip any processing of invisible items.
+ return 0;
+ }
+
int item_h = compute_item_height(p_item) + cache.vseparation;
bool skip = (p_item == root && hide_root);
@@ -2494,7 +2561,6 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
cache.click_column = col;
cache.click_pos = click_pos;
update();
- //emit_signal(SNAME("button_pressed"));
return -1;
}
@@ -2516,9 +2582,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
if (!c.selected || p_button == MouseButton::RIGHT) {
p_item->select(col);
emit_signal(SNAME("multi_selected"), p_item, col, true);
- if (p_button == MouseButton::RIGHT) {
- emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position());
- }
+ emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button);
//p_item->selected_signal.call(col);
} else {
@@ -2533,9 +2597,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
bool inrange = false;
select_single_item(p_item, root, col, selected_item, &inrange);
- if (p_button == MouseButton::RIGHT) {
- emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position());
- }
+ emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button);
} else {
int icount = _count_selected_items(root);
@@ -2547,9 +2609,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
select_single_item(p_item, root, col);
}
- if (p_button == MouseButton::RIGHT) {
- emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position());
- }
+ emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button);
}
}
@@ -2586,11 +2646,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
if (force_edit_checkbox_only_on_checkbox) {
if (x < cache.checked->get_width()) {
p_item->set_checked(col, !c.checked);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
}
} else {
p_item->set_checked(col, !c.checked);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
}
click_handled = true;
//p_item->edited_signal.call(col);
@@ -2632,17 +2692,17 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
p_item->set_range(col, c.val + (up ? 1.0 : -1.0) * c.step);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
} else if (p_button == MouseButton::RIGHT) {
p_item->set_range(col, (up ? c.max : c.min));
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
} else if (p_button == MouseButton::WHEEL_UP) {
p_item->set_range(col, c.val + c.step);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
} else if (p_button == MouseButton::WHEEL_DOWN) {
p_item->set_range(col, c.val - c.step);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
}
//p_item->edited_signal.call(col);
@@ -2673,7 +2733,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
if (!p_item->cells[col].custom_button || !on_arrow) {
- item_edited(col, p_item, p_button == MouseButton::LEFT);
+ item_edited(col, p_item, p_button);
}
click_handled = true;
return -1;
@@ -2720,8 +2780,8 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
item_h += child_h;
}
}
- if (p_item == root && p_button == MouseButton::RIGHT) {
- emit_signal(SNAME("empty_rmb"), get_local_mouse_position());
+ if (p_item == root) {
+ emit_signal(SNAME("empty_clicked"), get_local_mouse_position(), p_button);
}
}
@@ -3129,7 +3189,6 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
Ref<InputEventMouseMotion> mm = p_event;
-
if (mm.is_valid()) {
if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff
update_cache();
@@ -3259,18 +3318,17 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
}
- Ref<InputEventMouseButton> b = p_event;
-
- if (b.is_valid()) {
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff
update_cache();
}
bool rtl = is_layout_rtl();
- if (!b->is_pressed()) {
- if (b->get_button_index() == MouseButton::LEFT) {
- Point2 pos = b->get_position();
+ if (!mb->is_pressed()) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
+ Point2 pos = mb->get_position();
if (rtl) {
pos.x = get_size().width - pos.x;
}
@@ -3305,7 +3363,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
warp_mouse(range_drag_capture_pos);
} else {
Rect2 rect = get_selected()->get_meta("__focus_rect");
- Point2 mpos = b->get_position();
+ Point2 mpos = mb->get_position();
int icon_size_x = 0;
Ref<Texture2D> icon = get_selected()->get_icon(selected_col);
if (icon.is_valid()) {
@@ -3333,17 +3391,6 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
pressing_for_editor = false;
}
- if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item != nullptr) {
- // make sure in case of wrong reference after reconstructing whole TreeItems
- cache.click_item = get_item_at_position(cache.click_pos);
- emit_signal(SNAME("button_pressed"), cache.click_item, cache.click_column, cache.click_id);
- }
- cache.click_type = Cache::CLICK_NONE;
- cache.click_index = -1;
- cache.click_id = -1;
- cache.click_item = nullptr;
- cache.click_column = 0;
-
if (drag_touching) {
if (drag_speed == 0) {
drag_touching_deaccel = false;
@@ -3353,8 +3400,20 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
drag_touching_deaccel = true;
}
}
- update();
}
+
+ if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item != nullptr) {
+ // make sure in case of wrong reference after reconstructing whole TreeItems
+ cache.click_item = get_item_at_position(cache.click_pos);
+ emit_signal("button_clicked", cache.click_item, cache.click_column, cache.click_id, mb->get_button_index());
+ }
+
+ cache.click_type = Cache::CLICK_NONE;
+ cache.click_index = -1;
+ cache.click_id = -1;
+ cache.click_item = nullptr;
+ cache.click_column = 0;
+ update();
return;
}
@@ -3362,12 +3421,12 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- switch (b->get_button_index()) {
+ switch (mb->get_button_index()) {
case MouseButton::RIGHT:
case MouseButton::LEFT: {
Ref<StyleBox> bg = cache.bg;
- Point2 pos = b->get_position();
+ Point2 pos = mb->get_position();
if (rtl) {
pos.x = get_size().width - pos.x;
}
@@ -3377,7 +3436,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
pos.y -= _get_title_button_height();
if (pos.y < 0) {
- if (b->get_button_index() == MouseButton::LEFT) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
pos.x += cache.offset.x;
int len = 0;
for (int i = 0; i < columns.size(); i++) {
@@ -3394,10 +3453,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
break;
}
}
+
if (!root || (!root->get_first_child() && hide_root)) {
- if (b->get_button_index() == MouseButton::RIGHT && allow_rmb_select) {
- emit_signal(SNAME("empty_tree_rmb_selected"), get_local_mouse_position());
- }
break;
}
@@ -3412,17 +3469,17 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
cache.rtl = is_layout_rtl();
blocked++;
- propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, b->is_double_click(), root, b->get_button_index(), b);
+ propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, mb->is_double_click(), root, mb->get_button_index(), mb);
blocked--;
if (pressing_for_editor) {
- pressing_pos = b->get_position();
+ pressing_pos = mb->get_position();
if (rtl) {
pressing_pos.x = get_size().width - pressing_pos.x;
}
}
- if (b->get_button_index() == MouseButton::RIGHT) {
+ if (mb->get_button_index() == MouseButton::RIGHT) {
break;
}
@@ -3445,8 +3502,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
set_physics_process_internal(true);
}
- if (b->get_button_index() == MouseButton::LEFT) {
- if (get_item_at_position(b->get_position()) == nullptr && !b->is_shift_pressed() && !b->is_ctrl_pressed() && !b->is_command_pressed()) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
+ if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && !mb->is_command_pressed()) {
emit_signal(SNAME("nothing_selected"));
}
}
@@ -3460,7 +3517,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::WHEEL_UP: {
double prev_value = v_scroll->get_value();
- v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8);
+ v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * mb->get_factor() / 8);
if (v_scroll->get_value() != prev_value) {
accept_event();
}
@@ -3468,7 +3525,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::WHEEL_DOWN: {
double prev_value = v_scroll->get_value();
- v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8);
+ v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * mb->get_factor() / 8);
if (v_scroll->get_value() != prev_value) {
accept_event();
}
@@ -3914,16 +3971,15 @@ TreeItem *Tree::get_last_item() const {
return last;
}
-void Tree::item_edited(int p_column, TreeItem *p_item, bool p_lmb) {
+void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mouse_index) {
edited_item = p_item;
edited_col = p_column;
if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) {
edited_item->cells.write[p_column].dirty = true;
}
- if (p_lmb) {
- emit_signal(SNAME("item_edited"));
- } else {
- emit_signal(SNAME("item_rmb_edited"));
+ emit_signal(SNAME("item_edited"));
+ if (p_custom_mouse_index != MouseButton::NONE) {
+ emit_signal(SNAME("custom_item_clicked"), p_custom_mouse_index);
}
}
@@ -4150,7 +4206,7 @@ int Tree::get_column_minimum_width(int p_column) const {
depth += 1;
} else {
TreeItem *common_parent = item->get_parent();
- while (common_parent != next->get_parent()) {
+ while (common_parent != next->get_parent() && common_parent) {
common_parent = common_parent->get_parent();
depth -= 1;
}
@@ -4467,7 +4523,8 @@ Point2 Tree::get_scroll() const {
}
void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) {
- if (!is_visible_in_tree()) {
+ ERR_FAIL_NULL(p_item);
+ if (!is_visible_in_tree() || !p_item->is_visible()) {
return; // Hack to work around crash in get_item_rect() if Tree is not in tree.
}
@@ -4591,7 +4648,7 @@ void Tree::_do_incr_search(const String &p_add) {
TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int &section) const {
Point2 pos = p_pos;
- if (root != p_item || !hide_root) {
+ if ((root != p_item || !hide_root) && p_item->is_visible()) {
h = compute_item_height(p_item) + cache.vseparation;
if (pos.y < h) {
if (drop_mode_flags == DROP_MODE_ON_ITEM) {
@@ -4624,7 +4681,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
h = 0;
}
- if (p_item->is_collapsed()) {
+ if (p_item->is_collapsed() || !p_item->is_visible()) {
return nullptr; // do not try children, it's collapsed
}
@@ -4968,17 +5025,15 @@ void Tree::_bind_methods() {
ADD_SIGNAL(MethodInfo("item_selected"));
ADD_SIGNAL(MethodInfo("cell_selected"));
ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::BOOL, "selected")));
- ADD_SIGNAL(MethodInfo("item_rmb_selected", PropertyInfo(Variant::VECTOR2, "position")));
- ADD_SIGNAL(MethodInfo("empty_rmb", PropertyInfo(Variant::VECTOR2, "position")));
- ADD_SIGNAL(MethodInfo("empty_tree_rmb_selected", PropertyInfo(Variant::VECTOR2, "position")));
+ ADD_SIGNAL(MethodInfo("item_mouse_selected", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index")));
+ ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index")));
ADD_SIGNAL(MethodInfo("item_edited"));
- ADD_SIGNAL(MethodInfo("item_rmb_edited"));
+ ADD_SIGNAL(MethodInfo("custom_item_clicked", PropertyInfo(Variant::INT, "mouse_button_index")));
ADD_SIGNAL(MethodInfo("item_custom_button_pressed"));
ADD_SIGNAL(MethodInfo("item_double_clicked"));
ADD_SIGNAL(MethodInfo("item_collapsed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem")));
ADD_SIGNAL(MethodInfo("check_propagated_to_item", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column")));
- //ADD_SIGNAL( MethodInfo("item_double_clicked" ) );
- ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id")));
+ ADD_SIGNAL(MethodInfo("button_clicked", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "mouse_button_index")));
ADD_SIGNAL(MethodInfo("custom_popup_edited", PropertyInfo(Variant::BOOL, "arrow_clicked")));
ADD_SIGNAL(MethodInfo("item_activated"));
ADD_SIGNAL(MethodInfo("column_title_pressed", PropertyInfo(Variant::INT, "column")));
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 8ee2a3c382..0a8dd3204a 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -124,6 +124,7 @@ private:
Vector<Cell> cells;
bool collapsed = false; // won't show children
+ bool visible = true;
bool disable_folding = false;
int custom_min_height = 0;
@@ -209,6 +210,9 @@ private:
void _propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal);
void _propagate_check_through_parents(int p_column, bool p_emit_signal);
+ TreeItem *_get_prev_visible(bool p_wrap = false);
+ TreeItem *_get_next_visible(bool p_wrap = false);
+
public:
void set_text(int p_column, String p_text);
String get_text(int p_column) const;
@@ -273,6 +277,9 @@ public:
void set_collapsed(bool p_collapsed);
bool is_collapsed();
+ void set_visible(bool p_visible);
+ bool is_visible();
+
void uncollapse_tree();
void set_custom_minimum_height(int p_height);
@@ -335,6 +342,7 @@ public:
TreeItem *get_next_visible(bool p_wrap = false);
TreeItem *get_child(int p_idx);
+ int get_visible_child_count();
int get_child_count();
Array get_children();
int get_index();
@@ -466,7 +474,7 @@ private:
void _notification(int p_what);
- void item_edited(int p_column, TreeItem *p_item, bool p_lmb = true);
+ void item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mouse_index = MouseButton::NONE);
void item_changed(int p_column, TreeItem *p_item);
void item_selected(int p_column, TreeItem *p_item);
void item_deselected(int p_column, TreeItem *p_item);
diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp
index ca2dad71af..20bc9a1028 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.cpp
@@ -443,7 +443,7 @@ void VideoStreamPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_track", PROPERTY_HINT_RANGE, "0,128,1"), "set_audio_track", "get_audio_track");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VideoStream"), "set_stream", "get_stream");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,0.01"), "set_volume_db", "get_volume_db");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,0.01,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume", PROPERTY_HINT_RANGE, "0,15,0.01,exp", PROPERTY_USAGE_NONE), "set_volume", "get_volume");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "has_autoplay");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused"), "set_paused", "is_paused");