Browse Source

added a rudimentary system for undoing adding, deleteing and pasting nodes

Jonathan Higgins 7 months ago
parent
commit
bd9e161ad8

+ 1 - 1
.godot/editor/editor_layout.cfg

@@ -33,7 +33,7 @@ current_scene="res://scenes/main/control.tscn"
 center_split_offset=0
 selected_default_debugger_tab_idx=0
 selected_main_editor_idx=2
-selected_bottom_panel_item=0
+selected_bottom_panel_item=1
 
 [EditorWindow]
 

+ 10 - 10
.godot/editor/filesystem_cache10

@@ -1,5 +1,5 @@
 ea4bc82a6ad023ab7ee23ee620429895
-::res://::1746716919
+::res://::1746745335
 config_handler.gd::GDScript/GDScript::9123848664534566230::1746479238::0::1::::<>Node<><>0<>0<><>::
 export_presets.cfg::TextFile/TextFile::-1::1746584894::0::1::::<><><>0<>0<><>::
 Global.gd::GDScript/GDScript::7717406573998402474::1746534642::0::1::::<>Node<><>0<>0<><>::
@@ -13,18 +13,18 @@ AudioStreamPreview.tscn::PackedScene/PackedScene::3762817095482496943::174645250
 voice_preview_generator.gd::GDScript/GDScript::6244997812245505292::1746444274::0::1::::<>Node<><>0<>1<><>::
 voice_preview_generator.tscn::PackedScene/PackedScene::6679166981814140597::1746444565::0::1::::<><><>0<>0<><>::uid://cu8eg4agw08xs::::res://addons/audio_preview/voice_preview_generator.gd
 ::res://scenes/::1746186301
-::res://scenes/main/::1746716900
-control.gd::GDScript::2620037524409541442::1746716900::0::1::::<>Control<><>0<>0<><>::
-control.tscn::PackedScene::2566019287410494992::1746716900::0::1::::<><><>0<>0<><>::uid://bdlfvuljckmu1::::res://scenes/main/control.gd<>uid://l2yejnjysupr::::res://scenes/main/graph_edit.gd<>uid://b0wdj8v6o0wq0::::res://scenes/menu/menu.tscn
+::res://scenes/main/::1746718071
+control.gd::GDScript::2620037524409541442::1746718070::0::1::::<>Control<><>0<>0<><>::
+control.tscn::PackedScene::2566019287410494992::1746718071::0::1::::<><><>0<>0<><>::uid://bdlfvuljckmu1::::res://scenes/main/control.gd<>uid://l2yejnjysupr::::res://scenes/main/graph_edit.gd<>uid://b0wdj8v6o0wq0::::res://scenes/menu/menu.tscn
 graph_edit.gd::GDScript/GDScript::829280323614315599::1746582824::0::1::::<>GraphEdit<><>0<>0<><>::
-::res://scenes/menu/::1746716852
-menu.tscn::PackedScene::4186758075496332121::1746716852::0::1::::<><><>0<>0<><>::
-::res://scenes/Nodes/::1746716852
+::res://scenes/menu/::1746718072
+menu.tscn::PackedScene::4186758075496332121::1746718072::0::1::::<><><>0<>0<><>::
+::res://scenes/Nodes/::1746718072
 audioplayer.gd::GDScript/GDScript::5570864814132306429::1746710375::0::1::::<>Control<><>0<>0<><>::
-audioplayer.tscn::PackedScene::6037166449976350293::1746716852::0::1::::<><><>0<>0<><>::uid://clmtlg8via3qn::::res://scenes/Nodes/audioplayer.gd
+audioplayer.tscn::PackedScene::6037166449976350293::1746718072::0::1::::<><><>0<>0<><>::uid://clmtlg8via3qn::::res://scenes/Nodes/audioplayer.gd
 focus_accu_sliders.gd::GDScript/GDScript::8821949764991756997::1746492997::0::1::::<>GraphNode<><>0<>0<><>::
-nodes.tscn::PackedScene::8614413456730569426::1746716852::0::1::::<><><>0<>0<><>::uid://csapiqka522fh::::res://scenes/Nodes/audioplayer.tscn<>uid://dya5kxx132fgp::::res://scenes/Nodes/valueslider.tscn<>uid://dyf0qutxeqio3::::res://scenes/Nodes/scatter_value.gd<>uid://dxxohuvlw5e3n::::res://scenes/Nodes/focus_accu_sliders.gd
+nodes.tscn::PackedScene::8614413456730569426::1746718072::0::1::::<><><>0<>0<><>::uid://csapiqka522fh::::res://scenes/Nodes/audioplayer.tscn<>uid://dya5kxx132fgp::::res://scenes/Nodes/valueslider.tscn<>uid://dyf0qutxeqio3::::res://scenes/Nodes/scatter_value.gd<>uid://dxxohuvlw5e3n::::res://scenes/Nodes/focus_accu_sliders.gd
 scatter_value.gd::GDScript/GDScript::8855663765553304696::1746710375::0::1::::<>VBoxContainer<><>0<>0<><>::
 valueslider.gd::GDScript/GDScript::2557655848205010713::1746187131::0::1::::<>VBoxContainer<><>0<>0<><>::
-valueslider.tscn::PackedScene::8845634910901483783::1746716852::0::1::::<><><>0<>0<><>::uid://bco7hof3wqck4::::res://scenes/Nodes/valueslider.gd
+valueslider.tscn::PackedScene::8845634910901483783::1746718072::0::1::::<><><>0<>0<><>::uid://bco7hof3wqck4::::res://scenes/Nodes/valueslider.gd
 waveform_preview.gd::GDScript/GDScript::5688062150584079786::1746458041::0::1::::<>Control<><>0<>0<><>::

+ 4 - 4
.godot/editor/script_editor_cache.cfg

@@ -3,11 +3,11 @@
 state={
 "bookmarks": PackedInt32Array(),
 "breakpoints": PackedInt32Array(),
-"column": 36,
-"folded_lines": Array[int]([]),
+"column": 26,
+"folded_lines": Array[int]([94, 125, 148, 151, 154, 195, 198, 204]),
 "h_scroll_position": 0,
-"row": 215,
-"scroll_position": 193.0,
+"row": 316,
+"scroll_position": 217.0,
 "selection": false,
 "syntax_highlighter": "GDScript"
 }

+ 11 - 0
project.godot

@@ -48,6 +48,17 @@ paste_node={
 "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":false,"pressed":false,"keycode":0,"physical_keycode":86,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
 ]
 }
+undo={
+"deadzone": 0.2,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":false,"pressed":false,"keycode":0,"physical_keycode":90,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
+redo={
+"deadzone": 0.2,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":89,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"command_or_control_autoremap":true,"alt_pressed":false,"shift_pressed":true,"pressed":false,"keycode":0,"physical_keycode":90,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
 
 [rendering]
 

+ 109 - 60
scenes/main/control.gd

@@ -10,6 +10,7 @@ var delete_intermediate_outputs
 var final_output_dir
 var copied_nodes_data = []
 var copied_connections = []
+var undo_redo := UndoRedo.new()
 
 # Called when the node enters the scene tree for the first time.
 func _ready() -> void:
@@ -74,6 +75,23 @@ func _on_cdp_location_dialog_canceled() -> void:
 func _process(delta: float) -> void:
 	showmenu()
 	
+
+func _input(event):
+	if event.is_action_pressed("copy_node"):
+		copy_selected_nodes()
+		get_viewport().set_input_as_handled()
+
+	elif event.is_action_pressed("paste_node"):
+		paste_copied_nodes()
+		get_viewport().set_input_as_handled()
+	elif event.is_action_pressed("undo"):
+		undo_redo.undo()
+	elif event.is_action_pressed("redo"):
+		undo_redo.redo()
+
+
+#logic for making, connecting, disconnecting, copy pasting and deleteing nodes and connections in GraphEdit
+#mostly taken from https://gdscript.com/solutions/godot-graphnode-and-graphedit-tutorial/
 func showmenu():
 	#check for mouse input and if menu is already open and then open or close the menu
 	#stores mouse position at time of right click to later place a node in that location
@@ -99,16 +117,91 @@ func _on_button_pressed(button: Button):
 	effect.position_offset = effect_position
 
 
-#Handles copying and pasting nodes
-func _input(event):
-	if event.is_action_pressed("copy_node"):
-		copy_selected_nodes()
-		get_viewport().set_input_as_handled()
+	# Remove node with UndoRedo
+	undo_redo.create_action("Add Node")
+	undo_redo.add_undo_method(Callable(graph_edit, "remove_child").bind(effect))
+	undo_redo.add_undo_method(Callable(effect, "queue_free"))
+	undo_redo.commit_action()
 
-	elif event.is_action_pressed("paste_node"):
-		paste_copied_nodes()
-		get_viewport().set_input_as_handled()
-		
+func _on_graph_edit_connection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
+	#get_node("GraphEdit").connect_node(from_node, from_port, to_node, to_port)
+	var graph_edit = get_node("GraphEdit")
+	var to_graph_node = graph_edit.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 = graph_edit.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:
+					$MultipleConnectionsPopup.show()
+					return
+
+	# If no conflict, allow the connection
+	graph_edit.connect_node(from_node, from_port, to_node, to_port)
+
+func _on_graph_edit_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
+	get_node("GraphEdit").disconnect_node(from_node, from_port, to_node, to_port)
+
+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 _on_graph_edit_delete_nodes_request(nodes: Array[StringName]) -> void:
+	var graph_edit = get_node("GraphEdit")
+	undo_redo.create_action("Delete Nodes (Undo only)")
+
+	for node in selected_nodes.keys():
+		if selected_nodes[node]:
+			if node.name in ["inputfile", "outputfile"]:
+				print("can't delete input or output")
+			else:
+				# 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 graph_edit.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()
+
+				# Register undo restore
+				undo_redo.add_undo_method(Callable(graph_edit, "add_child").bind(node_data, true))
+				undo_redo.add_undo_method(Callable(node_data, "set_position_offset").bind(position))
+				for con in conns:
+					undo_redo.add_undo_method(Callable(graph_edit, "connect_node").bind(
+						con["from_node"], con["from_port"],
+						con["to_node"], con["to_port"]
+					))
+				undo_redo.add_undo_method(Callable(self, "set_node_selected").bind(node_data, true))
+
+	# Clear selection
+	selected_nodes = {}
+
+	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_node("GraphEdit").get_connection_list():
+		if con["to_node"] == node.name or con["from_node"] == node.name:
+			get_node("GraphEdit").disconnect_node(con["from_node"], con["from_port"], con["to_node"], con["to_port"])
+			
+#copy and paste nodes with vertical offset on paste
 func copy_selected_nodes():
 	copied_nodes_data.clear()
 	copied_connections.clear()
@@ -215,57 +308,13 @@ func paste_copied_nodes():
 		graph_edit.set_selected(pasted_node)
 		selected_nodes[pasted_node] = true
 
-#logic for connecting, disconnecting and deleteing nodes and connections in GraphEdit
-#mostly taken from https://gdscript.com/solutions/godot-graphnode-and-graphedit-tutorial/
-func _on_graph_edit_connection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
-	#get_node("GraphEdit").connect_node(from_node, from_port, to_node, to_port)
-	var graph_edit = get_node("GraphEdit")
-	var to_graph_node = graph_edit.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 = graph_edit.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:
-					$MultipleConnectionsPopup.show()
-					return
-
-	# If no conflict, allow the connection
-	graph_edit.connect_node(from_node, from_port, to_node, to_port)
-
-func _on_graph_edit_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
-	get_node("GraphEdit").disconnect_node(from_node, from_port, to_node, to_port)
-
-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 _on_graph_edit_delete_nodes_request(nodes: Array[StringName]) -> void:
-	for node in selected_nodes.keys():
-		if selected_nodes[node]:
-			if  node.name == "inputfile":
-				print("can't delete input")
-			elif  node.name == "outputfile":
-				print("can't delete output")
-			else:
-				remove_connections_to_node(node)
-				node.queue_free()
-	selected_nodes = {}
-
-func remove_connections_to_node(node):
-	for con in get_node("GraphEdit").get_connection_list():
-		if con["to_node"] == node.name or con["from_node"] == node.name:
-			get_node("GraphEdit").disconnect_node(con["from_node"], con["from_port"], con["to_node"], con["to_port"])
-			
+	# Remove node with UndoRedo
+	undo_redo.create_action("Paste Nodes")
+	for pasted_node in pasted_nodes:
+		undo_redo.add_undo_method(Callable(graph_edit, "remove_child").bind(pasted_node))
+		undo_redo.add_undo_method(Callable(pasted_node, "queue_free"))
+		undo_redo.add_undo_method(Callable(self, "remove_connections_to_node").bind(pasted_node))
+	undo_redo.commit_action()
 
 #Here be dragons
 #Scans through all nodes and generates a batch file based on their order