From b341c0d827b43690e051e9cb69c6fa1a5212d8ba Mon Sep 17 00:00:00 2001 From: ajreckof Date: Sat, 30 Mar 2024 16:22:02 +0100 Subject: [PATCH 01/29] Fix editing exported nodes in array as text. (cherry picked from commit ab30c682d391496d28e5ba1ab56009e564b39c42) --- editor/editor_properties.cpp | 8 ++++---- editor/editor_properties.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 19f4a5c86ae..9facacd247b 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2721,7 +2721,7 @@ Variant EditorPropertyNodePath::_get_cache_value(const StringName &p_prop, bool return Variant(); } -void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { +void EditorPropertyNodePath::_node_selected(const NodePath &p_path, bool p_absolute) { NodePath path = p_path; Node *base_node = get_base_node(); @@ -2731,7 +2731,7 @@ void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { path = get_tree()->get_edited_scene_root()->get_path_to(to_node); } - if (base_node) { // for AnimationTrackKeyEdit + if (p_absolute && base_node) { // for AnimationTrackKeyEdit path = base_node->get_path().rel_path_to(p_path); } @@ -2753,7 +2753,7 @@ void EditorPropertyNodePath::_node_assign() { scene_tree->get_scene_tree()->set_show_enabled_subscene(true); scene_tree->set_valid_types(valid_types); add_child(scene_tree); - scene_tree->connect("selected", callable_mp(this, &EditorPropertyNodePath::_node_selected)); + scene_tree->connect("selected", callable_mp(this, &EditorPropertyNodePath::_node_selected).bind(true)); } Variant val = get_edited_property_value(); @@ -2821,7 +2821,7 @@ void EditorPropertyNodePath::_accept_text() { void EditorPropertyNodePath::_text_submitted(const String &p_text) { NodePath np = p_text; - emit_changed(get_edited_property(), np); + _node_selected(np, false); edit->hide(); assign->show(); menu->show(); diff --git a/editor/editor_properties.h b/editor/editor_properties.h index e4ed222e242..3b9dcefdf63 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -659,7 +659,7 @@ class EditorPropertyNodePath : public EditorProperty { bool editing_node = false; Vector valid_types; - void _node_selected(const NodePath &p_path); + void _node_selected(const NodePath &p_path, bool p_absolute = true); void _node_assign(); Node *get_base_node(); void _update_menu(); From 6602d313dd6e228cf44214cfe65967542ff9b343 Mon Sep 17 00:00:00 2001 From: passivestar <60579014+passivestar@users.noreply.github.com> Date: Sun, 7 Jul 2024 14:46:14 +0400 Subject: [PATCH 02/29] [macOS] Change the shortcut for Align Transform with View (cherry picked from commit 891173b3ea6f9bca531100f17e912c4ce9091fad) --- editor/plugins/node_3d_editor_plugin.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 3373d8d232b..12bf1965e3b 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -8704,7 +8704,12 @@ Node3DEditor::Node3DEditor() { ED_SHORTCUT("spatial_editor/focus_origin", TTR("Focus Origin"), Key::O); ED_SHORTCUT("spatial_editor/focus_selection", TTR("Focus Selection"), Key::F); ED_SHORTCUT_ARRAY("spatial_editor/align_transform_with_view", TTR("Align Transform with View"), - { int32_t(KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::KP_0), int32_t(KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::M) }); + { int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::KP_0), + int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::M), + int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::G) }); + ED_SHORTCUT_OVERRIDE_ARRAY("spatial_editor/align_transform_with_view", "macos", + { int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::KP_0), + int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::G) }); ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTR("Align Rotation with View"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::F); ED_SHORTCUT("spatial_editor/freelook_toggle", TTR("Toggle Freelook"), KeyModifierMask::SHIFT + Key::F); ED_SHORTCUT("spatial_editor/decrease_fov", TTR("Decrease Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::EQUAL); // Usually direct access key for `KEY_PLUS`. From 5c6cecb893ec8a72a73b47e1cc344918d29ec1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E9=9D=92=E5=B1=B1?= Date: Mon, 14 Oct 2024 09:48:58 +0800 Subject: [PATCH 03/29] Add `EditorHelpBitTooltip` as a child of `p_target` to avoid jitter Previously the tooltip popup was added as a child of the `p_target`'s viewport (usually the root window), which caused the root window to recalculate the window size, that could cause jitter issues on i3wm if the actual width of the root window was less than `1024`. (cherry picked from commit bc30bb4fc059b1b5a6ee2419fb1f495233f50c96) --- editor/editor_help.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 5e1d9b4ded0..928585ad0da 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -3819,7 +3819,7 @@ void EditorHelpBitTooltip::show_tooltip(EditorHelpBit *p_help_bit, Control *p_ta EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target)); p_help_bit->connect("request_hide", callable_mp(tooltip, &EditorHelpBitTooltip::_safe_queue_free)); tooltip->add_child(p_help_bit); - p_target->get_viewport()->add_child(tooltip); + p_target->add_child(tooltip); p_help_bit->update_content_height(); tooltip->popup_under_cursor(); } From 023e91e9cf7a6fa1a1adf0a996540a78266c4165 Mon Sep 17 00:00:00 2001 From: Hilderin <81109165+Hilderin@users.noreply.github.com> Date: Thu, 1 Aug 2024 20:03:15 -0400 Subject: [PATCH 04/29] Fix connecting a signal with a double click is too difficult Co-authored-by: Danil Alexeev (cherry picked from commit 4e19ab8afed8407f1b077458863de1f7431b7f61) --- core/input/input.cpp | 16 +++ core/input/input.h | 1 + editor/connections_dialog.cpp | 4 +- editor/editor_help.cpp | 135 ++++++++++++------- editor/editor_help.h | 18 +-- editor/editor_inspector.cpp | 45 +++---- editor/plugins/theme_editor_plugin.cpp | 4 +- platform/linuxbsd/x11/display_server_x11.cpp | 9 +- scene/main/viewport.cpp | 17 ++- 9 files changed, 147 insertions(+), 102 deletions(-) diff --git a/core/input/input.cpp b/core/input/input.cpp index b042020c124..032fcbe0eb1 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -267,6 +267,22 @@ bool Input::is_anything_pressed() const { return false; } +bool Input::is_anything_pressed_except_mouse() const { + _THREAD_SAFE_METHOD_ + + if (!keys_pressed.is_empty() || !joy_buttons_pressed.is_empty()) { + return true; + } + + for (const KeyValue &E : action_states) { + if (E.value.cache.pressed) { + return true; + } + } + + return false; +} + bool Input::is_key_pressed(Key p_keycode) const { _THREAD_SAFE_METHOD_ return keys_pressed.has(p_keycode); diff --git a/core/input/input.h b/core/input/input.h index de1b145fa74..53fd9d8cb60 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -287,6 +287,7 @@ class Input : public Object { static Input *get_singleton(); bool is_anything_pressed() const; + bool is_anything_pressed_except_mouse() const; bool is_key_pressed(Key p_keycode) const; bool is_physical_key_pressed(Key p_keycode) const; bool is_key_label_pressed(Key p_keycode) const; diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 774b1fdbb49..ff9b47545e9 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -902,9 +902,7 @@ Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const { return nullptr; } - EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text)); - EditorHelpBitTooltip::show_tooltip(help_bit, const_cast(this)); - return memnew(Control); // Make the standard tooltip invisible. + return EditorHelpBitTooltip::show_tooltip(const_cast(this), p_text); } struct _ConnectionsDockMethodInfoSort { diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 928585ad0da..195fb25eab2 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -3636,7 +3636,7 @@ void EditorHelpBit::_notification(int p_what) { } } -void EditorHelpBit::parse_symbol(const String &p_symbol) { +void EditorHelpBit::parse_symbol(const String &p_symbol, const String &p_prologue) { const PackedStringArray slices = p_symbol.split("|", true, 2); ERR_FAIL_COND_MSG(slices.size() < 3, "Invalid doc id. The expected format is 'item_type|class_name|item_name'."); @@ -3683,6 +3683,14 @@ void EditorHelpBit::parse_symbol(const String &p_symbol) { symbol_visible_type = visible_type; symbol_name = name; + if (!p_prologue.is_empty()) { + if (help_data.description.is_empty()) { + help_data.description = p_prologue; + } else { + help_data.description = p_prologue + "\n" + help_data.description; + } + } + if (help_data.description.is_empty()) { help_data.description = "[color=][i]" + TTR("No description available.") + "[/i][/color]"; } @@ -3706,14 +3714,6 @@ void EditorHelpBit::set_custom_text(const String &p_type, const String &p_name, } } -void EditorHelpBit::set_description(const String &p_text) { - help_data.description = p_text; - - if (is_inside_tree()) { - _update_labels(); - } -} - void EditorHelpBit::set_content_height_limits(float p_min, float p_max) { ERR_FAIL_COND(p_min > p_max); content_min_height = p_min; @@ -3733,15 +3733,15 @@ void EditorHelpBit::update_content_height() { content->set_custom_minimum_size(Size2(content->get_custom_minimum_size().x, CLAMP(content_height, content_min_height, content_max_height))); } -EditorHelpBit::EditorHelpBit(const String &p_symbol) { +EditorHelpBit::EditorHelpBit(const String &p_symbol, const String &p_prologue, bool p_allow_selection) { add_theme_constant_override("separation", 0); title = memnew(RichTextLabel); title->set_theme_type_variation("EditorHelpBitTitle"); title->set_custom_minimum_size(Size2(512 * EDSCALE, 0)); // GH-93031. Set the minimum width even if `fit_content` is true. title->set_fit_content(true); - title->set_selection_enabled(true); - //title->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip. + title->set_selection_enabled(p_allow_selection); + title->set_context_menu_enabled(p_allow_selection); title->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); title->hide(); add_child(title); @@ -3752,76 +3752,108 @@ EditorHelpBit::EditorHelpBit(const String &p_symbol) { content = memnew(RichTextLabel); content->set_theme_type_variation("EditorHelpBitContent"); content->set_custom_minimum_size(Size2(512 * EDSCALE, content_min_height)); - content->set_selection_enabled(true); - //content->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip. + content->set_selection_enabled(p_allow_selection); + content->set_context_menu_enabled(p_allow_selection); content->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); add_child(content); if (!p_symbol.is_empty()) { - parse_symbol(p_symbol); + parse_symbol(p_symbol, p_prologue); } } /// EditorHelpBitTooltip /// +bool EditorHelpBitTooltip::_is_tooltip_visible = false; + +Control *EditorHelpBitTooltip::_make_invisible_control() { + Control *control = memnew(Control); + control->set_visible(false); + return control; +} + void EditorHelpBitTooltip::_start_timer() { if (timer->is_inside_tree() && timer->is_stopped()) { timer->start(); } } -void EditorHelpBitTooltip::_safe_queue_free() { - if (_pushing_input > 0) { - _need_free = true; - } else { - queue_free(); - } -} - void EditorHelpBitTooltip::_target_gui_input(const Ref &p_event) { - const Ref mouse_event = p_event; - if (mouse_event.is_valid()) { - _start_timer(); + // Only scrolling is not checked in `NOTIFICATION_INTERNAL_PROCESS`. + const Ref mb = p_event; + if (mb.is_valid()) { + switch (mb->get_button_index()) { + case MouseButton::WHEEL_UP: + case MouseButton::WHEEL_DOWN: + case MouseButton::WHEEL_LEFT: + case MouseButton::WHEEL_RIGHT: + queue_free(); + break; + default: + break; + } } } void EditorHelpBitTooltip::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_TREE: + _is_tooltip_visible = true; + _enter_tree_time = OS::get_singleton()->get_ticks_msec(); + break; + case NOTIFICATION_EXIT_TREE: + _is_tooltip_visible = false; + break; case NOTIFICATION_WM_MOUSE_ENTER: + _is_mouse_inside_tooltip = true; timer->stop(); break; case NOTIFICATION_WM_MOUSE_EXIT: + _is_mouse_inside_tooltip = false; _start_timer(); break; + case NOTIFICATION_INTERNAL_PROCESS: + // A workaround to hide the tooltip since the window does not receive keyboard events + // with `FLAG_POPUP` and `FLAG_NO_FOCUS` flags, so we can't use `_input_from_window()`. + if (is_inside_tree()) { + if (Input::get_singleton()->is_action_just_pressed(SNAME("ui_cancel"), true)) { + queue_free(); + get_parent_viewport()->set_input_as_handled(); + } else if (Input::get_singleton()->is_anything_pressed_except_mouse()) { + queue_free(); + } else if (!Input::get_singleton()->get_mouse_button_mask().is_empty()) { + if (!_is_mouse_inside_tooltip) { + queue_free(); + } + } else if (!Input::get_singleton()->get_last_mouse_velocity().is_zero_approx()) { + if (!_is_mouse_inside_tooltip && OS::get_singleton()->get_ticks_msec() - _enter_tree_time > 250) { + _start_timer(); + } + } + } + break; } } -// Forwards non-mouse input to the parent viewport. -void EditorHelpBitTooltip::_input_from_window(const Ref &p_event) { - if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { - _safe_queue_free(); - } else { - const Ref mouse_event = p_event; - if (mouse_event.is_null()) { - // GH-91652. Prevents use-after-free since `ProgressDialog` calls `Main::iteration()`. - _pushing_input++; - get_parent_viewport()->push_input(p_event); - _pushing_input--; - if (_pushing_input <= 0 && _need_free) { - queue_free(); - } - } +Control *EditorHelpBitTooltip::show_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue) { + // Show the custom tooltip only if it is not already visible. + // The viewport will retrigger `make_custom_tooltip()` every few seconds + // because the return control is not visible even if the custom tooltip is displayed. + if (_is_tooltip_visible || Input::get_singleton()->is_anything_pressed()) { + return _make_invisible_control(); } -} -void EditorHelpBitTooltip::show_tooltip(EditorHelpBit *p_help_bit, Control *p_target) { - ERR_FAIL_NULL(p_help_bit); + EditorHelpBit *help_bit = memnew(EditorHelpBit(p_symbol, p_prologue, false)); + EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target)); - p_help_bit->connect("request_hide", callable_mp(tooltip, &EditorHelpBitTooltip::_safe_queue_free)); - tooltip->add_child(p_help_bit); + help_bit->connect("request_hide", callable_mp(static_cast(tooltip), &Node::queue_free)); + tooltip->add_child(help_bit); p_target->add_child(tooltip); - p_help_bit->update_content_height(); + + help_bit->update_content_height(); tooltip->popup_under_cursor(); + + return _make_invisible_control(); } // Copy-paste from `Viewport::_gui_show_tooltip()`. @@ -3861,6 +3893,9 @@ void EditorHelpBitTooltip::popup_under_cursor() { r.position.y = vr.position.y; } + // When `FLAG_POPUP` is false, it prevents the editor from losing focus when displaying the tooltip. + // This way, clicks and double-clicks are still available outside the tooltip. + set_flag(Window::FLAG_POPUP, false); set_flag(Window::FLAG_NO_FOCUS, true); popup(r); } @@ -3869,13 +3904,15 @@ EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) { set_theme_type_variation("TooltipPanel"); timer = memnew(Timer); - timer->set_wait_time(0.2); - timer->connect("timeout", callable_mp(this, &EditorHelpBitTooltip::_safe_queue_free)); + timer->set_wait_time(0.25); + timer->connect("timeout", callable_mp(static_cast(this), &Node::queue_free)); add_child(timer); ERR_FAIL_NULL(p_target); p_target->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorHelpBitTooltip::_start_timer)); p_target->connect(SceneStringName(gui_input), callable_mp(this, &EditorHelpBitTooltip::_target_gui_input)); + + set_process_internal(true); } #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) diff --git a/editor/editor_help.h b/editor/editor_help.h index 4f4df43a764..85d236799d7 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -311,15 +311,13 @@ class EditorHelpBit : public VBoxContainer { void _notification(int p_what); public: - void parse_symbol(const String &p_symbol); + void parse_symbol(const String &p_symbol, const String &p_prologue = String()); void set_custom_text(const String &p_type, const String &p_name, const String &p_description); - void set_description(const String &p_text); - _FORCE_INLINE_ String get_description() const { return help_data.description; } void set_content_height_limits(float p_min, float p_max); void update_content_height(); - EditorHelpBit(const String &p_symbol = String()); + EditorHelpBit(const String &p_symbol = String(), const String &p_prologue = String(), bool p_allow_selection = true); }; // Standard tooltips do not allow you to hover over them. @@ -327,20 +325,22 @@ class EditorHelpBit : public VBoxContainer { class EditorHelpBitTooltip : public PopupPanel { GDCLASS(EditorHelpBitTooltip, PopupPanel); + static bool _is_tooltip_visible; + Timer *timer = nullptr; - int _pushing_input = 0; - bool _need_free = false; + uint64_t _enter_tree_time = 0; + bool _is_mouse_inside_tooltip = false; + + static Control *_make_invisible_control(); void _start_timer(); - void _safe_queue_free(); void _target_gui_input(const Ref &p_event); protected: void _notification(int p_what); - virtual void _input_from_window(const Ref &p_event) override; public: - static void show_tooltip(EditorHelpBit *p_help_bit, Control *p_target); + static Control *show_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue = String()); void popup_under_cursor(); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 2817a9cb0c3..4e83ce7b7cd 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -987,36 +987,33 @@ void EditorProperty::_update_pin_flags() { } Control *EditorProperty::make_custom_tooltip(const String &p_text) const { - String custom_warning; + String symbol; + String prologue; + if (object->has_method("_get_property_warning")) { - custom_warning = object->call("_get_property_warning", property); + const String custom_warning = object->call("_get_property_warning", property); + if (!custom_warning.is_empty()) { + prologue = "[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + custom_warning + "[/color][/b]"; + } } - if (has_doc_tooltip || !custom_warning.is_empty()) { - EditorHelpBit *help_bit = memnew(EditorHelpBit); + if (has_doc_tooltip) { + symbol = p_text; - if (has_doc_tooltip) { - help_bit->parse_symbol(p_text); - - const EditorInspector *inspector = get_parent_inspector(); - if (inspector) { - const String custom_description = inspector->get_custom_property_description(p_text); - if (!custom_description.is_empty()) { - help_bit->set_description(custom_description); + const EditorInspector *inspector = get_parent_inspector(); + if (inspector) { + const String custom_description = inspector->get_custom_property_description(p_text); + if (!custom_description.is_empty()) { + if (!prologue.is_empty()) { + prologue += '\n'; } + prologue += custom_description; } } + } - if (!custom_warning.is_empty()) { - String description = "[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + custom_warning + "[/color][/b]"; - if (!help_bit->get_description().is_empty()) { - description += "\n" + help_bit->get_description(); - } - help_bit->set_description(description); - } - - EditorHelpBitTooltip::show_tooltip(help_bit, const_cast(this)); - return memnew(Control); // Make the standard tooltip invisible. + if (!symbol.is_empty() || !prologue.is_empty()) { + return EditorHelpBitTooltip::show_tooltip(const_cast(this), symbol, prologue); } return nullptr; @@ -1272,9 +1269,7 @@ Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) cons return nullptr; } - EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text)); - EditorHelpBitTooltip::show_tooltip(help_bit, const_cast(this)); - return memnew(Control); // Make the standard tooltip invisible. + return EditorHelpBitTooltip::show_tooltip(const_cast(this), p_text); } Size2 EditorInspectorCategory::get_minimum_size() const { diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 00829ef41e4..39978c87e6b 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -2269,9 +2269,7 @@ ThemeTypeDialog::ThemeTypeDialog() { /////////////////////// Control *ThemeItemLabel::make_custom_tooltip(const String &p_text) const { - EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text)); - EditorHelpBitTooltip::show_tooltip(help_bit, const_cast(this)); - return memnew(Control); // Make the standard tooltip invisible. + return EditorHelpBitTooltip::show_tooltip(const_cast(this), p_text); } VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) { diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 92846741103..aba75f0e7c5 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -4842,14 +4842,7 @@ void DisplayServerX11::process_events() { WindowID window_id_other = INVALID_WINDOW_ID; Window wd_other_x11_window; - if (wd.focused) { - // Handle cases where an unfocused popup is open that needs to receive button-up events. - WindowID popup_id = _get_focused_window_or_popup(); - if (popup_id != INVALID_WINDOW_ID && popup_id != window_id) { - window_id_other = popup_id; - wd_other_x11_window = windows[popup_id].x11_window; - } - } else { + if (!wd.focused) { // Propagate the event to the focused window, // because it's received only on the topmost window. // Note: This is needed for drag & drop to work between windows, diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 6762892b34b..809354e3724 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1457,7 +1457,18 @@ void Viewport::_gui_show_tooltip() { &tooltip_owner); gui.tooltip_text = gui.tooltip_text.strip_edges(); - if (gui.tooltip_text.is_empty()) { + // Controls can implement `make_custom_tooltip` to provide their own tooltip. + // This should be a Control node which will be added as child to a TooltipPanel. + Control *base_tooltip = tooltip_owner->make_custom_tooltip(gui.tooltip_text); + + // When the custom control is not visible, don't show any tooltip. + // This way, the custom tooltip from `ConnectionsDockTree` can create + // its own tooltip without conflicting with the default one, even an empty tooltip. + if (base_tooltip && !base_tooltip->is_visible()) { + return; + } + + if (gui.tooltip_text.is_empty() && !base_tooltip) { return; // Nothing to show. } @@ -1478,10 +1489,6 @@ void Viewport::_gui_show_tooltip() { // Ensure no opaque background behind the panel as its StyleBox can be partially transparent (e.g. corners). panel->set_transparent_background(true); - // Controls can implement `make_custom_tooltip` to provide their own tooltip. - // This should be a Control node which will be added as child to a TooltipPanel. - Control *base_tooltip = tooltip_owner->make_custom_tooltip(gui.tooltip_text); - // If no custom tooltip is given, use a default implementation. if (!base_tooltip) { gui.tooltip_label = memnew(Label); From 781b0c3b8cf37b03451c4b8124076fbf596b1a3d Mon Sep 17 00:00:00 2001 From: Giganzo <158825920+Giganzo@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:29:35 +0200 Subject: [PATCH 05/29] Fix Lock and Group for canvas items not updated in editor after changed in SceenTree (cherry picked from commit f03e081efb24219d01bff60663c3ba00828249c2) --- editor/plugins/canvas_item_editor_plugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index b5d3411e209..f13b95335c1 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4007,6 +4007,7 @@ void CanvasItemEditor::_notification(int p_what) { case NOTIFICATION_READY: { _update_lock_and_group_button(); + SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button)); EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true)); EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false)); ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &CanvasItemEditor::_project_settings_changed)); From 0ec5916601c97eab33457e22401db0f91407d640 Mon Sep 17 00:00:00 2001 From: Marius Hanl Date: Mon, 2 Sep 2024 00:28:44 +0200 Subject: [PATCH 06/29] Fix jumping to editor help does not scroll correctly sometimes (cherry picked from commit af926854455bba29658271dee7b8d0ce7556bfaa) --- editor/editor_help.cpp | 6 +----- editor/plugins/script_editor_plugin.cpp | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 195fb25eab2..4569e8fbb93 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -2341,11 +2341,7 @@ void EditorHelp::_help_callback(const String &p_topic) { } if (class_desc->is_ready()) { - // call_deferred() is not enough. - if (class_desc->is_connected(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph))) { - class_desc->disconnect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph)); - } - class_desc->connect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph).bind(line), CONNECT_ONE_SHOT | CONNECT_DEFERRED); + class_desc->scroll_to_paragraph(line); } else { scroll_to = line; } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 6553d166619..3cd8384160f 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -3529,14 +3529,13 @@ void ScriptEditor::_help_class_goto(const String &p_desc) { eh->set_name(cname); tab_container->add_child(eh); + _go_to_tab(tab_container->get_tab_count() - 1); eh->go_to_help(p_desc); eh->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto)); _add_recent_script(eh->get_class()); _sort_list_on_update = true; _update_script_names(); _save_layout(); - - callable_mp(this, &ScriptEditor::_help_tab_goto).call_deferred(cname, p_desc); } bool ScriptEditor::_help_tab_goto(const String &p_name, const String &p_desc) { From f43d34191b6be2bf6dbf81408f7063f7679fe947 Mon Sep 17 00:00:00 2001 From: Robert Yevdokimov <105675984+ryevdokimov@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:37:37 -0700 Subject: [PATCH 07/29] Hide white circle outline during instant transformations (cherry picked from commit 92b3e5fe8a0ad7d38d2f65e988fea4efc3f5253b) --- editor/plugins/node_3d_editor_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 12bf1965e3b..cab38fd886f 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -3949,7 +3949,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { xform.orthonormalize(); xform.basis.scale(scale); RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform); - RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)); + RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && !_edit.instant && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)); } void Node3DEditorViewport::set_state(const Dictionary &p_state) { From 964337af29e1ed8833287126232309ee4d6802a9 Mon Sep 17 00:00:00 2001 From: Haoyu Qiu Date: Fri, 20 Sep 2024 16:21:03 +0800 Subject: [PATCH 08/29] Update AnimationTree parameter list when updating AnimationNodeTransition input names (cherry picked from commit f169616cc6b394b5ba95e2d1d99f217552dab60f) --- scene/animation/animation_blend_tree.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index daf6cf848dc..84295a59a9c 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -1162,7 +1162,11 @@ void AnimationNodeTransition::remove_input(int p_index) { bool AnimationNodeTransition::set_input_name(int p_input, const String &p_name) { pending_update = true; - return AnimationNode::set_input_name(p_input, p_name); + if (!AnimationNode::set_input_name(p_input, p_name)) { + return false; + } + emit_signal(SNAME("tree_changed")); // For updating enum options. + return true; } void AnimationNodeTransition::set_input_as_auto_advance(int p_input, bool p_enable) { From 74009d80829930370122059034a6baa49221b391 Mon Sep 17 00:00:00 2001 From: Haoyu Qiu Date: Fri, 23 Aug 2024 14:30:51 +0800 Subject: [PATCH 09/29] Add `String.is_valid_unicode_identifier()` - Adds `is_valid_unicode_identifier()` - Adds `is_valid_ascii_identifier()` - Deprecates `is_valid_identifier()` - Renames `validate_identifier()` to `validate_ascii_identifier()` (cherry picked from commit 8bf4ecc026029eb7b16645c11930b73cf642dbfa) --- core/core_bind.cpp | 2 +- core/extension/gdextension.cpp | 2 +- core/object/object.cpp | 2 +- core/string/ustring.cpp | 24 ++++++++++++-- core/string/ustring.h | 8 +++-- core/variant/variant_call.cpp | 2 ++ doc/classes/String.xml | 32 ++++++++++++++++++- doc/classes/StringName.xml | 32 ++++++++++++++++++- editor/editor_autoload_settings.cpp | 2 +- editor/editor_inspector.cpp | 2 +- editor/plugins/script_text_editor.cpp | 10 +++--- .../plugins/visual_shader_editor_plugin.cpp | 4 +-- editor/project_settings_editor.cpp | 2 +- editor/script_create_dialog.cpp | 2 +- editor/shader_globals_editor.cpp | 2 +- modules/gdscript/gdscript_editor.cpp | 6 ++-- scene/resources/visual_shader.cpp | 4 +-- servers/rendering/rendering_device_binds.cpp | 2 +- servers/rendering/shader_preprocessor.cpp | 2 +- servers/text_server.cpp | 18 +---------- tests/core/object/test_class_db.h | 4 +-- tests/core/string/test_string.h | 28 ++++++++-------- 22 files changed, 132 insertions(+), 60 deletions(-) diff --git a/core/core_bind.cpp b/core/core_bind.cpp index ecbbaa31590..c7d41d7affc 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1752,7 +1752,7 @@ Object *Engine::get_singleton_object(const StringName &p_name) const { void Engine::register_singleton(const StringName &p_name, Object *p_object) { ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name)); - ERR_FAIL_COND_MSG(!String(p_name).is_valid_identifier(), "Singleton name is not a valid identifier: " + p_name); + ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), "Singleton name is not a valid identifier: " + p_name); ::Engine::Singleton s; s.class_name = p_name; s.name = p_name; diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index bf66ad41021..bf04659d979 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -461,7 +461,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr StringName class_name = *reinterpret_cast(p_class_name); StringName parent_class_name = *reinterpret_cast(p_parent_class_name); - ERR_FAIL_COND_MSG(!String(class_name).is_valid_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier."); + ERR_FAIL_COND_MSG(!String(class_name).is_valid_ascii_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier."); ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered."); Extension *parent_extension = nullptr; diff --git a/core/object/object.cpp b/core/object/object.cpp index fdbee1e5f88..29e8cb41770 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -999,7 +999,7 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) { if (E) { E->value = p_value; } else { - ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_identifier(), "Invalid metadata identifier: '" + p_name + "'."); + ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), "Invalid metadata identifier: '" + p_name + "'."); Variant *V = &metadata.insert(p_name, p_value)->value; const String &sname = p_name; diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 3d6f37f78f1..2cfdad3c01e 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -4589,7 +4589,7 @@ bool String::is_absolute_path() const { } } -String String::validate_identifier() const { +String String::validate_ascii_identifier() const { if (is_empty()) { return "_"; // Empty string is not a valid identifier; } @@ -4612,7 +4612,7 @@ String String::validate_identifier() const { return result; } -bool String::is_valid_identifier() const { +bool String::is_valid_ascii_identifier() const { int len = length(); if (len == 0) { @@ -4634,6 +4634,26 @@ bool String::is_valid_identifier() const { return true; } +bool String::is_valid_unicode_identifier() const { + const char32_t *str = ptr(); + int len = length(); + + if (len == 0) { + return false; // Empty string. + } + + if (!is_unicode_identifier_start(str[0])) { + return false; + } + + for (int i = 1; i < len; i++) { + if (!is_unicode_identifier_continue(str[i])) { + return false; + } + } + return true; +} + bool String::is_valid_string() const { int l = length(); const char32_t *src = get_data(); diff --git a/core/string/ustring.h b/core/string/ustring.h index be51ffa7676..e71ea9fa79c 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -461,10 +461,11 @@ class String { // node functions static String get_invalid_node_name_characters(bool p_allow_internal = false); String validate_node_name() const; - String validate_identifier() const; + String validate_ascii_identifier() const; String validate_filename() const; - bool is_valid_identifier() const; + bool is_valid_ascii_identifier() const; + bool is_valid_unicode_identifier() const; bool is_valid_int() const; bool is_valid_float() const; bool is_valid_hex_number(bool p_with_prefix) const; @@ -472,6 +473,9 @@ class String { bool is_valid_ip_address() const; bool is_valid_filename() const; + // Use `is_valid_ascii_identifier()` instead. Kept for compatibility. + bool is_valid_identifier() const { return is_valid_ascii_identifier(); } + /** * The constructors must not depend on other overloads */ diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index d0c78c7cc93..3074509c3d4 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1726,6 +1726,8 @@ static void _register_variant_builtin_methods_string() { bind_string_method(validate_node_name, sarray(), varray()); bind_string_method(validate_filename, sarray(), varray()); + bind_string_method(is_valid_ascii_identifier, sarray(), varray()); + bind_string_method(is_valid_unicode_identifier, sarray(), varray()); bind_string_method(is_valid_identifier, sarray(), varray()); bind_string_method(is_valid_int, sarray(), varray()); bind_string_method(is_valid_float, sarray(), varray()); diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 098a86c112f..19fae596af2 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -453,6 +453,19 @@ Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order, [b]ignoring case[/b]. + + + + Returns [code]true[/code] if this string is a valid ASCII identifier. A valid ASCII identifier may contain only letters, digits, and underscores ([code]_[/code]), and the first character may not be a digit. + [codeblock] + print("node_2d".is_valid_ascii_identifier()) # Prints true + print("TYPE_FLOAT".is_valid_ascii_identifier()) # Prints true + print("1st_method".is_valid_ascii_identifier()) # Prints false + print("MyMethod#2".is_valid_ascii_identifier()) # Prints false + [/codeblock] + See also [method is_valid_unicode_identifier]. + + @@ -492,7 +505,7 @@ Returns [code]true[/code] if this string is a valid color in hexadecimal HTML notation. The string must be a hexadecimal value (see [method is_valid_hex_number]) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). Other HTML notations for colors, such as names or [code]hsl()[/code], are not considered valid. See also [method Color.html]. - + Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]), and the first character may not be a digit. @@ -523,6 +536,23 @@ Returns [code]true[/code] if this string represents a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]"0.0.0.0"[/code] and [code]"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"[/code] as valid. + + + + Returns [code]true[/code] if this string is a valid Unicode identifier. + A valid Unicode identifier must begin with a Unicode character of class [code]XID_Start[/code] or [code]"_"[/code], and may contain Unicode characters of class [code]XID_Continue[/code] in the other positions. + [codeblock] + print("node_2d".is_valid_unicode_identifier()) # Prints true + print("1st_method".is_valid_unicode_identifier()) # Prints false + print("MyMethod#2".is_valid_unicode_identifier()) # Prints false + print("állóképesség".is_valid_unicode_identifier()) # Prints true + print("выносливость".is_valid_unicode_identifier()) # Prints true + print("体力".is_valid_unicode_identifier()) # Prints true + [/codeblock] + See also [method is_valid_ascii_identifier]. + [b]Note:[/b] This method checks identifiers the same way as GDScript. See [method TextServer.is_valid_identifier] for more advanced checks. + + diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index bebafd7779b..45401269be9 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -421,6 +421,19 @@ Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order, [b]ignoring case[/b]. + + + + Returns [code]true[/code] if this string is a valid ASCII identifier. A valid ASCII identifier may contain only letters, digits, and underscores ([code]_[/code]), and the first character may not be a digit. + [codeblock] + print("node_2d".is_valid_ascii_identifier()) # Prints true + print("TYPE_FLOAT".is_valid_ascii_identifier()) # Prints true + print("1st_method".is_valid_ascii_identifier()) # Prints false + print("MyMethod#2".is_valid_ascii_identifier()) # Prints false + [/codeblock] + See also [method is_valid_unicode_identifier]. + + @@ -460,7 +473,7 @@ Returns [code]true[/code] if this string is a valid color in hexadecimal HTML notation. The string must be a hexadecimal value (see [method is_valid_hex_number]) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). Other HTML notations for colors, such as names or [code]hsl()[/code], are not considered valid. See also [method Color.html]. - + Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]), and the first character may not be a digit. @@ -491,6 +504,23 @@ Returns [code]true[/code] if this string represents a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]"0.0.0.0"[/code] and [code]"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"[/code] as valid. + + + + Returns [code]true[/code] if this string is a valid Unicode identifier. + A valid Unicode identifier must begin with a Unicode character of class [code]XID_Start[/code] or [code]"_"[/code], and may contain Unicode characters of class [code]XID_Continue[/code] in the other positions. + [codeblock] + print("node_2d".is_valid_unicode_identifier()) # Prints true + print("1st_method".is_valid_unicode_identifier()) # Prints false + print("MyMethod#2".is_valid_unicode_identifier()) # Prints false + print("állóképesség".is_valid_unicode_identifier()) # Prints true + print("выносливость".is_valid_unicode_identifier()) # Prints true + print("体力".is_valid_unicode_identifier()) # Prints true + [/codeblock] + See also [method is_valid_ascii_identifier]. + [b]Note:[/b] This method checks identifiers the same way as GDScript. See [method TextServer.is_valid_identifier] for more advanced checks. + + diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index 7e56e0ed266..834acb1ac6f 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -90,7 +90,7 @@ void EditorAutoloadSettings::_notification(int p_what) { } bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, String *r_error) { - if (!p_name.is_valid_identifier()) { + if (!p_name.is_valid_ascii_identifier()) { if (r_error) { *r_error = TTR("Invalid name.") + " "; if (p_name.size() > 0 && p_name.left(1).is_numeric()) { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 4e83ce7b7cd..16831648dbf 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -4269,7 +4269,7 @@ void EditorInspector::_check_meta_name() { if (meta_name.is_empty()) { validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty."), EditorValidationPanel::MSG_ERROR); - } else if (!meta_name.is_valid_identifier()) { + } else if (!meta_name.is_valid_ascii_identifier()) { validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name must be a valid identifier."), EditorValidationPanel::MSG_ERROR); } else if (object->has_meta(meta_name)) { validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists."), meta_name), EditorValidationPanel::MSG_ERROR); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 3aa4e880bb7..c0d4e332782 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1818,9 +1818,9 @@ static String _get_dropped_resource_line(const Ref &p_resource, bool p } if (is_script) { - variable_name = variable_name.to_pascal_case().validate_identifier(); + variable_name = variable_name.to_pascal_case().validate_ascii_identifier(); } else { - variable_name = variable_name.to_snake_case().to_upper().validate_identifier(); + variable_name = variable_name.to_snake_case().to_upper().validate_ascii_identifier(); } return vformat("const %s = preload(%s)", variable_name, _quote_drop_data(path)); } @@ -1934,13 +1934,13 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data path = sn->get_path_to(node); } for (const String &segment : path.split("/")) { - if (!segment.is_valid_identifier()) { + if (!segment.is_valid_ascii_identifier()) { path = _quote_drop_data(path); break; } } - String variable_name = String(node->get_name()).to_snake_case().validate_identifier(); + String variable_name = String(node->get_name()).to_snake_case().validate_ascii_identifier(); if (use_type) { StringName class_name = node->get_class_name(); Ref