From 8520f240d048223479b0e1875e5191aaeaea0760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Thu, 30 Nov 2023 12:10:43 +0300 Subject: [PATCH] add copying and pasting nodes --- classes/deck/deck.gd | 121 ++++++++++++------ classes/deck/deck_node.gd | 48 ++++++- .../deck_renderer_graph_edit.gd | 34 ++++- .../deck_renderer_graph_edit.tscn | 2 + 4 files changed, 161 insertions(+), 44 deletions(-) diff --git a/classes/deck/deck.gd b/classes/deck/deck.gd index 743f9a8..5a51433 100644 --- a/classes/deck/deck.gd +++ b/classes/deck/deck.gd @@ -59,6 +59,7 @@ func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool node._id = uuid else: nodes[assign_id] = node + node._id = assign_id node_added.emit(node) @@ -197,6 +198,86 @@ func get_group(uuid: String) -> Deck: return groups.get(uuid) +func copy_nodes_json(nodes_to_copy: Array[String]) -> String: + var d := {"nodes": {}} + + for node_id: String in nodes_to_copy: + d.nodes[node_id] = get_node(node_id).to_dict() + + for node: String in d.nodes: + var outgoing_connections: Dictionary = d.nodes[node].outgoing_connections.duplicate(true) + + for from_port: int in outgoing_connections: + for to_node: String in outgoing_connections[from_port]: + if !(to_node in nodes_to_copy): + (d.nodes[node].outgoing_connections[from_port] as Dictionary).erase(to_node) + + var incoming_connections: Dictionary = d.nodes[node].incoming_connections.duplicate(true) + + for to_port: int in incoming_connections: + for from_node: String in incoming_connections[to_port]: + if !(from_node in nodes_to_copy): + (d.nodes[node].incoming_connections[to_port] as Dictionary).erase(from_node) + + for node: Dictionary in d.nodes.values().slice(1): + node.position.x = node.position.x - d.nodes.values()[0].position.x + node.position.y = node.position.y - d.nodes.values()[0].position.y + + d.nodes.values()[0].position.x = 0 + d.nodes.values()[0].position.y = 0 + + return JSON.stringify(d) + + +func allocate_ids(count: int) -> Array[String]: + var res: Array[String] + for i in count: + res.append(UUID.v4()) + return res + + +func paste_nodes_from_json(json: String, position: Vector2 = Vector2()) -> void: + var d: Dictionary = JSON.parse_string(json) as Dictionary + if !d.get("nodes"): + return + + var new_ids := allocate_ids(d.nodes.size()) + var ids_map := {} + for i: int in d.nodes.keys().size(): + var node_id: String = d.nodes.keys()[i] + ids_map[node_id] = new_ids[i] + + var new_json := json + for old_id: String in ids_map: + new_json = new_json.replace(old_id, ids_map[old_id]) + + for node_id: String in d.nodes: + d.nodes[node_id]._id = ids_map[node_id] + + d.nodes[node_id].position.x += position.x + d.nodes[node_id].position.y += position.y + + var outgoing_connections: Dictionary = d.nodes[node_id].outgoing_connections as Dictionary + var outgoing_connections_res := {} + for from_port: String in outgoing_connections: + outgoing_connections_res[from_port] = {} + for to_node_id: String in outgoing_connections[from_port]: + outgoing_connections_res[from_port][ids_map[to_node_id]] = outgoing_connections[from_port][to_node_id] + + var incoming_connections: Dictionary = d.nodes[node_id].incoming_connections as Dictionary + var incoming_connections_res := {} + for to_port: String in incoming_connections: + incoming_connections_res[to_port] = {} + for from_node_id: String in incoming_connections[to_port]: + incoming_connections_res[to_port][ids_map[from_node_id]] = incoming_connections[to_port][from_node_id] + + d.nodes[node_id].outgoing_connections = outgoing_connections_res + d.nodes[node_id].incoming_connections = incoming_connections_res + + var node := DeckNode.from_dict(d.nodes[node_id]) + add_node_inst(node, ids_map[node_id]) + + ## Returns a [Dictionary] representation of this deck. func to_dict(with_meta: bool = true) -> Dictionary: var inner := { @@ -239,44 +320,8 @@ static func from_dict(data: Dictionary, path: String = "") -> Deck: var nodes_data: Dictionary = data.deck.nodes as Dictionary for node_id in nodes_data: - var node := deck.add_node_type(nodes_data[node_id].node_type, node_id, false) - node._id = node_id - node.name = nodes_data[node_id].name - node._belonging_to = deck - node.position = nodes_data[node_id].position - - for prop in nodes_data[node_id].props: - node.set(prop, nodes_data[node_id].props[prop]) - - node._pre_connection() - - for from_port: String in nodes_data[node_id].outgoing_connections: - var connection_data: Dictionary = nodes_data[node_id].outgoing_connections[from_port] - node.outgoing_connections[int(from_port)] = {} - for to_node: String in connection_data: - var input_ports: Array = connection_data[to_node] - node.outgoing_connections[int(from_port)][to_node] = [] - for to_input_port in input_ports: - node.outgoing_connections[int(from_port)][to_node].append(int(to_input_port)) - - for to_port: String in nodes_data[node_id].incoming_connections: - var connection_data = nodes_data[node_id].incoming_connections[to_port] - for connection in connection_data: - connection_data[connection] = int(connection_data[connection]) - node.incoming_connections[int(to_port)] = connection_data - - for i in node.ports.size(): - var port_value: Variant - if (nodes_data[node_id].port_values as Array).size() <= i: - port_value = null - else: - port_value = (nodes_data[node_id].port_values as Array)[i] - node.ports[i].value = port_value - - for key in nodes_data[node_id].meta: - node.set_meta(key, str_to_var(nodes_data[node_id].meta[key])) - - node._post_load() + var node := DeckNode.from_dict(nodes_data[node_id]) + deck.add_node_inst(node, node_id) var groups_data: Dictionary = data.deck.groups as Dictionary diff --git a/classes/deck/deck_node.gd b/classes/deck/deck_node.gd index cbfcbf6..a46b356 100644 --- a/classes/deck/deck_node.gd +++ b/classes/deck/deck_node.gd @@ -250,12 +250,12 @@ func to_dict(with_meta: bool = true) -> Dictionary: var d := { "_id": _id, "name": name, - "outgoing_connections": outgoing_connections, - "incoming_connections": incoming_connections, + "outgoing_connections": outgoing_connections.duplicate(true), + "incoming_connections": incoming_connections.duplicate(true), "props": {}, "node_type": node_type, "port_values": [], - "position": position, + "position": position.duplicate(), } for prop in props_to_serialize: @@ -273,6 +273,48 @@ func to_dict(with_meta: bool = true) -> Dictionary: return d +static func from_dict(data: Dictionary) -> DeckNode: + var node := NodeDB.instance_node(data.node_type) + #node._id = data._id + node.name = data.name + node.position = data.position + + for prop in data.props: + node.set(prop, data.props[prop]) + + node._pre_connection() + + for from_port: String in data.outgoing_connections: + var connection_data: Dictionary = data.outgoing_connections[from_port] + node.outgoing_connections[int(from_port)] = {} + for to_node: String in connection_data: + var input_ports: Array = connection_data[to_node] + node.outgoing_connections[int(from_port)][to_node] = [] + for to_input_port in input_ports: + node.outgoing_connections[int(from_port)][to_node].append(int(to_input_port)) + + for to_port: String in data.incoming_connections: + var connection_data = data.incoming_connections[to_port] + for connection in connection_data: + connection_data[connection] = int(connection_data[connection]) + node.incoming_connections[int(to_port)] = connection_data + + for i in node.ports.size(): + var port_value: Variant + if (data.port_values as Array).size() <= i: + port_value = null + else: + port_value = (data.port_values as Array)[i] + node.ports[i].value = port_value + + for key in data.meta: + node.set_meta(key, str_to_var(data.meta[key])) + + node._post_load() + + return node + + ## Returns the node's [member position] as a [Vector2]. func position_as_vector2() -> Vector2: return Vector2(position.x, position.y) diff --git a/graph_node_renderer/deck_renderer_graph_edit.gd b/graph_node_renderer/deck_renderer_graph_edit.gd index f5fd5d7..d638e5c 100644 --- a/graph_node_renderer/deck_renderer_graph_edit.gd +++ b/graph_node_renderer/deck_renderer_graph_edit.gd @@ -129,10 +129,13 @@ func refresh_connections() -> void: for from_port in node.outgoing_connections: for to_node_id: String in node.outgoing_connections[from_port]: var to_node_ports = node.outgoing_connections[from_port][to_node_id] - var to_node: DeckNodeRendererGraphNode = get_children().filter( + var renderer: Array = get_children().filter( func(c: DeckNodeRendererGraphNode): return c.node._id == to_node_id - )[0] + ) + if renderer.is_empty(): + break + var to_node: DeckNodeRendererGraphNode = renderer[0] for to_node_port: int in to_node_ports: connect_node( from_node.name, @@ -148,6 +151,8 @@ func _on_deck_node_added(node: DeckNode) -> void: inst.node = node add_child(inst) inst.position_offset = inst.node.position_as_vector2() + clear_connections() + refresh_connections() ## Connected to [signal Deck.node_added], used to remove the specified ## [DeckNodeRendererGraphNode] and queue_free it. @@ -229,5 +234,28 @@ func _on_delete_nodes_request(nodes: Array[StringName]) -> void: for node_id in node_ids: deck.remove_node(node_id, true) - #await get_tree().process_frame refresh_connections() + + +func _on_copy_nodes_request() -> void: + var selected := get_selected_nodes() + if selected.is_empty(): + return + + var selected_ids: Array[String] + selected_ids.assign(selected.map( + func(x: DeckNodeRendererGraphNode): + return x.node._id + )) + + DisplayServer.clipboard_set(deck.copy_nodes_json(selected_ids)) + + +func _on_paste_nodes_request() -> void: + var clip := DisplayServer.clipboard_get() + var node_pos := (get_local_mouse_position() + scroll_offset - (Vector2(75.0, 0.0) * zoom)) / zoom + + if snapping_enabled: + node_pos = node_pos.snapped(Vector2(snapping_distance, snapping_distance)) + + deck.paste_nodes_from_json(clip, node_pos) diff --git a/graph_node_renderer/deck_renderer_graph_edit.tscn b/graph_node_renderer/deck_renderer_graph_edit.tscn index 5f7167c..9490f13 100644 --- a/graph_node_renderer/deck_renderer_graph_edit.tscn +++ b/graph_node_renderer/deck_renderer_graph_edit.tscn @@ -12,6 +12,8 @@ right_disconnects = true show_arrange_button = false script = ExtResource("1_pojfs") +[connection signal="copy_nodes_request" from="." to="." method="_on_copy_nodes_request"] [connection signal="delete_nodes_request" from="." to="." method="_on_delete_nodes_request"] +[connection signal="paste_nodes_request" from="." to="." method="_on_paste_nodes_request"] [connection signal="popup_request" from="." to="." method="_on_popup_request"] [connection signal="scroll_offset_changed" from="." to="." method="_on_scroll_offset_changed"]