| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- extends GraphEdit
- var control_script
- var graph_edit
- var open_help
- var multiple_connections
- var selected_nodes = {} #used to track which nodes in the GraphEdit are selected
- var copied_nodes_data = [] #stores node data on ctrl+c
- var copied_connections = [] #stores all connections on ctrl+c
- # Called when the node enters the scene tree for the first time.
- func _ready() -> void:
- snapping_enabled = false
- show_grid = false
- zoom = 0.9
- func init(main_node: Node, graphedit: GraphEdit, openhelp: Callable, multipleconnections: Window) -> void:
- control_script = main_node
- graph_edit = graphedit
- open_help = openhelp
- multiple_connections = multipleconnections
- func _make_node(command: String):
- #Find node with matching name to button and create a version of it in the graph edit
- #and position it close to the origin right click to open the menu
- var effect: GraphNode = Nodes.get_node(NodePath(command)).duplicate()
- effect.name = command
- add_child(effect, true)
- effect.connect("open_help", open_help)
- effect.set_position_offset((control_script.effect_position + graph_edit.scroll_offset) / graph_edit.zoom) #set node to current mouse position in graph edit
- _register_inputs_in_node(effect) #link sliders for changes tracking
- _register_node_movement() #link nodes for tracking position changes for changes tracking
- control_script.changesmade = true
- # Remove node with UndoRedo
- control_script.undo_redo.create_action("Add Node")
- control_script.undo_redo.add_undo_method(Callable(graph_edit, "remove_child").bind(effect))
- control_script.undo_redo.add_undo_method(Callable(effect, "queue_free"))
- control_script.undo_redo.add_undo_method(Callable(self, "_track_changes"))
- control_script.undo_redo.commit_action()
-
- func _on_connection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
- var to_graph_node = get_node(NodePath(to_node))
- # Get the type of the input port using GraphNode's built-in method
- var port_type = to_graph_node.get_input_port_type(to_port)
- # If port type is 1 and already has a connection, reject the request
- if port_type == 1:
- var connections = get_connection_list()
- var existing_connections = 0
- for conn in connections:
- if conn.to_node == to_node and conn.to_port == to_port:
- existing_connections += 1
- if existing_connections >= 1:
- var interface_settings = ConfigHandler.load_interface_settings()
- if interface_settings.disable_pvoc_warning == false:
- multiple_connections.popup_centered()
- return
- # If no conflict, allow the connection
- connect_node(from_node, from_port, to_node, to_port)
- control_script.changesmade = true
- func _on_graph_edit_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
- disconnect_node(from_node, from_port, to_node, to_port)
- control_script.changesmade = true
- func _on_graph_edit_node_selected(node: Node) -> void:
- selected_nodes[node] = true
- func _on_graph_edit_node_deselected(node: Node) -> void:
- selected_nodes[node] = false
- func _unhandled_key_input(event: InputEvent) -> void:
- if event is InputEventKey and event.pressed and not event.echo:
- if event.keycode == KEY_BACKSPACE:
- _on_graph_edit_delete_nodes_request(PackedStringArray(selected_nodes.keys().filter(func(k): return selected_nodes[k])))
- pass
- func _on_graph_edit_delete_nodes_request(nodes: Array[StringName]) -> void:
- control_script.undo_redo.create_action("Delete Nodes (Undo only)")
-
- #get the number of inputs in the patch
- var number_of_inputs = 0
- for allnodes in get_children():
- if allnodes.get_meta("command") == "inputfile":
- number_of_inputs += 1
-
- for node in selected_nodes.keys():
- if selected_nodes[node]:
- #check if node is the output or the last input node and do nothing
- if (number_of_inputs <= 1 and node.get_meta("command") == "inputfile") or node.get_meta("command") == "outputfile":
- pass
- else:
- number_of_inputs -= 1
- # Store duplicate and state for undo
- var node_data = node.duplicate()
- var position = node.position_offset
- # Store all connections for undo
- var conns = []
- for con in get_connection_list():
- if con["to_node"] == node.name or con["from_node"] == node.name:
- conns.append(con)
- # Delete
- remove_connections_to_node(node)
- node.queue_free()
- control_script.changesmade = true
- # Register undo restore
- control_script.undo_redo.add_undo_method(Callable(self, "add_child").bind(node_data, true))
- control_script.undo_redo.add_undo_method(Callable(node_data, "set_position_offset").bind(position))
- for con in conns:
- control_script.undo_redo.add_undo_method(Callable(self, "connect_node").bind(
- con["from_node"], con["from_port"],
- con["to_node"], con["to_port"]
- ))
- control_script.undo_redo.add_undo_method(Callable(self, "set_node_selected").bind(node_data, true))
- control_script.undo_redo.add_undo_method(Callable(self, "_track_changes"))
- control_script.undo_redo.add_undo_method(Callable(self, "_register_inputs_in_node").bind(node_data)) #link sliders for changes tracking
- control_script.undo_redo.add_undo_method(Callable(self, "_register_node_movement")) # link nodes for changes tracking
- # Clear selection
- selected_nodes = {}
- control_script.undo_redo.commit_action()
- func set_node_selected(node: Node, selected: bool) -> void:
- selected_nodes[node] = selected
- #
- func remove_connections_to_node(node):
- for con in get_connection_list():
- if con["to_node"] == node.name or con["from_node"] == node.name:
- disconnect_node(con["from_node"], con["from_port"], con["to_node"], con["to_port"])
- control_script.changesmade = true
-
- #copy and paste nodes with vertical offset on paste
- func copy_selected_nodes():
- copied_nodes_data.clear()
- copied_connections.clear()
- # Store selected nodes and their slider values
- for node in get_children():
- # Check if the node is selected and not an 'inputfile' or 'outputfile'
- if node is GraphNode and selected_nodes.get(node, false):
- if node.get_meta("command") == "outputfile":
- continue # Skip these nodes
- var node_data = {
- "name": node.name,
- "type": node.get_class(),
- "offset": node.position_offset,
- "slider_values": {}
- }
- for child in node.get_children():
- if child is HSlider or child is VSlider:
- node_data["slider_values"][child.name] = child.value
- copied_nodes_data.append(node_data)
- # Store connections between selected nodes
- for conn in get_connection_list():
- var from_ref = get_node_or_null(NodePath(conn["from_node"]))
- var to_ref = get_node_or_null(NodePath(conn["to_node"]))
- var is_from_selected = from_ref != null and selected_nodes.get(from_ref, false)
- var is_to_selected = to_ref != null and selected_nodes.get(to_ref, false)
- # Skip if any of the connected nodes are 'inputfile' or 'outputfile'
- if (from_ref != null and (from_ref.get_meta("command") == "inputfile" or from_ref.get_meta("command") == "outputfile")) or (to_ref != null and (to_ref.get_meta("command") == "inputfile" or to_ref.get_meta("command") == "outputfile")):
- continue
- if is_from_selected and is_to_selected:
- # Store connection as dictionary
- var conn_data = {
- "from_node": conn["from_node"],
- "from_port": conn["from_port"],
- "to_node": conn["to_node"],
- "to_port": conn["to_port"]
- }
- copied_connections.append(conn_data)
- func paste_copied_nodes():
- if copied_nodes_data.is_empty():
- return
- var name_map = {}
- var pasted_nodes = []
- # Step 1: Find topmost and bottommost Y of copied nodes
- var min_y = INF
- var max_y = -INF
- for node_data in copied_nodes_data:
- var y = node_data["offset"].y
- min_y = min(min_y, y)
- max_y = max(max_y, y)
- # Step 2: Decide where to paste the group
- var base_y_offset = max_y + 350 # Pasting below the lowest node
- # Step 3: Paste nodes, preserving vertical layout
- for node_data in copied_nodes_data:
- var original_node = get_node_or_null(NodePath(node_data["name"]))
- if not original_node:
- continue
- var new_node = original_node.duplicate()
- new_node.name = node_data["name"] + "_copy_" + str(randi() % 10000)
- var relative_y = node_data["offset"].y - min_y
- new_node.position_offset = Vector2(
- node_data["offset"].x,
- base_y_offset + relative_y
- )
-
- # Restore sliders
- for child in new_node.get_children():
- if child.name in node_data["slider_values"]:
- child.value = node_data["slider_values"][child.name]
- add_child(new_node, true)
- new_node.connect("open_help", open_help)
- _register_inputs_in_node(new_node) #link sliders for changes tracking
- _register_node_movement() # link nodes for changes tracking
- name_map[node_data["name"]] = new_node.name
- pasted_nodes.append(new_node)
- # Step 4: Reconnect new nodes
- for conn_data in copied_connections:
- var new_from = name_map.get(conn_data["from_node"], null)
- var new_to = name_map.get(conn_data["to_node"], null)
- if new_from and new_to:
- connect_node(new_from, conn_data["from_port"], new_to, conn_data["to_port"])
- # Step 5: Select pasted nodes
- for pasted_node in pasted_nodes:
- set_selected(pasted_node)
- selected_nodes[pasted_node] = true
-
- control_script.changesmade = true
-
- # Remove node with UndoRedo
- control_script.undo_redo.create_action("Paste Nodes")
- for pasted_node in pasted_nodes:
- control_script.undo_redo.add_undo_method(Callable(self, "remove_child").bind(pasted_node))
- control_script.undo_redo.add_undo_method(Callable(pasted_node, "queue_free"))
- control_script.undo_redo.add_undo_method(Callable(self, "remove_connections_to_node").bind(pasted_node))
- control_script.undo_redo.add_undo_method(Callable(self, "_track_changes"))
- control_script.undo_redo.commit_action()
-
- #functions for tracking changes for save state detection
- func _register_inputs_in_node(node: Node):
- #tracks input to nodes sliders and codeedit to track if patch is saved
- # Track Sliders
- for slider in node.find_children("*", "HSlider", true, false):
- # Create a Callable to the correct method
- var callable = Callable(self, "_on_any_slider_changed")
- # Check if it's already connected, and connect if not
- if not slider.is_connected("value_changed", callable):
- slider.connect("value_changed", callable)
-
- for slider in node.find_children("*", "VBoxContainer", true, false):
- # Also connect to meta_changed if the slider has that signal
- if slider.has_signal("meta_changed"):
- var meta_callable = Callable(self, "_on_any_slider_meta_changed")
- if not slider.is_connected("meta_changed", meta_callable):
- slider.connect("meta_changed", meta_callable)
-
- # Track CodeEdits
- for editor in node.find_children("*", "CodeEdit", true, false):
- var callable = Callable(self, "_on_any_input_changed")
- if not editor.is_connected("text_changed", callable):
- editor.connect("text_changed", callable)
-
- func _on_any_slider_meta_changed():
- control_script.changesmade = true
- print("Meta changed in slider")
-
- func _register_node_movement():
- for graphnode in get_children():
- if graphnode is GraphNode:
- var callable = Callable(self, "_on_graphnode_moved")
- if not graphnode.is_connected("position_offset_changed", callable):
- graphnode.connect("position_offset_changed", callable)
- func _on_graphnode_moved():
- control_script.changesmade = true
-
- func _on_any_slider_changed(value: float) -> void:
- control_script.changesmade = true
-
- func _on_any_input_changed():
- control_script.changesmade = true
- func _track_changes():
- control_script.changesmade = true
|