diff options
Diffstat (limited to 'editor/editor_node.cpp')
-rw-r--r-- | editor/editor_node.cpp | 475 |
1 files changed, 318 insertions, 157 deletions
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 4fb1a86ce2..964061505f 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -90,7 +90,6 @@ #include "editor/editor_log.h" #include "editor/editor_native_shader_source_visualizer.h" #include "editor/editor_paths.h" -#include "editor/editor_plugin.h" #include "editor/editor_properties.h" #include "editor/editor_property_name_processor.h" #include "editor/editor_quick_open.h" @@ -134,12 +133,12 @@ #include "editor/inspector_dock.h" #include "editor/multi_node_edit.h" #include "editor/node_dock.h" -#include "editor/plugin_config_dialog.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/asset_library_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/debugger_editor_plugin.h" #include "editor/plugins/dedicated_server_export_plugin.h" +#include "editor/plugins/editor_plugin.h" #include "editor/plugins/editor_preview_plugins.h" #include "editor/plugins/editor_resource_conversion_plugin.h" #include "editor/plugins/gdextension_export_plugin.h" @@ -148,6 +147,7 @@ #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/plugins/packed_scene_translation_parser_plugin.h" #include "editor/plugins/particle_process_material_editor_plugin.h" +#include "editor/plugins/plugin_config_dialog.h" #include "editor/plugins/root_motion_editor_plugin.h" #include "editor/plugins/script_text_editor.h" #include "editor/plugins/text_editor.h" @@ -456,6 +456,9 @@ void EditorNode::_update_from_settings() { void EditorNode::_gdextensions_reloaded() { // In case the developer is inspecting an object that will be changed by the reload. InspectorDock::get_inspector_singleton()->update_tree(); + + // Regenerate documentation. + EditorHelp::generate_doc(); } void EditorNode::_select_default_main_screen_plugin() { @@ -518,7 +521,7 @@ void EditorNode::_update_theme(bool p_skip_creation) { scene_root_parent->add_theme_style_override("panel", theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles))); bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles))); distraction_free->set_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons))); - distraction_free->add_theme_style_override("pressed", theme->get_stylebox("normal", "FlatMenuButton")); + distraction_free->add_theme_style_override(SceneStringName(pressed), theme->get_stylebox("normal", "FlatMenuButton")); help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons))); help_menu->set_item_icon(help_menu->get_item_index(HELP_COPY_SYSTEM_INFO), theme->get_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons))); @@ -541,6 +544,9 @@ void EditorNode::_update_theme(bool p_skip_creation) { } } } + + editor_dock_manager->update_tab_styles(); + editor_dock_manager->set_tab_icon_max_width(theme->get_constant(SNAME("class_icon_size"), EditorStringName(Editor))); } void EditorNode::update_preview_themes(int p_mode) { @@ -665,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; @@ -749,7 +755,7 @@ void EditorNode::_notification(int p_what) { case NOTIFICATION_APPLICATION_FOCUS_OUT: { // Save on focus loss before applying the FPS limit to avoid slowing down the saving process. if (EDITOR_GET("interface/editor/save_on_focus_loss")) { - _menu_option_confirm(FILE_SAVE_SCENE, false); + _menu_option_confirm(FILE_SAVE_SCENE_SILENTLY, false); } // Set a low FPS cap to decrease CPU/GPU usage while the editor is unfocused. @@ -771,6 +777,12 @@ void EditorNode::_notification(int p_what) { EditorFileDialog::set_default_display_mode((EditorFileDialog::DisplayMode)EDITOR_GET("filesystem/file_dialog/display_mode").operator int()); } + if (EDITOR_GET("interface/editor/import_resources_when_unfocused")) { + scan_changes_timer->start(); + } else { + scan_changes_timer->stop(); + } + follow_system_theme = EDITOR_GET("interface/theme/follow_system_theme"); use_system_accent_color = EDITOR_GET("interface/theme/use_system_accent_color"); @@ -780,6 +792,10 @@ void EditorNode::_notification(int p_what) { recent_scenes->reset_size(); } + if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/dock_tab_style")) { + editor_dock_manager->update_tab_styles(); + } + if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/scene_tabs")) { scene_tabs->update_scene_tabs(); } @@ -1445,6 +1461,9 @@ void EditorNode::_dialog_display_load_error(String p_file, Error p_error) { case ERR_FILE_NOT_FOUND: { show_accept(vformat(TTR("Missing file '%s' or one of its dependencies."), p_file.get_file()), TTR("OK")); } break; + case ERR_FILE_UNRECOGNIZED: { + show_accept(vformat(TTR("File '%s' is saved in a format that is newer than the formats supported by this version of Godot, so it can't be opened."), p_file.get_file()), TTR("OK")); + } break; default: { show_accept(vformat(TTR("Error while loading file '%s'."), p_file.get_file()), TTR("OK")); } break; @@ -2355,7 +2374,13 @@ static bool overrides_external_editor(Object *p_object) { void EditorNode::_add_to_history(const Object *p_object, const String &p_property, bool p_inspector_only) { ObjectID id = p_object->get_instance_id(); - if (id != editor_history.get_current()) { + ObjectID history_id = editor_history.get_current(); + if (id != history_id) { + const MultiNodeEdit *multi_node_edit = Object::cast_to<const MultiNodeEdit>(p_object); + const MultiNodeEdit *history_multi_node_edit = Object::cast_to<const MultiNodeEdit>(ObjectDB::get_instance(history_id)); + if (multi_node_edit && history_multi_node_edit && multi_node_edit->is_same_selection(history_multi_node_edit)) { + return; + } if (p_inspector_only) { editor_history.add_object(id, String(), true); } else if (p_property.is_empty()) { @@ -2373,7 +2398,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update Ref<Resource> res = Object::cast_to<Resource>(current_obj); if (p_skip_foreign && res.is_valid()) { const int current_tab = scene_tabs->get_current_tab(); - if (res->get_path().find("::") > -1 && res->get_path().get_slice("::", 0) != editor_data.get_scene_path(current_tab)) { + if (res->get_path().contains("::") && res->get_path().get_slice("::", 0) != editor_data.get_scene_path(current_tab)) { // Trying to edit resource that belongs to another scene; abort. current_obj = nullptr; } @@ -2449,6 +2474,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update if (current_node->is_inside_tree()) { NodeDock::get_singleton()->set_node(current_node); SceneTreeDock::get_singleton()->set_selected(current_node); + SceneTreeDock::get_singleton()->set_selection({ current_node }); InspectorDock::get_singleton()->update(current_node); if (!inspector_only && !skip_main_plugin) { skip_main_plugin = stay_in_script_editor_on_node_selected && !ScriptEditor::get_singleton()->is_editor_floating() && ScriptEditor::get_singleton()->is_visible_in_tree(); @@ -2470,13 +2496,13 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update } else { Node *selected_node = nullptr; + Vector<Node *> multi_nodes; if (current_obj->is_class("MultiNodeEdit")) { Node *scene = get_edited_scene(); if (scene) { MultiNodeEdit *multi_node_edit = Object::cast_to<MultiNodeEdit>(current_obj); int node_count = multi_node_edit->get_node_count(); if (node_count > 0) { - List<Node *> multi_nodes; for (int node_index = 0; node_index < node_count; ++node_index) { Node *node = scene->get_node(multi_node_edit->get_node(node_index)); if (node) { @@ -2486,7 +2512,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update if (!multi_nodes.is_empty()) { // Pick the top-most node. multi_nodes.sort_custom<Node::Comparator>(); - selected_node = multi_nodes.front()->get(); + selected_node = multi_nodes[0]; } } } @@ -2495,6 +2521,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update InspectorDock::get_inspector_singleton()->edit(current_obj); NodeDock::get_singleton()->set_node(nullptr); SceneTreeDock::get_singleton()->set_selected(selected_node); + SceneTreeDock::get_singleton()->set_selection(multi_nodes); InspectorDock::get_singleton()->update(nullptr); } @@ -2583,8 +2610,8 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions); file->clear_filters(); - for (int i = 0; i < extensions.size(); i++) { - file->add_filter("*." + extensions[i], extensions[i].to_upper()); + for (const String &extension : extensions) { + file->add_filter("*." + extension, extension.to_upper()); } Node *scene = editor_data.get_edited_scene_root(); @@ -2646,6 +2673,16 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case FILE_CLOSE: { _scene_tab_closed(editor_data.get_edited_scene()); } break; + case FILE_SAVE_SCENE_SILENTLY: { + // Save scene without displaying progress dialog. Used to work around + // errors about parent node being busy setting up children + // when Save on Focus Loss kicks in. + Node *scene = editor_data.get_edited_scene_root(); + if (scene && !scene->get_scene_file_path().is_empty() && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { + _save_scene(scene->get_scene_file_path()); + save_editor_layout_delayed(); + } + } break; case SCENE_TAB_CLOSE: case FILE_SAVE_SCENE: { int scene_idx = (p_option == FILE_SAVE_SCENE) ? -1 : tab_closing_idx; @@ -2702,8 +2739,8 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { Ref<PackedScene> sd = memnew(PackedScene); ResourceSaver::get_recognized_extensions(sd, &extensions); file->clear_filters(); - for (int i = 0; i < extensions.size(); i++) { - file->add_filter("*." + extensions[i], extensions[i].to_upper()); + for (const String &extension : extensions) { + file->add_filter("*." + extension, extension.to_upper()); } if (!scene->get_scene_file_path().is_empty()) { @@ -3034,14 +3071,14 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions); file->clear_filters(); - for (int i = 0; i < extensions.size(); i++) { - file->add_filter("*." + extensions[i], extensions[i].to_upper()); + for (const String &extension : extensions) { + file->add_filter("*." + extension, extension.to_upper()); } Node *scene = editor_data.get_edited_scene_root(); if (scene) { file->set_current_path(scene->get_scene_file_path()); - }; + } file->set_title(TTR("Pick a Main Scene")); file->popup_file_dialog(); @@ -3055,8 +3092,8 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case HELP_DOCS: { OS::get_singleton()->shell_open(VERSION_DOCS_URL "/"); } break; - case HELP_QA: { - OS::get_singleton()->shell_open("https://godotengine.org/qa/"); + case HELP_FORUM: { + OS::get_singleton()->shell_open("https://forum.godotengine.org/"); } break; case HELP_REPORT_A_BUG: { OS::get_singleton()->shell_open("https://github.com/godotengine/godot/issues"); @@ -3431,7 +3468,7 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed icon->connect_changed(callable_mp((Control *)tb, &Control::update_minimum_size)); } - tb->connect("pressed", callable_mp(singleton, &EditorNode::editor_select).bind(singleton->main_editor_buttons.size())); + tb->connect(SceneStringName(pressed), callable_mp(singleton, &EditorNode::editor_select).bind(singleton->main_editor_buttons.size())); singleton->main_editor_buttons.push_back(tb); singleton->main_editor_button_hb->add_child(tb); @@ -3461,11 +3498,15 @@ void EditorNode::remove_editor_plugin(EditorPlugin *p_editor, bool p_config_chan break; } else { - singleton->main_editor_buttons[i]->disconnect("pressed", callable_mp(singleton, &EditorNode::editor_select)); - singleton->main_editor_buttons[i]->connect("pressed", callable_mp(singleton, &EditorNode::editor_select).bind(i - 1)); + singleton->main_editor_buttons[i]->disconnect(SceneStringName(pressed), callable_mp(singleton, &EditorNode::editor_select)); + singleton->main_editor_buttons[i]->connect(SceneStringName(pressed), callable_mp(singleton, &EditorNode::editor_select).bind(i - 1)); } } + if (singleton->editor_plugin_screen == p_editor) { + singleton->editor_plugin_screen = nullptr; + } + singleton->editor_table.erase(p_editor); } p_editor->make_visible(false); @@ -3832,6 +3873,8 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) { if (tabs_to_close.is_empty()) { callable_mp(this, &EditorNode::_set_main_scene_state).call_deferred(state, get_edited_scene()); // Do after everything else is done setting up. } + + _update_undo_redo_allowed(); } void EditorNode::setup_color_picker(ColorPicker *p_picker) { @@ -3917,14 +3960,18 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b } int prev = editor_data.get_edited_scene(); - int idx = editor_data.add_edited_scene(-1); + int idx = prev; - if (!editor_data.get_edited_scene_root() && editor_data.get_edited_scene_count() == 2) { - _remove_edited_scene(); - } else if (p_silent_change_tab) { - _set_current_scene_nocheck(idx); + if (prev == -1 || editor_data.get_edited_scene_root() || !editor_data.get_scene_path(prev).is_empty()) { + idx = editor_data.add_edited_scene(-1); + + if (p_silent_change_tab) { + _set_current_scene_nocheck(idx); + } else { + _set_current_scene(idx); + } } else { - _set_current_scene(idx); + EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id()); } dependency_errors.clear(); @@ -3941,7 +3988,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b dependency_error->show(DependencyErrorDialog::MODE_SCENE, lpath, errors); opening_prev = false; - if (prev != -1) { + if (prev != -1 && prev != idx) { _set_current_scene(prev); editor_data.remove_scene(idx); } @@ -3952,7 +3999,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b _dialog_display_load_error(lpath, err); opening_prev = false; - if (prev != -1) { + if (prev != -1 && prev != idx) { _set_current_scene(prev); editor_data.remove_scene(idx); } @@ -3988,7 +4035,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b sdata.unref(); _dialog_display_load_error(lpath, ERR_FILE_CORRUPT); opening_prev = false; - if (prev != -1) { + if (prev != -1 && prev != idx) { _set_current_scene(prev); editor_data.remove_scene(idx); } @@ -4014,10 +4061,6 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b _load_editor_plugin_states_from_config(editor_state_cf); } - _update_title(); - scene_tabs->update_scene_tabs(); - _add_to_recent_scenes(lpath); - if (editor_folding.has_folding_data(lpath)) { editor_folding.load_scene_folding(new_scene, lpath); } else if (EDITOR_GET("interface/inspector/auto_unfold_foreign_scenes")) { @@ -4029,11 +4072,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b EditorDebuggerNode::get_singleton()->update_live_edit_root(); - // Tell everything to edit this object, unless we're in the process of restoring scenes. - // If we are, we'll edit it after the restoration is done. - if (!restoring_scenes) { - push_item(new_scene); - } else { + if (restoring_scenes) { // Initialize history for restored scenes. ObjectID id = new_scene->get_instance_id(); if (id != editor_history.get_current()) { @@ -4057,10 +4096,18 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b save_editor_layout_delayed(); } + if (p_set_inherited) { + EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(editor_data.get_current_edited_scene_history_id()); + } + + _update_title(); + scene_tabs->update_scene_tabs(); + _add_to_recent_scenes(lpath); + return OK; } -HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node) { +HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node, bool p_node_references_only) { HashMap<StringName, Variant> modified_property_map; List<PropertyInfo> pinfo; @@ -4072,7 +4119,17 @@ HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node * Variant current_value = p_node->get(E.name); if (is_valid_revert) { if (PropertyUtils::is_property_value_different(current_value, revert_value)) { - modified_property_map[E.name] = current_value; + // If this property is a direct node reference, save a NodePath instead to prevent corrupted references. + if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) { + Node *target_node = Object::cast_to<Node>(current_value); + if (target_node) { + modified_property_map[E.name] = p_node->get_path_to(target_node); + } + } else { + if (!p_node_references_only) { + modified_property_map[E.name] = current_value; + } + } } } } @@ -4090,10 +4147,118 @@ void EditorNode::update_ownership_table_for_addition_node_ancestors(Node *p_curr } } -void EditorNode::update_diff_data_for_node( - Node *p_edited_scene, +void EditorNode::update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification) { + if (p_node) { + // First, attempt to restore the script property since it may affect the get_property_list method. + Variant *script_property_table_entry = p_node_modification.property_table.getptr(CoreStringName(script)); + if (script_property_table_entry) { + p_node->set_script(*script_property_table_entry); + } + + // Get properties for this node. + List<PropertyInfo> pinfo; + p_node->get_property_list(&pinfo); + + // Get names of all valid property names. + HashMap<StringName, bool> property_node_reference_table; + for (const PropertyInfo &E : pinfo) { + if (E.usage & PROPERTY_USAGE_STORAGE) { + if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) { + property_node_reference_table[E.name] = true; + } else { + property_node_reference_table[E.name] = false; + } + } + } + + // Restore the modified properties for this node. + for (const KeyValue<StringName, Variant> &E : p_node_modification.property_table) { + bool *property_node_reference_table_entry = property_node_reference_table.getptr(E.key); + if (property_node_reference_table_entry) { + // If the property is a node reference, attempt to restore from the node path instead. + bool is_node_reference = *property_node_reference_table_entry; + if (is_node_reference) { + if (E.value.get_type() == Variant::NODE_PATH) { + p_node->set(E.key, p_node->get_node_or_null(E.value)); + } + } else { + p_node->set(E.key, E.value); + } + } + } + + // Restore the connections to other nodes. + for (const ConnectionWithNodePath &E : p_node_modification.connections_to) { + Connection conn = E.connection; + + // Get the node the callable is targeting. + Node *target_node = Object::cast_to<Node>(conn.callable.get_object()); + + // If the callable object no longer exists or is marked for deletion, + // attempt to reaccquire the closest match by using the node path + // we saved earlier. + if (!target_node || !target_node->is_queued_for_deletion()) { + target_node = p_node->get_node_or_null(E.node_path); + } + + if (target_node) { + // Reconstruct the callable. + Callable new_callable = Callable(target_node, conn.callable.get_method()); + + if (!p_node->is_connected(conn.signal.get_name(), new_callable)) { + ERR_FAIL_COND(p_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK); + } + } + } + + // Restore the connections from other nodes. + for (const Connection &E : p_node_modification.connections_from) { + Connection conn = E; + + bool valid = p_node->has_method(conn.callable.get_method()) || Ref<Script>(p_node->get_script()).is_null() || Ref<Script>(p_node->get_script())->has_method(conn.callable.get_method()); + ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method())); + + // Get the object which the signal is connected from. + Object *source_object = conn.signal.get_object(); + + if (source_object) { + ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(p_node, conn.callable.get_method()), conn.flags) != OK); + } + } + + // Re-add the groups. + for (const Node::GroupInfo &E : p_node_modification.groups) { + p_node->add_to_group(E.name, E.persistent); + } + } +} + +void EditorNode::update_node_reference_modification_table_for_node( Node *p_root, Node *p_node, + List<Node *> p_excluded_nodes, + HashMap<NodePath, ModificationNodeEntry> &p_modification_table) { + if (!p_excluded_nodes.find(p_node)) { + HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, false); + + if (!modified_properties.is_empty()) { + ModificationNodeEntry modification_node_entry; + modification_node_entry.property_table = modified_properties; + + p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry; + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + update_node_reference_modification_table_for_node(p_root, child, p_excluded_nodes, p_modification_table); + } + } +} + +void EditorNode::update_reimported_diff_data_for_node( + Node *p_edited_scene, + Node *p_reimported_root, + Node *p_node, HashMap<NodePath, ModificationNodeEntry> &p_modification_table, List<AdditiveNodeEntry> &p_addition_list) { bool node_part_of_subscene = p_node != p_edited_scene && @@ -4103,14 +4268,14 @@ void EditorNode::update_diff_data_for_node( // Loop through the owners until either we reach the root node or nullptr Node *valid_node_owner = p_node->get_owner(); while (valid_node_owner) { - if (valid_node_owner == p_root) { + if (valid_node_owner == p_reimported_root) { break; } valid_node_owner = valid_node_owner->get_owner(); } - if ((valid_node_owner == p_root && (p_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_root) { - HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node); + if ((valid_node_owner == p_reimported_root && (p_reimported_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_reimported_root) { + HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, false); // Find all valid connections to other nodes. List<Connection> connections_to; @@ -4142,7 +4307,7 @@ void EditorNode::update_diff_data_for_node( if (source_node) { valid_source_owner = source_node->get_owner(); while (valid_source_owner) { - if (valid_source_owner == p_root) { + if (valid_source_owner == p_reimported_root) { break; } valid_source_owner = valid_source_owner->get_owner(); @@ -4168,41 +4333,55 @@ void EditorNode::update_diff_data_for_node( modification_node_entry.connections_from = valid_connections_from; modification_node_entry.groups = groups; - p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry; + p_modification_table[p_reimported_root->get_path_to(p_node)] = modification_node_entry; } } else { - AdditiveNodeEntry new_additive_node_entry; - new_additive_node_entry.node = p_node; - new_additive_node_entry.parent = p_root->get_path_to(p_node->get_parent()); - new_additive_node_entry.owner = p_node->get_owner(); - new_additive_node_entry.index = p_node->get_index(); + // Only save additional nodes which have an owner since this was causing issues transient ownerless nodes + // which get recreated upon scene tree entry. + // For now instead, assume all ownerless nodes are transient and will have to be recreated. + if (p_node->get_owner()) { + HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, true); + + if (p_node->get_parent()->get_owner() != nullptr && p_node->get_parent()->get_owner() != p_edited_scene) { + AdditiveNodeEntry new_additive_node_entry; + new_additive_node_entry.node = p_node; + new_additive_node_entry.parent = p_reimported_root->get_path_to(p_node->get_parent()); + new_additive_node_entry.owner = p_node->get_owner(); + new_additive_node_entry.index = p_node->get_index(); + + Node2D *node_2d = Object::cast_to<Node2D>(p_node); + if (node_2d) { + new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent()); + } + Node3D *node_3d = Object::cast_to<Node3D>(p_node); + if (node_3d) { + new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent()); + } - Node2D *node_2d = Object::cast_to<Node2D>(p_node); - if (node_2d) { - new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent()); - } - Node3D *node_3d = Object::cast_to<Node3D>(p_node); - if (node_3d) { - new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent()); - } + // Gathers the ownership of all ancestor nodes for later use. + HashMap<Node *, Node *> ownership_table; + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + update_ownership_table_for_addition_node_ancestors(child, ownership_table); + } - // Gathers the ownership of all ancestor nodes for later use. - HashMap<Node *, Node *> ownership_table; - for (int i = 0; i < p_node->get_child_count(); i++) { - Node *child = p_node->get_child(i); - update_ownership_table_for_addition_node_ancestors(child, ownership_table); - } + new_additive_node_entry.ownership_table = ownership_table; - new_additive_node_entry.ownership_table = ownership_table; + p_addition_list.push_back(new_additive_node_entry); + } - p_addition_list.push_back(new_additive_node_entry); + if (!modified_properties.is_empty()) { + ModificationNodeEntry modification_node_entry; + modification_node_entry.property_table = modified_properties; - return; + p_modification_table[p_reimported_root->get_path_to(p_node)] = modification_node_entry; + } + } } for (int i = 0; i < p_node->get_child_count(); i++) { Node *child = p_node->get_child(i); - update_diff_data_for_node(p_edited_scene, p_root, child, p_modification_table, p_addition_list); + update_reimported_diff_data_for_node(p_edited_scene, p_reimported_root, child, p_modification_table, p_addition_list); } } // @@ -5494,19 +5673,18 @@ 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) { add_io_error(vformat(TTR("Unable to write to file '%s', file in use, locked or lacking permissions."), p_str)); } -// Since we felt that a bespoke NOTIFICATION might not be desirable, this function -// provides the hardcoded callbacks to address known bugs which occur on certain -// nodes during reimport. -// Ideally, we should probably agree on a standardized method name which could be -// called from here instead. -void EditorNode::_notify_scene_updated(Node *p_node) { +// Recursive function to inform nodes that an array of nodes have had their scene reimported. +// It will attempt to call a method named '_nodes_scene_reimported' on every node in the +// tree so that editor scripts which create transient nodes will have the opportunity +// to recreate them. +void EditorNode::_notify_nodes_scene_reimported(Node *p_node, Array p_reimported_nodes) { Skeleton3D *skel_3d = Object::cast_to<Skeleton3D>(p_node); if (skel_3d) { skel_3d->reset_bone_poses(); @@ -5517,8 +5695,12 @@ void EditorNode::_notify_scene_updated(Node *p_node) { } } + if (p_node->has_method("_nodes_scene_reimported")) { + p_node->call("_nodes_scene_reimported", p_reimported_nodes); + } + for (int i = 0; i < p_node->get_child_count(); i++) { - _notify_scene_updated(p_node->get_child(i)); + _notify_nodes_scene_reimported(p_node->get_child(i), p_reimported_nodes); } } @@ -5588,6 +5770,7 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node * void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) { int original_edited_scene_idx = editor_data.get_edited_scene(); HashMap<int, List<Node *>> edited_scene_map; + Array replaced_nodes; // Walk through each opened scene to get a global list of all instances which match // the current reimported scenes. @@ -5628,12 +5811,16 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins // Update the version editor_data.is_scene_changed(current_scene_idx); + // Contains modifications in the edited scene which reference nodes inside of any nodes we will be reimporting. + HashMap<NodePath, ModificationNodeEntry> edited_scene_global_modification_table; + update_node_reference_modification_table_for_node(current_edited_scene, current_edited_scene, edited_scene_map_elem.value, edited_scene_global_modification_table); + for (Node *original_node : edited_scene_map_elem.value) { // Walk the tree for the current node and extract relevant diff data, storing it in the modification table. // For additional nodes which are part of the current scene, they get added to the addition table. HashMap<NodePath, ModificationNodeEntry> modification_table; List<AdditiveNodeEntry> addition_list; - update_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list); + update_reimported_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list); // Disconnect all relevant connections, all connections from and persistent connections to. for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : modification_table) { @@ -5715,7 +5902,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins // be properly updated. for (String path : required_load_paths) { if (!local_scene_cache.find(path)) { - current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); + current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err); local_scene_cache[path] = current_packed_scene; } else { current_packed_scene = local_scene_cache[path]; @@ -5733,6 +5920,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins ERR_FAIL_NULL(instantiated_node); + // For clear instance state for path recaching. + instantiated_node->set_scene_instance_state(Ref<SceneState>()); + bool original_node_is_displayed_folded = original_node->is_displayed_folded(); bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder(); @@ -5838,77 +6028,30 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins NodePath new_current_path = E.key; Node *modifiable_node = instantiated_node->get_node_or_null(new_current_path); - if (modifiable_node) { - // Get properties for this node. - List<PropertyInfo> pinfo; - modifiable_node->get_property_list(&pinfo); - - // Get names of all valid property names (TODO: make this more efficient). - List<String> property_names; - for (const PropertyInfo &E2 : pinfo) { - if (E2.usage & PROPERTY_USAGE_STORAGE) { - property_names.push_back(E2.name); - } - } - - // Restore the modified properties for this node. - for (const KeyValue<StringName, Variant> &E2 : E.value.property_table) { - if (property_names.find(E2.key)) { - modifiable_node->set(E2.key, E2.value); - } - } - // Restore the connections to other nodes. - for (const ConnectionWithNodePath &E2 : E.value.connections_to) { - Connection conn = E2.connection; - - // Get the node the callable is targeting. - Node *target_node = cast_to<Node>(conn.callable.get_object()); - - // If the callable object no longer exists or is marked for deletion, - // attempt to reaccquire the closest match by using the node path - // we saved earlier. - if (!target_node || !target_node->is_queued_for_deletion()) { - target_node = modifiable_node->get_node_or_null(E2.node_path); - } - - if (target_node) { - // Reconstruct the callable. - Callable new_callable = Callable(target_node, conn.callable.get_method()); - - if (!modifiable_node->is_connected(conn.signal.get_name(), new_callable)) { - ERR_FAIL_COND(modifiable_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK); - } - } - } - - // Restore the connections from other nodes. - for (const Connection &E2 : E.value.connections_from) { - Connection conn = E2; - - bool valid = modifiable_node->has_method(conn.callable.get_method()) || Ref<Script>(modifiable_node->get_script()).is_null() || Ref<Script>(modifiable_node->get_script())->has_method(conn.callable.get_method()); - ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method())); - - // Get the object which the signal is connected from. - Object *source_object = conn.signal.get_object(); + update_node_from_node_modification_entry(modifiable_node, E.value); + } + // Add the newly instantiated node to the edited scene's replaced node list. + replaced_nodes.push_back(instantiated_node); + } - if (source_object) { - ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(modifiable_node, conn.callable.get_method()), conn.flags) != OK); - } - } + // Attempt to restore the modified properties and signals for the instantitated node and all its owned children. + for (KeyValue<NodePath, ModificationNodeEntry> &E : edited_scene_global_modification_table) { + NodePath new_current_path = E.key; + Node *modifiable_node = current_edited_scene->get_node_or_null(new_current_path); - // Re-add the groups. - for (const Node::GroupInfo &E2 : E.value.groups) { - modifiable_node->add_to_group(E2.name, E2.persistent); - } - } + if (modifiable_node) { + update_node_from_node_modification_entry(modifiable_node, E.value); } } // Cleanup the history of the changes. editor_history.cleanup_history(); - - _notify_scene_updated(current_edited_scene); } + + // For the whole editor, call the _notify_nodes_scene_reimported with a list of replaced nodes. + // To inform anything that depends on them that they should update as appropriate. + _notify_nodes_scene_reimported(this, replaced_nodes); + edited_scene_map.clear(); } editor_data.set_edited_scene(original_edited_scene_idx); @@ -6116,7 +6259,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) { @@ -6250,6 +6393,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(); @@ -6574,6 +6725,12 @@ EditorNode::EditorNode() { editor_layout_save_delay_timer->set_one_shot(true); editor_layout_save_delay_timer->connect("timeout", callable_mp(this, &EditorNode::_save_editor_layout)); + scan_changes_timer = memnew(Timer); + scan_changes_timer->set_wait_time(0.5); + scan_changes_timer->set_autostart(EDITOR_GET("interface/editor/import_resources_when_unfocused")); + scan_changes_timer->connect("timeout", callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan_changes)); + add_child(scan_changes_timer); + top_split = memnew(VSplitContainer); center_split->add_child(top_split); top_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); @@ -6598,7 +6755,7 @@ EditorNode::EditorNode() { distraction_free->set_tooltip_text(TTR("Toggle distraction-free mode.")); distraction_free->set_toggle_mode(true); scene_tabs->add_extra_button(distraction_free); - distraction_free->connect("pressed", callable_mp(this, &EditorNode::_toggle_distraction_free_mode)); + distraction_free->connect(SceneStringName(pressed), callable_mp(this, &EditorNode::_toggle_distraction_free_mode)); scene_root_parent = memnew(PanelContainer); scene_root_parent->set_custom_minimum_size(Size2(0, 80) * EDSCALE); @@ -6642,6 +6799,8 @@ EditorNode::EditorNode() { main_menu->set_menu_tooltip(0, TTR("Operations with scene files.")); accept = memnew(AcceptDialog); + accept->set_autowrap(true); + accept->set_min_size(Vector2i(600, 0)); accept->set_unparent_when_invisible(true); save_accept = memnew(AcceptDialog); @@ -6886,7 +7045,7 @@ EditorNode::EditorNode() { help_menu->add_icon_shortcut(theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons)), ED_GET_SHORTCUT("editor/editor_help"), HELP_SEARCH); help_menu->add_separator(); help_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/online_docs", TTR("Online Documentation")), HELP_DOCS); - help_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/q&a", TTR("Questions & Answers")), HELP_QA); + help_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/forum", TTR("Forum")), HELP_FORUM); help_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/community", TTR("Community")), HELP_COMMUNITY); help_menu->add_separator(); help_menu->add_icon_shortcut(theme->get_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/copy_system_info", TTR("Copy System Info")), HELP_COPY_SYSTEM_INFO); @@ -7002,22 +7161,22 @@ EditorNode::EditorNode() { history_dock = memnew(HistoryDock); // Scene: Top left. - editor_dock_manager->add_dock(SceneTreeDock::get_singleton(), TTR("Scene"), EditorDockManager::DOCK_SLOT_LEFT_UR); + editor_dock_manager->add_dock(SceneTreeDock::get_singleton(), TTR("Scene"), EditorDockManager::DOCK_SLOT_LEFT_UR, nullptr, "PackedScene"); // Import: Top left, behind Scene. - editor_dock_manager->add_dock(ImportDock::get_singleton(), TTR("Import"), EditorDockManager::DOCK_SLOT_LEFT_UR); + editor_dock_manager->add_dock(ImportDock::get_singleton(), TTR("Import"), EditorDockManager::DOCK_SLOT_LEFT_UR, nullptr, "FileAccess"); // FileSystem: Bottom left. - editor_dock_manager->add_dock(FileSystemDock::get_singleton(), TTR("FileSystem"), EditorDockManager::DOCK_SLOT_LEFT_BR, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_filesystem_bottom_panel", TTR("Toggle FileSystem Bottom Panel"), KeyModifierMask::ALT | Key::F)); + editor_dock_manager->add_dock(FileSystemDock::get_singleton(), TTR("FileSystem"), EditorDockManager::DOCK_SLOT_LEFT_BR, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_filesystem_bottom_panel", TTR("Toggle FileSystem Bottom Panel"), KeyModifierMask::ALT | Key::F), "Folder"); // Inspector: Full height right. - editor_dock_manager->add_dock(InspectorDock::get_singleton(), TTR("Inspector"), EditorDockManager::DOCK_SLOT_RIGHT_UL); + editor_dock_manager->add_dock(InspectorDock::get_singleton(), TTR("Inspector"), EditorDockManager::DOCK_SLOT_RIGHT_UL, nullptr, "AnimationTrackList"); // Node: Full height right, behind Inspector. - editor_dock_manager->add_dock(NodeDock::get_singleton(), TTR("Node"), EditorDockManager::DOCK_SLOT_RIGHT_UL); + editor_dock_manager->add_dock(NodeDock::get_singleton(), TTR("Node"), EditorDockManager::DOCK_SLOT_RIGHT_UL, nullptr, "Object"); // History: Full height right, behind Node. - editor_dock_manager->add_dock(history_dock, TTR("History"), EditorDockManager::DOCK_SLOT_RIGHT_UL); + editor_dock_manager->add_dock(history_dock, TTR("History"), EditorDockManager::DOCK_SLOT_RIGHT_UL, nullptr, "History"); // Add some offsets to left_r and main hsplits to make LEFT_R and RIGHT_L docks wider than minsize. left_r_hsplit->set_split_offset(270 * EDSCALE); @@ -7053,7 +7212,7 @@ EditorNode::EditorNode() { Button *output_button = bottom_panel->add_item(TTR("Output"), log, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_output_bottom_panel", TTR("Toggle Output Bottom Panel"), KeyModifierMask::ALT | Key::O)); log->set_tool_button(output_button); - center_split->connect("resized", callable_mp(this, &EditorNode::_vp_resized)); + center_split->connect(SceneStringName(resized), callable_mp(this, &EditorNode::_vp_resized)); native_shader_source_visualizer = memnew(EditorNativeShaderSourceVisualizer); gui_base->add_child(native_shader_source_visualizer); @@ -7079,7 +7238,7 @@ EditorNode::EditorNode() { gradle_build_manage_templates = memnew(ConfirmationDialog); gradle_build_manage_templates->set_text(TTR("Android build template is missing, please install relevant templates.")); gradle_build_manage_templates->set_ok_button_text(TTR("Manage Templates")); - gradle_build_manage_templates->add_button(TTR("Install from file"))->connect("pressed", callable_mp(this, &EditorNode::_menu_option).bind(SETTINGS_INSTALL_ANDROID_BUILD_TEMPLATE)); + gradle_build_manage_templates->add_button(TTR("Install from file"))->connect(SceneStringName(pressed), callable_mp(this, &EditorNode::_menu_option).bind(SETTINGS_INSTALL_ANDROID_BUILD_TEMPLATE)); gradle_build_manage_templates->connect("confirmed", callable_mp(this, &EditorNode::_menu_option).bind(SETTINGS_MANAGE_EXPORT_TEMPLATES)); gui_base->add_child(gradle_build_manage_templates); @@ -7165,6 +7324,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); @@ -7178,9 +7339,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)); } |