summaryrefslogtreecommitdiffstats
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/base_button.cpp2
-rw-r--r--scene/gui/color_picker.cpp29
-rw-r--r--scene/gui/control.cpp12
-rw-r--r--scene/gui/control.h2
-rw-r--r--scene/gui/dialogs.cpp3
-rw-r--r--scene/gui/dialogs.h2
-rw-r--r--scene/gui/graph_edit.cpp2
-rw-r--r--scene/gui/graph_edit_arranger.cpp28
-rw-r--r--scene/gui/item_list.cpp8
-rw-r--r--scene/gui/label.cpp43
-rw-r--r--scene/gui/label.h4
-rw-r--r--scene/gui/line_edit.cpp23
-rw-r--r--scene/gui/menu_bar.cpp53
-rw-r--r--scene/gui/popup.cpp3
-rw-r--r--scene/gui/popup.h3
-rw-r--r--scene/gui/popup_menu.cpp71
-rw-r--r--scene/gui/popup_menu.h9
-rw-r--r--scene/gui/rich_text_label.cpp127
-rw-r--r--scene/gui/tab_bar.cpp38
-rw-r--r--scene/gui/tab_container.cpp43
-rw-r--r--scene/gui/tab_container.h2
-rw-r--r--scene/gui/text_edit.cpp20
-rw-r--r--scene/gui/tree.cpp20
-rw-r--r--scene/gui/video_stream_player.cpp10
-rw-r--r--scene/gui/view_panner.cpp11
25 files changed, 424 insertions, 144 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index f57afb66b3..66b14dc967 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -380,7 +380,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
queue_redraw();
accept_event();
- if (shortcut_feedback) {
+ if (shortcut_feedback && is_inside_tree()) {
if (shortcut_feedback_timer == nullptr) {
shortcut_feedback_timer = memnew(Timer);
shortcut_feedback_timer->set_one_shot(true);
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 669ce11e5d..1d5c1c0ade 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -74,7 +74,7 @@ void ColorPicker::_notification(int p_what) {
sliders[i]->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
}
alpha_label->set_custom_minimum_size(Size2(theme_cache.label_width, 0));
- alpha_label->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
+ alpha_slider->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
for (int i = 0; i < MODE_BUTTON_COUNT; i++) {
mode_btns[i]->begin_bulk_theme_override();
@@ -89,6 +89,10 @@ void ColorPicker::_notification(int p_what) {
shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_VHS_CIRCLE), theme_cache.shape_circle);
shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_OKHSL_CIRCLE), theme_cache.shape_circle);
+ if (current_shape != SHAPE_NONE) {
+ btn_shape->set_icon(shape_popup->get_item_icon(current_shape));
+ }
+
internal_margin->begin_bulk_theme_override();
internal_margin->add_theme_constant_override(SNAME("margin_bottom"), theme_cache.content_margin);
internal_margin->add_theme_constant_override(SNAME("margin_left"), theme_cache.content_margin);
@@ -552,16 +556,17 @@ void ColorPicker::_html_submitted(const String &p_html) {
return;
}
- const Color previous_color = color;
- color = Color::from_string(p_html.strip_edges(), previous_color);
+ Color new_color = Color::from_string(p_html.strip_edges(), color);
if (!is_editing_alpha()) {
- color.a = previous_color.a;
+ new_color.a = color.a;
}
- if (color == previous_color) {
+ if (new_color.to_argb32() == color.to_argb32()) {
return;
}
+ color = new_color;
+
if (!is_inside_tree()) {
return;
}
@@ -689,6 +694,12 @@ void ColorPicker::set_picker_shape(PickerShapeType p_shape) {
current_shape = p_shape;
+#ifdef TOOLS_ENABLED
+ if (editor_settings) {
+ editor_settings->call(SNAME("set_project_metadata"), "color_picker", "picker_shape", current_shape);
+ }
+#endif
+
_copy_color_to_hsv();
_update_controls();
@@ -923,6 +934,12 @@ void ColorPicker::set_color_mode(ColorModeType p_mode) {
current_mode = p_mode;
+#ifdef TOOLS_ENABLED
+ if (editor_settings) {
+ editor_settings->call(SNAME("set_project_metadata"), "color_picker", "color_mode", current_mode);
+ }
+#endif
+
if (!is_inside_tree()) {
return;
}
@@ -1792,8 +1809,6 @@ ColorPicker::ColorPicker() {
shape_popup->set_item_checked(current_shape, true);
shape_popup->connect("id_pressed", callable_mp(this, &ColorPicker::set_picker_shape));
- btn_shape->set_icon(shape_popup->get_item_icon(current_shape));
-
add_mode(new ColorModeRGB(this));
add_mode(new ColorModeHSV(this));
add_mode(new ColorModeRAW(this));
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index c7ff5980cb..ed54bd000c 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1653,6 +1653,7 @@ void Control::set_custom_minimum_size(const Size2 &p_custom) {
data.custom_minimum_size = p_custom;
update_minimum_size();
+ update_configuration_warnings();
}
Size2 Control::get_custom_minimum_size() const {
@@ -1831,9 +1832,18 @@ bool Control::has_point(const Point2 &p_point) const {
void Control::set_mouse_filter(MouseFilter p_filter) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX(p_filter, 3);
+
+ if (data.mouse_filter == p_filter) {
+ return;
+ }
+
data.mouse_filter = p_filter;
notify_property_list_changed();
update_configuration_warnings();
+
+ if (get_viewport()) {
+ get_viewport()->_gui_update_mouse_over();
+ }
}
Control::MouseFilter Control::get_mouse_filter() const {
@@ -3568,6 +3578,8 @@ void Control::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_RESIZED);
BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER);
BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT);
+ BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER_SELF);
+ BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT_SELF);
BIND_CONSTANT(NOTIFICATION_FOCUS_ENTER);
BIND_CONSTANT(NOTIFICATION_FOCUS_EXIT);
BIND_CONSTANT(NOTIFICATION_THEME_CHANGED);
diff --git a/scene/gui/control.h b/scene/gui/control.h
index abbdc42fa4..db1bd3a346 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -368,6 +368,8 @@ public:
NOTIFICATION_SCROLL_BEGIN = 47,
NOTIFICATION_SCROLL_END = 48,
NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49,
+ NOTIFICATION_MOUSE_ENTER_SELF = 60,
+ NOTIFICATION_MOUSE_EXIT_SELF = 61,
};
// Editor plugin interoperability.
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index 957a8f276e..3e827e76dc 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -42,6 +42,7 @@ void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) {
if (close_on_escape && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
_cancel_pressed();
}
+ Window::_input_from_window(p_event);
}
void AcceptDialog::_parent_focused() {
@@ -428,8 +429,6 @@ AcceptDialog::AcceptDialog() {
ok_button->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed));
set_title(TTRC("Alert!"));
-
- connect("window_input", callable_mp(this, &AcceptDialog::_input_from_window));
}
AcceptDialog::~AcceptDialog() {
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
index d5cbaaeef8..e28d6b7467 100644
--- a/scene/gui/dialogs.h
+++ b/scene/gui/dialogs.h
@@ -65,11 +65,11 @@ class AcceptDialog : public Window {
static bool swap_cancel_ok;
- void _input_from_window(const Ref<InputEvent> &p_event);
void _parent_focused();
protected:
virtual Size2 _get_contents_minimum_size() const override;
+ virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 8dddbf78cf..69023d2056 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -2015,7 +2015,7 @@ GraphEdit::GraphEdit() {
top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
connections_layer = memnew(Control);
- add_child(connections_layer, false);
+ add_child(connections_layer, false, INTERNAL_MODE_FRONT);
connections_layer->connect("draw", callable_mp(this, &GraphEdit::_connections_layer_draw));
connections_layer->set_name("_connection_layer");
connections_layer->set_disable_visibility_clip(true); // Necessary, so it can draw freely and be offset.
diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp
index 1dc778254b..3f2007f7e0 100644
--- a/scene/gui/graph_edit_arranger.cpp
+++ b/scene/gui/graph_edit_arranger.cpp
@@ -65,6 +65,9 @@ void GraphEditArranger::arrange_nodes() {
float gap_v = 100.0f;
float gap_h = 100.0f;
+ List<GraphEdit::Connection> connection_list;
+ graph_edit->get_connection_list(&connection_list);
+
for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) {
GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i));
if (!graph_element) {
@@ -74,8 +77,6 @@ void GraphEditArranger::arrange_nodes() {
if (graph_element->is_selected() || arrange_entire_graph) {
selected_nodes.insert(graph_element->get_name());
HashSet<StringName> s;
- List<GraphEdit::Connection> connection_list;
- graph_edit->get_connection_list(&connection_list);
for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) {
GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from_node]);
if (E->get().to_node == graph_element->get_name() && (p_from->is_selected() || arrange_entire_graph) && E->get().to_node != E->get().from_node) {
@@ -85,12 +86,6 @@ void GraphEditArranger::arrange_nodes() {
String s_connection = String(p_from->get_name()) + " " + String(E->get().to_node);
StringName _connection(s_connection);
Pair<int, int> ports(E->get().from_port, E->get().to_port);
- if (port_info.has(_connection)) {
- Pair<int, int> p_ports = port_info[_connection];
- if (p_ports.first < ports.first) {
- ports = p_ports;
- }
- }
port_info.insert(_connection, ports);
}
}
@@ -216,13 +211,14 @@ int GraphEditArranger::_set_operations(SET_OPERATIONS p_operation, HashSet<Strin
return 1;
} break;
case GraphEditArranger::DIFFERENCE: {
- for (HashSet<StringName>::Iterator E = r_u.begin(); E;) {
- HashSet<StringName>::Iterator N = E;
- ++N;
- if (r_v.has(*E)) {
- r_u.remove(E);
+ Vector<StringName> common;
+ for (const StringName &E : r_u) {
+ if (r_v.has(E)) {
+ common.append(E);
}
- E = N;
+ }
+ for (const StringName &E : common) {
+ r_u.erase(E);
}
return r_u.size();
} break;
@@ -260,9 +256,7 @@ HashMap<int, Vector<StringName>> GraphEditArranger::_layering(const HashSet<Stri
selected = true;
t.append_array(l[current_layer]);
l.insert(current_layer, t);
- HashSet<StringName> V;
- V.insert(E);
- _set_operations(GraphEditArranger::UNION, u, V);
+ u.insert(E);
}
}
if (!selected) {
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 343301e9c4..02d44caa1c 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -1431,11 +1431,11 @@ void ItemList::force_update_list_size() {
}
}
- for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) {
- items.write[j].rect_cache.size.y = max_h;
- }
-
if (all_fit) {
+ for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) {
+ items.write[j].rect_cache.size.y = max_h;
+ }
+
float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height);
float max = MAX(page, ofs.y + max_h);
if (auto_height) {
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 0d48cb1549..3df0d97160 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -33,6 +33,7 @@
#include "core/config/project_settings.h"
#include "core/string/print_string.h"
#include "core/string/translation.h"
+#include "scene/gui/container.h"
#include "scene/theme/theme_db.h"
#include "servers/text_server.h"
@@ -44,6 +45,7 @@ void Label::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
autowrap_mode = p_mode;
lines_dirty = true;
queue_redraw();
+ update_configuration_warnings();
if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
update_minimum_size();
@@ -238,10 +240,12 @@ void Label::_shape() {
if (i < jst_to_line) {
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags);
} else if (i == (visible_lines - 1)) {
+ TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
}
}
} else if (lines_hidden) {
+ TS->shaped_text_set_custom_ellipsis(lines_rid[visible_lines - 1], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
}
} else {
@@ -266,9 +270,11 @@ void Label::_shape() {
if (i < jst_to_line && horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) {
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags);
overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE);
+ TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
} else {
+ TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
}
}
@@ -327,6 +333,19 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col
PackedStringArray Label::get_configuration_warnings() const {
PackedStringArray warnings = Control::get_configuration_warnings();
+ // FIXME: This is not ideal and the sizing model should be fixed,
+ // but for now we have to warn about this impossible to resolve combination.
+ // See GH-83546.
+ if (is_inside_tree() && get_tree()->get_edited_scene_root() != this) {
+ // If the Label happens to be the root node of the edited scene, we don't need
+ // to check what its parent is. It's going to be some node from the editor tree
+ // and it can be a container, but that makes no difference to the user.
+ Container *parent_container = Object::cast_to<Container>(get_parent_control());
+ if (parent_container && autowrap_mode != TextServer::AUTOWRAP_OFF && get_custom_minimum_size() == Size2()) {
+ warnings.push_back(RTR("Labels with autowrapping enabled must have a custom minimum size configured to work correctly inside a container."));
+ }
+ }
+
// Ensure that the font can render all of the required glyphs.
Ref<Font> font;
if (settings.is_valid()) {
@@ -872,6 +891,27 @@ TextServer::OverrunBehavior Label::get_text_overrun_behavior() const {
return overrun_behavior;
}
+void Label::set_ellipsis_char(const String &p_char) {
+ String c = p_char;
+ if (c.length() > 1) {
+ WARN_PRINT("Ellipsis must be exactly one character long (" + itos(c.length()) + " characters given).");
+ c = c.left(1);
+ }
+ if (el_char == c) {
+ return;
+ }
+ el_char = c;
+ lines_dirty = true;
+ queue_redraw();
+ if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
+ update_minimum_size();
+ }
+}
+
+String Label::get_ellipsis_char() const {
+ return el_char;
+}
+
String Label::get_text() const {
return text;
}
@@ -992,6 +1032,8 @@ void Label::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_tab_stops"), &Label::get_tab_stops);
ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Label::set_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Label::get_text_overrun_behavior);
+ ClassDB::bind_method(D_METHOD("set_ellipsis_char", "char"), &Label::set_ellipsis_char);
+ ClassDB::bind_method(D_METHOD("get_ellipsis_char"), &Label::get_ellipsis_char);
ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &Label::set_uppercase);
ClassDB::bind_method(D_METHOD("is_uppercase"), &Label::is_uppercase);
ClassDB::bind_method(D_METHOD("get_line_height", "line"), &Label::get_line_height, DEFVAL(-1));
@@ -1022,6 +1064,7 @@ void Label::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "is_clipping_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "ellipsis_char"), "set_ellipsis_char", "get_ellipsis_char");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops");
diff --git a/scene/gui/label.h b/scene/gui/label.h
index a5126c6b91..44443e3eb4 100644
--- a/scene/gui/label.h
+++ b/scene/gui/label.h
@@ -45,6 +45,7 @@ private:
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
bool clip = false;
+ String el_char = U"…";
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
Size2 minsize;
bool uppercase = false;
@@ -147,6 +148,9 @@ public:
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const;
+ void set_ellipsis_char(const String &p_char);
+ String get_ellipsis_char() const;
+
void set_lines_skipped(int p_lines);
int get_lines_skipped() const;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 12ffafadf7..5cca09bcf6 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -1914,12 +1914,15 @@ bool LineEdit::is_secret() const {
}
void LineEdit::set_secret_character(const String &p_string) {
- if (secret_character == p_string) {
+ String c = p_string;
+ if (c.length() > 1) {
+ WARN_PRINT("Secret character must be exactly one character long (" + itos(c.length()) + " characters given).");
+ c = c.left(1);
+ }
+ if (secret_character == c) {
return;
}
-
- secret_character = p_string;
- update_configuration_warnings();
+ secret_character = c;
_shape();
queue_redraw();
}
@@ -2285,14 +2288,8 @@ void LineEdit::_shape() {
if (text.length() == 0 && ime_text.length() == 0) {
t = placeholder_translated;
} else if (pass) {
- // TODO: Integrate with text server to add support for non-latin scripts.
- // Allow secret_character as empty strings, act like if a space was used as a secret character.
- String secret = " ";
- // Allow values longer than 1 character in the property, but trim characters after the first one.
- if (!secret_character.is_empty()) {
- secret = secret_character.left(1);
- }
- t = secret.repeat(text.length() + ime_text.length());
+ String s = (secret_character.length() > 0) ? secret_character.left(1) : U"•";
+ t = s.repeat(text.length() + ime_text.length());
} else {
if (ime_text.length() > 0) {
t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length());
@@ -2643,7 +2640,7 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_and_drop_selection_enabled"), "set_drag_and_drop_selection_enabled", "is_drag_and_drop_selection_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_right_icon", "get_right_icon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus");
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 371d6c69af..7fa2653ed9 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -249,11 +249,13 @@ String MenuBar::bind_global_menu() {
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < menu_cache.size(); i++) {
String submenu_name = popups[i]->bind_global_menu();
- int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
- ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
- ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
- ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
- ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+ if (!popups[i]->is_system_menu()) {
+ int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
+ ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
+ ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
+ ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
+ ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+ }
}
return global_menu_name;
@@ -268,8 +270,10 @@ void MenuBar::unbind_global_menu() {
int global_start = _find_global_start_index();
Vector<PopupMenu *> popups = _get_popups();
for (int i = menu_cache.size() - 1; i >= 0; i--) {
- popups[i]->unbind_global_menu();
- ds->global_menu_remove_item("_main", global_start + i);
+ if (!popups[i]->is_system_menu()) {
+ popups[i]->unbind_global_menu();
+ ds->global_menu_remove_item("_main", global_start + i);
+ }
}
global_menu_name = String();
@@ -558,9 +562,12 @@ void MenuBar::add_child_notify(Node *p_child) {
if (!global_menu_name.is_empty()) {
String submenu_name = pm->bind_global_menu();
- int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
- DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+ if (!pm->is_system_menu()) {
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+ }
}
+ update_minimum_size();
}
void MenuBar::move_child_notify(Node *p_child) {
@@ -586,14 +593,16 @@ void MenuBar::move_child_notify(Node *p_child) {
menu_cache.insert(new_idx, menu);
if (!global_menu_name.is_empty()) {
- int global_start = _find_global_start_index();
- if (old_idx != -1) {
- DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
- }
- if (new_idx != -1) {
- String submenu_name = pm->bind_global_menu();
- int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
- DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+ if (!pm->is_system_menu()) {
+ int global_start = _find_global_start_index();
+ if (old_idx != -1) {
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
+ }
+ if (new_idx != -1) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+ }
}
}
}
@@ -611,8 +620,10 @@ void MenuBar::remove_child_notify(Node *p_child) {
menu_cache.remove_at(idx);
if (!global_menu_name.is_empty()) {
- pm->unbind_global_menu();
- DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+ if (!pm->is_system_menu()) {
+ pm->unbind_global_menu();
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+ }
}
p_child->remove_meta("_menu_name");
@@ -621,6 +632,8 @@ void MenuBar::remove_child_notify(Node *p_child) {
p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed));
p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed));
+
+ update_minimum_size();
}
void MenuBar::_bind_methods() {
@@ -808,6 +821,7 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) {
if (!global_menu_name.is_empty()) {
DisplayServer::get_singleton()->global_menu_set_item_text("_main", _find_global_start_index() + p_menu, atr(menu_cache[p_menu].name));
}
+ update_minimum_size();
}
String MenuBar::get_menu_title(int p_menu) const {
@@ -849,6 +863,7 @@ void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) {
if (!global_menu_name.is_empty()) {
DisplayServer::get_singleton()->global_menu_set_item_hidden("_main", _find_global_start_index() + p_menu, p_hidden);
}
+ update_minimum_size();
}
bool MenuBar::is_menu_hidden(int p_menu) const {
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index b16e8371a2..8369bedda9 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -39,6 +39,7 @@ void Popup::_input_from_window(const Ref<InputEvent> &p_event) {
if (get_flag(FLAG_POPUP) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
_close_pressed();
}
+ Window::_input_from_window(p_event);
}
void Popup::_initialize_visible_parents() {
@@ -204,8 +205,6 @@ Popup::Popup() {
set_flag(FLAG_BORDERLESS, true);
set_flag(FLAG_RESIZE_DISABLED, true);
set_flag(FLAG_POPUP, true);
-
- connect("window_input", callable_mp(this, &Popup::_input_from_window));
}
Popup::~Popup() {
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
index d524e448dd..25edca3657 100644
--- a/scene/gui/popup.h
+++ b/scene/gui/popup.h
@@ -47,14 +47,13 @@ class Popup : public Window {
Ref<StyleBox> panel_style;
} theme_cache;
- void _input_from_window(const Ref<InputEvent> &p_event);
-
void _initialize_visible_parents();
void _deinitialize_visible_parents();
protected:
void _close_pressed();
virtual Rect2i _popup_adjust_rect() const override;
+ virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index dfaf7d88b7..d9c633b238 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -40,6 +40,8 @@
#include "scene/gui/menu_bar.h"
#include "scene/theme/theme_db.h"
+HashMap<String, PopupMenu *> PopupMenu::system_menus;
+
String PopupMenu::bind_global_menu() {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
@@ -54,8 +56,20 @@ String PopupMenu::bind_global_menu() {
return global_menu_name; // Already bound;
}
- DisplayServer *ds = DisplayServer::get_singleton();
global_menu_name = "__PopupMenu#" + itos(get_instance_id());
+ if (system_menu_name.length() > 0) {
+ if (system_menus.has(system_menu_name)) {
+ WARN_PRINT(vformat("Attempting to bind PopupMenu to the special menu %s, but another menu is already bound to it. This menu: %s, current menu: %s", system_menu_name, this->get_description(), system_menus[system_menu_name]->get_description()));
+ } else {
+ const Dictionary &supported_special_names = DisplayServer::get_singleton()->global_menu_get_system_menu_roots();
+ if (supported_special_names.has(system_menu_name)) {
+ system_menus[system_menu_name] = this;
+ global_menu_name = system_menu_name;
+ }
+ }
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
ds->global_menu_set_popup_callbacks(global_menu_name, callable_mp(this, &PopupMenu::_about_to_popup), callable_mp(this, &PopupMenu::_about_to_close));
for (int i = 0; i < items.size(); i++) {
Item &item = items.write[i];
@@ -105,6 +119,10 @@ void PopupMenu::unbind_global_menu() {
return;
}
+ if (global_menu_name == system_menu_name && system_menus[system_menu_name] == this) {
+ system_menus.erase(system_menu_name);
+ }
+
for (int i = 0; i < items.size(); i++) {
Item &item = items.write[i];
if (!item.submenu.is_empty()) {
@@ -120,6 +138,24 @@ void PopupMenu::unbind_global_menu() {
global_menu_name = String();
}
+bool PopupMenu::is_system_menu() const {
+ return (global_menu_name == system_menu_name) && (system_menu_name.length() > 0);
+}
+
+void PopupMenu::set_system_menu_root(const String &p_special) {
+ if (is_inside_tree() && system_menu_name.length() > 0) {
+ unbind_global_menu();
+ }
+ system_menu_name = p_special;
+ if (is_inside_tree() && system_menu_name.length() > 0) {
+ bind_global_menu();
+ }
+}
+
+String PopupMenu::get_system_menu_root() const {
+ return system_menu_name;
+}
+
String PopupMenu::_get_accel_text(const Item &p_item) const {
if (p_item.shortcut.is_valid()) {
return p_item.shortcut->get_as_text();
@@ -378,9 +414,16 @@ void PopupMenu::_submenu_timeout() {
submenu_over = -1;
}
-void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
- ERR_FAIL_COND(p_event.is_null());
+void PopupMenu::_input_from_window(const Ref<InputEvent> &p_event) {
+ if (p_event.is_valid()) {
+ _input_from_window_internal(p_event);
+ } else {
+ WARN_PRINT_ONCE("PopupMenu has received an invalid InputEvent. Consider filtering invalid events out.");
+ }
+ Popup::_input_from_window(p_event);
+}
+void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
if (!items.is_empty()) {
Input *input = Input::get_singleton();
Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
@@ -947,6 +990,15 @@ void PopupMenu::_notification(int p_what) {
if (!is_embedded()) {
set_flag(FLAG_NO_FOCUS, true);
}
+ if (system_menu_name.length() > 0) {
+ bind_global_menu();
+ }
+ } break;
+
+ case NOTIFICATION_EXIT_TREE: {
+ if (system_menu_name.length() > 0) {
+ unbind_global_menu();
+ }
} break;
case NOTIFICATION_THEME_CHANGED:
@@ -1487,6 +1539,11 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
}
void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) {
+ String submenu_name_safe = p_submenu.replace("@", "_"); // Allow special characters for auto-generated names.
+ if (submenu_name_safe.validate_node_name() != submenu_name_safe) {
+ ERR_FAIL_MSG(vformat("Invalid node name '%s' for a submenu, the following characters are not allowed:\n%s", p_submenu, String::get_invalid_node_name_characters(true)));
+ }
+
Item item;
item.text = p_label;
item.xl_text = atr(p_label);
@@ -2186,6 +2243,7 @@ void PopupMenu::scroll_to_item(int p_idx) {
}
bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) {
+ ERR_FAIL_COND_V(p_event.is_null(), false);
Key code = Key::NONE;
Ref<InputEventKey> k = p_event;
@@ -2710,11 +2768,16 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search);
ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search);
+ ClassDB::bind_method(D_METHOD("is_system_menu"), &PopupMenu::is_system_menu);
+ ClassDB::bind_method(D_METHOD("set_system_menu_root", "special"), &PopupMenu::set_system_menu_root);
+ ClassDB::bind_method(D_METHOD("get_system_menu_root"), &PopupMenu::get_system_menu_root);
+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay", PROPERTY_HINT_NONE, "suffix:s"), "set_submenu_popup_delay", "get_submenu_popup_delay");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "system_menu_root", PROPERTY_HINT_ENUM, "Dock (macOS):_dock,Apple Menu(macOS):_apple,Window Menu(macOS):_window,Help Menu(macOS):_help"), "set_system_menu_root", "get_system_menu_root");
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
@@ -2793,8 +2856,6 @@ PopupMenu::PopupMenu() {
scroll_container->add_child(control, false, INTERNAL_MODE_FRONT);
control->connect("draw", callable_mp(this, &PopupMenu::_draw_items));
- connect("window_input", callable_mp(this, &PopupMenu::gui_input));
-
submenu_timer = memnew(Timer);
submenu_timer->set_wait_time(0.3);
submenu_timer->set_one_shot(true);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 5d5f4a8322..c1ab9544ea 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -40,6 +40,8 @@
class PopupMenu : public Popup {
GDCLASS(PopupMenu, Popup);
+ static HashMap<String, PopupMenu *> system_menus;
+
struct Item {
Ref<Texture2D> icon;
int icon_max_width = 0;
@@ -90,6 +92,7 @@ class PopupMenu : public Popup {
};
String global_menu_name;
+ String system_menu_name;
bool close_allowed = false;
bool activated_by_keyboard = false;
@@ -112,7 +115,6 @@ class PopupMenu : public Popup {
void _shape_item(int p_idx);
- virtual void gui_input(const Ref<InputEvent> &p_event);
void _activate_submenu(int p_over, bool p_by_keyboard = false);
void _submenu_timeout();
@@ -191,10 +193,12 @@ class PopupMenu : public Popup {
void _minimum_lifetime_timeout();
void _close_pressed();
void _menu_changed();
+ void _input_from_window_internal(const Ref<InputEvent> &p_event);
protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
+ virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
@@ -218,6 +222,9 @@ public:
String bind_global_menu();
void unbind_global_menu();
+ bool is_system_menu() const;
+ void set_system_menu_root(const String &p_special);
+ String get_system_menu_root() const;
void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 7768c2d84e..fbc374e89e 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -307,7 +307,7 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
Size2 img_size = img->size;
if (img->size_in_percent) {
img_size = _get_image_size(img->image, p_width * img->rq_size.width / 100.f, p_width * img->rq_size.height / 100.f, img->region);
- l.text_buf->resize_object((uint64_t)it, img_size, img->inline_align, 1);
+ l.text_buf->resize_object((uint64_t)it, img_size, img->inline_align);
}
} break;
case ITEM_TABLE: {
@@ -830,37 +830,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
prefix = segment + prefix;
}
}
- if (!prefix.is_empty()) {
- Ref<Font> font = theme_cache.normal_font;
- int font_size = theme_cache.normal_font_size;
-
- ItemFont *font_it = _find_font(l.from);
- if (font_it) {
- if (font_it->font.is_valid()) {
- font = font_it->font;
- }
- if (font_it->font_size > 0) {
- font_size = font_it->font_size;
- }
- }
- ItemFontSize *font_size_it = _find_font_size(l.from);
- if (font_size_it && font_size_it->font_size > 0) {
- font_size = font_size_it->font_size;
- }
- if (rtl) {
- float offx = 0.0f;
- if (!lrtl && p_frame == main) { // Skip Scrollbar.
- offx -= scroll_w;
- }
- font->draw_string(ci, p_ofs + Vector2(p_width - l.offset.x + offx, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color));
- } else {
- float offx = 0.0f;
- if (lrtl && p_frame == main) { // Skip Scrollbar.
- offx += scroll_w;
- }
- font->draw_string(ci, p_ofs + Vector2(offx, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color));
- }
- }
// Draw dropcap.
int dc_lines = l.text_buf->get_dropcap_lines();
@@ -924,6 +893,30 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
} break;
}
+ if (!prefix.is_empty() && line == 0) {
+ Ref<Font> font = theme_cache.normal_font;
+ int font_size = theme_cache.normal_font_size;
+
+ ItemFont *font_it = _find_font(l.from);
+ if (font_it) {
+ if (font_it->font.is_valid()) {
+ font = font_it->font;
+ }
+ if (font_it->font_size > 0) {
+ font_size = font_it->font_size;
+ }
+ }
+ ItemFontSize *font_size_it = _find_font_size(l.from);
+ if (font_size_it && font_size_it->font_size > 0) {
+ font_size = font_size_it->font_size;
+ }
+ if (rtl) {
+ font->draw_string(ci, p_ofs + Vector2(off.x + length, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color));
+ } else {
+ font->draw_string(ci, p_ofs + Vector2(off.x - l.offset.x, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color));
+ }
+ }
+
if (line <= dc_lines) {
if (rtl) {
off.x -= h_off;
@@ -977,11 +970,20 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
coff.x = rect.size.width - table->columns[col].width - coff.x;
}
if (row % 2 == 0) {
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg), true);
+ Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg;
+ if (c.a > 0.0) {
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true);
+ }
} else {
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg), true);
+ Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg;
+ if (c.a > 0.0) {
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true);
+ }
+ }
+ Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border;
+ if (bc.a > 0.0) {
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false);
}
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + 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 < (int)frame->lines.size(); j++) {
@@ -1204,14 +1206,17 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Vector2 ul_start;
bool ul_started = false;
+ Color ul_color_prev;
Color ul_color;
Vector2 dot_ul_start;
bool dot_ul_started = false;
+ Color dot_ul_color_prev;
Color dot_ul_color;
Vector2 st_start;
bool st_started = false;
+ Color st_color_prev;
Color st_color;
for (int i = 0; i < gl_size; i++) {
@@ -1219,9 +1224,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
Color font_color = _find_color(it, p_base_color);
if (_find_underline(it) || (_find_meta(it, nullptr) && underline_meta)) {
- if (!ul_started) {
+ if (ul_started && font_color != ul_color_prev) {
+ float y_off = TS->shaped_text_get_underline_position(rid);
+ float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
+ draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
+ ul_start = p_ofs + Vector2(off.x, off.y);
+ ul_color_prev = font_color;
+ ul_color = font_color;
+ ul_color.a *= 0.5;
+ } else if (!ul_started) {
ul_started = true;
ul_start = p_ofs + Vector2(off.x, off.y);
+ ul_color_prev = font_color;
ul_color = font_color;
ul_color.a *= 0.5;
}
@@ -1232,9 +1246,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
}
if (_find_hint(it, nullptr) && underline_hint) {
- if (!dot_ul_started) {
+ if (dot_ul_started && font_color != dot_ul_color_prev) {
+ float y_off = TS->shaped_text_get_underline_position(rid);
+ float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
+ draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
+ dot_ul_start = p_ofs + Vector2(off.x, off.y);
+ dot_ul_color_prev = font_color;
+ dot_ul_color = font_color;
+ dot_ul_color.a *= 0.5;
+ } else if (!dot_ul_started) {
dot_ul_started = true;
dot_ul_start = p_ofs + Vector2(off.x, off.y);
+ dot_ul_color_prev = font_color;
dot_ul_color = font_color;
dot_ul_color.a *= 0.5;
}
@@ -1245,9 +1268,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
}
if (_find_strikethrough(it)) {
- if (!st_started) {
+ if (st_started && font_color != st_color_prev) {
+ float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
+ float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
+ draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
+ st_start = p_ofs + Vector2(off.x, off.y);
+ st_color_prev = font_color;
+ st_color = font_color;
+ st_color.a *= 0.5;
+ } else if (!st_started) {
st_started = true;
st_start = p_ofs + Vector2(off.x, off.y);
+ st_color_prev = font_color;
st_color = font_color;
st_color.a *= 0.5;
}
@@ -1739,7 +1771,7 @@ void RichTextLabel::_scroll_changed(double) {
return;
}
- if (scroll_follow && vscroll->get_value() >= (vscroll->get_max() - vscroll->get_page())) {
+ if (scroll_follow && vscroll->get_value() >= (vscroll->get_max() - Math::round(vscroll->get_page()))) {
scroll_following = true;
} else {
scroll_following = false;
@@ -3089,6 +3121,8 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
current_char_ofs += t->text.length();
} else if (p_item->type == ITEM_IMAGE) {
current_char_ofs++;
+ } else if (p_item->type == ITEM_NEWLINE) {
+ current_char_ofs++;
}
if (p_enter) {
@@ -3193,6 +3227,9 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, int p_width, int p_
ERR_FAIL_COND(p_image.is_null());
ERR_FAIL_COND(p_image->get_width() == 0);
ERR_FAIL_COND(p_image->get_height() == 0);
+ ERR_FAIL_COND(p_width < 0);
+ ERR_FAIL_COND(p_height < 0);
+
ItemImage *item = memnew(ItemImage);
if (p_region.has_area()) {
@@ -3226,6 +3263,9 @@ void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask>
ERR_FAIL_COND(p_image->get_height() == 0);
}
+ ERR_FAIL_COND(p_width < 0);
+ ERR_FAIL_COND(p_height < 0);
+
bool reshape = false;
Item *it = main;
@@ -3277,6 +3317,9 @@ void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask>
}
}
if ((p_mask & UPDATE_SIZE) || (p_mask & UPDATE_REGION) || (p_mask & UPDATE_TEXTURE)) {
+ ERR_FAIL_COND(item->image.is_null());
+ ERR_FAIL_COND(item->image->get_width() == 0);
+ ERR_FAIL_COND(item->image->get_height() == 0);
Size2 new_size = _get_image_size(item->image, item->rq_size.width, item->rq_size.height, item->region);
if (item->size != new_size) {
reshape = true;
@@ -6288,11 +6331,9 @@ Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_id
Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressions) {
Dictionary d;
for (int i = 0; i < p_expressions.size(); i++) {
- String expression = p_expressions[i];
-
Array a;
- Vector<String> parts = expression.split("=", true);
- String key = parts[0];
+ Vector<String> parts = p_expressions[i].split("=", true);
+ const String &key = parts[0];
if (parts.size() != 2) {
return d;
}
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index 29e6d3d10d..e9fbdbb312 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -334,6 +334,12 @@ void TabBar::_shape(int p_tab) {
void TabBar::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ if (scroll_to_selected) {
+ ensure_tab_visible(current);
+ }
+ } break;
+
case NOTIFICATION_INTERNAL_PROCESS: {
Input *input = Input::get_singleton();
@@ -669,7 +675,7 @@ bool TabBar::select_previous_available() {
if (target_tab < 0) {
target_tab += get_tab_count();
}
- if (!is_tab_disabled(target_tab)) {
+ if (!is_tab_disabled(target_tab) && !is_tab_hidden(target_tab)) {
set_current_tab(target_tab);
return true;
}
@@ -681,7 +687,7 @@ bool TabBar::select_next_available() {
const int offset_end = (get_tab_count() - get_current_tab());
for (int i = 1; i < offset_end; i++) {
int target_tab = (get_current_tab() + i) % get_tab_count();
- if (!is_tab_disabled(target_tab)) {
+ if (!is_tab_disabled(target_tab) && !is_tab_hidden(target_tab)) {
set_current_tab(target_tab);
return true;
}
@@ -1094,6 +1100,23 @@ void TabBar::remove_tab(int p_idx) {
max_drawn_tab = 0;
previous = 0;
} else {
+ // Try to change to a valid tab if possible (without firing the `tab_selected` signal).
+ for (int i = current; i < tabs.size(); i++) {
+ if (!is_tab_disabled(i) && !is_tab_hidden(i)) {
+ current = i;
+ break;
+ }
+ }
+ // If nothing, try backwards.
+ if (is_tab_disabled(current) || is_tab_hidden(current)) {
+ for (int i = current - 1; i >= 0; i--) {
+ if (!is_tab_disabled(i) && !is_tab_hidden(i)) {
+ current = i;
+ break;
+ }
+ }
+ }
+
offset = MIN(offset, tabs.size() - 1);
max_drawn_tab = MIN(max_drawn_tab, tabs.size() - 1);
@@ -1641,7 +1664,7 @@ bool TabBar::_set(const StringName &p_name, const Variant &p_value) {
} else if (property == "icon") {
set_tab_icon(tab_index, p_value);
return true;
- } else if (components[1] == "disabled") {
+ } else if (property == "disabled") {
set_tab_disabled(tab_index, p_value);
return true;
}
@@ -1660,7 +1683,7 @@ bool TabBar::_get(const StringName &p_name, Variant &r_ret) const {
} else if (property == "icon") {
r_ret = get_tab_icon(tab_index);
return true;
- } else if (components[1] == "disabled") {
+ } else if (property == "disabled") {
r_ret = is_tab_disabled(tab_index);
return true;
}
@@ -1745,7 +1768,10 @@ void TabBar::_bind_methods() {
ADD_SIGNAL(MethodInfo("tab_hovered", PropertyInfo(Variant::INT, "tab")));
ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to")));
- ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab");
+ // "current_tab" property must come after "tab_count", otherwise the property isn't loaded correctly.
+ ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_");
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1"), "set_current_tab", "get_current_tab");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy");
@@ -1756,8 +1782,6 @@ void TabBar::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_to_selected"), "set_scroll_to_selected", "get_scroll_to_selected");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_with_rmb"), "set_select_with_rmb", "get_select_with_rmb");
- ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_");
-
BIND_ENUM_CONSTANT(ALIGNMENT_LEFT);
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
BIND_ENUM_CONSTANT(ALIGNMENT_RIGHT);
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index c21a9d14cb..0f461f4865 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -143,6 +143,13 @@ void TabContainer::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_POST_ENTER_TREE: {
+ if (setup_current_tab >= 0) {
+ set_current_tab(setup_current_tab);
+ setup_current_tab = -1;
+ }
+ } break;
+
case NOTIFICATION_READY:
case NOTIFICATION_RESIZED: {
_update_margins();
@@ -330,14 +337,23 @@ Vector<Control *> TabContainer::_get_tab_controls() const {
}
Variant TabContainer::_get_drag_data_fw(const Point2 &p_point, Control *p_from_control) {
+ if (!drag_to_rearrange_enabled) {
+ return Variant();
+ }
return tab_bar->_handle_get_drag_data("tab_container_tab", p_point);
}
bool TabContainer::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const {
+ if (!drag_to_rearrange_enabled) {
+ return false;
+ }
return tab_bar->_handle_can_drop_data("tab_container_tab", p_point, p_data);
}
void TabContainer::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) {
+ if (!drag_to_rearrange_enabled) {
+ return;
+ }
return tab_bar->_handle_drop_data("tab_container_tab", p_point, p_data, callable_mp(this, &TabContainer::_drag_move_tab), callable_mp(this, &TabContainer::_drag_move_tab_from));
}
@@ -365,22 +381,29 @@ void TabContainer::move_tab_from_tab_container(TabContainer *p_from, int p_from_
// Get the tab properties before they get erased by the child removal.
String tab_title = p_from->get_tab_title(p_from_index);
Ref<Texture2D> tab_icon = p_from->get_tab_icon(p_from_index);
+ Ref<Texture2D> tab_button_icon = p_from->get_tab_button_icon(p_from_index);
bool tab_disabled = p_from->is_tab_disabled(p_from_index);
+ bool tab_hidden = p_from->is_tab_hidden(p_from_index);
Variant tab_metadata = p_from->get_tab_metadata(p_from_index);
+ int tab_icon_max_width = p_from->get_tab_bar()->get_tab_icon_max_width(p_from_index);
Control *moving_tabc = p_from->get_tab_control(p_from_index);
p_from->remove_child(moving_tabc);
add_child(moving_tabc, true);
- set_tab_title(get_tab_count() - 1, tab_title);
- set_tab_icon(get_tab_count() - 1, tab_icon);
- set_tab_disabled(get_tab_count() - 1, tab_disabled);
- set_tab_metadata(get_tab_count() - 1, tab_metadata);
-
if (p_to_index < 0 || p_to_index > get_tab_count() - 1) {
p_to_index = get_tab_count() - 1;
}
move_child(moving_tabc, get_tab_control(p_to_index)->get_index(false));
+
+ set_tab_title(p_to_index, tab_title);
+ set_tab_icon(p_to_index, tab_icon);
+ set_tab_button_icon(p_to_index, tab_button_icon);
+ set_tab_disabled(p_to_index, tab_disabled);
+ set_tab_hidden(p_to_index, tab_hidden);
+ set_tab_metadata(p_to_index, tab_metadata);
+ get_tab_bar()->set_tab_icon_max_width(p_to_index, tab_icon_max_width);
+
if (!is_tab_disabled(p_to_index)) {
set_current_tab(p_to_index);
}
@@ -519,6 +542,10 @@ int TabContainer::get_tab_count() const {
}
void TabContainer::set_current_tab(int p_current) {
+ if (!is_inside_tree()) {
+ setup_current_tab = p_current;
+ return;
+ }
tab_bar->set_current_tab(p_current);
}
@@ -815,11 +842,11 @@ Popup *TabContainer::get_popup() const {
}
void TabContainer::set_drag_to_rearrange_enabled(bool p_enabled) {
- tab_bar->set_drag_to_rearrange_enabled(p_enabled);
+ drag_to_rearrange_enabled = p_enabled;
}
bool TabContainer::get_drag_to_rearrange_enabled() const {
- return tab_bar->get_drag_to_rearrange_enabled();
+ return drag_to_rearrange_enabled;
}
void TabContainer::set_tabs_rearrange_group(int p_group_id) {
@@ -903,7 +930,7 @@ void TabContainer::_bind_methods() {
ADD_SIGNAL(MethodInfo("pre_popup_pressed"));
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1"), "set_current_tab", "get_current_tab");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tabs_visible"), "set_tabs_visible", "are_tabs_visible");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "all_tabs_in_front"), "set_all_tabs_in_front", "is_all_tabs_in_front");
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
index 5750c6b82e..450143cd0c 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -46,6 +46,8 @@ class TabContainer : public Container {
bool use_hidden_tabs_for_min_size = false;
bool theme_changing = false;
Vector<Control *> children_removing;
+ bool drag_to_rearrange_enabled = false;
+ int setup_current_tab = -1;
struct ThemeCache {
int side_margin = 0;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 479480011c..308250c592 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -770,7 +770,14 @@ void TextEdit::_notification(int p_what) {
Dictionary color_map = _get_line_syntax_highlighting(minimap_line);
Color line_background_color = text.get_line_background_color(minimap_line);
- line_background_color.a *= 0.6;
+
+ if (line_background_color != theme_cache.background_color) {
+ // Make non-default background colors more visible, such as error markers.
+ line_background_color.a = 1.0;
+ } else {
+ line_background_color.a *= 0.6;
+ }
+
Color current_color = theme_cache.font_color;
if (!editable) {
current_color = theme_cache.font_readonly_color;
@@ -2948,6 +2955,8 @@ void TextEdit::_update_placeholder() {
return; // Not in tree?
}
+ const String placeholder_translated = atr(placeholder_text);
+
// Placeholder is generally smaller then text documents, and updates less so this should be fast enough for now.
placeholder_data_buf->clear();
placeholder_data_buf->set_width(text.get_width());
@@ -2958,9 +2967,9 @@ void TextEdit::_update_placeholder() {
placeholder_data_buf->set_direction((TextServer::Direction)text_direction);
}
placeholder_data_buf->set_preserve_control(draw_control_chars);
- placeholder_data_buf->add_string(placeholder_text, theme_cache.font, theme_cache.font_size, language);
+ placeholder_data_buf->add_string(placeholder_translated, theme_cache.font, theme_cache.font_size, language);
- placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_text);
+ placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_translated);
if (placeholder_bidi_override.is_empty()) {
TS->shaped_text_set_bidi_override(placeholder_data_buf->get_rid(), placeholder_bidi_override);
}
@@ -2985,7 +2994,7 @@ void TextEdit::_update_placeholder() {
placeholder_wraped_rows.clear();
for (int i = 0; i <= wrap_amount; i++) {
Vector2i line_range = placeholder_data_buf->get_line_range(i);
- placeholder_wraped_rows.push_back(placeholder_text.substr(line_range.x, line_range.y - line_range.x));
+ placeholder_wraped_rows.push_back(placeholder_translated.substr(line_range.x, line_range.y - line_range.x));
}
}
@@ -5305,8 +5314,7 @@ int TextEdit::get_line_wrap_index_at_column(int p_line, int p_column) const {
Vector<String> lines = get_line_wrapped_text(p_line);
for (int i = 0; i < lines.size(); i++) {
wrap_index = i;
- String s = lines[wrap_index];
- col += s.length();
+ col += lines[wrap_index].length();
if (col > p_column) {
break;
}
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 2d3166270b..8a243fd68f 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -70,19 +70,27 @@ void TreeItem::Cell::draw_icon(const RID &p_where, const Point2 &p_pos, const Si
}
void TreeItem::_changed_notify(int p_cell) {
- tree->item_changed(p_cell, this);
+ if (tree) {
+ tree->item_changed(p_cell, this);
+ }
}
void TreeItem::_changed_notify() {
- tree->item_changed(-1, this);
+ if (tree) {
+ tree->item_changed(-1, this);
+ }
}
void TreeItem::_cell_selected(int p_cell) {
- tree->item_selected(p_cell, this);
+ if (tree) {
+ tree->item_selected(p_cell, this);
+ }
}
void TreeItem::_cell_deselected(int p_cell) {
- tree->item_deselected(p_cell, this);
+ if (tree) {
+ tree->item_deselected(p_cell, this);
+ }
}
void TreeItem::_change_tree(Tree *p_tree) {
@@ -2050,7 +2058,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int text_width = item_width - theme_cache.inner_item_margin_left - theme_cache.inner_item_margin_right;
if (p_item->cells[i].icon.is_valid()) {
- text_width -= p_item->cells[i].get_icon_size().x + theme_cache.h_separation;
+ text_width -= _get_cell_icon_size(p_item->cells[i]).x + theme_cache.h_separation;
}
p_item->cells.write[i].text_buf->set_width(text_width);
@@ -4544,6 +4552,8 @@ TreeItem *Tree::get_selected() const {
void Tree::set_selected(TreeItem *p_item, int p_column) {
ERR_FAIL_INDEX(p_column, columns.size());
ERR_FAIL_NULL(p_item);
+ ERR_FAIL_COND_MSG(p_item->get_tree() != this, "The provided TreeItem does not belong to this Tree. Ensure that the TreeItem is a part of the Tree before setting it as selected.");
+
select_single_item(p_item, get_root(), p_column);
}
diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp
index ac09844128..41a210e180 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.cpp
@@ -237,6 +237,12 @@ bool VideoStreamPlayer::has_loop() const {
void VideoStreamPlayer::set_stream(const Ref<VideoStream> &p_stream) {
stop();
+ // Make sure to handle stream changes seamlessly, e.g. when done via
+ // translation remapping.
+ if (stream.is_valid()) {
+ stream->disconnect_changed(callable_mp(this, &VideoStreamPlayer::set_stream));
+ }
+
AudioServer::get_singleton()->lock();
mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size());
stream = p_stream;
@@ -248,6 +254,10 @@ void VideoStreamPlayer::set_stream(const Ref<VideoStream> &p_stream) {
}
AudioServer::get_singleton()->unlock();
+ if (stream.is_valid()) {
+ stream->connect_changed(callable_mp(this, &VideoStreamPlayer::set_stream).bind(stream));
+ }
+
if (!playback.is_null()) {
playback->set_paused(paused);
texture = playback->get_texture();
diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp
index fc03f2d887..c61fa1d9b8 100644
--- a/scene/gui/view_panner.cpp
+++ b/scene/gui/view_panner.cpp
@@ -125,6 +125,17 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
+ if (pan_gesture->is_ctrl_pressed()) {
+ // Zoom gesture.
+ float pan_zoom_factor = 1.02f;
+ float zoom_direction = pan_gesture->get_delta().x - pan_gesture->get_delta().y;
+ if (zoom_direction == 0.f) {
+ return true;
+ }
+ float zoom = zoom_direction < 0 ? 1.0 / pan_zoom_factor : pan_zoom_factor;
+ zoom_callback.call(zoom, pan_gesture->get_position(), p_event);
+ return true;
+ }
pan_callback.call(-pan_gesture->get_delta() * scroll_speed, p_event);
}