/*************************************************************************/ /* scene_tree_dock.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2022 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/array.h" #include "core/class_db.h" #include "core/dictionary.h" #include "core/error_list.h" #include "core/error_macros.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/math/vector2.h" #include "core/os/file_access.h" #include "core/os/input.h" #include "core/os/input_event.h" #include "core/os/keyboard.h" #include "core/os/memory.h" #include "core/os/os.h" #include "core/project_settings.h" #include "core/ref_ptr.h" #include "core/script_language.h" #include "core/string_name.h" #include "core/typedefs.h" #include "core/undo_redo.h" #include "core/variant.h" #include "editor/animation_track_editor.h" #include "editor/create_dialog.h" #include "editor/editor_data.h" #include "editor/editor_file_dialog.h" #include "editor/editor_file_system.h" #include "editor/editor_inspector.h" #include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_sub_scene.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/script_editor_plugin.h" #include "editor/plugins/spatial_editor_plugin.h" #include "editor/quick_open.h" #include "editor/rename_dialog.h" #include "editor/reparent_dialog.h" #include "editor/scene_tree_editor.h" #include "editor/script_create_dialog.h" #include "editor/script_editor_debugger.h" #include "modules/modules_enabled.gen.h" // For regex. #include "scene/2d/canvas_item.h" #include "scene/2d/node_2d.h" #include "scene/3d/spatial.h" #include "scene/animation/animation_player.h" #include "scene/gui/base_button.h" #include "scene/gui/button.h" #include "scene/gui/control.h" #include "scene/gui/dialogs.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/popup_menu.h" #include "scene/gui/scroll_container.h" #include "scene/gui/tool_button.h" #include "scene/gui/tree.h" #include "scene/main/node.h" #include "scene/main/scene_tree.h" #include "scene/main/viewport.h" #include "scene/property_utils.h" #include "scene/resources/animation.h" #include "scene/resources/packed_scene.h" #include "scene/resources/texture.h" void SceneTreeDock::_nodes_drag_begin() { pending_click_select = nullptr; } void SceneTreeDock::_quick_open() { instance_scenes(quick_open->get_selected_files(), scene_tree->get_selected()); } void SceneTreeDock::_input(Ref p_event) { Ref mb = p_event; if (pending_click_select && mb.is_valid() && !mb->is_pressed() && (mb->get_button_index() == BUTTON_LEFT || mb->get_button_index() == BUTTON_RIGHT)) { _push_item(pending_click_select); pending_click_select = nullptr; } } void SceneTreeDock::_unhandled_key_input(Ref p_event) { ERR_FAIL_COND(p_event.is_null()); if (get_viewport()->get_modal_stack_top()) { return; //ignore because of modal window } 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_INSTANCE); } 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/merge_from_scene", p_event)) { _tool_selected(TOOL_MERGE_FROM_SCENE); } 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); } } void SceneTreeDock::instance(const String &p_file) { Vector scenes; scenes.push_back(p_file); instance_scenes(scenes, scene_tree->get_selected()); } void SceneTreeDock::instance_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 instance a child at.")); } else { accept->set_text(TTR("No parent to instance the scenes at.")); } accept->popup_centered_minsize(); return; }; _perform_instance_scenes(p_files, parent, -1); } void SceneTreeDock::_perform_instance_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_minsize(); error = true; break; } Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instanced_scene) { current_option = -1; accept->set_text(vformat(TTR("Error instancing scene from %s"), p_files[i])); accept->popup_centered_minsize(); error = true; break; } if (edited_scene->get_filename() != "") { if (_cyclical_dependency_exists(edited_scene->get_filename(), instanced_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_minsize(); error = true; break; } } instanced_scene->set_filename(ProjectSettings::get_singleton()->localize_path(p_files[i])); instances.push_back(instanced_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 *instanced_scene = instances[i]; editor_data->get_undo_redo().add_do_method(parent, "add_child", instanced_scene); if (p_pos >= 0) { editor_data->get_undo_redo().add_do_method(parent, "move_child", instanced_scene, p_pos + i); } editor_data->get_undo_redo().add_do_method(instanced_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", instanced_scene); editor_data->get_undo_redo().add_do_reference(instanced_scene); editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene); String new_name = parent->validate_child_name(instanced_scene); ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); editor_data->get_undo_redo().add_do_method(sed, "live_debug_instance_node", edited_scene->get_path_to(parent), p_files[i], new_name); editor_data->get_undo_redo().add_undo_method(sed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).plus_file(new_name))); } editor_data->get_undo_redo().commit_action(); _push_item(instances[instances.size() - 1]); for (int i = 0; i < instances.size(); i++) { emit_signal("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_minsize(); return; } Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instanced_scene) { accept->set_text(vformat(TTR("Error instancing scene from %s"), p_file)); accept->popup_centered_minsize(); 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", instanced_scene); undo_redo->add_do_method(parent, "add_child", instanced_scene); undo_redo->add_undo_method(parent, "add_child", base); undo_redo->add_do_method(parent, "move_child", instanced_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 (List::Element *F = owned.front(); F; F = F->next()) { owners.push_back(F->get()); } undo_redo->add_do_method(instanced_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", instanced_scene); undo_redo->add_undo_method(editor_selection, "add_node", base); undo_redo->add_do_property(scene_tree, "set_selected", instanced_scene); undo_redo->add_undo_property(scene_tree, "set_selected", base); undo_redo->add_do_reference(instanced_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_filename() == 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->instance(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.empty()) { preferred_types.push_back("Control"); preferred_types.push_back("Node2D"); preferred_types.push_back("Spatial"); } 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("add_node_used"); } } break; case TOOL_INSTANCE: { 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("Instance Child Scene")); if (!p_confirm_override) { emit_signal("add_node_used"); } } break; case TOOL_EXPAND_COLLAPSE: { if (!scene_tree->get_selected()) { break; } 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 || (p_tool == TOOL_CUT && !_validate_no_foreign())) { break; } List selection = editor_selection->get_selected_node_list(); if (selection.size() == 0) { break; } if (!node_clipboard.empty()) { _clear_clipboard(); } clipboard_source_scene = editor->get_edited_scene()->get_filename(); selection.sort_custom(); for (List::Element *E = selection.front(); E; E = E->next()) { Node *node = E->get(); 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.empty() || !edited_scene) { break; } bool has_cycle = false; if (edited_scene->get_filename() != String()) { for (List::Element *E = node_clipboard.front(); E; E = E->next()) { if (edited_scene->get_filename() == E->get()->get_filename()) { 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_filename(); if (target_scene != clipboard_source_scene) { if (!clipboard_resource_remap.has(target_scene)) { Map remap; for (List::Element *E = node_clipboard.front(); E; E = E->next()) { _create_remap_for_node(E->get(), remap); } clipboard_resource_remap[target_scene] = remap; } resource_remap = clipboard_resource_remap[target_scene]; } for (List::Element *E = node_clipboard.front(); E; E = E->next()) { Node *node = E->get(); 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); for (Map::Element *E2 = duplimap.front(); E2; E2 = E2->next()) { 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().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.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