diff --git a/classes/deck/node_db.gd b/classes/deck/node_db.gd index d6d5314..93cd551 100644 --- a/classes/deck/node_db.gd +++ b/classes/deck/node_db.gd @@ -1,15 +1,25 @@ extends Node +class_name NodeDB_ +## Filepath used for referencing all [DeckNode] Files from. const BASE_NODE_PATH := "res://classes/deck/nodes/" +## Filepath where an Index of [NodeDB_.NodeDescriptor]s are saved to avoid reloading +## everything each run. +## @experimental const NODE_INDEX_CACHE_PATH := "user://nodes_index.json" +## Filepath where the the list of Favorite Nodes is stored. const FAVORITE_NODES_PATH := "user://favorite_nodes.json" +## [Array] used for storing all the "Favorite" Nodes that were set in the [AddNodeMenu] var favorite_nodes: Array[String] # Dictionary[node_type, NodeDescriptor] +## [Dictionary] filled with node_type, or [String] keys. Correlating to +## [NodeDB_.NodeDescriptor]'s. Essentially where all the nodes are loaded in for data reference. var nodes: Dictionary = {} - +## Loads in all the [DeckNode]s from [member BASE_NODE_PATH]. Or if a working +## cache exists at [member NODE_INDEX_CACHE_PATH] that takes priority func _init() -> void: load_favorites() #if load_node_index(): @@ -41,14 +51,14 @@ func _init() -> void: save_node_index() - +## Helper Function that instances a [DeckNode] based off of it's [member DeckNode.node_type] func instance_node(type: String) -> DeckNode: if !nodes.has(type): return null return load(nodes[type]["script_path"]).new() - +## Handles Saving the Index of all loaded nodes to [member NODE_INDEX_CACHE_PATH] func save_node_index() -> void: var d := {} for node_type in nodes: @@ -59,7 +69,8 @@ func save_node_index() -> void: var f := FileAccess.open(NODE_INDEX_CACHE_PATH, FileAccess.WRITE) f.store_string(json) - +## Loads the Node Index from [member NODE_INDEX_CACHE_PATH] adding all of the +## [NodeDB_.NodeDescriptor]s in it to the [member nodes] [Dictionary] func load_node_index() -> bool: var f := FileAccess.open(NODE_INDEX_CACHE_PATH, FileAccess.READ) if f == null: @@ -79,7 +90,8 @@ func load_node_index() -> bool: print("node index file exists, loaded") return true - +## Sets a specific [member DeckNode.node_type] to be a "favorite" for use in +## [AddNodeMenu]. Then stores the updated list of favorites at [member FAVORITE_NODES_PATH] func set_node_favorite(node_type: String, favorite: bool) -> void: if (favorite && node_type in favorite_nodes) || (!favorite && !(node_type in favorite_nodes)): return @@ -92,7 +104,7 @@ func set_node_favorite(node_type: String, favorite: bool) -> void: var f := FileAccess.open(FAVORITE_NODES_PATH, FileAccess.WRITE) f.store_string(JSON.stringify(favorite_nodes, "\t")) - +## Loads the list of Favorite [memeber DeckNode.node_type]s from [member FAVORITE_NODES_PATH] func load_favorites() -> void: var f := FileAccess.open(FAVORITE_NODES_PATH, FileAccess.READ) if !f: @@ -101,22 +113,32 @@ func load_favorites() -> void: favorite_nodes.clear() favorite_nodes.assign(data) - +## Returns [code]true[/code] if the specified [member DeckNode.node_type] is marked Favorite +## by the user. func is_node_favorite(node_type: String) -> bool: return node_type in favorite_nodes - +## Used for storing the shorthand data of a [DeckNode]. +## +## Allows for more simply storing [DeckNode]s properties without needing to +## keep an instance class NodeDescriptor: + ## Default Name of the [DeckNode] type this is storing properties of. var name: String + ## The [member DeckNode.node_type] of the [DeckNode] this is based off of. var type: String + ## The description of the [DeckNode] reference [member DeckNode.description] var description: String + ## The description of the [DeckNode] reference [member DeckNode.aliases] var aliases: String + ## The description of the [DeckNode] reference [member DeckNode.category] var category: String + ## The description of the [DeckNode] reference [member DeckNode.appears_in_search] var appears_in_search: bool - + + ## Stores the path to this nodes [Script] in res:// for later instantiation. var script_path: String - - + func _init( p_script_path: String, p_name: String, @@ -135,7 +157,7 @@ class NodeDescriptor: category = p_category appears_in_search = p_appears_in_search - + ## Converts all the properties in this [NodeDB_.NodeDescriptor] to a [Dictionary]. Format - {propery : value} func to_dictionary() -> Dictionary: var d := { "name": name, @@ -148,7 +170,7 @@ class NodeDescriptor: } return d - + ## Creates a [NodeDB_.NodeDescriptor] from a given [Dictionary] of properties. static func from_dictionary(data: Dictionary) -> NodeDescriptor: var nd := NodeDescriptor.new( data.get("script_path", ""), diff --git a/graph_node_renderer/deck_holder_renderer.gd b/graph_node_renderer/deck_holder_renderer.gd index 836c5d5..f132aa4 100644 --- a/graph_node_renderer/deck_holder_renderer.gd +++ b/graph_node_renderer/deck_holder_renderer.gd @@ -1,10 +1,19 @@ extends Control +class_name DeckHolderRenderer +## Renderer class for [DeckHolder] +## +## Entry point for the [GraphEdit] based Renderer + +## Reference to the base scene for [DeckRendererGraphEdit] const DECK_SCENE := preload("res://graph_node_renderer/deck_renderer_graph_edit.tscn") +## Reference to the main windows [TabContainerCustom] @onready var tab_container: TabContainerCustom = %TabContainerCustom as TabContainerCustom +## Reference to the [FileDialog] used for File operations through the program. @onready var file_dialog: FileDialog = $FileDialog +## Enum for storing the Options in the "File" PopupMenu. enum FileMenuId { NEW, OPEN, @@ -13,6 +22,7 @@ enum FileMenuId { CLOSE = 6, } +## Weak Reference to the Deck that is currently going to be saved. var _deck_to_save: WeakRef @@ -27,7 +37,8 @@ func _ready() -> void: file_dialog.canceled.connect(disconnect_file_dialog_signals) - +## Called when the File button in the [MenuBar] is pressed with the [param id] +## of the button within it that was pressed. func _on_file_id_pressed(id: int) -> void: match id: FileMenuId.NEW: @@ -41,7 +52,7 @@ func _on_file_id_pressed(id: int) -> void: FileMenuId.CLOSE: close_current_tab() - +## Adds an empty [DeckRendererGraphEdit] with a corresponding [Deck] for it's data. func add_empty_deck() -> void: var deck := DeckHolder.add_empty_deck() var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate() @@ -50,11 +61,12 @@ func add_empty_deck() -> void: tab_container.set_tab_metadata(tab_container.get_current_tab(), deck) inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested.bind(deck)) - +## Closes the current tab in [member tab_container] func close_current_tab() -> void: tab_container.close_tab(tab_container.get_current_tab()) - +## Opens [member file_dialog] with the mode [member FileDialog.FILE_MODE_SAVE_FILE] +## as well as getting a weakref to the active [Deck] func open_save_dialog(path: String) -> void: file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE file_dialog.title = "Save a Deck" @@ -63,7 +75,8 @@ func open_save_dialog(path: String) -> void: file_dialog.popup_centered() file_dialog.file_selected.connect(_on_file_dialog_save_file, CONNECT_ONE_SHOT) - +## Opens [member file_dialog] with the mode [FileDialog.FILE_MODE_OPEN_FILES] +## with the supplied [param path] func open_open_dialog(path: String) -> void: file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILES file_dialog.title = "Open Deck(s)" @@ -71,7 +84,8 @@ func open_open_dialog(path: String) -> void: file_dialog.popup_centered() file_dialog.files_selected.connect(_on_file_dialog_open_files, CONNECT_ONE_SHOT) - +## Connected to [signal FileDialog.save_file] on [member file_dialog]. +## Saves the selected [Deck] if it still exists. func _on_file_dialog_save_file(path: String) -> void: var deck: Deck = _deck_to_save.get_ref() as Deck if !deck: @@ -82,7 +96,8 @@ func _on_file_dialog_save_file(path: String) -> void: var f := FileAccess.open(path, FileAccess.WRITE) f.store_string(json) - +## Connected to [signal FileDialog.open_files] on [member file_dialog]. Opens +## the selected paths, instantiating [DeckRenderGraphEdit]s and [Deck]s for each. func _on_file_dialog_open_files(paths: PackedStringArray) -> void: for path in paths: var deck := DeckHolder.open_deck_from_file(path) @@ -92,14 +107,14 @@ func _on_file_dialog_open_files(paths: PackedStringArray) -> void: inst.initialize_from_deck() inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested.bind(deck)) - +## Gets the currently active [Deck] from [member tab_container] func get_active_deck() -> Deck: if tab_container.is_empty(): return null return (tab_container.get_content(tab_container.get_current_tab()) as DeckRendererGraphEdit).deck - +## Saves the active [Deck] in [member tab_container] func save_active_deck() -> void: if get_active_deck().save_path.is_empty(): open_save_dialog("res://") @@ -108,7 +123,7 @@ func save_active_deck() -> void: var f := FileAccess.open(get_active_deck().save_path, FileAccess.WRITE) f.store_string(json) - +## Disconnects the [FileDialog] signals if they are already connected. func disconnect_file_dialog_signals() -> void: if file_dialog.file_selected.is_connected(_on_file_dialog_save_file): file_dialog.file_selected.disconnect(_on_file_dialog_save_file) @@ -116,7 +131,9 @@ func disconnect_file_dialog_signals() -> void: if file_dialog.files_selected.is_connected(_on_file_dialog_open_files): file_dialog.files_selected.disconnect(_on_file_dialog_open_files) - +## Connected to [signal DeckRenderGraphEdit.group_entered_request] to allow entering +## groups based off the given [param group_id] and [param deck]. As well as adding +## a corresponding tab to [member tab_container] func _on_deck_renderer_group_enter_requested(group_id: String, deck: Deck) -> void: var group_deck := deck.get_group(group_id) var deck_renderer: DeckRendererGraphEdit = DECK_SCENE.instantiate() diff --git a/graph_node_renderer/deck_node_renderer_graph_node.gd b/graph_node_renderer/deck_node_renderer_graph_node.gd index 510572a..b1700f7 100644 --- a/graph_node_renderer/deck_node_renderer_graph_node.gd +++ b/graph_node_renderer/deck_node_renderer_graph_node.gd @@ -1,9 +1,14 @@ extends GraphNode class_name DeckNodeRendererGraphNode +## [GraphNode] based renderer of [DeckNode] + +## Stores the data container [DeckNode] that the visuals of this [DeckNodeRendererGraphNode] +## are based off. var node: DeckNode - +## Setups up all the properties based off [member node]. Including looping through +## [method DeckNode.get_all_ports()] and setting up all the descriptors. func _ready() -> void: title = node.name node.position_updated.connect(_on_node_position_updated) @@ -50,17 +55,21 @@ func _ready() -> void: Color.WHITE, ) - +## Connected to [signal GraphElement.position_offset_updated] and updates the +## [member node]s properties func _on_position_offset_changed() -> void: node.position.x = position_offset.x node.position.y = position_offset.y - +## Connected to [member node]s [signal position_updated] to keep parity with the +## data position. func _on_node_position_updated(new_position: Dictionary) -> void: position_offset.x = new_position.x position_offset.y = new_position.y - +## Connected to [member node]s [signal port_added] handles setting up the specified +## [member Port.descriptor] with it's required nodes/signals etc. + adding the port +## using [method GraphNode.set_slot] func _on_node_port_added(port_idx: int) -> void: var port := node.get_all_ports()[port_idx] @@ -103,7 +112,8 @@ func _on_node_port_added(port_idx: int) -> void: Color.WHITE, ) - +## Connected to [member node]s [signal port_removed], queue_frees the [Node] +## used for the descriptor, as well as using [method GraphNode.set_slot] to remove the slot. func _on_node_port_removed(port_idx: int) -> void: set_slot( port_idx, @@ -117,7 +127,8 @@ func _on_node_port_removed(port_idx: int) -> void: get_child(port_idx).queue_free() - +## Connected to [member node]s [signal ports_updated]. Remakes all of the ports +## + their descriptors whenever this is received to allow keeping the Renderers ports up to date. func _on_node_ports_updated() -> void: clear_all_slots() for c in get_children(): diff --git a/graph_node_renderer/deck_renderer_graph_edit.gd b/graph_node_renderer/deck_renderer_graph_edit.gd index af5d733..bb7ddfd 100644 --- a/graph_node_renderer/deck_renderer_graph_edit.gd +++ b/graph_node_renderer/deck_renderer_graph_edit.gd @@ -1,29 +1,40 @@ 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 -@export var add_node_popup_size: Vector2i = Vector2i(500, 300) +## 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 = add_node_popup_size + search_popup_panel.size = search_popup_size add_child(search_popup_panel, false, Node.INTERNAL_MODE_BACK) add_node_menu.node_selected.connect(_on_add_node_menu_node_selected) @@ -31,7 +42,8 @@ func _ready() -> void: 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)) @@ -47,7 +59,8 @@ func attempt_connection(from_node_name: StringName, from_port: int, to_node_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)) @@ -64,7 +77,8 @@ func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_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().slice(1): if i.node == node: @@ -72,11 +86,11 @@ func get_node_renderer(node: DeckNode) -> DeckNodeRendererGraphNode: 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: # TODO: wait for https://github.com/godotengine/godot/issues/85005 to get merged # until it is, all calls to GraphEdit#get_children will need to slice off the first element @@ -100,7 +114,8 @@ func initialize_from_deck() -> void: 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] @@ -131,14 +146,16 @@ func refresh_connections() -> void: 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().slice(1): if renderer.node != node: @@ -147,14 +164,16 @@ func _on_deck_node_removed(node: DeckNode) -> void: 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().slice(1).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("?") @@ -167,7 +186,8 @@ func _gui_input(event: InputEvent) -> void: 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 @@ -179,7 +199,7 @@ func _input(event: InputEvent) -> void: 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) @@ -188,7 +208,9 @@ func _on_popup_request(p_popup_position: Vector2) -> void: 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) diff --git a/graph_node_renderer/tab_container_custom.gd b/graph_node_renderer/tab_container_custom.gd index 35c9e4a..f77f44e 100644 --- a/graph_node_renderer/tab_container_custom.gd +++ b/graph_node_renderer/tab_container_custom.gd @@ -1,17 +1,32 @@ extends VBoxContainer class_name TabContainerCustom +## Custom Recreation of [TabContainer] for Flexibility +## +## Allows for more customizability within the [TabBar] thats used mainly. Extra buttons etc. + +## Reference to the [TabBar] at the top of the Container. @onready var tab_bar: TabBar = %TabBar +## Reference to the [Button] at the end of the [TabBar] that's +## used for adding new Tabs @onready var add_tab_button: Button = %Button +## Reference to the [MarginContainer] around the Tabs Contents. @onready var content_container: MarginContainer = %ContentContainer - +## Emitted when the add [Button] within [member tab_bar] is pressed signal add_button_pressed +## Emitted when the current tab in [member tab_bar] is changed. signal tab_changed(tab: int) +## Emitted when a tab in [member tab_bar] has been closed. +## See [signal TabBar.tab_close_requested] signal tab_closed(tab: int) +## Emitted when a request to close a tab in the [member tab_bar] has been +## requested using [signal TabBar.tab_close_pressed] signal tab_close_requested(tab: int) +## Emitted when the order of the tabs in [member tab_bar] has been changed. signal tab_rearranged(old: int, new: int) +## Holds the previously active tab in [member tab_bar] var _previous_active_tab: int = -1 @@ -43,21 +58,21 @@ func _ready() -> void: _previous_active_tab = idx_to ) - +## Adds the given [Node] as "content" for the given tabs name as a [String] func add_content(c: Node, tab_title: String) -> void: tab_bar.add_tab(tab_title) content_container.add_child(c) tab_bar.set_current_tab(tab_bar.tab_count - 1) - +## Returns the count of tabs in [member tab_bar] func get_tab_count() -> int: return tab_bar.tab_count - +## Returns [code]true[/code] if [method get_tab_count] returns 0. func is_empty() -> bool: return get_tab_count() == 0 - +## Closes the tab that is at the given [param tab] in [member tab_bar] func close_tab(tab: int) -> void: content_container.get_child(tab).queue_free() if !tab_bar.select_previous_available(): @@ -67,18 +82,20 @@ func close_tab(tab: int) -> void: if tab_bar.tab_count == 0: _previous_active_tab = -1 - +## Returns the currently selected tab in [member tab_bar] func get_current_tab() -> int: return tab_bar.current_tab - +## Returns the child of [member content_container] at the given [param idx] func get_content(idx: int) -> Control: return content_container.get_child(idx) - +## Sets the metadata value for the tab at index [param tab_idx], which can be +## retrieved later using [method TabBar.get_tab_metadata()] func set_tab_metadata(tab: int, metadata: Variant) -> void: tab_bar.set_tab_metadata(tab, metadata) - +## Returns the metadata value set to the tab at index [param tab_idx] using set_tab_metadata(). +## If no metadata was previously set, returns null by default. func get_tab_metadata(tab: int) -> Variant: return tab_bar.get_tab_metadata(tab)