/*************************************************************************/ /* scene_tree_dock.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* 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. */ /*************************************************************************/ #include "scene_tree_dock.h" #include "core/config/project_settings.h" #include "core/input/input.h" #include "core/io/resource_saver.h" #include "core/object/message_queue.h" #include "core/os/keyboard.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_feature_profile.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/multi_node_edit.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/shader_create_dialog.h" #include "scene/main/window.h" #include "scene/property_utils.h" #include "scene/resources/packed_scene.h" #include "servers/display_server.h" #include "servers/rendering_server.h" #include "modules/modules_enabled.gen.h" // For regex. void SceneTreeDock::_nodes_drag_begin() { if (restore_script_editor_on_drag) { EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); restore_script_editor_on_drag = false; } } void SceneTreeDock::_quick_open() { instantiate_scenes(quick_open->get_selected_files(), scene_tree->get_selected()); } void SceneTreeDock::input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref mb = p_event; if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { restore_script_editor_on_drag = false; //lost chance } } void SceneTreeDock::unhandled_key_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); if (get_focus_owner() && get_focus_owner()->is_text_field()) { return; } if (!p_event->is_pressed() || p_event->is_echo()) { return; } if (ED_IS_SHORTCUT("scene_tree/rename", p_event)) { _tool_selected(TOOL_RENAME); #ifdef MODULE_REGEX_ENABLED } else if (ED_IS_SHORTCUT("scene_tree/batch_rename", p_event)) { _tool_selected(TOOL_BATCH_RENAME); #endif // MODULE_REGEX_ENABLED } else if (ED_IS_SHORTCUT("scene_tree/add_child_node", p_event)) { _tool_selected(TOOL_NEW); } else if (ED_IS_SHORTCUT("scene_tree/instance_scene", p_event)) { _tool_selected(TOOL_INSTANTIATE); } else if (ED_IS_SHORTCUT("scene_tree/expand_collapse_all", p_event)) { _tool_selected(TOOL_EXPAND_COLLAPSE); } else if (ED_IS_SHORTCUT("scene_tree/cut_node", p_event)) { _tool_selected(TOOL_CUT); } else if (ED_IS_SHORTCUT("scene_tree/copy_node", p_event)) { _tool_selected(TOOL_COPY); } else if (ED_IS_SHORTCUT("scene_tree/paste_node", p_event)) { _tool_selected(TOOL_PASTE); } else if (ED_IS_SHORTCUT("scene_tree/change_node_type", p_event)) { _tool_selected(TOOL_REPLACE); } else if (ED_IS_SHORTCUT("scene_tree/duplicate", p_event)) { _tool_selected(TOOL_DUPLICATE); } else if (ED_IS_SHORTCUT("scene_tree/attach_script", p_event)) { _tool_selected(TOOL_ATTACH_SCRIPT); } else if (ED_IS_SHORTCUT("scene_tree/detach_script", p_event)) { _tool_selected(TOOL_DETACH_SCRIPT); } else if (ED_IS_SHORTCUT("scene_tree/move_up", p_event)) { _tool_selected(TOOL_MOVE_UP); } else if (ED_IS_SHORTCUT("scene_tree/move_down", p_event)) { _tool_selected(TOOL_MOVE_DOWN); } else if (ED_IS_SHORTCUT("scene_tree/reparent", p_event)) { _tool_selected(TOOL_REPARENT); } else if (ED_IS_SHORTCUT("scene_tree/save_branch_as_scene", p_event)) { _tool_selected(TOOL_NEW_SCENE_FROM); } else if (ED_IS_SHORTCUT("scene_tree/delete_no_confirm", p_event)) { _tool_selected(TOOL_ERASE, true); } else if (ED_IS_SHORTCUT("scene_tree/copy_node_path", p_event)) { _tool_selected(TOOL_COPY_NODE_PATH); } else if (ED_IS_SHORTCUT("scene_tree/delete", p_event)) { _tool_selected(TOOL_ERASE); } else { return; } // Tool selection was successful, accept the event to stop propagation. accept_event(); } void SceneTreeDock::instantiate(const String &p_file) { Vector scenes; scenes.push_back(p_file); instantiate_scenes(scenes, scene_tree->get_selected()); } void SceneTreeDock::instantiate_scenes(const Vector &p_files, Node *p_parent) { Node *parent = p_parent; if (!parent) { parent = scene_tree->get_selected(); } if (!parent) { parent = edited_scene; } if (!parent) { if (p_files.size() == 1) { accept->set_text(TTR("No parent to instantiate a child at.")); } else { accept->set_text(TTR("No parent to instantiate the scenes at.")); } accept->popup_centered(); return; }; _perform_instantiate_scenes(p_files, parent, -1); } void SceneTreeDock::_perform_instantiate_scenes(const Vector &p_files, Node *parent, int p_pos) { ERR_FAIL_COND(!parent); Vector instances; bool error = false; for (int i = 0; i < p_files.size(); i++) { Ref sdata = ResourceLoader::load(p_files[i]); if (!sdata.is_valid()) { current_option = -1; accept->set_text(vformat(TTR("Error loading scene from %s"), p_files[i])); accept->popup_centered(); error = true; break; } Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instantiated_scene) { current_option = -1; accept->set_text(vformat(TTR("Error instancing scene from %s"), p_files[i])); accept->popup_centered(); error = true; break; } if (edited_scene->get_scene_file_path() != "") { if (_cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) { accept->set_text(vformat(TTR("Cannot instance the scene '%s' because the current scene exists within one of its nodes."), p_files[i])); accept->popup_centered(); error = true; break; } } instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_files[i])); instances.push_back(instantiated_scene); } if (error) { for (int i = 0; i < instances.size(); i++) { memdelete(instances[i]); } return; } editor_data->get_undo_redo().create_action(TTR("Instance Scene(s)")); for (int i = 0; i < instances.size(); i++) { Node *instantiated_scene = instances[i]; editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene, true); if (p_pos >= 0) { editor_data->get_undo_redo().add_do_method(parent, "move_child", instantiated_scene, p_pos + i); } editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", edited_scene); editor_data->get_undo_redo().add_do_method(editor_selection, "clear"); editor_data->get_undo_redo().add_do_method(editor_selection, "add_node", instantiated_scene); editor_data->get_undo_redo().add_do_reference(instantiated_scene); editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instantiated_scene); String new_name = parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", edited_scene->get_path_to(parent), p_files[i], new_name); editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).plus_file(new_name))); } editor_data->get_undo_redo().commit_action(); editor->push_item(instances[instances.size() - 1]); for (int i = 0; i < instances.size(); i++) { emit_signal(SNAME("node_created"), instances[i]); } } void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base) { Ref sdata = ResourceLoader::load(p_file); if (!sdata.is_valid()) { accept->set_text(vformat(TTR("Error loading scene from %s"), p_file)); accept->popup_centered(); return; } Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instantiated_scene) { accept->set_text(vformat(TTR("Error instancing scene from %s"), p_file)); accept->popup_centered(); return; } UndoRedo *undo_redo = editor->get_undo_redo(); undo_redo->create_action(TTR("Replace with Branch Scene")); Node *parent = base->get_parent(); int pos = base->get_index(); undo_redo->add_do_method(parent, "remove_child", base); undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); undo_redo->add_undo_method(parent, "add_child", base, true); undo_redo->add_do_method(parent, "move_child", instantiated_scene, pos); undo_redo->add_undo_method(parent, "move_child", base, pos); List owned; base->get_owned_by(base->get_owner(), &owned); Array owners; for (Node *F : owned) { owners.push_back(F); } undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene); undo_redo->add_undo_method(this, "_set_owners", edited_scene, owners); undo_redo->add_do_method(editor_selection, "clear"); undo_redo->add_undo_method(editor_selection, "clear"); undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene); undo_redo->add_undo_method(editor_selection, "add_node", base); undo_redo->add_do_property(scene_tree, "set_selected", instantiated_scene); undo_redo->add_undo_property(scene_tree, "set_selected", base); undo_redo->add_do_reference(instantiated_scene); undo_redo->add_undo_reference(base); undo_redo->commit_action(); } bool SceneTreeDock::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) { int childCount = p_desired_node->get_child_count(); if (_track_inherit(p_target_scene_path, p_desired_node)) { return true; } for (int i = 0; i < childCount; i++) { Node *child = p_desired_node->get_child(i); if (_cyclical_dependency_exists(p_target_scene_path, child)) { return true; } } return false; } bool SceneTreeDock::_track_inherit(const String &p_target_scene_path, Node *p_desired_node) { Node *p = p_desired_node; bool result = false; Vector instances; while (true) { if (p->get_scene_file_path() == p_target_scene_path) { result = true; break; } Ref ss = p->get_scene_inherited_state(); if (ss.is_valid()) { String path = ss->get_path(); Ref data = ResourceLoader::load(path); if (data.is_valid()) { p = data->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!p) { continue; } instances.push_back(p); } else { break; } } else { break; } } for (int i = 0; i < instances.size(); i++) { memdelete(instances[i]); } return result; } void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { current_option = p_tool; switch (p_tool) { #ifdef MODULE_REGEX_ENABLED case TOOL_BATCH_RENAME: { if (!profile_allow_editing) { break; } if (editor_selection->get_selected_node_list().size() > 1) { rename_dialog->popup_centered(); } } break; #endif // MODULE_REGEX_ENABLED case TOOL_RENAME: { if (!profile_allow_editing) { break; } Tree *tree = scene_tree->get_scene_tree(); if (tree->is_anything_selected()) { tree->grab_focus(); tree->edit_selected(); } } break; case TOOL_NEW: case TOOL_REPARENT_TO_NEW_NODE: { if (!profile_allow_editing) { break; } if (reset_create_dialog && !p_confirm_override) { create_dialog->set_base_type("Node"); reset_create_dialog = false; } // Prefer nodes that inherit from the current scene root. Node *current_edited_scene_root = EditorNode::get_singleton()->get_edited_scene(); if (current_edited_scene_root) { String root_class = current_edited_scene_root->get_class_name(); static Vector preferred_types; if (preferred_types.is_empty()) { preferred_types.push_back("Control"); preferred_types.push_back("Node2D"); preferred_types.push_back("Node3D"); } for (int i = 0; i < preferred_types.size(); i++) { if (ClassDB::is_parent_class(root_class, preferred_types[i])) { create_dialog->set_preferred_search_result_type(preferred_types[i]); break; } } } create_dialog->popup_create(true); if (!p_confirm_override) { emit_signal(SNAME("add_node_used")); } } break; case TOOL_INSTANTIATE: { if (!profile_allow_editing) { break; } Node *scene = edited_scene; if (!scene) { EditorNode::get_singleton()->new_inherited_scene(); break; } quick_open->popup_dialog("PackedScene", true); quick_open->set_title(TTR("Instantiate Child Scene")); if (!p_confirm_override) { emit_signal(SNAME("add_node_used")); } } break; case TOOL_EXPAND_COLLAPSE: { Tree *tree = scene_tree->get_scene_tree(); TreeItem *selected_item = tree->get_selected(); if (!selected_item) { selected_item = tree->get_root(); } bool collapsed = _is_collapsed_recursive(selected_item); _set_collapsed_recursive(selected_item, !collapsed); tree->ensure_cursor_is_visible(); } break; case TOOL_CUT: case TOOL_COPY: { if (!edited_scene || !_validate_no_foreign()) { break; } List selection = editor_selection->get_selected_node_list(); if (selection.size() == 0) { break; } if (!node_clipboard.is_empty()) { _clear_clipboard(); } clipboard_source_scene = editor->get_edited_scene()->get_scene_file_path(); selection.sort_custom(); for (Node *node : selection) { Map duplimap; Node *dup = node->duplicate_from_editor(duplimap); ERR_CONTINUE(!dup); node_clipboard.push_back(dup); } if (p_tool == TOOL_CUT) { _delete_confirm(true); } } break; case TOOL_PASTE: { if (node_clipboard.is_empty() || !edited_scene) { break; } bool has_cycle = false; if (edited_scene->get_scene_file_path() != String()) { for (Node *E : node_clipboard) { if (edited_scene->get_scene_file_path() == E->get_scene_file_path()) { has_cycle = true; break; } } } if (has_cycle) { current_option = -1; accept->set_text(TTR("Can't paste root node into the same scene.")); accept->popup_centered(); break; } Node *paste_parent = edited_scene; List selection = editor_selection->get_selected_node_list(); if (selection.size() > 0) { paste_parent = selection.back()->get(); } Node *owner = paste_parent->get_owner(); if (!owner) { owner = paste_parent; } editor_data->get_undo_redo().create_action(TTR("Paste Node(s)")); editor_data->get_undo_redo().add_do_method(editor_selection, "clear"); Map resource_remap; String target_scene = editor->get_edited_scene()->get_scene_file_path(); if (target_scene != clipboard_source_scene) { if (!clipboard_resource_remap.has(target_scene)) { Map remap; for (Node *E : node_clipboard) { _create_remap_for_node(E, remap); } clipboard_resource_remap[target_scene] = remap; } resource_remap = clipboard_resource_remap[target_scene]; } for (Node *node : node_clipboard) { Map duplimap; Node *dup = node->duplicate_from_editor(duplimap, resource_remap); ERR_CONTINUE(!dup); editor_data->get_undo_redo().add_do_method(paste_parent, "add_child", dup, true); for (KeyValue &E2 : duplimap) { Node *d = E2.value; editor_data->get_undo_redo().add_do_method(d, "set_owner", owner); } editor_data->get_undo_redo().add_do_method(dup, "set_owner", owner); editor_data->get_undo_redo().add_do_method(editor_selection, "add_node", dup); editor_data->get_undo_redo().add_undo_method(paste_parent, "remove_child", dup); editor_data->get_undo_redo().add_do_reference(dup); if (node_clipboard.size() == 1) { editor_data->get_undo_redo().add_do_method(editor, "push_item", dup); } } editor_data->get_undo_redo().commit_action(); } break; case TOOL_REPLACE: { if (!profile_allow_editing) { break; } if (!_validate_no_foreign()) { break; } if (!_validate_no_instance()) { break; } if (reset_create_dialog) { create_dialog->set_base_type("Node"); reset_create_dialog = false; } Node *selected = scene_tree->get_selected(); if (!selected && !editor_selection->get_selected_node_list().is_empty()) { selected = editor_selection->get_selected_node_list().front()->get(); } if (selected) { create_dialog->popup_create(false, true, selected->get_class()); } } break; case TOOL_EXTEND_SCRIPT: { attach_script_to_selected(true); } break; case TOOL_ATTACH_SCRIPT: { attach_script_to_selected(false); } break; case TOOL_DETACH_SCRIPT: { if (!profile_allow_script_editing) { break; } Array selection = editor_selection->get_selected_nodes(); if (selection.is_empty()) { return; } editor_data->get_undo_redo().create_action(TTR("Detach Script")); editor_data->get_undo_redo().add_do_method(editor, "push_item", (Script *)nullptr); for (int i = 0; i < selection.size(); i++) { Node *n = Object::cast_to(selection[i]); Ref