From f37446c610bbd2c1eddac2db3d004c366071200e Mon Sep 17 00:00:00 2001 From: Dexter Date: Thu, 20 Nov 2025 17:56:33 +0330 Subject: [PATCH] Add Replace to SceneTree context menu --- editor/docks/scene_tree_dock.cpp | 177 +++++++++++++++++++++++++------ editor/docks/scene_tree_dock.h | 4 +- 2 files changed, 147 insertions(+), 34 deletions(-) diff --git a/editor/docks/scene_tree_dock.cpp b/editor/docks/scene_tree_dock.cpp index 156d9f7677..c12b84506f 100644 --- a/editor/docks/scene_tree_dock.cpp +++ b/editor/docks/scene_tree_dock.cpp @@ -207,8 +207,10 @@ void SceneTreeDock::shortcut_input(const Ref &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/paste_node_as_replacement", p_event)) { + _tool_selected(TOOL_PASTE_AS_REPLACEMENT); } else if (ED_IS_SHORTCUT("scene_tree/change_node_type", p_event)) { - _tool_selected(TOOL_REPLACE); + _tool_selected(TOOL_CHANGE_TYPE); } 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)) { @@ -753,7 +755,22 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { case TOOL_PASTE_AS_SIBLING: { paste_nodes(true); } break; - case TOOL_REPLACE: { + case TOOL_PASTE_AS_REPLACEMENT: { + if (!profile_allow_editing) { + break; + } + + if (node_clipboard.is_empty()) { + break; + } + + if (!_validate_no_foreign()) { + break; + } + + paste_node_as_replacement(); + } break; + case TOOL_CHANGE_TYPE: { if (!profile_allow_editing) { break; } @@ -2353,7 +2370,7 @@ bool SceneTreeDock::_validate_no_foreign() { // When edited_scene inherits from another one the root Node will be the parent Scene, // we don't want to consider that Node a foreign one otherwise we would not be able to // delete it. - if (edited_scene == E && current_option != TOOL_REPLACE) { + if (edited_scene == E && current_option != TOOL_CHANGE_TYPE) { continue; } @@ -3073,7 +3090,7 @@ void SceneTreeDock::_create() { _do_create(parent); - } else if (current_option == TOOL_REPLACE) { + } else if (current_option == TOOL_CHANGE_TYPE) { const List selection = editor_selection->get_top_selected_node_list(); ERR_FAIL_COND(selection.is_empty()); @@ -3895,15 +3912,48 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { END_SECTION() } + bool can_rename = true; + bool can_replace = true; + if (profile_allow_editing) { + for (Node *E : selection) { + if (E != edited_scene && (E->get_owner() != edited_scene || E->is_instance())) { + can_replace = false; + if (!E->is_instance()) { + can_rename = false; + } + } + + if (edited_scene->get_scene_inherited_state().is_valid()) { + if (E == edited_scene || edited_scene->get_scene_inherited_state()->find_node_by_path(edited_scene->get_path_to(E)) >= 0) { + can_replace = false; + can_rename = false; + } + } + + if (!can_rename && !can_replace) { + break; + } + } + BEGIN_SECTION() menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionCut")), ED_GET_SHORTCUT("scene_tree/cut_node"), TOOL_CUT); menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionCopy")), ED_GET_SHORTCUT("scene_tree/copy_node"), TOOL_COPY); - if (selection.size() == 1 && !node_clipboard.is_empty()) { - menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionPaste")), ED_GET_SHORTCUT("scene_tree/paste_node"), TOOL_PASTE); - menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionPaste")), ED_GET_SHORTCUT("scene_tree/paste_node_as_sibling"), TOOL_PASTE_AS_SIBLING); - if (selection.front()->get() == edited_scene) { - menu->set_item_disabled(-1, true); + if (!node_clipboard.is_empty()) { + if (selection.size() == 1) { + menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionPaste")), ED_GET_SHORTCUT("scene_tree/paste_node"), TOOL_PASTE); + menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/paste_node_as_sibling"), TOOL_PASTE_AS_SIBLING); + if (selection.front()->get() == edited_scene) { + menu->set_item_disabled(-1, true); + } + } + if (selection.size() >= 1) { + if (can_rename || can_replace) { + menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/paste_node_as_replacement"), TOOL_PASTE_AS_REPLACEMENT); + } + if (selection.front()->get() == edited_scene) { + menu->set_item_disabled(-1, true); + } } } END_SECTION() @@ -3941,36 +3991,13 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { } if (profile_allow_editing) { - bool can_rename = true; - bool can_replace = true; - - for (Node *E : selection) { - if (E != edited_scene && (E->get_owner() != edited_scene || E->is_instance())) { - can_replace = false; - if (!E->is_instance()) { - can_rename = false; - } - } - - if (edited_scene->get_scene_inherited_state().is_valid()) { - if (E == edited_scene || edited_scene->get_scene_inherited_state()->find_node_by_path(edited_scene->get_path_to(E)) >= 0) { - can_replace = false; - can_rename = false; - } - } - - if (!can_rename && !can_replace) { - break; - } - } - if (can_rename || can_replace) { BEGIN_SECTION() if (can_rename) { menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Rename")), ED_GET_SHORTCUT("scene_tree/rename"), TOOL_RENAME); } if (can_replace) { - menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Reload")), ED_GET_SHORTCUT("scene_tree/change_node_type"), TOOL_REPLACE); + menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Reload")), ED_GET_SHORTCUT("scene_tree/change_node_type"), TOOL_CHANGE_TYPE); } END_SECTION() } @@ -4464,6 +4491,89 @@ List SceneTreeDock::paste_nodes(bool p_paste_as_sibling) { return pasted_nodes; } +void SceneTreeDock::paste_node_as_replacement() { + List selected_node_list = editor_selection->get_top_selected_node_list(); + for (Node *selected : selected_node_list) { + Node *clipboard_node = node_clipboard.front()->get(); + HashMap duplimap; + Node *new_node = clipboard_node->duplicate_from_editor(duplimap); + if (!new_node) { + continue; + } + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + String old_scene_file_path = selected->get_scene_file_path(); + String new_scene_file_path = clipboard_node->get_scene_file_path(); + + bool old_is_scene = !old_scene_file_path.is_empty(); + bool new_is_scene = !new_scene_file_path.is_empty(); + + LocalVector old_children; + + ur->create_action(TTR("Paste Node(s) as Replacement"), UndoRedo::MERGE_ALL, selected); + + if (old_is_scene) { + for (int i = 0; i < selected->get_child_count(); i++) { + Node *child = selected->get_child(i); + if (child->get_owner() == selected) { + ur->add_do_method(selected, "remove_child", child); + + selected->remove_child(child); + old_children.push_back(child); + } + } + } + + ur->add_do_method(this, "replace_node", selected, new_node, false); + ur->add_do_method(new_node, "set_scene_file_path", new_scene_file_path); + ur->add_do_reference(new_node); + + if (new_is_scene) { + for (int i = 0; i < new_node->get_child_count(); i++) { + Node *child = new_node->get_child(i); + if (child->get_owner() == new_node) { + ur->add_undo_method(new_node, "add_child", child, true); + ur->add_undo_method(child, "set_owner", new_node); + ur->add_undo_reference(child); + } + } + } + + Variant selected_pos; + if (selected->has_method("get_position")) { + selected_pos = selected->call("get_position"); + } + _replace_node(selected, new_node, false); + new_node->set_scene_file_path(new_scene_file_path); + + if (new_node->has_method("set_position")) { + new_node->call("set_position", selected_pos); + } + + if (new_is_scene) { + for (int i = 0; i < new_node->get_child_count(); i++) { + Node *child = new_node->get_child(i); + if (child->get_owner() == new_node) { + ur->add_undo_method(new_node, "remove_child", child); + } + } + } + + ur->add_undo_method(this, "replace_node", new_node, selected, false); + ur->add_undo_method(selected, "set_scene_file_path", old_scene_file_path); + ur->add_undo_reference(selected); + + if (old_is_scene) { + for (Node *child : old_children) { + ur->add_undo_method(selected, "add_child", child, true); + ur->add_undo_method(child, "set_owner", selected); + ur->add_undo_reference(child); + } + } + + ur->commit_action(false); + } +} + List SceneTreeDock::get_node_clipboard() const { return node_clipboard; } @@ -4812,6 +4922,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec ED_SHORTCUT("scene_tree/copy_node", TTRC("Copy"), KeyModifierMask::CMD_OR_CTRL | Key::C); ED_SHORTCUT("scene_tree/paste_node", TTRC("Paste"), KeyModifierMask::CMD_OR_CTRL | Key::V); ED_SHORTCUT("scene_tree/paste_node_as_sibling", TTRC("Paste as Sibling"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::V); + ED_SHORTCUT("scene_tree/paste_node_as_replacement", TTRC("Paste as Replacement"), KeyModifierMask::ALT | Key::V); ED_SHORTCUT("scene_tree/change_node_type", TTRC("Change Type...")); ED_SHORTCUT("scene_tree/attach_script", TTRC("Attach Script...")); ED_SHORTCUT("scene_tree/extend_script", TTRC("Extend Script...")); diff --git a/editor/docks/scene_tree_dock.h b/editor/docks/scene_tree_dock.h index a5e4a2af25..e5ad32ae92 100644 --- a/editor/docks/scene_tree_dock.h +++ b/editor/docks/scene_tree_dock.h @@ -59,9 +59,10 @@ class SceneTreeDock : public EditorDock { TOOL_COPY, TOOL_PASTE, TOOL_PASTE_AS_SIBLING, + TOOL_PASTE_AS_REPLACEMENT, TOOL_RENAME, TOOL_BATCH_RENAME, - TOOL_REPLACE, + TOOL_CHANGE_TYPE, TOOL_EXTEND_SCRIPT, TOOL_ATTACH_SCRIPT, TOOL_DETACH_SCRIPT, @@ -356,6 +357,7 @@ public: void open_instance_child_dialog(); List paste_nodes(bool p_paste_as_sibling = false); + void paste_node_as_replacement(); List get_node_clipboard() const; ScriptCreateDialog *get_script_create_dialog() {