[3.2] Add node copy-paste
This commit is contained in:
parent
83e4e4dc52
commit
637117c8d1
4 changed files with 266 additions and 6 deletions
|
@ -90,6 +90,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)) {
|
||||
|
@ -401,6 +407,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.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.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) {
|
||||
|
@ -1804,8 +1918,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.empty())
|
||||
|
@ -1813,7 +1926,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;
|
||||
|
||||
|
@ -2472,6 +2589,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.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) {
|
||||
|
@ -2812,6 +2936,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() == "" || res->get_path().find("::") > -1) && !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() == "" || res->get_path().find("::") > -1) && !r_remap.has(res)) {
|
||||
_create_remap_for_resource(res, r_remap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SceneTreeDock::_bind_methods() {
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_tool_selected"), &SceneTreeDock::_tool_selected, DEFVAL(false));
|
||||
|
@ -2875,6 +3055,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"));
|
||||
|
@ -2887,7 +3070,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);
|
||||
|
||||
|
@ -2997,7 +3180,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
|
|||
|
||||
delete_dialog = memnew(ConfirmationDialog);
|
||||
add_child(delete_dialog);
|
||||
delete_dialog->connect("confirmed", this, "_delete_confirm");
|
||||
delete_dialog->connect("confirmed", this, "_delete_confirm", varray(false));
|
||||
|
||||
editable_instance_remove_dialog = memnew(ConfirmationDialog);
|
||||
add_child(editable_instance_remove_dialog);
|
||||
|
@ -3042,3 +3225,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.empty()) {
|
||||
_clear_clipboard();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,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,
|
||||
|
@ -129,6 +132,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;
|
||||
|
@ -186,7 +193,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);
|
||||
|
@ -234,6 +241,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;
|
||||
|
||||
|
@ -271,6 +282,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
|
||||
|
|
|
@ -2147,9 +2147,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.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
|
||||
|
@ -2157,6 +2165,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 {
|
||||
|
|
|
@ -364,6 +364,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
|
||||
|
|
Loading…
Reference in a new issue