diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 968cd5ab2330..96166dab3feb 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -53,6 +53,7 @@ #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" @@ -850,9 +851,10 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { break; } - if (p_confirm_override) { + bool allow_ask_delete_tracks = EDITOR_GET("docks/scene_tree/ask_before_deleting_related_animation_tracks").operator bool(); + bool has_tracks_to_delete = allow_ask_delete_tracks && _has_tracks_to_delete(edited_scene, remove_list); + if (p_confirm_override && !has_tracks_to_delete) { _delete_confirm(); - } else { String msg; if (remove_list.size() > 1) { @@ -863,18 +865,30 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { msg = vformat(any_children ? TTR("Delete %d nodes and any children?") : TTR("Delete %d nodes?"), remove_list.size()); } else { - Node *node = remove_list[0]; - if (node == editor_data->get_edited_scene_root()) { - msg = vformat(TTR("Delete the root node \"%s\"?"), node->get_name()); - } else if (node->get_scene_file_path().is_empty() && node->get_child_count() > 0) { - // Display this message only for non-instantiated scenes - msg = vformat(TTR("Delete node \"%s\" and its children?"), node->get_name()); + if (!p_confirm_override) { + Node *node = remove_list[0]; + if (node == editor_data->get_edited_scene_root()) { + msg = vformat(TTR("Delete the root node \"%s\"?"), node->get_name()); + } else if (node->get_scene_file_path().is_empty() && node->get_child_count() > 0) { + // Display this message only for non-instantiated scenes + msg = vformat(TTR("Delete node \"%s\" and its children?"), node->get_name()); + } else { + msg = vformat(TTR("Delete node \"%s\"?"), node->get_name()); + } + } + + if (has_tracks_to_delete) { + if (!msg.is_empty()) { + msg += "\n"; + } + msg += TTR("Some nodes are referenced by animation tracks."); + delete_tracks_checkbox->show(); } else { - msg = vformat(TTR("Delete node \"%s\"?"), node->get_name()); + delete_tracks_checkbox->hide(); } } - delete_dialog->set_text(msg); + delete_dialog_label->set_text(msg); // Resize the dialog to its minimum size. // This prevents the dialog from being too wide after displaying @@ -1496,12 +1510,10 @@ void SceneTreeDock::_set_owners(Node *p_owner, const Array &p_nodes) { void SceneTreeDock::_fill_path_renames(Vector base_path, Vector new_base_path, Node *p_node, HashMap *p_renames) { base_path.push_back(p_node->get_name()); - if (new_base_path.size()) { - new_base_path.push_back(p_node->get_name()); - } NodePath new_path; - if (new_base_path.size()) { + if (!new_base_path.is_empty()) { + new_base_path.push_back(p_node->get_name()); new_path = NodePath(new_base_path, true); } @@ -1512,6 +1524,43 @@ void SceneTreeDock::_fill_path_renames(Vector base_path, Vector &p_to_delete) const { + AnimationPlayer *ap = Object::cast_to(p_node); + if (ap) { + Node *root = ap->get_node(ap->get_root()); + if (root && !p_to_delete.find(root)) { + List anims; + ap->get_animation_list(&anims); + + for (const StringName &E : anims) { + Ref anim = ap->get_animation(E); + if (anim.is_null()) { + continue; + } + + for (int i = 0; i < anim->get_track_count(); i++) { + NodePath track_np = anim->track_get_path(i); + Node *n = root->get_node_or_null(track_np); + if (n) { + for (const Node *F : p_to_delete) { + if (F == n || F->is_ancestor_of(n)) { + return true; + } + } + } + } + } + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + if (_has_tracks_to_delete(p_node->get_child(i), p_to_delete)) { + return true; + } + } + return false; +} + void SceneTreeDock::fill_path_renames(Node *p_node, Node *p_new_parent, HashMap *p_renames) { Vector base_path; Node *n = p_node->get_parent(); @@ -1701,7 +1750,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, HashMap HashMap::Iterator found_path = p_renames->find(n); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (found_path) { - if (found_path->value == NodePath()) { + if (found_path->value.is_empty()) { //will be erased int idx = 0; @@ -2094,11 +2143,6 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { return; } - EditorNode::get_singleton()->hide_unused_editors(this); - - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(p_cut ? TTR("Cut Node(s)") : TTR("Remove Node(s)"), UndoRedo::MERGE_DISABLE, remove_list.front()->get()); - bool entire_scene = false; for (const Node *E : remove_list) { @@ -2108,27 +2152,34 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { } } + EditorNode::get_singleton()->hide_unused_editors(this); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(p_cut ? TTR("Cut Node(s)") : TTR("Remove Node(s)"), UndoRedo::MERGE_DISABLE, remove_list.front()->get()); + if (entire_scene) { undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr); undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", edited_scene); undo_redo->add_undo_method(edited_scene, "set_owner", edited_scene->get_owner()); undo_redo->add_undo_method(scene_tree, "update_tree"); undo_redo->add_undo_reference(edited_scene); - } else { - remove_list.sort_custom(); //sort nodes to keep positions - HashMap path_renames; + if (delete_tracks_checkbox->is_pressed() || p_cut) { + remove_list.sort_custom(); // Sort nodes to keep positions. + HashMap path_renames; - //delete from animation - for (Node *n : remove_list) { - if (!n->is_inside_tree() || !n->get_parent()) { - continue; + //delete from animation + for (Node *n : remove_list) { + if (!n->is_inside_tree() || !n->get_parent()) { + continue; + } + + fill_path_renames(n, nullptr, &path_renames); } - fill_path_renames(n, nullptr, &path_renames); + perform_node_renames(nullptr, &path_renames); } - perform_node_renames(nullptr, &path_renames); //delete for read for (Node *n : remove_list) { if (!n->is_inside_tree() || !n->get_parent()) { @@ -3774,6 +3825,16 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec add_child(delete_dialog); delete_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_delete_confirm).bind(false)); + VBoxContainer *vb = memnew(VBoxContainer); + delete_dialog->add_child(vb); + + delete_dialog_label = memnew(Label); + vb->add_child(delete_dialog_label); + + delete_tracks_checkbox = memnew(CheckBox(TTR("Delete Related Animation Tracks"))); + delete_tracks_checkbox->set_pressed(true); + vb->add_child(delete_tracks_checkbox); + editable_instance_remove_dialog = memnew(ConfirmationDialog); add_child(editable_instance_remove_dialog); editable_instance_remove_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_toggle_editable_children_from_selection)); @@ -3810,6 +3871,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec EDITOR_DEF("interface/editors/show_scene_tree_root_selection", true); EDITOR_DEF("interface/editors/derive_script_globals_by_name", true); + EDITOR_DEF("docks/scene_tree/ask_before_deleting_related_animation_tracks", true); EDITOR_DEF("_use_favorites_root_selection", false); Resource::_update_configuration_warning = _update_configuration_warning; diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 75acb3797355..e8a66863864f 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -36,6 +36,7 @@ #include "scene/gui/box_container.h" #include "scene/resources/animation.h" +class CheckBox; class EditorData; class EditorSelection; class EditorQuickOpen; @@ -148,6 +149,8 @@ class SceneTreeDock : public VBoxContainer { ShaderCreateDialog *shader_create_dialog = nullptr; AcceptDialog *accept = nullptr; ConfirmationDialog *delete_dialog = nullptr; + Label *delete_dialog_label = nullptr; + CheckBox *delete_tracks_checkbox = nullptr; ConfirmationDialog *editable_instance_remove_dialog = nullptr; ConfirmationDialog *placeholder_editable_instance_remove_dialog = nullptr; @@ -213,6 +216,7 @@ class SceneTreeDock : public VBoxContainer { void _shader_creation_closed(); void _delete_confirm(bool p_cut = false); + void _delete_dialog_closed(); void _toggle_editable_children_from_selection(); void _toggle_editable_children(Node *p_node); @@ -234,6 +238,7 @@ class SceneTreeDock : public VBoxContainer { void _update_script_button(); void _fill_path_renames(Vector base_path, Vector new_base_path, Node *p_node, HashMap *p_renames); + bool _has_tracks_to_delete(Node *p_node, List &p_to_delete) const; void _normalize_drop(Node *&to_node, int &to_pos, int p_type);