extends GraphEdit class_name DeckRendererGraphEdit ## Reference to the [DeckNodeRendererGraphNode] used for later instantiation const NODE_SCENE := preload("res://graph_node_renderer/deck_node_renderer_graph_node.tscn") ## Reference to the [AddNodeMenu] used for later instantiation const ADD_NODE_MENU_SCENE := preload("res://graph_node_renderer/add_node_menu.tscn") ## The [PopupPanel] that holds the [AddNodeMenu] scene. var search_popup_panel: PopupPanel ## Stores instance of [AddNodeMenu] that is used under [member search_popup_panel] var add_node_menu: AddNodeMenu ## Used to specify the size of [member search_popup_panel]. @export var search_popup_size: Vector2i = Vector2i(500, 300) ## Stores the position of the [member search_popup_panel] for use when adding ## nodes in [method _on_add_node_menu_node_selected] var popup_position: Vector2 ## References the [Deck] that holds all the functional properties of this [DeckRendererGraphEdit] var deck: Deck: set(v): deck = v deck.node_added.connect(_on_deck_node_added) deck.node_removed.connect(_on_deck_node_removed) ## Emits when Group creation is requested. Ex. Hitting the "group_nodes" Hotkey. signal group_enter_requested(group_id: String) ## Sets up the [member search_popup_panel] with an instance of [member ADD_NODE_SCENE] ## stored in [member add_node_menu]. And sets its size of [member search_popup_panel] to ## [member add_node_popup_size] func _ready() -> void: add_node_menu = ADD_NODE_MENU_SCENE.instantiate() search_popup_panel = PopupPanel.new() search_popup_panel.add_child(add_node_menu) search_popup_panel.size = search_popup_size add_child(search_popup_panel, false, Node.INTERNAL_MODE_BACK) for t: DeckType.Types in DeckType.CONVERSION_MAP: for out_type: DeckType.Types in DeckType.CONVERSION_MAP[t]: add_valid_connection_type(t, out_type) add_node_menu.node_selected.connect(_on_add_node_menu_node_selected) connection_request.connect(attempt_connection) disconnection_request.connect(attempt_disconnect) ## Receives [signal GraphEdit.connection_request] and attempts to create a ## connection between the two [DeckNode]s involved, utilizes [NodeDB] for accessing them. func attempt_connection(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void: var from_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(from_node_name)) var to_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(to_node_name)) #var from_output := from_node_renderer.node.get_global_port_idx_from_output(from_port) #var to_input := to_node_renderer.node.get_global_port_idx_from_input(to_port) if deck.connect_nodes(from_node_renderer.node, to_node_renderer.node, from_port, to_port): connect_node( from_node_renderer.name, from_port, to_node_renderer.name, to_port ) ## Receives [signal GraphEdit.disconnection_request] and attempts to disconnect the two [DeckNode]s ## involved, utilizes [NodeDB] for accessing them. func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void: var from_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(from_node_name)) var to_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(to_node_name)) #var from_output := from_node_renderer.node.get_global_port_idx_from_output(from_port) #var to_input := to_node_renderer.node.get_global_port_idx_from_input(to_port) deck.disconnect_nodes(from_node_renderer.node, to_node_renderer.node, from_port, to_port) disconnect_node( from_node_renderer.name, from_port, to_node_renderer.name, to_port ) ## Returns the associated [DeckNodeRendererGraphNode] for the supplied [DeckNode]. ## Or [code]null[/code] if none is found. func get_node_renderer(node: DeckNode) -> DeckNodeRendererGraphNode: for i: DeckNodeRendererGraphNode in get_children(): if i.node == node: return i return null ## Updates [member Deck]s meta property "offset" whenever [member GraphEdit.scroll_offset] func _on_scroll_offset_changed(offset: Vector2) -> void: deck.set_meta("offset", offset) ## Setups all the data from the set [member deck] in this [DeckRendererGraphEdit] func initialize_from_deck() -> void: for i in get_children(): i.queue_free() scroll_offset = deck.get_meta("offset", Vector2()) for node_id in deck.nodes: var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() node_renderer.node = deck.nodes[node_id] add_child(node_renderer) node_renderer.position_offset = node_renderer.node.position_as_vector2() for node_id in deck.nodes: var node: DeckNode = deck.nodes[node_id] var from_node = get_children().filter( func(c: DeckNodeRendererGraphNode): return c.node._id == node_id )[0] refresh_connections() ## Loops through all [DeckNode]s in [member Deck.nodes] and calls ## [method GraphEdit.connect_node] for all the connections that exist in each func refresh_connections() -> void: for node_id in deck.nodes: var node: DeckNode = deck.nodes[node_id] var from_node: DeckNodeRendererGraphNode = get_children().filter( func(c: DeckNodeRendererGraphNode): return c.node._id == node_id )[0] for from_port in node.outgoing_connections: for connection in node.outgoing_connections[from_port]: var to_node_id = connection.keys()[0] var to_node_port = connection.values()[0] var to_node: DeckNodeRendererGraphNode = get_children().filter( func(c: DeckNodeRendererGraphNode): return c.node._id == to_node_id )[0] #print("***") #print("calling connect_node with:") #print(from_node.node.name) #print(from_node.node.get_port_type_idx_from_global(from_port)) #print(to_node.node.name) #print(to_node.node.get_port_type_idx_from_global(to_node_port)) #print("***") connect_node( from_node.name, from_port, to_node.name, to_node_port ) ## Connected to [signal Deck.node_added], used to instance the required ## [DeckNodeRendererGraphNode] and set it's [member DeckNodeRenderGraphNode.position_offset] func _on_deck_node_added(node: DeckNode) -> void: var inst: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() inst.node = node add_child(inst) inst.position_offset = inst.node.position_as_vector2() ## Connected to [signal Deck.node_added], used to remove the specified ## [DeckNodeRendererGraphNode] and queue_free it. func _on_deck_node_removed(node: DeckNode) -> void: for renderer: DeckNodeRendererGraphNode in get_children(): if renderer.node != node: continue renderer.queue_free() break ## Utility function that gets all [DeckNodeRenderGraphNode]s that are selected ## See [member GraphNode.selected] func get_selected_nodes() -> Array: return get_children().filter( func(x: DeckNodeRendererGraphNode): return x.selected ) ## Executes functionality based off hotkey inputs. Specifically handles creating groups ## based off the action "group_nodes". func _gui_input(event: InputEvent) -> void: if event.is_action_pressed("group_nodes") && get_selected_nodes().size() > 0: print("?") clear_connections() var nodes = get_selected_nodes().map( func(x: DeckNodeRendererGraphNode): return x.node ) deck.group_nodes(nodes) refresh_connections() get_viewport().set_input_as_handled() ## Handles entering groups with action "enter_group". Done here to bypass neighbor ## functionality. func _input(event: InputEvent) -> void: if !has_focus(): return if event.is_action_pressed("enter_group") && get_selected_nodes().size() == 1: if !((get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.node_type == "group_node"): return print("tried to enter group") group_enter_requested.emit((get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.group_id) get_viewport().set_input_as_handled() ## Opens [member search_popup_panel] at the mouse position. Connected to [signal GraphEdit.popup_request] func _on_popup_request(p_popup_position: Vector2) -> void: var p := get_viewport_rect().position + get_global_mouse_position() p += Vector2(10, 10) var r := Rect2i(p, search_popup_panel.size) search_popup_panel.popup_on_parent(r) add_node_menu.focus_search_bar() popup_position = p_popup_position ## Connected to [signal AddNodeMenu.node_selected] and creates a [DeckNode] using ## [method NodeDB.instance_node]. Then placing it at the [member scroll_offset] + ## [member popup_position] / [member zoom] func _on_add_node_menu_node_selected(type: String) -> void: var node := NodeDB.instance_node(type) as DeckNode deck.add_node_inst(node) var node_pos := ((scroll_offset + popup_position) / zoom) if snapping_enabled: node_pos = node_pos.snapped(Vector2(snapping_distance, snapping_distance)) get_node_renderer(node).position_offset = node_pos search_popup_panel.hide()