group descriptors (#156)

closes #93

Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/156
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
This commit is contained in:
Lera Elvoé 2024-05-20 04:32:12 +00:00 committed by yagich
parent ba2dd6a837
commit 8156e4769f
12 changed files with 478 additions and 44 deletions

View file

@ -25,6 +25,8 @@ var is_group: bool = false
## Whether this deck is a library. Implies [member is_group]. ## Whether this deck is a library. Implies [member is_group].
var is_library: bool = false var is_library: bool = false
var group_descriptors := GroupDescriptors.new()
#region library group props #region library group props
## The initial name of the library. Displayed by the group node and [SearchProvider]. ## The initial name of the library. Displayed by the group node and [SearchProvider].
var lib_name: String 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_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_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_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_renamed(node_id: String, new_name: String, deck: Deck)
signal node_moved(node_id: String, new_position: Dictionary, deck: Deck) signal node_moved(node_id: String, new_position: Dictionary, deck: Deck)
#endregion #endregion
func _init() -> void:
group_descriptors.deck = self
## Instantiate a node by its' [member DeckNode.node_type] and add it to this deck.[br] ## Instantiate a node by its' [member DeckNode.node_type] and add it to this deck.[br]
## See [method add_node_inst] for parameter descriptions. ## See [method add_node_inst] for parameter descriptions.
func add_node_type(type: String, assign_id: String = "", assign_to_self: bool = true) -> DeckNode: 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 nodes[assign_id] = node
node._id = assign_id 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: if emit_node_added_signal:
node_added.emit(node) 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_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()]) print_verbose("Deck %s::%s: added node %s, id %s" % [id, instance_id, node.node_type, node.get_instance_id()])
return node 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 node.group_instance_id = group.instance_id
group.get_node(group.group_input_node).group_node = node group.get_node(group.group_input_node).group_node = node
group.get_node(group.group_output_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.input_node = group.get_node(group.group_input_node)
node.output_node = group.get_node(group.group_output_node) node.output_node = group.get_node(group.group_output_node)
node.init_io() node.init_io()
@ -598,6 +618,9 @@ func to_dict(with_meta: bool = true, group_ids: Array = []) -> Dictionary:
"connections": connections.to_dict(), "connections": connections.to_dict(),
} }
if not group_descriptors.is_empty():
inner["group_descriptors"] = group_descriptors.to_dict()
for node_id: String in nodes.keys(): for node_id: String in nodes.keys():
inner["nodes"][node_id] = nodes[node_id].to_dict(with_meta) 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: 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_description = lib_data.get("lib_description", "")
# deck.lib_aliases = lib_data.get("lib_aliases", []) # 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: for key in data.meta:
deck.set_meta(key, str_to_var(data.meta[key])) 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: for node_id in nodes_data:
var node := DeckNode.from_dict(nodes_data[node_id], deck.connections) var node := DeckNode.from_dict(nodes_data[node_id], deck.connections)
deck.add_node_inst(node, node_id) deck.add_node_inst(node, node_id)
node._post_deck_load()
var groups_data: Dictionary = data.deck.groups as Dictionary var groups_data: Dictionary = data.deck.groups as Dictionary
@ -986,3 +1014,172 @@ class OutgoingConnection:
func to_dict() -> Dictionary: func to_dict() -> Dictionary:
return {"to_node": to_node, "to_port": to_port} 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

View file

@ -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_added_to_group.connect(DeckHolder._on_node_added_to_group)
group.node_removed_from_group.connect(DeckHolder._on_node_removed_from_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_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_renamed.connect(DeckHolder._on_node_renamed)
group.node_moved.connect(DeckHolder._on_node_moved) 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 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: static func _on_node_renamed(node_id: String, new_name: String, deck: Deck) -> void:
var group_id := deck.id var group_id := deck.id
for instance_id: String in decks[group_id]: for instance_id: String in decks[group_id]:

View file

@ -250,6 +250,12 @@ func _post_load(connections: Deck.NodeConnections) -> void:
pass 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 ## A helper function to get a value on an input port. Returns the best match in the following
## order of priority:[br] ## order of priority:[br]
## 1. The direct result of [method request value], called asynchronously. [br] ## 1. The direct result of [method request value], called asynchronously. [br]

View file

@ -55,21 +55,25 @@ func _on_outgoing_connection_removed(port_idx: int) -> void:
func _pre_port_load() -> void: func _pre_port_load() -> void:
ports.clear()
for i in output_count: for i in output_count:
add_output_port( add_output_port(
DeckType.Types.ANY, DeckType.Types.ANY,
"Input %s" % (i + 1) "Input %s" % i
) )
output_count = 0 output_count = 0
func _post_load(connections: Deck.NodeConnections) -> void: func _post_load(connections: Deck.NodeConnections) -> void:
# ensure we have enough ports after connections # 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) var outgoing_connections = connections.get_all_outgoing_connections(_id)
for port: int in outgoing_connections: for port: int in outgoing_connections:
last_connected_port = port if outgoing_connections.has(port) else last_connected_port 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: if ports.size() <= last_connected_port:
for i in last_connected_port: for i in last_connected_port:
add_output_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: func _value_request(from_port: int) -> Variant:
if group_node: if group_node:
return await group_node.request_value_async(group_node.get_input_ports()[from_port].index_of_type) return await group_node.request_value_async(group_node.get_input_ports()[from_port].index_of_type)

View file

@ -23,13 +23,13 @@ func _init() -> void:
appears_in_search = false appears_in_search = false
func _pre_port_load() -> void: #func _pre_port_load() -> void:
for port_type: PortType in extra_ports: #for port_type: PortType in extra_ports:
match port_type: #match port_type:
PortType.OUTPUT: #PortType.OUTPUT:
add_output_port(DeckType.Types.ANY, "Output %s" % get_output_ports().size()) #add_output_port(DeckType.Types.ANY, "Output %s" % get_output_ports().size())
PortType.INPUT: #PortType.INPUT:
add_input_port(DeckType.Types.ANY, "Input %s" % get_input_ports().size()) #add_input_port(DeckType.Types.ANY, "Input %s" % get_input_ports().size())
func init_io() -> void: func init_io() -> void:
@ -64,14 +64,18 @@ func recalculate_ports() -> void:
for output_port: Port in output_node.get_input_ports(): for output_port: Port in output_node.get_input_ports():
add_output_port( add_output_port(
DeckType.Types.ANY, output_port.type,
"Output %s" % output_port.index output_port.label,
output_port.descriptor,
output_port.usage_type,
) )
for input_port: Port in input_node.get_output_ports(): for input_port: Port in input_node.get_output_ports():
add_input_port( add_input_port(
DeckType.Types.ANY, input_port.type,
"Input %s" % input_port.index input_port.label,
input_port.descriptor,
input_port.usage_type,
) )
extra_ports.clear() extra_ports.clear()

View file

@ -56,10 +56,11 @@ func _on_incoming_connection_removed(port_idx: int) -> void:
func _pre_port_load() -> void: func _pre_port_load() -> void:
ports.clear()
for i in input_count: for i in input_count:
add_input_port( add_input_port(
DeckType.Types.ANY, DeckType.Types.ANY,
"Output %s" % (i + 1) "Output %s" % i
) )
input_count = 0 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: func _receive(to_input_port: int, data: Variant) -> void:
if group_node: if group_node:
group_node.send(group_node.get_output_ports()[to_input_port].index_of_type, data) group_node.send(group_node.get_output_ports()[to_input_port].index_of_type, data)

View file

@ -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. ## 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: 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) p_signal.connect(p_func)
## Disconnects the [param p_func] from [param p_signal] if that connection exists. ## Disconnects the [param p_func] from [param p_signal] if that connection exists.
static func safe_disconnect(p_signal: Signal, p_func: Callable) -> void: 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) p_signal.disconnect(p_func)

View file

@ -112,14 +112,7 @@ func _ready() -> void:
add_recents_to_menu() add_recents_to_menu()
tab_container.tab_close_requested.connect( tab_container.tab_close_requested.connect(request_tab_close)
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)
)
file_dialog.canceled.connect(disconnect_file_dialog_signals) file_dialog.canceled.connect(disconnect_file_dialog_signals)
Connections.obs_websocket = no_obsws 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: func _on_tab_container_tab_about_to_change(previous_tab: int) -> void:
var deck := get_deck_at_tab(previous_tab) var deck := get_deck_at_tab(previous_tab)
if deck == null: if deck == null:
sidebar.set_edited_deck()
return return
Util.safe_disconnect(deck.variables_updated, bottom_dock.rebuild_variable_tree) 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: func _on_tab_container_tab_changed(tab: int) -> void:
var deck := get_active_deck() var deck := get_active_deck()
if deck == null: if deck == null:
sidebar.set_edited_deck()
return return
var is_group = tab_container.get_tab_metadata(tab, "group", false) var is_group = tab_container.get_tab_metadata(tab, "group", false)
file_popup_menu.set_item_disabled(FileMenuId.SAVE, is_group) 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: FileMenuId.SAVE_AS when tab_container.get_tab_count() > 0:
open_save_dialog(tab_container.get_tab_metadata(tab_container.get_current_tab(), "path")) open_save_dialog(tab_container.get_tab_metadata(tab_container.get_current_tab(), "path"))
FileMenuId.CLOSE: FileMenuId.CLOSE:
close_current_tab() request_tab_close(tab_container.get_current_tab())
_ when id in range(FileMenuId.RECENTS, FileMenuId.RECENTS + max_recents + 1): _ when id in range(FileMenuId.RECENTS, FileMenuId.RECENTS + max_recents + 1):
open_deck_at_path(recent_files[id - FileMenuId.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()) 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: func close_tab(tab: int) -> void:
if not tab_container.get_tab_metadata(tab, "group"): if not tab_container.get_tab_metadata(tab, "group"):
var groups := DeckHolder.close_deck(tab_container.get_tab_metadata(tab, "id")) 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 await get_tree().process_frame
tab_container.close_tab(tab) tab_container.close_tab(tab)
var tc = tab_container.get_tab_count()
if tab_container.get_tab_count() == 0: if tab_container.get_tab_count() == 0:
bottom_dock.variable_viewer.disable_new_button() bottom_dock.variable_viewer.disable_new_button()
sidebar.set_edited_deck()
## Opens [member file_dialog] with the mode [member FileDialog.FILE_MODE_SAVE_FILE] ## Opens [member file_dialog] with the mode [member FileDialog.FILE_MODE_SAVE_FILE]

View file

@ -19,11 +19,11 @@ func _setup(port: Port, node: DeckNode) -> void:
else: else:
spin_box.step = 1.0 spin_box.step = 1.0
else: else:
spin_box.min_value = float(descriptor[2]) spin_box.min_value = float(descriptor[1])
if descriptor.size() > 2: if descriptor.size() > 2:
spin_box.max_value = float(descriptor[3]) spin_box.max_value = float(descriptor[2])
if descriptor.size() > 3: if descriptor.size() > 3:
spin_box.step = float(descriptor[4]) spin_box.step = float(descriptor[3])
port.value_callback = spin_box.get_value port.value_callback = spin_box.get_value
spin_box.value_changed.connect(port.set_value) spin_box.value_changed.connect(port.set_value)
spin_box.editable = not node._belonging_to.is_library spin_box.editable = not node._belonging_to.is_library

View file

@ -40,6 +40,8 @@ var _title: String
var _renamed_lambda = func(): var _renamed_lambda = func():
_set_title(name) _set_title(name)
signal menu_collapsed(is_visible: bool)
func _enter_tree() -> void: func _enter_tree() -> void:
if get_child_count(true) > 0 and get_child(0, true) is CollapseButton: 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): for child in get_children(false):
child.visible = false child.visible = false
if is_inside_tree(): if is_inside_tree():
menu_collapsed.emit(true)
await get_tree().process_frame await get_tree().process_frame
update_minimum_size() update_minimum_size()
@ -103,6 +106,7 @@ func uncollapse() -> void:
for child in get_children(false): for child in get_children(false):
child.visible = true child.visible = true
if is_inside_tree(): if is_inside_tree():
menu_collapsed.emit(false)
await get_tree().process_frame await get_tree().process_frame
update_minimum_size() update_minimum_size()

View file

@ -16,6 +16,8 @@ var edited_node_id: String
var deck_inspector: DeckInspector var deck_inspector: DeckInspector
var node_inspector: NodeInspector var node_inspector: NodeInspector
static var collapsed_menus: Dictionary # Dictionary[String -> id, bool -> collapsed]
signal go_to_node_requested(node_id: String) 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: class Inspector extends HBoxContainer:
func _init(text: String, editor: Control = null) -> void: func _init(text: String, editor: Control = null) -> void:
var _label = Label.new() var _label = Label.new()
@ -148,13 +162,14 @@ class Inspector extends HBoxContainer:
class DeckInspector: class DeckInspector:
var ref: WeakRef var ref: WeakRef
var nodes: Array[Control] var nodes: Array[Control]
var group_descriptors_inspector: GroupDescriptorsInspector
func add_inspector(text: String, control: Control = null) -> void: func add_inspector(text: String, control: Control = null) -> void:
nodes.append(Inspector.new(text, control)) nodes.append(Inspector.new(text, control))
func create_label(text: String) -> Label: static func create_label(text: String) -> Label:
var l := Label.new() var l := Label.new()
l.text = text l.text = text
l.size_flags_horizontal = Control.SIZE_EXPAND_FILL l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
@ -165,7 +180,7 @@ class DeckInspector:
var hb := HBoxContainer.new() var hb := HBoxContainer.new()
control.size_flags_horizontal = Control.SIZE_EXPAND_FILL 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) hb.add_child(control)
return hb return hb
@ -186,7 +201,7 @@ class DeckInspector:
else: else:
lib_group_text = "This deck is a group." 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.autowrap_mode = TextServer.AUTOWRAP_WORD
l.custom_minimum_size.x = 40 l.custom_minimum_size.x = 40
lib_menu.add_child(l) lib_menu.add_child(l)
@ -215,7 +230,166 @@ class DeckInspector:
desc_field.custom_minimum_size.x = 100 desc_field.custom_minimum_size.x = 100
lib_menu.add_child(create_hb_label("Library description:", desc_field)) lib_menu.add_child(create_hb_label("Library description:", desc_field))
group_descriptors_inspector = GroupDescriptorsInspector.new(deck)
nodes.append(lib_menu) 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: class NodeInspector:
@ -241,13 +415,6 @@ class NodeInspector:
nodes.append(Inspector.new(text, control)) 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: func _name_field_rename(new_name: String) -> void:
if _name_field.has_focus(): if _name_field.has_focus():
return return
@ -259,7 +426,7 @@ class NodeInspector:
ref = weakref(DeckHolder.get_deck(deck_id).get_node(id)) ref = weakref(DeckHolder.get_deck(deck_id).get_node(id))
var node: DeckNode = ref.get_ref() as DeckNode 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() var type_label_settings := LabelSettings.new()
type_label_settings.font = SYSTEM_CODE_FONT type_label_settings.font = SYSTEM_CODE_FONT
type_label_settings.font_size = 14 type_label_settings.font_size = 14
@ -310,19 +477,19 @@ class NodeInspector:
acc.draw_background = true acc.draw_background = true
acc.set_title("Port %s" % port.index_of_type) 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) 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) acc.add_child(type_label)
var usage_is_both := port.usage_type == Port.UsageType.BOTH 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_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) acc.add_child(usage_label)
var descriptor_split := port.descriptor.split(":") var descriptor_split := port.descriptor.split(":")
if DESCRIPTOR_SCENES.has(descriptor_split[0]): if DESCRIPTOR_SCENES.has(descriptor_split[0]):
var port_hb := HBoxContainer.new() var port_hb := HBoxContainer.new()
var value_label := create_label("Value:") var value_label := DeckInspector.create_label("Value:")
port_hb.add_child(value_label) port_hb.add_child(value_label)
var desc: DescriptorContainer = DESCRIPTOR_SCENES[descriptor_split[0]].instantiate() var desc: DescriptorContainer = DESCRIPTOR_SCENES[descriptor_split[0]].instantiate()

View file

@ -21,7 +21,7 @@ class_name TabContainerCustom
signal add_button_pressed signal add_button_pressed
## Emitted when the current tab in [member tab_bar] is changed. ## Emitted when the current tab in [member tab_bar] is changed.
signal tab_changed(tab: int) 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) signal tab_about_to_change(previous_tab: int)
## Emitted when a tab in [member tab_bar] has been closed. ## Emitted when a tab in [member tab_bar] has been closed.
## See [signal TabBar.tab_close_requested] ## See [signal TabBar.tab_close_requested]