diff --git a/classes/deck/deck_holder.gd b/classes/deck/deck_holder.gd index 07e981a..c5c23c9 100644 --- a/classes/deck/deck_holder.gd +++ b/classes/deck/deck_holder.gd @@ -26,8 +26,8 @@ static func add_empty_deck() -> Deck: var uuid := UUID.v4() decks[uuid] = deck deck.id = uuid - signals.deck_added.emit(uuid) deck.connect_rpc_signals() + signals.deck_added.emit(uuid) return deck @@ -44,8 +44,8 @@ static func open_deck_from_file(path: String) -> Deck: static func open_deck_from_dict(data: Dictionary, path := "") -> Deck: var deck := Deck.from_dict(data, path) decks[deck.id] = deck - signals.deck_added.emit(deck.id) deck.connect_rpc_signals() + signals.deck_added.emit(deck.id) return deck @@ -61,6 +61,7 @@ static func add_group_from_dict(data: Dictionary, deck_id: String, instance_id: connect_group_signals(group) if deck_id not in groups_emitted: + group.connect_rpc_signals() signals.deck_added.emit(deck_id) groups_emitted.append(deck_id) @@ -81,6 +82,7 @@ static func add_empty_group() -> Deck: decks[group.id] = {group.instance_id: group} connect_group_signals(group) if group.id not in groups_emitted: + group.connect_rpc_signals() signals.deck_added.emit(group.id) groups_emitted.append(group.id) return group diff --git a/graph_node_renderer/debug_decks_list.gd b/graph_node_renderer/debug_decks_list.gd index edb876f..3381f2b 100644 --- a/graph_node_renderer/debug_decks_list.gd +++ b/graph_node_renderer/debug_decks_list.gd @@ -18,7 +18,18 @@ func _ready() -> void: b.pressed.connect(func(): item_pressed.emit(deck_id, "") ) - add_child(b) + var cb := Button.new() + cb.text = "Copy" + cb.pressed.connect( + func(): + DisplayServer.clipboard_set(deck_id) + ) + + var hb := HBoxContainer.new() + hb.add_child(b) + hb.add_child(cb) + + add_child(hb) else: for instance_id: String in DeckHolder.decks[deck_id]: var b := Button.new() diff --git a/graph_node_renderer/debug_nodes_list.gd b/graph_node_renderer/debug_nodes_list.gd new file mode 100644 index 0000000..165e20c --- /dev/null +++ b/graph_node_renderer/debug_nodes_list.gd @@ -0,0 +1,18 @@ +# (c) 2023-present Eroax +# (c) 2023-present Yagich +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +extends VBoxContainer +class_name DebugNodesList + + +func build(deck: Deck) -> void: + get_children().map(func(e: Node): e.queue_free()) + for node_id in deck.nodes: + var b := Button.new() + var node = deck.get_node(node_id) + b.text = "%s (%s)" % [node.name, node._id.left(8)] + b.pressed.connect( + func(): + DisplayServer.clipboard_set(node_id) + ) + add_child(b) diff --git a/graph_node_renderer/debug_nodes_list.tscn b/graph_node_renderer/debug_nodes_list.tscn new file mode 100644 index 0000000..7e67bc9 --- /dev/null +++ b/graph_node_renderer/debug_nodes_list.tscn @@ -0,0 +1,8 @@ +[gd_scene load_steps=2 format=3 uid="uid://cddfyvpf5nqq7"] + +[ext_resource type="Script" path="res://graph_node_renderer/debug_nodes_list.gd" id="1_jciri"] + +[node name="DebugNodesList" type="VBoxContainer"] +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("1_jciri") diff --git a/graph_node_renderer/deck_holder_renderer.gd b/graph_node_renderer/deck_holder_renderer.gd index 05997f0..f2d5c95 100644 --- a/graph_node_renderer/deck_holder_renderer.gd +++ b/graph_node_renderer/deck_holder_renderer.gd @@ -11,6 +11,7 @@ class_name DeckHolderRenderer ## Reference to the base scene for [DeckRendererGraphEdit] @export var DECK_SCENE: PackedScene @export var DEBUG_DECKS_LIST: PackedScene +@export var DEBUG_NODES_LIST: PackedScene const PERSISTENCE_NAMESPACE := "default" @@ -48,6 +49,7 @@ enum ConnectionsMenuId { enum DebugMenuId { DECKS, + NODES, EMBED_SUBWINDOWS, } @onready var debug_popup_menu: PopupMenu = %Debug @@ -377,6 +379,15 @@ func _on_debug_id_pressed(id: int) -> void: debug_decks.item_pressed.connect(_on_debug_decks_viewer_item_pressed) add_child(d) d.popup_centered() + DebugMenuId.NODES: + var d := AcceptDialog.new() + var debug_nodes: DebugNodesList = DEBUG_NODES_LIST.instantiate() + d.add_child(debug_nodes) + d.canceled.connect(d.queue_free) + d.confirmed.connect(d.queue_free) + debug_nodes.build(get_active_deck()) + add_child(d) + d.popup_centered() DebugMenuId.EMBED_SUBWINDOWS: var c := debug_popup_menu.is_item_checked(id) debug_popup_menu.set_item_checked(id, not c) diff --git a/graph_node_renderer/deck_holder_renderer.tscn b/graph_node_renderer/deck_holder_renderer.tscn index f144eb7..7acde6f 100644 --- a/graph_node_renderer/deck_holder_renderer.tscn +++ b/graph_node_renderer/deck_holder_renderer.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=24 format=3 uid="uid://duaah5x0jhkn6"] +[gd_scene load_steps=25 format=3 uid="uid://duaah5x0jhkn6"] [ext_resource type="Script" path="res://graph_node_renderer/deck_holder_renderer.gd" id="1_67g2g"] [ext_resource type="PackedScene" uid="uid://b84f2ngtcm5b8" path="res://graph_node_renderer/tab_container_custom.tscn" id="1_s3ug2"] @@ -8,6 +8,7 @@ [ext_resource type="Script" path="res://addons/no-obs-ws/NoOBSWS.gd" id="4_nu72u"] [ext_resource type="PackedScene" uid="uid://dm7sc6364j84i" path="res://graph_node_renderer/debug_decks_list.tscn" id="4_ux0jt"] [ext_resource type="Script" path="res://addons/no_twitch/twitch_connection.gd" id="5_3n36q"] +[ext_resource type="PackedScene" uid="uid://cddfyvpf5nqq7" path="res://graph_node_renderer/debug_nodes_list.tscn" id="5_pnfg8"] [ext_resource type="PackedScene" uid="uid://eioso6jb42jy" path="res://graph_node_renderer/obs_websocket_setup_dialog.tscn" id="5_uo2gj"] [ext_resource type="PackedScene" uid="uid://bq2lxmbnic4lc" path="res://graph_node_renderer/twitch_setup_dialog.tscn" id="7_7rhap"] [ext_resource type="PackedScene" uid="uid://cuwou2aa7qfc2" path="res://graph_node_renderer/unsaved_changes_dialog_single_deck.tscn" id="8_qf6ve"] @@ -71,6 +72,7 @@ theme = ExtResource("1_tgul2") script = ExtResource("1_67g2g") DECK_SCENE = ExtResource("3_uf16c") DEBUG_DECKS_LIST = ExtResource("4_ux0jt") +DEBUG_NODES_LIST = ExtResource("5_pnfg8") new_deck_shortcut = SubResource("Shortcut_30rq6") open_deck_shortcut = SubResource("Shortcut_m48tj") save_deck_shortcut = SubResource("Shortcut_xr6s4") @@ -135,13 +137,15 @@ item_1/id = 1 [node name="Debug" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"] unique_name_in_owner = true -item_count = 2 +item_count = 3 item_0/text = "Decks..." item_0/id = 0 -item_1/text = "Embed subwindows" -item_1/checkable = 1 -item_1/checked = true +item_1/text = "Nodes..." item_1/id = 1 +item_2/text = "Embed subwindows" +item_2/checkable = 1 +item_2/checked = true +item_2/id = 2 [node name="Help" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"] unique_name_in_owner = true diff --git a/graph_node_renderer/deck_renderer_graph_edit.gd b/graph_node_renderer/deck_renderer_graph_edit.gd index 02df6a3..99d0103 100644 --- a/graph_node_renderer/deck_renderer_graph_edit.gd +++ b/graph_node_renderer/deck_renderer_graph_edit.gd @@ -188,7 +188,9 @@ func _on_deck_node_removed(node: DeckNode) -> void: for renderer: DeckNodeRendererGraphNode in get_children(): if renderer.node != node: continue - + # TODO: when multiple nodes are removed and they are connected, the renderer will break + # trying to get an invalid node. (GraphEdit, this is not on us.) + # consider a batch removed signal for Deck or a separate signal for grouping nodes in 0.0.6. renderer.queue_free() break dirty = true diff --git a/rpc_renderer/rpc_renderer.gd b/rpc_renderer/rpc_renderer.gd index d5f8151..5e40798 100644 --- a/rpc_renderer/rpc_renderer.gd +++ b/rpc_renderer/rpc_renderer.gd @@ -105,10 +105,19 @@ func _on_ws_message(peer_id: int, message: Variant) -> void: return var req := RPCRequest.from_dict(result.data, peer_id) - for scope in scopes: - if scope.can_handle_request(req): - scope.handle_request(req) - return + var scope_idx := -1 + for i in scopes.size(): + var scope := scopes[i] + if scope.name == req.scope: + scope_idx = i + break + + if scope_idx == -1: + return # TODO: error + + var scope := scopes[scope_idx] + if scope.can_handle_request(req): + scope.handle_request(req) func _on_ws_client_connected(peer_id: int) -> void: diff --git a/rpc_renderer/rpc_scope.gd b/rpc_renderer/rpc_scope.gd index 1d9cf9b..16d5717 100644 --- a/rpc_renderer/rpc_scope.gd +++ b/rpc_renderer/rpc_scope.gd @@ -79,6 +79,20 @@ func create_generic_success(request: RPCRequest) -> RPCRequestResponse: return r +func create_generic_failure(request: RPCRequest) -> RPCRequestResponse: + var r := RPCRequestResponse.new(request) + r.for_request = request.id + r.scope = name + r.kept = request.keep + r.peer_id = request.peer_id + + r.data = { + "success": false + } + + return r + + func create_event(type: String, data: Variant, condition := {}) -> RPCEvent: return RPCEvent.new(type, name, data, condition) diff --git a/rpc_renderer/scopes/scope_deck.gd b/rpc_renderer/scopes/scope_deck.gd index 3049009..95e57b2 100644 --- a/rpc_renderer/scopes/scope_deck.gd +++ b/rpc_renderer/scopes/scope_deck.gd @@ -9,6 +9,21 @@ func _init() -> void: operation_types = { "add_node": add_node, + "remove_node": remove_node, + "get_node": get_node, + + "is_valid_connection": is_valid_connection, + "connect_nodes": connect_nodes, + "disconnect_nodes": disconnect_nodes, + + "group_nodes": group_nodes, + + "set_variable": set_variable, + "get_variable": get_variable, + "get_variable_list": get_variable_list, + + "send_event": send_event, + "get_referenced_groups": get_referenced_groups, } RPCSignalLayer.signals.deck_node_added.connect(_on_deck_node_added) @@ -16,6 +31,7 @@ func _init() -> void: RPCSignalLayer.signals.deck_nodes_connected.connect(_on_deck_nodes_connected) RPCSignalLayer.signals.deck_nodes_disconnected.connect(_on_deck_nodes_disconnected) + RPCSignalLayer.signals.deck_variables_updated.connect(_on_deck_variables_updated) func add_node(r: RPCRequest) -> void: @@ -36,6 +52,174 @@ func add_node(r: RPCRequest) -> void: ) +func remove_node(r: RPCRequest) -> void: + reconnect( + RPCSignalLayer.signals.deck_node_removed, + _on_deck_node_removed, + func(): + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var node_id: String = r.operation.payload.node_id + + deck.remove_node(node_id, true) + + var node_partial := RPCNodePartial.new() + node_partial.deck_id = deck.id + node_partial.id = node_id + + var resp := create_generic_success(r) + resp.create_event_counterpart(node_partial) + response.emit(resp) + ) + + +func get_node(r: RPCRequest) -> void: + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var node := deck.get_node(r.operation.payload.node_id) + var node_data := node.to_dict() + + var resp := create_response(r, {"node": node_data}) + response.emit(resp) + + +func is_valid_connection(r: RPCRequest) -> void: + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var from_node_id: String = r.operation.payload.from_node_id + var from_output_port := int(r.operation.payload.from_output_port) + var to_node_id: String = r.operation.payload.to_node_id + var to_input_port := int(r.operation.payload.to_input_port) + + var is_valid = deck.is_valid_connection(from_node_id, to_node_id, from_output_port, to_input_port) + + var resp := create_response(r, {"valid": is_valid}) + response.emit(resp) + + +func connect_nodes(r: RPCRequest) -> void: + reconnect( + RPCSignalLayer.signals.deck_nodes_connected, + _on_deck_nodes_connected, + func(): + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var from_node_id: String = r.operation.payload.from_node_id + var from_output_port := int(r.operation.payload.from_output_port) + var to_node_id: String = r.operation.payload.to_node_id + var to_input_port := int(r.operation.payload.to_input_port) + + var connected := deck.connect_nodes(from_node_id, to_node_id, from_output_port, to_input_port) + if connected: + var connection := RPCNodeConnection.new() + connection.from_node_id = from_node_id + connection.to_node_id = to_node_id + connection.from_output_port = from_output_port + connection.to_input_port = to_input_port + var resp := create_generic_success(r) + resp.create_event_counterpart(connection) + response.emit(resp) + else: + var err := create_generic_failure(r) + response.emit(err) + ) + + +func disconnect_nodes(r: RPCRequest) -> void: + reconnect( + RPCSignalLayer.signals.deck_nodes_disconnected, + _on_deck_nodes_disconnected, + func(): + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var from_node_id: String = r.operation.payload.from_node_id + var from_output_port := int(r.operation.payload.from_output_port) + var to_node_id: String = r.operation.payload.to_node_id + var to_input_port := int(r.operation.payload.to_input_port) + + deck.disconnect_nodes(from_node_id, to_node_id, from_output_port, to_input_port) + + var connection := RPCNodeConnection.new() + connection.from_node_id = from_node_id + connection.to_node_id = to_node_id + connection.from_output_port = from_output_port + connection.to_input_port = to_input_port + + var resp := create_generic_success(r) + resp.create_event_counterpart(connection) + response.emit(resp) + ) + + +func group_nodes(r: RPCRequest) -> void: + reconnect( + RPCSignalLayer.signals.deck_node_removed, + _on_deck_node_removed, + func(): + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var node_ids: Array = r.operation.payload.nodes + var nodes := node_ids.map( + func(e: String): + return deck.get_node(e) + ) + var group := deck.group_nodes(nodes) + if group == null: + var err := create_error(r, "Error grouping") + response.emit(err) + else: + var dp := RPCDeckPartial.new(group) + var resp := create_response(r, dp) + resp.create_event_counterpart(node_ids) + response.emit(resp) + ) + + +func set_variable(r: RPCRequest) -> void: + reconnect( + RPCSignalLayer.signals.deck_variables_updated, + _on_deck_variables_updated, + func(): + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var var_name: String = r.operation.payload.name + var var_value = r.operation.payload.value + + deck.set_variable(var_name, var_value) + var resp := create_generic_success(r) + resp.create_event_counterpart({}) + response.emit(resp) + ) + + +func get_variable(r: RPCRequest) -> void: + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var var_name: String = r.operation.payload.name + var value = deck.variable_stack.get(var_name) + + var resp := create_response(r, {"name": var_name, "value": value}) + response.emit(resp) + + +func get_variable_list(r: RPCRequest) -> void: + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + + var resp := create_response(r, {"variables": deck.variable_stack.keys()}) + response.emit(resp) + + +func send_event(r: RPCRequest) -> void: + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var event_name: StringName = r.operation.payload.event_name + var event_data: Dictionary = r.operation.payload.get("event_data", {}) + + deck.send_event(event_name, event_data) + + var resp := create_generic_success(r) + response.emit(resp) + + +func get_referenced_groups(r: RPCRequest) -> void: + var deck := DeckHolder.get_deck(r.operation.condition.deck_id) + var groups := deck.get_referenced_groups() + + var resp := create_response(r, {"groups": groups}) + response.emit(resp) + + func _on_deck_node_added(deck_id: String, node_id: String) -> void: event.emit(create_event("node_added", {"node_id": node_id}, {"deck_id": deck_id})) @@ -64,3 +248,8 @@ func _on_deck_nodes_disconnected(deck_id: String, from_node_id: String, to_node_ var ev := create_event("nodes_disconnected", connection, {"deck_id": deck_id}) event.emit(ev) + + +func _on_deck_variables_updated(deck_id: String) -> void: + var ev := create_event("variables_updated", {}, {"deck_id": deck_id}) + event.emit(ev) diff --git a/rpc_renderer/scopes/scope_deck_holder.gd b/rpc_renderer/scopes/scope_deck_holder.gd index ff2fba1..caf4de9 100644 --- a/rpc_renderer/scopes/scope_deck_holder.gd +++ b/rpc_renderer/scopes/scope_deck_holder.gd @@ -11,17 +11,12 @@ func _init() -> void: #"new_deck": new_deck, # TODO: evaluate later "get_deck": get_deck, "send_event": send_event, - "error": error, } RPCSignalLayer.signals.deck_added.connect(_on_deck_holder_deck_added) RPCSignalLayer.signals.deck_closed.connect(_on_deck_holder_deck_closed) -func error(r: RPCRequest) -> void: - response.emit(create_error(r, "random error")) - - func new_deck(r: RPCRequest) -> void: reconnect( RPCSignalLayer.signals.deck_added,