From 8156e4769f23852f1139c0407dc9c6448aa8e1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Mon, 20 May 2024 04:32:12 +0000 Subject: [PATCH] group descriptors (#156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #93 Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/156 Co-authored-by: Lera ElvoƩ Co-committed-by: Lera ElvoƩ --- classes/deck/deck.gd | 197 ++++++++++++++++++ classes/deck/deck_holder.gd | 24 +++ classes/deck/deck_node.gd | 6 + classes/deck/nodes/group/group_input_node.gd | 19 +- classes/deck/nodes/group/group_node.gd | 26 ++- classes/deck/nodes/group/group_output_node.gd | 14 +- classes/util.gd | 4 +- graph_node_renderer/deck_holder_renderer.gd | 23 +- .../descriptors/spin_box_descriptor.gd | 6 +- graph_node_renderer/sidebar/accordion_menu.gd | 4 + graph_node_renderer/sidebar/sidebar.gd | 197 ++++++++++++++++-- graph_node_renderer/tab_container_custom.gd | 2 +- 12 files changed, 478 insertions(+), 44 deletions(-) diff --git a/classes/deck/deck.gd b/classes/deck/deck.gd index ef9ef9c..67794e5 100644 --- a/classes/deck/deck.gd +++ b/classes/deck/deck.gd @@ -25,6 +25,8 @@ var is_group: bool = false ## Whether this deck is a library. Implies [member is_group]. var is_library: bool = false +var group_descriptors := GroupDescriptors.new() + #region library group props ## The initial name of the library. Displayed by the group node and [SearchProvider]. var lib_name: String @@ -73,11 +75,16 @@ signal node_moved_to_deck(node: DeckNode, other: Deck) signal node_added_to_group(node: DeckNode, assign_id: String, assign_to_self: bool, deck: Deck) signal node_removed_from_group(node_id: String, remove_connections: bool, deck: Deck) signal node_port_value_updated(node_id: String, port_idx: int, new_value: Variant, deck: Deck) +signal node_ports_updated(node_id: String, deck: Deck) signal node_renamed(node_id: String, new_name: String, deck: Deck) signal node_moved(node_id: String, new_position: Dictionary, deck: Deck) #endregion +func _init() -> void: + group_descriptors.deck = self + + ## 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: @@ -104,6 +111,12 @@ func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool nodes[assign_id] = node node._id = assign_id + if node.node_type == "group_input": + group_input_node = node._id + + if node.node_type == "group_output": + group_output_node = node._id + if emit_node_added_signal: node_added.emit(node) @@ -128,6 +141,12 @@ func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool node_moved.emit(node._id, new_position, self) ) + node.ports_updated.connect( + func(): + if is_group and emit_group_signals: + node_ports_updated.emit(node._id, self) + ) + print_verbose("Deck %s::%s: added node %s, id %s" % [id, instance_id, node.node_type, node.get_instance_id()]) return node @@ -479,6 +498,7 @@ func paste_nodes_from_dict(nodes_to_paste: Dictionary, position: Vector2 = Vecto node.group_instance_id = group.instance_id group.get_node(group.group_input_node).group_node = node group.get_node(group.group_output_node).group_node = node + node.input_node = group.get_node(group.group_input_node) node.output_node = group.get_node(group.group_output_node) node.init_io() @@ -598,6 +618,9 @@ func to_dict(with_meta: bool = true, group_ids: Array = []) -> Dictionary: "connections": connections.to_dict(), } + if not group_descriptors.is_empty(): + inner["group_descriptors"] = group_descriptors.to_dict() + for node_id: String in nodes.keys(): inner["nodes"][node_id] = nodes[node_id].to_dict(with_meta) if get_node(node_id).node_type == "group_node" and not get_node(node_id).is_library: @@ -645,6 +668,10 @@ static func from_dict(data: Dictionary, path: String = "") -> Deck: deck.lib_description = lib_data.get("lib_description", "") # deck.lib_aliases = lib_data.get("lib_aliases", []) + if data.deck.has("group_descriptors"): + deck.group_descriptors = GroupDescriptors.from_dict(data.deck.group_descriptors) + deck.group_descriptors.deck = deck + for key in data.meta: deck.set_meta(key, str_to_var(data.meta[key])) @@ -653,6 +680,7 @@ static func from_dict(data: Dictionary, path: String = "") -> Deck: for node_id in nodes_data: var node := DeckNode.from_dict(nodes_data[node_id], deck.connections) deck.add_node_inst(node, node_id) + node._post_deck_load() var groups_data: Dictionary = data.deck.groups as Dictionary @@ -986,3 +1014,172 @@ class OutgoingConnection: func to_dict() -> Dictionary: return {"to_node": to_node, "to_port": to_port} + + +class GroupDescriptors: + var input_ports: Dictionary = {} # Dictionary[int -> input port idx, GroupPort] + var output_ports: Dictionary = {} # Dictionary[int -> output port idx, GroupPort] + var deck: Deck + + + func is_empty() -> bool: + if input_ports.is_empty() and output_ports.is_empty(): + return true + + var test := func(e: GroupPort) -> bool: + return not e.is_empty() + + var has_inputs = input_ports.values().any(test) + var has_outputs = output_ports.values().any(test) + + if has_inputs or has_outputs: + return false + + return true + + + func _on_input_updated(port_idx: int) -> void: + var node := deck.get_node(deck.group_input_node) + var port := node.get_output_ports()[port_idx] + var port_override := get_input_port(port_idx) + + if port_override.label.is_empty(): + port.label = "Input %s" % port_idx + else: + port.label = port_override.label + + port.type = port_override.type + port.descriptor = port_override.descriptor + port.usage_type = port_override.usage_type + + node.ports_updated.emit() + + + func _on_output_updated(port_idx: int) -> void: + var node := deck.get_node(deck.group_output_node) + var port := node.get_input_ports()[port_idx] + var port_override := get_output_port(port_idx) + if port_override.label.is_empty(): + port.label = "Output %s" % port_idx + else: + port.label = port_override.label + + port.type = port_override.type + port.descriptor = port_override.descriptor + port.usage_type = port_override.usage_type + + node.ports_updated.emit() + + + func _get_or_create_input(port_idx: int) -> GroupPort: + var res := input_ports.get(port_idx, null) as GroupPort + if res == null: + var gr := GroupPort.new() + input_ports[port_idx] = gr + res = gr + + Util.safe_connect(res.updated, _on_input_updated.bind(port_idx)) + + return res + + + func _get_or_create_output(port_idx: int) -> GroupPort: + var res := output_ports.get(port_idx, null) as GroupPort + if res == null: + var gr := GroupPort.new() + output_ports[port_idx] = gr + res = gr + + Util.safe_connect(res.updated, _on_output_updated.bind(port_idx)) + + return res + + + func get_input_port(port_idx: int) -> GroupPort: + return _get_or_create_input(port_idx) + + + func get_output_port(port_idx: int) -> GroupPort: + return _get_or_create_output(port_idx) + + + func to_dict() -> Dictionary: + var res := { + "input_ports": {}, + "output_ports": {}, + } + + for port_idx: int in input_ports: + var gp := input_ports[port_idx] as GroupPort + if gp.is_empty(): + continue + res["input_ports"][port_idx] = gp.to_dict() + + for port_idx: int in output_ports: + var gp := output_ports[port_idx] as GroupPort + if gp.is_empty(): + continue + res["output_ports"][port_idx] = gp.to_dict() + + return res + + + static func from_dict(d: Dictionary) -> GroupDescriptors: + var res := GroupDescriptors.new() + + for port_idx in d.input_ports: + res.input_ports[int(port_idx)] = GroupPort.from_dict(d.input_ports[port_idx]) + + for port_idx in d.output_ports: + res.output_ports[int(port_idx)] = GroupPort.from_dict(d.output_ports[port_idx]) + + return res + + +class GroupPort: + var type: DeckType.Types = DeckType.Types.ANY: + set(v): + type = v + updated.emit() + var label: String: + set(v): + label = v + updated.emit() + var descriptor: String: + set(v): + descriptor = v + updated.emit() + var usage_type: Port.UsageType = Port.UsageType.BOTH: + set(v): + usage_type = v + updated.emit() + + signal updated() + + + func is_empty() -> bool: + return\ + type == DeckType.Types.ANY and\ + label.is_empty() and\ + descriptor.is_empty() and\ + usage_type == Port.UsageType.BOTH + + + func to_dict() -> Dictionary: + var res := { + "type": type, + "label": label, + "descriptor": descriptor, + "usage_type": usage_type, + } + + return res + + + static func from_dict(d: Dictionary) -> GroupPort: + var res := GroupPort.new() + res.type = d.type as DeckType.Types + res.label = d.label + res.descriptor = d.descriptor + res.usage_type = d.usage_type as Port.UsageType + return res diff --git a/classes/deck/deck_holder.gd b/classes/deck/deck_holder.gd index b9b3b57..49e7ba8 100644 --- a/classes/deck/deck_holder.gd +++ b/classes/deck/deck_holder.gd @@ -185,6 +185,7 @@ static func connect_group_signals(group: Deck) -> void: group.node_added_to_group.connect(DeckHolder._on_node_added_to_group) group.node_removed_from_group.connect(DeckHolder._on_node_removed_from_group) group.node_port_value_updated.connect(DeckHolder._on_node_port_value_updated) + group.node_ports_updated.connect(DeckHolder._on_node_ports_updated) group.node_renamed.connect(DeckHolder._on_node_renamed) group.node_moved.connect(DeckHolder._on_node_moved) @@ -343,6 +344,29 @@ static func _on_node_port_value_updated(node_id: String, port_idx: int, new_valu instance.emit_group_signals = true +static func _on_node_ports_updated(node_id: String, deck: Deck) -> void: + var group_id := deck.id + var original_node := deck.get_node(node_id) + for instance_id: String in decks[group_id]: + if instance_id == deck.instance_id: + continue + + var instance: Deck = get_group_instance(group_id, instance_id) + instance.emit_group_signals = false + var node := instance.get_node(node_id) + node.ports.clear() + for port in original_node.ports: + node.add_port( + port.type, + port.label, + port.port_type, + port.index_of_type, + port.descriptor, + port.usage_type, + ) + instance.emit_group_signals = true + + static func _on_node_renamed(node_id: String, new_name: String, deck: Deck) -> void: var group_id := deck.id for instance_id: String in decks[group_id]: diff --git a/classes/deck/deck_node.gd b/classes/deck/deck_node.gd index d37a3c8..b0828af 100644 --- a/classes/deck/deck_node.gd +++ b/classes/deck/deck_node.gd @@ -250,6 +250,12 @@ func _post_load(connections: Deck.NodeConnections) -> void: pass +## Virtual function that's called by the parent [Deck] after the node has been deserialized, and after +## [method _post_load]. +func _post_deck_load() -> void: + pass + + ## A helper function to get a value on an input port. Returns the best match in the following ## order of priority:[br] ## 1. The direct result of [method request value], called asynchronously. [br] diff --git a/classes/deck/nodes/group/group_input_node.gd b/classes/deck/nodes/group/group_input_node.gd index 4a72e04..c92b0d8 100644 --- a/classes/deck/nodes/group/group_input_node.gd +++ b/classes/deck/nodes/group/group_input_node.gd @@ -55,21 +55,25 @@ func _on_outgoing_connection_removed(port_idx: int) -> void: func _pre_port_load() -> void: + ports.clear() for i in output_count: add_output_port( DeckType.Types.ANY, - "Input %s" % (i + 1) + "Input %s" % i ) output_count = 0 func _post_load(connections: Deck.NodeConnections) -> void: # ensure we have enough ports after connections - var last_connected_port := 0 + var last_connected_port := -1 var outgoing_connections = connections.get_all_outgoing_connections(_id) for port: int in outgoing_connections: last_connected_port = port if outgoing_connections.has(port) else last_connected_port + if last_connected_port == -1: + return + if ports.size() <= last_connected_port: for i in last_connected_port: add_output_port( @@ -78,6 +82,17 @@ func _post_load(connections: Deck.NodeConnections) -> void: ) +func _post_deck_load() -> void: + for port in get_output_ports(): + var gp := _belonging_to.group_descriptors.get_input_port(port.index_of_type) + if gp.is_empty(): + continue + port.label = gp.label + port.descriptor = gp.descriptor + port.type = gp.type + port.usage_type = gp.usage_type + + func _value_request(from_port: int) -> Variant: if group_node: return await group_node.request_value_async(group_node.get_input_ports()[from_port].index_of_type) diff --git a/classes/deck/nodes/group/group_node.gd b/classes/deck/nodes/group/group_node.gd index b6cda5d..86ada0d 100644 --- a/classes/deck/nodes/group/group_node.gd +++ b/classes/deck/nodes/group/group_node.gd @@ -23,13 +23,13 @@ func _init() -> void: appears_in_search = false -func _pre_port_load() -> void: - for port_type: PortType in extra_ports: - match port_type: - PortType.OUTPUT: - add_output_port(DeckType.Types.ANY, "Output %s" % get_output_ports().size()) - PortType.INPUT: - add_input_port(DeckType.Types.ANY, "Input %s" % get_input_ports().size()) +#func _pre_port_load() -> void: + #for port_type: PortType in extra_ports: + #match port_type: + #PortType.OUTPUT: + #add_output_port(DeckType.Types.ANY, "Output %s" % get_output_ports().size()) + #PortType.INPUT: + #add_input_port(DeckType.Types.ANY, "Input %s" % get_input_ports().size()) func init_io() -> void: @@ -64,14 +64,18 @@ func recalculate_ports() -> void: for output_port: Port in output_node.get_input_ports(): add_output_port( - DeckType.Types.ANY, - "Output %s" % output_port.index + output_port.type, + output_port.label, + output_port.descriptor, + output_port.usage_type, ) for input_port: Port in input_node.get_output_ports(): add_input_port( - DeckType.Types.ANY, - "Input %s" % input_port.index + input_port.type, + input_port.label, + input_port.descriptor, + input_port.usage_type, ) extra_ports.clear() diff --git a/classes/deck/nodes/group/group_output_node.gd b/classes/deck/nodes/group/group_output_node.gd index 48749a7..7514191 100644 --- a/classes/deck/nodes/group/group_output_node.gd +++ b/classes/deck/nodes/group/group_output_node.gd @@ -56,10 +56,11 @@ func _on_incoming_connection_removed(port_idx: int) -> void: func _pre_port_load() -> void: + ports.clear() for i in input_count: add_input_port( DeckType.Types.ANY, - "Output %s" % (i + 1) + "Output %s" % i ) input_count = 0 @@ -79,6 +80,17 @@ func _post_load(connections: Deck.NodeConnections) -> void: ) +func _post_deck_load() -> void: + for port in get_input_ports(): + var gp := _belonging_to.group_descriptors.get_output_port(port.index_of_type) + if gp.is_empty(): + continue + port.label = gp.label + port.descriptor = gp.descriptor + port.type = gp.type + port.usage_type = gp.usage_type + + func _receive(to_input_port: int, data: Variant) -> void: if group_node: group_node.send(group_node.get_output_ports()[to_input_port].index_of_type, data) diff --git a/classes/util.gd b/classes/util.gd index db22793..7b950bc 100644 --- a/classes/util.gd +++ b/classes/util.gd @@ -9,13 +9,13 @@ static var _batches: Dictionary # Dictionary[StringName, BatchConnection] ## Connects the [param p_func] to [param p_signal] if that connection doesn't already exist. static func safe_connect(p_signal: Signal, p_func: Callable) -> void: - if not p_signal.is_null() and p_func.is_valid() and not p_signal.is_connected(p_func): + if is_instance_valid(p_signal.get_object()) and is_instance_valid(p_func.get_object()) and not p_signal.is_null() and p_func.is_valid() and not p_signal.is_connected(p_func): p_signal.connect(p_func) ## Disconnects the [param p_func] from [param p_signal] if that connection exists. static func safe_disconnect(p_signal: Signal, p_func: Callable) -> void: - if not p_signal.is_null() and p_func.is_valid() and p_signal.is_connected(p_func): + if is_instance_valid(p_signal.get_object()) and is_instance_valid(p_func.get_object()) and not p_signal.is_null() and p_func.is_valid() and p_signal.is_connected(p_func): p_signal.disconnect(p_func) diff --git a/graph_node_renderer/deck_holder_renderer.gd b/graph_node_renderer/deck_holder_renderer.gd index 020efa0..c88c338 100644 --- a/graph_node_renderer/deck_holder_renderer.gd +++ b/graph_node_renderer/deck_holder_renderer.gd @@ -112,14 +112,7 @@ func _ready() -> void: add_recents_to_menu() - tab_container.tab_close_requested.connect( - func(tab: int): - if tab_container.get_tab_metadata(tab, "dirty") and not tab_container.get_tab_metadata(tab, "group"): - unsaved_changes_dialog_single_deck.set_meta("tab", tab) - unsaved_changes_dialog_single_deck.show() - return - await close_tab(tab) - ) + tab_container.tab_close_requested.connect(request_tab_close) file_dialog.canceled.connect(disconnect_file_dialog_signals) Connections.obs_websocket = no_obsws @@ -184,6 +177,7 @@ func _notification(what: int) -> void: func _on_tab_container_tab_about_to_change(previous_tab: int) -> void: var deck := get_deck_at_tab(previous_tab) if deck == null: + sidebar.set_edited_deck() return Util.safe_disconnect(deck.variables_updated, bottom_dock.rebuild_variable_tree) @@ -198,6 +192,7 @@ func _on_tab_container_tab_about_to_change(previous_tab: int) -> void: func _on_tab_container_tab_changed(tab: int) -> void: var deck := get_active_deck() if deck == null: + sidebar.set_edited_deck() return var is_group = tab_container.get_tab_metadata(tab, "group", false) file_popup_menu.set_item_disabled(FileMenuId.SAVE, is_group) @@ -245,7 +240,7 @@ func _on_file_id_pressed(id: int) -> void: FileMenuId.SAVE_AS when tab_container.get_tab_count() > 0: open_save_dialog(tab_container.get_tab_metadata(tab_container.get_current_tab(), "path")) FileMenuId.CLOSE: - close_current_tab() + request_tab_close(tab_container.get_current_tab()) _ when id in range(FileMenuId.RECENTS, FileMenuId.RECENTS + max_recents + 1): open_deck_at_path(recent_files[id - FileMenuId.RECENTS - 1]) @@ -299,6 +294,14 @@ func close_current_tab() -> void: tab_container.close_tab(tab_container.get_current_tab()) +func request_tab_close(tab: int): + if tab_container.get_tab_metadata(tab, "dirty") and not tab_container.get_tab_metadata(tab, "group"): + unsaved_changes_dialog_single_deck.set_meta("tab", tab) + unsaved_changes_dialog_single_deck.show() + return + await close_tab(tab) + + func close_tab(tab: int) -> void: if not tab_container.get_tab_metadata(tab, "group"): var groups := DeckHolder.close_deck(tab_container.get_tab_metadata(tab, "id")) @@ -310,8 +313,10 @@ func close_tab(tab: int) -> void: await get_tree().process_frame tab_container.close_tab(tab) + var tc = tab_container.get_tab_count() if tab_container.get_tab_count() == 0: bottom_dock.variable_viewer.disable_new_button() + sidebar.set_edited_deck() ## Opens [member file_dialog] with the mode [member FileDialog.FILE_MODE_SAVE_FILE] diff --git a/graph_node_renderer/descriptors/spin_box_descriptor.gd b/graph_node_renderer/descriptors/spin_box_descriptor.gd index f86caf1..6711ba2 100644 --- a/graph_node_renderer/descriptors/spin_box_descriptor.gd +++ b/graph_node_renderer/descriptors/spin_box_descriptor.gd @@ -19,11 +19,11 @@ func _setup(port: Port, node: DeckNode) -> void: else: spin_box.step = 1.0 else: - spin_box.min_value = float(descriptor[2]) + spin_box.min_value = float(descriptor[1]) if descriptor.size() > 2: - spin_box.max_value = float(descriptor[3]) + spin_box.max_value = float(descriptor[2]) if descriptor.size() > 3: - spin_box.step = float(descriptor[4]) + spin_box.step = float(descriptor[3]) port.value_callback = spin_box.get_value spin_box.value_changed.connect(port.set_value) spin_box.editable = not node._belonging_to.is_library diff --git a/graph_node_renderer/sidebar/accordion_menu.gd b/graph_node_renderer/sidebar/accordion_menu.gd index 5988503..58bfe71 100644 --- a/graph_node_renderer/sidebar/accordion_menu.gd +++ b/graph_node_renderer/sidebar/accordion_menu.gd @@ -40,6 +40,8 @@ var _title: String var _renamed_lambda = func(): _set_title(name) +signal menu_collapsed(is_visible: bool) + func _enter_tree() -> void: if get_child_count(true) > 0 and get_child(0, true) is CollapseButton: @@ -92,6 +94,7 @@ func collapse() -> void: for child in get_children(false): child.visible = false if is_inside_tree(): + menu_collapsed.emit(true) await get_tree().process_frame update_minimum_size() @@ -103,6 +106,7 @@ func uncollapse() -> void: for child in get_children(false): child.visible = true if is_inside_tree(): + menu_collapsed.emit(false) await get_tree().process_frame update_minimum_size() diff --git a/graph_node_renderer/sidebar/sidebar.gd b/graph_node_renderer/sidebar/sidebar.gd index 9674603..41abcbb 100644 --- a/graph_node_renderer/sidebar/sidebar.gd +++ b/graph_node_renderer/sidebar/sidebar.gd @@ -16,6 +16,8 @@ var edited_node_id: String var deck_inspector: DeckInspector var node_inspector: NodeInspector +static var collapsed_menus: Dictionary # Dictionary[String -> id, bool -> collapsed] + signal go_to_node_requested(node_id: String) @@ -133,6 +135,18 @@ func set_edited_node(id: String = "") -> void: #) +static func create_menu(title: String, id: String, default_collapsed: bool = false) -> AccordionMenu: + var res := AccordionMenu.new() + res.set_title(title) + res.collapsed = collapsed_menus.get(id, default_collapsed) + res.menu_collapsed.connect( + func(p_is_visible: bool) -> void: + collapsed_menus[id] = p_is_visible + ) + + return res + + class Inspector extends HBoxContainer: func _init(text: String, editor: Control = null) -> void: var _label = Label.new() @@ -148,13 +162,14 @@ class Inspector extends HBoxContainer: class DeckInspector: var ref: WeakRef var nodes: Array[Control] + var group_descriptors_inspector: GroupDescriptorsInspector func add_inspector(text: String, control: Control = null) -> void: nodes.append(Inspector.new(text, control)) - func create_label(text: String) -> Label: + static func create_label(text: String) -> Label: var l := Label.new() l.text = text l.size_flags_horizontal = Control.SIZE_EXPAND_FILL @@ -165,7 +180,7 @@ class DeckInspector: var hb := HBoxContainer.new() control.size_flags_horizontal = Control.SIZE_EXPAND_FILL - hb.add_child(create_label(text)) + hb.add_child(DeckInspector.create_label(text)) hb.add_child(control) return hb @@ -186,7 +201,7 @@ class DeckInspector: else: lib_group_text = "This deck is a group." - var l := create_label(lib_group_text) + var l := DeckInspector.create_label(lib_group_text) l.autowrap_mode = TextServer.AUTOWRAP_WORD l.custom_minimum_size.x = 40 lib_menu.add_child(l) @@ -215,7 +230,166 @@ class DeckInspector: desc_field.custom_minimum_size.x = 100 lib_menu.add_child(create_hb_label("Library description:", desc_field)) + group_descriptors_inspector = GroupDescriptorsInspector.new(deck) + nodes.append(lib_menu) + nodes.append(group_descriptors_inspector.menu) + + +class GroupDescriptorsInspector: + var menu: AccordionMenu + + var inputs_menu: AccordionMenu + var outputs_menu: AccordionMenu + var deck: Deck + + + func _init(p_deck: Deck) -> void: + deck = p_deck + + menu = AccordionMenu.new() + menu.set_title("Group Inputs/Outputs") + + inputs_menu = Sidebar.create_menu("Inputs", "group_inputs", true) + outputs_menu = Sidebar.create_menu("Outputs", "group_outputs", true) + + deck.node_added.connect(_on_deck_node_added) + + create_inputs() + create_outputs() + + menu.add_child(inputs_menu) + menu.add_child(outputs_menu) + + + func _on_deck_node_added(node: DeckNode) -> void: + if node.node_type == "group_input": + Util.safe_connect(node.ports_updated, refresh_inputs.bind(node)) + create_inputs(node) + if node.node_type == "group_output": + Util.safe_connect(node.ports_updated, refresh_outputs.bind(node)) + create_outputs(node) + + + func refresh_inputs(node: DeckNode) -> void: + # oh boy + var root := menu.get_tree().get_root() + var fo := root.gui_get_focus_owner() + if inputs_menu.is_ancestor_of(fo): + return + + create_inputs(node) + + + func refresh_outputs(node: DeckNode) -> void: + # oh boy + var root := menu.get_tree().get_root() + var fo := root.gui_get_focus_owner() + if outputs_menu.is_ancestor_of(fo): + return + + create_outputs(node) + + + func create_inputs(node: DeckNode = null) -> void: + create_io(true, node) + + + func create_outputs(node: DeckNode = null) -> void: + create_io(false, node) + + + func create_io(input: bool = true, node: DeckNode = null) -> void: + var io_menu := inputs_menu if input else outputs_menu + for i in io_menu.get_children(false): + i.queue_free() + + if node == null: + if input and deck.get_node(deck.group_input_node) == null: + var l := DeckInspector.create_label("No input node") + io_menu.add_child(l) + return + if not input and deck.get_node(deck.group_output_node) == null: + var l := DeckInspector.create_label("No output node") + io_menu.add_child(l) + return + + if input: + node = deck.get_node(deck.group_input_node) + else: + node = deck.get_node(deck.group_output_node) + + var refresh_func := refresh_inputs if input else refresh_outputs + Util.safe_connect(node.ports_updated, refresh_func.bind(node)) + + var ports := node.get_output_ports() if input else node.get_input_ports() + var get_port := func(index: int) -> Deck.GroupPort: + if input: + return deck.group_descriptors.get_input_port(index) + else: + return deck.group_descriptors.get_output_port(index) + + + for i in ports.size(): + var port_override: Deck.GroupPort = get_port.call(i) + var menu_title := "Input %s" if input else "Output %s" + var menu_id := "group_input_%s" if input else "group_output_%s" + var _menu := Sidebar.create_menu(menu_title % i, menu_id % i, true) + #input_menu.set_title("Input %s" % i) + + var port_label_field := LineEdit.new() + #port_label_field.placeholder_text = ports[i].label + port_label_field.placeholder_text = menu_title % i + + port_label_field.text = port_override.label + + port_label_field.text_changed.connect( + func(new_text: String) -> void: + port_override.label = new_text + ) + + _menu.add_child(Inspector.new("Label:", port_label_field)) + + var type_combo := OptionButton.new() + for type in DeckType.Types.size(): + type_combo.add_item(DeckType.type_str(type).capitalize()) + + type_combo.selected = port_override.type + + type_combo.item_selected.connect( + func(idx: int) -> void: + port_override.type = idx as DeckType.Types + ) + + _menu.add_child(Inspector.new("Type:", type_combo)) + + var usage_combo := OptionButton.new() + for usage in Port.UsageType.size(): + usage_combo.add_item(Port.UsageType.keys()[usage].capitalize()) + + usage_combo.selected = port_override.usage_type + + usage_combo.item_selected.connect( + func(idx: int) -> void: + port_override.usage_type = idx as Port.UsageType + ) + + _menu.add_child(Inspector.new("Usage:", usage_combo)) + + var descriptor_field := LineEdit.new() + descriptor_field.placeholder_text = "Descriptor (advanced)" + descriptor_field.tooltip_text = "Advanced use only.\nSeparate arguments with colon (:).\nPress Enter to confirm." + + descriptor_field.text = port_override.descriptor + + descriptor_field.text_submitted.connect( + func(new_text: String) -> void: + port_override.descriptor = new_text + ) + + _menu.add_child(Inspector.new("Descriptor:", descriptor_field)) + + io_menu.add_child(_menu) class NodeInspector: @@ -241,13 +415,6 @@ class NodeInspector: nodes.append(Inspector.new(text, control)) - func create_label(text: String) -> Label: - var l := Label.new() - l.text = text - l.size_flags_horizontal = Control.SIZE_EXPAND_FILL - return l - - func _name_field_rename(new_name: String) -> void: if _name_field.has_focus(): return @@ -259,7 +426,7 @@ class NodeInspector: ref = weakref(DeckHolder.get_deck(deck_id).get_node(id)) var node: DeckNode = ref.get_ref() as DeckNode - var type_label := create_label(node.node_type) + var type_label := DeckInspector.create_label(node.node_type) var type_label_settings := LabelSettings.new() type_label_settings.font = SYSTEM_CODE_FONT type_label_settings.font_size = 14 @@ -310,19 +477,19 @@ class NodeInspector: acc.draw_background = true acc.set_title("Port %s" % port.index_of_type) - var label_label := create_label("Name: %s" % port.label) + var label_label := DeckInspector.create_label("Name: %s" % port.label) acc.add_child(label_label) - var type_label := create_label("Type: %s" % DeckType.type_str(port.type).to_lower()) + var type_label := DeckInspector.create_label("Type: %s" % DeckType.type_str(port.type).to_lower()) acc.add_child(type_label) var usage_is_both := port.usage_type == Port.UsageType.BOTH var usage_text = "Both (Value Request or Trigger)" if usage_is_both else Port.UsageType.keys()[port.usage_type].capitalize() - var usage_label := create_label("Usage: %s" % usage_text) + var usage_label := DeckInspector.create_label("Usage: %s" % usage_text) acc.add_child(usage_label) var descriptor_split := port.descriptor.split(":") if DESCRIPTOR_SCENES.has(descriptor_split[0]): var port_hb := HBoxContainer.new() - var value_label := create_label("Value:") + var value_label := DeckInspector.create_label("Value:") port_hb.add_child(value_label) var desc: DescriptorContainer = DESCRIPTOR_SCENES[descriptor_split[0]].instantiate() diff --git a/graph_node_renderer/tab_container_custom.gd b/graph_node_renderer/tab_container_custom.gd index 9beeb76..e8d77a0 100644 --- a/graph_node_renderer/tab_container_custom.gd +++ b/graph_node_renderer/tab_container_custom.gd @@ -21,7 +21,7 @@ class_name TabContainerCustom signal add_button_pressed ## Emitted when the current tab in [member tab_bar] is changed. signal tab_changed(tab: int) -## Emitted just before the current tab in [member tab_bar] is changed.s +## Emitted just before the current tab in [member tab_bar] is changed. signal tab_about_to_change(previous_tab: int) ## Emitted when a tab in [member tab_bar] has been closed. ## See [signal TabBar.tab_close_requested]