浏览代码

Merge pull request #34892 from KoBeWi/copy-pasta_v7

Yet another node copy-paste PR
Rémi Verschelde 4 年之前
父节点
当前提交
e7ab3a4132
共有 4 个文件被更改,包括 267 次插入5 次删除
  1. 194 4
      editor/scene_tree_dock.cpp
  2. 13 1
      editor/scene_tree_dock.h
  3. 57 0
      scene/main/node.cpp
  4. 3 0
      scene/main/node.h

+ 194 - 4
editor/scene_tree_dock.cpp

@@ -87,6 +87,12 @@ void SceneTreeDock::_unhandled_key_input(Ref<InputEvent> 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)) {
@@ -397,6 +403,114 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 			tree->ensure_cursor_is_visible();
 
 		} break;
+		case TOOL_CUT:
+		case TOOL_COPY: {
+			if (!edited_scene || !_validate_no_foreign()) {
+				break;
+			}
+
+			List<Node *> 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_filename();
+
+			selection.sort_custom<Node::Comparator>();
+
+			for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+				Node *node = E->get();
+				Map<const Node *, Node *> 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_filename() != String()) {
+				for (List<Node *>::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<Node *> 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<RES, RES> 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<RES, RES> remap;
+					for (List<Node *>::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<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) {
+				Node *node = E->get();
+				Map<const Node *, Node *> 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<const Node *, Node *>::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;
@@ -1795,7 +1909,7 @@ void SceneTreeDock::_toggle_editable_children(Node *p_node) {
 	}
 }
 
-void SceneTreeDock::_delete_confirm() {
+void SceneTreeDock::_delete_confirm(bool p_cut) {
 	List<Node *> remove_list = editor_selection->get_selected_node_list();
 
 	if (remove_list.is_empty()) {
@@ -1804,7 +1918,11 @@ void SceneTreeDock::_delete_confirm() {
 
 	editor->get_editor_plugins_over()->make_visible(false);
 
-	editor_data->get_undo_redo().create_action(TTR("Remove Node(s)"));
+	if (p_cut) {
+		editor_data->get_undo_redo().create_action(TTR("Cut Node(s)"));
+	} else {
+		editor_data->get_undo_redo().create_action(TTR("Remove Node(s)"));
+	}
 
 	bool entire_scene = false;
 
@@ -2444,6 +2562,13 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
 	}
 
 	if (profile_allow_script_editing) {
+		menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/cut_node"), TOOL_CUT);
+		menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/copy_node"), TOOL_COPY);
+		if (selection.size() == 1 && !node_clipboard.is_empty()) {
+			menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/paste_node"), TOOL_PASTE);
+		}
+		menu->add_separator();
+
 		bool add_separator = false;
 
 		if (full_selection.size() == 1) {
@@ -2775,6 +2900,62 @@ void SceneTreeDock::_feature_profile_changed() {
 	_update_script_button();
 }
 
+void SceneTreeDock::_clear_clipboard() {
+	for (List<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) {
+		memdelete(E->get());
+	}
+	node_clipboard.clear();
+	clipboard_resource_remap.clear();
+}
+
+void SceneTreeDock::_create_remap_for_node(Node *p_node, Map<RES, RES> &r_remap) {
+	List<PropertyInfo> props;
+	p_node->get_property_list(&props);
+
+	for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+		if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
+			continue;
+		}
+
+		Variant v = p_node->get(E->get().name);
+		if (v.is_ref()) {
+			RES res = v;
+			if (res.is_valid()) {
+				if (res->get_path() == "" && !r_remap.has(res)) {
+					_create_remap_for_resource(res, r_remap);
+				}
+			}
+		}
+	}
+
+	for (int i = 0; i < p_node->get_child_count(); i++) {
+		_create_remap_for_node(p_node->get_child(i), r_remap);
+	}
+}
+
+void SceneTreeDock::_create_remap_for_resource(RES p_resource, Map<RES, RES> &r_remap) {
+	r_remap[p_resource] = p_resource->duplicate();
+
+	List<PropertyInfo> props;
+	p_resource->get_property_list(&props);
+
+	for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+		if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
+			continue;
+		}
+
+		Variant v = p_resource->get(E->get().name);
+		if (v.is_ref()) {
+			RES res = v;
+			if (res.is_valid()) {
+				if (res->get_path() == "" && !r_remap.has(res)) {
+					_create_remap_for_resource(res, r_remap);
+				}
+			}
+		}
+	}
+}
+
 void SceneTreeDock::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_set_owners"), &SceneTreeDock::_set_owners);
 	ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &SceneTreeDock::_unhandled_key_input);
@@ -2806,6 +2987,9 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
 	ED_SHORTCUT("scene_tree/add_child_node", TTR("Add Child Node"), KEY_MASK_CMD | KEY_A);
 	ED_SHORTCUT("scene_tree/instance_scene", TTR("Instance Child Scene"));
 	ED_SHORTCUT("scene_tree/expand_collapse_all", TTR("Expand/Collapse All"));
+	ED_SHORTCUT("scene_tree/cut_node", TTR("Cut"), KEY_MASK_CMD | KEY_X);
+	ED_SHORTCUT("scene_tree/copy_node", TTR("Copy"), KEY_MASK_CMD | KEY_C);
+	ED_SHORTCUT("scene_tree/paste_node", TTR("Paste"), KEY_MASK_CMD | KEY_V);
 	ED_SHORTCUT("scene_tree/change_node_type", TTR("Change Type"));
 	ED_SHORTCUT("scene_tree/attach_script", TTR("Attach Script"));
 	ED_SHORTCUT("scene_tree/extend_script", TTR("Extend Script"));
@@ -2818,7 +3002,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
 	ED_SHORTCUT("scene_tree/make_root", TTR("Make Scene Root"));
 	ED_SHORTCUT("scene_tree/merge_from_scene", TTR("Merge From Scene"));
 	ED_SHORTCUT("scene_tree/save_branch_as_scene", TTR("Save Branch as Scene"));
-	ED_SHORTCUT("scene_tree/copy_node_path", TTR("Copy Node Path"), KEY_MASK_CMD | KEY_C);
+	ED_SHORTCUT("scene_tree/copy_node_path", TTR("Copy Node Path"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_C);
 	ED_SHORTCUT("scene_tree/delete_no_confirm", TTR("Delete (No Confirm)"), KEY_MASK_SHIFT | KEY_DELETE);
 	ED_SHORTCUT("scene_tree/delete", TTR("Delete"), KEY_DELETE);
 
@@ -2936,7 +3120,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
 
 	delete_dialog = memnew(ConfirmationDialog);
 	add_child(delete_dialog);
-	delete_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_delete_confirm));
+	delete_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_delete_confirm), varray(false));
 
 	editable_instance_remove_dialog = memnew(ConfirmationDialog);
 	add_child(editable_instance_remove_dialog);
@@ -2981,3 +3165,9 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
 	EDITOR_DEF("interface/editors/derive_script_globals_by_name", true);
 	EDITOR_DEF("_use_favorites_root_selection", false);
 }
+
+SceneTreeDock::~SceneTreeDock() {
+	if (!node_clipboard.is_empty()) {
+		_clear_clipboard();
+	}
+}

+ 13 - 1
editor/scene_tree_dock.h

@@ -58,6 +58,9 @@ class SceneTreeDock : public VBoxContainer {
 		TOOL_NEW,
 		TOOL_INSTANCE,
 		TOOL_EXPAND_COLLAPSE,
+		TOOL_CUT,
+		TOOL_COPY,
+		TOOL_PASTE,
 		TOOL_RENAME,
 		TOOL_BATCH_RENAME,
 		TOOL_REPLACE,
@@ -126,6 +129,10 @@ class SceneTreeDock : public VBoxContainer {
 	EditorData *editor_data;
 	EditorSelection *editor_selection;
 
+	List<Node *> node_clipboard;
+	String clipboard_source_scene;
+	HashMap<String, Map<RES, RES>> clipboard_resource_remap;
+
 	ScriptCreateDialog *script_create_dialog;
 	AcceptDialog *accept;
 	ConfirmationDialog *delete_dialog;
@@ -183,7 +190,7 @@ class SceneTreeDock : public VBoxContainer {
 	void _script_created(Ref<Script> p_script);
 	void _script_creation_closed();
 
-	void _delete_confirm();
+	void _delete_confirm(bool p_cut = false);
 
 	void _toggle_editable_children_from_selection();
 	void _toggle_editable_children(Node *p_node);
@@ -230,6 +237,10 @@ class SceneTreeDock : public VBoxContainer {
 
 	void _feature_profile_changed();
 
+	void _clear_clipboard();
+	void _create_remap_for_node(Node *p_node, Map<RES, RES> &r_remap);
+	void _create_remap_for_resource(RES p_resource, Map<RES, RES> &r_remap);
+
 	bool profile_allow_editing;
 	bool profile_allow_script_editing;
 
@@ -267,6 +278,7 @@ public:
 	ScriptCreateDialog *get_script_create_dialog() { return script_create_dialog; }
 
 	SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSelection *p_editor_selection, EditorData &p_editor_data);
+	~SceneTreeDock();
 };
 
 #endif // SCENE_TREE_DOCK_H

+ 57 - 0
scene/main/node.cpp

@@ -2138,8 +2138,17 @@ Node *Node::duplicate(int p_flags) const {
 
 #ifdef TOOLS_ENABLED
 Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const {
+	return duplicate_from_editor(r_duplimap, Map<RES, RES>());
+}
+
+Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const Map<RES, RES> &p_resource_remap) const {
 	Node *dupe = _duplicate(DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANCING | DUPLICATE_FROM_EDITOR, &r_duplimap);
 
+	// This is used by SceneTreeDock's paste functionality. When pasting to foreign scene, resources are duplicated.
+	if (!p_resource_remap.is_empty()) {
+		remap_node_resources(dupe, p_resource_remap);
+	}
+
 	// Duplication of signals must happen after all the node descendants have been copied,
 	// because re-targeting of connections from some descendant to another is not possible
 	// if the emitter node comes later in tree order than the receiver
@@ -2147,6 +2156,54 @@ Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const {
 
 	return dupe;
 }
+
+void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_remap) const {
+	List<PropertyInfo> props;
+	p_node->get_property_list(&props);
+
+	for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+		if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
+			continue;
+		}
+
+		Variant v = p_node->get(E->get().name);
+		if (v.is_ref()) {
+			RES res = v;
+			if (res.is_valid()) {
+				if (p_resource_remap.has(res)) {
+					p_node->set(E->get().name, p_resource_remap[res]);
+					remap_nested_resources(res, p_resource_remap);
+				}
+			}
+		}
+	}
+
+	for (int i = 0; i < p_node->get_child_count(); i++) {
+		remap_node_resources(p_node->get_child(i), p_resource_remap);
+	}
+}
+
+void Node::remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resource_remap) const {
+	List<PropertyInfo> props;
+	p_resource->get_property_list(&props);
+
+	for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+		if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
+			continue;
+		}
+
+		Variant v = p_resource->get(E->get().name);
+		if (v.is_ref()) {
+			RES res = v;
+			if (res.is_valid()) {
+				if (p_resource_remap.has(res)) {
+					p_resource->set(E->get().name, p_resource_remap[res]);
+					remap_nested_resources(res, p_resource_remap);
+				}
+			}
+		}
+	}
+}
 #endif
 
 void Node::_duplicate_and_reown(Node *p_new_parent, const Map<Node *, Node *> &p_reown_map) const {

+ 3 - 0
scene/main/node.h

@@ -362,6 +362,9 @@ public:
 	Node *duplicate_and_reown(const Map<Node *, Node *> &p_reown_map) const;
 #ifdef TOOLS_ENABLED
 	Node *duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const;
+	Node *duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const Map<RES, RES> &p_resource_remap) const;
+	void remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_remap) const;
+	void remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resource_remap) const;
 #endif
 
 	// used by editors, to save what has changed only