/**************************************************************************/ /* scene_tree_dock.cpp */ /**************************************************************************/ /* 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. */ /**************************************************************************/ #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/class_db.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_paths.h" #include "editor/editor_quick_open.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" #include "editor/gui/editor_file_dialog.h" #include "editor/inspector_dock.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/reparent_dialog.h" #include "editor/shader_create_dialog.h" #include "scene/gui/check_box.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. #ifdef MODULE_REGEX_ENABLED #include "editor/rename_dialog.h" #endif // MODULE_REGEX_ENABLED void SceneTreeDock::_nodes_drag_begin() { pending_click_select = nullptr; } 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->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT)) { if (mb->is_pressed() && scene_tree->get_rect().has_point(scene_tree->get_local_mouse_position())) { tree_clicked = true; } else if (!mb->is_pressed()) { tree_clicked = false; } if (!mb->is_pressed() && pending_click_select) { _push_item(pending_click_select); pending_click_select = nullptr; } } } void SceneTreeDock::shortcut_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_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/instantiate_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/paste_node_as_sibling", p_event)) { _tool_selected(TOOL_PASTE_AS_SIBLING); } 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/reparent_to_new_node", p_event)) { _tool_selected(TOOL_REPARENT_TO_NEW_NODE); } 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/toggle_unique_name", p_event)) { _tool_selected(TOOL_TOGGLE_SCENE_UNIQUE_NAME); } 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_NULL(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 instantiating scene from %s"), p_files[i])); accept->popup_centered(); error = true; break; } if (!edited_scene->get_scene_file_path().is_empty()) { if (_cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) { accept->set_text(vformat(TTR("Cannot instantiate 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; } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Instantiate Scene(s)")); for (int i = 0; i < instances.size(); i++) { Node *instantiated_scene = instances[i]; undo_redo->add_do_method(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(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); String new_name = 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->commit_action(); _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) { // `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."); 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 instantiating scene from %s"), p_file)); accept->popup_centered(); return; } instantiated_scene->set_unique_name_in_owner(base->is_unique_name_in_owner()); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Replace with Branch Scene")); Node *parent = base->get_parent(); int pos = base->get_index(false); 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 pack_data = ResourceLoader::load(path); if (pack_data.is_valid()) { p = pack_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_REPARENT_TO_NEW_NODE: if (!_validate_no_foreign()) { break; } [[fallthrough]]; case TOOL_NEW: { 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(); if (!selected_item) { break; } } bool collapsed = selected_item->is_any_collapsed(); selected_item->set_collapsed_recursive(!collapsed); tree->ensure_cursor_is_visible(); } break; case TOOL_CUT: case TOOL_COPY: { if (!edited_scene || (p_tool == TOOL_CUT && !_validate_no_foreign())) { break; } List selection = editor_selection->get_selected_node_list(); if (selection.size() == 0) { break; } bool was_empty = false; if (!node_clipboard.is_empty()) { _clear_clipboard(); } else { was_empty = true; } clipboard_source_scene = EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path(); selection.sort_custom(); for (Node *node : selection) { HashMap duplimap; Node *dup = node->duplicate_from_editor(duplimap); ERR_CONTINUE(!dup); // Preserve ownership relations ready for pasting. List owned; node->get_owned_by(node->get_owner() ? node->get_owner() : node, &owned); for (Node *F : owned) { if (!duplimap.has(F) || F == node) { continue; } Node *d = duplimap[F]; // Only use this as a marker that ownership needs to be assigned when pasting. // The actual owner doesn't matter. d->set_owner(dup); } node_clipboard.push_back(dup); } if (p_tool == TOOL_CUT) { _delete_confirm(true); } if (was_empty) { _update_create_root_dialog(); } } break; case TOOL_PASTE: { paste_nodes(false); } break; case TOOL_PASTE_AS_SIBLING: { paste_nodes(true); } 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(), selected->get_name()); } } 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; } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Detach Script"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); undo_redo->add_do_method(EditorNode::get_singleton(), "push_item", (Script *)nullptr); for (int i = 0; i < selection.size(); i++) { Node *n = Object::cast_to(selection[i]); Ref