diff --git a/classes/deck/deck.gd b/classes/deck/deck.gd index 5097e6c..42e31cf 100644 --- a/classes/deck/deck.gd +++ b/classes/deck/deck.gd @@ -1,7 +1,13 @@ class_name Deck +## A deck/graph with nodes. +## +## A container for [DeckNode]s, managing connections between them. +## The [DeckNode]s that belong to this deck. The key is the node's id, the value +## is the [DeckNode] instance. var nodes: Dictionary +## The list of [DeckType] for easy port creation. enum Types{ ERROR = -1, BOOL, @@ -11,7 +17,7 @@ enum Types{ DICTIONARY, } - +## A dictionary mapping [enum Types] to [DeckType] subclasses. static var type_assoc: Dictionary = { Types.ERROR: DeckType.DeckTypeError, Types.BOOL: DeckType.DeckTypeBool, @@ -21,27 +27,47 @@ static var type_assoc: Dictionary = { Types.DICTIONARY: DeckType.DeckTypeDictionary, } +## A map of variables set on this deck. var variable_stack: Dictionary = {} +## The path to save this deck on the file system. var save_path: String = "" var is_group: bool = false -var groups: Dictionary = {} #Dictionary[String -> Deck.id, Deck] +## List of groups belonging to this deck, in the format of[br] +## [code]Dictionary[String -> Deck.id, Deck][/code] +var groups: Dictionary = {} +## A unique identifier for this deck. var id: String = "" +## The parent deck of this deck, if this is a group. var _belonging_to: Deck # for groups +## The ID of this group's input node. Used only if [member is_group] is [code]true[/code]. var group_input_node: String +## The ID of this group's input node. Used only if [member is_group] is [code]true[/code]. var group_output_node: String +## The ID of the group node this group is represented by, contained in this deck's parent deck. +## Used only if [member is_group] is [code]true[/code]. +## @experimental var group_node: String +## Emitted when a node has been added to this deck. signal node_added(node: DeckNode) +## Emitted when a node has been removed from this deck. signal node_removed(node: DeckNode) +## Instantiate a node by its' [member DeckNode.node_type] and add it to this deck.[br] +## See [method add_node_inst] for parameter descriptions. func add_node_type(type: String, assign_id: String = "", assign_to_self: bool = true) -> DeckNode: var node_inst: DeckNode = NodeDB.instance_node(type) return add_node_inst(node_inst, assign_id, assign_to_self) +## Add a [DeckNode] instance to this deck.[br] +## If [param assign_id] is empty, the node will get its' ID (re-)assigned. +## Otherwise, it will be assigned to be that value.[br] +## If [param assign_to_self] is [code]true[/code], the node's +## [member DeckNode._belonging_to] property will be set to [code]self[/code]. func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool = true) -> DeckNode: if assign_to_self: node._belonging_to = self @@ -58,10 +84,12 @@ func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool return node +## Get a node belonging to this deck by its' ID. func get_node(uuid: String) -> DeckNode: return nodes.get(uuid) +## Attempt to connect two nodes. Returns [code]true[/code] if the connection succeeded. func connect_nodes(from_node: DeckNode, to_node: DeckNode, from_output_port: int, to_input_port: int) -> bool: # first, check that we can do the type conversion. var type_a: Types = from_node.get_output_ports()[from_output_port].type @@ -77,6 +105,7 @@ func connect_nodes(from_node: DeckNode, to_node: DeckNode, from_output_port: int return true +## Remove a connection from two nodes. func disconnect_nodes(from_node: DeckNode, to_node: DeckNode, from_output_port: int, to_input_port: int) -> void: var hash = {to_node._id: to_input_port}.hash() @@ -84,10 +113,12 @@ func disconnect_nodes(from_node: DeckNode, to_node: DeckNode, from_output_port: to_node.remove_incoming_connection(to_input_port) +## Returns true if this deck has no nodes and no variables. func is_empty() -> bool: return nodes.is_empty() && variable_stack.is_empty() +## Remove a node from this deck. func remove_node(uuid: String) -> void: var node = nodes.get(uuid) nodes.erase(uuid) @@ -95,6 +126,9 @@ func remove_node(uuid: String) -> void: node_removed.emit(node) +## Group the [param nodes_to_group] into a new deck and return it. +## Returns [code]null[/code] on failure.[br] +## Adds a group node to this deck, and adds group input and output nodes in the group. func group_nodes(nodes_to_group: Array) -> Deck: if nodes_to_group.is_empty(): return null @@ -151,10 +185,12 @@ func group_nodes(nodes_to_group: Array) -> Deck: return group +## Get a group belonging to this deck by its ID. func get_group(uuid: String) -> Deck: return groups.get(uuid) +## Returns a [Dictionary] representation of this deck. func to_dict(with_meta: bool = true) -> Dictionary: var inner := { "nodes": {}, @@ -183,6 +219,7 @@ func to_dict(with_meta: bool = true) -> Dictionary: return d +## Create a new deck from a [Dictionary] representation, such as one created by [method to_dict]. static func from_dict(data: Dictionary, path: String = "") -> Deck: var deck := Deck.new() deck.save_path = path diff --git a/classes/deck/deck_holder.gd b/classes/deck/deck_holder.gd index 0cc160c..14879f9 100644 --- a/classes/deck/deck_holder.gd +++ b/classes/deck/deck_holder.gd @@ -1,9 +1,12 @@ class_name DeckHolder +## @experimental +## A static class holding references to all decks opened in the current session. - +## List of decks opened this session. static var decks: Array[Deck] +## Returns a new empty deck and assigns a new random ID to it. static func add_empty_deck() -> Deck: var deck := Deck.new() DeckHolder.decks.append(deck) @@ -12,6 +15,7 @@ static func add_empty_deck() -> Deck: return deck +## Opens a deck from the [param path]. static func open_deck_from_file(path: String) -> Deck: var f := FileAccess.open(path, FileAccess.READ) if f.get_error() != OK: @@ -23,5 +27,6 @@ static func open_deck_from_file(path: String) -> Deck: return deck +## Unloads a deck. static func close_deck(deck: Deck) -> void: DeckHolder.decks.erase(deck) diff --git a/classes/deck/deck_node.gd b/classes/deck/deck_node.gd index 8e64178..98df43e 100644 --- a/classes/deck/deck_node.gd +++ b/classes/deck/deck_node.gd @@ -1,25 +1,44 @@ class_name DeckNode +## A node in a [Deck]. +## +## Nodes are the essential building block of a [Deck] graph. They can have connections between ports +## and send data through them. +## The name initially shown to a renderer. var name: String -## [code]Dictionary[int -> output port, Array[Dictionary[String -> DeckNode#_id, int -> input port]]] +## A map of outgoing connections from this node, in the format[br] +## [code]Dictionary[int -> output port, Array[Dictionary[String -> DeckNode#_id, int -> input port]]][/code] var outgoing_connections: Dictionary -## [code]Dictionary[int -> input port, [Dictionary[String -> DeckNode#_id, int -> output port]] +## A map of incoming connections to this node, in the format[br] +## [code]Dictionary[int -> input port, [Dictionary[String -> DeckNode#_id, int -> output port]][/code] var incoming_connections: Dictionary +## A list of [Port]s on this node. var ports: Array[Port] +## The deck this node belongs to. var _belonging_to: Deck +## A unique identifier for this node. var _id: String +## The type of this node, used for instantiation. var node_type: String +## The description of this node, shown to the user by a renderer. var description: String +## A list of aliases for this node, used by search. var aliases: Array[String] +## The category of this node. Must be snake_case. This is additional data which +## a renderer can optionally show to the user. var category: String +## Controls whether this node should appear in [SearchProvider]. var appears_in_search: bool = true +## A list of additional properties to save when this node is saved. var props_to_serialize: Array[StringName] +## The position of this node relative to the parent graph. +## Only used by renderers. var position: Dictionary = {"x": 0.0, "y": 0.0} enum PortType{ @@ -28,28 +47,40 @@ enum PortType{ VIRTUAL, ## Virtual port type (no slot on left [i]or[/i] right). } +## Emitted when this node has been moved. signal position_updated(new_position: Dictionary) +## Emitted when a port has been added. signal port_added(port: int) +## Emitted when a port has been removed. signal port_removed(port: int) +## Emitted when a port or multiple ports have been updated (added or removed). signal ports_updated() +## Emitted when a connection from this node has been added. signal outgoing_connection_added(from_port: int) +## Emitted when a connection from this node has been removed. signal outgoing_connection_removed(from_port: int) +## Emitted when a connection to this node has been added. signal incoming_connection_added(from_port: int) +## Emitted when a connection to this node has been removed. signal incoming_connection_removed(from_port: int) +## Add an input port to this node. Usually only used at initialization. func add_input_port(type: Deck.Types, label: String, descriptor: String = "") -> void: add_port(type, label, PortType.INPUT, get_input_ports().size(), descriptor) +## Add an output port to this node. Usually only used at initialization. func add_output_port(type: Deck.Types, label: String, descriptor: String = "") -> void: add_port(type, label, PortType.OUTPUT, get_output_ports().size(), descriptor) +## Add a virtual port to this node. Usually only used at initialization. func add_virtual_port(type: Deck.Types, label: String, descriptor: String = "") -> void: add_port(type, label, PortType.VIRTUAL, get_virtual_ports().size(), descriptor) +## Add a port to this node. Usually only used at initialization. func add_port(type: Deck.Types, label: String, port_type: PortType, index_of_type: int, descriptor: String = "") -> void: var port := Port.new(type, label, ports.size(), port_type, index_of_type, descriptor) ports.append(port) @@ -57,6 +88,7 @@ func add_port(type: Deck.Types, label: String, port_type: PortType, index_of_typ ports_updated.emit() +## Remove a port from this node. func remove_port(port_idx: int) -> void: outgoing_connections.erase(port_idx) incoming_connections.erase(port_idx) @@ -64,6 +96,7 @@ func remove_port(port_idx: int) -> void: port_removed.emit(port_idx) +## Send data to all outgoing connections on port [param from_output_port]. func send(from_output_port: int, data: DeckType, extra_data: Array = []) -> void: if outgoing_connections.get(from_output_port) == null: return @@ -76,10 +109,12 @@ func send(from_output_port: int, data: DeckType, extra_data: Array = []) -> void get_node(node)._receive(connection[node], data, extra_data) +## Virtual function that's called when this node receives data from another node's [method send] call. func _receive(to_input_port: int, data: DeckType, extra_data: Array = []) -> void: pass +## @deprecated func add_outgoing_connection_to_port(output_port: int, to_node: String, to_input_port: int) -> void: add_outgoing_connection( get_global_port_idx_from_output(output_port), @@ -88,6 +123,8 @@ func add_outgoing_connection_to_port(output_port: int, to_node: String, to_input ) +## Add a connection from the output port at [param from_port] to [param to_node]'s input port +## at [param to_port]. func add_outgoing_connection(from_port: int, to_node: String, to_port: int) -> void: var port_connections: Array = outgoing_connections.get(from_port, []) port_connections.append({to_node: to_port}) @@ -96,12 +133,17 @@ func add_outgoing_connection(from_port: int, to_node: String, to_port: int) -> v outgoing_connection_added.emit(from_port) +## Add an incoming connection from [param from_node]'s output port at [param from_port] to this node's +## input port at [param to_port]. func add_incoming_connection(to_port: int, from_node: String, from_port: int) -> void: var connection := {from_node: from_port} incoming_connections[to_port] = connection incoming_connection_added.emit(to_port) +## Request a value from an incoming connection on this node's input port at [param on_port]. +## Returns [code]null[/code] if no incoming connection exists on that port. +## The connected node may also return [code]null[/code]. func request_value(on_port: int) -> Variant: if !incoming_connections.has(on_port): return null @@ -111,11 +153,14 @@ func request_value(on_port: int) -> Variant: return node._value_request(connection.values()[0]) -# override this +## Virtual function that's called when this node has been requested a value from the output port +## at [param from_port]. func _value_request(from_port: int) -> Variant: return null +## Remove an outgoing connection from this node. +## Does [b]not[/b] remove the other node's incoming connection equivalent. func remove_outgoing_connection(from_port: int, connection_hash: int) -> void: var port_connections: Array = (outgoing_connections.get(from_port, []) as Array).duplicate(true) if port_connections.is_empty(): @@ -138,15 +183,19 @@ func remove_outgoing_connection(from_port: int, connection_hash: int) -> void: outgoing_connection_removed.emit(from_port) +## Remove an incoming connection to this node on the input port at [param to_port]. +## Does [b]not[/b] remove the other node's outgoing connection equivalent. func remove_incoming_connection(to_port: int) -> void: incoming_connections.erase(to_port) incoming_connection_removed.emit(to_port) +## @deprecated func remove_outgoing_connection_from_port(output_port: int, connection_hash: int) -> void: remove_outgoing_connection(get_global_port_idx_from_output(output_port), connection_hash) +## Returns a list of all input ports. func get_input_ports() -> Array[Port]: return ports.filter( func(port: Port) -> bool: @@ -154,6 +203,7 @@ func get_input_ports() -> Array[Port]: ) +## Returns a list of all output ports. func get_output_ports() -> Array[Port]: return ports.filter( func(port: Port) -> bool: @@ -161,6 +211,7 @@ func get_output_ports() -> Array[Port]: ) +## Returns a list of all virtual ports. func get_virtual_ports() -> Array[Port]: return ports.filter( func(port: Port) -> bool: @@ -168,6 +219,7 @@ func get_virtual_ports() -> Array[Port]: ) +## Returns the global port index from the input port index at [param idx]. func get_global_port_idx_from_input(idx: int) -> int: if get_input_ports().size() > idx: return get_input_ports()[idx].index @@ -175,6 +227,7 @@ func get_global_port_idx_from_input(idx: int) -> int: return -1 +## Returns the global port index from the output port index at [param idx]. func get_global_port_idx_from_output(idx: int) -> int: if get_output_ports().size() > idx: return get_output_ports()[idx].index @@ -182,6 +235,7 @@ func get_global_port_idx_from_output(idx: int) -> int: return -1 +## Returns the global port index from the virtual port index at [param idx]. func get_global_port_idx_from_virtual(idx: int) -> int: if get_virtual_ports().size() > idx: return get_virtual_ports()[idx].index @@ -189,44 +243,52 @@ func get_global_port_idx_from_virtual(idx: int) -> int: return -1 +## Get a port's local index from the global port index at [param idx]. func get_port_type_idx_from_global(idx: int) -> int: return ports[idx].index_of_type +## Returns the amount of outgoing connections on output port at [param port]. func get_outgoing_connection_count(port: int) -> int: return (outgoing_connections[port] as Array).size() +## @deprecated func get_outgoing_connection_count_on_output(port: int) -> int: return get_outgoing_connection_count(get_global_port_idx_from_output(port)) +## Returns [code]true[/code] if the node has an incoming connection in input port at [param port]. func has_incoming_connection(port: int) -> bool: return incoming_connections.has(port) +## @deprecated func has_incoming_connection_on_input(port: int) -> bool: return has_incoming_connection(get_global_port_idx_from_input(port)) +## Returns the list of all ports. func get_all_ports() -> Array[Port]: return ports +## Get a sibling node by its' ID. func get_node(uuid: String) -> DeckNode: return _belonging_to.get_node(uuid) -# override this to do setup before connections are loaded but after props were set +## Virtual function that's called during deserialization before connections are loaded in. func _pre_connection() -> void: pass -# override this to do extra setup after it's done loading from dictionary +## Virtual function that's called after the node has been deserialized. func _post_load() -> void: pass +## Returns a [Dictionary] representation of this node. func to_dict(with_meta: bool = true) -> Dictionary: var d := { "_id": _id, @@ -254,5 +316,6 @@ func to_dict(with_meta: bool = true) -> Dictionary: return d +## Returns the node's [member position] as a [Vector2]. func position_as_vector2() -> Vector2: return Vector2(position.x, position.y) diff --git a/classes/deck/port.gd b/classes/deck/port.gd index 61d47d5..3f41cbf 100644 --- a/classes/deck/port.gd +++ b/classes/deck/port.gd @@ -1,14 +1,30 @@ class_name Port +## A data type representing a port of a [DeckNode]. +## +## Ports are used for connections between [DeckNode]s and can contain data that is passed between +## them on a node. +## The type index of this port. var type: Deck.Types +## The label of this port. Used by the renderer to display. How it's displayed depends on the renderer +## and the [member descriptor]. var label: String +## Hints to the renderer on how to display this port.[br] +## Can be either one of these: [code]button textfield spinbox slider textblock codeblock checkbox singlechoice multichoice[/code].[br] +## Additional descriptor properties can be specified after the type, delimited by a colon ([code]:[/code]).[br] +## @experimental var descriptor: String +## A callback to get this port's value. Intended usage is by renderers that show an input field of some kind. var value_callback: Callable +## The type of this port (input, output or virtual) var port_type: DeckNode.PortType +## The local index of this port. var index_of_type: int +## The global index of this port. var index: int +## The value of this port. var value: Variant: set = set_value diff --git a/classes/deck/search_provider.gd b/classes/deck/search_provider.gd index cf8eeab..1445001 100644 --- a/classes/deck/search_provider.gd +++ b/classes/deck/search_provider.gd @@ -1,5 +1,10 @@ class_name SearchProvider +## A class facilitating the searching of nodes. +## +## Allows a renderer or subscribing client to search for nodes registered in NodeDB, optionally with filters. + +## A list of all filters that can be applied to a search string. static var filters: Array[Filter] = [ # favorites filter. will only show nodes marked as favorite. syntax: "#f" Filter.new( @@ -40,6 +45,7 @@ static var filters: Array[Filter] = [ ] +## Performs a search for nodes. Filters can be provided directly in the search [param term]. static func search(term: String) -> Array[NodeDB.NodeDescriptor]: var res: Array[NodeDB.NodeDescriptor] = [] @@ -75,6 +81,10 @@ static func search(term: String) -> Array[NodeDB.NodeDescriptor]: return filtered_res +## A filter that can be applied to a search term in [SearchProvider]. +## +## Filters have a set of functions that are run by the [SearchProvider] +## to determine if any items should be removed from the final match list. class Filter: ## Return [code]true[/code] if this filter should be applied to the search.[br] ## [code]Callable(search_string: String) -> bool[/code] diff --git a/classes/types/deck_type.gd b/classes/types/deck_type.gd index 040e138..d666dfd 100644 --- a/classes/types/deck_type.gd +++ b/classes/types/deck_type.gd @@ -1,9 +1,11 @@ class_name DeckType +## Base class for defining the types that can be used on a [Port]. var _value: Variant var _success: bool = true +## Returns [code]true[/code] if the type is valid and can be used. func is_valid() -> bool: return _success @@ -16,11 +18,17 @@ func set_value(new_value: Variant) -> void: _value = new_value +## Virtual function. Used to convert [param other] to the overriding class' type. static func from(other: DeckType): return null +## Generic Error type. +## +## Always returns [code]null[/code] as the value, and [code]false[/code] as success.[br] +## Always returns a new [DeckType.DeckTypeError] on conversion from any other type. class DeckTypeError extends DeckType: + ## An optional error message. var error_message: String @@ -35,11 +43,16 @@ class DeckTypeError extends DeckType: return DeckTypeError.new() +## Numeric type. Corresponds to the JSON number type, so only supports the float primitive type. class DeckTypeNumeric extends DeckType: func _init(value: float = 0.0) -> void: _value = value - + ## Converts either a [DeckType.DeckTypeString] or [DeckType.DeckTypeBool] to numeric. + ## In the case of [DeckType.DeckTypeBool], [code]1.0[/code] is used for [code]true[/code], + ## [code]0.0[/code] for [code]false[/code].[br] + ## In the case of [DeckType.DeckTypeString], converts to String if it is a valid number + ## or [DeckType.DeckTypeError] otherwise. static func from(other: DeckType): if other is DeckTypeNumeric: return other @@ -65,6 +78,7 @@ class DeckTypeNumeric extends DeckType: return err +## String type. Corresponds to the JSON string type. Any type can be converted to String. class DeckTypeString extends DeckType: func _init(value: String = "") -> void: _value = value @@ -78,11 +92,17 @@ class DeckTypeString extends DeckType: inst._value = var_to_str(other.get_value()) +## Boolean type. Corresponds to the JSON bool type. class DeckTypeBool extends DeckType: func _init(value: bool = false) -> void: _value = value - + ## Converts either [DeckType.DeckTypeNumeric], [DeckType.DeckTypeDictionary] or [DeckType.DeckTypeArray] + ## to a boolean.[br] + ## In the case of [DeckType.DeckTypeNumeric], the resulting value will be [code]false[/code] + ## if the value is a zero value ([code]0.0[/code] and [code]-0.0[/code]), [code]true[/code] otherwise.[br] + ## In the case of [DeckType.DeckTypeDictionary] or [DeckType.DeckTypeArray], + ## the resulting value will be [code]true[/code] if the container is not empty. static func from(other: DeckType): if other is DeckTypeBool: return other @@ -98,11 +118,14 @@ class DeckTypeBool extends DeckType: return inst +## Array type. Corresponds to the JSON Array type. class DeckTypeArray extends DeckType: func _init(value: Array = []) -> void: _value = value + ## Arrays can only be converted from a string in the format + ## [code]"["foo", 2, "bar"][/code]. static func from(other: DeckType): if other is DeckTypeString: var inst := DeckTypeArray.new() @@ -114,11 +137,14 @@ class DeckTypeArray extends DeckType: return err +## Dictionary class. Corresponds to the JSON Object type. class DeckTypeDictionary extends DeckType: func _init(value: Dictionary = {}) -> void: _value = value + ## Dictionaries can only be converted from a string in the format + ## [code]{"key": "value"}[/code]. static func from(other: DeckType): if other is DeckTypeString: var inst := DeckTypeDictionary.new() diff --git a/graph_node_renderer/add_node_menu.gd b/graph_node_renderer/add_node_menu.gd index 3c5e74f..577a700 100644 --- a/graph_node_renderer/add_node_menu.gd +++ b/graph_node_renderer/add_node_menu.gd @@ -1,13 +1,18 @@ extends MarginContainer class_name AddNodeMenu +## A menu for adding nodes with a search bar. + @onready var search_line_edit: LineEdit = %SearchLineEdit @onready var scroll_content_container: VBoxContainer = %ScrollContentContainer @onready var scroll_container: ScrollContainer = $VBoxContainer/ScrollContainer +## The categories currently shown in the menu. var categories: Dictionary = {} # Dictionary[String, Category] +## A list of categories to remember the collapsed state of so they remain collapsed when the search list is rebuilt. var collapsed_categories: Array[String] +## Emitted when a node is selected either by clicking on it or pressing [constant @GlobalScope.KEY_ENTER] while the [member search_line_edit] is focused. signal node_selected(type: String) @@ -15,6 +20,7 @@ func _ready() -> void: search("") +## Add a new category to the menu. func add_category(category_name: String) -> void: var c := Category.new(category_name.capitalize()) categories[category_name] = c @@ -30,11 +36,13 @@ func add_category(category_name: String) -> void: ) +## Add an item to a category. func add_category_item(category: String, item: String, tooltip: String = "", favorite: bool = false) -> void: var c: Category = categories[category] c.add_item(item, tooltip, favorite) +## Wrapper around [method add_category_item] and [method add_category]. Adds an item to a [param category], creating the category if it doesn't exist yet. func add_item(category: String, item: String, tooltip: String = "", favorite: bool = false) -> void: if !categories.has(category): add_category(category) @@ -42,15 +50,18 @@ func add_item(category: String, item: String, tooltip: String = "", favorite: bo add_category_item(category, item, tooltip, favorite) +## Get a [AddNodeMenu.Category] node by its' identifier. func get_category(category: String) -> Category: return categories[category] +## Focus the search bar and select all its' text. func focus_search_bar() -> void: search_line_edit.select_all() search_line_edit.grab_focus() +## Searches for a node using [SearchProvider] and puts the results as items. func search(text: String) -> void: scroll_content_container.get_children().map(func(c: Node): c.queue_free()) categories.clear() @@ -69,6 +80,7 @@ func search(text: String) -> void: get_category(categories.keys()[0]).highlight_item(0) +## Callback for [member search_line_edit]'s input events. Handles highlighting items when navigating with up/down arrow keys. func _on_search_line_edit_gui_input(event: InputEvent) -> void: if event.is_action_pressed("ui_down"): var category: Category @@ -123,6 +135,7 @@ func _on_search_line_edit_gui_input(event: InputEvent) -> void: scroll_container.ensure_control_visible(category.get_child(item - 1)) +## Callback for [member search_line_edit]. Handles emitting [signal node_selected] func _on_search_line_edit_text_submitted(_new_text: String) -> void: var category: Category for i: String in categories: @@ -139,15 +152,22 @@ func _on_category_collapse_toggled(collapsed: bool, category: String) -> void: else: collapsed_categories.erase(category) - +## A collapsible item menu. +## +## An analog to [Tree] and [ItemList] made with nodes. Allows collapsing its +## children [AddNodeMenu.CategoryItem] nodes and highlighting them.[br] +## [b]Note:[/b] only one [AddNodeMenu.CategoryItem] can be highlighted at any time. class Category extends VBoxContainer: const COLLAPSE_ICON := preload("res://graph_node_renderer/textures/collapse-icon.svg") const COLLAPSE_ICON_COLLAPSED := preload("res://graph_node_renderer/textures/collapse-icon-collapsed.svg") var collapse_button: Button + ## Emitted when a child item has been pressed. signal item_pressed(item: int) + ## Emitted when a child item's favorite button has been pressed. signal item_favorite_button_toggled(item: int, toggled: bool) + ## Emitted when the category's collapsed state has been toggled. signal collapse_toggled(collapsed: bool) @@ -171,6 +191,7 @@ class Category extends VBoxContainer: collapse_button.toggled.connect(set_collapsed) + ## If [param collapsed] is [code]true[/code], collapses the category, hiding its children. func set_collapsed(collapsed: bool) -> void: collapse_button.icon = COLLAPSE_ICON_COLLAPSED if collapsed else COLLAPSE_ICON collapse_button.set_pressed_no_signal(collapsed) @@ -178,6 +199,7 @@ class Category extends VBoxContainer: c.visible = !collapsed + ## Add an item to the category. func add_item(p_name: String, p_tooltip: String, p_favorite: bool = false) -> void: var item := CategoryItem.new(p_name, p_tooltip, p_favorite) item.favorite_toggled.connect( @@ -191,38 +213,46 @@ class Category extends VBoxContainer: add_child(item) + ## Set an item's metadata at index [param item]. func set_item_metadata(item: int, key: StringName, metadata: Variant) -> void: get_child(item).set_meta(key, metadata) + ## Retrieve an item's metadata at index [param item]. func get_item_metadata(item: int, key: StringName) -> Variant: return get_child(item).get_meta(key) + ## Get the amount of items in this category. func get_item_count() -> int: return get_child_count() + ## Toggle an item at index [param item]'s favorite state. func set_item_favorite(item:int, favorite: bool) -> void: var _item := get_child(item) as CategoryItem _item.set_favorite(favorite) + ## Returns [code]true[/code] if the item at [param item] is marked as favorite. func is_item_favorite(item: int) -> bool: var _item := get_child(item) as CategoryItem return _item.is_favorite() + ## Highlight item at index [item], and unhighlight all other items. func highlight_item(item: int) -> void: for c: CategoryItem in get_children(): c.set_highlighted(c.get_index() == item) + ## Unhighlight all items. func unhighlight_all() -> void: for c: CategoryItem in get_children(): c.set_highlighted(false) + ## Returns the index of the currently highlighted item. Returns [code]-1[/code] if no item is highlighted in this category. func get_highlighted_item() -> int: for c: CategoryItem in get_children(): if c.is_highlighted: @@ -231,11 +261,15 @@ class Category extends VBoxContainer: return -1 +## Represents an item in a [AddNodeMenu.Category]. +## +## A selectable and highlightable category item with a favorite button. class CategoryItem extends HBoxContainer: const FAVORITE_ICON := preload("res://graph_node_renderer/textures/favorite-icon.svg") const NON_FAVORITE_ICON := preload("res://graph_node_renderer/textures/non-favorite-icon.svg") const ITEM_MARGIN := 16 + ## The stylebox to use if this item is highlighted. var highlighted_stylebox := StyleBoxFlat.new() var is_highlighted: bool @@ -244,7 +278,9 @@ class CategoryItem extends HBoxContainer: var name_button: Button var panel: PanelContainer + ## Emitted when this item has been pressed. signal pressed + ## Emitted when this item's [member fav_button] has been pressed. signal favorite_toggled(toggled: bool) @@ -289,15 +325,18 @@ class CategoryItem extends HBoxContainer: add_child(panel) + ## Toggle this item's favorite state. func set_favorite(favorite: bool) -> void: fav_button.icon = FAVORITE_ICON if favorite else NON_FAVORITE_ICON fav_button.set_pressed_no_signal(favorite) + ## Returns [code]true[/code] if this item is marked as favorite. func is_favorite() -> bool: return fav_button.icon == FAVORITE_ICON + ## Toggle this item's highlighted state. func set_highlighted(highlighted: bool) -> void: is_highlighted = highlighted if highlighted: