diff options
Diffstat (limited to 'editor')
68 files changed, 1033 insertions, 422 deletions
diff --git a/editor/SCsub b/editor/SCsub index e613a71238..029048969a 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -4,11 +4,12 @@ Import("env") env.editor_sources = [] -import os import glob +import os + import editor_builders -import methods +import methods if env.editor_build: # Generate doc data paths diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp index 6ff0520ab6..3023c5907a 100644 --- a/editor/action_map_editor.cpp +++ b/editor/action_map_editor.cpp @@ -505,6 +505,9 @@ void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_info event_item->set_button_color(2, 1, Color(1, 1, 1, 0.75)); } } + + // Update UI. + clear_all_search->set_disabled(action_list_search->get_text().is_empty() && action_list_search_by_event->get_event().is_null()); } void ActionMapEditor::show_message(const String &p_message) { @@ -550,8 +553,9 @@ ActionMapEditor::ActionMapEditor() { action_list_search_by_event->connect(SceneStringName(focus_exited), callable_mp(this, &ActionMapEditor::_on_filter_unfocused)); top_hbox->add_child(action_list_search_by_event); - Button *clear_all_search = memnew(Button); + clear_all_search = memnew(Button); clear_all_search->set_text(TTR("Clear All")); + clear_all_search->set_tooltip_text(TTR("Clear all search filters.")); clear_all_search->connect(SceneStringName(pressed), callable_mp(action_list_search_by_event, &EventListenerLineEdit::clear_event)); clear_all_search->connect(SceneStringName(pressed), callable_mp(action_list_search, &LineEdit::clear)); top_hbox->add_child(clear_all_search); diff --git a/editor/action_map_editor.h b/editor/action_map_editor.h index 2b329f2fca..017296bfaa 100644 --- a/editor/action_map_editor.h +++ b/editor/action_map_editor.h @@ -52,7 +52,7 @@ public: bool has_initial = false; Dictionary action_initial; - Ref<Texture2D> icon = Ref<Texture2D>(); + Ref<Texture2D> icon; bool editable = true; }; @@ -85,6 +85,7 @@ private: CheckButton *show_builtin_actions_checkbutton = nullptr; LineEdit *action_list_search = nullptr; EventListenerLineEdit *action_list_search_by_event = nullptr; + Button *clear_all_search = nullptr; HBoxContainer *add_hbox = nullptr; LineEdit *add_edit = nullptr; diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index ebb63dd57c..045774080c 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -7312,17 +7312,19 @@ AnimationTrackEditor::AnimationTrackEditor() { bottom_hb->add_child(zoom); timeline->set_zoom(zoom); + ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F); + auto_fit = memnew(Button); auto_fit->set_flat(true); auto_fit->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_auto_fit)); - auto_fit->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F)); + auto_fit->set_shortcut(ED_GET_SHORTCUT("animation_editor/auto_fit")); bottom_hb->add_child(auto_fit); auto_fit_bezier = memnew(Button); auto_fit_bezier->set_flat(true); auto_fit_bezier->set_visible(false); auto_fit_bezier->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_auto_fit_bezier)); - auto_fit_bezier->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F)); + auto_fit_bezier->set_shortcut(ED_GET_SHORTCUT("animation_editor/auto_fit")); bottom_hb->add_child(auto_fit_bezier); edit = memnew(MenuButton); diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index 1d3c7aec3f..2f7183b883 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -314,12 +314,18 @@ void EditorDebuggerNode::stop(bool p_force) { void EditorDebuggerNode::_notification(int p_what) { switch (p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - if (tabs->get_tab_count() > 1 && EditorThemeManager::is_generated_theme_outdated()) { + if (!EditorThemeManager::is_generated_theme_outdated()) { + return; + } + + if (tabs->get_tab_count() > 1) { add_theme_constant_override("margin_left", -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles))->get_margin(SIDE_LEFT)); add_theme_constant_override("margin_right", -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles))->get_margin(SIDE_RIGHT)); tabs->add_theme_style_override("panel", EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("DebuggerPanel"), EditorStringName(EditorStyles))); } + + remote_scene_tree->update_icon_max_width(); } break; case NOTIFICATION_READY: { diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp index 63053d2574..12b590da3c 100644 --- a/editor/debugger/editor_debugger_tree.cpp +++ b/editor/debugger/editor_debugger_tree.cpp @@ -31,6 +31,7 @@ #include "editor_debugger_tree.h" #include "editor/editor_node.h" +#include "editor/editor_string_names.h" #include "editor/gui/editor_file_dialog.h" #include "editor/scene_tree_dock.h" #include "scene/debugger/scene_debugger.h" @@ -62,6 +63,10 @@ void EditorDebuggerTree::_notification(int p_what) { connect("item_collapsed", callable_mp(this, &EditorDebuggerTree::_scene_tree_folded)); connect("item_mouse_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_rmb_selected)); } break; + + case NOTIFICATION_ENTER_TREE: { + update_icon_max_width(); + } break; } } @@ -293,6 +298,10 @@ Variant EditorDebuggerTree::get_drag_data(const Point2 &p_point) { return vformat("\"%s\"", path); } +void EditorDebuggerTree::update_icon_max_width() { + add_theme_constant_override("icon_max_width", get_theme_constant("class_icon_size", EditorStringName(Editor))); +} + String EditorDebuggerTree::get_selected_path() { if (!get_selected()) { return ""; diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h index 895f33f1a2..dbffb0f219 100644 --- a/editor/debugger/editor_debugger_tree.h +++ b/editor/debugger/editor_debugger_tree.h @@ -72,6 +72,7 @@ public: virtual Variant get_drag_data(const Point2 &p_point) override; + void update_icon_max_width(); String get_selected_path(); ObjectID get_selected_object(); int get_current_debugger(); // Would love to have one tree for every debugger. diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index 69cf13ea0b..0e2a7ee599 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -34,6 +34,7 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/themes/editor_scale.h" +#include "editor/themes/editor_theme_manager.h" #include "scene/resources/image_texture.h" void EditorProfiler::_make_metric_ptrs(Metric &m) { @@ -423,6 +424,15 @@ void EditorProfiler::_notification(int p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { activate->set_icon(get_editor_theme_icon(SNAME("Play"))); clear_button->set_icon(get_editor_theme_icon(SNAME("Clear"))); + + theme_cache.seek_line_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor)); + theme_cache.seek_line_color.a = 0.8; + theme_cache.seek_line_hover_color = theme_cache.seek_line_color; + theme_cache.seek_line_hover_color.a = 0.4; + + if (total_metrics > 0) { + _update_plot(); + } } break; } } @@ -434,11 +444,11 @@ void EditorProfiler::_graph_tex_draw() { if (seeking) { int frame = cursor_metric_edit->get_value() - _get_frame_metric(0).frame_number; int cur_x = (2 * frame + 1) * graph->get_size().x / (2 * frame_metrics.size()) + 1; - graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), Color(1, 1, 1, 0.8)); + graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), theme_cache.seek_line_color); } if (hover_metric > -1 && hover_metric < total_metrics) { int cur_x = (2 * hover_metric + 1) * graph->get_size().x / (2 * frame_metrics.size()) + 1; - graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), Color(1, 1, 1, 0.4)); + graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), theme_cache.seek_line_hover_color); } } diff --git a/editor/debugger/editor_profiler.h b/editor/debugger/editor_profiler.h index 620d21fe98..64253070b1 100644 --- a/editor/debugger/editor_profiler.h +++ b/editor/debugger/editor_profiler.h @@ -94,6 +94,11 @@ public: }; private: + struct ThemeCache { + Color seek_line_color; + Color seek_line_hover_color; + } theme_cache; + Button *activate = nullptr; Button *clear_button = nullptr; TextureRect *graph = nullptr; diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index b58e82b7e7..331dacf6ad 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -344,25 +344,6 @@ void DocTools::merge_from(const DocTools &p_data) { merge_theme_properties(c.theme_properties, cf.theme_properties); merge_operators(c.operators, cf.operators); - -#ifndef MODULE_MONO_ENABLED - // The Mono module defines some properties that we want to keep when - // re-generating docs with a non-Mono build, to prevent pointless diffs - // (and loss of descriptions) depending on the config of the doc writer. - // We use a horrible hack to force keeping the relevant properties, - // hardcoded below. At least it's an ad hoc hack... ¯\_(ツ)_/¯ - // Don't show this to your kids. - if (c.name == "@GlobalScope") { - // Retrieve GodotSharp singleton. - for (int j = 0; j < cf.properties.size(); j++) { - if (cf.properties[j].name == "GodotSharp") { - c.properties.push_back(cf.properties[j]); - c.properties.sort(); - break; - } - } - } -#endif } } diff --git a/editor/editor_builders.py b/editor/editor_builders.py index cfe6e56b49..625d570666 100644 --- a/editor/editor_builders.py +++ b/editor/editor_builders.py @@ -7,6 +7,7 @@ import subprocess import tempfile import uuid import zlib + from methods import print_warning diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index 5a7e914c11..020706c714 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -497,8 +497,8 @@ void EditorFeatureProfileManager::_hide_requested() { _cancel_pressed(); // From AcceptDialog. } -void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) { - TreeItem *class_item = class_list->create_item(p_parent); +void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected, int p_class_insert_index) { + TreeItem *class_item = class_list->create_item(p_parent, p_class_insert_index); class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class)); String text = p_class; @@ -647,7 +647,7 @@ void EditorFeatureProfileManager::_class_list_item_edited() { String class_selected = md; edited->set_disable_class(class_selected, !checked); _save_and_update(); - _update_selected_profile(); + _update_profile_tree_from(item); } else if (md.get_type() == Variant::INT) { int feature_selected = md; edited->set_disable_feature(EditorFeatureProfile::Feature(feature_selected), !checked); @@ -703,19 +703,29 @@ void EditorFeatureProfileManager::_property_item_edited() { String property_selected = md; edited->set_disable_class_property(class_name, property_selected, !checked); _save_and_update(); - _update_selected_profile(); + _update_profile_tree_from(class_list->get_selected()); } else if (md.get_type() == Variant::INT) { int feature_selected = md; switch (feature_selected) { case CLASS_OPTION_DISABLE_EDITOR: { edited->set_disable_class_editor(class_name, !checked); _save_and_update(); - _update_selected_profile(); + _update_profile_tree_from(class_list->get_selected()); } break; } } } +void EditorFeatureProfileManager::_update_profile_tree_from(TreeItem *p_edited) { + String edited_class = p_edited->get_metadata(0); + + TreeItem *edited_parent = p_edited->get_parent(); + int class_insert_index = p_edited->get_index(); + p_edited->get_parent()->remove_child(p_edited); + + _fill_classes_from(edited_parent, edited_class, edited_class, class_insert_index); +} + void EditorFeatureProfileManager::_update_selected_profile() { String class_selected; int feature_selected = -1; diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h index 2fa6ae9813..7458a04e19 100644 --- a/editor/editor_feature_profile.h +++ b/editor/editor_feature_profile.h @@ -147,7 +147,8 @@ class EditorFeatureProfileManager : public AcceptDialog { String current_profile; void _update_profile_list(const String &p_select_profile = String()); void _update_selected_profile(); - void _fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected); + void _update_profile_tree_from(TreeItem *p_edited); + void _fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected, int p_class_insert_index = -1); Ref<EditorFeatureProfile> current; Ref<EditorFeatureProfile> edited; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index a8d978c66d..48c0c7ac06 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1967,7 +1967,7 @@ void EditorHelp::_update_doc() { class_desc->add_text(argument.name); class_desc->add_text(": "); - _add_type(argument.type); + _add_type(argument.type, argument.enumeration, argument.is_bitfield); if (!argument.default_value.is_empty()) { class_desc->push_color(theme_cache.symbol_color); @@ -2890,7 +2890,7 @@ void EditorHelp::_load_doc_thread(void *p_udata) { callable_mp_static(&EditorHelp::_gen_extensions_docs).call_deferred(); } else { // We have to go back to the main thread to start from scratch, bypassing any possibly existing cache. - callable_mp_static(&EditorHelp::generate_doc).bind(false).call_deferred(); + callable_mp_static(&EditorHelp::generate_doc).call_deferred(false); } OS::get_singleton()->benchmark_end_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count)); diff --git a/editor/editor_inspector.compat.inc b/editor/editor_inspector.compat.inc new file mode 100644 index 0000000000..53c410ba26 --- /dev/null +++ b/editor/editor_inspector.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* editor_inspector.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void EditorInspectorPlugin::_add_property_editor_bind_compat_92322(const String &p_for_property, Control *p_prop, bool p_add_to_end) { + add_property_editor(p_for_property, p_prop, p_add_to_end, ""); +} + +void EditorInspectorPlugin::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("add_property_editor", "property", "editor", "add_to_end"), &EditorInspectorPlugin::_add_property_editor_bind_compat_92322, DEFVAL(false)); +} + +#endif // DISABLE_DEPRECATED diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 8e608fb797..f4dcc8bd4a 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "editor_inspector.h" +#include "editor_inspector.compat.inc" #include "core/os/keyboard.h" #include "editor/doc_tools.h" @@ -1128,11 +1129,12 @@ void EditorInspectorPlugin::add_custom_control(Control *control) { added_editors.push_back(ae); } -void EditorInspectorPlugin::add_property_editor(const String &p_for_property, Control *p_prop, bool p_add_to_end) { +void EditorInspectorPlugin::add_property_editor(const String &p_for_property, Control *p_prop, bool p_add_to_end, const String &p_label) { AddedEditor ae; ae.properties.push_back(p_for_property); ae.property_editor = p_prop; ae.add_to_end = p_add_to_end; + ae.label = p_label; added_editors.push_back(ae); } @@ -1174,7 +1176,7 @@ void EditorInspectorPlugin::parse_end(Object *p_object) { void EditorInspectorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_custom_control", "control"), &EditorInspectorPlugin::add_custom_control); - ClassDB::bind_method(D_METHOD("add_property_editor", "property", "editor", "add_to_end"), &EditorInspectorPlugin::add_property_editor, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_property_editor", "property", "editor", "add_to_end", "label"), &EditorInspectorPlugin::add_property_editor, DEFVAL(false), DEFVAL(String())); ClassDB::bind_method(D_METHOD("add_property_editor_for_multiple_properties", "label", "properties", "editor"), &EditorInspectorPlugin::add_property_editor_for_multiple_properties); GDVIRTUAL_BIND(_can_handle, "object") @@ -2727,6 +2729,8 @@ void EditorInspector::update_tree() { // TODO: Can be useful to store more context for the focusable, such as the caret position in LineEdit. StringName current_selected = property_selected; int current_focusable = -1; + // Temporarily disable focus following to avoid jumping while the inspector is updating. + set_follow_focus(false); if (property_focusable != -1) { // Check that focusable is actually focusable. @@ -3482,6 +3486,7 @@ void EditorInspector::update_tree() { // Updating inspector might invalidate some editing owners. EditorNode::get_singleton()->hide_unused_editors(); } + set_follow_focus(true); } void EditorInspector::update_property(const String &p_prop) { @@ -3779,7 +3784,6 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo } emit_signal(_prop_edited, p_name); - } else if (Object::cast_to<MultiNodeEdit>(object)) { Object::cast_to<MultiNodeEdit>(object)->set_property_field(p_name, p_value, p_changed_field); _edit_request_change(object, p_name); @@ -3956,7 +3960,7 @@ void EditorInspector::_property_checked(const String &p_path, bool p_checked) { //property checked if (autoclear) { if (!p_checked) { - object->set(p_path, Variant()); + _edit_set(p_path, Variant(), false, ""); } else { Variant to_create; List<PropertyInfo> pinfo; @@ -3968,7 +3972,7 @@ void EditorInspector::_property_checked(const String &p_path, bool p_checked) { break; } } - object->set(p_path, to_create); + _edit_set(p_path, to_create, false, ""); } if (editor_property_map.has(p_path)) { @@ -3979,7 +3983,6 @@ void EditorInspector::_property_checked(const String &p_path, bool p_checked) { E->update_cache(); } } - } else { emit_signal(SNAME("property_toggled"), p_path, p_checked); } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 3cbee5c502..a0ced55bd8 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -252,9 +252,13 @@ protected: GDVIRTUAL7R(bool, _parse_property, Object *, Variant::Type, String, PropertyHint, String, BitField<PropertyUsageFlags>, bool) GDVIRTUAL1(_parse_end, Object *) +#ifndef DISABLE_DEPRECATED + void _add_property_editor_bind_compat_92322(const String &p_for_property, Control *p_prop, bool p_add_to_end); + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED public: void add_custom_control(Control *control); - void add_property_editor(const String &p_for_property, Control *p_prop, bool p_add_to_end = false); + void add_property_editor(const String &p_for_property, Control *p_prop, bool p_add_to_end = false, const String &p_label = String()); void add_property_editor_for_multiple_properties(const String &p_label, const Vector<String> &p_properties, Control *p_prop); virtual bool can_handle(Object *p_object); @@ -344,7 +348,7 @@ class EditorInspectorArray : public EditorInspectorSection { MODE_NONE, MODE_USE_COUNT_PROPERTY, MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION, - } mode; + } mode = MODE_NONE; StringName count_property; StringName array_element_prefix; String swap_method; diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 6615133dea..166d09af30 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -59,7 +59,7 @@ void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_f MessageType message_type = p_type == ERR_HANDLER_WARNING ? MSG_TYPE_WARNING : MSG_TYPE_ERROR; if (self->current != Thread::get_caller_id()) { - callable_mp(self, &EditorLog::add_message).bind(err_str, message_type).call_deferred(); + callable_mp(self, &EditorLog::add_message).call_deferred(err_str, message_type); } else { self->add_message(err_str, message_type); } @@ -273,6 +273,10 @@ void EditorLog::_undo_redo_cbk(void *p_self, const String &p_name) { } void EditorLog::_rebuild_log() { + if (messages.is_empty()) { + return; + } + log->clear(); int line_count = 0; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 68fcc613ee..a436f63c3e 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -671,7 +671,7 @@ void EditorNode::_notification(int p_what) { callable_mp(this, &EditorNode::_begin_first_scan).call_deferred(); - DisplayServer::get_singleton()->set_system_theme_change_callback(callable_mp(this, &EditorNode::_update_theme)); + DisplayServer::get_singleton()->set_system_theme_change_callback(callable_mp(this, &EditorNode::_update_theme).bind(false)); /* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */ } break; @@ -5541,7 +5541,7 @@ void EditorNode::_add_dropped_files_recursive(const Vector<String> &p_files, Str } void EditorNode::_file_access_close_error_notify(const String &p_str) { - callable_mp_static(&EditorNode::_file_access_close_error_notify_impl).bind(p_str).call_deferred(); + callable_mp_static(&EditorNode::_file_access_close_error_notify_impl).call_deferred(p_str); } void EditorNode::_file_access_close_error_notify_impl(const String &p_str) { @@ -6163,7 +6163,7 @@ static Node *_resource_get_edited_scene() { } void EditorNode::_print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich) { - callable_mp_static(&EditorNode::_print_handler_impl).bind(p_string, p_error, p_rich).call_deferred(); + callable_mp_static(&EditorNode::_print_handler_impl).call_deferred(p_string, p_error, p_rich); } void EditorNode::_print_handler_impl(const String &p_string, bool p_error, bool p_rich) { @@ -6297,6 +6297,14 @@ EditorNode::EditorNode() { EditorSettings::create(); } + ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L); + ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L); + ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G); + ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G); + + // Used in the GPUParticles/CPUParticles 2D/3D editor plugins. + ED_SHORTCUT("particles/restart_emission", TTR("Restart Emission"), KeyModifierMask::CTRL | Key::R); + FileAccess::set_backup_save(EDITOR_GET("filesystem/on_save/safe_save_on_backup_then_rename")); _update_vsync_mode(); @@ -7220,6 +7228,8 @@ EditorNode::EditorNode() { disk_changed = memnew(ConfirmationDialog); { + disk_changed->set_title(TTR("Files have been modified on disk")); + VBoxContainer *vbc = memnew(VBoxContainer); disk_changed->add_child(vbc); @@ -7233,9 +7243,9 @@ EditorNode::EditorNode() { disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_modified_scenes)); disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_project_settings)); - disk_changed->set_ok_button_text(TTR("Reload")); + disk_changed->set_ok_button_text(TTR("Discard local changes and reload")); - disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); + disk_changed->add_button(TTR("Keep local changes and overwrite"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); disk_changed->connect("custom_action", callable_mp(this, &EditorNode::_resave_scenes)); } diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 49b30bd06e..103ea3ffc3 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2645,7 +2645,7 @@ void EditorPropertyColor::_color_changed(const Color &p_color) { } void EditorPropertyColor::_popup_closed() { - get_edited_object()->set(get_edited_property(), last_color); + get_edited_object()->set(get_edited_property(), was_checked ? Variant(last_color) : Variant()); if (!picker->get_pick_color().is_equal_approx(last_color)) { emit_changed(get_edited_property(), picker->get_pick_color(), "", false); } @@ -2653,6 +2653,7 @@ void EditorPropertyColor::_popup_closed() { void EditorPropertyColor::_picker_opening() { last_color = picker->get_pick_color(); + was_checked = !is_checkable() || is_checked(); } void EditorPropertyColor::_notification(int p_what) { @@ -3217,7 +3218,7 @@ void EditorPropertyResource::_open_editor_pressed() { Ref<Resource> res = get_edited_property_value(); if (res.is_valid()) { // May clear the editor so do it deferred. - callable_mp(EditorNode::get_singleton(), &EditorNode::edit_item).bind(res.ptr(), this).call_deferred(); + callable_mp(EditorNode::get_singleton(), &EditorNode::edit_item).call_deferred(res.ptr(), this); } } diff --git a/editor/editor_properties.h b/editor/editor_properties.h index d16c80bfc4..f2c5497e4f 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -625,6 +625,7 @@ class EditorPropertyColor : public EditorProperty { Color last_color; bool live_changes_enabled = true; + bool was_checked = false; protected: virtual void _set_read_only(bool p_read_only) override; diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index ca1070d7f3..b4fc47323a 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -960,6 +960,7 @@ void EditorPropertyDictionary::update_property() { memdelete(container); button_add_item = nullptr; container = nullptr; + add_panel = nullptr; slots.clear(); } return; diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp index 2c6c13401f..b1f3875175 100644 --- a/editor/editor_settings_dialog.cpp +++ b/editor/editor_settings_dialog.cpp @@ -30,14 +30,13 @@ #include "editor_settings_dialog.h" -#include "core/config/project_settings.h" #include "core/input/input_map.h" #include "core/os/keyboard.h" #include "editor/debugger/editor_debugger_node.h" -#include "editor/editor_file_system.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_property_name_processor.h" +#include "editor/editor_sectioned_inspector.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" @@ -45,7 +44,9 @@ #include "editor/input_event_configuration_dialog.h" #include "editor/themes/editor_scale.h" #include "editor/themes/editor_theme_manager.h" -#include "scene/gui/margin_container.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/tab_container.h" +#include "scene/gui/texture_rect.h" void EditorSettingsDialog::ok_pressed() { if (!EditorSettings::get_singleton()) { @@ -499,6 +500,9 @@ void EditorSettingsDialog::_update_shortcuts() { memdelete(section); } } + + // Update UI. + clear_all_search->set_disabled(shortcut_search_box->get_text().is_empty() && shortcut_search_by_event->get_event().is_null()); } void EditorSettingsDialog::_shortcut_button_pressed(Object *p_item, int p_column, int p_idx, MouseButton p_button) { @@ -782,8 +786,9 @@ EditorSettingsDialog::EditorSettingsDialog() { shortcut_search_by_event->connect(SceneStringName(focus_exited), callable_mp((AcceptDialog *)this, &AcceptDialog::set_close_on_escape).bind(true)); top_hbox->add_child(shortcut_search_by_event); - Button *clear_all_search = memnew(Button); + clear_all_search = memnew(Button); clear_all_search->set_text(TTR("Clear All")); + clear_all_search->set_tooltip_text(TTR("Clear all search filters.")); clear_all_search->connect(SceneStringName(pressed), callable_mp(shortcut_search_box, &LineEdit::clear)); clear_all_search->connect(SceneStringName(pressed), callable_mp(shortcut_search_by_event, &EventListenerLineEdit::clear_event)); top_hbox->add_child(clear_all_search); diff --git a/editor/editor_settings_dialog.h b/editor/editor_settings_dialog.h index fdfc0a43ec..cab8fe9da1 100644 --- a/editor/editor_settings_dialog.h +++ b/editor/editor_settings_dialog.h @@ -32,13 +32,14 @@ #define EDITOR_SETTINGS_DIALOG_H #include "editor/action_map_editor.h" -#include "editor/editor_inspector.h" -#include "editor/editor_sectioned_inspector.h" #include "scene/gui/dialogs.h" -#include "scene/gui/panel_container.h" -#include "scene/gui/rich_text_label.h" -#include "scene/gui/tab_container.h" -#include "scene/gui/texture_rect.h" + +class PanelContainer; +class SectionedInspector; +class TabContainer; +class TextureRect; +class Tree; +class TreeItem; class EditorSettingsDialog : public AcceptDialog { GDCLASS(EditorSettingsDialog, AcceptDialog); @@ -53,6 +54,7 @@ class EditorSettingsDialog : public AcceptDialog { LineEdit *shortcut_search_box = nullptr; EventListenerLineEdit *shortcut_search_by_event = nullptr; SectionedInspector *inspector = nullptr; + Button *clear_all_search = nullptr; // Shortcuts enum ShortcutButton { diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index c6a671b7f9..dd283ebfd6 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -131,7 +131,7 @@ void ProjectExportDialog::popup_export() { if (saved_size != Rect2()) { popup(saved_size); } else { - popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8); + popup_centered_clamped(Size2(900, 500) * EDSCALE, 0.7); } } @@ -1311,9 +1311,15 @@ ProjectExportDialog::ProjectExportDialog() { // Resources export parameters. + ScrollContainer *resources_scroll_container = memnew(ScrollContainer); + resources_scroll_container->set_name(TTR("Resources")); + resources_scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + sections->add_child(resources_scroll_container); + VBoxContainer *resources_vb = memnew(VBoxContainer); - sections->add_child(resources_vb); - resources_vb->set_name(TTR("Resources")); + resources_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + resources_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL); + resources_scroll_container->add_child(resources_vb); export_filter = memnew(OptionButton); export_filter->add_item(TTR("Export all resources in the project")); @@ -1332,6 +1338,7 @@ ProjectExportDialog::ProjectExportDialog() { resources_vb->add_child(include_margin); include_files = memnew(Tree); + include_files->set_custom_minimum_size(Size2(1, 75 * EDSCALE)); include_margin->add_child(include_files); include_files->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); include_files->connect("item_edited", callable_mp(this, &ProjectExportDialog::_tree_changed)); @@ -1384,18 +1391,25 @@ ProjectExportDialog::ProjectExportDialog() { VBoxContainer *feature_vb = memnew(VBoxContainer); feature_vb->set_name(TTR("Features")); + feature_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); custom_features = memnew(LineEdit); custom_features->connect("text_changed", callable_mp(this, &ProjectExportDialog::_custom_features_changed)); feature_vb->add_margin_child(TTR("Custom (comma-separated):"), custom_features); custom_feature_display = memnew(RichTextLabel); + custom_feature_display->set_custom_minimum_size(Size2(1, 75 * EDSCALE)); custom_feature_display->set_v_size_flags(Control::SIZE_EXPAND_FILL); feature_vb->add_margin_child(TTR("Feature List:"), custom_feature_display, true); sections->add_child(feature_vb); // Encryption export parameters. + ScrollContainer *sec_scroll_container = memnew(ScrollContainer); + sec_scroll_container->set_name(TTR("Encryption")); + sec_scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + VBoxContainer *sec_vb = memnew(VBoxContainer); - sec_vb->set_name(TTR("Encryption")); + sec_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + sec_scroll_container->add_child(sec_vb); enc_pck = memnew(CheckButton); enc_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_pck_changed)); @@ -1426,7 +1440,7 @@ ProjectExportDialog::ProjectExportDialog() { script_key_error->add_theme_color_override("font_color", EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("error_color"), EditorStringName(Editor))); sec_vb->add_margin_child(TTR("Encryption Key (256-bits as hexadecimal):"), script_key); sec_vb->add_child(script_key_error); - sections->add_child(sec_vb); + sections->add_child(sec_scroll_container); Label *sec_info = memnew(Label); sec_info->set_text(TTR("Note: Encryption key needs to be stored in the binary,\nyou need to build the export templates from source.")); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index b05cafa694..2e88540fc4 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -79,6 +79,15 @@ Control *FileSystemList::make_custom_tooltip(const String &p_text) const { } void FileSystemList::_line_editor_submit(const String &p_text) { + if (popup_edit_commited) { + return; // Already processed by _text_editor_popup_modal_close + } + + if (popup_editor->get_hide_reason() == Popup::HIDE_REASON_CANCELED) { + return; // ESC pressed, app focus lost, or forced close from code. + } + + popup_edit_commited = true; // End edit popup processing. popup_editor->hide(); emit_signal(SNAME("item_edited")); @@ -127,6 +136,7 @@ bool FileSystemList::edit_selected() { line_editor->set_text(name); line_editor->select(0, name.rfind(".")); + popup_edit_commited = false; // Start edit popup processing. popup_editor->popup(); popup_editor->child_controls_changed(); line_editor->grab_focus(); @@ -138,8 +148,12 @@ String FileSystemList::get_edit_text() { } void FileSystemList::_text_editor_popup_modal_close() { + if (popup_edit_commited) { + return; // Already processed by _text_editor_popup_modal_close + } + if (popup_editor->get_hide_reason() == Popup::HIDE_REASON_CANCELED) { - return; + return; // ESC pressed, app focus lost, or forced close from code. } _line_editor_submit(line_editor->get_text()); @@ -3265,7 +3279,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect if (p_paths.size() == 1) { const String &fpath = p_paths[0]; - bool added_separator = false; + [[maybe_unused]] bool added_separator = false; if (favorites_list.has(fpath)) { TreeItem *favorites_item = tree->get_root()->get_first_child(); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 7449657c06..3fbff3ef19 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -59,6 +59,7 @@ class FileSystemTree : public Tree { class FileSystemList : public ItemList { GDCLASS(FileSystemList, ItemList); + bool popup_edit_commited = true; VBoxContainer *popup_editor_vb = nullptr; Popup *popup_editor = nullptr; LineEdit *line_editor = nullptr; diff --git a/editor/gui/editor_toaster.cpp b/editor/gui/editor_toaster.cpp index 7aa5335b77..df6c494392 100644 --- a/editor/gui/editor_toaster.cpp +++ b/editor/gui/editor_toaster.cpp @@ -149,7 +149,7 @@ void EditorToaster::_notification(int p_what) { void EditorToaster::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) { // This may be called from a thread. Since we will deal with non-thread-safe elements, // we have to put it in the queue for safety. - callable_mp_static(&EditorToaster::_error_handler_impl).bind(String::utf8(p_file), p_line, String::utf8(p_error), String::utf8(p_errorexp), p_editor_notify, p_type).call_deferred(); + callable_mp_static(&EditorToaster::_error_handler_impl).call_deferred(String::utf8(p_file), p_line, String::utf8(p_error), String::utf8(p_errorexp), p_editor_notify, p_type); } void EditorToaster::_error_handler_impl(const String &p_file, int p_line, const String &p_error, const String &p_errorexp, bool p_editor_notify, int p_type) { diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index a06dd310ea..ddf22c46e6 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -614,9 +614,9 @@ void SceneTreeEditor::_update_tree(bool p_scroll_to_selected) { updating_tree = true; tree->clear(); + last_hash = hash_djb2_one_64(0); if (get_scene_node()) { _add_nodes(get_scene_node(), nullptr); - last_hash = hash_djb2_one_64(0); _compute_hash(get_scene_node(), last_hash); } updating_tree = false; @@ -1368,22 +1368,35 @@ bool SceneTreeEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_d } bool scene_drop = true; + bool audio_drop = true; for (int i = 0; i < files.size(); i++) { String ftype = EditorFileSystem::get_singleton()->get_file_type(files[i]); if (ftype != "PackedScene") { scene_drop = false; - break; + } + if (audio_drop && !ClassDB::is_parent_class(ftype, "AudioStream")) { + audio_drop = false; } } if (scene_drop) { tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM); - } else { + return true; + } + + if (audio_drop) { if (files.size() > 1) { - return false; + tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN); + } else { + tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM); } - tree->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM); + return true; + } + + if (files.size() > 1) { + return false; } + tree->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM); return true; } diff --git a/editor/icons/AudioStreamPlayer.svg b/editor/icons/AudioStreamPlayer.svg index 317b25d3e9..ba84502d99 100644 --- a/editor/icons/AudioStreamPlayer.svg +++ b/editor/icons/AudioStreamPlayer.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" gradientUnits="userSpaceOnUse" x2="0" y1="1" y2="15"><stop offset="0" stop-color="#ff5f5f"/><stop offset=".5" stop-color="#e1da5b"/><stop offset="1" stop-color="#5fff97"/></linearGradient><path d="M9 14a1 1 0 0 0 1.5.85l4-2.511a1 1 0 0 0 0-1.724l-4-2.511a1 1 0 0 0-1.5.85z" fill="#e0e0e0"/><path d="M13 2a1 1 0 0 0-1-1L4.754 3A1 1 0 0 0 4 4v5.55A2.5 2.5 0 1 0 6 12V4.756l5-1.428V6.5l2-1z" fill="url(#a)"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path d="M1.382.335.777.858H.204v.673h.564l.614.531Z" style="fill:#e0e0e0;stroke:#e0e0e0;stroke-width:.1764;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"/><path d="M1.718.572c.275.374.275.882 0 1.256M1.947.343c.402.5.402 1.213 0 1.714" style="fill:none;stroke:#e0e0e0;stroke-width:.176328;stroke-linecap:round;stroke-opacity:1"/></svg>
\ No newline at end of file diff --git a/editor/icons/AudioStreamPlayer2D.svg b/editor/icons/AudioStreamPlayer2D.svg index 87beccd19a..54ff128a4d 100644 --- a/editor/icons/AudioStreamPlayer2D.svg +++ b/editor/icons/AudioStreamPlayer2D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" gradientUnits="userSpaceOnUse" x2="0" y1="1" y2="15"><stop offset="0" stop-color="#ff5f5f"/><stop offset=".5" stop-color="#e1da5b"/><stop offset="1" stop-color="#5fff97"/></linearGradient><path d="M9 14a1 1 0 0 0 1.5.85l4-2.511a1 1 0 0 0 0-1.724l-4-2.511a1 1 0 0 0-1.5.85z" fill="#8da5f3"/><path d="M13 2a1 1 0 0 0-1-1L4.754 3A1 1 0 0 0 4 4v5.55A2.5 2.5 0 1 0 6 12V4.756l5-1.428V6.5l2-1z" fill="url(#a)"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path d="M1.382.335.777.858H.204v.673h.564l.614.531Z" style="fill:#8da5f3;stroke:#8da5f3;stroke-width:.1764;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"/><path d="M1.718.572c.275.374.275.882 0 1.256M1.947.343c.402.5.402 1.213 0 1.714" style="fill:none;stroke:#8da5f3;stroke-width:.176328;stroke-linecap:round;stroke-opacity:1"/></svg>
\ No newline at end of file diff --git a/editor/icons/AudioStreamPlayer3D.svg b/editor/icons/AudioStreamPlayer3D.svg index 440a5e52ef..fc0231a657 100644 --- a/editor/icons/AudioStreamPlayer3D.svg +++ b/editor/icons/AudioStreamPlayer3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" gradientUnits="userSpaceOnUse" x2="0" y1="1" y2="15"><stop offset="0" stop-color="#ff5f5f"/><stop offset=".5" stop-color="#e1da5b"/><stop offset="1" stop-color="#5fff97"/></linearGradient><path d="M9 14a1 1 0 0 0 1.5.85l4-2.511a1 1 0 0 0 0-1.724l-4-2.511a1 1 0 0 0-1.5.85z" fill="#fc7f7f"/><path d="M13 2a1 1 0 0 0-1-1L4.754 3A1 1 0 0 0 4 4v5.55A2.5 2.5 0 1 0 6 12V4.756l5-1.428V6.5l2-1z" fill="url(#a)"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path d="M1.382.335.777.858H.204v.673h.564l.614.531Z" style="fill:#fc7f7f;stroke:#fc7f7f;stroke-width:.1764;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"/><path d="M1.718.572c.275.374.275.882 0 1.256M1.947.343c.402.5.402 1.213 0 1.714" style="fill:none;stroke:#fc7f7f;stroke-width:.176328;stroke-linecap:round;stroke-opacity:1"/></svg>
\ No newline at end of file diff --git a/editor/icons/SCsub b/editor/icons/SCsub index d2d752cff4..0d9ac43c46 100644 --- a/editor/icons/SCsub +++ b/editor/icons/SCsub @@ -3,8 +3,8 @@ Import("env") import os -import editor_icons_builders +import editor_icons_builders env["BUILDERS"]["MakeEditorIconsBuilder"] = Builder( action=env.Run(editor_icons_builders.make_editor_icons_action), diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 070c44419a..c0d38af26a 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -279,7 +279,7 @@ bool ResourceImporterScene::get_option_visibility(const String &p_path, const St } } - if (animation_importer && (p_option.begins_with("nodes/") || p_option.begins_with("meshes/") || p_option.begins_with("skins/"))) { + if (animation_importer && (p_option == "nodes/root_type" || p_option == "nodes/root_name" || p_option.begins_with("meshes/") || p_option.begins_with("skins/"))) { return false; // Nothing to do here for animations. } @@ -2329,6 +2329,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import } script_ext_hint += "*." + E; } + bool trimming_defaults_on = p_path.get_extension().to_lower() == "fbx"; r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/apply_root_scale"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "nodes/root_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001"), 1.0)); @@ -2342,7 +2343,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "skins/use_named_skins"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 30)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/trimming"), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/trimming"), trimming_defaults_on)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/remove_immutable_tracks"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import_rest_as_RESET"), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "import_script/path", PROPERTY_HINT_FILE, script_ext_hint), "")); diff --git a/editor/import/resource_importer_shader_file.cpp b/editor/import/resource_importer_shader_file.cpp index bde2e3d0bf..6b20a8c9d5 100644 --- a/editor/import/resource_importer_shader_file.cpp +++ b/editor/import/resource_importer_shader_file.cpp @@ -106,7 +106,7 @@ Error ResourceImporterShaderFile::import(const String &p_source_file, const Stri if (err != OK) { if (!ShaderFileEditor::singleton->is_visible_in_tree()) { - callable_mp_static(&EditorNode::add_io_error).bind(vformat(TTR("Error importing GLSL shader file: '%s'. Open the file in the filesystem dock in order to see the reason."), p_source_file)).call_deferred(); + callable_mp_static(&EditorNode::add_io_error).call_deferred(vformat(TTR("Error importing GLSL shader file: '%s'. Open the file in the filesystem dock in order to see the reason."), p_source_file)); } } diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index 8cf104725a..487b8fc175 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -269,9 +269,9 @@ void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<I for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { Vector<uint8_t> data; if (use_webp) { - data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); + data = Image::webp_lossless_packer(i ? p_image->get_image_from_mipmap(i) : p_image); } else { - data = Image::png_packer(p_image->get_image_from_mipmap(i)); + data = Image::png_packer(i ? p_image->get_image_from_mipmap(i) : p_image); } int data_len = data.size(); f->store_32(data_len); @@ -289,7 +289,7 @@ void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<I f->store_32(p_image->get_format()); for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { - Vector<uint8_t> data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); + Vector<uint8_t> data = Image::webp_lossy_packer(i ? p_image->get_image_from_mipmap(i) : p_image, p_lossy_quality); int data_len = data.size(); f->store_32(data_len); diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index fbe76e1d5c..2d87e6592f 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -188,10 +188,20 @@ void ImportDock::_update_options(const String &p_path, const Ref<ConfigFile> &p_ params->checked.clear(); params->base_options_path = p_path; + HashMap<StringName, Variant> import_options; + List<String> section_keys; + p_config->get_section_keys("params", §ion_keys); + for (const String §ion_key : section_keys) { + import_options[section_key] = p_config->get_value("params", section_key); + } + if (params->importer.is_valid()) { + params->importer->handle_compatibility_options(import_options); + } + for (const ResourceImporter::ImportOption &E : options) { params->properties.push_back(E.option); - if (p_config.is_valid() && p_config->has_section_key("params", E.option.name)) { - params->values[E.option.name] = p_config->get_value("params", E.option.name); + if (p_config.is_valid() && import_options.has(E.option.name)) { + params->values[E.option.name] = import_options[E.option.name]; } else { params->values[E.option.name] = E.default_value; } diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index 865729c7c3..2ecce2f739 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -446,6 +446,11 @@ void InputEventConfigurationDialog::_key_location_selected(int p_location) { _set_event(k, original_event); } +void InputEventConfigurationDialog::_input_list_item_activated() { + TreeItem *selected = input_list_tree->get_selected(); + selected->set_collapsed(!selected->is_collapsed()); +} + void InputEventConfigurationDialog::_input_list_item_selected() { TreeItem *selected = input_list_tree->get_selected(); @@ -670,6 +675,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() { input_list_tree = memnew(Tree); input_list_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); input_list_tree->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); // Min height for tree + input_list_tree->connect("item_activated", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_activated)); input_list_tree->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_selected)); input_list_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); manual_vbox->add_child(input_list_tree); diff --git a/editor/input_event_configuration_dialog.h b/editor/input_event_configuration_dialog.h index 1d2cc8ba36..b27f25a5b7 100644 --- a/editor/input_event_configuration_dialog.h +++ b/editor/input_event_configuration_dialog.h @@ -107,6 +107,7 @@ private: void _search_term_updated(const String &p_term); void _update_input_list(); + void _input_list_item_activated(); void _input_list_item_selected(); void _mod_toggled(bool p_checked, int p_index); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 1366a38bec..62369cc2c1 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -1711,7 +1711,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2_step_prepare(int p_step_offs OS::get_singleton()->get_main_loop()->process(0); // This is the key: process the frame and let all callbacks/updates/notifications happen // so everything (transforms, skeletons, etc.) is up-to-date visually. - callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_step_capture).bind(p_step_offset, p_capture_idx).call_deferred(); + callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_step_capture).call_deferred(p_step_offset, p_capture_idx); return; } else { next_capture_idx++; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index a44430ca7f..8b44d6b486 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -46,10 +46,12 @@ #include "editor/scene_tree_dock.h" #include "editor/themes/editor_scale.h" #include "editor/themes/editor_theme_manager.h" +#include "scene/2d/audio_stream_player_2d.h" #include "scene/2d/polygon_2d.h" #include "scene/2d/skeleton_2d.h" #include "scene/2d/sprite_2d.h" #include "scene/2d/touch_screen_button.h" +#include "scene/gui/base_button.h" #include "scene/gui/flow_container.h" #include "scene/gui/grid_container.h" #include "scene/gui/separator.h" @@ -966,7 +968,7 @@ void CanvasItemEditor::_add_node_pressed(int p_result) { } } -void CanvasItemEditor::_node_created(Node *p_node) { +void CanvasItemEditor::_adjust_new_node_position(Node *p_node) { if (node_create_position == Point2()) { return; } @@ -5161,7 +5163,7 @@ CanvasItemEditor::CanvasItemEditor() { editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed)); - SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created)); + SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_adjust_new_node_position)); SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position)); // Add some margin to the sides for better esthetics. @@ -5417,7 +5419,7 @@ CanvasItemEditor::CanvasItemEditor() { lock_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(LOCK_SELECTED)); lock_button->set_tooltip_text(TTR("Lock selected node, preventing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - lock_button->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L)); + lock_button->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes")); unlock_button = memnew(Button); unlock_button->set_theme_type_variation("FlatButton"); @@ -5425,7 +5427,7 @@ CanvasItemEditor::CanvasItemEditor() { unlock_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNLOCK_SELECTED)); unlock_button->set_tooltip_text(TTR("Unlock selected node, allowing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - unlock_button->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L)); + unlock_button->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes")); group_button = memnew(Button); group_button->set_theme_type_variation("FlatButton"); @@ -5433,7 +5435,7 @@ CanvasItemEditor::CanvasItemEditor() { group_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(GROUP_SELECTED)); group_button->set_tooltip_text(TTR("Groups the selected node with its children. This causes the parent to be selected when any child node is clicked in 2D and 3D view.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - group_button->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G)); + group_button->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes")); ungroup_button = memnew(Button); ungroup_button->set_theme_type_variation("FlatButton"); @@ -5441,7 +5443,7 @@ CanvasItemEditor::CanvasItemEditor() { ungroup_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNGROUP_SELECTED)); ungroup_button->set_tooltip_text(TTR("Ungroups the selected node from its children. Child nodes will be individual items in 2D and 3D view.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - ungroup_button->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G)); + ungroup_button->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes")); main_menu_hbox->add_child(memnew(VSeparator)); @@ -5636,7 +5638,7 @@ CanvasItemEditor::CanvasItemEditor() { clear(); // Make sure values are initialized. // Update the menus' checkboxes. - callable_mp(this, &CanvasItemEditor::set_state).bind(get_state()).call_deferred(); + callable_mp(this, &CanvasItemEditor::set_state).call_deferred(get_state()); } CanvasItemEditor *CanvasItemEditor::singleton = nullptr; @@ -5697,15 +5699,15 @@ CanvasItemEditorPlugin::~CanvasItemEditorPlugin() { } void CanvasItemEditorViewport::_on_mouse_exit() { - if (!selector->is_visible()) { + if (!texture_node_type_selector->is_visible()) { _remove_preview(); } } -void CanvasItemEditorViewport::_on_select_type(Object *selected) { +void CanvasItemEditorViewport::_on_select_texture_node_type(Object *selected) { CheckBox *check = Object::cast_to<CheckBox>(selected); String type = check->get_text(); - selector->set_title(vformat(TTR("Add %s"), type)); + texture_node_type_selector->set_title(vformat(TTR("Add %s"), type)); label->set_text(vformat(TTR("Adding %s..."), type)); } @@ -5717,7 +5719,7 @@ void CanvasItemEditorViewport::_on_change_type_confirmed() { CheckBox *check = Object::cast_to<CheckBox>(button_group->get_pressed_button()); default_texture_node_type = check->get_text(); _perform_drop_data(); - selector->hide(); + texture_node_type_selector->hide(); } void CanvasItemEditorViewport::_on_change_type_closed() { @@ -5728,35 +5730,35 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons bool add_preview = false; for (int i = 0; i < files.size(); i++) { Ref<Resource> res = ResourceLoader::load(files[i]); - ERR_FAIL_COND(res.is_null()); - Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res)); - Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - if (texture != nullptr || scene != nullptr) { - String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") + - "\n" + TTR("Hold Shift when dropping to add as child of selected node.") + - "\n" + TTR("Hold Alt when dropping to add as child of root node."); - - if (texture != nullptr) { - Sprite2D *sprite = memnew(Sprite2D); - sprite->set_texture(texture); - sprite->set_modulate(Color(1, 1, 1, 0.7f)); - preview_node->add_child(sprite); - label->show(); - label_desc->show(); - desc += "\n" + TTR("Hold Alt + Shift when dropping to add as a different node type."); - label_desc->set_text(desc); - } else { - if (scene.is_valid()) { - Node *instance = scene->instantiate(); - if (instance) { - preview_node->add_child(instance); - label_desc->show(); - label_desc->set_text(desc); - } - } + ERR_CONTINUE(res.is_null()); + + Ref<Texture2D> texture = res; + if (texture.is_valid()) { + Sprite2D *sprite = memnew(Sprite2D); + sprite->set_texture(texture); + sprite->set_modulate(Color(1, 1, 1, 0.7f)); + preview_node->add_child(sprite); + add_preview = true; + } + + Ref<PackedScene> scene = res; + if (scene.is_valid()) { + Node *instance = scene->instantiate(); + if (instance) { + preview_node->add_child(instance); } add_preview = true; } + + Ref<AudioStream> audio = res; + if (audio.is_valid()) { + Sprite2D *sprite = memnew(Sprite2D); + sprite->set_texture(get_editor_theme_icon(SNAME("AudioStreamPlayer2D"))); + sprite->set_modulate(Color(1, 1, 1, 0.7f)); + sprite->set_position(Vector2(0, -sprite->get_texture()->get_size().height) * EDSCALE); + preview_node->add_child(sprite); + add_preview = true; + } } if (add_preview) { @@ -5797,44 +5799,46 @@ bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_targe return false; } -void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point) { +void CanvasItemEditorViewport::_create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point) { // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. - String name = path.get_file().get_basename(); - child->set_name(Node::adjust_name_casing(name)); + const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename()); + if (!node_name.is_empty()) { + p_child->set_name(node_name); + } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - Ref<Texture2D> texture = ResourceCache::get_ref(path); + Ref<Texture2D> texture = ResourceCache::get_ref(p_path); - if (parent) { - undo_redo->add_do_method(parent, "add_child", child, true); - undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); - undo_redo->add_do_reference(child); - undo_redo->add_undo_method(parent, "remove_child", child); + if (p_parent) { + undo_redo->add_do_method(p_parent, "add_child", p_child, true); + undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(p_child); + undo_redo->add_undo_method(p_parent, "remove_child", p_child); } else { // If no parent is selected, set as root node of the scene. - undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child); - undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); - undo_redo->add_do_reference(child); + undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", p_child); + undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(p_child); undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr); } - if (parent) { - String new_name = parent->validate_child_name(child); + if (p_parent) { + String new_name = p_parent->validate_child_name(p_child); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), child->get_class(), new_name); - undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_child->get_class(), new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name)); } - if (Object::cast_to<TouchScreenButton>(child) || Object::cast_to<TextureButton>(child)) { - undo_redo->add_do_property(child, "texture_normal", texture); + if (Object::cast_to<TouchScreenButton>(p_child) || Object::cast_to<TextureButton>(p_child)) { + undo_redo->add_do_property(p_child, "texture_normal", texture); } else { - undo_redo->add_do_property(child, "texture", texture); + undo_redo->add_do_property(p_child, "texture", texture); } // make visible for certain node type - if (Object::cast_to<Control>(child)) { + if (Object::cast_to<Control>(p_child)) { Size2 texture_size = texture->get_size(); - undo_redo->add_do_property(child, "size", texture_size); - } else if (Object::cast_to<Polygon2D>(child)) { + undo_redo->add_do_property(p_child, "size", texture_size); + } else if (Object::cast_to<Polygon2D>(p_child)) { Size2 texture_size = texture->get_size(); Vector<Vector2> list = { Vector2(0, 0), @@ -5842,7 +5846,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & Vector2(texture_size.width, texture_size.height), Vector2(0, texture_size.height) }; - undo_redo->add_do_property(child, "polygon", list); + undo_redo->add_do_property(p_child, "polygon", list); } // Compute the global position @@ -5850,21 +5854,68 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & Point2 target_position = xform.affine_inverse().xform(p_point); // Adjust position for Control and TouchScreenButton - if (Object::cast_to<Control>(child) || Object::cast_to<TouchScreenButton>(child)) { + if (Object::cast_to<Control>(p_child) || Object::cast_to<TouchScreenButton>(p_child)) { target_position -= texture->get_size() / 2; } - // there's nothing to be used as source position so snapping will work as absolute if enabled + // There's nothing to be used as source position, so snapping will work as absolute if enabled. + target_position = canvas_item_editor->snap_point(target_position); + + CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent); + Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position; + + undo_redo->add_do_method(p_child, "set_position", local_target_pos); +} + +void CanvasItemEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) { + AudioStreamPlayer2D *child = memnew(AudioStreamPlayer2D); + child->set_stream(ResourceCache::get_ref(p_path)); + + // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. + const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename()); + if (!node_name.is_empty()) { + child->set_name(node_name); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + if (p_parent) { + undo_redo->add_do_method(p_parent, "add_child", child, true); + undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(child); + undo_redo->add_undo_method(p_parent, "remove_child", child); + } else { // If no parent is selected, set as root node of the scene. + undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child); + undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(child); + undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr); + } + + if (p_parent) { + String new_name = p_parent->validate_child_name(child); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), child->get_class(), new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name)); + } + + // Compute the global position + Transform2D xform = canvas_item_editor->get_canvas_transform(); + Point2 target_position = xform.affine_inverse().xform(p_point); + + // There's nothing to be used as source position, so snapping will work as absolute if enabled. target_position = canvas_item_editor->snap_point(target_position); - CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent); + CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent); Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position; undo_redo->add_do_method(child, "set_position", local_target_pos); + + EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); + undo_redo->add_do_method(editor_selection, "add_node", child); } -bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) { - Ref<PackedScene> sdata = ResourceLoader::load(path); +bool CanvasItemEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) { + Ref<PackedScene> sdata = ResourceLoader::load(p_path); if (!sdata.is_valid()) { // invalid scene return false; } @@ -5883,27 +5934,27 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons } } - instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path)); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); - undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); + undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true); undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene); undo_redo->add_do_reference(instantiated_scene); - undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); + undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene); undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene); - String new_name = parent->validate_child_name(instantiated_scene); + String new_name = p_parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), path, new_name); - undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)) + "/" + new_name)); + undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_path, new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)) + "/" + new_name)); CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene); if (instance_ci) { Vector2 target_pos = canvas_item_editor->get_canvas_transform().affine_inverse().xform(p_point); target_pos = canvas_item_editor->snap_point(target_pos); - CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent); + CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent); if (parent_ci) { target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos); } @@ -5922,12 +5973,8 @@ void CanvasItemEditorViewport::_perform_drop_data() { _remove_preview(); if (!target_node) { - // Without root dropping multiple files is not allowed - if (selected_files.size() > 1) { - accept->set_text(TTR("Cannot instantiate multiple nodes without root.")); - accept->popup_centered(); - return; - } + // Should already be handled by `can_drop_data`. + ERR_FAIL_COND_MSG(selected_files.size() > 1, "Can't instantiate multiple nodes without root."); const String &path = selected_files[0]; Ref<Resource> res = ResourceLoader::load(path); @@ -5973,9 +6020,14 @@ void CanvasItemEditorViewport::_perform_drop_data() { Ref<Texture2D> texture = res; if (texture.is_valid()) { Node *child = Object::cast_to<Node>(ClassDB::instantiate(default_texture_node_type)); - _create_nodes(target_node, child, path, drop_pos); + _create_texture_node(target_node, child, path, drop_pos); undo_redo->add_do_method(editor_selection, "add_node", child); } + + Ref<AudioStream> audio = res; + if (audio.is_valid()) { + _create_audio_node(target_node, path, drop_pos); + } } undo_redo->commit_action(); @@ -5994,71 +6046,108 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian } Vector<String> files = d["files"]; - bool can_instantiate = false; - bool is_cyclical_dep = false; - String error_file; - // Check if at least one of the dragged files is a texture or scene. - for (int i = 0; i < files.size(); i++) { - bool is_scene = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "PackedScene"); - bool is_texture = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Texture2D"); + const Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + if (!edited_scene && files.size() > 1) { + canvas_item_editor->message = TTR("Can't instantiate multiple nodes without root."); + canvas_item_editor->update_viewport(); + return false; + } + + enum { + SCENE = 1 << 0, + TEXTURE = 1 << 1, + AUDIO = 1 << 2, + }; + int instantiate_type = 0; - if (is_scene || is_texture) { - Ref<Resource> res = ResourceLoader::load(files[i]); - if (res.is_null()) { + for (const String &path : files) { + const String &res_type = ResourceLoader::get_resource_type(path); + String error_message; + + if (ClassDB::is_parent_class(res_type, "PackedScene")) { + Ref<PackedScene> scn = ResourceLoader::load(path); + ERR_CONTINUE(scn.is_null()); + + Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + if (!instantiated_scene) { continue; } - Ref<PackedScene> scn = res; - if (scn.is_valid()) { - Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); - if (!instantiated_scene) { - continue; - } - - Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); - if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) { - memdelete(instantiated_scene); - can_instantiate = false; - is_cyclical_dep = true; - error_file = files[i].get_file(); - break; - } - memdelete(instantiated_scene); + if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) { + error_message = vformat(TTR("Circular dependency found at %s."), path.get_file()); } - can_instantiate = true; + memdelete(instantiated_scene); + instantiate_type |= SCENE; + } + if (ClassDB::is_parent_class(res_type, "Texture2D")) { + instantiate_type |= TEXTURE; + } + if (ClassDB::is_parent_class(res_type, "AudioStream")) { + instantiate_type |= AUDIO; + } + + if (!error_message.is_empty()) { + // TRANSLATORS: The placeholder is the error message. + canvas_item_editor->message = vformat(TTR("Can't instantiate: %s"), error_message); + canvas_item_editor->update_viewport(); + return false; } } - if (is_cyclical_dep) { - canvas_item_editor->message = vformat(TTR("Can't instantiate: %s."), vformat(TTR("Circular dependency found at %s"), error_file)); - canvas_item_editor->update_viewport(); + if (instantiate_type == 0) { return false; } - if (can_instantiate) { - if (!preview_node->get_parent()) { // create preview only once - _create_preview(files); - } - ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false); - Transform2D trans = canvas_item_editor->get_canvas_transform(); - preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x); + if (!preview_node->get_parent()) { // create preview only once + _create_preview(files); + } + ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false); + + const Transform2D trans = canvas_item_editor->get_canvas_transform(); + preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x); + + if (!edited_scene && instantiate_type & SCENE) { String scene_file_path = preview_node->get_child(0)->get_scene_file_path(); - if (scene_file_path.is_empty() || preview_node->get_tree()->get_edited_scene_root()) { - double snap = EDITOR_GET("interface/inspector/default_float_step"); - int snap_step_decimals = Math::range_step_decimals(snap); + // TRANSLATORS: The placeholder is the file path of the scene being instantiated. + canvas_item_editor->message = vformat(TTR("Creating inherited scene from: %s"), scene_file_path); + } else { + double snap = EDITOR_GET("interface/inspector/default_float_step"); + int snap_step_decimals = Math::range_step_decimals(snap); #define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals))) - Vector2 preview_node_pos = preview_node->get_global_position(); - canvas_item_editor->message = TTR("Instantiating:") + " (" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px"; - label->set_text(vformat(TTR("Adding %s..."), default_texture_node_type)); - } else { - canvas_item_editor->message = TTR("Creating inherited scene from: ") + scene_file_path; + Vector2 preview_node_pos = preview_node->get_global_position(); + canvas_item_editor->message = TTR("Instantiating: ") + "(" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px"; + } + canvas_item_editor->update_viewport(); + + if (instantiate_type & TEXTURE && instantiate_type & AUDIO) { + // TRANSLATORS: The placeholders are the types of nodes being instantiated. + label->set_text(vformat(TTR("Adding %s and %s..."), default_texture_node_type, "AudioStreamPlayer2D")); + } else { + String node_type; + if (instantiate_type & TEXTURE) { + node_type = default_texture_node_type; + } else if (instantiate_type & AUDIO) { + node_type = "AudioStreamPlayer2D"; + } + if (!node_type.is_empty()) { + // TRANSLATORS: The placeholder is the type of node being instantiated. + label->set_text(vformat(TTR("Adding %s..."), node_type)); } + } + label->set_visible(instantiate_type & ~SCENE); - canvas_item_editor->update_viewport(); + String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") + + "\n" + TTR("Hold Shift when dropping to add as child of selected node.") + + "\n" + TTR("Hold Alt when dropping to add as child of root node."); + if (instantiate_type & TEXTURE) { + desc += "\n" + TTR("Hold Alt + Shift when dropping to add as different node type."); } - return can_instantiate; + label_desc->set_text(desc); + label_desc->show(); + + return true; } -void CanvasItemEditorViewport::_show_resource_type_selector() { +void CanvasItemEditorViewport::_show_texture_node_type_selector() { _remove_preview(); List<BaseButton *> btn_list; button_group->get_buttons(&btn_list); @@ -6067,18 +6156,17 @@ void CanvasItemEditorViewport::_show_resource_type_selector() { CheckBox *check = Object::cast_to<CheckBox>(btn); check->set_pressed(check->get_text() == default_texture_node_type); } - selector->set_title(vformat(TTR("Add %s"), default_texture_node_type)); - selector->popup_centered(); + texture_node_type_selector->set_title(vformat(TTR("Add %s"), default_texture_node_type)); + texture_node_type_selector->popup_centered(); } -bool CanvasItemEditorViewport::_only_packed_scenes_selected() const { +bool CanvasItemEditorViewport::_is_any_texture_selected() const { for (int i = 0; i < selected_files.size(); ++i) { - if (ResourceLoader::load(selected_files[i])->get_class() != "PackedScene") { - return false; + if (ClassDB::is_parent_class(ResourceLoader::get_resource_type(selected_files[i]), "Texture2D")) { + return true; } } - - return true; + return false; } void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) { @@ -6115,8 +6203,8 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p drop_pos = p_point; - if (is_alt && is_shift && !_only_packed_scenes_selected()) { - _show_resource_type_selector(); + if (is_alt && is_shift && _is_any_texture_selected()) { + _show_texture_node_type_selector(); } else { _perform_drop_data(); } @@ -6176,19 +6264,19 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it accept = memnew(AcceptDialog); EditorNode::get_singleton()->get_gui_base()->add_child(accept); - selector = memnew(AcceptDialog); - EditorNode::get_singleton()->get_gui_base()->add_child(selector); - selector->set_title(TTR("Change Default Type")); - selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed)); - selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed)); + texture_node_type_selector = memnew(AcceptDialog); + EditorNode::get_singleton()->get_gui_base()->add_child(texture_node_type_selector); + texture_node_type_selector->set_title(TTR("Change Default Type")); + texture_node_type_selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed)); + texture_node_type_selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed)); VBoxContainer *vbc = memnew(VBoxContainer); - selector->add_child(vbc); + texture_node_type_selector->add_child(vbc); vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); vbc->set_v_size_flags(Control::SIZE_EXPAND_FILL); vbc->set_custom_minimum_size(Size2(240, 260) * EDSCALE); - btn_group = memnew(VBoxContainer); + VBoxContainer *btn_group = memnew(VBoxContainer); vbc->add_child(btn_group); btn_group->set_h_size_flags(SIZE_EXPAND_FILL); @@ -6197,7 +6285,7 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it CheckBox *check = memnew(CheckBox); btn_group->add_child(check); check->set_text(texture_node_types[i]); - check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_type).bind(check)); + check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_texture_node_type).bind(check)); check->set_button_group(button_group); } diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 6d951a77ec..a9de5e9a0b 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -32,10 +32,11 @@ #define CANVAS_ITEM_EDITOR_PLUGIN_H #include "editor/plugins/editor_plugin.h" -#include "scene/gui/base_button.h" #include "scene/gui/box_container.h" class AcceptDialog; +class Button; +class ButtonGroup; class CanvasItemEditorViewport; class ConfirmationDialog; class EditorData; @@ -409,7 +410,7 @@ private: void _selection_result_pressed(int); void _selection_menu_hide(); void _add_node_pressed(int p_result); - void _node_created(Node *p_node); + void _adjust_new_node_position(Node *p_node); void _reset_create_position(); void _update_editor_settings(); bool _is_grid_visible() const; @@ -634,15 +635,13 @@ class CanvasItemEditorViewport : public Control { CanvasItemEditor *canvas_item_editor = nullptr; Control *preview_node = nullptr; AcceptDialog *accept = nullptr; - AcceptDialog *selector = nullptr; - Label *selector_label = nullptr; + AcceptDialog *texture_node_type_selector = nullptr; Label *label = nullptr; Label *label_desc = nullptr; - VBoxContainer *btn_group = nullptr; Ref<ButtonGroup> button_group; void _on_mouse_exit(); - void _on_select_type(Object *selected); + void _on_select_texture_node_type(Object *selected); void _on_change_type_confirmed(); void _on_change_type_closed(); @@ -650,11 +649,12 @@ class CanvasItemEditorViewport : public Control { void _remove_preview(); bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const; - bool _only_packed_scenes_selected() const; - void _create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point); - bool _create_instance(Node *parent, String &path, const Point2 &p_point); + bool _is_any_texture_selected() const; + void _create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point); + void _create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point); + bool _create_instance(Node *p_parent, const String &p_path, const Point2 &p_point); void _perform_drop_data(); - void _show_resource_type_selector(); + void _show_texture_node_type_selector(); void _update_theme(); protected: diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp index dfc8323fc0..1d53a1b4d4 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "canvas_item_editor_plugin.h" #include "core/io/image_loader.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" #include "editor/gui/editor_file_dialog.h" #include "editor/scene_tree_dock.h" @@ -268,7 +269,7 @@ CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() { toolbar->hide(); menu = memnew(MenuButton); - menu->get_popup()->add_item(TTR("Restart"), MENU_RESTART); + menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_RESTART); menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK); menu->get_popup()->add_item(TTR("Convert to GPUParticles2D"), MENU_CONVERT_TO_GPU_PARTICLES); menu->set_text(TTR("CPUParticles2D")); diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp index b5e3f102cf..baf70e45f0 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_3d_editor_plugin.cpp @@ -31,6 +31,7 @@ #include "cpu_particles_3d_editor_plugin.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" #include "editor/gui/scene_tree_editor.h" #include "editor/plugins/node_3d_editor_plugin.h" @@ -168,7 +169,7 @@ CPUParticles3DEditor::CPUParticles3DEditor() { particles_editor_hb->hide(); options->set_text(TTR("CPUParticles3D")); - options->get_popup()->add_item(TTR("Restart"), MENU_OPTION_RESTART); + options->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_OPTION_RESTART); options->get_popup()->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB); options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE); options->get_popup()->add_item(TTR("Convert to GPUParticles3D"), MENU_OPTION_CONVERT_TO_GPU_PARTICLES); diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp index e9f1b07c34..328b272562 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "canvas_item_editor_plugin.h" #include "core/io/image_loader.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" #include "editor/gui/editor_file_dialog.h" #include "editor/scene_tree_dock.h" @@ -370,7 +371,7 @@ GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() { toolbar->hide(); menu = memnew(MenuButton); - menu->get_popup()->add_item(TTR("Restart"), MENU_RESTART); + menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_RESTART); menu->get_popup()->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT); menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK); // menu->get_popup()->add_item(TTR("Clear Emission Mask"), MENU_CLEAR_EMISSION_MASK); diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index 04b0a8aa26..9063109ece 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -32,6 +32,7 @@ #include "core/io/resource_loader.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/scene_tree_dock.h" @@ -414,7 +415,7 @@ GPUParticles3DEditor::GPUParticles3DEditor() { particles_editor_hb->hide(); options->set_text(TTR("GPUParticles3D")); - options->get_popup()->add_item(TTR("Restart"), MENU_OPTION_RESTART); + options->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_OPTION_RESTART); options->get_popup()->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB); options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE); options->get_popup()->add_item(TTR("Convert to CPUParticles3D"), MENU_OPTION_CONVERT_TO_CPU_PARTICLES); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index ca7ea821e8..69b66cd7b2 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -78,12 +78,14 @@ #include "editor/plugins/gizmos/voxel_gi_gizmo_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" #include "editor/scene_tree_dock.h" +#include "scene/3d/audio_stream_player_3d.h" #include "scene/3d/camera_3d.h" #include "scene/3d/decal.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics/collision_shape_3d.h" #include "scene/3d/physics/physics_body_3d.h" +#include "scene/3d/sprite_3d.h" #include "scene/3d/visual_instance_3d.h" #include "scene/3d/world_environment.h" #include "scene/gui/center_container.h" @@ -737,9 +739,21 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) { return; } + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + + // Prevent selection of nodes not owned by the edited scene. + while (node && node != edited_scene->get_parent()) { + Node *node_owner = node->get_owner(); + if (node_owner == edited_scene || node == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) { + break; + } + node = node->get_parent(); + selected = Object::cast_to<Node3D>(node); + } + if (!p_allow_locked) { // Replace the node by the group if grouped - while (node && node != EditorNode::get_singleton()->get_edited_scene()->get_parent()) { + while (node && node != edited_scene->get_parent()) { Node3D *selected_tmp = Object::cast_to<Node3D>(node); if (selected_tmp && node->has_meta("_edit_group_")) { selected = selected_tmp; @@ -1042,25 +1056,34 @@ void Node3DEditorViewport::_select_region() { found_nodes.insert(sp); - Node *item = Object::cast_to<Node>(sp); - if (item != edited_scene) { - item = edited_scene->get_deepest_editable_node(item); + Node *node = Object::cast_to<Node>(sp); + if (node != edited_scene) { + node = edited_scene->get_deepest_editable_node(node); + } + + // Prevent selection of nodes not owned by the edited scene. + while (node && node != edited_scene->get_parent()) { + Node *node_owner = node->get_owner(); + if (node_owner == edited_scene || node == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) { + break; + } + node = node->get_parent(); } // Replace the node by the group if grouped - if (item->is_class("Node3D")) { - Node3D *sel = Object::cast_to<Node3D>(item); - while (item && item != EditorNode::get_singleton()->get_edited_scene()->get_parent()) { - Node3D *selected_tmp = Object::cast_to<Node3D>(item); - if (selected_tmp && item->has_meta("_edit_group_")) { + if (node->is_class("Node3D")) { + Node3D *sel = Object::cast_to<Node3D>(node); + while (node && node != EditorNode::get_singleton()->get_edited_scene()->get_parent()) { + Node3D *selected_tmp = Object::cast_to<Node3D>(node); + if (selected_tmp && node->has_meta("_edit_group_")) { sel = selected_tmp; } - item = item->get_parent(); + node = node->get_parent(); } - item = sel; + node = sel; } - if (_is_node_locked(item)) { + if (_is_node_locked(node)) { continue; } @@ -1072,7 +1095,7 @@ void Node3DEditorViewport::_select_region() { } if (seg->intersect_frustum(camera, frustum)) { - selected.push_back(item); + selected.push_back(node); } } } @@ -1529,23 +1552,35 @@ bool Node3DEditorViewport ::_is_node_locked(const Node *p_node) { } void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { - _find_items_at_pos(b->get_position(), selection_results, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT); + Vector<_RayResult> potential_selection_results; + _find_items_at_pos(b->get_position(), potential_selection_results, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT); - Node *scene = EditorNode::get_singleton()->get_edited_scene(); + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); - for (int i = 0; i < selection_results.size(); i++) { - Node3D *item = selection_results[i].item; - if (item != scene && item->get_owner() != scene && item != scene->get_deepest_editable_node(item)) { - //invalid result - selection_results.remove_at(i); - i--; + // Filter to a list of nodes which include either the edited scene or nodes directly owned by the edited scene. + // If a node has an invalid owner, recursively check their parents until a valid node is found. + for (int i = 0; i < potential_selection_results.size(); i++) { + Node3D *node = potential_selection_results[i].item; + while (true) { + if (node == nullptr || node == edited_scene->get_parent()) { + break; + } else { + Node *node_owner = node->get_owner(); + if (node == edited_scene || node_owner == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) { + if (selection_results.has(node)) { + selection_results.append(node); + } + break; + } + } + node = Object::cast_to<Node3D>(node->get_parent()); } } clicked_wants_append = b->is_shift_pressed(); if (selection_results.size() == 1) { - clicked = selection_results[0].item->get_instance_id(); + clicked = selection_results[0]->get_instance_id(); selection_results.clear(); if (clicked.is_valid()) { @@ -1556,7 +1591,7 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { StringName root_name = root_path.get_name(root_path.get_name_count() - 1); for (int i = 0; i < selection_results.size(); i++) { - Node3D *spat = selection_results[i].item; + Node3D *spat = selection_results[i]; Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(spat, "Node"); @@ -2899,10 +2934,8 @@ void Node3DEditorViewport::_notification(int p_what) { // FPS Counter. bool show_fps = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FRAME_TIME)); - if (show_fps != fps_label->is_visible()) { - cpu_time_label->set_visible(show_fps); - gpu_time_label->set_visible(show_fps); - fps_label->set_visible(show_fps); + if (show_fps != frame_time_panel->is_visible()) { + frame_time_panel->set_visible(show_fps); RS::get_singleton()->viewport_set_measure_render_time(viewport->get_viewport_rid(), show_fps); for (int i = 0; i < FRAME_TIME_HISTORY; i++) { // Initialize to 120 FPS, so that the initial estimation until we get enough data is always reasonable. @@ -3031,9 +3064,15 @@ void Node3DEditorViewport::_notification(int p_what) { frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color"), EditorStringName(Editor))); info_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles))); - cpu_time_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles))); - gpu_time_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles))); - fps_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles))); + + frame_time_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles))); + // Set a minimum width to prevent the width from changing all the time + // when numbers vary rapidly. This minimum width is set based on a + // GPU time of 999.99 ms in the current editor language. + const float min_width = get_theme_font(SNAME("main"), EditorStringName(EditorFonts))->get_string_size(vformat(TTR("GPU Time: %s ms"), 999.99)).x; + frame_time_panel->set_custom_minimum_size(Size2(min_width, 0) * EDSCALE); + frame_time_vbox->add_theme_constant_override("separation", Math::round(-1 * EDSCALE)); + cinema_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles))); locked_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles))); } break; @@ -3748,7 +3787,7 @@ void Node3DEditorViewport::_selection_result_pressed(int p_result) { return; } - clicked = selection_results_menu[p_result].item->get_instance_id(); + clicked = selection_results_menu[p_result]->get_instance_id(); if (clicked.is_valid()) { _select_clicked(spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT); @@ -4192,27 +4231,37 @@ Node *Node3DEditorViewport::_sanitize_preview_node(Node *p_node) const { void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) const { bool add_preview = false; - for (int i = 0; i < files.size(); i++) { - Ref<Resource> res = ResourceLoader::load(files[i]); + for (const String &path : files) { + Ref<Resource> res = ResourceLoader::load(path); ERR_CONTINUE(res.is_null()); - Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res)); - if (mesh != nullptr || scene != nullptr) { - if (mesh != nullptr) { - MeshInstance3D *mesh_instance = memnew(MeshInstance3D); - mesh_instance->set_mesh(mesh); - preview_node->add_child(mesh_instance); - } else { - if (scene.is_valid()) { - Node *instance = scene->instantiate(); - if (instance) { - instance = _sanitize_preview_node(instance); - preview_node->add_child(instance); - } - } + + Ref<PackedScene> scene = res; + if (scene.is_valid()) { + Node *instance = scene->instantiate(); + if (instance) { + instance = _sanitize_preview_node(instance); + preview_node->add_child(instance); } add_preview = true; } + + Ref<Mesh> mesh = res; + if (mesh.is_valid()) { + MeshInstance3D *mesh_instance = memnew(MeshInstance3D); + mesh_instance->set_mesh(mesh); + preview_node->add_child(mesh_instance); + add_preview = true; + } + + Ref<AudioStream> audio = res; + if (audio.is_valid()) { + Sprite3D *sprite = memnew(Sprite3D); + sprite->set_texture(get_editor_theme_icon(SNAME("Gizmo3DSamplePlayer"))); + sprite->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); + sprite->set_pixel_size(0.005); + preview_node->add_child(sprite); + add_preview = true; + } } if (add_preview) { EditorNode::get_singleton()->get_scene_root()->add_child(preview_node); @@ -4346,12 +4395,12 @@ bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_sc return false; } -bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) { - Ref<Resource> res = ResourceLoader::load(path); +bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) { + Ref<Resource> res = ResourceLoader::load(p_path); ERR_FAIL_COND_V(res.is_null(), false); - Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res)); + Ref<PackedScene> scene = res; + Ref<Mesh> mesh = res; Node *instantiated_scene = nullptr; @@ -4361,8 +4410,10 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po mesh_instance->set_mesh(mesh); // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. - String name = path.get_file().get_basename(); - mesh_instance->set_name(Node::adjust_name_casing(name)); + const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename()); + if (!node_name.is_empty()) { + mesh_instance->set_name(node_name); + } instantiated_scene = mesh_instance; } else { @@ -4386,25 +4437,25 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po } if (scene != nullptr) { - instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path)); } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); + undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true); undo_redo->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->get_edited_scene()); undo_redo->add_do_reference(instantiated_scene); - undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); + undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene); undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene); - String new_name = parent->validate_child_name(instantiated_scene); + String new_name = p_parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), path, new_name); - undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_path, new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name)); Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene); if (node3d) { Transform3D parent_tf; - Node3D *parent_node3d = Object::cast_to<Node3D>(parent); + Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent); if (parent_node3d) { parent_tf = parent_node3d->get_global_gizmo_transform(); } @@ -4419,6 +4470,46 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po return true; } +bool Node3DEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) { + Ref<AudioStream> audio = ResourceLoader::load(p_path); + ERR_FAIL_COND_V(audio.is_null(), false); + + AudioStreamPlayer3D *audio_player = memnew(AudioStreamPlayer3D); + audio_player->set_stream(audio); + + // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. + const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename()); + if (!node_name.is_empty()) { + audio_player->set_name(node_name); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->add_do_method(p_parent, "add_child", audio_player, true); + undo_redo->add_do_method(audio_player, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(audio_player); + undo_redo->add_undo_method(p_parent, "remove_child", audio_player); + undo_redo->add_do_method(editor_selection, "add_node", audio_player); + + const String new_name = p_parent->validate_child_name(audio_player); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), audio_player->get_class(), new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name)); + + Transform3D parent_tf; + Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent); + if (parent_node3d) { + parent_tf = parent_node3d->get_global_gizmo_transform(); + } + + Transform3D new_tf = audio_player->get_transform(); + new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + audio_player->get_position()); + new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis; + + undo_redo->add_do_method(audio_player, "set_transform", new_tf); + + return true; +} + void Node3DEditorViewport::_perform_drop_data() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (spatial_editor->get_preview_material_target().is_valid()) { @@ -4453,11 +4544,18 @@ void Node3DEditorViewport::_perform_drop_data() { if (res.is_null()) { continue; } - Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res)); - if (mesh != nullptr || scene != nullptr) { - bool success = _create_instance(target_node, path, drop_pos); - if (!success) { + + Ref<PackedScene> scene = res; + Ref<Mesh> mesh = res; + if (mesh.is_valid() || scene.is_valid()) { + if (!_create_instance(target_node, path, drop_pos)) { + error_files.push_back(path.get_file()); + } + } + + Ref<AudioStream> audio = res; + if (audio.is_valid()) { + if (!_create_audio_node(target_node, path, drop_pos)) { error_files.push_back(path.get_file()); } } @@ -4488,12 +4586,14 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant bool is_other_valid = false; // Check if at least one of the dragged files is a mesh, material, texture or scene. for (int i = 0; i < files.size(); i++) { - bool is_scene = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "PackedScene"); - bool is_mesh = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Mesh"); - bool is_material = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Material"); - bool is_texture = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Texture"); - - if (is_mesh || is_scene || is_material || is_texture) { + const String &res_type = ResourceLoader::get_resource_type(files[i]); + bool is_scene = ClassDB::is_parent_class(res_type, "PackedScene"); + bool is_mesh = ClassDB::is_parent_class(res_type, "Mesh"); + bool is_material = ClassDB::is_parent_class(res_type, "Material"); + bool is_texture = ClassDB::is_parent_class(res_type, "Texture"); + bool is_audio = ClassDB::is_parent_class(res_type, "AudioStream"); + + if (is_mesh || is_scene || is_material || is_texture || is_audio) { Ref<Resource> res = ResourceLoader::load(files[i]); if (res.is_null()) { continue; @@ -4502,6 +4602,7 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant Ref<Mesh> mesh = res; Ref<Material> mat = res; Ref<Texture2D> tex = res; + Ref<AudioStream> audio = res; if (scn.is_valid()) { Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instantiated_scene) { @@ -4537,6 +4638,8 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant spatial_editor->set_preview_material(new_mat); is_other_valid = true; continue; + } else if (!is_other_valid && audio.is_valid()) { + is_other_valid = true; } else { continue; } @@ -5313,10 +5416,6 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p top_right_vbox = memnew(VBoxContainer); top_right_vbox->set_anchors_and_offsets_preset(PRESET_TOP_RIGHT, PRESET_MODE_MINSIZE, 10.0 * EDSCALE); top_right_vbox->set_h_grow_direction(GROW_DIRECTION_BEGIN); - // Make sure frame time labels don't touch the viewport's edge. - top_right_vbox->set_custom_minimum_size(Size2(100, 0) * EDSCALE); - // Prevent visible spacing between frame time labels. - top_right_vbox->add_theme_constant_override("separation", 0); const int navigation_control_size = 150; @@ -5348,18 +5447,22 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p rotation_control->set_viewport(this); top_right_vbox->add_child(rotation_control); + frame_time_panel = memnew(PanelContainer); + top_right_vbox->add_child(frame_time_panel); + frame_time_panel->hide(); + + frame_time_vbox = memnew(VBoxContainer); + frame_time_panel->add_child(frame_time_vbox); + // Individual Labels are used to allow coloring each label with its own color. cpu_time_label = memnew(Label); - top_right_vbox->add_child(cpu_time_label); - cpu_time_label->hide(); + frame_time_vbox->add_child(cpu_time_label); gpu_time_label = memnew(Label); - top_right_vbox->add_child(gpu_time_label); - gpu_time_label->hide(); + frame_time_vbox->add_child(gpu_time_label); fps_label = memnew(Label); - top_right_vbox->add_child(fps_label); - fps_label->hide(); + frame_time_vbox->add_child(fps_label); surface->add_child(top_right_vbox); @@ -8410,7 +8513,7 @@ Node3DEditor::Node3DEditor() { tool_button[TOOL_LOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_LOCK_SELECTED)); tool_button[TOOL_LOCK_SELECTED]->set_tooltip_text(TTR("Lock selected node, preventing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L)); + tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes")); tool_button[TOOL_UNLOCK_SELECTED] = memnew(Button); main_menu_hbox->add_child(tool_button[TOOL_UNLOCK_SELECTED]); @@ -8418,7 +8521,7 @@ Node3DEditor::Node3DEditor() { tool_button[TOOL_UNLOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNLOCK_SELECTED)); tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip_text(TTR("Unlock selected node, allowing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L)); + tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes")); tool_button[TOOL_GROUP_SELECTED] = memnew(Button); main_menu_hbox->add_child(tool_button[TOOL_GROUP_SELECTED]); @@ -8426,7 +8529,7 @@ Node3DEditor::Node3DEditor() { tool_button[TOOL_GROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_GROUP_SELECTED)); tool_button[TOOL_GROUP_SELECTED]->set_tooltip_text(TTR("Groups the selected node with its children. This selects the parent when any child node is clicked in 2D and 3D view.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G)); + tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes")); tool_button[TOOL_UNGROUP_SELECTED] = memnew(Button); main_menu_hbox->add_child(tool_button[TOOL_UNGROUP_SELECTED]); @@ -8434,7 +8537,7 @@ Node3DEditor::Node3DEditor() { tool_button[TOOL_UNGROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNGROUP_SELECTED)); tool_button[TOOL_UNGROUP_SELECTED]->set_tooltip_text(TTR("Ungroups the selected node from its children. Child nodes will be individual items in 2D and 3D view.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G)); + tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes")); main_menu_hbox->add_child(memnew(VSeparator)); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index a3e1224cb8..859d075732 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -255,6 +255,8 @@ private: ViewportNavigationControl *look_control = nullptr; ViewportRotationControl *rotation_control = nullptr; Gradient *frame_time_gradient = nullptr; + PanelContainer *frame_time_panel = nullptr; + VBoxContainer *frame_time_vbox = nullptr; Label *cpu_time_label = nullptr; Label *gpu_time_label = nullptr; Label *fps_label = nullptr; @@ -296,8 +298,8 @@ private: ObjectID clicked; ObjectID material_target; - Vector<_RayResult> selection_results; - Vector<_RayResult> selection_results_menu; + Vector<Node3D *> selection_results; + Vector<Node3D *> selection_results_menu; bool clicked_wants_append = false; bool selection_in_progress = false; @@ -443,7 +445,8 @@ private: void _reset_preview_material() const; void _remove_preview_material(); bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const; - bool _create_instance(Node *parent, String &path, const Point2 &p_point); + bool _create_instance(Node *p_parent, const String &p_path, const Point2 &p_point); + bool _create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point); void _perform_drop_data(); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index eb6282ca0c..0a0909ec9f 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -765,7 +765,7 @@ void ScriptEditor::_update_recent_scripts() { } recent_scripts->add_separator(); - recent_scripts->add_shortcut(ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Files"))); + recent_scripts->add_shortcut(ED_GET_SHORTCUT("script_editor/clear_recent")); recent_scripts->set_item_disabled(recent_scripts->get_item_id(recent_scripts->get_item_count() - 1), rc.is_empty()); recent_scripts->reset_size(); @@ -3591,13 +3591,13 @@ void ScriptEditor::_update_selected_editor_menu() { script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_next", TTR("Find Next"), Key::F3), HELP_SEARCH_FIND_NEXT); script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_previous", TTR("Find Previous"), KeyModifierMask::SHIFT | Key::F3), HELP_SEARCH_FIND_PREVIOUS); script_search_menu->get_popup()->add_separator(); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/replace_in_files", TTR("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R), REPLACE_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/find_in_files"), SEARCH_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/replace_in_files"), REPLACE_IN_FILES); script_search_menu->show(); } else { if (tab_container->get_tab_count() == 0) { - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/replace_in_files", TTR("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R), REPLACE_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/find_in_files"), SEARCH_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/replace_in_files"), REPLACE_IN_FILES); script_search_menu->show(); } else { script_search_menu->hide(); @@ -4101,7 +4101,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new", TTR("New Script..."), KeyModifierMask::CMD_OR_CTRL | Key::N), FILE_NEW); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new_textfile", TTR("New Text File..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::N), FILE_NEW_TEXTFILE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/open", TTR("Open...")), FILE_OPEN); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T), FILE_REOPEN_CLOSED); + file_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/reopen_closed_script"), FILE_REOPEN_CLOSED); recent_scripts = memnew(PopupMenu); file_menu->get_popup()->add_submenu_node_item(TTR("Open Recent"), recent_scripts, FILE_OPEN_RECENT); @@ -4122,6 +4122,9 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_previous", TTR("History Previous"), KeyModifierMask::ALT | Key::LEFT), WINDOW_PREV); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_next", TTR("History Next"), KeyModifierMask::ALT | Key::RIGHT), WINDOW_NEXT); + ED_SHORTCUT_OVERRIDE("script_editor/history_previous", "macos", KeyModifierMask::ALT | KeyModifierMask::META | Key::LEFT); + ED_SHORTCUT_OVERRIDE("script_editor/history_next", "macos", KeyModifierMask::ALT | KeyModifierMask::META | Key::RIGHT); + file_menu->get_popup()->add_separator(); theme_submenu = memnew(PopupMenu); @@ -4246,12 +4249,18 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { disk_changed = memnew(ConfirmationDialog); { + disk_changed->set_title(TTR("Files have been modified on disk")); + VBoxContainer *vbc = memnew(VBoxContainer); disk_changed->add_child(vbc); - Label *dl = memnew(Label); - dl->set_text(TTR("The following files are newer on disk.\nWhat action should be taken?:")); - vbc->add_child(dl); + Label *files_are_newer_label = memnew(Label); + files_are_newer_label->set_text(TTR("The following files are newer on disk.")); + vbc->add_child(files_are_newer_label); + + Label *what_action_label = memnew(Label); + what_action_label->set_text(TTR("What action should be taken?:")); + vbc->add_child(what_action_label); disk_changed_list = memnew(Tree); vbc->add_child(disk_changed_list); @@ -4259,9 +4268,9 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { disk_changed_list->set_v_size_flags(SIZE_EXPAND_FILL); disk_changed->connect("confirmed", callable_mp(this, &ScriptEditor::reload_scripts).bind(false)); - disk_changed->set_ok_button_text(TTR("Reload")); + disk_changed->set_ok_button_text(TTR("Discard local changes and reload")); - disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); + disk_changed->add_button(TTR("Keep local changes and overwrite"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); disk_changed->connect("custom_action", callable_mp(this, &ScriptEditor::_resave_scripts)); } @@ -4499,6 +4508,15 @@ void ScriptEditorPlugin::edited_scene_changed() { } ScriptEditorPlugin::ScriptEditorPlugin() { + ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T); + ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Scripts")); + ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F); + ED_SHORTCUT("script_editor/replace_in_files", TTR("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R); + + ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4); + ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5); + ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6); + window_wrapper = memnew(WindowWrapper); window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Script Editor"))); window_wrapper->set_margins_enabled(true); @@ -4527,9 +4545,6 @@ ScriptEditorPlugin::ScriptEditorPlugin() { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "text_editor/external/exec_path", PROPERTY_HINT_GLOBAL_FILE)); EDITOR_DEF("text_editor/external/exec_flags", "{file}"); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "text_editor/external/exec_flags", PROPERTY_HINT_PLACEHOLDER_TEXT, "Call flags with placeholders: {project}, {file}, {col}, {line}.")); - - ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T); - ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Scripts")); } ScriptEditorPlugin::~ScriptEditorPlugin() { diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 191d58228b..a5d89ff54c 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -2133,10 +2133,11 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { void ScriptTextEditor::_color_changed(const Color &p_color) { String new_args; + const int decimals = 3; if (p_color.a == 1.0f) { - new_args = String("(" + rtos(p_color.r) + ", " + rtos(p_color.g) + ", " + rtos(p_color.b) + ")"); + new_args = String("(" + String::num(p_color.r, decimals) + ", " + String::num(p_color.g, decimals) + ", " + String::num(p_color.b, decimals) + ")"); } else { - new_args = String("(" + rtos(p_color.r) + ", " + rtos(p_color.g) + ", " + rtos(p_color.b) + ", " + rtos(p_color.a) + ")"); + new_args = String("(" + String::num(p_color.r, decimals) + ", " + String::num(p_color.g, decimals) + ", " + String::num(p_color.b, decimals) + ", " + String::num(p_color.a, decimals) + ")"); } String line = code_editor->get_text_editor()->get_line(color_position.x); @@ -2311,9 +2312,9 @@ void ScriptTextEditor::_enable_code_editor() { edit_menu->get_popup()->add_separator(); { PopupMenu *sub_menu = memnew(PopupMenu); - sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4), EDIT_TO_UPPERCASE); - sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5), EDIT_TO_LOWERCASE); - sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6), EDIT_CAPITALIZE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/capitalize"), EDIT_CAPITALIZE); sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_submenu_node_item(TTR("Convert Case"), sub_menu); } diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 56f8e1173f..c14336418c 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -2181,6 +2181,7 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_h->set_min(1); split_sheet_h->set_max(128); split_sheet_h->set_step(1); + split_sheet_h->set_select_all_on_focus(true); split_sheet_h_hb->add_child(split_sheet_h); split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT)); split_sheet_settings_vb->add_child(split_sheet_h_hb); @@ -2197,6 +2198,7 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_v->set_min(1); split_sheet_v->set_max(128); split_sheet_v->set_step(1); + split_sheet_v->set_select_all_on_focus(true); split_sheet_v_hb->add_child(split_sheet_v); split_sheet_v->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT)); split_sheet_settings_vb->add_child(split_sheet_v_hb); @@ -2216,6 +2218,7 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_size_x->set_min(1); split_sheet_size_x->set_step(1); split_sheet_size_x->set_suffix("px"); + split_sheet_size_x->set_select_all_on_focus(true); split_sheet_size_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE)); split_sheet_size_vb->add_child(split_sheet_size_x); split_sheet_size_y = memnew(SpinBox); @@ -2223,6 +2226,7 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_size_y->set_min(1); split_sheet_size_y->set_step(1); split_sheet_size_y->set_suffix("px"); + split_sheet_size_y->set_select_all_on_focus(true); split_sheet_size_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE)); split_sheet_size_vb->add_child(split_sheet_size_y); split_sheet_size_hb->add_child(split_sheet_size_vb); @@ -2242,12 +2246,14 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_sep_x->set_min(0); split_sheet_sep_x->set_step(1); split_sheet_sep_x->set_suffix("px"); + split_sheet_sep_x->set_select_all_on_focus(true); split_sheet_sep_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); split_sheet_sep_vb->add_child(split_sheet_sep_x); split_sheet_sep_y = memnew(SpinBox); split_sheet_sep_y->set_min(0); split_sheet_sep_y->set_step(1); split_sheet_sep_y->set_suffix("px"); + split_sheet_sep_y->set_select_all_on_focus(true); split_sheet_sep_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); split_sheet_sep_vb->add_child(split_sheet_sep_y); split_sheet_sep_hb->add_child(split_sheet_sep_vb); @@ -2267,12 +2273,14 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_offset_x->set_min(0); split_sheet_offset_x->set_step(1); split_sheet_offset_x->set_suffix("px"); + split_sheet_offset_x->set_select_all_on_focus(true); split_sheet_offset_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); split_sheet_offset_vb->add_child(split_sheet_offset_x); split_sheet_offset_y = memnew(SpinBox); split_sheet_offset_y->set_min(0); split_sheet_offset_y->set_step(1); split_sheet_offset_y->set_suffix("px"); + split_sheet_offset_y->set_select_all_on_focus(true); split_sheet_offset_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); split_sheet_offset_vb->add_child(split_sheet_offset_y); split_sheet_offset_hb->add_child(split_sheet_offset_vb); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 2aa04d7874..5bc3cafe19 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -647,9 +647,9 @@ TextEditor::TextEditor() { edit_menu->get_popup()->add_separator(); PopupMenu *convert_case = memnew(PopupMenu); edit_menu->get_popup()->add_submenu_node_item(TTR("Convert Case"), convert_case); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase")), EDIT_TO_UPPERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase")), EDIT_TO_LOWERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize")), EDIT_CAPITALIZE); + convert_case->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE); + convert_case->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE); + convert_case->add_shortcut(ED_GET_SHORTCUT("script_text_editor/capitalize"), EDIT_CAPITALIZE); convert_case->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option)); highlighter_menu = memnew(PopupMenu); diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp index bb74bf8d1f..d6cef3ccb9 100644 --- a/editor/plugins/text_shader_editor.cpp +++ b/editor/plugins/text_shader_editor.cpp @@ -30,12 +30,12 @@ #include "text_shader_editor.h" +#include "core/config/project_settings.h" #include "core/version_generated.gen.h" +#include "editor/editor_file_system.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" -#include "editor/filesystem_dock.h" -#include "editor/project_settings_editor.h" #include "editor/themes/editor_scale.h" #include "editor/themes/editor_theme_manager.h" #include "scene/gui/split_container.h" @@ -735,6 +735,13 @@ void TextShaderEditor::_menu_option(int p_option) { } } +void TextShaderEditor::_prepare_edit_menu() { + const CodeEdit *tx = code_editor->get_text_editor(); + PopupMenu *popup = edit_menu->get_popup(); + popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo()); + popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo()); +} + void TextShaderEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: @@ -1088,6 +1095,9 @@ void TextShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); + context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !code_editor->get_text_editor()->has_undo()); + context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !code_editor->get_text_editor()->has_redo()); + context_menu->set_position(get_screen_position() + p_position); context_menu->reset_size(); context_menu->popup(); @@ -1128,6 +1138,7 @@ TextShaderEditor::TextShaderEditor() { edit_menu->set_shortcut_context(this); edit_menu->set_text(TTR("Edit")); edit_menu->set_switch_on_hover(true); + edit_menu->connect("about_to_popup", callable_mp(this, &TextShaderEditor::_prepare_edit_menu)); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); diff --git a/editor/plugins/text_shader_editor.h b/editor/plugins/text_shader_editor.h index be16148744..6d2ac743b8 100644 --- a/editor/plugins/text_shader_editor.h +++ b/editor/plugins/text_shader_editor.h @@ -34,7 +34,6 @@ #include "editor/code_editor.h" #include "scene/gui/margin_container.h" #include "scene/gui/menu_button.h" -#include "scene/gui/panel_container.h" #include "scene/gui/rich_text_label.h" #include "servers/rendering/shader_warnings.h" @@ -153,6 +152,7 @@ class TextShaderEditor : public MarginContainer { bool compilation_success = true; void _menu_option(int p_option); + void _prepare_edit_menu(); mutable Ref<Shader> shader; mutable Ref<ShaderInclude> shader_inc; diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index 2ee0dc0316..52b58b74a3 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -59,6 +59,9 @@ void TileAtlasView::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<In } Size2i TileAtlasView::_compute_base_tiles_control_size() { + if (tile_set_atlas_source.is_null()) { + return Size2i(); + } // Update the texture. Vector2i size; Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); @@ -69,6 +72,9 @@ Size2i TileAtlasView::_compute_base_tiles_control_size() { } Size2i TileAtlasView::_compute_alternative_tiles_control_size() { + if (tile_set_atlas_source.is_null()) { + return Size2i(); + } Vector2i size; for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); @@ -89,6 +95,9 @@ Size2i TileAtlasView::_compute_alternative_tiles_control_size() { } void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) { + if (tile_set_atlas_source.is_null()) { + return; + } float zoom = zoom_widget->get_zoom(); // Compute the minimum sizes. @@ -153,6 +162,9 @@ void TileAtlasView::_center_view() { } void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) { + if (tile_set_atlas_source.is_null()) { + return; + } base_tiles_root_control->set_tooltip_text(""); Ref<InputEventMouseMotion> mm = p_event; @@ -169,6 +181,9 @@ void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_ } void TileAtlasView::_draw_base_tiles() { + if (tile_set.is_null() || tile_set_atlas_source.is_null()) { + return; + } Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); if (texture.is_valid()) { Vector2i margins = tile_set_atlas_source->get_margins(); @@ -250,7 +265,7 @@ void TileAtlasView::_draw_base_tiles() { Vector2 offset_pos = Rect2(base_frame_rect).get_center() + Vector2(tile_set_atlas_source->get_tile_data(atlas_coords, 0)->get_texture_origin()); // Draw the tile. - TileMap::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, 0, frame); + TileMapLayer::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, 0, frame); } } @@ -314,6 +329,9 @@ void TileAtlasView::_clear_material_canvas_items() { } void TileAtlasView::_draw_base_tiles_texture_grid() { + if (tile_set_atlas_source.is_null()) { + return; + } Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); if (texture.is_valid()) { Vector2i margins = tile_set_atlas_source->get_margins(); @@ -344,6 +362,9 @@ void TileAtlasView::_draw_base_tiles_texture_grid() { } void TileAtlasView::_draw_base_tiles_shape_grid() { + if (tile_set.is_null() || tile_set_atlas_source.is_null()) { + return; + } // Draw the shapes. Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Vector2i tile_shape_size = tile_set->get_tile_size(); @@ -382,6 +403,9 @@ void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEve } void TileAtlasView::_draw_alternatives() { + if (tile_set.is_null() || tile_set_atlas_source.is_null()) { + return; + } // Draw the alternative tiles. Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); if (texture.is_valid()) { @@ -411,7 +435,7 @@ void TileAtlasView::_draw_alternatives() { } // Draw the tile. - TileMap::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, alternative_id); + TileMapLayer::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, alternative_id); // Increment the x position. current_pos.x += transposed ? texture_region_size.y : texture_region_size.x; @@ -432,12 +456,12 @@ void TileAtlasView::_draw_background_right() { } void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) { - tile_set = p_tile_set; - tile_set_atlas_source = p_tile_set_atlas_source; + tile_set = Ref<TileSet>(p_tile_set); + tile_set_atlas_source = Ref<TileSetAtlasSource>(p_tile_set_atlas_source); _clear_material_canvas_items(); - if (!tile_set) { + if (tile_set.is_null()) { return; } @@ -485,6 +509,10 @@ void TileAtlasView::set_padding(Side p_side, int p_padding) { } Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos, bool p_clamp) const { + if (tile_set_atlas_source.is_null()) { + return Vector2i(); + } + Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); if (!texture.is_valid()) { return TileSetSource::INVALID_ATLAS_COORDS; @@ -508,6 +536,10 @@ Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos, bool p } void TileAtlasView::_update_alternative_tiles_rect_cache() { + if (tile_set_atlas_source.is_null()) { + return; + } + alternative_tiles_rect_cache.clear(); Rect2i current; diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index e5b4863b05..8fcf942056 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -45,8 +45,8 @@ class TileAtlasView : public Control { GDCLASS(TileAtlasView, Control); private: - TileSet *tile_set = nullptr; - TileSetAtlasSource *tile_set_atlas_source = nullptr; + Ref<TileSet> tile_set; + Ref<TileSetAtlasSource> tile_set_atlas_source; int source_id = TileSet::INVALID_SOURCE; enum DragType { diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 2d0e5214db..a94080e253 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -144,7 +144,8 @@ void GenericTilePolygonEditor::_base_control_draw() { } // Draw tile-related things. - Size2 tile_size = tile_set->get_tile_size(); + const Size2 base_tile_size = tile_set->get_tile_size(); + const Size2 tile_size = background_region.size; Transform2D xform; xform.set_origin(base_control->get_size() / 2 + panning); @@ -170,12 +171,16 @@ void GenericTilePolygonEditor::_base_control_draw() { // Draw grid. if (current_snap_option == SNAP_GRID) { - Vector2 spacing = tile_size / snap_subdivision->get_value(); + Vector2 spacing = base_tile_size / snap_subdivision->get_value(); Vector2 offset = -tile_size / 2; + int w = snap_subdivision->get_value() * (tile_size / base_tile_size).x; + int h = snap_subdivision->get_value() * (tile_size / base_tile_size).y; - for (int i = 1; i < snap_subdivision->get_value(); i++) { - base_control->draw_line(Vector2(spacing.x * i, 0) + offset, Vector2(spacing.x * i, tile_size.y) + offset, Color(1, 1, 1, 0.33)); - base_control->draw_line(Vector2(0, spacing.y * i) + offset, Vector2(tile_size.x, spacing.y * i) + offset, Color(1, 1, 1, 0.33)); + for (int y = 1; y < h; y++) { + for (int x = 1; x < w; x++) { + base_control->draw_line(Vector2(spacing.x * x, 0) + offset, Vector2(spacing.x * x, tile_size.y) + offset, Color(1, 1, 1, 0.33)); + base_control->draw_line(Vector2(0, spacing.y * y) + offset, Vector2(tile_size.x, spacing.y * y) + offset, Color(1, 1, 1, 0.33)); + } } } @@ -1320,7 +1325,7 @@ TileDataDefaultEditor::TileDataDefaultEditor() { picker_button = memnew(Button); picker_button->set_theme_type_variation("FlatButton"); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); + picker_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/picker")); toolbar->add_child(picker_button); } @@ -2807,7 +2812,7 @@ TileDataTerrainsEditor::TileDataTerrainsEditor() { picker_button = memnew(Button); picker_button->set_theme_type_variation("FlatButton"); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); + picker_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/picker")); toolbar->add_child(picker_button); // Setup diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp index 98c05e9b15..5fd4fb3602 100644 --- a/editor/plugins/tiles/tile_map_layer_editor.cpp +++ b/editor/plugins/tiles/tile_map_layer_editor.cpp @@ -2183,13 +2183,6 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { ->get_viewport_control() ->connect(SceneStringName(mouse_exited), callable_mp(this, &TileMapLayerEditorTilesPlugin::_mouse_exited_viewport)); - // --- Shortcuts --- - ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KeyModifierMask::CMD_OR_CTRL | Key::X); - ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KeyModifierMask::CMD_OR_CTRL | Key::C); - ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KeyModifierMask::CMD_OR_CTRL | Key::V); - ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), Key::ESCAPE); - ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE); - // --- Initialize references --- tile_map_clipboard.instantiate(); selection_pattern.instantiate(); @@ -2217,7 +2210,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { paint_tool_button->set_theme_type_variation("FlatButton"); paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); - paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D)); + paint_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/paint_tool")); paint_tool_button->set_tooltip_text(TTR("Shift: Draw line.") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Shift: Draw rectangle.")); paint_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); @@ -2228,7 +2221,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { line_tool_button->set_toggle_mode(true); line_tool_button->set_button_group(tool_buttons_group); // TRANSLATORS: This refers to the line tool in the tilemap editor. - line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", TTR("Line", "Tool"), Key::L)); + line_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/line_tool")); line_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(line_tool_button); viewport_shortcut_buttons.push_back(line_tool_button); @@ -2237,7 +2230,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { rect_tool_button->set_theme_type_variation("FlatButton"); rect_tool_button->set_toggle_mode(true); rect_tool_button->set_button_group(tool_buttons_group); - rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R)); + rect_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/rect_tool")); rect_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(rect_tool_button); viewport_shortcut_buttons.push_back(rect_tool_button); @@ -2246,7 +2239,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { bucket_tool_button->set_theme_type_variation("FlatButton"); bucket_tool_button->set_toggle_mode(true); bucket_tool_button->set_button_group(tool_buttons_group); - bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", TTR("Bucket"), Key::B)); + bucket_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/bucket_tool")); bucket_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(bucket_tool_button); toolbar->add_child(tilemap_tiles_tools_buttons); @@ -2263,7 +2256,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { picker_button = memnew(Button); picker_button->set_theme_type_variation("FlatButton"); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); + picker_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/picker")); Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL; picker_button->set_tooltip_text(vformat(TTR("Alternatively hold %s with other tools to pick tile."), find_keycode_name(key))); picker_button->connect(SceneStringName(pressed), callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); @@ -2274,7 +2267,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { erase_button = memnew(Button); erase_button->set_theme_type_variation("FlatButton"); erase_button->set_toggle_mode(true); - erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E)); + erase_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/eraser")); erase_button->set_tooltip_text(TTR("Alternatively use RMB to erase tiles.")); erase_button->connect(SceneStringName(pressed), callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); @@ -3552,7 +3545,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); paint_tool_button->set_pressed(true); - paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D)); + paint_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/paint_tool")); paint_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); viewport_shortcut_buttons.push_back(paint_tool_button); @@ -3561,7 +3554,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { line_tool_button->set_theme_type_variation("FlatButton"); line_tool_button->set_toggle_mode(true); line_tool_button->set_button_group(tool_buttons_group); - line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", TTR("Line"), Key::L)); + line_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/line_tool")); line_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(line_tool_button); viewport_shortcut_buttons.push_back(line_tool_button); @@ -3570,7 +3563,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { rect_tool_button->set_theme_type_variation("FlatButton"); rect_tool_button->set_toggle_mode(true); rect_tool_button->set_button_group(tool_buttons_group); - rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R)); + rect_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/rect_tool")); rect_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(rect_tool_button); viewport_shortcut_buttons.push_back(rect_tool_button); @@ -3579,7 +3572,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { bucket_tool_button->set_theme_type_variation("FlatButton"); bucket_tool_button->set_toggle_mode(true); bucket_tool_button->set_button_group(tool_buttons_group); - bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", TTR("Bucket"), Key::B)); + bucket_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/bucket_tool")); bucket_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(bucket_tool_button); viewport_shortcut_buttons.push_back(bucket_tool_button); @@ -3597,7 +3590,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { picker_button = memnew(Button); picker_button->set_theme_type_variation("FlatButton"); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); + picker_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/picker")); picker_button->connect(SceneStringName(pressed), callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(picker_button); viewport_shortcut_buttons.push_back(picker_button); @@ -3606,7 +3599,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { erase_button = memnew(Button); erase_button->set_theme_type_variation("FlatButton"); erase_button->set_toggle_mode(true); - erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E)); + erase_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/eraser")); erase_button->connect(SceneStringName(pressed), callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); viewport_shortcut_buttons.push_back(erase_button); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index e4fd32a637..03070bc6b5 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -2627,7 +2627,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tools_settings_erase_button = memnew(Button); tools_settings_erase_button->set_theme_type_variation("FlatButton"); tools_settings_erase_button->set_toggle_mode(true); - tools_settings_erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E)); + tools_settings_erase_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/eraser")); tools_settings_erase_button->set_shortcut_context(this); tool_settings->add_child(tools_settings_erase_button); @@ -2687,7 +2687,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_create_help->set_grow_direction_preset(Control::PRESET_BOTTOM_LEFT); base_tile_popup_menu = memnew(PopupMenu); - base_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE), TILE_DELETE); + base_tile_popup_menu->add_shortcut(ED_GET_SHORTCUT("tiles_editor/delete"), TILE_DELETE); base_tile_popup_menu->add_item(TTR("Create an Alternative Tile"), TILE_CREATE_ALTERNATIVE); base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); tile_atlas_view->add_child(base_tile_popup_menu); @@ -2938,6 +2938,18 @@ bool EditorInspectorPluginTileData::parse_property(Object *p_object, const Varia add_property_editor(p_path, ep); return true; } + } else if (p_path.begins_with("custom_data_") && p_path.trim_prefix("custom_data_").is_valid_int()) { + // Custom data layers. + int layer_index = components[0].trim_prefix("custom_data_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + EditorProperty *ep = EditorInspectorDefaultPlugin::get_editor_for_property(p_object, p_type, p_path, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + const TileSetAtlasSourceEditor::AtlasTileProxyObject *proxy_obj = Object::cast_to<TileSetAtlasSourceEditor::AtlasTileProxyObject>(p_object); + const TileSetAtlasSource *atlas_source = *proxy_obj->get_edited_tile_set_atlas_source(); + ERR_FAIL_NULL_V(atlas_source, false); + const TileSet *tile_set = atlas_source->get_tile_set(); + ERR_FAIL_NULL_V(tile_set, false); + add_property_editor(p_path, ep, false, tile_set->get_custom_data_layer_name(layer_index)); + return true; } return false; } diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 6833a36138..f1cc69ce18 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -303,6 +303,19 @@ TilesEditorUtils::TilesEditorUtils() { singleton = this; // Pattern preview generation thread. pattern_preview_thread.start(_thread_func, this); + + ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KeyModifierMask::CMD_OR_CTRL | Key::X); + ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KeyModifierMask::CMD_OR_CTRL | Key::C); + ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KeyModifierMask::CMD_OR_CTRL | Key::V); + ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), Key::ESCAPE); + ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE); + + ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D); + ED_SHORTCUT("tiles_editor/line_tool", TTR("Line", "Tool"), Key::L); + ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R); + ED_SHORTCUT("tiles_editor/bucket_tool", TTR("Bucket"), Key::B); + ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E); + ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P); } TilesEditorUtils::~TilesEditorUtils() { diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 01551a52c2..438d798120 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -60,7 +60,6 @@ #include "scene/gui/view_panner.h" #include "scene/main/window.h" #include "scene/resources/curve_texture.h" -#include "scene/resources/image_texture.h" #include "scene/resources/style_box_flat.h" #include "scene/resources/visual_shader_nodes.h" #include "scene/resources/visual_shader_particle_nodes.h" @@ -5135,7 +5134,7 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, c } int new_node_id = connection_remap[item.id]; - int new_frame_id = connection_remap[node->get_frame()]; + int new_frame_id = node->get_frame(); undo_redo->add_do_method(visual_shader.ptr(), "attach_node_to_frame", type, new_node_id, new_frame_id); undo_redo->add_do_method(graph_plugin.ptr(), "attach_node_to_frame", type, new_node_id, new_frame_id); } diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index 32f088ec60..f474464f6c 100644 --- a/editor/project_manager/project_list.cpp +++ b/editor/project_manager/project_list.cpp @@ -32,6 +32,8 @@ #include "core/config/project_settings.h" #include "core/io/dir_access.h" +#include "core/os/time.h" +#include "core/version.h" #include "editor/editor_paths.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -130,12 +132,29 @@ void ProjectListItemControl::set_project_icon(const Ref<Texture2D> &p_icon) { project_icon->set_texture(p_icon); } +void ProjectListItemControl::set_last_edited_info(const String &p_info) { + last_edited_info->set_text(p_info); +} + +void ProjectListItemControl::set_project_version(const String &p_info) { + project_version->set_text(p_info); +} + void ProjectListItemControl::set_unsupported_features(PackedStringArray p_features) { if (p_features.size() > 0) { String tooltip_text = ""; for (int i = 0; i < p_features.size(); i++) { if (ProjectList::project_feature_looks_like_version(p_features[i])) { - tooltip_text += TTR("This project was last edited in a different Godot version: ") + p_features[i] + "\n"; + PackedStringArray project_version_split = p_features[i].split("."); + int project_version_major = 0, project_version_minor = 0; + if (project_version_split.size() >= 2) { + project_version_major = project_version_split[0].to_int(); + project_version_minor = project_version_split[1].to_int(); + } + if (VERSION_MAJOR != project_version_major || VERSION_MINOR <= project_version_minor) { + // Don't show a warning if the project was last edited in a previous minor version. + tooltip_text += TTR("This project was last edited in a different Godot version: ") + p_features[i] + "\n"; + } p_features.remove_at(i); i--; } @@ -144,6 +163,10 @@ void ProjectListItemControl::set_unsupported_features(PackedStringArray p_featur String unsupported_features_str = String(", ").join(p_features); tooltip_text += TTR("This project uses features unsupported by the current build:") + "\n" + unsupported_features_str; } + if (tooltip_text.is_empty()) { + return; + } + project_version->set_tooltip_text(tooltip_text); project_unsupported_features->set_tooltip_text(tooltip_text); project_unsupported_features->show(); } else { @@ -272,12 +295,24 @@ ProjectListItemControl::ProjectListItemControl() { project_path->set_modulate(Color(1, 1, 1, 0.5)); path_hb->add_child(project_path); + last_edited_info = memnew(Label); + last_edited_info->set_name("LastEditedInfo"); + last_edited_info->set_mouse_filter(Control::MOUSE_FILTER_PASS); + last_edited_info->set_tooltip_text("Last edited timestamp"); + last_edited_info->set_modulate(Color(1, 1, 1, 0.5)); + path_hb->add_child(last_edited_info); + project_unsupported_features = memnew(TextureRect); project_unsupported_features->set_name("ProjectUnsupportedFeatures"); project_unsupported_features->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); path_hb->add_child(project_unsupported_features); project_unsupported_features->hide(); + project_version = memnew(Label); + project_version->set_name("ProjectVersion"); + project_version->set_mouse_filter(Control::MOUSE_FILTER_PASS); + path_hb->add_child(project_version); + Control *spacer = memnew(Control); spacer->set_custom_minimum_size(Size2(10, 10)); path_hb->add_child(spacer); @@ -409,6 +444,24 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa PackedStringArray project_features = cf->get_value("application", "config/features", PackedStringArray()); PackedStringArray unsupported_features = ProjectSettings::get_unsupported_features(project_features); + String project_version = "?"; + for (int i = 0; i < project_features.size(); i++) { + if (ProjectList::project_feature_looks_like_version(project_features[i])) { + project_version = project_features[i]; + break; + } + } + + if (config_version < ProjectSettings::CONFIG_VERSION) { + // Previous versions may not have unsupported features. + if (config_version == 4) { + unsupported_features.push_back("3.x"); + project_version = "3.x"; + } else { + unsupported_features.push_back("Unknown version"); + } + } + uint64_t last_edited = 0; if (cf_err == OK) { // The modification date marks the date the project was last edited. @@ -433,7 +486,7 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa ProjectManager::get_singleton()->add_new_tag(tag); } - return Item(project_name, description, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version); + return Item(project_name, description, project_version, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version); } void ProjectList::_update_icons_async() { @@ -706,6 +759,8 @@ void ProjectList::_create_project_item_control(int p_index) { hb->set_tooltip_text(item.description); hb->set_tags(item.tags, this); hb->set_unsupported_features(item.unsupported_features.duplicate()); + hb->set_project_version(item.project_version); + hb->set_last_edited_info(Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true)); hb->set_is_favorite(item.favorite); hb->set_is_missing(item.missing); diff --git a/editor/project_manager/project_list.h b/editor/project_manager/project_list.h index 981df0f3a0..6e0f5830ac 100644 --- a/editor/project_manager/project_list.h +++ b/editor/project_manager/project_list.h @@ -51,6 +51,8 @@ class ProjectListItemControl : public HBoxContainer { TextureRect *project_icon = nullptr; Label *project_title = nullptr; Label *project_path = nullptr; + Label *last_edited_info = nullptr; + Label *project_version = nullptr; TextureRect *project_unsupported_features = nullptr; HBoxContainer *tag_container = nullptr; @@ -71,6 +73,8 @@ public: void set_project_path(const String &p_path); void set_tags(const PackedStringArray &p_tags, ProjectList *p_parent_list); void set_project_icon(const Ref<Texture2D> &p_icon); + void set_last_edited_info(const String &p_info); + void set_project_version(const String &p_version); void set_unsupported_features(PackedStringArray p_features); bool should_load_project_icon() const; @@ -100,6 +104,7 @@ public: struct Item { String project_name; String description; + String project_version; PackedStringArray tags; String tag_sort_string; String path; @@ -118,6 +123,7 @@ public: Item(const String &p_name, const String &p_description, + const String &p_project_version, const PackedStringArray &p_tags, const String &p_path, const String &p_icon, @@ -130,6 +136,7 @@ public: int p_version) { project_name = p_name; description = p_description; + project_version = p_project_version; tags = p_tags; path = p_path; icon = p_icon; diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index cff95cadc8..dc9d1df4e8 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -54,7 +54,7 @@ void ProjectSettingsEditor::popup_project_settings(bool p_clear_filter) { if (saved_size != Rect2()) { popup(saved_size); } else { - popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8); + popup_centered_clamped(Size2(1200, 700) * EDSCALE, 0.8); } _add_feature_overrides(); diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp index 51f9d319a6..edefaa61a8 100644 --- a/editor/rename_dialog.cpp +++ b/editor/rename_dialog.cpp @@ -289,6 +289,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor) { vbc->add_child(lbl_preview_title); lbl_preview = memnew(Label); + lbl_preview->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); lbl_preview->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); vbc->add_child(lbl_preview); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 53145c3450..9209c26876 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -56,6 +56,7 @@ #include "editor/shader_create_dialog.h" #include "editor/themes/editor_scale.h" #include "scene/animation/animation_tree.h" +#include "scene/audio/audio_stream_player.h" #include "scene/gui/check_box.h" #include "scene/property_utils.h" #include "scene/resources/packed_scene.h" @@ -254,8 +255,8 @@ void SceneTreeDock::instantiate_scenes(const Vector<String> &p_files, Node *p_pa _perform_instantiate_scenes(p_files, parent, -1); } -void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, Node *parent, int p_pos) { - ERR_FAIL_NULL(parent); +void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, Node *p_parent, int p_pos) { + ERR_FAIL_NULL(p_parent); Vector<Node *> instances; @@ -302,25 +303,25 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Instantiate Scene(s)")); + undo_redo->create_action_for_history(TTRN("Instantiate Scene", "Instantiate Scenes", instances.size()), editor_data->get_current_edited_scene_history_id()); + undo_redo->add_do_method(editor_selection, "clear"); for (int i = 0; i < instances.size(); i++) { Node *instantiated_scene = instances[i]; - undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); + undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true); if (p_pos >= 0) { - undo_redo->add_do_method(parent, "move_child", instantiated_scene, p_pos + i); + undo_redo->add_do_method(p_parent, "move_child", instantiated_scene, p_pos + i); } undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene); - undo_redo->add_do_method(editor_selection, "clear"); undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene); undo_redo->add_do_reference(instantiated_scene); - undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); + undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene); - String new_name = parent->validate_child_name(instantiated_scene); + String new_name = p_parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), p_files[i], new_name); - undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).path_join(new_name))); + undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_files[i], new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).path_join(new_name))); } undo_redo->commit_action(); @@ -330,6 +331,75 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N } } +void SceneTreeDock::_perform_create_audio_stream_players(const Vector<String> &p_files, Node *p_parent, int p_pos) { + ERR_FAIL_NULL(p_parent); + + StringName node_type = "AudioStreamPlayer"; + if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { + if (Object::cast_to<Node2D>(p_parent)) { + node_type = "AudioStreamPlayer2D"; + } else if (Object::cast_to<Node3D>(p_parent)) { + node_type = "AudioStreamPlayer3D"; + } + } + + Vector<Node *> nodes; + bool error = false; + + for (const String &path : p_files) { + Ref<AudioStream> stream = ResourceLoader::load(path); + if (stream.is_null()) { + current_option = -1; + accept->set_text(vformat(TTR("Error loading audio stream from %s"), path)); + accept->popup_centered(); + error = true; + break; + } + + Node *player = Object::cast_to<Node>(ClassDB::instantiate(node_type)); + player->set("stream", stream); + + // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. + const String &node_name = Node::adjust_name_casing(path.get_file().get_basename()); + if (!node_name.is_empty()) { + player->set_name(node_name); + } + + nodes.push_back(player); + } + + if (error) { + for (Node *node : nodes) { + memdelete(node); + } + return; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action_for_history(TTRN("Create AudioStreamPlayer", "Create AudioStreamPlayers", nodes.size()), editor_data->get_current_edited_scene_history_id()); + undo_redo->add_do_method(editor_selection, "clear"); + + for (int i = 0; i < nodes.size(); i++) { + Node *node = nodes[i]; + + undo_redo->add_do_method(p_parent, "add_child", node, true); + if (p_pos >= 0) { + undo_redo->add_do_method(p_parent, "move_child", node, p_pos + i); + } + undo_redo->add_do_method(node, "set_owner", edited_scene); + undo_redo->add_do_method(editor_selection, "add_node", node); + undo_redo->add_do_reference(node); + undo_redo->add_undo_method(p_parent, "remove_child", node); + + String new_name = p_parent->validate_child_name(node); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + undo_redo->add_do_method(ed, "live_debug_create_node", edited_scene->get_path_to(p_parent), node->get_class(), new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).path_join(new_name))); + } + + undo_redo->commit_action(); +} + void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base) { // `move_child` + `get_index` doesn't really work for internal nodes. ERR_FAIL_COND_MSG(base->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to replace internal node, this is not supported."); @@ -3034,7 +3104,23 @@ void SceneTreeDock::set_edited_scene(Node *p_scene) { edited_scene = p_scene; } +static bool _is_same_selection(const Vector<Node *> &p_first, const List<Node *> &p_second) { + if (p_first.size() != p_second.size()) { + return false; + } + for (Node *node : p_second) { + if (!p_first.has(node)) { + return false; + } + } + return true; +} + void SceneTreeDock::set_selection(const Vector<Node *> &p_nodes) { + // If the nodes selected are the same independently of order then return early. + if (_is_same_selection(p_nodes, editor_selection->get_full_selected_node_list())) { + return; + } editor_selection->clear(); for (Node *node : p_nodes) { editor_selection->add_node(node); @@ -3196,15 +3282,13 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) { void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type) { Node *node = get_node(p_to); ERR_FAIL_NULL(node); + ERR_FAIL_COND(p_files.is_empty()); - if (scene_tree->get_scene_tree()->get_drop_mode_flags() & Tree::DROP_MODE_INBETWEEN) { - // Dropped PackedScene, instance it. - int to_pos = -1; - _normalize_drop(node, to_pos, p_type); - _perform_instantiate_scenes(p_files, node, to_pos); - } else { - const String &res_path = p_files[0]; - StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path); + const String &res_path = p_files[0]; + const StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path); + + // Dropping as property when possible. + if (p_type == 0 && p_files.size() == 1) { List<String> valid_properties; List<PropertyInfo> pinfo; @@ -3238,10 +3322,22 @@ void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, menu_properties->reset_size(); menu_properties->set_position(get_screen_position() + get_local_mouse_position()); menu_properties->popup(); - } else if (!valid_properties.is_empty()) { + return; + } + if (!valid_properties.is_empty()) { _perform_property_drop(node, valid_properties.front()->get(), ResourceLoader::load(res_path)); + return; } } + + // Either instantiate scenes or create AudioStreamPlayers. + int to_pos = -1; + _normalize_drop(node, to_pos, p_type); + if (ClassDB::is_parent_class(res_type, "PackedScene")) { + _perform_instantiate_scenes(p_files, node, to_pos); + } else if (ClassDB::is_parent_class(res_type, "AudioStream")) { + _perform_create_audio_stream_players(p_files, node, to_pos); + } } void SceneTreeDock::_script_dropped(const String &p_file, NodePath p_to) { @@ -3571,7 +3667,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { if (profile_allow_editing) { menu->add_separator(); - menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Remove")), ED_SHORTCUT("scene_tree/delete", TTR("Delete Node(s)"), Key::KEY_DELETE), TOOL_ERASE); + menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Remove")), ED_GET_SHORTCUT("scene_tree/delete"), TOOL_ERASE); } menu->reset_size(); menu->set_position(p_menu_pos); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 21e1b00f93..abef990995 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -272,7 +272,8 @@ class SceneTreeDock : public VBoxContainer { void _filter_option_selected(int option); void _append_filter_options_to(PopupMenu *p_menu, bool p_include_separator = true); - void _perform_instantiate_scenes(const Vector<String> &p_files, Node *parent, int p_pos); + void _perform_instantiate_scenes(const Vector<String> &p_files, Node *p_parent, int p_pos); + void _perform_create_audio_stream_players(const Vector<String> &p_files, Node *p_parent, int p_pos); void _replace_with_branch_scene(const String &p_file, Node *base); void _remote_tree_selected(); diff --git a/editor/themes/SCsub b/editor/themes/SCsub index 65cfb6a8be..e8f96e4299 100644 --- a/editor/themes/SCsub +++ b/editor/themes/SCsub @@ -3,8 +3,8 @@ Import("env") import glob -import editor_theme_builders +import editor_theme_builders # Fonts flist = glob.glob(env.Dir("#thirdparty").abspath + "/fonts/*.ttf") |