Browse Source

Merge pull request #30972 from Chaosus/vs_copy_paste

Implement copy/paste in visual shaders
Rémi Verschelde 6 years ago
parent
commit
d66461e85a

+ 6 - 0
doc/classes/GraphEdit.xml

@@ -233,6 +233,9 @@
 				Signal sent when user dragging connection from output port into empty space of the graph.
 			</description>
 		</signal>
+		<signal name="copy_nodes_request">
+			Signal sent when the user presses [code]Ctrl + C[/code].
+		</signal>
 		<signal name="delete_nodes_request">
 			<description>
 				Signal sent when a GraphNode is attempted to be removed from the GraphEdit.
@@ -263,6 +266,9 @@
 				Emitted when a GraphNode is selected.
 			</description>
 		</signal>
+		<signal name="paste_nodes_request">
+			Signal sent when the user presses [code]Ctrl + V[/code].
+		</signal>
 		<signal name="popup_request">
 			<argument index="0" name="position" type="Vector2">
 			</argument>

+ 111 - 22
editor/plugins/visual_shader_editor_plugin.cpp

@@ -1379,6 +1379,9 @@ void VisualShaderEditor::_delete_request(int which) {
 	undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, which);
 	undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, which), which);
 
+	undo_redo->add_do_method(this, "_clear_buffer");
+	undo_redo->add_undo_method(this, "_clear_buffer");
+
 	// restore size, inputs and outputs if node is group
 	VisualShaderNodeGroupBase *group = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr());
 	if (group) {
@@ -1533,12 +1536,32 @@ void VisualShaderEditor::_node_changed(int p_id) {
 	}
 }
 
-void VisualShaderEditor::_duplicate_nodes() {
+void VisualShaderEditor::_dup_update_excluded(int p_type, Set<int> &r_excluded) {
+	r_excluded.clear();
+	VisualShader::Type type = (VisualShader::Type)p_type;
 
-	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+	for (int i = 0; i < graph->get_child_count(); i++) {
 
-	List<int> nodes;
-	Set<int> excluded;
+		GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i));
+		if (gn) {
+			int id = String(gn->get_name()).to_int();
+			Ref<VisualShaderNode> node = visual_shader->get_node(type, id);
+			Ref<VisualShaderNodeOutput> output = node;
+			if (output.is_valid()) {
+				r_excluded.insert(id);
+				continue;
+			}
+			r_excluded.insert(id);
+		}
+	}
+}
+
+void VisualShaderEditor::_dup_copy_nodes(int p_type, List<int> &r_nodes, Set<int> &r_excluded) {
+
+	VisualShader::Type type = (VisualShader::Type)p_type;
+
+	selection_center.x = 0.0f;
+	selection_center.y = 0.0f;
 
 	for (int i = 0; i < graph->get_child_count(); i++) {
 
@@ -1548,33 +1571,37 @@ void VisualShaderEditor::_duplicate_nodes() {
 			Ref<VisualShaderNode> node = visual_shader->get_node(type, id);
 			Ref<VisualShaderNodeOutput> output = node;
 			if (output.is_valid()) { // can't duplicate output
-				excluded.insert(id);
+				r_excluded.insert(id);
 				continue;
 			}
 			if (node.is_valid() && gn->is_selected()) {
-				nodes.push_back(id);
+				Vector2 pos = visual_shader->get_node_position(type, id);
+				selection_center += pos;
+				r_nodes.push_back(id);
 			}
-			excluded.insert(id);
+			r_excluded.insert(id);
 		}
 	}
 
-	if (nodes.empty())
-		return;
+	selection_center /= (float)r_nodes.size();
+}
 
-	undo_redo->create_action(TTR("Duplicate Nodes"));
+void VisualShaderEditor::_dup_paste_nodes(int p_type, List<int> &r_nodes, Set<int> &r_excluded, const Vector2 &p_offset, bool p_select) {
+
+	VisualShader::Type type = (VisualShader::Type)p_type;
 
 	int base_id = visual_shader->get_valid_node_id(type);
 	int id_from = base_id;
 	Map<int, int> connection_remap;
 
-	for (List<int>::Element *E = nodes.front(); E; E = E->next()) {
+	for (List<int>::Element *E = r_nodes.front(); E; E = E->next()) {
 
 		connection_remap[E->get()] = id_from;
 		Ref<VisualShaderNode> node = visual_shader->get_node(type, E->get());
 
 		Ref<VisualShaderNode> dupli = node->duplicate();
 
-		undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, dupli, visual_shader->get_node_position(type, E->get()) + Vector2(10, 10) * EDSCALE, id_from);
+		undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, dupli, visual_shader->get_node_position(type, E->get()) + p_offset, id_from);
 		undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_from);
 
 		// duplicate size, inputs and outputs if node is group
@@ -1606,21 +1633,71 @@ void VisualShaderEditor::_duplicate_nodes() {
 	undo_redo->add_undo_method(this, "_update_graph");
 	undo_redo->commit_action();
 
-	// reselect duplicated nodes by excluding the other ones
-	for (int i = 0; i < graph->get_child_count(); i++) {
-
-		GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i));
-		if (gn) {
-			int id = String(gn->get_name()).to_int();
-			if (!excluded.has(id)) {
-				gn->set_selected(true);
-			} else {
-				gn->set_selected(false);
+	if (p_select) {
+		// reselect duplicated nodes by excluding the other ones
+		for (int i = 0; i < graph->get_child_count(); i++) {
+
+			GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i));
+			if (gn) {
+				int id = String(gn->get_name()).to_int();
+				if (!r_excluded.has(id)) {
+					gn->set_selected(true);
+				} else {
+					gn->set_selected(false);
+				}
 			}
 		}
 	}
 }
 
+void VisualShaderEditor::_clear_buffer() {
+
+	copy_nodes_buffer.clear();
+	copy_nodes_excluded_buffer.clear();
+}
+
+void VisualShaderEditor::_duplicate_nodes() {
+
+	int type = edit_type->get_selected();
+
+	List<int> nodes;
+	Set<int> excluded;
+
+	_dup_copy_nodes(type, nodes, excluded);
+
+	if (nodes.empty())
+		return;
+
+	undo_redo->create_action(TTR("Duplicate Nodes"));
+
+	_dup_paste_nodes(type, nodes, excluded, Vector2(10, 10) * EDSCALE, true);
+}
+
+void VisualShaderEditor::_copy_nodes() {
+
+	copy_type = edit_type->get_selected();
+
+	_clear_buffer();
+
+	_dup_copy_nodes(copy_type, copy_nodes_buffer, copy_nodes_excluded_buffer);
+}
+
+void VisualShaderEditor::_paste_nodes() {
+
+	if (copy_nodes_buffer.empty())
+		return;
+
+	int type = edit_type->get_selected();
+
+	undo_redo->create_action(TTR("Paste Nodes"));
+
+	float scale = graph->get_zoom();
+
+	_dup_paste_nodes(type, copy_nodes_buffer, copy_nodes_excluded_buffer, (graph->get_scroll_ofs() / scale + graph->get_local_mouse_position() / scale - selection_center), false);
+
+	_dup_update_excluded(type, copy_nodes_excluded_buffer); // to prevent selection of previous copies at new paste
+}
+
 void VisualShaderEditor::_on_nodes_delete() {
 
 	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
@@ -1647,6 +1724,9 @@ void VisualShaderEditor::_on_nodes_delete() {
 		undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, F->get());
 		undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, F->get()), F->get());
 
+		undo_redo->add_do_method(this, "_clear_buffer");
+		undo_redo->add_undo_method(this, "_clear_buffer");
+
 		// restore size, inputs and outputs if node is group
 		VisualShaderNodeGroupBase *group = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr());
 		if (group) {
@@ -1691,6 +1771,10 @@ void VisualShaderEditor::_on_nodes_delete() {
 }
 
 void VisualShaderEditor::_mode_selected(int p_id) {
+
+	copy_nodes_buffer.clear();
+	copy_nodes_excluded_buffer.clear();
+
 	_update_options_menu();
 	_update_graph();
 }
@@ -1884,6 +1968,8 @@ void VisualShaderEditor::_bind_methods() {
 	ClassDB::bind_method("_line_edit_changed", &VisualShaderEditor::_line_edit_changed);
 	ClassDB::bind_method("_port_name_focus_out", &VisualShaderEditor::_port_name_focus_out);
 	ClassDB::bind_method("_duplicate_nodes", &VisualShaderEditor::_duplicate_nodes);
+	ClassDB::bind_method("_copy_nodes", &VisualShaderEditor::_copy_nodes);
+	ClassDB::bind_method("_paste_nodes", &VisualShaderEditor::_paste_nodes);
 	ClassDB::bind_method("_mode_selected", &VisualShaderEditor::_mode_selected);
 	ClassDB::bind_method("_input_select_item", &VisualShaderEditor::_input_select_item);
 	ClassDB::bind_method("_preview_select_port", &VisualShaderEditor::_preview_select_port);
@@ -1898,6 +1984,7 @@ void VisualShaderEditor::_bind_methods() {
 	ClassDB::bind_method("_remove_output_port", &VisualShaderEditor::_remove_output_port);
 	ClassDB::bind_method("_node_resized", &VisualShaderEditor::_node_resized);
 	ClassDB::bind_method("_set_node_size", &VisualShaderEditor::_set_node_size);
+	ClassDB::bind_method("_clear_buffer", &VisualShaderEditor::_clear_buffer);
 
 	ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &VisualShaderEditor::get_drag_data_fw);
 	ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &VisualShaderEditor::can_drop_data_fw);
@@ -1943,6 +2030,8 @@ VisualShaderEditor::VisualShaderEditor() {
 	graph->connect("node_selected", this, "_node_selected");
 	graph->connect("scroll_offset_changed", this, "_scroll_changed");
 	graph->connect("duplicate_nodes_request", this, "_duplicate_nodes");
+	graph->connect("copy_nodes_request", this, "_copy_nodes");
+	graph->connect("paste_nodes_request", this, "_paste_nodes");
 	graph->connect("delete_nodes_request", this, "_on_nodes_delete");
 	graph->connect("gui_input", this, "_graph_gui_input");
 	graph->connect("connection_to_empty", this, "_connection_to_empty");

+ 13 - 0
editor/plugins/visual_shader_editor_plugin.h

@@ -176,8 +176,21 @@ class VisualShaderEditor : public VBoxContainer {
 
 	void _port_name_focus_out(Object *line_edit, int p_node_id, int p_port_id, bool p_output);
 
+	void _dup_copy_nodes(int p_type, List<int> &r_nodes, Set<int> &r_excluded);
+	void _dup_update_excluded(int p_type, Set<int> &r_excluded);
+	void _dup_paste_nodes(int p_type, List<int> &r_nodes, Set<int> &r_excluded, const Vector2 &p_offset, bool p_select);
+
 	void _duplicate_nodes();
 
+	Vector2 selection_center;
+	int copy_type; // shader type
+	List<int> copy_nodes_buffer;
+	Set<int> copy_nodes_excluded_buffer;
+
+	void _clear_buffer();
+	void _copy_nodes();
+	void _paste_nodes();
+
 	Vector<Ref<VisualShaderNodePlugin> > plugins;
 
 	void _mode_selected(int p_id);

+ 23 - 7
scene/gui/graph_edit.cpp

@@ -1030,14 +1030,28 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
 	}
 
 	Ref<InputEventKey> k = p_ev;
-	if (k.is_valid() && k->get_scancode() == KEY_D && k->is_pressed() && k->get_command()) {
-		emit_signal("duplicate_nodes_request");
-		accept_event();
-	}
 
-	if (k.is_valid() && k->get_scancode() == KEY_DELETE && k->is_pressed()) {
-		emit_signal("delete_nodes_request");
-		accept_event();
+	if (k.is_valid()) {
+
+		if (k->get_scancode() == KEY_D && k->is_pressed() && k->get_command()) {
+			emit_signal("duplicate_nodes_request");
+			accept_event();
+		}
+
+		if (k->get_scancode() == KEY_C && k->is_pressed() && k->get_command()) {
+			emit_signal("copy_nodes_request");
+			accept_event();
+		}
+
+		if (k->get_scancode() == KEY_V && k->is_pressed() && k->get_command()) {
+			emit_signal("paste_nodes_request");
+			accept_event();
+		}
+
+		if (k->get_scancode() == KEY_DELETE && k->is_pressed()) {
+			emit_signal("delete_nodes_request");
+			accept_event();
+		}
 	}
 
 	Ref<InputEventMagnifyGesture> magnify_gesture = p_ev;
@@ -1297,6 +1311,8 @@ void GraphEdit::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING, "to"), PropertyInfo(Variant::INT, "to_slot")));
 	ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "position")));
 	ADD_SIGNAL(MethodInfo("duplicate_nodes_request"));
+	ADD_SIGNAL(MethodInfo("copy_nodes_request"));
+	ADD_SIGNAL(MethodInfo("paste_nodes_request"));
 	ADD_SIGNAL(MethodInfo("node_selected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
 	ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::VECTOR2, "release_position")));
 	ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING, "to"), PropertyInfo(Variant::INT, "to_slot"), PropertyInfo(Variant::VECTOR2, "release_position")));