From 9ac5365be6503665864afb07abc9e0e6b6232e53 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Thu, 11 Sep 2025 23:00:19 +0300 Subject: [PATCH 01/24] - created graph operations and handled input from parley grap view --- .../graph_operations/delete_operation.gd | 31 ++++ .../graph_operations/delete_operation.gd.uid | 1 + .../graph_operations/graph_operation.gd | 12 ++ .../graph_operations/graph_operation.gd.uid | 1 + addons/parley/graph_operations/paste.gd.uid | 1 + .../graph_operations/paste_operation.gd | 21 +++ .../graph_operations/paste_operation.gd.uid | 1 + addons/parley/views/graph_connection.gd | 44 +++++ addons/parley/views/graph_connection.gd.uid | 1 + addons/parley/views/parley_graph_view.gd | 170 +++++++++++++++++- 10 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 addons/parley/graph_operations/delete_operation.gd create mode 100644 addons/parley/graph_operations/delete_operation.gd.uid create mode 100644 addons/parley/graph_operations/graph_operation.gd create mode 100644 addons/parley/graph_operations/graph_operation.gd.uid create mode 100644 addons/parley/graph_operations/paste.gd.uid create mode 100644 addons/parley/graph_operations/paste_operation.gd create mode 100644 addons/parley/graph_operations/paste_operation.gd.uid create mode 100644 addons/parley/views/graph_connection.gd create mode 100644 addons/parley/views/graph_connection.gd.uid diff --git a/addons/parley/graph_operations/delete_operation.gd b/addons/parley/graph_operations/delete_operation.gd new file mode 100644 index 0000000..1ad146b --- /dev/null +++ b/addons/parley/graph_operations/delete_operation.gd @@ -0,0 +1,31 @@ +# DeleteShortcut.gd +extends GraphOperation +class_name DeleteOperation + +var selectedConnections: Array[GraphConnection] +var selectedNodes: Array[GraphNode] +var graph_edit: GraphEdit + +func _init(_graph_edit: GraphEdit, _selected_connections: Array[GraphConnection], _selected_nodes: Array[GraphNode]) -> void: + graph_edit = _graph_edit + selectedConnections = _selected_connections.duplicate() + selectedNodes = _selected_nodes.duplicate() + +func undo() -> void: + # Re-add deleted nodes + for connection : GraphConnection in selectedConnections: + var result: int = connection.connect_node(graph_edit) + if result == FAILED: + print("Couldn't delete connection: ", connection.as_string()) + + for node : GraphNode in selectedNodes: + graph_edit.add_child(node) + +func do() -> void: + # Remove deleted nodes + for connection : GraphConnection in selectedConnections: + connection.disconnect_node(graph_edit) + + for selectedNode : GraphNode in selectedNodes: + if graph_edit.has_node(NodePath(selectedNode.name)): + graph_edit.remove_child(selectedNode) diff --git a/addons/parley/graph_operations/delete_operation.gd.uid b/addons/parley/graph_operations/delete_operation.gd.uid new file mode 100644 index 0000000..3537f53 --- /dev/null +++ b/addons/parley/graph_operations/delete_operation.gd.uid @@ -0,0 +1 @@ +uid://d3u5heup1nc6i diff --git a/addons/parley/graph_operations/graph_operation.gd b/addons/parley/graph_operations/graph_operation.gd new file mode 100644 index 0000000..48d0400 --- /dev/null +++ b/addons/parley/graph_operations/graph_operation.gd @@ -0,0 +1,12 @@ +extends Object +class_name GraphOperation + +# Base class for all Graph operations + +func undo() -> void: + # override in child class + push_error("undo() not implemented in subclass") + +func do() -> void: + # override in child class + push_error("redo() not implemented in subclass") \ No newline at end of file diff --git a/addons/parley/graph_operations/graph_operation.gd.uid b/addons/parley/graph_operations/graph_operation.gd.uid new file mode 100644 index 0000000..969145e --- /dev/null +++ b/addons/parley/graph_operations/graph_operation.gd.uid @@ -0,0 +1 @@ +uid://bijyelfjxk4v1 diff --git a/addons/parley/graph_operations/paste.gd.uid b/addons/parley/graph_operations/paste.gd.uid new file mode 100644 index 0000000..a5fd7ce --- /dev/null +++ b/addons/parley/graph_operations/paste.gd.uid @@ -0,0 +1 @@ +uid://c10vxi3gjtejs diff --git a/addons/parley/graph_operations/paste_operation.gd b/addons/parley/graph_operations/paste_operation.gd new file mode 100644 index 0000000..2971c42 --- /dev/null +++ b/addons/parley/graph_operations/paste_operation.gd @@ -0,0 +1,21 @@ +# PasteShortcut.gd +extends GraphOperation +class_name PasteOperation + +var pasted_nodes: Array = [] +var graph_edit: GraphEdit + +func _init(_graph_edit: GraphEdit, nodes: Array) -> void: + graph_edit = _graph_edit + pasted_nodes = nodes.duplicate() + +func undo() -> void: + # Remove pasted nodes + for node: GraphNode in pasted_nodes: + if graph_edit.has_node(NodePath(node.name)): + graph_edit.remove_child(node) + +func do() -> void: + # Re-add pasted nodes + for node: GraphNode in pasted_nodes: + graph_edit.add_child(node) \ No newline at end of file diff --git a/addons/parley/graph_operations/paste_operation.gd.uid b/addons/parley/graph_operations/paste_operation.gd.uid new file mode 100644 index 0000000..7665b15 --- /dev/null +++ b/addons/parley/graph_operations/paste_operation.gd.uid @@ -0,0 +1 @@ +uid://66w01kcmm2tw diff --git a/addons/parley/views/graph_connection.gd b/addons/parley/views/graph_connection.gd new file mode 100644 index 0000000..5917e31 --- /dev/null +++ b/addons/parley/views/graph_connection.gd @@ -0,0 +1,44 @@ +# GraphConnection.gd +# Represents a connection between two GraphNodes in a GraphEdit + +extends Object +class_name GraphConnection +var from_node: ParleyGraphNode +var from_port: int +var to_node: ParleyGraphNode +var to_port: int + +var previousFromColor: Color +var previousToColor: Color + +func _init(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> void: + from_node = _from_node + from_port = _from_port + to_node = _to_node + to_port = _to_port + +func disconnect_node(graph_edit: GraphEdit) -> void: + if not graph_edit: + return + graph_edit.disconnect_node(from_node.name, from_port, to_node.name, to_port) + +func connect_node(graph_edit: GraphEdit) -> int: + if not graph_edit: + return FAILED + return graph_edit.connect_node(from_node.name, from_port, to_node.name, to_port) + +func select() -> void: + var output_port_count : int = from_node.get_output_port_count() + previousFromColor = from_node.get_slot_color_right(output_port_count + from_port - 1) + previousToColor = to_node.get_slot_color_left(to_port) + from_node.set_slot_color_right(output_port_count + from_port - 1, Color.DEEP_SKY_BLUE) # "output_port_count + from_port - 1" is this a bug i really don't know but only this works + to_node.set_slot_color_left(to_port, Color.DEEP_SKY_BLUE) + +func unselect() -> void: + var output_port_count : int = from_node.get_output_port_count() + + from_node.set_slot_color_right(output_port_count + from_port - 1,previousFromColor) + to_node.set_slot_color_left(to_port, previousToColor) + +func as_string() -> String: + return "%s:%d -> %s:%d" % [from_node, from_port, to_node, to_port] diff --git a/addons/parley/views/graph_connection.gd.uid b/addons/parley/views/graph_connection.gd.uid new file mode 100644 index 0000000..b0d0442 --- /dev/null +++ b/addons/parley/views/graph_connection.gd.uid @@ -0,0 +1 @@ +uid://bb4mruigkoqs5 diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index fa9d22e..12cb923 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -351,4 +351,172 @@ func set_selected_by_id(id: String, _goto: bool = true) -> void: set_selected(node) _goto_node(node as ParleyGraphNode) return -#endregion + +#region SHORTCUTS + +const CLICK_DISTANCE: float = 50 +var onUnselect: Array[Callable] = [] +var undoHistory: Array[GraphOperation] = [] +var redoHistory: Array[GraphOperation] = [] +var selectedConnections: Array[GraphConnection] = [] +var selectedNodes: Array[GraphNode] = [] + +func _gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton: + _handle_mouse_select(event as InputEventMouseButton) + + if event is InputEventKey: + var key_event: InputEventKey = event as InputEventKey + if key_event.pressed and not key_event.echo: + # Ctrl + C + if key_event.ctrl_pressed and key_event.keycode == KEY_C: + _copy() + # Ctrl + V + elif key_event.ctrl_pressed and key_event.keycode == KEY_V: + _paste() + #Ctrl + Z + elif key_event.ctrl_pressed and key_event.keycode == KEY_Z: + _undo() + #Ctrl + Y + elif key_event.ctrl_pressed and key_event.keycode == KEY_Y: + _redo() + #Ctrl + S + elif key_event.ctrl_pressed and key_event.keycode == KEY_S: + _save() + # Delete + elif key_event.keycode == KEY_DELETE: + _delete_selected() + + +func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: + var foundAnyConnection: bool = false + if mouse_event.pressed and mouse_event.button_index == MOUSE_BUTTON_LEFT: + var pointer_pos: Vector2 = (mouse_event.position + scroll_offset) / zoom + for conn: Dictionary in get_connection_list(): + var from_node_name: String = conn.get("from_node") + var to_node_name: String = conn.get("to_node") + var from_port: int = conn.get("from_port") + var to_port: int = conn.get("to_port") + + var from_node: ParleyGraphNode = get_node(NodePath(from_node_name)) as ParleyGraphNode + var to_node: ParleyGraphNode = get_node(NodePath(to_node_name)) as ParleyGraphNode + if not from_node or not to_node: + continue + + var from_pos: Vector2 = _get_slot_position(from_node, from_port, true) + var to_pos: Vector2 = _get_slot_position(to_node, to_port, false) + var distance: float = get_distance_to_segment(pointer_pos, from_pos, to_pos) * zoom + # print("From: ",from_node ," To", to_node," Comparing x: ", from_pos.x, " ", pointer_pos.x, " ", to_pos.x , " distance: ", distance) + + if distance <= CLICK_DISTANCE: + # basically ctrl+click selects multiple + if not mouse_event.is_command_or_control_pressed(): + for connection :GraphConnection in selectedConnections: + connection.unselect() + selectedConnections.clear() + selectedNodes.clear() + + foundAnyConnection = true + var connection: GraphConnection = GraphConnection.new(from_node, from_port, to_node, to_port) + connection.select() + selectedConnections.append(connection as GraphConnection) + onUnselect.append(func() -> void: + connection.unselect() + selectedConnections.erase(connection) + ) + print("Clicked connection:", connection.as_string()) + break + + if not foundAnyConnection: + while onUnselect.size() > 0: + var callable: Callable = onUnselect.pop_front() + callable.call() + +func _delete_selected()-> void: + if selectedConnections.size() > 0 || selectedNodes.size() > 0: + var deleteConnection: DeleteOperation = DeleteOperation.new(self, selectedConnections, selectedNodes) + deleteConnection.do() + add_undo_operation(deleteConnection) + +func _copy() -> void: + print("Copied selected node(s)") + # TODO: implement copy logic + +func _paste() -> void: + print("Pasted copied node(s)") + # TODO: implement paste logic + +func add_undo_operation(operation: GraphOperation) -> void: + redoHistory.clear() + undoHistory.push_back(operation) + +func _undo() -> void: + if undoHistory.size() > 0: + print("Undo") + var operation: GraphOperation = undoHistory.pop_back() + operation.undo() + redoHistory.push_back(operation) + +func _redo() -> void: + if redoHistory.size() > 0: + print("Redo") + var operation: GraphOperation = redoHistory.pop_back() + operation.do() + add_undo_operation(operation) + +func _save() -> void: + var result: int = _save_dialogue() + if not result == FAILED: + print("Dialog saved!") +#TODO: This doesnt work find out why + +func _save_dialogue() -> int: + if not ast or not ast.resource_path: + push_error(ParleyUtils.log.error_msg("Unable to save Dialogue Sequence. Dialogue Sequence does not exist in the file system, please create one using the file menu in the top left-hand side")) + return ERR_DOES_NOT_EXIST + var ok: int = ResourceSaver.save(ast) + if ok != OK: + push_warning(ParleyUtils.log.warn_msg("Error saving the Dialogue AST: %d" % [ok])) + return ok + # This is needed to correctly reload upon file saves + if Engine.is_editor_hint(): + EditorInterface.get_resource_filesystem().reimport_files([ast.resource_path]) + return OK + +func _get_slot_position(node: GraphNode, slot_idx: int, is_output: bool) -> Vector2: + var local_pos: Vector2 + if is_output: + local_pos = node.get_output_port_position(slot_idx) + else: + local_pos = node.get_input_port_position(slot_idx) + + # Convert to GraphEdit local coordinates + return (node.position + scroll_offset) / zoom + local_pos + +func get_distance_to_segment(point: Vector2, segment_start: Vector2, segment_end: Vector2) -> float: + # Calculate the vector of the line segment. + var segment_vec: Vector2 = segment_end - segment_start + + # If the segment has no length, return the distance to the start point. + if segment_vec.length_squared() == 0.0: + return point.distance_to(segment_start) + + # Project the point onto the line defined by the segment. + # The result 't' is a factor from 0.0 (at segment_start) to 1.0 (at segment_end). + var t:float = (point - segment_start).dot(segment_vec) / segment_vec.length_squared() + + # Clamp 't' to the range [0, 1] to stay within the segment. + # - If t < 0, the closest point is segment_start. + # - If t > 1, the closest point is segment_end. + # - Otherwise, it's a point along the segment. + if t > 1: + return point.distance_to(segment_end) + if t < 0: + return point.distance_to(segment_start) + else: + var closest_point_on_segment: Vector2 = segment_start.lerp(segment_end, t) + + # Return the distance between the original point and the closest point on the segment. + return point.distance_to(closest_point_on_segment) + +#endregion \ No newline at end of file From 0271472f67dcb945c23aec6b4083a3b28003bb7b Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sun, 28 Sep 2025 13:22:51 +0300 Subject: [PATCH 02/24] - selecting multibple connections, deleting connection and nodes works, undo delete WIP --- .../graph_operations/delete_operation.gd | 43 ++++++--- addons/parley/main_panel.gd | 37 ++++++++ addons/parley/main_panel.tscn | 17 +++- ...aph_connection.gd => parley_graph_edge.gd} | 11 ++- addons/parley/views/parley_graph_edge.gd.uid | 1 + addons/parley/views/parley_graph_view.gd | 94 +++++-------------- 6 files changed, 109 insertions(+), 94 deletions(-) rename addons/parley/views/{graph_connection.gd => parley_graph_edge.gd} (92%) create mode 100644 addons/parley/views/parley_graph_edge.gd.uid diff --git a/addons/parley/graph_operations/delete_operation.gd b/addons/parley/graph_operations/delete_operation.gd index 1ad146b..ce35b1c 100644 --- a/addons/parley/graph_operations/delete_operation.gd +++ b/addons/parley/graph_operations/delete_operation.gd @@ -1,31 +1,44 @@ # DeleteShortcut.gd -extends GraphOperation class_name DeleteOperation +extends GraphOperation -var selectedConnections: Array[GraphConnection] -var selectedNodes: Array[GraphNode] -var graph_edit: GraphEdit +var selectedConnections: Array[ParleyGraphEdge] +var selectedNodes: Array[ParleyGraphNode] +var deletedNodes: Array[ParleyGraphNode] +var graph_view: ParleyGraphView -func _init(_graph_edit: GraphEdit, _selected_connections: Array[GraphConnection], _selected_nodes: Array[GraphNode]) -> void: - graph_edit = _graph_edit +func _init(_graph_view: ParleyGraphView, _selected_connections: Array[ParleyGraphEdge], _selected_nodes: Array[ParleyGraphNode]) -> void: + graph_view = _graph_view selectedConnections = _selected_connections.duplicate() selectedNodes = _selected_nodes.duplicate() func undo() -> void: # Re-add deleted nodes - for connection : GraphConnection in selectedConnections: - var result: int = connection.connect_node(graph_edit) + + # TODO this will be implemented shortly + # for node : GraphNode in selectedNodes: + # graph_view.add_child(node) + # graph_view.ast.add_ast_node(node.) + + for connection : ParleyGraphEdge in selectedConnections: + var result: int = connection.connect_node(graph_view) if result == FAILED: print("Couldn't delete connection: ", connection.as_string()) - for node : GraphNode in selectedNodes: - graph_edit.add_child(node) func do() -> void: # Remove deleted nodes - for connection : GraphConnection in selectedConnections: - connection.disconnect_node(graph_edit) + for connection : ParleyGraphEdge in selectedConnections: + connection.disconnect_node(graph_view) + + for selectedNode : ParleyGraphNode in selectedNodes: + if graph_view.has_node(NodePath(selectedNode.name)): + graph_view._on_node_deselected(selectedNode) + graph_view.remove_child(selectedNode) + graph_view.ast.remove_node(selectedNode.id) + deletedNodes.append(selectedNode) - for selectedNode : GraphNode in selectedNodes: - if graph_edit.has_node(NodePath(selectedNode.name)): - graph_edit.remove_child(selectedNode) +func flush() -> void: + for deletedNode : ParleyGraphNode in deletedNodes: + deletedNode.queue_free() + pass \ No newline at end of file diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index 8992d19..6aa73ec 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -55,6 +55,8 @@ var selected_node_ast: ParleyNodeAst: set = _set_selected_node_ast signal dialogue_ast_selected(dialogue_ast: ParleyDialogueSequenceAst) signal node_selected(node_ast: ParleyNodeAst) +var undoHistory: Array[GraphOperation] = [] +var redoHistory: Array[GraphOperation] = [] #region SETUP func _ready() -> void: @@ -599,6 +601,7 @@ func update_edge(edge: ParleyEdgeAst) -> void: # TODO: add to docs func delete_node_by_id(id: String) -> void: + print(id) if not dialogue_ast: return if not selected_node_id or not is_instance_of(selected_node_id, TYPE_STRING): @@ -660,6 +663,40 @@ func _on_docs_button_pressed() -> void: push_error(ParleyUtils.log.error_msg("Unable to navigate to Parley Documentation at %s: %s" % [href, result])) #endregion +func _duplicate_nodes_request() -> void: + + pass + +func _copy_nodes_request() -> void: + pass + +func _paste_nodes_request() -> void: + + pass + +func _delete_selected() -> void: + if graph_view.selectedConnections.size() > 0 || graph_view.selectedNodes.size() > 0: + var deleteConnection: DeleteOperation = DeleteOperation.new(graph_view, graph_view.selectedConnections, graph_view.selectedNodes) + deleteConnection.do() + add_undo_operation(deleteConnection) + +func add_undo_operation(operation: GraphOperation) -> void: + redoHistory.clear() + undoHistory.push_back(operation) + +func _undo() -> void: + if undoHistory.size() > 0: + print("Undo") + var operation: GraphOperation = undoHistory.pop_back() + operation.undo() + redoHistory.push_back(operation) + +func _redo() -> void: + if redoHistory.size() > 0: + print("Redo") + var operation: GraphOperation = redoHistory.pop_back() + operation.do() + add_undo_operation(operation) #region HELPERS func remove_edge(from_node: String, from_slot: int, to_node: String, to_slot: int) -> void: diff --git a/addons/parley/main_panel.tscn b/addons/parley/main_panel.tscn index d87eff6..9cf219a 100644 --- a/addons/parley/main_panel.tscn +++ b/addons/parley/main_panel.tscn @@ -54,7 +54,7 @@ script = ExtResource("28_anq7l") characters = Array[ExtResource("11_7jaih")]([]) id = "" -[sub_resource type="Image" id="Image_8yxrj"] +[sub_resource type="Image" id="Image_kaj3b"] data = { "data": PackedByteArray(255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 255, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 231, 255, 90, 90, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 90, 90, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 42, 255, 90, 90, 0, 255, 94, 94, 0, 255, 91, 91, 42, 255, 93, 93, 233, 255, 92, 92, 232, 255, 93, 93, 41, 255, 90, 90, 0, 255, 94, 94, 0, 255, 91, 91, 42, 255, 93, 93, 233, 255, 92, 92, 232, 255, 92, 92, 0, 255, 92, 92, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 45, 255, 93, 93, 44, 255, 91, 91, 0, 255, 91, 91, 42, 255, 91, 91, 42, 255, 93, 93, 0, 255, 91, 91, 45, 255, 93, 93, 44, 255, 91, 91, 0, 255, 91, 91, 42, 255, 91, 91, 42, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 45, 255, 92, 92, 235, 255, 92, 92, 234, 255, 89, 89, 43, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 45, 255, 92, 92, 235, 255, 92, 92, 234, 255, 89, 89, 43, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 91, 91, 59, 255, 92, 92, 61, 255, 92, 92, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 91, 91, 59, 255, 92, 92, 61, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0), "format": "RGBA8", @@ -63,8 +63,8 @@ data = { "width": 16 } -[sub_resource type="ImageTexture" id="ImageTexture_h2jtb"] -image = SubResource("Image_8yxrj") +[sub_resource type="ImageTexture" id="ImageTexture_s5oy3"] +image = SubResource("Image_kaj3b") [sub_resource type="Resource" id="Resource_7hjis"] script = ExtResource("32_pogw7") @@ -255,7 +255,7 @@ unique_name_in_owner = true layout_mode = 2 tooltip_text = "Navigate to the Parley Documentation." text = "Docs" -icon = SubResource("ImageTexture_h2jtb") +icon = SubResource("ImageTexture_s5oy3") flat = true [node name="EditorView" type="HSplitContainer" parent="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer"] @@ -314,10 +314,19 @@ visible = false [connection signal="node_selected" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/Sidebar" to="." method="_on_sidebar_node_selected"] [connection signal="connection_request" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_on_graph_view_connection_request"] [connection signal="connection_to_empty" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_on_graph_view_connection_to_empty"] +[connection signal="copy_nodes_request" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_copy_nodes_request"] +[connection signal="delete_selected" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_delete_selected"] [connection signal="disconnection_request" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_on_graph_view_disconnection_request"] +[connection signal="duplicate_nodes_request" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_duplicate_nodes_request"] [connection signal="node_deselected" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_on_graph_view_node_deselected"] +[connection signal="node_deselected" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" method="_on_node_deselected"] [connection signal="node_selected" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_on_graph_view_node_selected"] +[connection signal="node_selected" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" method="_on_node_selected"] +[connection signal="paste_nodes_request" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_paste_nodes_request"] +[connection signal="redo_request" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_redo"] +[connection signal="save_request" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_on_save_pressed"] [connection signal="scroll_offset_changed" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_on_graph_view_scroll_offset_changed"] +[connection signal="undo_request" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/GraphView" to="." method="_undo"] [connection signal="sidebar_toggled" from="MarginContainer/MainContainer/HSplitContainer/MainViewPanel/MainViewContainer/EditorView/GraphContainer/BottomPanel" to="." method="_on_bottom_panel_sidebar_toggled"] [connection signal="file_selected" from="OpenFileDialog" to="." method="_on_open_dialog_file_selected"] [connection signal="dialogue_ast_created" from="NewDialogueSequenceModal" to="." method="_on_new_dialogue_sequence_modal_dialogue_ast_created"] diff --git a/addons/parley/views/graph_connection.gd b/addons/parley/views/parley_graph_edge.gd similarity index 92% rename from addons/parley/views/graph_connection.gd rename to addons/parley/views/parley_graph_edge.gd index 5917e31..21857b1 100644 --- a/addons/parley/views/graph_connection.gd +++ b/addons/parley/views/parley_graph_edge.gd @@ -1,8 +1,8 @@ -# GraphConnection.gd +# ParleyGraphEdge.gd # Represents a connection between two GraphNodes in a GraphEdit extends Object -class_name GraphConnection +class_name ParleyGraphEdge var from_node: ParleyGraphNode var from_port: int var to_node: ParleyGraphNode @@ -10,6 +10,7 @@ var to_port: int var previousFromColor: Color var previousToColor: Color +var selected: bool func _init(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> void: from_node = _from_node @@ -28,6 +29,7 @@ func connect_node(graph_edit: GraphEdit) -> int: return graph_edit.connect_node(from_node.name, from_port, to_node.name, to_port) func select() -> void: + selected = true var output_port_count : int = from_node.get_output_port_count() previousFromColor = from_node.get_slot_color_right(output_port_count + from_port - 1) previousToColor = to_node.get_slot_color_left(to_port) @@ -35,9 +37,10 @@ func select() -> void: to_node.set_slot_color_left(to_port, Color.DEEP_SKY_BLUE) func unselect() -> void: - var output_port_count : int = from_node.get_output_port_count() + selected = false - from_node.set_slot_color_right(output_port_count + from_port - 1,previousFromColor) + var output_port_count : int = from_node.get_output_port_count() + from_node.set_slot_color_right(output_port_count + from_port - 1, previousFromColor) to_node.set_slot_color_left(to_port, previousToColor) func as_string() -> String: diff --git a/addons/parley/views/parley_graph_edge.gd.uid b/addons/parley/views/parley_graph_edge.gd.uid new file mode 100644 index 0000000..f32007b --- /dev/null +++ b/addons/parley/views/parley_graph_edge.gd.uid @@ -0,0 +1 @@ +uid://gt7kw2eq3gnf diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index 12cb923..c9cc55b 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -19,6 +19,11 @@ const end_node_scene: PackedScene = preload("../components/end/end_node.tscn") const group_node_scene: PackedScene = preload("../components/group/group_node.tscn") const jump_node_scene: PackedScene = preload("../components/jump/jump_node.tscn") +signal delete_selected() +signal save_request() +signal undo_request() +signal redo_request() + # Called when the node enters the scene tree for the first time. func _ready() -> void: await clear() @@ -354,12 +359,17 @@ func set_selected_by_id(id: String, _goto: bool = true) -> void: #region SHORTCUTS + const CLICK_DISTANCE: float = 50 var onUnselect: Array[Callable] = [] -var undoHistory: Array[GraphOperation] = [] -var redoHistory: Array[GraphOperation] = [] -var selectedConnections: Array[GraphConnection] = [] -var selectedNodes: Array[GraphNode] = [] +var selectedConnections: Array[ParleyGraphEdge] = [] +var selectedNodes: Array[ParleyGraphNode] = [] + +func _on_node_selected(node: Node) -> void: + selectedNodes.append(node) +func _on_node_deselected(node: Node) -> void: + selectedNodes.erase(node) + pass func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton: @@ -368,25 +378,18 @@ func _gui_input(event: InputEvent) -> void: if event is InputEventKey: var key_event: InputEventKey = event as InputEventKey if key_event.pressed and not key_event.echo: - # Ctrl + C - if key_event.ctrl_pressed and key_event.keycode == KEY_C: - _copy() - # Ctrl + V - elif key_event.ctrl_pressed and key_event.keycode == KEY_V: - _paste() #Ctrl + Z - elif key_event.ctrl_pressed and key_event.keycode == KEY_Z: - _undo() + if key_event.ctrl_pressed and key_event.keycode == KEY_Z: + undo_request.emit() #Ctrl + Y elif key_event.ctrl_pressed and key_event.keycode == KEY_Y: - _redo() + redo_request.emit() #Ctrl + S elif key_event.ctrl_pressed and key_event.keycode == KEY_S: - _save() + save_request.emit() # Delete elif key_event.keycode == KEY_DELETE: - _delete_selected() - + delete_selected.emit() func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: var foundAnyConnection: bool = false @@ -411,15 +414,15 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: if distance <= CLICK_DISTANCE: # basically ctrl+click selects multiple if not mouse_event.is_command_or_control_pressed(): - for connection :GraphConnection in selectedConnections: + for connection :ParleyGraphEdge in selectedConnections: connection.unselect() selectedConnections.clear() selectedNodes.clear() foundAnyConnection = true - var connection: GraphConnection = GraphConnection.new(from_node, from_port, to_node, to_port) + var connection: ParleyGraphEdge = ParleyGraphEdge.new(from_node, from_port, to_node, to_port) connection.select() - selectedConnections.append(connection as GraphConnection) + selectedConnections.append(connection as ParleyGraphEdge) onUnselect.append(func() -> void: connection.unselect() selectedConnections.erase(connection) @@ -432,57 +435,6 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: var callable: Callable = onUnselect.pop_front() callable.call() -func _delete_selected()-> void: - if selectedConnections.size() > 0 || selectedNodes.size() > 0: - var deleteConnection: DeleteOperation = DeleteOperation.new(self, selectedConnections, selectedNodes) - deleteConnection.do() - add_undo_operation(deleteConnection) - -func _copy() -> void: - print("Copied selected node(s)") - # TODO: implement copy logic - -func _paste() -> void: - print("Pasted copied node(s)") - # TODO: implement paste logic - -func add_undo_operation(operation: GraphOperation) -> void: - redoHistory.clear() - undoHistory.push_back(operation) - -func _undo() -> void: - if undoHistory.size() > 0: - print("Undo") - var operation: GraphOperation = undoHistory.pop_back() - operation.undo() - redoHistory.push_back(operation) - -func _redo() -> void: - if redoHistory.size() > 0: - print("Redo") - var operation: GraphOperation = redoHistory.pop_back() - operation.do() - add_undo_operation(operation) - -func _save() -> void: - var result: int = _save_dialogue() - if not result == FAILED: - print("Dialog saved!") -#TODO: This doesnt work find out why - -func _save_dialogue() -> int: - if not ast or not ast.resource_path: - push_error(ParleyUtils.log.error_msg("Unable to save Dialogue Sequence. Dialogue Sequence does not exist in the file system, please create one using the file menu in the top left-hand side")) - return ERR_DOES_NOT_EXIST - var ok: int = ResourceSaver.save(ast) - if ok != OK: - push_warning(ParleyUtils.log.warn_msg("Error saving the Dialogue AST: %d" % [ok])) - return ok - # This is needed to correctly reload upon file saves - if Engine.is_editor_hint(): - EditorInterface.get_resource_filesystem().reimport_files([ast.resource_path]) - return OK - func _get_slot_position(node: GraphNode, slot_idx: int, is_output: bool) -> Vector2: var local_pos: Vector2 if is_output: @@ -519,4 +471,4 @@ func get_distance_to_segment(point: Vector2, segment_start: Vector2, segment_end # Return the distance between the original point and the closest point on the segment. return point.distance_to(closest_point_on_segment) -#endregion \ No newline at end of file +#endregion From 376b719d490be638d9cea23b1e20676b1e751b04 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sun, 28 Sep 2025 13:30:36 +0300 Subject: [PATCH 03/24] - naming changes --- .../parley/graph_operations/delete_operation.gd.uid | 1 - .../parley/graph_operations/graph_operation.gd.uid | 1 - ...elete_operation.gd => parley_delete_operation.gd} | 4 ++-- .../graph_operations/parley_delete_operation.gd.uid | 1 + ...{graph_operation.gd => parley_graph_operation.gd} | 2 +- .../graph_operations/parley_graph_operation.gd.uid | 1 + ...{paste_operation.gd => parley_paste_operation.gd} | 4 ++-- .../graph_operations/parley_paste_operation.gd.uid | 1 + addons/parley/graph_operations/paste.gd.uid | 1 - .../parley/graph_operations/paste_operation.gd.uid | 1 - addons/parley/main_panel.gd | 12 ++++++------ 11 files changed, 14 insertions(+), 15 deletions(-) delete mode 100644 addons/parley/graph_operations/delete_operation.gd.uid delete mode 100644 addons/parley/graph_operations/graph_operation.gd.uid rename addons/parley/graph_operations/{delete_operation.gd => parley_delete_operation.gd} (95%) create mode 100644 addons/parley/graph_operations/parley_delete_operation.gd.uid rename addons/parley/graph_operations/{graph_operation.gd => parley_graph_operation.gd} (88%) create mode 100644 addons/parley/graph_operations/parley_graph_operation.gd.uid rename addons/parley/graph_operations/{paste_operation.gd => parley_paste_operation.gd} (88%) create mode 100644 addons/parley/graph_operations/parley_paste_operation.gd.uid delete mode 100644 addons/parley/graph_operations/paste.gd.uid delete mode 100644 addons/parley/graph_operations/paste_operation.gd.uid diff --git a/addons/parley/graph_operations/delete_operation.gd.uid b/addons/parley/graph_operations/delete_operation.gd.uid deleted file mode 100644 index 3537f53..0000000 --- a/addons/parley/graph_operations/delete_operation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://d3u5heup1nc6i diff --git a/addons/parley/graph_operations/graph_operation.gd.uid b/addons/parley/graph_operations/graph_operation.gd.uid deleted file mode 100644 index 969145e..0000000 --- a/addons/parley/graph_operations/graph_operation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bijyelfjxk4v1 diff --git a/addons/parley/graph_operations/delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd similarity index 95% rename from addons/parley/graph_operations/delete_operation.gd rename to addons/parley/graph_operations/parley_delete_operation.gd index ce35b1c..76f97e3 100644 --- a/addons/parley/graph_operations/delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -1,6 +1,6 @@ # DeleteShortcut.gd -class_name DeleteOperation -extends GraphOperation +class_name ParleyDeleteOperation +extends ParleyGraphOperation var selectedConnections: Array[ParleyGraphEdge] var selectedNodes: Array[ParleyGraphNode] diff --git a/addons/parley/graph_operations/parley_delete_operation.gd.uid b/addons/parley/graph_operations/parley_delete_operation.gd.uid new file mode 100644 index 0000000..5cf71de --- /dev/null +++ b/addons/parley/graph_operations/parley_delete_operation.gd.uid @@ -0,0 +1 @@ +uid://p0j3epmvx0me diff --git a/addons/parley/graph_operations/graph_operation.gd b/addons/parley/graph_operations/parley_graph_operation.gd similarity index 88% rename from addons/parley/graph_operations/graph_operation.gd rename to addons/parley/graph_operations/parley_graph_operation.gd index 48d0400..35daeec 100644 --- a/addons/parley/graph_operations/graph_operation.gd +++ b/addons/parley/graph_operations/parley_graph_operation.gd @@ -1,5 +1,5 @@ extends Object -class_name GraphOperation +class_name ParleyGraphOperation # Base class for all Graph operations diff --git a/addons/parley/graph_operations/parley_graph_operation.gd.uid b/addons/parley/graph_operations/parley_graph_operation.gd.uid new file mode 100644 index 0000000..2668dbd --- /dev/null +++ b/addons/parley/graph_operations/parley_graph_operation.gd.uid @@ -0,0 +1 @@ +uid://c7fq04vgdch28 diff --git a/addons/parley/graph_operations/paste_operation.gd b/addons/parley/graph_operations/parley_paste_operation.gd similarity index 88% rename from addons/parley/graph_operations/paste_operation.gd rename to addons/parley/graph_operations/parley_paste_operation.gd index 2971c42..6993d1a 100644 --- a/addons/parley/graph_operations/paste_operation.gd +++ b/addons/parley/graph_operations/parley_paste_operation.gd @@ -1,6 +1,6 @@ # PasteShortcut.gd -extends GraphOperation -class_name PasteOperation +extends ParleyGraphOperation +class_name ParleyPasteOperation var pasted_nodes: Array = [] var graph_edit: GraphEdit diff --git a/addons/parley/graph_operations/parley_paste_operation.gd.uid b/addons/parley/graph_operations/parley_paste_operation.gd.uid new file mode 100644 index 0000000..138c084 --- /dev/null +++ b/addons/parley/graph_operations/parley_paste_operation.gd.uid @@ -0,0 +1 @@ +uid://cu3oresgof8xq diff --git a/addons/parley/graph_operations/paste.gd.uid b/addons/parley/graph_operations/paste.gd.uid deleted file mode 100644 index a5fd7ce..0000000 --- a/addons/parley/graph_operations/paste.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c10vxi3gjtejs diff --git a/addons/parley/graph_operations/paste_operation.gd.uid b/addons/parley/graph_operations/paste_operation.gd.uid deleted file mode 100644 index 7665b15..0000000 --- a/addons/parley/graph_operations/paste_operation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://66w01kcmm2tw diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index 6aa73ec..d844f34 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -55,8 +55,8 @@ var selected_node_ast: ParleyNodeAst: set = _set_selected_node_ast signal dialogue_ast_selected(dialogue_ast: ParleyDialogueSequenceAst) signal node_selected(node_ast: ParleyNodeAst) -var undoHistory: Array[GraphOperation] = [] -var redoHistory: Array[GraphOperation] = [] +var undoHistory: Array[ParleyGraphOperation] = [] +var redoHistory: Array[ParleyGraphOperation] = [] #region SETUP func _ready() -> void: @@ -676,25 +676,25 @@ func _paste_nodes_request() -> void: func _delete_selected() -> void: if graph_view.selectedConnections.size() > 0 || graph_view.selectedNodes.size() > 0: - var deleteConnection: DeleteOperation = DeleteOperation.new(graph_view, graph_view.selectedConnections, graph_view.selectedNodes) + var deleteConnection: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selectedConnections, graph_view.selectedNodes) deleteConnection.do() add_undo_operation(deleteConnection) -func add_undo_operation(operation: GraphOperation) -> void: +func add_undo_operation(operation: ParleyGraphOperation) -> void: redoHistory.clear() undoHistory.push_back(operation) func _undo() -> void: if undoHistory.size() > 0: print("Undo") - var operation: GraphOperation = undoHistory.pop_back() + var operation: ParleyGraphOperation = undoHistory.pop_back() operation.undo() redoHistory.push_back(operation) func _redo() -> void: if redoHistory.size() > 0: print("Redo") - var operation: GraphOperation = redoHistory.pop_back() + var operation: ParleyGraphOperation = redoHistory.pop_back() operation.do() add_undo_operation(operation) From 50aae38fee0388a917f87b71e5606b33e35a8cb4 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sun, 12 Oct 2025 20:59:39 +0300 Subject: [PATCH 04/24] - delete redo undo cycle mostly done --- .../parley_delete_operation.gd | 105 +++++++++++++----- addons/parley/models/dialogue_sequence_ast.gd | 12 +- addons/parley/views/parley_graph_edge.gd | 41 ++++--- addons/parley/views/parley_graph_view.gd | 28 +++-- 4 files changed, 135 insertions(+), 51 deletions(-) diff --git a/addons/parley/graph_operations/parley_delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd index 76f97e3..d1e894d 100644 --- a/addons/parley/graph_operations/parley_delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -2,43 +2,94 @@ class_name ParleyDeleteOperation extends ParleyGraphOperation -var selectedConnections: Array[ParleyGraphEdge] -var selectedNodes: Array[ParleyGraphNode] -var deletedNodes: Array[ParleyGraphNode] +var selected_connections: Array[ParleyGraphEdge] +var selected_nodes: Array[ParleyGraphNode] +var deleted_node_datas: Array[NodeData] var graph_view: ParleyGraphView func _init(_graph_view: ParleyGraphView, _selected_connections: Array[ParleyGraphEdge], _selected_nodes: Array[ParleyGraphNode]) -> void: graph_view = _graph_view - selectedConnections = _selected_connections.duplicate() - selectedNodes = _selected_nodes.duplicate() + selected_connections = _selected_connections.duplicate() + selected_nodes = _selected_nodes.duplicate() func undo() -> void: + pass # Re-add deleted nodes - # TODO this will be implemented shortly - # for node : GraphNode in selectedNodes: - # graph_view.add_child(node) - # graph_view.ast.add_ast_node(node.) - - for connection : ParleyGraphEdge in selectedConnections: - var result: int = connection.connect_node(graph_view) - if result == FAILED: - print("Couldn't delete connection: ", connection.as_string()) + selected_nodes.clear() + var graph_nodes: Dictionary = {} + for node_data : NodeData in deleted_node_datas: + var deleted_node : ParleyNodeAst = node_data.node_ast + graph_view._add_node(graph_nodes, deleted_node) + graph_view.ast.add_node_from_ast(deleted_node) + var added_node : ParleyGraphNode = graph_nodes[deleted_node.id] + added_node.position = deleted_node.position + for node_data : NodeData in deleted_node_datas: + for connection : ParleyGraphEdge in node_data.connections: + connection.to_node = graph_view.get_node(NodePath(connection.to_node_name)) as ParleyGraphNode + connection.from_node = graph_view.get_node(NodePath(connection.from_node_name)) as ParleyGraphNode + if node_data.node_name == connection.to_node_name: + selected_nodes.append(connection.to_node) + pass + elif node_data.node_name == connection.from_node_name: + selected_nodes.append(connection.from_node) + pass + connection.connect_node(graph_view) + + for connection : ParleyGraphEdge in selected_connections: + connection.to_node = graph_view.get_node(NodePath(connection.to_node_name)) as ParleyGraphNode + connection.from_node = graph_view.get_node(NodePath(connection.from_node_name)) as ParleyGraphNode + connection.connect_node(graph_view) + + graph_view.generate() func do() -> void: - # Remove deleted nodes - for connection : ParleyGraphEdge in selectedConnections: + for selected_node : ParleyGraphNode in selected_nodes: + if graph_view.has_node(NodePath(selected_node.name)): + var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node.id) + var connections : Array[ParleyGraphEdge] = get_connections_for_node(graph_view, selected_node) + deleted_node_datas.append(NodeData.new(selected_node.name, ast, connections)) + + for selected_node : ParleyGraphNode in selected_nodes: + if graph_view.has_node(NodePath(selected_node.name)): + print("remove node") + var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node.id) + graph_view._on_node_deselected(selected_node) + graph_view.remove_child(selected_node) + graph_view.ast.remove_node(selected_node.id) + + for connection : ParleyGraphEdge in selected_connections: + print("disconnect connection") connection.disconnect_node(graph_view) + + graph_view.generate() + +func get_connections_for_node(graph_edit: GraphEdit, node: GraphNode) -> Array[ParleyGraphEdge]: + var result: Array[ParleyGraphEdge] = [] + var connections: Array[Dictionary] = graph_edit.get_connection_list() + + for conn: Dictionary in connections: + var from_name: String = conn.get("from_node") + var to_name: String = conn.get("to_node") + var from_port: int = conn.get("from_port") + var to_port: int = conn.get("to_port") + var to_node: ParleyGraphNode = graph_view.get_node(NodePath(to_name)) as ParleyGraphNode + var from_node: ParleyGraphNode = graph_view.get_node(NodePath(from_name)) as ParleyGraphNode + + if from_name == node.name or to_name == node.name: + result.append(ParleyGraphEdge.new(from_node, from_port, to_node, to_port)) + + return result + + +class NodeData: + var node_ast: ParleyNodeAst + var connections: Array[ParleyGraphEdge] + var node_name: String + + func _init(_node_name: String, _node_ast :ParleyNodeAst, _connections: Array[ParleyGraphEdge]) -> void: + node_name = _node_name + node_ast = _node_ast + connections = _connections - for selectedNode : ParleyGraphNode in selectedNodes: - if graph_view.has_node(NodePath(selectedNode.name)): - graph_view._on_node_deselected(selectedNode) - graph_view.remove_child(selectedNode) - graph_view.ast.remove_node(selectedNode.id) - deletedNodes.append(selectedNode) - -func flush() -> void: - for deletedNode : ParleyGraphNode in deletedNodes: - deletedNode.queue_free() - pass \ No newline at end of file diff --git a/addons/parley/models/dialogue_sequence_ast.gd b/addons/parley/models/dialogue_sequence_ast.gd index 50a52a8..60b1e57 100644 --- a/addons/parley/models/dialogue_sequence_ast.gd +++ b/addons/parley/models/dialogue_sequence_ast.gd @@ -144,6 +144,17 @@ func add_new_node(type: Type, position: Vector2 = Vector2.ZERO) -> ParleyNodeAst _emit_dialogue_updated() return ast_node +func add_node_from_ast(node_ast: ParleyNodeAst) -> ParleyNodeAst: + # Probably need to make sure the provided ID doesn't already exist + for node : ParleyNodeAst in nodes: + if node.id == node_ast.id: + print("Id already exists in dialogue sequence") + return + + # Also worth storing the insertion index somewhere as well? + nodes.append(node_ast) + _emit_dialogue_updated() + return node_ast ## Update Node AST position func update_node_position(ast_node_id: String, position: Vector2) -> void: @@ -241,7 +252,6 @@ func remove_node(node_id: String) -> void: _emit_dialogue_updated() - ## Find a Node AST by its ID. ## Example: ast.find_node_by_id("1") func find_node_by_id(id: String) -> ParleyNodeAst: diff --git a/addons/parley/views/parley_graph_edge.gd b/addons/parley/views/parley_graph_edge.gd index 21857b1..5b32923 100644 --- a/addons/parley/views/parley_graph_edge.gd +++ b/addons/parley/views/parley_graph_edge.gd @@ -4,9 +4,13 @@ extends Object class_name ParleyGraphEdge var from_node: ParleyGraphNode +var from_slot: int var from_port: int var to_node: ParleyGraphNode +var to_slot: int var to_port: int +var to_node_name: String +var from_node_name: String var previousFromColor: Color var previousToColor: Color @@ -17,31 +21,40 @@ func _init(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNo from_port = _from_port to_node = _to_node to_port = _to_port + from_slot = from_node.get_output_port_slot(from_port) + to_slot = to_node.get_input_port_slot(to_port) + from_node_name = from_node.name + to_node_name = to_node.name -func disconnect_node(graph_edit: GraphEdit) -> void: - if not graph_edit: +func disconnect_node(graph_view: ParleyGraphView) -> void: + if not graph_view: return - graph_edit.disconnect_node(from_node.name, from_port, to_node.name, to_port) + graph_view.disconnect_node(from_node_name, from_port, to_node_name, to_port) + graph_view.ast.remove_edge(from_node_name, from_port, to_node_name, to_port) -func connect_node(graph_edit: GraphEdit) -> int: - if not graph_edit: +func connect_node(graph_view: ParleyGraphView) -> int: + if not graph_view: return FAILED - return graph_edit.connect_node(from_node.name, from_port, to_node.name, to_port) + return graph_view.connect_node(from_node_name, from_port, to_node_name, to_port) func select() -> void: + if selected: + return selected = true - var output_port_count : int = from_node.get_output_port_count() - previousFromColor = from_node.get_slot_color_right(output_port_count + from_port - 1) - previousToColor = to_node.get_slot_color_left(to_port) - from_node.set_slot_color_right(output_port_count + from_port - 1, Color.DEEP_SKY_BLUE) # "output_port_count + from_port - 1" is this a bug i really don't know but only this works - to_node.set_slot_color_left(to_port, Color.DEEP_SKY_BLUE) + + previousFromColor = from_node.get_slot_color_right(from_slot) + previousToColor = to_node.get_slot_color_left(to_slot) + from_node.set_slot_color_right(from_slot, Color.DEEP_SKY_BLUE) # "output_port_count + from_port - 1" is this a bug i really don't know but only this works + to_node.set_slot_color_left(to_slot, Color.DEEP_SKY_BLUE) func unselect() -> void: + if not selected: + return + selected = false - var output_port_count : int = from_node.get_output_port_count() - from_node.set_slot_color_right(output_port_count + from_port - 1, previousFromColor) - to_node.set_slot_color_left(to_port, previousToColor) + from_node.set_slot_color_right(from_slot, previousFromColor) + to_node.set_slot_color_left(to_slot, previousToColor) func as_string() -> String: return "%s:%d -> %s:%d" % [from_node, from_port, to_node, to_port] diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index c9cc55b..cdc7a19 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -370,7 +370,6 @@ func _on_node_selected(node: Node) -> void: func _on_node_deselected(node: Node) -> void: selectedNodes.erase(node) pass - func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton: _handle_mouse_select(event as InputEventMouseButton) @@ -420,21 +419,32 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: selectedNodes.clear() foundAnyConnection = true - var connection: ParleyGraphEdge = ParleyGraphEdge.new(from_node, from_port, to_node, to_port) - connection.select() - selectedConnections.append(connection as ParleyGraphEdge) - onUnselect.append(func() -> void: - connection.unselect() + var connection: ParleyGraphEdge = _find_existing_connection(from_node, from_port, to_node, to_port) + if connection == null: + connection = ParleyGraphEdge.new(from_node, from_port, to_node, to_port) + connection.select() + selectedConnections.append(connection as ParleyGraphEdge) + onUnselect.append(func() -> void: + connection.unselect() + selectedConnections.erase(connection) + ) + print("Selected connection:", connection.as_string()) + else: selectedConnections.erase(connection) - ) - print("Clicked connection:", connection.as_string()) + connection.unselect() + print("Unselected existing connection:", connection.as_string()) break - if not foundAnyConnection: + if not foundAnyConnection && not mouse_event.is_command_or_control_pressed(): while onUnselect.size() > 0: var callable: Callable = onUnselect.pop_front() callable.call() +func _find_existing_connection(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> ParleyGraphEdge: + for con: ParleyGraphEdge in selectedConnections: + if con.from_node == _from_node && con.from_port == _from_port && con.to_node == _to_node && con.to_port == _to_port: + return con + return null func _get_slot_position(node: GraphNode, slot_idx: int, is_output: bool) -> Vector2: var local_pos: Vector2 if is_output: From aa310bd53200c0412543595e54f7f98c560dfc94 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sat, 18 Oct 2025 13:31:25 +0300 Subject: [PATCH 05/24] - delete operation improvements - undo redo history grouped by dialoge sequence ast --- .../parley_delete_operation.gd | 66 +++++++++---------- addons/parley/main_panel.gd | 23 ++++--- addons/parley/models/dialogue_sequence_ast.gd | 5 ++ addons/parley/views/parley_graph_edge.gd | 9 ++- addons/parley/views/parley_graph_view.gd | 12 +++- 5 files changed, 64 insertions(+), 51 deletions(-) diff --git a/addons/parley/graph_operations/parley_delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd index d1e894d..b836477 100644 --- a/addons/parley/graph_operations/parley_delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -3,20 +3,17 @@ class_name ParleyDeleteOperation extends ParleyGraphOperation var selected_connections: Array[ParleyGraphEdge] -var selected_nodes: Array[ParleyGraphNode] +var selected_node_ids: Array[String] var deleted_node_datas: Array[NodeData] var graph_view: ParleyGraphView func _init(_graph_view: ParleyGraphView, _selected_connections: Array[ParleyGraphEdge], _selected_nodes: Array[ParleyGraphNode]) -> void: graph_view = _graph_view selected_connections = _selected_connections.duplicate() - selected_nodes = _selected_nodes.duplicate() - + for node: ParleyGraphNode in _selected_nodes: + selected_node_ids.append(node.id) + func undo() -> void: - pass - # Re-add deleted nodes - - selected_nodes.clear() var graph_nodes: Dictionary = {} for node_data : NodeData in deleted_node_datas: var deleted_node : ParleyNodeAst = node_data.node_ast @@ -27,47 +24,43 @@ func undo() -> void: for node_data : NodeData in deleted_node_datas: for connection : ParleyGraphEdge in node_data.connections: - connection.to_node = graph_view.get_node(NodePath(connection.to_node_name)) as ParleyGraphNode - connection.from_node = graph_view.get_node(NodePath(connection.from_node_name)) as ParleyGraphNode - if node_data.node_name == connection.to_node_name: - selected_nodes.append(connection.to_node) - pass - elif node_data.node_name == connection.from_node_name: - selected_nodes.append(connection.from_node) - pass + connection.to_node = graph_view.get_node(NodePath(connection.to_node_name)) + connection.from_node = graph_view.get_node(NodePath(connection.from_node_name)) connection.connect_node(graph_view) for connection : ParleyGraphEdge in selected_connections: - connection.to_node = graph_view.get_node(NodePath(connection.to_node_name)) as ParleyGraphNode - connection.from_node = graph_view.get_node(NodePath(connection.from_node_name)) as ParleyGraphNode + connection.to_node = graph_view.get_node(NodePath(connection.to_node_name)) + connection.from_node = graph_view.get_node(NodePath(connection.from_node_name)) connection.connect_node(graph_view) graph_view.generate() func do() -> void: - for selected_node : ParleyGraphNode in selected_nodes: - if graph_view.has_node(NodePath(selected_node.name)): - var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node.id) - var connections : Array[ParleyGraphEdge] = get_connections_for_node(graph_view, selected_node) - deleted_node_datas.append(NodeData.new(selected_node.name, ast, connections)) + deleted_node_datas.clear() + for selected_node_id : String in selected_node_ids: + var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node_id) + if ast != null: + var connections : Array[ParleyGraphEdge] = get_connections_for_node(graph_view, selected_node_id) + deleted_node_datas.append(NodeData.new(selected_node_id, ast, connections)) - for selected_node : ParleyGraphNode in selected_nodes: - if graph_view.has_node(NodePath(selected_node.name)): - print("remove node") - var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node.id) + for selected_node_id : String in selected_node_ids: + var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node_id) + if ast != null: + print(selected_node_id) + var selected_node : ParleyGraphNode = graph_view.find_node_by_id(selected_node_id) graph_view._on_node_deselected(selected_node) graph_view.remove_child(selected_node) - graph_view.ast.remove_node(selected_node.id) + graph_view.ast.remove_node(selected_node_id) + graph_view._on_connections_deselected() for connection : ParleyGraphEdge in selected_connections: - print("disconnect connection") connection.disconnect_node(graph_view) - + graph_view.generate() -func get_connections_for_node(graph_edit: GraphEdit, node: GraphNode) -> Array[ParleyGraphEdge]: +func get_connections_for_node(graph_view: ParleyGraphView, node_id: String) -> Array[ParleyGraphEdge]: var result: Array[ParleyGraphEdge] = [] - var connections: Array[Dictionary] = graph_edit.get_connection_list() + var connections: Array[Dictionary] = graph_view.get_connection_list() for conn: Dictionary in connections: var from_name: String = conn.get("from_node") @@ -77,8 +70,9 @@ func get_connections_for_node(graph_edit: GraphEdit, node: GraphNode) -> Array[P var to_node: ParleyGraphNode = graph_view.get_node(NodePath(to_name)) as ParleyGraphNode var from_node: ParleyGraphNode = graph_view.get_node(NodePath(from_name)) as ParleyGraphNode - if from_name == node.name or to_name == node.name: - result.append(ParleyGraphEdge.new(from_node, from_port, to_node, to_port)) + if to_node.id == node_id or from_node.id == node_id: + var edge_ast: ParleyEdgeAst = graph_view.ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) + result.append(ParleyGraphEdge.new(edge_ast, from_node, from_port, to_node, to_port)) return result @@ -86,10 +80,10 @@ func get_connections_for_node(graph_edit: GraphEdit, node: GraphNode) -> Array[P class NodeData: var node_ast: ParleyNodeAst var connections: Array[ParleyGraphEdge] - var node_name: String + var node_id: String - func _init(_node_name: String, _node_ast :ParleyNodeAst, _connections: Array[ParleyGraphEdge]) -> void: - node_name = _node_name + func _init(_node_id: String, _node_ast :ParleyNodeAst, _connections: Array[ParleyGraphEdge]) -> void: + node_id = _node_id node_ast = _node_ast connections = _connections diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index d844f34..797964e 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -55,8 +55,8 @@ var selected_node_ast: ParleyNodeAst: set = _set_selected_node_ast signal dialogue_ast_selected(dialogue_ast: ParleyDialogueSequenceAst) signal node_selected(node_ast: ParleyNodeAst) -var undoHistory: Array[ParleyGraphOperation] = [] -var redoHistory: Array[ParleyGraphOperation] = [] +var undoHistory: Dictionary = {} +var redoHistory: Dictionary = {} #region SETUP func _ready() -> void: @@ -681,20 +681,25 @@ func _delete_selected() -> void: add_undo_operation(deleteConnection) func add_undo_operation(operation: ParleyGraphOperation) -> void: - redoHistory.clear() - undoHistory.push_back(operation) + if not undoHistory.has(dialogue_ast): + undoHistory[dialogue_ast] = [] as Array[ParleyGraphOperation] + if not redoHistory.has(dialogue_ast): + redoHistory[dialogue_ast] = [] as Array[ParleyGraphOperation] + + redoHistory[dialogue_ast].clear() + undoHistory[dialogue_ast].push_back(operation) func _undo() -> void: - if undoHistory.size() > 0: + if undoHistory[dialogue_ast].size() > 0: print("Undo") - var operation: ParleyGraphOperation = undoHistory.pop_back() + var operation: ParleyGraphOperation = undoHistory[dialogue_ast].pop_back() operation.undo() - redoHistory.push_back(operation) + redoHistory[dialogue_ast].push_back(operation) func _redo() -> void: - if redoHistory.size() > 0: + if redoHistory[dialogue_ast].size() > 0: print("Redo") - var operation: ParleyGraphOperation = redoHistory.pop_back() + var operation: ParleyGraphOperation = redoHistory[dialogue_ast].pop_back() operation.do() add_undo_operation(operation) diff --git a/addons/parley/models/dialogue_sequence_ast.gd b/addons/parley/models/dialogue_sequence_ast.gd index 60b1e57..321b5b5 100644 --- a/addons/parley/models/dialogue_sequence_ast.gd +++ b/addons/parley/models/dialogue_sequence_ast.gd @@ -706,6 +706,11 @@ func _emit_dialogue_updated() -> void: if is_ready: dialogue_updated.emit(self) +func get_edge_ast(from_node: String, from_slot: int, to_node: String, to_slot: int) -> ParleyEdgeAst: + for edge: ParleyEdgeAst in edges: + if edge.from_node == from_node && edge.from_slot == from_slot && edge.to_node == to_node && edge.to_slot == to_slot: + return edge + return null func _to_string() -> String: return "ParleyDialogueSequenceAst" % [nodes.size(), edges.size()] diff --git a/addons/parley/views/parley_graph_edge.gd b/addons/parley/views/parley_graph_edge.gd index 5b32923..866270c 100644 --- a/addons/parley/views/parley_graph_edge.gd +++ b/addons/parley/views/parley_graph_edge.gd @@ -11,12 +11,13 @@ var to_slot: int var to_port: int var to_node_name: String var from_node_name: String +var edge_ast: ParleyEdgeAst var previousFromColor: Color var previousToColor: Color var selected: bool -func _init(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> void: +func _init(_edge_ast: ParleyEdgeAst, _from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> void: from_node = _from_node from_port = _from_port to_node = _to_node @@ -25,16 +26,18 @@ func _init(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNo to_slot = to_node.get_input_port_slot(to_port) from_node_name = from_node.name to_node_name = to_node.name + edge_ast = _edge_ast func disconnect_node(graph_view: ParleyGraphView) -> void: if not graph_view: return + graph_view.ast.remove_edge(from_node.id, from_port, to_node.id, to_port) graph_view.disconnect_node(from_node_name, from_port, to_node_name, to_port) - graph_view.ast.remove_edge(from_node_name, from_port, to_node_name, to_port) func connect_node(graph_view: ParleyGraphView) -> int: if not graph_view: return FAILED + graph_view.ast.edges.append(edge_ast) return graph_view.connect_node(from_node_name, from_port, to_node_name, to_port) func select() -> void: @@ -57,4 +60,4 @@ func unselect() -> void: to_node.set_slot_color_left(to_slot, previousToColor) func as_string() -> String: - return "%s:%d -> %s:%d" % [from_node, from_port, to_node, to_port] + return "%s:%d -> %s:%d" % [from_node_name, from_port, to_node_name, to_port] diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index cdc7a19..62a0e41 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -370,6 +370,11 @@ func _on_node_selected(node: Node) -> void: func _on_node_deselected(node: Node) -> void: selectedNodes.erase(node) pass +func _on_connections_deselected() -> void: + while onUnselect.size() > 0: + var callable: Callable = onUnselect.pop_front() + callable.call() + func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton: _handle_mouse_select(event as InputEventMouseButton) @@ -420,19 +425,20 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: foundAnyConnection = true var connection: ParleyGraphEdge = _find_existing_connection(from_node, from_port, to_node, to_port) + var edge_ast: ParleyEdgeAst = ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) if connection == null: - connection = ParleyGraphEdge.new(from_node, from_port, to_node, to_port) + connection = ParleyGraphEdge.new(edge_ast, from_node, from_port, to_node, to_port) connection.select() selectedConnections.append(connection as ParleyGraphEdge) onUnselect.append(func() -> void: connection.unselect() selectedConnections.erase(connection) ) - print("Selected connection:", connection.as_string()) + print("Selected connection: ", connection.as_string()) else: selectedConnections.erase(connection) connection.unselect() - print("Unselected existing connection:", connection.as_string()) + print("Unselected existing connection: ", connection.as_string()) break if not foundAnyConnection && not mouse_event.is_command_or_control_pressed(): From 322f9e32f18f26a95104e51674a60e49eff71e23 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sun, 16 Nov 2025 17:21:35 +0300 Subject: [PATCH 06/24] - bug fixes and improvements --- .../parley_delete_operation.gd | 4 -- addons/parley/main_panel.gd | 37 +++++++------- addons/parley/views/parley_graph_edge.gd | 17 +++++-- addons/parley/views/parley_graph_view.gd | 48 ++++++++++--------- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/addons/parley/graph_operations/parley_delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd index b836477..4a5bd7e 100644 --- a/addons/parley/graph_operations/parley_delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -24,13 +24,9 @@ func undo() -> void: for node_data : NodeData in deleted_node_datas: for connection : ParleyGraphEdge in node_data.connections: - connection.to_node = graph_view.get_node(NodePath(connection.to_node_name)) - connection.from_node = graph_view.get_node(NodePath(connection.from_node_name)) connection.connect_node(graph_view) for connection : ParleyGraphEdge in selected_connections: - connection.to_node = graph_view.get_node(NodePath(connection.to_node_name)) - connection.from_node = graph_view.get_node(NodePath(connection.from_node_name)) connection.connect_node(graph_view) graph_view.generate() diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index 797964e..6be7e85 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -55,8 +55,8 @@ var selected_node_ast: ParleyNodeAst: set = _set_selected_node_ast signal dialogue_ast_selected(dialogue_ast: ParleyDialogueSequenceAst) signal node_selected(node_ast: ParleyNodeAst) -var undoHistory: Dictionary = {} -var redoHistory: Dictionary = {} +var undo_history: Dictionary = {} +var redo_history: Dictionary = {} #region SETUP func _ready() -> void: @@ -675,33 +675,36 @@ func _paste_nodes_request() -> void: pass func _delete_selected() -> void: - if graph_view.selectedConnections.size() > 0 || graph_view.selectedNodes.size() > 0: - var deleteConnection: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selectedConnections, graph_view.selectedNodes) + if graph_view.selected_connections.size() > 0 || graph_view.selected_nodes.size() > 0: + var deleteConnection: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selected_connections, graph_view.selected_nodes) deleteConnection.do() add_undo_operation(deleteConnection) +func _validate_history() -> void: + if not undo_history.has(dialogue_ast): + undo_history[dialogue_ast] = [] as Array[ParleyGraphOperation] + if not redo_history.has(dialogue_ast): + redo_history[dialogue_ast] = [] as Array[ParleyGraphOperation] func add_undo_operation(operation: ParleyGraphOperation) -> void: - if not undoHistory.has(dialogue_ast): - undoHistory[dialogue_ast] = [] as Array[ParleyGraphOperation] - if not redoHistory.has(dialogue_ast): - redoHistory[dialogue_ast] = [] as Array[ParleyGraphOperation] - - redoHistory[dialogue_ast].clear() - undoHistory[dialogue_ast].push_back(operation) + _validate_history() + redo_history[dialogue_ast].clear() + undo_history[dialogue_ast].push_back(operation) func _undo() -> void: - if undoHistory[dialogue_ast].size() > 0: + _validate_history() + if undo_history[dialogue_ast].size() > 0: print("Undo") - var operation: ParleyGraphOperation = undoHistory[dialogue_ast].pop_back() + var operation: ParleyGraphOperation = undo_history[dialogue_ast].pop_back() operation.undo() - redoHistory[dialogue_ast].push_back(operation) + redo_history[dialogue_ast].push_back(operation) func _redo() -> void: - if redoHistory[dialogue_ast].size() > 0: + _validate_history() + if redo_history[dialogue_ast].size() > 0: print("Redo") - var operation: ParleyGraphOperation = redoHistory[dialogue_ast].pop_back() + var operation: ParleyGraphOperation = redo_history[dialogue_ast].pop_back() operation.do() - add_undo_operation(operation) + undo_history[dialogue_ast].push_back(operation) #region HELPERS func remove_edge(from_node: String, from_slot: int, to_node: String, to_slot: int) -> void: diff --git a/addons/parley/views/parley_graph_edge.gd b/addons/parley/views/parley_graph_edge.gd index 866270c..0459a89 100644 --- a/addons/parley/views/parley_graph_edge.gd +++ b/addons/parley/views/parley_graph_edge.gd @@ -4,9 +4,11 @@ extends Object class_name ParleyGraphEdge var from_node: ParleyGraphNode +var from_node_id: String var from_slot: int var from_port: int var to_node: ParleyGraphNode +var to_node_id: String var to_slot: int var to_port: int var to_node_name: String @@ -24,6 +26,8 @@ func _init(_edge_ast: ParleyEdgeAst, _from_node: ParleyGraphNode, _from_port: in to_port = _to_port from_slot = from_node.get_output_port_slot(from_port) to_slot = to_node.get_input_port_slot(to_port) + from_node_id = from_node.id + to_node_id = to_node.id from_node_name = from_node.name to_node_name = to_node.name edge_ast = _edge_ast @@ -31,12 +35,14 @@ func _init(_edge_ast: ParleyEdgeAst, _from_node: ParleyGraphNode, _from_port: in func disconnect_node(graph_view: ParleyGraphView) -> void: if not graph_view: return - graph_view.ast.remove_edge(from_node.id, from_port, to_node.id, to_port) + + graph_view.ast.remove_edge(from_node_id, from_port, to_node_id, to_port) graph_view.disconnect_node(from_node_name, from_port, to_node_name, to_port) func connect_node(graph_view: ParleyGraphView) -> int: if not graph_view: return FAILED + graph_view.ast.edges.append(edge_ast) return graph_view.connect_node(from_node_name, from_port, to_node_name, to_port) @@ -44,18 +50,19 @@ func select() -> void: if selected: return selected = true - + if not is_instance_valid(from_node) or not is_instance_valid(to_node): + return previousFromColor = from_node.get_slot_color_right(from_slot) previousToColor = to_node.get_slot_color_left(to_slot) - from_node.set_slot_color_right(from_slot, Color.DEEP_SKY_BLUE) # "output_port_count + from_port - 1" is this a bug i really don't know but only this works + from_node.set_slot_color_right(from_slot, Color.DEEP_SKY_BLUE) to_node.set_slot_color_left(to_slot, Color.DEEP_SKY_BLUE) func unselect() -> void: if not selected: return - selected = false - + if not is_instance_valid(from_node) or not is_instance_valid(to_node): + return from_node.set_slot_color_right(from_slot, previousFromColor) to_node.set_slot_color_left(to_slot, previousToColor) diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index 62a0e41..1243885 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -34,6 +34,7 @@ func _exit_tree() -> void: await clear() ast = null connections = [] + _clear_selection() func generate(arrange: bool = false) -> void: @@ -361,18 +362,18 @@ func set_selected_by_id(id: String, _goto: bool = true) -> void: const CLICK_DISTANCE: float = 50 -var onUnselect: Array[Callable] = [] -var selectedConnections: Array[ParleyGraphEdge] = [] -var selectedNodes: Array[ParleyGraphNode] = [] +var on_unselect: Array[Callable] = [] +var selected_connections: Array[ParleyGraphEdge] = [] +var selected_nodes: Array[ParleyGraphNode] = [] func _on_node_selected(node: Node) -> void: - selectedNodes.append(node) + selected_nodes.append(node) func _on_node_deselected(node: Node) -> void: - selectedNodes.erase(node) + selected_nodes.erase(node) pass func _on_connections_deselected() -> void: - while onUnselect.size() > 0: - var callable: Callable = onUnselect.pop_front() + while on_unselect.size() > 0: + var callable: Callable = on_unselect.pop_front() callable.call() func _gui_input(event: InputEvent) -> void: @@ -383,18 +384,24 @@ func _gui_input(event: InputEvent) -> void: var key_event: InputEventKey = event as InputEventKey if key_event.pressed and not key_event.echo: #Ctrl + Z - if key_event.ctrl_pressed and key_event.keycode == KEY_Z: + if key_event.is_action_pressed("ui_undo"): undo_request.emit() #Ctrl + Y - elif key_event.ctrl_pressed and key_event.keycode == KEY_Y: + elif key_event.is_action_pressed("ui_redo"): redo_request.emit() #Ctrl + S - elif key_event.ctrl_pressed and key_event.keycode == KEY_S: + elif key_event.is_command_or_control_pressed() and key_event.keycode == KEY_S: save_request.emit() # Delete elif key_event.keycode == KEY_DELETE: delete_selected.emit() - + +func _clear_selection() -> void: + for connection :ParleyGraphEdge in selected_connections: + connection.unselect() + selected_connections.clear() + selected_nodes.clear() + func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: var foundAnyConnection: bool = false if mouse_event.pressed and mouse_event.button_index == MOUSE_BUTTON_LEFT: @@ -418,10 +425,7 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: if distance <= CLICK_DISTANCE: # basically ctrl+click selects multiple if not mouse_event.is_command_or_control_pressed(): - for connection :ParleyGraphEdge in selectedConnections: - connection.unselect() - selectedConnections.clear() - selectedNodes.clear() + _clear_selection() foundAnyConnection = true var connection: ParleyGraphEdge = _find_existing_connection(from_node, from_port, to_node, to_port) @@ -429,25 +433,25 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: if connection == null: connection = ParleyGraphEdge.new(edge_ast, from_node, from_port, to_node, to_port) connection.select() - selectedConnections.append(connection as ParleyGraphEdge) - onUnselect.append(func() -> void: + selected_connections.append(connection as ParleyGraphEdge) + on_unselect.append(func() -> void: connection.unselect() - selectedConnections.erase(connection) + selected_connections.erase(connection) ) print("Selected connection: ", connection.as_string()) else: - selectedConnections.erase(connection) + selected_connections.erase(connection) connection.unselect() print("Unselected existing connection: ", connection.as_string()) break if not foundAnyConnection && not mouse_event.is_command_or_control_pressed(): - while onUnselect.size() > 0: - var callable: Callable = onUnselect.pop_front() + while on_unselect.size() > 0: + var callable: Callable = on_unselect.pop_front() callable.call() func _find_existing_connection(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> ParleyGraphEdge: - for con: ParleyGraphEdge in selectedConnections: + for con: ParleyGraphEdge in selected_connections: if con.from_node == _from_node && con.from_port == _from_port && con.to_node == _to_node && con.to_port == _to_port: return con return null From ae8f3df513515e4a6cb8d76ffa24bb2867ed270b Mon Sep 17 00:00:00 2001 From: JosephStar318 <44904505+JosephStar318@users.noreply.github.com> Date: Sun, 16 Nov 2025 17:24:41 +0300 Subject: [PATCH 07/24] changed a comment Co-authored-by: Jonny Green --- addons/parley/views/parley_graph_view.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index 1243885..6602c90 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -423,7 +423,7 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: # print("From: ",from_node ," To", to_node," Comparing x: ", from_pos.x, " ", pointer_pos.x, " ", to_pos.x , " distance: ", distance) if distance <= CLICK_DISTANCE: - # basically ctrl+click selects multiple + # basically ctrl/cmd+click selects multiple if not mouse_event.is_command_or_control_pressed(): _clear_selection() From 8e2a2fbc975ed0b7d07dd83e1420861ee9750178 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sun, 16 Nov 2025 17:28:19 +0300 Subject: [PATCH 08/24] - inserted new lines --- .../graph_operations/parley_delete_operation.gd | 3 +++ addons/parley/main_panel.gd | 10 ++++++++++ addons/parley/views/parley_graph_edge.gd | 5 +++++ addons/parley/views/parley_graph_view.gd | 12 +++++++++++- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/addons/parley/graph_operations/parley_delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd index 4a5bd7e..35046a0 100644 --- a/addons/parley/graph_operations/parley_delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -13,6 +13,7 @@ func _init(_graph_view: ParleyGraphView, _selected_connections: Array[ParleyGrap for node: ParleyGraphNode in _selected_nodes: selected_node_ids.append(node.id) + func undo() -> void: var graph_nodes: Dictionary = {} for node_data : NodeData in deleted_node_datas: @@ -31,6 +32,7 @@ func undo() -> void: graph_view.generate() + func do() -> void: deleted_node_datas.clear() for selected_node_id : String in selected_node_ids: @@ -54,6 +56,7 @@ func do() -> void: graph_view.generate() + func get_connections_for_node(graph_view: ParleyGraphView, node_id: String) -> Array[ParleyGraphEdge]: var result: Array[ParleyGraphEdge] = [] var connections: Array[Dictionary] = graph_view.get_connection_list() diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index 6be7e85..75bac3a 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -663,33 +663,41 @@ func _on_docs_button_pressed() -> void: push_error(ParleyUtils.log.error_msg("Unable to navigate to Parley Documentation at %s: %s" % [href, result])) #endregion + func _duplicate_nodes_request() -> void: pass + func _copy_nodes_request() -> void: pass + func _paste_nodes_request() -> void: pass + func _delete_selected() -> void: if graph_view.selected_connections.size() > 0 || graph_view.selected_nodes.size() > 0: var deleteConnection: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selected_connections, graph_view.selected_nodes) deleteConnection.do() add_undo_operation(deleteConnection) + + func _validate_history() -> void: if not undo_history.has(dialogue_ast): undo_history[dialogue_ast] = [] as Array[ParleyGraphOperation] if not redo_history.has(dialogue_ast): redo_history[dialogue_ast] = [] as Array[ParleyGraphOperation] + func add_undo_operation(operation: ParleyGraphOperation) -> void: _validate_history() redo_history[dialogue_ast].clear() undo_history[dialogue_ast].push_back(operation) + func _undo() -> void: _validate_history() if undo_history[dialogue_ast].size() > 0: @@ -698,6 +706,7 @@ func _undo() -> void: operation.undo() redo_history[dialogue_ast].push_back(operation) + func _redo() -> void: _validate_history() if redo_history[dialogue_ast].size() > 0: @@ -706,6 +715,7 @@ func _redo() -> void: operation.do() undo_history[dialogue_ast].push_back(operation) + #region HELPERS func remove_edge(from_node: String, from_slot: int, to_node: String, to_slot: int) -> void: if not dialogue_ast: diff --git a/addons/parley/views/parley_graph_edge.gd b/addons/parley/views/parley_graph_edge.gd index 0459a89..bf36624 100644 --- a/addons/parley/views/parley_graph_edge.gd +++ b/addons/parley/views/parley_graph_edge.gd @@ -32,6 +32,7 @@ func _init(_edge_ast: ParleyEdgeAst, _from_node: ParleyGraphNode, _from_port: in to_node_name = to_node.name edge_ast = _edge_ast + func disconnect_node(graph_view: ParleyGraphView) -> void: if not graph_view: return @@ -39,6 +40,7 @@ func disconnect_node(graph_view: ParleyGraphView) -> void: graph_view.ast.remove_edge(from_node_id, from_port, to_node_id, to_port) graph_view.disconnect_node(from_node_name, from_port, to_node_name, to_port) + func connect_node(graph_view: ParleyGraphView) -> int: if not graph_view: return FAILED @@ -46,6 +48,7 @@ func connect_node(graph_view: ParleyGraphView) -> int: graph_view.ast.edges.append(edge_ast) return graph_view.connect_node(from_node_name, from_port, to_node_name, to_port) + func select() -> void: if selected: return @@ -57,6 +60,7 @@ func select() -> void: from_node.set_slot_color_right(from_slot, Color.DEEP_SKY_BLUE) to_node.set_slot_color_left(to_slot, Color.DEEP_SKY_BLUE) + func unselect() -> void: if not selected: return @@ -66,5 +70,6 @@ func unselect() -> void: from_node.set_slot_color_right(from_slot, previousFromColor) to_node.set_slot_color_left(to_slot, previousToColor) + func as_string() -> String: return "%s:%d -> %s:%d" % [from_node_name, from_port, to_node_name, to_port] diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index 1243885..0d06321 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -360,7 +360,6 @@ func set_selected_by_id(id: String, _goto: bool = true) -> void: #region SHORTCUTS - const CLICK_DISTANCE: float = 50 var on_unselect: Array[Callable] = [] var selected_connections: Array[ParleyGraphEdge] = [] @@ -368,14 +367,19 @@ var selected_nodes: Array[ParleyGraphNode] = [] func _on_node_selected(node: Node) -> void: selected_nodes.append(node) + + func _on_node_deselected(node: Node) -> void: selected_nodes.erase(node) pass + + func _on_connections_deselected() -> void: while on_unselect.size() > 0: var callable: Callable = on_unselect.pop_front() callable.call() + func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton: _handle_mouse_select(event as InputEventMouseButton) @@ -396,12 +400,14 @@ func _gui_input(event: InputEvent) -> void: elif key_event.keycode == KEY_DELETE: delete_selected.emit() + func _clear_selection() -> void: for connection :ParleyGraphEdge in selected_connections: connection.unselect() selected_connections.clear() selected_nodes.clear() + func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: var foundAnyConnection: bool = false if mouse_event.pressed and mouse_event.button_index == MOUSE_BUTTON_LEFT: @@ -450,11 +456,14 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: var callable: Callable = on_unselect.pop_front() callable.call() + func _find_existing_connection(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> ParleyGraphEdge: for con: ParleyGraphEdge in selected_connections: if con.from_node == _from_node && con.from_port == _from_port && con.to_node == _to_node && con.to_port == _to_port: return con return null + + func _get_slot_position(node: GraphNode, slot_idx: int, is_output: bool) -> Vector2: var local_pos: Vector2 if is_output: @@ -465,6 +474,7 @@ func _get_slot_position(node: GraphNode, slot_idx: int, is_output: bool) -> Vect # Convert to GraphEdit local coordinates return (node.position + scroll_offset) / zoom + local_pos + func get_distance_to_segment(point: Vector2, segment_start: Vector2, segment_end: Vector2) -> float: # Calculate the vector of the line segment. var segment_vec: Vector2 = segment_end - segment_start From 542ec8f4603ca0fbc15ebc91d9339c74487dd961 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sun, 16 Nov 2025 17:35:14 +0300 Subject: [PATCH 09/24] - print rich and removed unnecessary prints --- addons/parley/graph_operations/parley_delete_operation.gd | 1 - addons/parley/main_panel.gd | 2 -- addons/parley/views/parley_graph_view.gd | 4 ++-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/addons/parley/graph_operations/parley_delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd index 35046a0..1db8224 100644 --- a/addons/parley/graph_operations/parley_delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -44,7 +44,6 @@ func do() -> void: for selected_node_id : String in selected_node_ids: var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node_id) if ast != null: - print(selected_node_id) var selected_node : ParleyGraphNode = graph_view.find_node_by_id(selected_node_id) graph_view._on_node_deselected(selected_node) graph_view.remove_child(selected_node) diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index 75bac3a..d6a9642 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -701,7 +701,6 @@ func add_undo_operation(operation: ParleyGraphOperation) -> void: func _undo() -> void: _validate_history() if undo_history[dialogue_ast].size() > 0: - print("Undo") var operation: ParleyGraphOperation = undo_history[dialogue_ast].pop_back() operation.undo() redo_history[dialogue_ast].push_back(operation) @@ -710,7 +709,6 @@ func _undo() -> void: func _redo() -> void: _validate_history() if redo_history[dialogue_ast].size() > 0: - print("Redo") var operation: ParleyGraphOperation = redo_history[dialogue_ast].pop_back() operation.do() undo_history[dialogue_ast].push_back(operation) diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index fbe6f86..9fd298e 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -444,11 +444,11 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: connection.unselect() selected_connections.erase(connection) ) - print("Selected connection: ", connection.as_string()) + print_rich(ParleyUtils.log.info_msg("Selected connection: {connection}".format({"connection": connection.as_string()}))) else: selected_connections.erase(connection) connection.unselect() - print("Unselected existing connection: ", connection.as_string()) + print_rich(ParleyUtils.log.info_msg("Unselected existing connection: {connection}".format({"connection": connection.as_string()}))) break if not foundAnyConnection && not mouse_event.is_command_or_control_pressed(): From eb0fa5a6dcf4f267d537f8889a12f718547a68cc Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sat, 6 Dec 2025 13:52:04 +0300 Subject: [PATCH 10/24] - edge selection color improvements - storing node_ids instead of nodes to prevent freed instance errors --- .../parley_delete_operation.gd | 5 +- addons/parley/main_panel.gd | 4 +- addons/parley/views/parley_graph_edge.gd | 10 +++- addons/parley/views/parley_graph_view.gd | 47 +++++++++++++------ 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/addons/parley/graph_operations/parley_delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd index 1db8224..aef60b7 100644 --- a/addons/parley/graph_operations/parley_delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -7,11 +7,10 @@ var selected_node_ids: Array[String] var deleted_node_datas: Array[NodeData] var graph_view: ParleyGraphView -func _init(_graph_view: ParleyGraphView, _selected_connections: Array[ParleyGraphEdge], _selected_nodes: Array[ParleyGraphNode]) -> void: +func _init(_graph_view: ParleyGraphView, _selected_connections: Array[ParleyGraphEdge], _selected_node_ids: Array[String]) -> void: graph_view = _graph_view selected_connections = _selected_connections.duplicate() - for node: ParleyGraphNode in _selected_nodes: - selected_node_ids.append(node.id) + selected_node_ids = _selected_node_ids.duplicate() func undo() -> void: diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index d6a9642..c7e7ef7 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -679,8 +679,8 @@ func _paste_nodes_request() -> void: func _delete_selected() -> void: - if graph_view.selected_connections.size() > 0 || graph_view.selected_nodes.size() > 0: - var deleteConnection: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selected_connections, graph_view.selected_nodes) + if graph_view.selected_connections.size() > 0 || graph_view.selected_node_ids.size() > 0: + var deleteConnection: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selected_connections, graph_view.selected_node_ids) deleteConnection.do() add_undo_operation(deleteConnection) diff --git a/addons/parley/views/parley_graph_edge.gd b/addons/parley/views/parley_graph_edge.gd index bf36624..f96789d 100644 --- a/addons/parley/views/parley_graph_edge.gd +++ b/addons/parley/views/parley_graph_edge.gd @@ -19,6 +19,7 @@ var previousFromColor: Color var previousToColor: Color var selected: bool +# Duplicate should not be used on this class. The class is generated one time only at runtime when it is needed func _init(_edge_ast: ParleyEdgeAst, _from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> void: from_node = _from_node from_port = _from_port @@ -55,8 +56,13 @@ func select() -> void: selected = true if not is_instance_valid(from_node) or not is_instance_valid(to_node): return - previousFromColor = from_node.get_slot_color_right(from_slot) - previousToColor = to_node.get_slot_color_left(to_slot) + if edge_ast.should_override_colour: + previousFromColor = edge_ast.colour_override + previousToColor = edge_ast.colour_override + else: + previousFromColor = Color.LAWN_GREEN + previousToColor = Color.LAWN_GREEN + from_node.set_slot_color_right(from_slot, Color.DEEP_SKY_BLUE) to_node.set_slot_color_left(to_slot, Color.DEEP_SKY_BLUE) diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index 9fd298e..a152c27 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -34,8 +34,7 @@ func _exit_tree() -> void: await clear() ast = null connections = [] - _clear_selection() - + func generate(arrange: bool = false) -> void: await clear() @@ -45,6 +44,8 @@ func generate(arrange: bool = false) -> void: func clear() -> void: + _clear_selected_connections() + _clear_selected_nodes() clear_connections() var children: Array[ParleyGraphNode] = [] for child: Node in get_children(): @@ -227,7 +228,6 @@ func set_edge_colour(edge: ParleyEdgeAst) -> void: func get_ast_node_name(ast_node: ParleyNodeAst) -> String: return "%s-%s" % [str(ParleyDialogueSequenceAst.Type.find_key(ast_node.type)), ast_node.id.replace(ParleyNodeAst.id_prefix, '')] - func _goto_node(node: ParleyGraphNode) -> void: scroll_offset = (node.position_offset + node.size * 0.5) * zoom - size * 0.5 #endregion @@ -363,15 +363,15 @@ func set_selected_by_id(id: String, _goto: bool = true) -> void: const CLICK_DISTANCE: float = 50 var on_unselect: Array[Callable] = [] var selected_connections: Array[ParleyGraphEdge] = [] -var selected_nodes: Array[ParleyGraphNode] = [] +var selected_node_ids: Array[String] = [] func _on_node_selected(node: Node) -> void: - selected_nodes.append(node) + var node_id: String = (node as ParleyGraphNode).id + selected_node_ids.append(node_id) func _on_node_deselected(node: Node) -> void: - selected_nodes.erase(node) - pass + selected_node_ids.erase((node as ParleyGraphNode).id) func _on_connections_deselected() -> void: @@ -401,17 +401,28 @@ func _gui_input(event: InputEvent) -> void: delete_selected.emit() -func _clear_selection() -> void: +func _clear_selected_nodes() -> void: + selected_node_ids.clear() + + +func _clear_selected_connections() -> void: for connection :ParleyGraphEdge in selected_connections: connection.unselect() - selected_connections.clear() - selected_nodes.clear() func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: var foundAnyConnection: bool = false - if mouse_event.pressed and mouse_event.button_index == MOUSE_BUTTON_LEFT: + if mouse_event.is_released() and mouse_event.button_index == MOUSE_BUTTON_LEFT: var pointer_pos: Vector2 = (mouse_event.position + scroll_offset) / zoom + + if _node_exist_at_position(mouse_event.position): + _clear_selected_connections() + return + # basically ctrl/cmd+click selects multiple + if not mouse_event.is_command_or_control_pressed(): + _clear_selected_nodes() + _clear_selected_connections() + for conn: Dictionary in get_connection_list(): var from_node_name: String = conn.get("from_node") var to_node_name: String = conn.get("to_node") @@ -429,10 +440,6 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: # print("From: ",from_node ," To", to_node," Comparing x: ", from_pos.x, " ", pointer_pos.x, " ", to_pos.x , " distance: ", distance) if distance <= CLICK_DISTANCE: - # basically ctrl/cmd+click selects multiple - if not mouse_event.is_command_or_control_pressed(): - _clear_selection() - foundAnyConnection = true var connection: ParleyGraphEdge = _find_existing_connection(from_node, from_port, to_node, to_port) var edge_ast: ParleyEdgeAst = ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) @@ -501,4 +508,14 @@ func get_distance_to_segment(point: Vector2, segment_start: Vector2, segment_end # Return the distance between the original point and the closest point on the segment. return point.distance_to(closest_point_on_segment) +func _node_exist_at_position(pos: Vector2) -> GraphNode: + # GraphEdit applies zoom + scroll internally, so convert position properly + for node_id : String in selected_node_ids: + var node : ParleyGraphNode = find_node_by_id(node_id) + var node_rect: Rect2 = Rect2(node.position, node.size) + if node_rect.has_point(pos) and node is not ParleyGroupNode: + return node + + return null + #endregion From 0052249b196a8989be6d9645f1fef74860ed93cd Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sat, 7 Feb 2026 14:52:30 +0300 Subject: [PATCH 11/24] - color selection bug fix - implemented bezier approximation --- addons/parley/views/parley_graph_edge.gd | 20 +++--- addons/parley/views/parley_graph_view.gd | 80 ++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/addons/parley/views/parley_graph_edge.gd b/addons/parley/views/parley_graph_edge.gd index f96789d..2de5f45 100644 --- a/addons/parley/views/parley_graph_edge.gd +++ b/addons/parley/views/parley_graph_edge.gd @@ -56,15 +56,9 @@ func select() -> void: selected = true if not is_instance_valid(from_node) or not is_instance_valid(to_node): return - if edge_ast.should_override_colour: - previousFromColor = edge_ast.colour_override - previousToColor = edge_ast.colour_override - else: - previousFromColor = Color.LAWN_GREEN - previousToColor = Color.LAWN_GREEN - - from_node.set_slot_color_right(from_slot, Color.DEEP_SKY_BLUE) - to_node.set_slot_color_left(to_slot, Color.DEEP_SKY_BLUE) + + from_node.select_from_slot(edge_ast.from_slot) + to_node.select_to_slot(edge_ast.to_slot) func unselect() -> void: @@ -73,8 +67,12 @@ func unselect() -> void: selected = false if not is_instance_valid(from_node) or not is_instance_valid(to_node): return - from_node.set_slot_color_right(from_slot, previousFromColor) - to_node.set_slot_color_left(to_slot, previousToColor) + if edge_ast.should_override_colour: + from_node.deselect_from_slot(edge_ast.from_slot, edge_ast.colour_override) + else: + from_node.deselect_from_slot(edge_ast.from_slot) + var from_node_colour: Color = from_node.get_from_slot_colour(edge_ast.from_slot) + to_node.unselect_to_slot(edge_ast.to_slot, from_node_colour) func as_string() -> String: diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index a152c27..60d21f6 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -44,7 +44,7 @@ func generate(arrange: bool = false) -> void: func clear() -> void: - _clear_selected_connections() + _unselect_connections() _clear_selected_nodes() clear_connections() var children: Array[ParleyGraphNode] = [] @@ -360,7 +360,7 @@ func set_selected_by_id(id: String, _goto: bool = true) -> void: #region SHORTCUTS -const CLICK_DISTANCE: float = 50 +const CLICK_DISTANCE: float = 5 var on_unselect: Array[Callable] = [] var selected_connections: Array[ParleyGraphEdge] = [] var selected_node_ids: Array[String] = [] @@ -405,21 +405,26 @@ func _clear_selected_nodes() -> void: selected_node_ids.clear() -func _clear_selected_connections() -> void: +func _unselect_connections() -> void: for connection :ParleyGraphEdge in selected_connections: connection.unselect() +func _clear_selected_connections() -> void: + selected_connections.clear() + + func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: var foundAnyConnection: bool = false if mouse_event.is_released() and mouse_event.button_index == MOUSE_BUTTON_LEFT: var pointer_pos: Vector2 = (mouse_event.position + scroll_offset) / zoom if _node_exist_at_position(mouse_event.position): - _clear_selected_connections() + _unselect_connections() return # basically ctrl/cmd+click selects multiple if not mouse_event.is_command_or_control_pressed(): + _unselect_connections() _clear_selected_nodes() _clear_selected_connections() @@ -436,7 +441,12 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: var from_pos: Vector2 = _get_slot_position(from_node, from_port, true) var to_pos: Vector2 = _get_slot_position(to_node, to_port, false) - var distance: float = get_distance_to_segment(pointer_pos, from_pos, to_pos) * zoom + var controls : Array = get_graph_bezier_controls(from_pos, to_pos) + var p0: Vector2 = controls[0] + var p1: Vector2 = controls[1] + var p2: Vector2 = controls[2] + var p3: Vector2 = controls[3] + var distance: float = get_distance_to_bezier(pointer_pos, p0, p1, p2, p3) * zoom # print("From: ",from_node ," To", to_node," Comparing x: ", from_pos.x, " ", pointer_pos.x, " ", to_pos.x , " distance: ", distance) if distance <= CLICK_DISTANCE: @@ -508,6 +518,66 @@ func get_distance_to_segment(point: Vector2, segment_start: Vector2, segment_end # Return the distance between the original point and the closest point on the segment. return point.distance_to(closest_point_on_segment) + +func get_graph_bezier_controls(from: Vector2, to: Vector2) -> Array: + var dx : float= abs(to.x - from.x) + var offset : float = max(dx * 0.5, 40.0) + + var p0 : Vector2 = from + var p1 : Vector2 = from + Vector2(offset, 0) + var p2 : Vector2 = to - Vector2(offset, 0) + var p3 : Vector2 = to + + return [p0, p1, p2, p3] + + +func cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float) -> Vector2: + var u : float = 1.0 - t + + return ( + u*u*u * p0 + + 3.0 * u*u * t * p1 + + 3.0 * u * t*t * p2 + + t*t*t * p3 + ) + + +func get_distance_to_bezier(point: Vector2, p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2) -> float: + var closest_dist : float = INF + var closest_t : float = 0.0 + + # --- 1. Coarse sampling --- + const SAMPLES : float = 20 + for i: int in range(SAMPLES + 1): + var t : float = float(i) / SAMPLES + var bez_point : Vector2 = cubic_bezier(p0, p1, p2, p3, t) + var d : float = point.distance_squared_to(bez_point) + + if d < closest_dist: + closest_dist = d + closest_t = t + + # --- 2. Refinement --- + var step : float = 1.0 / SAMPLES + for _i: int in range(5): # iterations + var t_left : float = clamp(closest_t - step, 0.0, 1.0) + var t_right : float = clamp(closest_t + step, 0.0, 1.0) + + var d_left : float = point.distance_squared_to(cubic_bezier(p0, p1, p2, p3, t_left)) + var d_right : float = point.distance_squared_to(cubic_bezier(p0, p1, p2, p3, t_right)) + + if d_left < closest_dist: + closest_dist = d_left + closest_t = t_left + elif d_right < closest_dist: + closest_dist = d_right + closest_t = t_right + + step *= 0.5 + + return sqrt(closest_dist) + + func _node_exist_at_position(pos: Vector2) -> GraphNode: # GraphEdit applies zoom + scroll internally, so convert position properly for node_id : String in selected_node_ids: From fb45bc97761ab86ba27b5b14d6dc9b3b348e494f Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sat, 7 Feb 2026 16:05:53 +0300 Subject: [PATCH 12/24] - added create node opertion and connection to empty node operation --- .../parley_connection_to_empty_operation.gd | 52 +++++++++++++++++++ ...arley_connection_to_empty_operation.gd.uid | 1 + .../parley_create_node_operation.gd | 30 +++++++++++ .../parley_create_node_operation.gd.uid | 1 + .../parley_delete_operation.gd | 30 +---------- .../graph_operations/parley_graph_utils.gd | 20 +++++++ .../parley_graph_utils.gd.uid | 1 + .../graph_operations/parley_node_data.gd | 10 ++++ .../graph_operations/parley_node_data.gd.uid | 1 + addons/parley/main_panel.gd | 24 +++++---- 10 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 addons/parley/graph_operations/parley_connection_to_empty_operation.gd create mode 100644 addons/parley/graph_operations/parley_connection_to_empty_operation.gd.uid create mode 100644 addons/parley/graph_operations/parley_create_node_operation.gd create mode 100644 addons/parley/graph_operations/parley_create_node_operation.gd.uid create mode 100644 addons/parley/graph_operations/parley_graph_utils.gd create mode 100644 addons/parley/graph_operations/parley_graph_utils.gd.uid create mode 100644 addons/parley/graph_operations/parley_node_data.gd create mode 100644 addons/parley/graph_operations/parley_node_data.gd.uid diff --git a/addons/parley/graph_operations/parley_connection_to_empty_operation.gd b/addons/parley/graph_operations/parley_connection_to_empty_operation.gd new file mode 100644 index 0000000..6fce3d3 --- /dev/null +++ b/addons/parley/graph_operations/parley_connection_to_empty_operation.gd @@ -0,0 +1,52 @@ +class_name ParleyConnectionToEmptyOperation +extends ParleyGraphOperation + +var node_type: ParleyDialogueSequenceAst.Type +var graph_view: ParleyGraphView +var position: Vector2 +var node_id: String +var from_node_name: StringName +var from_slot: int + +func _init(_graph_view: ParleyGraphView, _node_type: ParleyDialogueSequenceAst.Type, _position: Vector2, _from_node_name: StringName, _from_slot: int) -> void: + graph_view = _graph_view + node_type = _node_type + position = _position + from_node_name = _from_node_name + from_slot = _from_slot + + +func undo() -> void: + var connections : Array[ParleyGraphEdge] = ParleyGraphUtils.get_connections_for_node(graph_view, node_id) + + for connection: ParleyGraphEdge in connections: + connection.disconnect_node(graph_view) + + var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(node_id) + if ast != null: + graph_view.ast.remove_node(node_id) + + var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) + if node: + graph_view._on_node_deselected(node) + graph_view.remove_child(node) + + +func do() -> void: + var ast_node: ParleyNodeAst = graph_view.ast.add_new_node(node_type, position) + if ast_node: + node_id = ast_node.id + await graph_view.generate() + + var to_node_name: String = graph_view.get_ast_node_name(ast_node) + # TODO: This is the entry slot for a Dialogue AST Node, it may be better to create a helper function for this + var to_slot: int = 0 + _add_edge(to_node_name, to_slot) + +func _add_edge(to_node_name: StringName, to_slot: int) -> void: + var from_node_id: String = from_node_name.split('-')[1] + var to_node_id: String = to_node_name.split('-')[1] + var added_edge: ParleyEdgeAst = graph_view.ast.add_new_edge(from_node_id, from_slot, to_node_id, to_slot) + if added_edge: + graph_view.add_edge(added_edge, from_node_name, to_node_name) + diff --git a/addons/parley/graph_operations/parley_connection_to_empty_operation.gd.uid b/addons/parley/graph_operations/parley_connection_to_empty_operation.gd.uid new file mode 100644 index 0000000..84f023f --- /dev/null +++ b/addons/parley/graph_operations/parley_connection_to_empty_operation.gd.uid @@ -0,0 +1 @@ +uid://cmt2ds0h681od diff --git a/addons/parley/graph_operations/parley_create_node_operation.gd b/addons/parley/graph_operations/parley_create_node_operation.gd new file mode 100644 index 0000000..52c7577 --- /dev/null +++ b/addons/parley/graph_operations/parley_create_node_operation.gd @@ -0,0 +1,30 @@ +class_name ParleyCreateNodeOperation +extends ParleyGraphOperation + +var node_type: ParleyDialogueSequenceAst.Type +var graph_view: ParleyGraphView +var position: Vector2 +var node_id: String + +func _init(_graph_view: ParleyGraphView, _node_type: ParleyDialogueSequenceAst.Type, _position: Vector2) -> void: + graph_view = _graph_view + node_type = _node_type + position = _position + + +func undo() -> void: + var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(node_id) + if ast != null: + graph_view.ast.remove_node(node_id) + + var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) + if node: + graph_view._on_node_deselected(node) + graph_view.remove_child(node) + + +func do() -> void: + var ast_node: Variant = graph_view.ast.add_new_node(node_type, position) + if ast_node: + node_id = ast_node.id + graph_view.generate() \ No newline at end of file diff --git a/addons/parley/graph_operations/parley_create_node_operation.gd.uid b/addons/parley/graph_operations/parley_create_node_operation.gd.uid new file mode 100644 index 0000000..052ba71 --- /dev/null +++ b/addons/parley/graph_operations/parley_create_node_operation.gd.uid @@ -0,0 +1 @@ +uid://cd4fjjrnhnld4 diff --git a/addons/parley/graph_operations/parley_delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd index aef60b7..165952c 100644 --- a/addons/parley/graph_operations/parley_delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -1,4 +1,3 @@ -# DeleteShortcut.gd class_name ParleyDeleteOperation extends ParleyGraphOperation @@ -37,7 +36,7 @@ func do() -> void: for selected_node_id : String in selected_node_ids: var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node_id) if ast != null: - var connections : Array[ParleyGraphEdge] = get_connections_for_node(graph_view, selected_node_id) + var connections : Array[ParleyGraphEdge] = ParleyGraphUtils.get_connections_for_node(graph_view, selected_node_id) deleted_node_datas.append(NodeData.new(selected_node_id, ast, connections)) for selected_node_id : String in selected_node_ids: @@ -55,32 +54,5 @@ func do() -> void: graph_view.generate() -func get_connections_for_node(graph_view: ParleyGraphView, node_id: String) -> Array[ParleyGraphEdge]: - var result: Array[ParleyGraphEdge] = [] - var connections: Array[Dictionary] = graph_view.get_connection_list() - - for conn: Dictionary in connections: - var from_name: String = conn.get("from_node") - var to_name: String = conn.get("to_node") - var from_port: int = conn.get("from_port") - var to_port: int = conn.get("to_port") - var to_node: ParleyGraphNode = graph_view.get_node(NodePath(to_name)) as ParleyGraphNode - var from_node: ParleyGraphNode = graph_view.get_node(NodePath(from_name)) as ParleyGraphNode - - if to_node.id == node_id or from_node.id == node_id: - var edge_ast: ParleyEdgeAst = graph_view.ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) - result.append(ParleyGraphEdge.new(edge_ast, from_node, from_port, to_node, to_port)) - - return result - - -class NodeData: - var node_ast: ParleyNodeAst - var connections: Array[ParleyGraphEdge] - var node_id: String - func _init(_node_id: String, _node_ast :ParleyNodeAst, _connections: Array[ParleyGraphEdge]) -> void: - node_id = _node_id - node_ast = _node_ast - connections = _connections diff --git a/addons/parley/graph_operations/parley_graph_utils.gd b/addons/parley/graph_operations/parley_graph_utils.gd new file mode 100644 index 0000000..9005069 --- /dev/null +++ b/addons/parley/graph_operations/parley_graph_utils.gd @@ -0,0 +1,20 @@ +class_name ParleyGraphUtils +extends Node + +static func get_connections_for_node(graph_view: ParleyGraphView, node_id: String) -> Array: + var result: Array[ParleyGraphEdge] = [] + var connections: Array[Dictionary] = graph_view.get_connection_list() + + for conn: Dictionary in connections: + var from_name: String = conn.get("from_node") + var to_name: String = conn.get("to_node") + var from_port: int = conn.get("from_port") + var to_port: int = conn.get("to_port") + var to_node: ParleyGraphNode = graph_view.get_node(NodePath(to_name)) as ParleyGraphNode + var from_node: ParleyGraphNode = graph_view.get_node(NodePath(from_name)) as ParleyGraphNode + + if to_node.id == node_id or from_node.id == node_id: + var edge_ast: ParleyEdgeAst = graph_view.ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) + result.append(ParleyGraphEdge.new(edge_ast, from_node, from_port, to_node, to_port)) + + return result diff --git a/addons/parley/graph_operations/parley_graph_utils.gd.uid b/addons/parley/graph_operations/parley_graph_utils.gd.uid new file mode 100644 index 0000000..a22b494 --- /dev/null +++ b/addons/parley/graph_operations/parley_graph_utils.gd.uid @@ -0,0 +1 @@ +uid://bxvx2algatfbk diff --git a/addons/parley/graph_operations/parley_node_data.gd b/addons/parley/graph_operations/parley_node_data.gd new file mode 100644 index 0000000..ebe2b0c --- /dev/null +++ b/addons/parley/graph_operations/parley_node_data.gd @@ -0,0 +1,10 @@ +class_name NodeData + +var node_ast: ParleyNodeAst +var connections: Array[ParleyGraphEdge] +var node_id: String + +func _init(_node_id: String, _node_ast :ParleyNodeAst, _connections: Array[ParleyGraphEdge]) -> void: + node_id = _node_id + node_ast = _node_ast + connections = _connections \ No newline at end of file diff --git a/addons/parley/graph_operations/parley_node_data.gd.uid b/addons/parley/graph_operations/parley_node_data.gd.uid new file mode 100644 index 0000000..7b5c2a2 --- /dev/null +++ b/addons/parley/graph_operations/parley_node_data.gd.uid @@ -0,0 +1 @@ +uid://bclqgywlkgvmj diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index c7e7ef7..43cb508 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -297,9 +297,10 @@ func _on_insert_id_pressed(type: ParleyDialogueSequenceAst.Type) -> void: if not dialogue_ast: push_warning(ParleyUtils.log.warn_msg("Unable to add Node of type %s to the Dialogue Sequence. Dialogue Sequence is not currently loaded into Parley. Please open one via the file menu in the top left-hand side." % ParleyDialogueSequenceAst.get_type_name(type))) return - var ast_node: Variant = dialogue_ast.add_new_node(type, (graph_view.scroll_offset + graph_view.size * 0.5) / graph_view.zoom) - if ast_node: - await refresh() + var node_position: Vector2 = (graph_view.scroll_offset + graph_view.size * 0.5) / graph_view.zoom + var create_node_operation: ParleyCreateNodeOperation = ParleyCreateNodeOperation.new(graph_view, type, node_position) + create_node_operation.do() + add_undo_operation(create_node_operation) func _on_save_pressed() -> void: @@ -562,14 +563,15 @@ func _on_graph_view_connection_to_empty(from_node_name: StringName, from_slot: i if not dialogue_ast: return # TODO: it may be better to create a helper for this calculation - var ast_node_variant: Variant = dialogue_ast.add_new_node(ParleyDialogueSequenceAst.Type.DIALOGUE, ((graph_view.scroll_offset + release_position) / graph_view.zoom) + Vector2(0, -90)) - if ast_node_variant and ast_node_variant is ParleyNodeAst: - var ast_node: ParleyNodeAst = ast_node_variant - await refresh() - var to_node_name: String = graph_view.get_ast_node_name(ast_node) - # TODO: This is the entry slot for a Dialogue AST Node, it may be better to create a helper function for this - var to_slot: int = 0 - _add_edge(from_node_name, from_slot, to_node_name, to_slot) + var node_position: Vector2 = ((graph_view.scroll_offset + release_position) / graph_view.zoom) + Vector2(0, -90) + var connection_to_empty_operation: ParleyConnectionToEmptyOperation = ParleyConnectionToEmptyOperation.new( + graph_view, + ParleyDialogueSequenceAst.Type.DIALOGUE, + node_position, + from_node_name, + from_slot) + connection_to_empty_operation.do() + add_undo_operation(connection_to_empty_operation) # TODO: add to docs From 6a5fbbc2b8bf37f24d18506537544fde19ae57f1 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sat, 7 Feb 2026 20:58:27 +0300 Subject: [PATCH 13/24] - added duplicate operation --- .../parley_duplicate_operation.gd | 51 +++++++++++++++++++ .../parley_duplicate_operation.gd.uid | 1 + .../graph_operations/parley_graph_utils.gd | 7 +++ .../parley_paste_operation.gd | 22 ++++---- addons/parley/main_panel.gd | 18 ++++--- 5 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 addons/parley/graph_operations/parley_duplicate_operation.gd create mode 100644 addons/parley/graph_operations/parley_duplicate_operation.gd.uid diff --git a/addons/parley/graph_operations/parley_duplicate_operation.gd b/addons/parley/graph_operations/parley_duplicate_operation.gd new file mode 100644 index 0000000..5e37455 --- /dev/null +++ b/addons/parley/graph_operations/parley_duplicate_operation.gd @@ -0,0 +1,51 @@ +class_name ParleyDuplicateOperation +extends ParleyGraphOperation + +var selected_node_ids: Array[String] +var created_node_datas: Array[NodeData] +var graph_view: ParleyGraphView + + +func _init(_graph_view: ParleyGraphView, _selected_node_ids: Array[String]) -> void: + graph_view = _graph_view + selected_node_ids = _selected_node_ids.duplicate() + + +func do() -> void: + + graph_view._clear_selected_nodes() + var created_node_list: Array[String] + + for node_id: String in selected_node_ids: + var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) + var ast_node: ParleyNodeAst = graph_view.ast.add_new_node(node.type, node.position_offset + Vector2.DOWN * 200 + Vector2.RIGHT * 200 ) + if ast_node: + node_id = ast_node.id + var connections : Array[ParleyGraphEdge] = ParleyGraphUtils.get_connections_for_node(graph_view, node_id) + created_node_datas.append(NodeData.new(node_id, ast_node, connections)) + created_node_list.append(node_id) + + await graph_view.generate() + + for node_id: String in created_node_list: + var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) + node.set_selected(true) + + +func undo() -> void: + for node_data: NodeData in created_node_datas: + for connection: ParleyGraphEdge in node_data.connections: + connection.disconnect_node(graph_view) + + var selected_node : ParleyGraphNode = graph_view.find_node_by_id(node_data.node_id) + if selected_node: + graph_view._on_node_deselected(selected_node) + graph_view.remove_child(selected_node) + + graph_view.ast.remove_node(node_data.node_id) + + await graph_view.generate() + for node_id: String in selected_node_ids: + var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) + node.set_selected(true) + \ No newline at end of file diff --git a/addons/parley/graph_operations/parley_duplicate_operation.gd.uid b/addons/parley/graph_operations/parley_duplicate_operation.gd.uid new file mode 100644 index 0000000..75a3a58 --- /dev/null +++ b/addons/parley/graph_operations/parley_duplicate_operation.gd.uid @@ -0,0 +1 @@ +uid://cgohkiyv0owkr diff --git a/addons/parley/graph_operations/parley_graph_utils.gd b/addons/parley/graph_operations/parley_graph_utils.gd index 9005069..22befa1 100644 --- a/addons/parley/graph_operations/parley_graph_utils.gd +++ b/addons/parley/graph_operations/parley_graph_utils.gd @@ -18,3 +18,10 @@ static func get_connections_for_node(graph_view: ParleyGraphView, node_id: Strin result.append(ParleyGraphEdge.new(edge_ast, from_node, from_port, to_node, to_port)) return result + + +static func get_cursor_pos_at_graph_view(graph_view: ParleyGraphView) -> Vector2: + var mouse_pos: Vector2 = graph_view.get_viewport().get_mouse_position() + return (mouse_pos + graph_view.scroll_offset) / graph_view.zoom + + diff --git a/addons/parley/graph_operations/parley_paste_operation.gd b/addons/parley/graph_operations/parley_paste_operation.gd index 6993d1a..f85ede5 100644 --- a/addons/parley/graph_operations/parley_paste_operation.gd +++ b/addons/parley/graph_operations/parley_paste_operation.gd @@ -2,20 +2,16 @@ extends ParleyGraphOperation class_name ParleyPasteOperation -var pasted_nodes: Array = [] -var graph_edit: GraphEdit +var node_ids: Array[String] +var graph_view: ParleyGraphView -func _init(_graph_edit: GraphEdit, nodes: Array) -> void: - graph_edit = _graph_edit - pasted_nodes = nodes.duplicate() +func _init(_graph_view: ParleyGraphView, _node_ids: Array[String]) -> void: + graph_view = _graph_view + node_ids = _node_ids.duplicate() -func undo() -> void: - # Remove pasted nodes - for node: GraphNode in pasted_nodes: - if graph_edit.has_node(NodePath(node.name)): - graph_edit.remove_child(node) func do() -> void: - # Re-add pasted nodes - for node: GraphNode in pasted_nodes: - graph_edit.add_child(node) \ No newline at end of file + pass + +func undo() -> void: + pass diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index 43cb508..f9be06e 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -50,7 +50,7 @@ var parley_manager: ParleyManager # TODO: remove this var selected_node_id: Variant var selected_node_ast: ParleyNodeAst: set = _set_selected_node_ast - +var copied_node_ids: Array[String] signal dialogue_ast_selected(dialogue_ast: ParleyDialogueSequenceAst) signal node_selected(node_ast: ParleyNodeAst) @@ -667,17 +667,23 @@ func _on_docs_button_pressed() -> void: func _duplicate_nodes_request() -> void: + if graph_view.selected_node_ids.size() > 0 : + var duplicate_nodes_operation: ParleyDuplicateOperation = ParleyDuplicateOperation.new(graph_view, graph_view.selected_node_ids) + duplicate_nodes_operation.do() + add_undo_operation(duplicate_nodes_operation) - pass - func _copy_nodes_request() -> void: - pass + copied_node_ids.clear() + for node_id: String in graph_view.selected_node_ids: + copied_node_ids.append(node_id) func _paste_nodes_request() -> void: - - pass + if copied_node_ids.size() > 0: + var duplicate_nodes_operation: ParleyPasteOperation = ParleyPasteOperation.new(graph_view, copied_node_ids) + duplicate_nodes_operation.do() + add_undo_operation(duplicate_nodes_operation) func _delete_selected() -> void: From d92b29fde3622ba68d9969a4d520a7880667e39d Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sun, 8 Feb 2026 18:44:24 +0300 Subject: [PATCH 14/24] - bug fixes and improvements --- .../parley_connection_to_empty_operation.gd | 2 +- .../parley_delete_operation.gd | 20 ++- .../parley_duplicate_operation.gd | 11 +- .../graph_operations/parley_graph_utils.gd | 34 ++-- addons/parley/main_panel.gd | 48 ++--- addons/parley/views/parley_graph_edge.gd | 32 ++-- addons/parley/views/parley_graph_view.gd | 164 +++++++++++------- addons/parley/views/parley_graph_view.tscn | 3 + 8 files changed, 196 insertions(+), 118 deletions(-) diff --git a/addons/parley/graph_operations/parley_connection_to_empty_operation.gd b/addons/parley/graph_operations/parley_connection_to_empty_operation.gd index 6fce3d3..0beb355 100644 --- a/addons/parley/graph_operations/parley_connection_to_empty_operation.gd +++ b/addons/parley/graph_operations/parley_connection_to_empty_operation.gd @@ -20,7 +20,7 @@ func undo() -> void: var connections : Array[ParleyGraphEdge] = ParleyGraphUtils.get_connections_for_node(graph_view, node_id) for connection: ParleyGraphEdge in connections: - connection.disconnect_node(graph_view) + connection.disconnect_node() var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(node_id) if ast != null: diff --git a/addons/parley/graph_operations/parley_delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd index 165952c..9e710c7 100644 --- a/addons/parley/graph_operations/parley_delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -14,22 +14,34 @@ func _init(_graph_view: ParleyGraphView, _selected_connections: Array[ParleyGrap func undo() -> void: var graph_nodes: Dictionary = {} + var created_node_list: Array[String] + for node_data : NodeData in deleted_node_datas: var deleted_node : ParleyNodeAst = node_data.node_ast graph_view._add_node(graph_nodes, deleted_node) graph_view.ast.add_node_from_ast(deleted_node) var added_node : ParleyGraphNode = graph_nodes[deleted_node.id] added_node.position = deleted_node.position + created_node_list.append(added_node.id) for node_data : NodeData in deleted_node_datas: for connection : ParleyGraphEdge in node_data.connections: - connection.connect_node(graph_view) + connection.connect_node() for connection : ParleyGraphEdge in selected_connections: - connection.connect_node(graph_view) + connection.connect_node() - graph_view.generate() + await graph_view.generate() + + for i: int in range(created_node_list.size()): + var node_id: String = created_node_list[i] + var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) + node.set_selected(true) + for connection : ParleyGraphEdge in selected_connections: + connection.unselect() + for connection : ParleyGraphEdge in selected_connections: + connection.select() func do() -> void: deleted_node_datas.clear() @@ -49,7 +61,7 @@ func do() -> void: graph_view._on_connections_deselected() for connection : ParleyGraphEdge in selected_connections: - connection.disconnect_node(graph_view) + connection.disconnect_node() graph_view.generate() diff --git a/addons/parley/graph_operations/parley_duplicate_operation.gd b/addons/parley/graph_operations/parley_duplicate_operation.gd index 5e37455..b31a8da 100644 --- a/addons/parley/graph_operations/parley_duplicate_operation.gd +++ b/addons/parley/graph_operations/parley_duplicate_operation.gd @@ -15,6 +15,7 @@ func do() -> void: graph_view._clear_selected_nodes() var created_node_list: Array[String] + var node_names: Array[String] for node_id: String in selected_node_ids: var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) @@ -24,18 +25,24 @@ func do() -> void: var connections : Array[ParleyGraphEdge] = ParleyGraphUtils.get_connections_for_node(graph_view, node_id) created_node_datas.append(NodeData.new(node_id, ast_node, connections)) created_node_list.append(node_id) + node_names.append(node.name) + if ast_node is ParleyGroupNodeAst: + (ast_node as ParleyGroupNodeAst).size = node.size await graph_view.generate() - for node_id: String in created_node_list: + for i: int in range(created_node_list.size()): + var node_id: String = created_node_list[i] + var node_name: String = node_names[i] var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) node.set_selected(true) + node.name = node_name func undo() -> void: for node_data: NodeData in created_node_datas: for connection: ParleyGraphEdge in node_data.connections: - connection.disconnect_node(graph_view) + connection.disconnect_node() var selected_node : ParleyGraphNode = graph_view.find_node_by_id(node_data.node_id) if selected_node: diff --git a/addons/parley/graph_operations/parley_graph_utils.gd b/addons/parley/graph_operations/parley_graph_utils.gd index 22befa1..c333716 100644 --- a/addons/parley/graph_operations/parley_graph_utils.gd +++ b/addons/parley/graph_operations/parley_graph_utils.gd @@ -2,26 +2,24 @@ class_name ParleyGraphUtils extends Node static func get_connections_for_node(graph_view: ParleyGraphView, node_id: String) -> Array: - var result: Array[ParleyGraphEdge] = [] - var connections: Array[Dictionary] = graph_view.get_connection_list() + var result: Array[ParleyGraphEdge] = [] + var connections: Array[Dictionary] = graph_view.get_connection_list() - for conn: Dictionary in connections: - var from_name: String = conn.get("from_node") - var to_name: String = conn.get("to_node") - var from_port: int = conn.get("from_port") - var to_port: int = conn.get("to_port") - var to_node: ParleyGraphNode = graph_view.get_node(NodePath(to_name)) as ParleyGraphNode - var from_node: ParleyGraphNode = graph_view.get_node(NodePath(from_name)) as ParleyGraphNode + for conn: Dictionary in connections: + var from_name: String = conn.get("from_node") + var to_name: String = conn.get("to_node") + var from_port: int = conn.get("from_port") + var to_port: int = conn.get("to_port") + var to_node: ParleyGraphNode = graph_view.get_node(NodePath(to_name)) as ParleyGraphNode + var from_node: ParleyGraphNode = graph_view.get_node(NodePath(from_name)) as ParleyGraphNode - if to_node.id == node_id or from_node.id == node_id: - var edge_ast: ParleyEdgeAst = graph_view.ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) - result.append(ParleyGraphEdge.new(edge_ast, from_node, from_port, to_node, to_port)) - - return result + if to_node.id == node_id or from_node.id == node_id: + var edge_ast: ParleyEdgeAst = graph_view.ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) + result.append(ParleyGraphEdge.new(graph_view, edge_ast, from_node, from_port, to_node, to_port)) + return result static func get_cursor_pos_at_graph_view(graph_view: ParleyGraphView) -> Vector2: - var mouse_pos: Vector2 = graph_view.get_viewport().get_mouse_position() - return (mouse_pos + graph_view.scroll_offset) / graph_view.zoom - - + var viewport_mouse : Vector2 = graph_view.get_viewport().get_mouse_position() + var local_mouse : Vector2 = viewport_mouse - graph_view.get_global_rect().position + return (local_mouse + graph_view.scroll_offset) / graph_view.zoom diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index f9be06e..f91a3c7 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -55,8 +55,8 @@ var copied_node_ids: Array[String] signal dialogue_ast_selected(dialogue_ast: ParleyDialogueSequenceAst) signal node_selected(node_ast: ParleyNodeAst) -var undo_history: Dictionary = {} -var redo_history: Dictionary = {} +var undo_histories: Dictionary = {} +var redo_histories: Dictionary = {} #region SETUP func _ready() -> void: @@ -667,7 +667,9 @@ func _on_docs_button_pressed() -> void: func _duplicate_nodes_request() -> void: + print("duplicate request",graph_view.selected_node_ids) if graph_view.selected_node_ids.size() > 0 : + print("duplicating") var duplicate_nodes_operation: ParleyDuplicateOperation = ParleyDuplicateOperation.new(graph_view, graph_view.selected_node_ids) duplicate_nodes_operation.do() add_undo_operation(duplicate_nodes_operation) @@ -681,45 +683,51 @@ func _copy_nodes_request() -> void: func _paste_nodes_request() -> void: if copied_node_ids.size() > 0: - var duplicate_nodes_operation: ParleyPasteOperation = ParleyPasteOperation.new(graph_view, copied_node_ids) - duplicate_nodes_operation.do() - add_undo_operation(duplicate_nodes_operation) + var paste_operation: ParleyPasteOperation = ParleyPasteOperation.new(graph_view, copied_node_ids) + paste_operation.do() + add_undo_operation(paste_operation) func _delete_selected() -> void: if graph_view.selected_connections.size() > 0 || graph_view.selected_node_ids.size() > 0: - var deleteConnection: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selected_connections, graph_view.selected_node_ids) - deleteConnection.do() - add_undo_operation(deleteConnection) + var delete_operation: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selected_connections, graph_view.selected_node_ids) + delete_operation.do() + add_undo_operation(delete_operation) func _validate_history() -> void: - if not undo_history.has(dialogue_ast): - undo_history[dialogue_ast] = [] as Array[ParleyGraphOperation] - if not redo_history.has(dialogue_ast): - redo_history[dialogue_ast] = [] as Array[ParleyGraphOperation] + if not undo_histories.has(dialogue_ast): + undo_histories[dialogue_ast] = [] as Array[ParleyGraphOperation] + if not redo_histories.has(dialogue_ast): + redo_histories[dialogue_ast] = [] as Array[ParleyGraphOperation] func add_undo_operation(operation: ParleyGraphOperation) -> void: _validate_history() - redo_history[dialogue_ast].clear() - undo_history[dialogue_ast].push_back(operation) + var redo_history: Array[ParleyGraphOperation] = redo_histories[dialogue_ast] + var undo_history: Array[ParleyGraphOperation] = undo_histories[dialogue_ast] + redo_history.clear() + undo_history.push_back(operation) func _undo() -> void: _validate_history() - if undo_history[dialogue_ast].size() > 0: - var operation: ParleyGraphOperation = undo_history[dialogue_ast].pop_back() + var redo_history: Array[ParleyGraphOperation] = redo_histories[dialogue_ast] + var undo_history: Array[ParleyGraphOperation] = undo_histories[dialogue_ast] + if undo_history.size() > 0: + var operation: ParleyGraphOperation = undo_history.pop_back() operation.undo() - redo_history[dialogue_ast].push_back(operation) + redo_history.push_back(operation) func _redo() -> void: _validate_history() - if redo_history[dialogue_ast].size() > 0: - var operation: ParleyGraphOperation = redo_history[dialogue_ast].pop_back() + var redo_history: Array[ParleyGraphOperation] = redo_histories[dialogue_ast] + var undo_history: Array[ParleyGraphOperation] = undo_histories[dialogue_ast] + if redo_history.size() > 0: + var operation: ParleyGraphOperation = redo_history.pop_back() operation.do() - undo_history[dialogue_ast].push_back(operation) + undo_history.push_back(operation) #region HELPERS diff --git a/addons/parley/views/parley_graph_edge.gd b/addons/parley/views/parley_graph_edge.gd index 2de5f45..07ef43a 100644 --- a/addons/parley/views/parley_graph_edge.gd +++ b/addons/parley/views/parley_graph_edge.gd @@ -15,12 +15,12 @@ var to_node_name: String var from_node_name: String var edge_ast: ParleyEdgeAst -var previousFromColor: Color -var previousToColor: Color var selected: bool +var graph_view: ParleyGraphView # Duplicate should not be used on this class. The class is generated one time only at runtime when it is needed -func _init(_edge_ast: ParleyEdgeAst, _from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> void: +func _init(_graph_view: ParleyGraphView, _edge_ast: ParleyEdgeAst, _from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> void: + graph_view = _graph_view from_node = _from_node from_port = _from_port to_node = _to_node @@ -34,18 +34,12 @@ func _init(_edge_ast: ParleyEdgeAst, _from_node: ParleyGraphNode, _from_port: in edge_ast = _edge_ast -func disconnect_node(graph_view: ParleyGraphView) -> void: - if not graph_view: - return - +func disconnect_node() -> void: graph_view.ast.remove_edge(from_node_id, from_port, to_node_id, to_port) graph_view.disconnect_node(from_node_name, from_port, to_node_name, to_port) -func connect_node(graph_view: ParleyGraphView) -> int: - if not graph_view: - return FAILED - +func connect_node() -> int: graph_view.ast.edges.append(edge_ast) return graph_view.connect_node(from_node_name, from_port, to_node_name, to_port) @@ -54,8 +48,8 @@ func select() -> void: if selected: return selected = true - if not is_instance_valid(from_node) or not is_instance_valid(to_node): - return + + validate_nodes() from_node.select_from_slot(edge_ast.from_slot) to_node.select_to_slot(edge_ast.to_slot) @@ -65,8 +59,9 @@ func unselect() -> void: if not selected: return selected = false - if not is_instance_valid(from_node) or not is_instance_valid(to_node): - return + + validate_nodes() + if edge_ast.should_override_colour: from_node.deselect_from_slot(edge_ast.from_slot, edge_ast.colour_override) else: @@ -75,5 +70,12 @@ func unselect() -> void: to_node.unselect_to_slot(edge_ast.to_slot, from_node_colour) +func validate_nodes() -> void: + if not is_instance_valid(from_node): + from_node = graph_view.find_node_by_id(from_node_id) + if not is_instance_valid(to_node): + to_node = graph_view.find_node_by_id(to_node_id) + + func as_string() -> String: return "%s:%d -> %s:%d" % [from_node_name, from_port, to_node_name, to_port] diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index 60d21f6..2b1f0f4 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -37,16 +37,19 @@ func _exit_tree() -> void: func generate(arrange: bool = false) -> void: + print("generate") await clear() _generate_dialogue_nodes() if arrange: arrange_nodes() + + call_deferred("refresh_connections") func clear() -> void: - _unselect_connections() - _clear_selected_nodes() - clear_connections() + # _unselect_connections() + # _clear_selected_nodes() + # clear_connections() var children: Array[ParleyGraphNode] = [] for child: Node in get_children(): if child is ParleyGraphNode: @@ -318,6 +321,7 @@ func _create_group_node(ast_node: ParleyGroupNodeAst, _should_regenerate: bool = # EXPERIMENTAL: see how feedback goes. This is certainly a candidate to be put into settings ParleyUtils.signals.safe_connect(node.dragged, func(_from: Vector2, _to: Vector2) -> void: var _nodes: Array[ParleyGraphNode] = _update_nodes_covered_by_group_node(node, ast_node) + print("dragged") ) return node @@ -364,13 +368,20 @@ const CLICK_DISTANCE: float = 5 var on_unselect: Array[Callable] = [] var selected_connections: Array[ParleyGraphEdge] = [] var selected_node_ids: Array[String] = [] +var _node_selection_triggered: bool +var moving_nodes: bool func _on_node_selected(node: Node) -> void: + # if _try_select_connection(ParleyGraphUtils.get_cursor_pos_at_graph_view(self), is_command_or_control_pressed()): + # return + _node_selection_triggered = true var node_id: String = (node as ParleyGraphNode).id selected_node_ids.append(node_id) + print("selected callback ", node_id, " ", selected_node_ids.size()) func _on_node_deselected(node: Node) -> void: + print("deselected callback ", selected_node_ids.size()) selected_node_ids.erase((node as ParleyGraphNode).id) @@ -381,7 +392,7 @@ func _on_connections_deselected() -> void: func _gui_input(event: InputEvent) -> void: - if event is InputEventMouseButton: + if event is InputEventMouseButton: _handle_mouse_select(event as InputEventMouseButton) if event is InputEventKey: @@ -402,6 +413,7 @@ func _gui_input(event: InputEvent) -> void: func _clear_selected_nodes() -> void: + print("cleared selection", selected_node_ids.size()) selected_node_ids.clear() @@ -410,68 +422,95 @@ func _unselect_connections() -> void: connection.unselect() +func refresh_connections() -> void: + print("refreshing connections " , selected_connections.size()) + for connection :ParleyGraphEdge in selected_connections: + connection.unselect() + for connection :ParleyGraphEdge in selected_connections: + connection.select() + func _clear_selected_connections() -> void: selected_connections.clear() func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: - var foundAnyConnection: bool = false + if _node_selection_triggered: + print("returned") + _node_selection_triggered = false + return + if moving_nodes: + return + if mouse_event.is_released() and mouse_event.button_index == MOUSE_BUTTON_LEFT: var pointer_pos: Vector2 = (mouse_event.position + scroll_offset) / zoom + var is_control_pressed: bool = mouse_event.is_command_or_control_pressed() - if _node_exist_at_position(mouse_event.position): - _unselect_connections() - return - # basically ctrl/cmd+click selects multiple - if not mouse_event.is_command_or_control_pressed(): - _unselect_connections() - _clear_selected_nodes() - _clear_selected_connections() - - for conn: Dictionary in get_connection_list(): - var from_node_name: String = conn.get("from_node") - var to_node_name: String = conn.get("to_node") - var from_port: int = conn.get("from_port") - var to_port: int = conn.get("to_port") - - var from_node: ParleyGraphNode = get_node(NodePath(from_node_name)) as ParleyGraphNode - var to_node: ParleyGraphNode = get_node(NodePath(to_node_name)) as ParleyGraphNode - if not from_node or not to_node: - continue - - var from_pos: Vector2 = _get_slot_position(from_node, from_port, true) - var to_pos: Vector2 = _get_slot_position(to_node, to_port, false) - var controls : Array = get_graph_bezier_controls(from_pos, to_pos) - var p0: Vector2 = controls[0] - var p1: Vector2 = controls[1] - var p2: Vector2 = controls[2] - var p3: Vector2 = controls[3] - var distance: float = get_distance_to_bezier(pointer_pos, p0, p1, p2, p3) * zoom - # print("From: ",from_node ," To", to_node," Comparing x: ", from_pos.x, " ", pointer_pos.x, " ", to_pos.x , " distance: ", distance) - - if distance <= CLICK_DISTANCE: - foundAnyConnection = true - var connection: ParleyGraphEdge = _find_existing_connection(from_node, from_port, to_node, to_port) - var edge_ast: ParleyEdgeAst = ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) - if connection == null: - connection = ParleyGraphEdge.new(edge_ast, from_node, from_port, to_node, to_port) - connection.select() - selected_connections.append(connection as ParleyGraphEdge) - on_unselect.append(func() -> void: - connection.unselect() - selected_connections.erase(connection) - ) - print_rich(ParleyUtils.log.info_msg("Selected connection: {connection}".format({"connection": connection.as_string()}))) - else: - selected_connections.erase(connection) + # if _node_exist_at_position(mouse_event.position): + # _unselect_connections() + # return + + if _try_select_connection(pointer_pos, is_control_pressed): + pass + + +func _try_select_connection(pointer_pos: Vector2, is_control_pressed: bool) -> bool: + # print("pointer_pos ", pointer_pos," control pressed: ", is_control_pressed) + # basically ctrl/cmd+click selects multiple + if not is_control_pressed: + _unselect_connections() + _clear_selected_nodes() + _clear_selected_connections() + + var foundAnyConnection: bool = false + for conn: Dictionary in get_connection_list(): + var from_node_name: String = conn.get("from_node") + var to_node_name: String = conn.get("to_node") + var from_port: int = conn.get("from_port") + var to_port: int = conn.get("to_port") + + var from_node: ParleyGraphNode = get_node(NodePath(from_node_name)) as ParleyGraphNode + var to_node: ParleyGraphNode = get_node(NodePath(to_node_name)) as ParleyGraphNode + if not from_node or not to_node: + continue + + var from_pos: Vector2 = _get_slot_position(from_node, from_port, true) + var to_pos: Vector2 = _get_slot_position(to_node, to_port, false) + var controls : Array = get_graph_bezier_controls(from_pos, to_pos) + var p0: Vector2 = controls[0] + var p1: Vector2 = controls[1] + var p2: Vector2 = controls[2] + var p3: Vector2 = controls[3] + var distance: float = get_distance_to_bezier(pointer_pos, p0, p1, p2, p3) * zoom + # print("From: ",from_node ," To", to_node," Comparing x: ", from_pos.x, " ", pointer_pos.x, " ", to_pos.x , " distance: ", distance) + + if distance <= CLICK_DISTANCE: + foundAnyConnection = true + var connection: ParleyGraphEdge = _find_existing_connection(from_node, from_port, to_node, to_port) + var edge_ast: ParleyEdgeAst = ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) + if connection == null: + connection = ParleyGraphEdge.new(self, edge_ast, from_node, from_port, to_node, to_port) + connection.select() + selected_connections.append(connection as ParleyGraphEdge) + on_unselect.append(func() -> void: connection.unselect() - print_rich(ParleyUtils.log.info_msg("Unselected existing connection: {connection}".format({"connection": connection.as_string()}))) - break + selected_connections.erase(connection) + ) + print_rich(ParleyUtils.log.info_msg("Selected connection: {connection}".format({"connection": connection.as_string()}))) + else: + selected_connections.erase(connection) + connection.unselect() + print_rich(ParleyUtils.log.info_msg("Unselected existing connection: {connection}".format({"connection": connection.as_string()}))) + break + + if not foundAnyConnection && not is_control_pressed: + while on_unselect.size() > 0: + var callable: Callable = on_unselect.pop_front() + callable.call() + return foundAnyConnection + - if not foundAnyConnection && not mouse_event.is_command_or_control_pressed(): - while on_unselect.size() > 0: - var callable: Callable = on_unselect.pop_front() - callable.call() +func is_command_or_control_pressed() -> bool: + return Input.is_key_pressed(KEY_CTRL) or Input.is_key_pressed(KEY_META) func _find_existing_connection(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> ParleyGraphEdge: @@ -582,10 +621,19 @@ func _node_exist_at_position(pos: Vector2) -> GraphNode: # GraphEdit applies zoom + scroll internally, so convert position properly for node_id : String in selected_node_ids: var node : ParleyGraphNode = find_node_by_id(node_id) - var node_rect: Rect2 = Rect2(node.position, node.size) - if node_rect.has_point(pos) and node is not ParleyGroupNode: + var node_rect: Rect2 = Rect2(node.position, node.size / zoom) + print("pos",pos, "rect: ",node_rect) + if node_rect.has_point(pos): return node return null #endregion + +func _on_begin_node_move() -> void: + moving_nodes = true + print("moving begin") + +func _on_end_node_move() -> void: + moving_nodes = false + print("moving end") diff --git a/addons/parley/views/parley_graph_view.tscn b/addons/parley/views/parley_graph_view.tscn index 87c0b3e..bc5a7a9 100644 --- a/addons/parley/views/parley_graph_view.tscn +++ b/addons/parley/views/parley_graph_view.tscn @@ -14,3 +14,6 @@ zoom = 0.5 show_zoom_label = true show_arrange_button = false script = ExtResource("1_iscqg") + +[connection signal="begin_node_move" from="." to="." method="_on_begin_node_move"] +[connection signal="end_node_move" from="." to="." method="_on_end_node_move"] From 81fe847b9b53544f1f728512777d291e881c24cc Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sun, 8 Feb 2026 19:29:13 +0300 Subject: [PATCH 15/24] - group selection fix and removed logs --- addons/parley/main_panel.gd | 2 -- addons/parley/views/parley_graph_view.gd | 31 ++++++++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index f91a3c7..85affe4 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -667,9 +667,7 @@ func _on_docs_button_pressed() -> void: func _duplicate_nodes_request() -> void: - print("duplicate request",graph_view.selected_node_ids) if graph_view.selected_node_ids.size() > 0 : - print("duplicating") var duplicate_nodes_operation: ParleyDuplicateOperation = ParleyDuplicateOperation.new(graph_view, graph_view.selected_node_ids) duplicate_nodes_operation.do() add_undo_operation(duplicate_nodes_operation) diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index 2b1f0f4..9bbcb4a 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -37,7 +37,6 @@ func _exit_tree() -> void: func generate(arrange: bool = false) -> void: - print("generate") await clear() _generate_dialogue_nodes() if arrange: @@ -372,10 +371,13 @@ var _node_selection_triggered: bool var moving_nodes: bool func _on_node_selected(node: Node) -> void: - # if _try_select_connection(ParleyGraphUtils.get_cursor_pos_at_graph_view(self), is_command_or_control_pressed()): - # return _node_selection_triggered = true var node_id: String = (node as ParleyGraphNode).id + if _try_select_connection(ParleyGraphUtils.get_cursor_pos_at_graph_view(self), is_command_or_control_pressed()): + await _wait_for_mouse_release() + var unselected_node : ParleyGraphNode = find_node_by_id(node_id) + unselected_node.call_deferred("set_selected", false) + return selected_node_ids.append(node_id) print("selected callback ", node_id, " ", selected_node_ids.size()) @@ -413,7 +415,6 @@ func _gui_input(event: InputEvent) -> void: func _clear_selected_nodes() -> void: - print("cleared selection", selected_node_ids.size()) selected_node_ids.clear() @@ -423,19 +424,22 @@ func _unselect_connections() -> void: func refresh_connections() -> void: - print("refreshing connections " , selected_connections.size()) for connection :ParleyGraphEdge in selected_connections: connection.unselect() for connection :ParleyGraphEdge in selected_connections: connection.select() + +func _wait_for_mouse_release() -> void: + while Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): + await get_tree().process_frame + func _clear_selected_connections() -> void: selected_connections.clear() func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: if _node_selection_triggered: - print("returned") _node_selection_triggered = false return if moving_nodes: @@ -632,8 +636,19 @@ func _node_exist_at_position(pos: Vector2) -> GraphNode: func _on_begin_node_move() -> void: moving_nodes = true - print("moving begin") + func _on_end_node_move() -> void: moving_nodes = false - print("moving end") + + +func _force_stop_drag() -> void: + release_focus() + + # Reset cursor explicitly + Input.set_default_cursor_shape(Input.CURSOR_ARROW) + + # This is the critical part: + # force GraphEdit to re-evaluate drag state + set_process_unhandled_input(false) + set_process_unhandled_input(true) \ No newline at end of file From 348b24b112f5d283a584c8fac0eb0eac64c118a8 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sun, 22 Feb 2026 13:30:55 +0300 Subject: [PATCH 16/24] - added @tool - changed name connection to edge - removed some of the commented code - added copy function to nodeast - improved duplicate operation - added copy paste support --- .../parley_connection_to_empty_operation.gd | 7 +- .../parley_create_node_operation.gd | 1 + .../parley_delete_operation.gd | 33 +++---- .../parley_duplicate_operation.gd | 32 ++++--- .../parley_graph_operation.gd | 1 + .../graph_operations/parley_graph_utils.gd | 3 +- .../graph_operations/parley_node_data.gd | 7 +- .../parley_paste_operation.gd | 53 ++++++++++- addons/parley/main_panel.gd | 4 +- addons/parley/models/dialogue_sequence_ast.gd | 8 ++ addons/parley/models/node_ast.gd | 12 ++- addons/parley/views/parley_graph_view.gd | 95 ++++++++----------- 12 files changed, 157 insertions(+), 99 deletions(-) diff --git a/addons/parley/graph_operations/parley_connection_to_empty_operation.gd b/addons/parley/graph_operations/parley_connection_to_empty_operation.gd index 0beb355..ffcd417 100644 --- a/addons/parley/graph_operations/parley_connection_to_empty_operation.gd +++ b/addons/parley/graph_operations/parley_connection_to_empty_operation.gd @@ -1,3 +1,4 @@ +@tool class_name ParleyConnectionToEmptyOperation extends ParleyGraphOperation @@ -17,10 +18,10 @@ func _init(_graph_view: ParleyGraphView, _node_type: ParleyDialogueSequenceAst.T func undo() -> void: - var connections : Array[ParleyGraphEdge] = ParleyGraphUtils.get_connections_for_node(graph_view, node_id) + var edges : Array[ParleyGraphEdge] = ParleyGraphUtils.get_edges_for_node(graph_view, node_id) - for connection: ParleyGraphEdge in connections: - connection.disconnect_node() + for edge: ParleyGraphEdge in edges: + edge.disconnect_node() var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(node_id) if ast != null: diff --git a/addons/parley/graph_operations/parley_create_node_operation.gd b/addons/parley/graph_operations/parley_create_node_operation.gd index 52c7577..d8bb847 100644 --- a/addons/parley/graph_operations/parley_create_node_operation.gd +++ b/addons/parley/graph_operations/parley_create_node_operation.gd @@ -1,3 +1,4 @@ +@tool class_name ParleyCreateNodeOperation extends ParleyGraphOperation diff --git a/addons/parley/graph_operations/parley_delete_operation.gd b/addons/parley/graph_operations/parley_delete_operation.gd index 9e710c7..6095e0c 100644 --- a/addons/parley/graph_operations/parley_delete_operation.gd +++ b/addons/parley/graph_operations/parley_delete_operation.gd @@ -1,14 +1,15 @@ +@tool class_name ParleyDeleteOperation extends ParleyGraphOperation -var selected_connections: Array[ParleyGraphEdge] +var selected_edges: Array[ParleyGraphEdge] var selected_node_ids: Array[String] var deleted_node_datas: Array[NodeData] var graph_view: ParleyGraphView -func _init(_graph_view: ParleyGraphView, _selected_connections: Array[ParleyGraphEdge], _selected_node_ids: Array[String]) -> void: +func _init(_graph_view: ParleyGraphView, _selected_edges: Array[ParleyGraphEdge], _selected_node_ids: Array[String]) -> void: graph_view = _graph_view - selected_connections = _selected_connections.duplicate() + selected_edges = _selected_edges.duplicate() selected_node_ids = _selected_node_ids.duplicate() @@ -25,11 +26,11 @@ func undo() -> void: created_node_list.append(added_node.id) for node_data : NodeData in deleted_node_datas: - for connection : ParleyGraphEdge in node_data.connections: - connection.connect_node() + for edge : ParleyGraphEdge in node_data.edges: + edge.connect_node() - for connection : ParleyGraphEdge in selected_connections: - connection.connect_node() + for edge : ParleyGraphEdge in selected_edges: + edge.connect_node() await graph_view.generate() @@ -38,18 +39,18 @@ func undo() -> void: var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) node.set_selected(true) - for connection : ParleyGraphEdge in selected_connections: - connection.unselect() - for connection : ParleyGraphEdge in selected_connections: - connection.select() + for edge : ParleyGraphEdge in selected_edges: + edge.unselect() + for edge : ParleyGraphEdge in selected_edges: + edge.select() func do() -> void: deleted_node_datas.clear() for selected_node_id : String in selected_node_ids: var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node_id) if ast != null: - var connections : Array[ParleyGraphEdge] = ParleyGraphUtils.get_connections_for_node(graph_view, selected_node_id) - deleted_node_datas.append(NodeData.new(selected_node_id, ast, connections)) + var edges : Array[ParleyGraphEdge] = ParleyGraphUtils.get_edges_for_node(graph_view, selected_node_id) + deleted_node_datas.append(NodeData.new(selected_node_id, ast, edges)) for selected_node_id : String in selected_node_ids: var ast : ParleyNodeAst = graph_view.ast.find_node_by_id(selected_node_id) @@ -59,9 +60,9 @@ func do() -> void: graph_view.remove_child(selected_node) graph_view.ast.remove_node(selected_node_id) - graph_view._on_connections_deselected() - for connection : ParleyGraphEdge in selected_connections: - connection.disconnect_node() + graph_view._on_edges_deselected() + for edge : ParleyGraphEdge in selected_edges: + edge.disconnect_node() graph_view.generate() diff --git a/addons/parley/graph_operations/parley_duplicate_operation.gd b/addons/parley/graph_operations/parley_duplicate_operation.gd index b31a8da..1b015ff 100644 --- a/addons/parley/graph_operations/parley_duplicate_operation.gd +++ b/addons/parley/graph_operations/parley_duplicate_operation.gd @@ -1,3 +1,4 @@ +@tool class_name ParleyDuplicateOperation extends ParleyGraphOperation @@ -12,22 +13,29 @@ func _init(_graph_view: ParleyGraphView, _selected_node_ids: Array[String]) -> v func do() -> void: - graph_view._clear_selected_nodes() var created_node_list: Array[String] var node_names: Array[String] for node_id: String in selected_node_ids: - var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) - var ast_node: ParleyNodeAst = graph_view.ast.add_new_node(node.type, node.position_offset + Vector2.DOWN * 200 + Vector2.RIGHT * 200 ) + var ast_node : ParleyNodeAst = graph_view.ast.find_node_by_id(node_id) if ast_node: - node_id = ast_node.id - var connections : Array[ParleyGraphEdge] = ParleyGraphUtils.get_connections_for_node(graph_view, node_id) - created_node_datas.append(NodeData.new(node_id, ast_node, connections)) - created_node_list.append(node_id) - node_names.append(node.name) - if ast_node is ParleyGroupNodeAst: - (ast_node as ParleyGroupNodeAst).size = node.size + var new_ast_node : ParleyNodeAst = graph_view.ast.copy_node_ast(ast_node) + new_ast_node.position = new_ast_node.position + Vector2.RIGHT * 200 + Vector2.DOWN * 200 + + # TODO: if we want to duplicate the connections create edges and pass them to the edge list later + created_node_datas.append(NodeData.new(new_ast_node.id, new_ast_node, [])) + created_node_list.append(new_ast_node.id) + node_names.append(graph_view.get_ast_node_name(new_ast_node)) + + # var selected_ast : ParleyNodeAst = graph_view.ast.find_node_by_id(node_id) + # var ast_node: ParleyNodeAst = graph_view.ast.add_new_node(selected_ast.type, selected_ast.position + Vector2.DOWN * 200 + Vector2.RIGHT * 200 ) + # if ast_node: + # node_id = ast_node.id + # var connections : Array[ParleyGraphEdge] = ParleyGraphUtils.get_connections_for_node(graph_view, node_id) + # created_node_datas.append(NodeData.new(node_id, ast_node, connections)) + # created_node_list.append(node_id) + # node_names.append(selected_ast.resource_name) await graph_view.generate() @@ -41,8 +49,8 @@ func do() -> void: func undo() -> void: for node_data: NodeData in created_node_datas: - for connection: ParleyGraphEdge in node_data.connections: - connection.disconnect_node() + for edge: ParleyGraphEdge in node_data.edges: + edge.disconnect_node() var selected_node : ParleyGraphNode = graph_view.find_node_by_id(node_data.node_id) if selected_node: diff --git a/addons/parley/graph_operations/parley_graph_operation.gd b/addons/parley/graph_operations/parley_graph_operation.gd index 35daeec..b059048 100644 --- a/addons/parley/graph_operations/parley_graph_operation.gd +++ b/addons/parley/graph_operations/parley_graph_operation.gd @@ -1,3 +1,4 @@ +@tool extends Object class_name ParleyGraphOperation diff --git a/addons/parley/graph_operations/parley_graph_utils.gd b/addons/parley/graph_operations/parley_graph_utils.gd index c333716..2c392b5 100644 --- a/addons/parley/graph_operations/parley_graph_utils.gd +++ b/addons/parley/graph_operations/parley_graph_utils.gd @@ -1,7 +1,8 @@ +@tool class_name ParleyGraphUtils extends Node -static func get_connections_for_node(graph_view: ParleyGraphView, node_id: String) -> Array: +static func get_edges_for_node(graph_view: ParleyGraphView, node_id: String) -> Array: var result: Array[ParleyGraphEdge] = [] var connections: Array[Dictionary] = graph_view.get_connection_list() diff --git a/addons/parley/graph_operations/parley_node_data.gd b/addons/parley/graph_operations/parley_node_data.gd index ebe2b0c..467d469 100644 --- a/addons/parley/graph_operations/parley_node_data.gd +++ b/addons/parley/graph_operations/parley_node_data.gd @@ -1,10 +1,11 @@ +@tool class_name NodeData var node_ast: ParleyNodeAst -var connections: Array[ParleyGraphEdge] +var edges: Array[ParleyGraphEdge] var node_id: String -func _init(_node_id: String, _node_ast :ParleyNodeAst, _connections: Array[ParleyGraphEdge]) -> void: +func _init(_node_id: String, _node_ast :ParleyNodeAst, _edges: Array[ParleyGraphEdge]) -> void: node_id = _node_id node_ast = _node_ast - connections = _connections \ No newline at end of file + edges = _edges \ No newline at end of file diff --git a/addons/parley/graph_operations/parley_paste_operation.gd b/addons/parley/graph_operations/parley_paste_operation.gd index f85ede5..d4bbf34 100644 --- a/addons/parley/graph_operations/parley_paste_operation.gd +++ b/addons/parley/graph_operations/parley_paste_operation.gd @@ -1,9 +1,11 @@ -# PasteShortcut.gd +@tool + extends ParleyGraphOperation class_name ParleyPasteOperation var node_ids: Array[String] var graph_view: ParleyGraphView +var created_node_datas: Array[NodeData] func _init(_graph_view: ParleyGraphView, _node_ids: Array[String]) -> void: graph_view = _graph_view @@ -11,7 +13,52 @@ func _init(_graph_view: ParleyGraphView, _node_ids: Array[String]) -> void: func do() -> void: - pass + graph_view._clear_selected_nodes() + var created_node_list: Array[String] + var node_names: Array[String] + + var node_asts : Array[ParleyNodeAst] = _get_node_asts() + var center_position: Vector2 = Vector2.ZERO + + for node_ast: ParleyNodeAst in node_asts: + center_position += node_ast.position + center_position /= node_asts.size() + + for node_ast: ParleyNodeAst in node_asts: + if node_ast: + var new_ast_node : ParleyNodeAst = graph_view.ast.copy_node_ast(node_ast) + var diff: Vector2 = ParleyGraphUtils.get_cursor_pos_at_graph_view(graph_view) - center_position + new_ast_node.position = new_ast_node.position + diff + # TODO: if we want to duplicate the connections create edges and pass them to the edge list later + created_node_datas.append(NodeData.new(new_ast_node.id, new_ast_node, [])) + created_node_list.append(new_ast_node.id) + node_names.append(graph_view.get_ast_node_name(new_ast_node)) + + await graph_view.generate() + + for i: int in range(created_node_list.size()): + var node_id: String = created_node_list[i] + var node_name: String = node_names[i] + var node : ParleyGraphNode = graph_view.find_node_by_id(node_id) + node.set_selected(true) + node.name = node_name func undo() -> void: - pass + for node_data: NodeData in created_node_datas: + for edge: ParleyGraphEdge in node_data.edges: + edge.disconnect_node() + + var selected_node : ParleyGraphNode = graph_view.find_node_by_id(node_data.node_id) + if selected_node: + graph_view._on_node_deselected(selected_node) + graph_view.remove_child(selected_node) + + graph_view.ast.remove_node(node_data.node_id) + + await graph_view.generate() + +func _get_node_asts() -> Array[ParleyNodeAst]: + var result: Array[ParleyNodeAst] + for node_id: String in node_ids: + result.append(graph_view.ast.find_node_by_id(node_id)) + return result \ No newline at end of file diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index 85affe4..0506669 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -687,8 +687,8 @@ func _paste_nodes_request() -> void: func _delete_selected() -> void: - if graph_view.selected_connections.size() > 0 || graph_view.selected_node_ids.size() > 0: - var delete_operation: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selected_connections, graph_view.selected_node_ids) + if graph_view.selected_edges.size() > 0 || graph_view.selected_node_ids.size() > 0: + var delete_operation: ParleyDeleteOperation = ParleyDeleteOperation.new(graph_view, graph_view.selected_edges, graph_view.selected_node_ids) delete_operation.do() add_undo_operation(delete_operation) diff --git a/addons/parley/models/dialogue_sequence_ast.gd b/addons/parley/models/dialogue_sequence_ast.gd index 321b5b5..02380a9 100644 --- a/addons/parley/models/dialogue_sequence_ast.gd +++ b/addons/parley/models/dialogue_sequence_ast.gd @@ -144,6 +144,13 @@ func add_new_node(type: Type, position: Vector2 = Vector2.ZERO) -> ParleyNodeAst _emit_dialogue_updated() return ast_node +func copy_node_ast(ast_node: ParleyNodeAst) -> ParleyNodeAst: + print_rich(ParleyUtils.log.info_msg('Inserting new Node into the AST of type: %s' % [ast_node.type])) + var new_node_ast : ParleyNodeAst = ast_node.copy(_generate_node_id()) + nodes.push_back(new_node_ast) + _emit_dialogue_updated() + return new_node_ast + func add_node_from_ast(node_ast: ParleyNodeAst) -> ParleyNodeAst: # Probably need to make sure the provided ID doesn't already exist for node : ParleyNodeAst in nodes: @@ -257,6 +264,7 @@ func remove_node(node_id: String) -> void: func find_node_by_id(id: String) -> ParleyNodeAst: var filtered_nodes: Array = nodes.filter(func(node: ParleyNodeAst) -> bool: return str(node.id) == str(id)) if filtered_nodes.size() != 1: + print("filtered node count",filtered_nodes.size()) print_rich(ParleyUtils.log.info_msg("No AST Node found with ID: {id}".format({'id': id}))) return null return filtered_nodes.front() diff --git a/addons/parley/models/node_ast.gd b/addons/parley/models/node_ast.gd index 49ec21f..7a58dac 100644 --- a/addons/parley/models/node_ast.gd +++ b/addons/parley/models/node_ast.gd @@ -36,10 +36,10 @@ func _init(_id: String = "", _position: Vector2 = Vector2.ZERO) -> void: #region SETTERS func _set_id(new_id: String) -> void: # Once defined, id should be immutable - if not id or id == id_prefix or not id.begins_with(id_prefix): + if id.begins_with(id_prefix): + id = new_id + elif not id or id == id_prefix or not id.begins_with(id_prefix): id = new_id if new_id.begins_with(id_prefix) else "%s%s" % [id_prefix, new_id] -#endregion - #region UTILS ## Convert this resource into a Dictionary for storage @@ -58,3 +58,9 @@ func _to_string() -> String: static func get_colour() -> Color: return Color("#7a2167") #endregion + +func copy(_id: String) -> ParleyNodeAst: + var duplicated: ParleyNodeAst = self.duplicate() + duplicated.id = _id + return duplicated + diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index 9bbcb4a..0d3e67a 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -42,13 +42,10 @@ func generate(arrange: bool = false) -> void: if arrange: arrange_nodes() - call_deferred("refresh_connections") + call_deferred("refresh_edges") func clear() -> void: - # _unselect_connections() - # _clear_selected_nodes() - # clear_connections() var children: Array[ParleyGraphNode] = [] for child: Node in get_children(): if child is ParleyGraphNode: @@ -365,7 +362,7 @@ func set_selected_by_id(id: String, _goto: bool = true) -> void: const CLICK_DISTANCE: float = 5 var on_unselect: Array[Callable] = [] -var selected_connections: Array[ParleyGraphEdge] = [] +var selected_edges: Array[ParleyGraphEdge] = [] var selected_node_ids: Array[String] = [] var _node_selection_triggered: bool var moving_nodes: bool @@ -373,21 +370,21 @@ var moving_nodes: bool func _on_node_selected(node: Node) -> void: _node_selection_triggered = true var node_id: String = (node as ParleyGraphNode).id - if _try_select_connection(ParleyGraphUtils.get_cursor_pos_at_graph_view(self), is_command_or_control_pressed()): + if _try_select_edge(ParleyGraphUtils.get_cursor_pos_at_graph_view(self), is_command_or_control_pressed()): await _wait_for_mouse_release() var unselected_node : ParleyGraphNode = find_node_by_id(node_id) unselected_node.call_deferred("set_selected", false) return selected_node_ids.append(node_id) - print("selected callback ", node_id, " ", selected_node_ids.size()) + # print("selected callback ", node_id, " ", selected_node_ids.size()) func _on_node_deselected(node: Node) -> void: - print("deselected callback ", selected_node_ids.size()) + # print("deselected callback ", selected_node_ids.size()) selected_node_ids.erase((node as ParleyGraphNode).id) -func _on_connections_deselected() -> void: +func _on_edges_deselected() -> void: while on_unselect.size() > 0: var callable: Callable = on_unselect.pop_front() callable.call() @@ -418,24 +415,24 @@ func _clear_selected_nodes() -> void: selected_node_ids.clear() -func _unselect_connections() -> void: - for connection :ParleyGraphEdge in selected_connections: - connection.unselect() +func _unselect_edges() -> void: + for edge :ParleyGraphEdge in selected_edges: + edge.unselect() -func refresh_connections() -> void: - for connection :ParleyGraphEdge in selected_connections: - connection.unselect() - for connection :ParleyGraphEdge in selected_connections: - connection.select() +func refresh_edges() -> void: + for edge :ParleyGraphEdge in selected_edges: + edge.unselect() + for edge :ParleyGraphEdge in selected_edges: + edge.select() func _wait_for_mouse_release() -> void: while Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): await get_tree().process_frame -func _clear_selected_connections() -> void: - selected_connections.clear() +func _clear_selected_edges() -> void: + selected_edges.clear() func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: @@ -450,22 +447,22 @@ func _handle_mouse_select(mouse_event: InputEventMouseButton) -> void: var is_control_pressed: bool = mouse_event.is_command_or_control_pressed() # if _node_exist_at_position(mouse_event.position): - # _unselect_connections() + # _unselect_edges() # return - if _try_select_connection(pointer_pos, is_control_pressed): + if _try_select_edge(pointer_pos, is_control_pressed): pass -func _try_select_connection(pointer_pos: Vector2, is_control_pressed: bool) -> bool: +func _try_select_edge(pointer_pos: Vector2, is_control_pressed: bool) -> bool: # print("pointer_pos ", pointer_pos," control pressed: ", is_control_pressed) # basically ctrl/cmd+click selects multiple if not is_control_pressed: - _unselect_connections() + _unselect_edges() _clear_selected_nodes() - _clear_selected_connections() + _clear_selected_edges() - var foundAnyConnection: bool = false + var foundAnyEdge: bool = false for conn: Dictionary in get_connection_list(): var from_node_name: String = conn.get("from_node") var to_node_name: String = conn.get("to_node") @@ -488,37 +485,35 @@ func _try_select_connection(pointer_pos: Vector2, is_control_pressed: bool) -> b # print("From: ",from_node ," To", to_node," Comparing x: ", from_pos.x, " ", pointer_pos.x, " ", to_pos.x , " distance: ", distance) if distance <= CLICK_DISTANCE: - foundAnyConnection = true - var connection: ParleyGraphEdge = _find_existing_connection(from_node, from_port, to_node, to_port) + foundAnyEdge = true + var edge: ParleyGraphEdge = _find_existing_edge(from_node, from_port, to_node, to_port) var edge_ast: ParleyEdgeAst = ast.get_edge_ast(from_node.id, from_port, to_node.id, to_port) - if connection == null: - connection = ParleyGraphEdge.new(self, edge_ast, from_node, from_port, to_node, to_port) - connection.select() - selected_connections.append(connection as ParleyGraphEdge) + if edge == null: + edge = ParleyGraphEdge.new(self, edge_ast, from_node, from_port, to_node, to_port) + edge.select() + selected_edges.append(edge as ParleyGraphEdge) on_unselect.append(func() -> void: - connection.unselect() - selected_connections.erase(connection) + edge.unselect() + selected_edges.erase(edge) ) - print_rich(ParleyUtils.log.info_msg("Selected connection: {connection}".format({"connection": connection.as_string()}))) + print_rich(ParleyUtils.log.info_msg("Selected edge: {edge}".format({"edge": edge.as_string()}))) else: - selected_connections.erase(connection) - connection.unselect() - print_rich(ParleyUtils.log.info_msg("Unselected existing connection: {connection}".format({"connection": connection.as_string()}))) + selected_edges.erase(edge) + edge.unselect() + print_rich(ParleyUtils.log.info_msg("Unselected existing edge: {edge}".format({"edge": edge.as_string()}))) break - if not foundAnyConnection && not is_control_pressed: - while on_unselect.size() > 0: - var callable: Callable = on_unselect.pop_front() - callable.call() - return foundAnyConnection + if not foundAnyEdge && not is_control_pressed: + _on_edges_deselected() + return foundAnyEdge func is_command_or_control_pressed() -> bool: return Input.is_key_pressed(KEY_CTRL) or Input.is_key_pressed(KEY_META) -func _find_existing_connection(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> ParleyGraphEdge: - for con: ParleyGraphEdge in selected_connections: +func _find_existing_edge(_from_node: ParleyGraphNode, _from_port: int, _to_node: ParleyGraphNode, _to_port: int) -> ParleyGraphEdge: + for con: ParleyGraphEdge in selected_edges: if con.from_node == _from_node && con.from_port == _from_port && con.to_node == _to_node && con.to_port == _to_port: return con return null @@ -640,15 +635,3 @@ func _on_begin_node_move() -> void: func _on_end_node_move() -> void: moving_nodes = false - - -func _force_stop_drag() -> void: - release_focus() - - # Reset cursor explicitly - Input.set_default_cursor_shape(Input.CURSOR_ARROW) - - # This is the critical part: - # force GraphEdit to re-evaluate drag state - set_process_unhandled_input(false) - set_process_unhandled_input(true) \ No newline at end of file From 53d1116e1cf74e02852080f5505771050ab450f4 Mon Sep 17 00:00:00 2001 From: JosephStar318 <44904505+JosephStar318@users.noreply.github.com> Date: Sat, 7 Mar 2026 12:14:38 +0300 Subject: [PATCH 17/24] Update addons/parley/models/dialogue_sequence_ast.gd Co-authored-by: Jonny Green --- addons/parley/models/dialogue_sequence_ast.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/parley/models/dialogue_sequence_ast.gd b/addons/parley/models/dialogue_sequence_ast.gd index 02380a9..3d30af0 100644 --- a/addons/parley/models/dialogue_sequence_ast.gd +++ b/addons/parley/models/dialogue_sequence_ast.gd @@ -155,7 +155,7 @@ func add_node_from_ast(node_ast: ParleyNodeAst) -> ParleyNodeAst: # Probably need to make sure the provided ID doesn't already exist for node : ParleyNodeAst in nodes: if node.id == node_ast.id: - print("Id already exists in dialogue sequence") + push_warning(ParleyUtils.log.warn_msg("ID already exists in Dialogue Sequence. Not adding to AST.")) return # Also worth storing the insertion index somewhere as well? From 2eea03ba0537ff1ed07d366ca697c8f0fc5ff058 Mon Sep 17 00:00:00 2001 From: JosephStar318 <44904505+JosephStar318@users.noreply.github.com> Date: Sat, 7 Mar 2026 12:15:34 +0300 Subject: [PATCH 18/24] Update addons/parley/models/dialogue_sequence_ast.gd Co-authored-by: Jonny Green --- addons/parley/models/dialogue_sequence_ast.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/parley/models/dialogue_sequence_ast.gd b/addons/parley/models/dialogue_sequence_ast.gd index 3d30af0..11271f5 100644 --- a/addons/parley/models/dialogue_sequence_ast.gd +++ b/addons/parley/models/dialogue_sequence_ast.gd @@ -158,7 +158,7 @@ func add_node_from_ast(node_ast: ParleyNodeAst) -> ParleyNodeAst: push_warning(ParleyUtils.log.warn_msg("ID already exists in Dialogue Sequence. Not adding to AST.")) return - # Also worth storing the insertion index somewhere as well? + # TODO: also worth storing the insertion index somewhere as well nodes.append(node_ast) _emit_dialogue_updated() return node_ast From 784ba06be56eed4d82263c23e89f247a02156aa1 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sat, 7 Mar 2026 12:17:56 +0300 Subject: [PATCH 19/24] - comment cheange on id setter method on node_ast --- addons/parley/models/node_ast.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/parley/models/node_ast.gd b/addons/parley/models/node_ast.gd index 7a58dac..0e21586 100644 --- a/addons/parley/models/node_ast.gd +++ b/addons/parley/models/node_ast.gd @@ -35,7 +35,7 @@ func _init(_id: String = "", _position: Vector2 = Vector2.ZERO) -> void: #region SETTERS func _set_id(new_id: String) -> void: - # Once defined, id should be immutable + # Only allow change when copying or if not defined if id.begins_with(id_prefix): id = new_id elif not id or id == id_prefix or not id.begins_with(id_prefix): From cf989b9b239d5928f98fe04665bdbbc94468f135 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sat, 7 Mar 2026 13:29:46 +0300 Subject: [PATCH 20/24] - added feature flag and made shortcuts an optional feature - copy shortcut fixes and comment changes --- addons/parley/constants.gd | 1 + addons/parley/main_panel.gd | 44 +++++++++++++------ addons/parley/models/dialogue_sequence_ast.gd | 21 ++++++--- addons/parley/models/node_ast.gd | 6 --- addons/parley/settings.gd | 1 + addons/parley/views/parley_graph_view.gd | 13 +++++- project.godot | 1 + 7 files changed, 60 insertions(+), 27 deletions(-) diff --git a/addons/parley/constants.gd b/addons/parley/constants.gd index a0574e8..d74158e 100644 --- a/addons/parley/constants.gd +++ b/addons/parley/constants.gd @@ -20,6 +20,7 @@ const PARLEY_RUNTIME_SINGLETON: String = "ParleyRuntime" #region Editor # User settings const EDITOR_CURRENT_DIALOGUE_SEQUENCE_PATH: String = "parley/editor/current_dialogue_sequence_path" +const EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE: String = "parley/editor/keyboard_shortcuts" #endregion #region Dialogue diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index 0506669..d84e36e 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -3,7 +3,7 @@ @tool class_name ParleyMainPanel extends VBoxContainer - +const Constants = preload('./constants.gd') const new_file_icon: CompressedTexture2D = preload("./assets/New.svg") const load_file_icon: CompressedTexture2D = preload("./assets/Load.svg") const export_to_csv_icon: CompressedTexture2D = preload("./assets/Export.svg") @@ -17,7 +17,6 @@ const end_node_icon: CompressedTexture2D = preload("./assets/End.svg") const group_node_icon: CompressedTexture2D = preload("./assets/Group.svg") const jump_node_icon: CompressedTexture2D = preload("./assets/Jump.svg") - var parley_manager: ParleyManager @export var dialogue_ast: ParleyDialogueSequenceAst = ParleyDialogueSequenceAst.new(): set = _set_dialogue_ast @export var action_store: ParleyActionStore = ParleyActionStore.new(): set = _set_action_store @@ -298,9 +297,16 @@ func _on_insert_id_pressed(type: ParleyDialogueSequenceAst.Type) -> void: push_warning(ParleyUtils.log.warn_msg("Unable to add Node of type %s to the Dialogue Sequence. Dialogue Sequence is not currently loaded into Parley. Please open one via the file menu in the top left-hand side." % ParleyDialogueSequenceAst.get_type_name(type))) return var node_position: Vector2 = (graph_view.scroll_offset + graph_view.size * 0.5) / graph_view.zoom - var create_node_operation: ParleyCreateNodeOperation = ParleyCreateNodeOperation.new(graph_view, type, node_position) - create_node_operation.do() - add_undo_operation(create_node_operation) + + if ParleySettings.get_setting(Constants.EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE, false): + var create_node_operation: ParleyCreateNodeOperation = ParleyCreateNodeOperation.new(graph_view, type, node_position) + create_node_operation.do() + add_undo_operation(create_node_operation) + else: + var ast_node: Variant = dialogue_ast.add_new_node(type, (graph_view.scroll_offset + graph_view.size * 0.5) / graph_view.zoom) + if ast_node: + await refresh() + func _on_save_pressed() -> void: @@ -563,15 +569,25 @@ func _on_graph_view_connection_to_empty(from_node_name: StringName, from_slot: i if not dialogue_ast: return # TODO: it may be better to create a helper for this calculation - var node_position: Vector2 = ((graph_view.scroll_offset + release_position) / graph_view.zoom) + Vector2(0, -90) - var connection_to_empty_operation: ParleyConnectionToEmptyOperation = ParleyConnectionToEmptyOperation.new( - graph_view, - ParleyDialogueSequenceAst.Type.DIALOGUE, - node_position, - from_node_name, - from_slot) - connection_to_empty_operation.do() - add_undo_operation(connection_to_empty_operation) + if ParleySettings.get_setting(Constants.EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE, false): + var node_position: Vector2 = ((graph_view.scroll_offset + release_position) / graph_view.zoom) + Vector2(0, -90) + var connection_to_empty_operation: ParleyConnectionToEmptyOperation = ParleyConnectionToEmptyOperation.new( + graph_view, + ParleyDialogueSequenceAst.Type.DIALOGUE, + node_position, + from_node_name, + from_slot) + connection_to_empty_operation.do() + add_undo_operation(connection_to_empty_operation) + else: + var ast_node_variant: Variant = dialogue_ast.add_new_node(ParleyDialogueSequenceAst.Type.DIALOGUE, ((graph_view.scroll_offset + release_position) / graph_view.zoom) + Vector2(0, -90)) + if ast_node_variant and ast_node_variant is ParleyNodeAst: + var ast_node: ParleyNodeAst = ast_node_variant + await refresh() + var to_node_name: String = graph_view.get_ast_node_name(ast_node) + # TODO: This is the entry slot for a Dialogue AST Node, it may be better to create a helper function for this + var to_slot: int = 0 + _add_edge(from_node_name, from_slot, to_node_name, to_slot) # TODO: add to docs diff --git a/addons/parley/models/dialogue_sequence_ast.gd b/addons/parley/models/dialogue_sequence_ast.gd index 11271f5..b66b637 100644 --- a/addons/parley/models/dialogue_sequence_ast.gd +++ b/addons/parley/models/dialogue_sequence_ast.gd @@ -58,7 +58,7 @@ func _set_title(new_title: String) -> void: #region BUILDING DIALOGUE ## Add a node to the list of nodes from an AST -func add_ast_node(node: Dictionary) -> void: +func add_ast_node(node: Dictionary) -> ParleyNodeAst: var type: Type = Type.get(node.get('type'), Type.UNKNOWN) var id_variant: Variant = node.get('id') var position: Vector2 = _parse_position_from_raw_node_ast(node) @@ -111,6 +111,7 @@ func add_ast_node(node: Dictionary) -> void: return ast_node.position = position nodes.push_back(ast_node) + return ast_node ## Add a new node to the list of nodes @@ -144,13 +145,18 @@ func add_new_node(type: Type, position: Vector2 = Vector2.ZERO) -> ParleyNodeAst _emit_dialogue_updated() return ast_node + +# Copy the input Node AST and add to the Dialogue Sequenece AST func copy_node_ast(ast_node: ParleyNodeAst) -> ParleyNodeAst: - print_rich(ParleyUtils.log.info_msg('Inserting new Node into the AST of type: %s' % [ast_node.type])) - var new_node_ast : ParleyNodeAst = ast_node.copy(_generate_node_id()) - nodes.push_back(new_node_ast) - _emit_dialogue_updated() + print_rich(ParleyUtils.log.info_msg('Copying Node with ID %s and Type %s' % [ast_node.id, ast_node.type])) + var new_node_dict: Dictionary = ast_node.to_dict() + new_node_dict.id = _generate_node_id() + var new_node_ast: ParleyNodeAst = add_ast_node(new_node_dict) + if new_node_ast: + _emit_dialogue_updated() return new_node_ast + func add_node_from_ast(node_ast: ParleyNodeAst) -> ParleyNodeAst: # Probably need to make sure the provided ID doesn't already exist for node : ParleyNodeAst in nodes: @@ -653,7 +659,10 @@ static func is_dialogue_options(p_nodes: Array[ParleyNodeAst]) -> bool: func _parse_position_from_raw_node_ast(node: Dictionary) -> Vector2: var default: Vector2 = Vector2.ZERO var raw_position: Variant = node.get('position', str(default)) - if not is_instance_of(raw_position, TYPE_STRING): + if is_instance_of(raw_position, TYPE_VECTOR2): + return raw_position + elif not is_instance_of(raw_position, TYPE_STRING): + print(raw_position) push_warning(ParleyUtils.log.warn_msg("Unable to parse position of node: %s. Defaulting to %s" % [node.get('id', 'unknown'), str(default)])) return default var position: String = raw_position diff --git a/addons/parley/models/node_ast.gd b/addons/parley/models/node_ast.gd index 0e21586..08290d5 100644 --- a/addons/parley/models/node_ast.gd +++ b/addons/parley/models/node_ast.gd @@ -58,9 +58,3 @@ func _to_string() -> String: static func get_colour() -> Color: return Color("#7a2167") #endregion - -func copy(_id: String) -> ParleyNodeAst: - var duplicated: ParleyNodeAst = self.duplicate() - duplicated.id = _id - return duplicated - diff --git a/addons/parley/settings.gd b/addons/parley/settings.gd index 4d505ca..52c4169 100644 --- a/addons/parley/settings.gd +++ b/addons/parley/settings.gd @@ -18,6 +18,7 @@ static var DEFAULT_SETTINGS: Dictionary = { # We can't preload this because of circular deps so let's # hardcode it for now but allow people to edit it in settings ParleyConstants.TEST_DIALOGUE_SEQUENCE_TEST_SCENE_PATH: "res://addons/parley/views/test_dialogue_sequence_scene.tscn", + ParleyConstants.EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE: false, } diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index 0d3e67a..e4e77ff 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -9,6 +9,7 @@ var fact_store: ParleyFactStore = ParleyFactStore.new(): set = _set_fact_store var character_store: ParleyCharacterStore = ParleyCharacterStore.new(): set = _set_character_store #region SETUP +const Constants = preload('../constants.gd') const dialogue_node_scene: PackedScene = preload("../components/dialogue/dialogue_node.tscn") const dialogue_option_node_scene: PackedScene = preload("../components/dialogue_option/dialogue_option_node.tscn") const action_node_scene: PackedScene = preload("../components/action/action_node.tscn") @@ -25,7 +26,7 @@ signal undo_request() signal redo_request() # Called when the node enters the scene tree for the first time. -func _ready() -> void: +func _ready() -> void: await clear() scroll_offset = Vector2(-50, -50) @@ -46,6 +47,7 @@ func generate(arrange: bool = false) -> void: func clear() -> void: + clear_connections() var children: Array[ParleyGraphNode] = [] for child: Node in get_children(): if child is ParleyGraphNode: @@ -368,6 +370,9 @@ var _node_selection_triggered: bool var moving_nodes: bool func _on_node_selected(node: Node) -> void: + if not ParleySettings.get_setting(Constants.EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE, false): + return + _node_selection_triggered = true var node_id: String = (node as ParleyGraphNode).id if _try_select_edge(ParleyGraphUtils.get_cursor_pos_at_graph_view(self), is_command_or_control_pressed()): @@ -380,17 +385,23 @@ func _on_node_selected(node: Node) -> void: func _on_node_deselected(node: Node) -> void: + if not ParleySettings.get_setting(Constants.EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE, false): + return # print("deselected callback ", selected_node_ids.size()) selected_node_ids.erase((node as ParleyGraphNode).id) func _on_edges_deselected() -> void: + if not ParleySettings.get_setting(Constants.EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE, false): + return while on_unselect.size() > 0: var callable: Callable = on_unselect.pop_front() callable.call() func _gui_input(event: InputEvent) -> void: + if not ParleySettings.get_setting(Constants.EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE, false): + return if event is InputEventMouseButton: _handle_mouse_select(event as InputEventMouseButton) diff --git a/project.godot b/project.godot index a1c5e76..021fc79 100644 --- a/project.godot +++ b/project.godot @@ -58,3 +58,4 @@ locale/locale_filter_mode=1 stores/action_store_path="uid://bfwohwb0ftpuq" stores/fact_store_path="uid://c3c7k7lljjqv3" stores/character_store_path="uid://ceouii84qmu0w" +editor/keyboard_shortcuts=true From 5927f3c282d1496cb7ed994fc24522d377126f4a Mon Sep 17 00:00:00 2001 From: JosephStar318 <44904505+JosephStar318@users.noreply.github.com> Date: Sat, 23 May 2026 11:04:20 +0300 Subject: [PATCH 21/24] Update addons/parley/models/node_ast.gd #endregion added back. Co-authored-by: Jonny Green --- addons/parley/models/node_ast.gd | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/parley/models/node_ast.gd b/addons/parley/models/node_ast.gd index 08290d5..ecaf092 100644 --- a/addons/parley/models/node_ast.gd +++ b/addons/parley/models/node_ast.gd @@ -40,6 +40,7 @@ func _set_id(new_id: String) -> void: id = new_id elif not id or id == id_prefix or not id.begins_with(id_prefix): id = new_id if new_id.begins_with(id_prefix) else "%s%s" % [id_prefix, new_id] +#endregion #region UTILS ## Convert this resource into a Dictionary for storage From 54f66b313aa0a8bee0f689938738f028c8db8e9d Mon Sep 17 00:00:00 2001 From: JosephStar318 <44904505+JosephStar318@users.noreply.github.com> Date: Sat, 23 May 2026 11:06:04 +0300 Subject: [PATCH 22/24] Update addons/parley/views/parley_graph_view.gd Co-authored-by: Jonny Green --- addons/parley/views/parley_graph_view.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index e4e77ff..5896aad 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -26,7 +26,7 @@ signal undo_request() signal redo_request() # Called when the node enters the scene tree for the first time. -func _ready() -> void: +func _ready() -> void: await clear() scroll_offset = Vector2(-50, -50) From 377b7c8d420e1346ca782537320ff0f38e918d61 Mon Sep 17 00:00:00 2001 From: Joseph Star <> Date: Sat, 23 May 2026 11:10:04 +0300 Subject: [PATCH 23/24] -removed some logs and refresh edges will only be active if shortcuts enabled --- addons/parley/models/dialogue_sequence_ast.gd | 2 -- addons/parley/views/graph_connection.gd.uid | 1 - addons/parley/views/parley_graph_view.gd | 6 ++---- 3 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 addons/parley/views/graph_connection.gd.uid diff --git a/addons/parley/models/dialogue_sequence_ast.gd b/addons/parley/models/dialogue_sequence_ast.gd index b66b637..3a2395f 100644 --- a/addons/parley/models/dialogue_sequence_ast.gd +++ b/addons/parley/models/dialogue_sequence_ast.gd @@ -270,7 +270,6 @@ func remove_node(node_id: String) -> void: func find_node_by_id(id: String) -> ParleyNodeAst: var filtered_nodes: Array = nodes.filter(func(node: ParleyNodeAst) -> bool: return str(node.id) == str(id)) if filtered_nodes.size() != 1: - print("filtered node count",filtered_nodes.size()) print_rich(ParleyUtils.log.info_msg("No AST Node found with ID: {id}".format({'id': id}))) return null return filtered_nodes.front() @@ -662,7 +661,6 @@ func _parse_position_from_raw_node_ast(node: Dictionary) -> Vector2: if is_instance_of(raw_position, TYPE_VECTOR2): return raw_position elif not is_instance_of(raw_position, TYPE_STRING): - print(raw_position) push_warning(ParleyUtils.log.warn_msg("Unable to parse position of node: %s. Defaulting to %s" % [node.get('id', 'unknown'), str(default)])) return default var position: String = raw_position diff --git a/addons/parley/views/graph_connection.gd.uid b/addons/parley/views/graph_connection.gd.uid deleted file mode 100644 index b0d0442..0000000 --- a/addons/parley/views/graph_connection.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bb4mruigkoqs5 diff --git a/addons/parley/views/parley_graph_view.gd b/addons/parley/views/parley_graph_view.gd index e4e77ff..44d64d1 100644 --- a/addons/parley/views/parley_graph_view.gd +++ b/addons/parley/views/parley_graph_view.gd @@ -43,7 +43,8 @@ func generate(arrange: bool = false) -> void: if arrange: arrange_nodes() - call_deferred("refresh_edges") + if ParleySettings.get_setting(Constants.EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE, false): + call_deferred("refresh_edges") func clear() -> void: @@ -319,7 +320,6 @@ func _create_group_node(ast_node: ParleyGroupNodeAst, _should_regenerate: bool = # EXPERIMENTAL: see how feedback goes. This is certainly a candidate to be put into settings ParleyUtils.signals.safe_connect(node.dragged, func(_from: Vector2, _to: Vector2) -> void: var _nodes: Array[ParleyGraphNode] = _update_nodes_covered_by_group_node(node, ast_node) - print("dragged") ) return node @@ -381,13 +381,11 @@ func _on_node_selected(node: Node) -> void: unselected_node.call_deferred("set_selected", false) return selected_node_ids.append(node_id) - # print("selected callback ", node_id, " ", selected_node_ids.size()) func _on_node_deselected(node: Node) -> void: if not ParleySettings.get_setting(Constants.EDITOR_IS_KEYBOARD_SHORTCUTS_ACTIVE, false): return - # print("deselected callback ", selected_node_ids.size()) selected_node_ids.erase((node as ParleyGraphNode).id) From 5d7af23ae827bff4dd42d7ccfaf71344444c3d2a Mon Sep 17 00:00:00 2001 From: JosephStar318 <44904505+JosephStar318@users.noreply.github.com> Date: Sat, 23 May 2026 11:10:59 +0300 Subject: [PATCH 24/24] Update addons/parley/main_panel.gd Co-authored-by: Jonny Green --- addons/parley/main_panel.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/parley/main_panel.gd b/addons/parley/main_panel.gd index d84e36e..ec3ddd8 100644 --- a/addons/parley/main_panel.gd +++ b/addons/parley/main_panel.gd @@ -49,7 +49,7 @@ var parley_manager: ParleyManager # TODO: remove this var selected_node_id: Variant var selected_node_ast: ParleyNodeAst: set = _set_selected_node_ast -var copied_node_ids: Array[String] +var copied_node_ids: Array[String] = [] signal dialogue_ast_selected(dialogue_ast: ParleyDialogueSequenceAst) signal node_selected(node_ast: ParleyNodeAst)