diff options
Diffstat (limited to 'scene/gui')
-rw-r--r-- | scene/gui/base_button.cpp | 2 | ||||
-rw-r--r-- | scene/gui/color_picker.cpp | 29 | ||||
-rw-r--r-- | scene/gui/control.cpp | 12 | ||||
-rw-r--r-- | scene/gui/control.h | 2 | ||||
-rw-r--r-- | scene/gui/dialogs.cpp | 3 | ||||
-rw-r--r-- | scene/gui/dialogs.h | 2 | ||||
-rw-r--r-- | scene/gui/graph_edit.cpp | 2 | ||||
-rw-r--r-- | scene/gui/graph_edit_arranger.cpp | 28 | ||||
-rw-r--r-- | scene/gui/item_list.cpp | 8 | ||||
-rw-r--r-- | scene/gui/label.cpp | 43 | ||||
-rw-r--r-- | scene/gui/label.h | 4 | ||||
-rw-r--r-- | scene/gui/line_edit.cpp | 23 | ||||
-rw-r--r-- | scene/gui/menu_bar.cpp | 53 | ||||
-rw-r--r-- | scene/gui/popup.cpp | 3 | ||||
-rw-r--r-- | scene/gui/popup.h | 3 | ||||
-rw-r--r-- | scene/gui/popup_menu.cpp | 71 | ||||
-rw-r--r-- | scene/gui/popup_menu.h | 9 | ||||
-rw-r--r-- | scene/gui/rich_text_label.cpp | 127 | ||||
-rw-r--r-- | scene/gui/tab_bar.cpp | 38 | ||||
-rw-r--r-- | scene/gui/tab_container.cpp | 43 | ||||
-rw-r--r-- | scene/gui/tab_container.h | 2 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 20 | ||||
-rw-r--r-- | scene/gui/tree.cpp | 20 | ||||
-rw-r--r-- | scene/gui/video_stream_player.cpp | 10 | ||||
-rw-r--r-- | scene/gui/view_panner.cpp | 11 |
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); } |