mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
library groups (#151)
~~cl0ses #51~~ ~~cl0ses #93~~ ~~cl0ses #98~~ ~~cl0ses #150~~ another change in this PR: the Deck and DeckNode classes now use manual memory management. Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/151 Co-authored-by: Lera Elvoé <yagich@poto.cafe> Co-committed-by: Lera Elvoé <yagich@poto.cafe>
This commit is contained in:
parent
a80af97377
commit
240750c48e
42 changed files with 1284 additions and 273 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,3 +3,6 @@
|
|||
|
||||
# distribution folder
|
||||
dist/*/
|
||||
|
||||
# vscode folder
|
||||
.vscode/
|
|
@ -1,6 +1,7 @@
|
|||
# (c) 2023-present Eroax
|
||||
# (c) 2023-present Yagich
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
extends Object
|
||||
class_name Deck
|
||||
## A deck/graph with nodes.
|
||||
##
|
||||
|
@ -19,7 +20,20 @@ var save_path: String = ""
|
|||
## The connections graph.
|
||||
var connections := NodeConnections.new()
|
||||
|
||||
## Whether this deck is a group instance.
|
||||
var is_group: bool = false
|
||||
## Whether this deck is a library. Implies [member is_group].
|
||||
var is_library: bool = false
|
||||
|
||||
#region library group props
|
||||
## The initial name of the library. Displayed by the group node and [SearchProvider].
|
||||
var lib_name: String
|
||||
## The description of this library, shown to the user by a renderer.
|
||||
var lib_description: String
|
||||
## A list of aliases for this library, used by search.
|
||||
var lib_aliases: Array[String]
|
||||
#endregion
|
||||
|
||||
## List of groups belonging to this deck, in the format of[br]
|
||||
## [code]Dictionary[String -> Deck.id, Deck][/code]
|
||||
#var groups: Dictionary = {}
|
||||
|
@ -52,6 +66,8 @@ signal nodes_connected(from_node_id: String, to_node_id: String, from_output_por
|
|||
signal nodes_disconnected(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int)
|
||||
## Emitted when the [member variable_stack] has been modified.
|
||||
signal variables_updated()
|
||||
## Emitted when a node has been moved to a different deck.
|
||||
signal node_moved_to_deck(node: DeckNode, other: Deck)
|
||||
|
||||
#region group signals
|
||||
signal node_added_to_group(node: DeckNode, assign_id: String, assign_to_self: bool, deck: Deck)
|
||||
|
@ -88,6 +104,7 @@ func add_node_type(type: String, assign_id: String = "", assign_to_self: bool =
|
|||
func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool = true) -> DeckNode:
|
||||
if assign_to_self:
|
||||
node._belonging_to = self
|
||||
#node._belonging_to_instance = instance_id
|
||||
|
||||
if assign_id == "":
|
||||
var uuid := UUID.v4()
|
||||
|
@ -122,10 +139,37 @@ func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool
|
|||
)
|
||||
|
||||
node.connect_rpc_signals()
|
||||
print_verbose("Deck %s::%s: added node %s, id %s" % [id, instance_id, node.node_type, node.get_instance_id()])
|
||||
|
||||
return node
|
||||
|
||||
|
||||
func add_lib_group_node(type: String) -> DeckNode:
|
||||
var group_node := add_node_type("group_node")
|
||||
var lib := DeckHolder.add_lib_instance(type, id)
|
||||
group_node.is_library = true
|
||||
group_node.group_id = lib.id
|
||||
group_node.group_instance_id = lib.instance_id
|
||||
|
||||
for node_id: String in lib.nodes:
|
||||
var node := lib.get_node(node_id)
|
||||
if node.node_type == "group_input":
|
||||
group_node.input_node = node
|
||||
group_node.input_node_id = node._id
|
||||
node.group_node = group_node
|
||||
lib.group_input_node = node._id
|
||||
continue
|
||||
|
||||
if node.node_type == "group_output":
|
||||
group_node.output_node = node
|
||||
group_node.output_node_id = node._id
|
||||
node.group_node = group_node
|
||||
lib.group_output_node = node._id
|
||||
continue
|
||||
group_node.init_io()
|
||||
return group_node
|
||||
|
||||
|
||||
## Get a node belonging to this deck by its' ID.
|
||||
func get_node(uuid: String) -> DeckNode:
|
||||
return nodes.get(uuid)
|
||||
|
@ -194,6 +238,21 @@ func disconnect_nodes(from_node_id: String, to_node_id: String, from_output_port
|
|||
to_node.incoming_connection_removed.emit(to_input_port)
|
||||
|
||||
|
||||
func disconnect_pair(pair: ConnectionPair) -> void:
|
||||
connections.remove_pair(pair)
|
||||
var from_node_id := pair.incoming.from_node
|
||||
var to_node_id := pair.outgoing.to_node
|
||||
var from_output_port := pair.incoming.from_port
|
||||
var to_input_port := pair.outgoing.to_port
|
||||
connections.remove_connection(from_node_id, to_node_id, from_output_port, to_input_port)
|
||||
nodes_disconnected.emit(from_node_id, to_node_id, from_output_port, to_input_port)
|
||||
|
||||
var from_node := get_node(from_node_id)
|
||||
var to_node := get_node(to_node_id)
|
||||
from_node.outgoing_connection_removed.emit(from_output_port)
|
||||
to_node.incoming_connection_removed.emit(to_input_port)
|
||||
|
||||
|
||||
## Returns true if this deck has no nodes and no variables.
|
||||
func is_empty() -> bool:
|
||||
return nodes.is_empty() and variable_stack.is_empty()
|
||||
|
@ -212,16 +271,19 @@ func remove_node(uuid: String, remove_connections: bool = false, force: bool = f
|
|||
DeckHolder.close_group_instance(node.group_id, node.group_instance_id)
|
||||
|
||||
if remove_connections:
|
||||
var outgoing_connections := connections.get_all_outgoing_connections(uuid)
|
||||
for from_port: int in outgoing_connections:
|
||||
for outgoing: OutgoingConnection in outgoing_connections[from_port]:
|
||||
var incoming := outgoing.counterpart.get_ref() as IncomingConnection
|
||||
disconnect_nodes(uuid, outgoing.to_node, incoming.from_port, outgoing.to_port)
|
||||
# var outgoing_connections := connections.get_all_outgoing_connections(uuid)
|
||||
# for from_port: int in outgoing_connections.keys():
|
||||
# for outgoing: OutgoingConnection in outgoing_connections[from_port]:
|
||||
# var incoming := outgoing.counterpart.get_ref() as IncomingConnection
|
||||
# disconnect_nodes(uuid, outgoing.to_node, incoming.from_port, outgoing.to_port)
|
||||
|
||||
var incoming_connections := connections.get_all_incoming_connections(uuid)
|
||||
for to_port: int in incoming_connections:
|
||||
var incoming := connections.get_incoming_connection(uuid, to_port)
|
||||
disconnect_nodes(incoming.from_node, uuid, incoming.from_port, to_port)
|
||||
# var incoming_connections := connections.get_all_incoming_connections(uuid)
|
||||
# for to_port: int in incoming_connections.keys():
|
||||
# var incoming := connections.get_incoming_connection(uuid, to_port)
|
||||
# disconnect_nodes(incoming.from_node, uuid, incoming.from_port, to_port)
|
||||
var pairs := connections.get_node_pairs(uuid)
|
||||
for pair in pairs:
|
||||
disconnect_pair(pair)
|
||||
|
||||
nodes.erase(uuid)
|
||||
|
||||
|
@ -229,6 +291,16 @@ func remove_node(uuid: String, remove_connections: bool = false, force: bool = f
|
|||
|
||||
if is_group and emit_group_signals:
|
||||
node_removed_from_group.emit(uuid, remove_connections, self)
|
||||
|
||||
print_verbose("Deck %s::%s: freeing node %s, id %s" % [id, instance_id, node.node_type, node.get_instance_id()])
|
||||
node.free()
|
||||
|
||||
|
||||
func pre_exit_cleanup() -> void:
|
||||
for node_id: String in nodes:
|
||||
var node := get_node(node_id)
|
||||
print_verbose("Deck %s::%s: freeing node %s, id %s" % [id, instance_id, node.node_type, node.get_instance_id()])
|
||||
node.free()
|
||||
|
||||
|
||||
## Set a variable on this deck.
|
||||
|
@ -275,47 +347,20 @@ func group_nodes(nodes_to_group: Array) -> Deck:
|
|||
var rightmost := -INF
|
||||
var leftmost := INF
|
||||
for node: DeckNode in nodes_to_group:
|
||||
#if node.node_type == "group_node":
|
||||
#var _group_id: String = node.group_id
|
||||
#var _group: Deck = groups[_group_id]
|
||||
#groups.erase(_group)
|
||||
#group.groups[_group_id] = _group
|
||||
#_group._belonging_to = group
|
||||
|
||||
|
||||
if node.position.x > rightmost:
|
||||
rightmost = node.position.x
|
||||
if node.position.x < leftmost:
|
||||
leftmost = node.position.x
|
||||
|
||||
#var outgoing_connections := node.outgoing_connections.duplicate(true)
|
||||
#
|
||||
#for from_port: int in outgoing_connections:
|
||||
#for to_node: String in outgoing_connections[from_port]:
|
||||
#for to_port: int in outgoing_connections[from_port][to_node]:
|
||||
#if to_node not in node_ids_to_keep:
|
||||
#disconnect_nodes(node._id, to_node, from_port, to_port)
|
||||
|
||||
var outgoing_connections := connections.get_all_outgoing_connections(node._id)
|
||||
for from_port: int in outgoing_connections:
|
||||
for outgoing: OutgoingConnection in outgoing_connections[from_port]:
|
||||
var incoming := outgoing.counterpart.get_ref() as IncomingConnection
|
||||
disconnect_nodes(node._id, outgoing.to_node, incoming.from_port, outgoing.to_port)
|
||||
|
||||
#var incoming_connections := node.incoming_connections.duplicate(true)
|
||||
#
|
||||
#for to_port: int in incoming_connections:
|
||||
#for from_node: String in incoming_connections[to_port]:
|
||||
#if from_node not in node_ids_to_keep:
|
||||
#disconnect_nodes(from_node, node._id, incoming_connections[to_port][from_node], to_port)
|
||||
|
||||
var incoming_connections := connections.get_all_incoming_connections(node._id)
|
||||
for to_port: int in incoming_connections:
|
||||
var incoming := connections.get_incoming_connection(node._id, to_port)
|
||||
disconnect_nodes(incoming.from_node, node._id, incoming.from_port, to_port)
|
||||
var pairs := connections.get_node_pairs(node._id)
|
||||
for pair in pairs:
|
||||
disconnect_pair(pair)
|
||||
|
||||
midpoint += node.position_as_vector2()
|
||||
remove_node(node._id, false, true)
|
||||
group.add_node_inst(node, node._id)
|
||||
# remove_node(node._id, false, true)
|
||||
# group.add_node_inst(node, node._id)
|
||||
move_node_to_deck(node, group)
|
||||
|
||||
midpoint /= nodes_to_group.size()
|
||||
|
||||
|
@ -353,9 +398,23 @@ 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)
|
||||
func move_node_to_deck(node: DeckNode, other: Deck) -> void:
|
||||
if not node._id in nodes:
|
||||
return
|
||||
|
||||
nodes.erase(node._id)
|
||||
other.nodes[node._id] = node
|
||||
# node_moved_to_deck.emit(node, other)
|
||||
|
||||
node._belonging_to = other
|
||||
|
||||
node_removed.emit(node)
|
||||
other.node_added.emit(node)
|
||||
|
||||
if is_group and emit_group_signals:
|
||||
node_removed_from_group.emit(node._id, false, self)
|
||||
if other.is_group and other.emit_group_signals:
|
||||
node_added_to_group.emit(node, node._id, true, other)
|
||||
|
||||
|
||||
func copy_nodes(nodes_to_copy: Array[String]) -> Dictionary:
|
||||
|
@ -367,38 +426,6 @@ func copy_nodes(nodes_to_copy: Array[String]) -> Dictionary:
|
|||
for node_id: String in nodes_to_copy:
|
||||
d.nodes[node_id] = get_node(node_id).to_dict()
|
||||
|
||||
#for node: String in d.nodes:
|
||||
#var outgoing_connections: Dictionary = d.nodes[node].outgoing_connections.duplicate(true)
|
||||
#
|
||||
#for from_port: int in outgoing_connections:
|
||||
#for to_node: String in outgoing_connections[from_port]:
|
||||
#if to_node not in nodes_to_copy:
|
||||
#(d.nodes[node].outgoing_connections[from_port] as Dictionary).erase(to_node)
|
||||
#
|
||||
#var keys_to_erase := []
|
||||
#for from_port: int in d.nodes[node].outgoing_connections:
|
||||
#if (d.nodes[node].outgoing_connections[from_port] as Dictionary).is_empty():
|
||||
#keys_to_erase.append(from_port)
|
||||
#
|
||||
#for key in keys_to_erase:
|
||||
#(d.nodes[node].outgoing_connections as Dictionary).erase(key)
|
||||
#
|
||||
#var incoming_connections: Dictionary = d.nodes[node].incoming_connections.duplicate(true)
|
||||
#
|
||||
#for to_port: int in incoming_connections:
|
||||
#for from_node: String in incoming_connections[to_port]:
|
||||
#if from_node not in nodes_to_copy:
|
||||
#(d.nodes[node].incoming_connections[to_port] as Dictionary).erase(from_node)
|
||||
#
|
||||
#keys_to_erase.clear()
|
||||
#for to_port: int in d.nodes[node].incoming_connections:
|
||||
#if (d.nodes[node].incoming_connections[to_port] as Dictionary).is_empty():
|
||||
#keys_to_erase.append(to_port)
|
||||
#
|
||||
#for key in keys_to_erase:
|
||||
#(d.nodes[node].incoming_connections as Dictionary).erase(key)
|
||||
#keys_to_erase.clear()
|
||||
|
||||
for node: Dictionary in d.nodes.values().slice(1):
|
||||
node.position.x = node.position.x - d.nodes.values()[0].position.x
|
||||
node.position.y = node.position.y - d.nodes.values()[0].position.y
|
||||
|
@ -456,15 +483,37 @@ func paste_nodes_from_dict(nodes_to_paste: Dictionary, position: Vector2 = Vecto
|
|||
var node := DeckNode.from_dict(nodes_to_paste.nodes[node_id])
|
||||
var group_needs_unique := false
|
||||
if node.node_type == "group_node":
|
||||
var group := DeckHolder.make_new_group_instance(node.group_id, id)
|
||||
var old_group := DeckHolder.get_group_instance(node.group_id, node.group_instance_id)
|
||||
group_needs_unique = old_group._belonging_to != id
|
||||
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()
|
||||
if not node.is_library:
|
||||
var group := DeckHolder.make_new_group_instance(node.group_id, id)
|
||||
var old_group := DeckHolder.get_group_instance(node.group_id, node.group_instance_id)
|
||||
group_needs_unique = old_group._belonging_to != id
|
||||
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()
|
||||
else:
|
||||
var type: String = node.group_id.get_file().trim_suffix(".deck")
|
||||
var lib := DeckHolder.add_lib_instance(type, id)
|
||||
node.group_instance_id = lib.instance_id
|
||||
for l_node_id: String in lib.nodes:
|
||||
var l_node := lib.get_node(l_node_id)
|
||||
if l_node.node_type == "group_input":
|
||||
node.input_node = l_node
|
||||
node.input_node_id = l_node._id
|
||||
l_node.group_node = node
|
||||
lib.group_input_node = l_node_id
|
||||
continue
|
||||
|
||||
if l_node.node_type == "group_output":
|
||||
node.output_node = l_node
|
||||
node.output_node_id = l_node._id
|
||||
l_node.group_node = node
|
||||
lib.group_output_node = l_node_id
|
||||
continue
|
||||
node.init_io()
|
||||
|
||||
add_node_inst(node, ids_map[node_id])
|
||||
if group_needs_unique:
|
||||
node.make_unique()
|
||||
|
@ -522,10 +571,9 @@ func get_referenced_groups() -> Array[String]:
|
|||
var res: Array[String] = []
|
||||
for node_id: String in nodes:
|
||||
var node := get_node(node_id)
|
||||
if node.node_type != "group_node":
|
||||
continue
|
||||
res.append(node.group_id)
|
||||
res.append_array(DeckHolder.get_deck(node.group_id).get_referenced_groups())
|
||||
if node.node_type == "group_node" and not node.is_library:
|
||||
res.append(node.group_id)
|
||||
res.append_array(DeckHolder.get_deck(node.group_id).get_referenced_groups())
|
||||
return res
|
||||
|
||||
|
||||
|
@ -533,15 +581,14 @@ func get_referenced_group_instances() -> Array[Dictionary]:
|
|||
var res: Array[Dictionary] = []
|
||||
for node_id: String in nodes:
|
||||
var node := get_node(node_id)
|
||||
if node.node_type != "group_node":
|
||||
continue
|
||||
res.append({
|
||||
"group_id": node.group_id,
|
||||
"instance_id": node.group_instance_id,
|
||||
"parent_id": node._belonging_to.id,
|
||||
"group_node": node_id,
|
||||
})
|
||||
res.append_array(DeckHolder.get_group_instance(node.group_id, node.group_instance_id).get_referenced_group_instances())
|
||||
if node.node_type == "group_node" and not node.is_library:
|
||||
res.append({
|
||||
"group_id": node.group_id,
|
||||
"instance_id": node.group_instance_id,
|
||||
"parent_id": node._belonging_to,
|
||||
"group_node": node_id,
|
||||
})
|
||||
res.append_array(DeckHolder.get_group_instance(node.group_id, node.group_instance_id).get_referenced_group_instances())
|
||||
return res
|
||||
|
||||
|
||||
|
@ -564,7 +611,7 @@ func to_dict(with_meta: bool = true, group_ids: Array = []) -> Dictionary:
|
|||
|
||||
for node_id: String in nodes.keys():
|
||||
inner["nodes"][node_id] = nodes[node_id].to_dict(with_meta)
|
||||
if (nodes[node_id] as DeckNode).node_type == "group_node":
|
||||
if get_node(node_id).node_type == "group_node" and not get_node(node_id).is_library:
|
||||
if nodes[node_id].group_id not in group_ids:
|
||||
inner["groups"][nodes[node_id].group_id] = DeckHolder.get_deck(nodes[node_id].group_id).to_dict(with_meta, group_ids)
|
||||
group_ids.append(nodes[node_id].group_id)
|
||||
|
@ -577,6 +624,15 @@ func to_dict(with_meta: bool = true, group_ids: Array = []) -> Dictionary:
|
|||
inner["group_input_node"] = group_input_node
|
||||
inner["group_output_node"] = group_output_node
|
||||
|
||||
# don't save library stuff if this is not a library
|
||||
if not lib_name.is_empty():
|
||||
var lib := {
|
||||
"lib_name": lib_name,
|
||||
"lib_description": lib_description,
|
||||
"lib_aliases": lib_aliases,
|
||||
}
|
||||
inner["library"] = lib
|
||||
|
||||
var d := {"deck": inner}
|
||||
|
||||
if with_meta:
|
||||
|
@ -594,6 +650,12 @@ static func from_dict(data: Dictionary, path: String = "") -> Deck:
|
|||
deck.id = data.deck.id
|
||||
deck.connections = NodeConnections.from_dict(data.deck.connections)
|
||||
|
||||
if data.deck.has("library"):
|
||||
var lib_data: Dictionary = data.deck.library
|
||||
deck.lib_name = lib_data.get("lib_name", "")
|
||||
deck.lib_description = lib_data.get("lib_description", "")
|
||||
# deck.lib_aliases = lib_data.get("lib_aliases", [])
|
||||
|
||||
for key in data.meta:
|
||||
deck.set_meta(key, str_to_var(data.meta[key]))
|
||||
|
||||
|
@ -611,23 +673,30 @@ static func from_dict(data: Dictionary, path: String = "") -> Deck:
|
|||
continue
|
||||
var group_id: String = node.group_id
|
||||
var group_instance_id: String = node.group_instance_id
|
||||
var group_data: Dictionary = groups_data[group_id]
|
||||
var group := DeckHolder.add_group_from_dict(group_data, group_id, group_instance_id, deck.id)
|
||||
group.get_node(group.group_input_node).group_node = node
|
||||
group.get_node(group.group_output_node).group_node = node
|
||||
var group: Deck
|
||||
if not node.is_library:
|
||||
var group_data: Dictionary = groups_data[group_id]
|
||||
group = DeckHolder.add_group_from_dict(group_data, group_id, group_instance_id, deck.id)
|
||||
group.get_node(node.input_node_id).group_node = node
|
||||
group.get_node(node.output_node_id).group_node = node
|
||||
else:
|
||||
group = DeckHolder.add_lib_instance(
|
||||
node.group_id.get_file().trim_suffix(".deck"),
|
||||
deck.id,
|
||||
node.group_instance_id
|
||||
)
|
||||
for io_node_id: String in deck.nodes:
|
||||
var io_node := deck.get_node(io_node_id)
|
||||
if io_node.node_type == "group_input":
|
||||
node.input_node_id = io_node._id
|
||||
io_node.group_node = node
|
||||
continue
|
||||
if io_node.node_type == "group_output":
|
||||
node.output_node_id = io_node._id
|
||||
io_node.group_node = node
|
||||
continue
|
||||
node.init_io()
|
||||
|
||||
#for group_id: String in groups_data:
|
||||
#var group := Deck.from_dict(groups_data[group_id])
|
||||
#group._belonging_to = deck
|
||||
#group.is_group = true
|
||||
#deck.groups[group_id] = group
|
||||
#group.group_node = groups_data[group_id]["deck"]["group_node"]
|
||||
#group.group_input_node = groups_data[group_id]["deck"]["group_input_node"]
|
||||
#group.group_output_node = groups_data[group_id]["deck"]["group_output_node"]
|
||||
#deck.get_node(group.group_node).init_io()
|
||||
|
||||
|
||||
return deck
|
||||
|
||||
|
||||
|
@ -670,6 +739,16 @@ class NodeConnections:
|
|||
to_connection.erase(to_input_port)
|
||||
out_list.erase(out)
|
||||
break
|
||||
|
||||
# remove if empty
|
||||
if data[from_node_id] == _create_empty_connection():
|
||||
data.erase(from_node_id)
|
||||
|
||||
if data[to_node_id] == _create_empty_connection():
|
||||
data.erase(to_node_id)
|
||||
|
||||
if (data[from_node_id].outgoing[from_output_port] as Array).is_empty():
|
||||
(data[from_node_id].outgoing as Dictionary).erase(from_output_port)
|
||||
|
||||
|
||||
func has_incoming_connection(node_id: String, on_port: int) -> bool:
|
||||
|
@ -699,6 +778,9 @@ class NodeConnections:
|
|||
if data.get(node_id, _create_empty_connection()).outgoing.is_empty():
|
||||
return []
|
||||
|
||||
if not (data[node_id].outgoing as Dictionary).has(from_port):
|
||||
return []
|
||||
|
||||
var res: Array[OutgoingConnection] = []
|
||||
|
||||
res.assign(data[node_id].outgoing[from_port])
|
||||
|
@ -727,12 +809,32 @@ class NodeConnections:
|
|||
return res
|
||||
|
||||
|
||||
func get_node_pairs(node: String) -> Array[ConnectionPair]:
|
||||
var res: Array[ConnectionPair] = []
|
||||
|
||||
var connections: Dictionary = data.get(node, _create_empty_connection())
|
||||
for to_port: int in connections.outgoing:
|
||||
for outgoing: OutgoingConnection in connections.outgoing[to_port]:
|
||||
res.append(ConnectionPair.from_outgoing(outgoing))
|
||||
for from_port: int in connections.incoming:
|
||||
res.append(ConnectionPair.from_incoming(connections.incoming[from_port]))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
|
||||
func add_pair(pair: ConnectionPair) -> void:
|
||||
var outgoing := pair.outgoing
|
||||
var incoming := pair.incoming
|
||||
add_connection(incoming.from_node, outgoing.to_node, incoming.from_port, outgoing.to_port)
|
||||
|
||||
|
||||
func remove_pair(pair: ConnectionPair) -> void:
|
||||
var outgoing := pair.outgoing
|
||||
var incoming := pair.incoming
|
||||
remove_connection(incoming.from_node, outgoing.to_node, incoming.from_port, outgoing.to_port)
|
||||
|
||||
|
||||
func _create_empty_connection() -> Dictionary:
|
||||
return {
|
||||
"incoming": {},
|
||||
|
|
|
@ -9,6 +9,9 @@ class_name DeckHolder
|
|||
#static var decks: Array[Deck]
|
||||
static var decks: Dictionary # Dictionary[String -> id, (Deck|Dictionary[String -> instance_id, Deck])]
|
||||
|
||||
## List of library groups open this session.
|
||||
static var lib_groups: Dictionary # Dictionary[String -> path, Dictionary[String -> instance_id, Deck]]
|
||||
|
||||
static var groups_emitted: Array[String]
|
||||
|
||||
static var logger := Logger.new()
|
||||
|
@ -22,6 +25,8 @@ enum Compat {
|
|||
static func _static_init() -> void:
|
||||
signals.deck_added.connect(RPCSignalLayer._on_deck_added)
|
||||
signals.deck_closed.connect(RPCSignalLayer._on_deck_closed)
|
||||
|
||||
NodeDB.init()
|
||||
|
||||
|
||||
## Returns a new empty deck and assigns a new random ID to it.
|
||||
|
@ -32,6 +37,7 @@ static func add_empty_deck() -> Deck:
|
|||
deck.id = uuid
|
||||
deck.connect_rpc_signals()
|
||||
signals.deck_added.emit(uuid)
|
||||
print_verbose("DeckHolder: added empty deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
||||
return deck
|
||||
|
||||
|
||||
|
@ -74,6 +80,7 @@ static func open_deck_from_dict(data: Dictionary, path := "") -> Deck:
|
|||
decks[deck.id] = deck
|
||||
deck.connect_rpc_signals()
|
||||
signals.deck_added.emit(deck.id)
|
||||
print_verbose("DeckHolder: opened deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
||||
return deck
|
||||
|
||||
|
||||
|
@ -94,6 +101,7 @@ static func add_group_from_dict(data: Dictionary, deck_id: String, instance_id:
|
|||
signals.deck_added.emit(deck_id)
|
||||
groups_emitted.append(deck_id)
|
||||
#print(decks)
|
||||
print_verbose("DeckHolder: added group instance %s::%s, id %s" % [group.id, group.instance_id, group.get_instance_id()])
|
||||
return group
|
||||
|
||||
|
||||
|
@ -144,9 +152,42 @@ static func add_empty_group(parent: String = "") -> Deck:
|
|||
group.connect_rpc_signals()
|
||||
signals.deck_added.emit(group.id)
|
||||
groups_emitted.append(group.id)
|
||||
print_verbose("DeckHolder: added empty group %s::%s, id %s" % [group.id, group.instance_id, group.get_instance_id()])
|
||||
return group
|
||||
|
||||
|
||||
static func add_lib_instance(type: String, to_deck: String, instance_id: String = UUID.v4()) -> Deck:
|
||||
var nd: NodeDB.NodeDescriptor = NodeDB.libraries[type]
|
||||
var path := nd.script_path
|
||||
var deck_data: Dictionary
|
||||
if lib_groups.has(path):
|
||||
deck_data = (lib_groups[path].values()[0] as Deck).to_dict()
|
||||
else:
|
||||
if not NodeDB.libraries.has(type):
|
||||
return null
|
||||
|
||||
var f := FileAccess.open(path, FileAccess.READ)
|
||||
if not f:
|
||||
return null
|
||||
|
||||
deck_data = JSON.parse_string(f.get_as_text())
|
||||
|
||||
var instances := lib_groups.get(path, {}) as Dictionary
|
||||
|
||||
var deck := Deck.from_dict(deck_data)
|
||||
deck.id = path
|
||||
deck.instance_id = instance_id
|
||||
deck.is_group = true
|
||||
deck.is_library = true
|
||||
deck._belonging_to = to_deck
|
||||
|
||||
instances[instance_id] = deck
|
||||
lib_groups[path] = instances
|
||||
|
||||
print_verbose("DeckHolder: added lib group %s::%s, id %s" % [deck.id, deck.instance_id, deck.get_instance_id()])
|
||||
return deck
|
||||
|
||||
|
||||
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)
|
||||
|
@ -157,7 +198,7 @@ static func connect_group_signals(group: Deck) -> void:
|
|||
|
||||
static func get_deck(id: String) -> Deck:
|
||||
if not decks.has(id):
|
||||
return null
|
||||
return get_lib(id)
|
||||
|
||||
if not decks[id] is Dictionary:
|
||||
return decks[id]
|
||||
|
@ -165,9 +206,16 @@ static func get_deck(id: String) -> Deck:
|
|||
return (decks[id] as Dictionary).values()[0]
|
||||
|
||||
|
||||
static func get_lib(path: String) -> Deck:
|
||||
if not lib_groups.has(path):
|
||||
return null
|
||||
|
||||
return (lib_groups[path] as Dictionary).values()[0]
|
||||
|
||||
|
||||
static func get_group_instance(group_id: String, instance_id: String) -> Deck:
|
||||
if not decks.has(group_id):
|
||||
return null
|
||||
return get_lib_instance(group_id, instance_id)
|
||||
|
||||
if decks[group_id] is Dictionary:
|
||||
return (decks[group_id] as Dictionary).get(instance_id)
|
||||
|
@ -175,25 +223,41 @@ static func get_group_instance(group_id: String, instance_id: String) -> Deck:
|
|||
return null
|
||||
|
||||
|
||||
static func get_lib_instance(path: String, instance_id: String) -> Deck:
|
||||
if not lib_groups.has(path):
|
||||
return null
|
||||
|
||||
return (lib_groups[path] as Dictionary).get(instance_id, null)
|
||||
|
||||
|
||||
static func close_group_instance(group_id: String, instance_id: String) -> void:
|
||||
# this is kinda dumb, but to close groups that may be dangling
|
||||
# when all instances are closed, we have to get that list
|
||||
# *before* we close the instance
|
||||
var dangling_groups := get_deck(group_id).get_referenced_groups()
|
||||
|
||||
var group_instances: Dictionary = decks.get(group_id, {}) as Dictionary
|
||||
var from := lib_groups if group_id.is_absolute_path() else decks
|
||||
var group_instances: Dictionary = from.get(group_id, {}) as Dictionary
|
||||
var group_inst: Deck = group_instances[instance_id]
|
||||
print_verbose("DeckHolder: freeing group instance %s::%s, id %s" % [group_inst.id, group_inst.instance_id, group_inst.get_instance_id()])
|
||||
group_inst.pre_exit_cleanup()
|
||||
group_inst.free()
|
||||
group_instances.erase(instance_id)
|
||||
if group_instances.is_empty():
|
||||
for group in dangling_groups:
|
||||
close_all_group_instances(group)
|
||||
signals.deck_closed.emit(group_id)
|
||||
groups_emitted.erase(group_id)
|
||||
decks.erase(group_id)
|
||||
from.erase(group_id)
|
||||
|
||||
|
||||
static func close_all_group_instances(group_id: String) -> void:
|
||||
if decks.get(group_id) is Dictionary:
|
||||
decks.erase(group_id)
|
||||
var from := lib_groups if group_id.is_absolute_path() else decks
|
||||
if from.get(group_id) is Dictionary:
|
||||
for instance_id: String in from[group_id]:
|
||||
print_verbose("DeckHolder: freeing group instance %s::%s, id %s" % [group_id, instance_id, from[group_id][instance_id].get_instance_id()])
|
||||
from[group_id][instance_id].pre_exit_cleanup()
|
||||
from[group_id][instance_id].free()
|
||||
from.erase(group_id)
|
||||
|
||||
|
||||
## Unloads a deck. Returns a list of groups that are closed as a result of
|
||||
|
@ -206,10 +270,35 @@ static func close_deck(deck_id: String) -> Array:
|
|||
close_all_group_instances(group)
|
||||
signals.deck_closed.emit(deck_id)
|
||||
decks.erase(deck_id)
|
||||
print_verbose("DeckHolder: freeing deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
||||
deck.pre_exit_cleanup()
|
||||
deck.free()
|
||||
return groups
|
||||
return []
|
||||
|
||||
|
||||
static func pre_exit_cleanup() -> void:
|
||||
for deck_id: String in decks:
|
||||
if decks[deck_id] is Deck:
|
||||
var deck: Deck = decks[deck_id]
|
||||
print_verbose("DeckHolder: freeing deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
||||
deck.pre_exit_cleanup()
|
||||
deck.free()
|
||||
else:
|
||||
for instance_id: String in decks[deck_id]:
|
||||
var deck: Deck = decks[deck_id][instance_id]
|
||||
print_verbose("DeckHolder: freeing group %s::%s, id %s" % [deck_id, instance_id, deck.get_instance_id()])
|
||||
deck.pre_exit_cleanup()
|
||||
deck.free()
|
||||
|
||||
for lib_id: String in lib_groups:
|
||||
for instance_id: String in lib_groups[lib_id]:
|
||||
var deck: Deck = lib_groups[lib_id][instance_id]
|
||||
print_verbose("DeckHolder: freeing lib group %s::%s, id %s" % [lib_id, instance_id, deck.get_instance_id()])
|
||||
deck.pre_exit_cleanup()
|
||||
deck.free()
|
||||
|
||||
|
||||
static func send_event(event_name: StringName, event_data: Dictionary = {}) -> void:
|
||||
for deck_id: String in decks:
|
||||
if decks[deck_id] is Deck:
|
||||
|
@ -217,6 +306,10 @@ static func send_event(event_name: StringName, event_data: Dictionary = {}) -> v
|
|||
else:
|
||||
for deck_instance_id: String in decks[deck_id]:
|
||||
(decks[deck_id][deck_instance_id] as Deck).send_event(event_name, event_data)
|
||||
|
||||
for lib_group_id: String in lib_groups:
|
||||
for lib_instance_id: String in lib_groups[lib_group_id]:
|
||||
(lib_groups[lib_group_id][lib_instance_id] as Deck).send_event(event_name, event_data)
|
||||
|
||||
|
||||
#region group signal callbacks
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# (c) 2023-present Eroax
|
||||
# (c) 2023-present Yagich
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
extends Object
|
||||
class_name DeckNode
|
||||
## A node in a [Deck].
|
||||
##
|
||||
|
@ -10,14 +11,16 @@ class_name DeckNode
|
|||
## The name initially shown to a renderer.
|
||||
var name: String
|
||||
|
||||
|
||||
## A list of [Port]s on this node.
|
||||
var ports: Array[Port]
|
||||
|
||||
var _last_send_id: String
|
||||
|
||||
## The deck this node belongs to.
|
||||
## The [Deck] this node belongs to.
|
||||
var _belonging_to: Deck
|
||||
|
||||
## The instance ID of the group this node belongs to, if it belongs to a group.
|
||||
#var _belonging_to_instance: String
|
||||
## A unique identifier for this node.
|
||||
var _id: String
|
||||
## The type of this node, used for instantiation.
|
||||
|
@ -63,6 +66,8 @@ signal outgoing_connection_removed(from_port: int)
|
|||
signal incoming_connection_added(from_port: int)
|
||||
## Emitted when a connection to this node has been removed.
|
||||
signal incoming_connection_removed(from_port: int)
|
||||
## Emitted when the node is about to be freed.
|
||||
signal about_to_free()
|
||||
|
||||
signal port_value_updated(port_idx: int, new_value: Variant)
|
||||
|
||||
|
@ -233,12 +238,21 @@ func get_node(uuid: String) -> DeckNode:
|
|||
return _belonging_to.get_node(uuid)
|
||||
|
||||
|
||||
## Get the deck this node belongs to.
|
||||
#func get_deck() -> Deck:
|
||||
#if not DeckHolder.get_deck(_belonging_to).is_group:
|
||||
#return DeckHolder.get_deck(_belonging_to)
|
||||
#else:
|
||||
#return DeckHolder.get_group_instance(_belonging_to, _belonging_to_instance)
|
||||
|
||||
|
||||
## Virtual function that's called during deserialization before connections are loaded in.[br]
|
||||
func _pre_port_load() -> void:
|
||||
pass
|
||||
|
||||
|
||||
## Virtual function that's called after the node has been deserialized.
|
||||
@warning_ignore("unused_parameter")
|
||||
func _post_load(connections: Deck.NodeConnections) -> void:
|
||||
pass
|
||||
|
||||
|
@ -334,3 +348,8 @@ static func from_dict(data: Dictionary, connections: Deck.NodeConnections = null
|
|||
## Returns the node's [member position] as a [Vector2].
|
||||
func position_as_vector2() -> Vector2:
|
||||
return Vector2(position.x, position.y)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
about_to_free.emit()
|
||||
|
|
|
@ -16,6 +16,9 @@ enum LogCategory {
|
|||
RENDERER,
|
||||
}
|
||||
|
||||
# Dictionary["text": String, "type": LogType]
|
||||
var toast_history: Array[Dictionary]
|
||||
|
||||
signal log_message(text: String, type: LogType, category: LogCategory)
|
||||
signal log_toast(text: String, type: LogType)
|
||||
|
||||
|
@ -57,6 +60,11 @@ func toast_error(text: String) -> void:
|
|||
|
||||
func toast(text: String, type: LogType) -> void:
|
||||
log_toast.emit(text, type)
|
||||
|
||||
toast_history.append(
|
||||
{
|
||||
"text": text,
|
||||
"type": type,
|
||||
}
|
||||
)
|
||||
if OS.has_feature("editor"):
|
||||
prints("(t)", LogType.keys()[type].capitalize(), text)
|
||||
|
|
|
@ -24,13 +24,17 @@ static var favorite_nodes: Array[String]
|
|||
## The node index. Maps [member DeckNode.node_type]s to [NodeDB.NodeDescriptor].
|
||||
static var nodes: Dictionary = {}
|
||||
|
||||
static var libraries: Dictionary = {}
|
||||
|
||||
static func _static_init() -> void:
|
||||
|
||||
static func init() -> void:
|
||||
load_favorites()
|
||||
#if load_node_index():
|
||||
#return
|
||||
|
||||
create_descriptors(BASE_NODE_PATH)
|
||||
|
||||
reload_libraries()
|
||||
|
||||
save_node_index()
|
||||
|
||||
|
@ -38,32 +42,87 @@ static func _static_init() -> void:
|
|||
## Fills the [member nodes] index.
|
||||
static func create_descriptors(path: String) -> void:
|
||||
var dir := DirAccess.open(path)
|
||||
if not dir:
|
||||
return
|
||||
dir.list_dir_begin()
|
||||
var current_file := dir.get_next()
|
||||
while current_file != "":
|
||||
if dir.current_is_dir():
|
||||
create_descriptors(path.path_join(current_file))
|
||||
else:
|
||||
if current_file.ends_with(".gd"):
|
||||
var script_path := path.path_join(current_file)
|
||||
var node: DeckNode = load(script_path).new() as DeckNode
|
||||
var aliases: String = node.aliases.reduce(
|
||||
func(accum, el):
|
||||
return accum + el
|
||||
, "")
|
||||
var descriptor := NodeDescriptor.new(
|
||||
script_path,
|
||||
node.name,
|
||||
node.node_type,
|
||||
node.description,
|
||||
aliases,
|
||||
path.get_slice("/", path.get_slice_count("/") - 1),
|
||||
node.appears_in_search,
|
||||
)
|
||||
nodes[node.node_type] = descriptor
|
||||
elif current_file.ends_with(".gd"):
|
||||
var script_path := path.path_join(current_file)
|
||||
var node: DeckNode = load(script_path).new() as DeckNode
|
||||
var aliases: String = node.aliases.reduce(
|
||||
func(accum, el):
|
||||
return accum + el
|
||||
, "")
|
||||
var descriptor := NodeDescriptor.new(
|
||||
script_path,
|
||||
node.name,
|
||||
node.node_type,
|
||||
node.description,
|
||||
aliases,
|
||||
path.get_slice("/", path.get_slice_count("/") - 1),
|
||||
node.appears_in_search,
|
||||
false,
|
||||
)
|
||||
nodes[node.node_type] = descriptor
|
||||
print_verbose("NodeDB: freeing node %s, id %s" % [node.node_type, node.get_instance_id()])
|
||||
node.free()
|
||||
current_file = dir.get_next()
|
||||
|
||||
|
||||
## Fills the [member libraries] index.
|
||||
static func create_lib_descriptors(path: String) -> void:
|
||||
var dir := DirAccess.open(path)
|
||||
if not dir:
|
||||
return
|
||||
dir.list_dir_begin()
|
||||
var current_file := dir.get_next()
|
||||
while current_file != "":
|
||||
if dir.current_is_dir():
|
||||
create_lib_descriptors(path.path_join(current_file))
|
||||
elif current_file.ends_with(".deck"):
|
||||
var load_path := path.path_join(current_file)
|
||||
var f := FileAccess.open(load_path, FileAccess.READ)
|
||||
var deck: Dictionary = JSON.parse_string(f.get_as_text())
|
||||
if not deck.deck.has("library"):
|
||||
current_file = dir.get_next()
|
||||
continue
|
||||
var type := current_file.trim_suffix(".deck")
|
||||
if nodes.has(type):
|
||||
DeckHolder.logger.toast_error("Library group '%s' collides with a node with the same type." % type)
|
||||
current_file = dir.get_next()
|
||||
continue
|
||||
if libraries.has(type):
|
||||
DeckHolder.logger.toast_error("Library group '%s' collides with a library group with the same type." % type)
|
||||
current_file = dir.get_next()
|
||||
continue
|
||||
var lib = deck.deck.library
|
||||
var aliases: String = lib.lib_aliases.reduce(
|
||||
func(accum, el):
|
||||
return accum + el
|
||||
, "")
|
||||
var descriptor := NodeDescriptor.new(
|
||||
load_path,
|
||||
lib.lib_name,
|
||||
type,
|
||||
lib.lib_description,
|
||||
aliases,
|
||||
path.get_slice("/", path.get_slice_count("/") - 1),
|
||||
true,
|
||||
true,
|
||||
)
|
||||
libraries[descriptor.type] = descriptor
|
||||
current_file = dir.get_next()
|
||||
|
||||
|
||||
static func reload_libraries() -> void:
|
||||
libraries.clear()
|
||||
for path in StreamGraphConfig.get_library_search_paths():
|
||||
create_lib_descriptors(path)
|
||||
|
||||
|
||||
## Instantiates a [DeckNode] from a given [param node_type]. See [member DeckNode.node_type].
|
||||
static func instance_node(node_type: String) -> DeckNode:
|
||||
if not nodes.has(node_type):
|
||||
|
@ -137,6 +196,10 @@ static func is_node_favorite(node_type: String) -> bool:
|
|||
return node_type in favorite_nodes
|
||||
|
||||
|
||||
static func is_library(type: String) -> bool:
|
||||
return libraries.has(type)
|
||||
|
||||
|
||||
## Returns a capitalized category string.
|
||||
static func get_category_capitalization(category: String) -> String:
|
||||
return CATEGORY_CAPITALIZATION.get(category, category.capitalize())
|
||||
|
@ -157,6 +220,8 @@ class NodeDescriptor:
|
|||
var category: String
|
||||
## Whether this [DeckNode] reference will appear when searching. See [member DeckNode.appears_in_search].
|
||||
var appears_in_search: bool
|
||||
|
||||
var is_library: bool
|
||||
|
||||
## Stores the path to this node's script for later instantiation.
|
||||
var script_path: String
|
||||
|
@ -169,6 +234,7 @@ class NodeDescriptor:
|
|||
p_aliases: String,
|
||||
p_category: String,
|
||||
p_appears_in_search: bool,
|
||||
p_is_library: bool,
|
||||
) -> void:
|
||||
script_path = p_script_path
|
||||
|
||||
|
@ -178,6 +244,7 @@ class NodeDescriptor:
|
|||
aliases = p_aliases
|
||||
category = p_category
|
||||
appears_in_search = p_appears_in_search
|
||||
is_library = p_is_library
|
||||
|
||||
|
||||
## Returns a [Dictionary] representation of this node descriptor.
|
||||
|
@ -190,6 +257,7 @@ class NodeDescriptor:
|
|||
"script_path": script_path,
|
||||
"category": category,
|
||||
"appears_in_search": appears_in_search,
|
||||
"is_library": is_library,
|
||||
}
|
||||
return d
|
||||
|
||||
|
@ -204,6 +272,7 @@ class NodeDescriptor:
|
|||
data.get("aliases", ""),
|
||||
data.get("category", ""),
|
||||
data.get("appears_in_search", false),
|
||||
data.get("is_library", false)
|
||||
)
|
||||
|
||||
return nd
|
||||
|
|
|
@ -24,7 +24,6 @@ func _init() -> void:
|
|||
|
||||
|
||||
func _receive(to_input_port: int, data: Variant) -> void:
|
||||
print(_belonging_to.get_reference_count())
|
||||
var data_to_print := ""
|
||||
if to_input_port == 1:
|
||||
data = await resolve_input_port_value_async(0)
|
||||
|
|
|
@ -5,7 +5,10 @@ extends DeckNode
|
|||
|
||||
var output_count: int:
|
||||
get:
|
||||
return get_all_ports().size() - 1
|
||||
if output_count == 0:
|
||||
return get_all_ports().size()
|
||||
else:
|
||||
return output_count
|
||||
|
||||
var group_node: DeckNode
|
||||
|
||||
|
@ -14,8 +17,8 @@ func _init() -> void:
|
|||
name = "Group input"
|
||||
node_type = "group_input"
|
||||
props_to_serialize = [&"output_count"]
|
||||
appears_in_search = false
|
||||
user_can_delete = false
|
||||
# appears_in_search = false
|
||||
# user_can_delete = false
|
||||
|
||||
add_output_port(
|
||||
DeckType.Types.ANY,
|
||||
|
@ -52,11 +55,12 @@ func _on_outgoing_connection_removed(port_idx: int) -> void:
|
|||
|
||||
|
||||
func _pre_port_load() -> void:
|
||||
for i in output_count + 1:
|
||||
for i in output_count:
|
||||
add_output_port(
|
||||
DeckType.Types.ANY,
|
||||
"Input %s" % (i + 1)
|
||||
)
|
||||
output_count = 0
|
||||
|
||||
|
||||
func _post_load(connections: Deck.NodeConnections) -> void:
|
||||
|
@ -75,4 +79,7 @@ func _post_load(connections: Deck.NodeConnections) -> void:
|
|||
|
||||
|
||||
func _value_request(from_port: int) -> Variant:
|
||||
return await group_node.request_value_async(group_node.get_input_ports()[from_port].index_of_type)
|
||||
if group_node:
|
||||
return await group_node.request_value_async(group_node.get_input_ports()[from_port].index_of_type)
|
||||
else:
|
||||
return null
|
||||
|
|
|
@ -8,6 +8,8 @@ var group_instance_id: String
|
|||
var input_node: DeckNode
|
||||
var output_node: DeckNode
|
||||
|
||||
var is_library: bool
|
||||
|
||||
var input_node_id: String
|
||||
var output_node_id: String
|
||||
|
||||
|
@ -17,7 +19,7 @@ var extra_ports: Array
|
|||
func _init() -> void:
|
||||
name = "Group"
|
||||
node_type = "group_node"
|
||||
props_to_serialize = [&"group_id", &"group_instance_id", &"extra_ports", &"input_node_id", &"output_node_id"]
|
||||
props_to_serialize = [&"group_id", &"group_instance_id", &"extra_ports", &"input_node_id", &"output_node_id", &"is_library"]
|
||||
appears_in_search = false
|
||||
|
||||
|
||||
|
@ -41,8 +43,8 @@ func init_io() -> void:
|
|||
if output_node and output_node.ports_updated.is_connected(recalculate_ports):
|
||||
output_node.ports_updated.disconnect(recalculate_ports)
|
||||
|
||||
input_node = group.get_node(group.group_input_node)
|
||||
output_node = group.get_node(group.group_output_node)
|
||||
input_node = group.get_node(input_node_id)
|
||||
output_node = group.get_node(output_node_id)
|
||||
|
||||
recalculate_ports()
|
||||
setup_connections()
|
||||
|
@ -78,7 +80,7 @@ func recalculate_ports() -> void:
|
|||
|
||||
|
||||
func _receive(to_input_port: int, data: Variant):
|
||||
var i = DeckHolder.get_group_instance(group_id, group_instance_id).get_node(input_node_id)
|
||||
# var i = DeckHolder.get_group_instance(group_id, group_instance_id).get_node(input_node_id)
|
||||
#i.send(get_input_ports()[to_input_port].index_of_type, data)
|
||||
input_node.send(get_input_ports()[to_input_port].index_of_type, data)
|
||||
|
||||
|
|
|
@ -5,7 +5,10 @@ extends DeckNode
|
|||
|
||||
var input_count: int:
|
||||
get:
|
||||
return get_all_ports().size() - 1
|
||||
if input_count == 0:
|
||||
return get_all_ports().size()
|
||||
else:
|
||||
return input_count
|
||||
|
||||
var group_node: DeckNode
|
||||
|
||||
|
@ -14,8 +17,8 @@ func _init() -> void:
|
|||
name = "Group output"
|
||||
node_type = "group_output"
|
||||
props_to_serialize = [&"input_count"]
|
||||
appears_in_search = false
|
||||
user_can_delete = false
|
||||
# appears_in_search = false
|
||||
# user_can_delete = false
|
||||
|
||||
add_input_port(
|
||||
DeckType.Types.ANY,
|
||||
|
@ -53,11 +56,12 @@ func _on_incoming_connection_removed(port_idx: int) -> void:
|
|||
|
||||
|
||||
func _pre_port_load() -> void:
|
||||
for i in input_count + 1:
|
||||
for i in input_count:
|
||||
add_input_port(
|
||||
DeckType.Types.ANY,
|
||||
"Output %s" % (i + 1)
|
||||
)
|
||||
input_count = 0
|
||||
|
||||
|
||||
func _post_load(connections: Deck.NodeConnections) -> void:
|
||||
|
@ -76,4 +80,5 @@ func _post_load(connections: Deck.NodeConnections) -> void:
|
|||
|
||||
|
||||
func _receive(to_input_port: int, data: Variant) -> void:
|
||||
group_node.send(group_node.get_output_ports()[to_input_port].index_of_type, data)
|
||||
if group_node:
|
||||
group_node.send(group_node.get_output_ports()[to_input_port].index_of_type, data)
|
||||
|
|
|
@ -45,6 +45,18 @@ static var filters: Array[Filter] = [
|
|||
#prints("c:", c, "r:", search_string.replace(c, ""))
|
||||
return search_string.replace(c, "")
|
||||
),
|
||||
|
||||
# library filter. will only match library nodes. syntax: "#l"
|
||||
Filter.new(
|
||||
func(search_string: String) -> bool:
|
||||
return "#l" in search_string,
|
||||
|
||||
func(element: NodeDB.NodeDescriptor, _search_string: String, _pre_strip_string: String) -> bool:
|
||||
return element.is_library,
|
||||
|
||||
func(search_string: String) -> String:
|
||||
return search_string.replace("#l", "")
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -72,6 +84,16 @@ static func search(term: String) -> Array[NodeDB.NodeDescriptor]:
|
|||
if cleaned_search_string.is_subsequence_ofn(full_search_string):
|
||||
res.append(nd)
|
||||
|
||||
# same thing but libraries
|
||||
for node_type: String in NodeDB.libraries:
|
||||
var nd: NodeDB.NodeDescriptor = NodeDB.libraries[node_type]
|
||||
if not nd.appears_in_search:
|
||||
continue
|
||||
|
||||
var full_search_string := nd.name + nd.aliases
|
||||
if cleaned_search_string.is_subsequence_ofn(full_search_string):
|
||||
res.append(nd)
|
||||
|
||||
# no filters apply, just return the results straight
|
||||
if filters_to_apply.is_empty():
|
||||
return res
|
||||
|
|
110
classes/stream_graph_config.gd
Normal file
110
classes/stream_graph_config.gd
Normal file
|
@ -0,0 +1,110 @@
|
|||
# (c) 2023-present Eroax
|
||||
# (c) 2023-present Yagich
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
class_name StreamGraphConfig
|
||||
|
||||
|
||||
static var config := {
|
||||
&"library_search_paths": [
|
||||
ProjectSettings.globalize_path("user://library_groups"),
|
||||
],
|
||||
}
|
||||
|
||||
const SAVE_PATH := "user://config.json"
|
||||
|
||||
|
||||
static func _static_init() -> void:
|
||||
var f := FileAccess.open(SAVE_PATH, FileAccess.READ)
|
||||
if not f:
|
||||
return
|
||||
|
||||
var d = JSON.parse_string(f.get_as_text())
|
||||
config.merge(d, true)
|
||||
|
||||
|
||||
static func get_p(property: StringName) -> Variant:
|
||||
return config.get(property)
|
||||
|
||||
|
||||
static func has(property: StringName) -> bool:
|
||||
return config.has(property)
|
||||
|
||||
|
||||
static func set_p(property: StringName, value: Variant) -> void:
|
||||
config[property] = value
|
||||
save()
|
||||
|
||||
|
||||
static func get_dict() -> Dictionary:
|
||||
return config
|
||||
|
||||
|
||||
static func merge(with: Dictionary) -> void:
|
||||
config.merge(with, true)
|
||||
save()
|
||||
|
||||
|
||||
static func add_library_search_path(path: String) -> void:
|
||||
var arr: Array = config[&"library_search_paths"]
|
||||
arr.append(path)
|
||||
NodeDB.reload_libraries()
|
||||
save()
|
||||
|
||||
|
||||
static func remove_library_search_path(path: String) -> void:
|
||||
var arr: Array = config[&"library_search_paths"]
|
||||
if arr.find(path) < 1:
|
||||
return
|
||||
arr.erase(path)
|
||||
NodeDB.reload_libraries()
|
||||
save()
|
||||
|
||||
|
||||
static func rename_library_search_path(old_path: String, new_path: String) -> void:
|
||||
var arr: Array = config[&"library_search_paths"]
|
||||
var idx := arr.find(old_path)
|
||||
if idx < 1:
|
||||
return
|
||||
|
||||
arr[idx] = new_path
|
||||
NodeDB.reload_libraries()
|
||||
save()
|
||||
|
||||
|
||||
static func move_library_path_up(path: String) -> void:
|
||||
var arr: Array = config[&"library_search_paths"]
|
||||
var idx := arr.find(path)
|
||||
if idx < 1:
|
||||
return
|
||||
|
||||
var old_path = arr[idx]
|
||||
arr[idx] = arr[idx - 1]
|
||||
arr[idx - 1] = old_path
|
||||
NodeDB.reload_libraries()
|
||||
save()
|
||||
|
||||
|
||||
static func move_library_path_down(path: String) -> void:
|
||||
var arr: Array = config[&"library_search_paths"]
|
||||
var idx := arr.find(path)
|
||||
if idx < 1 or idx == arr.size() - 1:
|
||||
return
|
||||
|
||||
var old_path = arr[idx]
|
||||
arr[idx] = arr[idx + 1]
|
||||
arr[idx + 1] = old_path
|
||||
NodeDB.reload_libraries()
|
||||
save()
|
||||
|
||||
|
||||
static func get_library_search_paths() -> Array:
|
||||
return config[&"library_search_paths"]
|
||||
|
||||
|
||||
static func save() -> void:
|
||||
var f := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
|
||||
if not f:
|
||||
DeckHolder.logger.log_system("Could not open config file for writing", Logger.LogType.ERROR)
|
||||
return
|
||||
|
||||
f.store_string(JSON.stringify(config))
|
|
@ -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_connected(p_func) and not p_signal.is_null() and p_func.is_valid():
|
||||
if 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 p_signal.is_connected(p_func) and not p_signal.is_null() and p_func.is_valid():
|
||||
if not p_signal.is_null() and p_func.is_valid() and p_signal.is_connected(p_func):
|
||||
p_signal.disconnect(p_func)
|
||||
|
||||
|
||||
|
|
|
@ -40,17 +40,17 @@ 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:
|
||||
func add_category_item(category: String, item: String, tooltip: String = "", favorite: bool = false, library: bool = false) -> void:
|
||||
var c: Category = categories[category]
|
||||
c.add_item(item, tooltip, favorite)
|
||||
c.add_item(item, tooltip, favorite, library)
|
||||
|
||||
|
||||
## 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:
|
||||
func add_item(category: String, item: String, tooltip: String = "", favorite: bool = false, library: bool = false) -> void:
|
||||
if not categories.has(category):
|
||||
add_category(category)
|
||||
|
||||
add_category_item(category, item, tooltip, favorite)
|
||||
add_category_item(category, item, tooltip, favorite, library)
|
||||
|
||||
|
||||
## Get a [AddNodeMenu.Category] node by its' identifier.
|
||||
|
@ -74,7 +74,7 @@ func search(text: String) -> void:
|
|||
return
|
||||
|
||||
for nd in search_results:
|
||||
add_item(nd.category, nd.name, nd.description, NodeDB.is_node_favorite(nd.type))
|
||||
add_item(nd.category, nd.name, nd.description, NodeDB.is_node_favorite(nd.type), nd.is_library)
|
||||
var c := get_category(nd.category)
|
||||
c.set_item_metadata(c.get_item_count() - 1, "type", nd.type)
|
||||
c.set_item_metadata(c.get_item_count() - 1, "node_descriptor", weakref(nd))
|
||||
|
@ -231,8 +231,8 @@ class Category extends VBoxContainer:
|
|||
|
||||
|
||||
## 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)
|
||||
func add_item(p_name: String, p_tooltip: String, p_favorite: bool = false, p_library: bool = false) -> void:
|
||||
var item := CategoryItem.new(p_name, p_tooltip, p_favorite, p_library)
|
||||
item.favorite_toggled.connect(
|
||||
func(toggled: bool):
|
||||
item_favorite_button_toggled.emit(item.get_index(), toggled)
|
||||
|
@ -302,6 +302,7 @@ class Category extends VBoxContainer:
|
|||
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 GROUP_ICON = preload("res://graph_node_renderer/textures/group_icon.svg")
|
||||
const ITEM_MARGIN := 16
|
||||
|
||||
## The stylebox to use if this item is highlighted.
|
||||
|
@ -309,6 +310,7 @@ class CategoryItem extends HBoxContainer:
|
|||
|
||||
var is_highlighted: bool
|
||||
|
||||
var group_texture_rect: TextureRect
|
||||
var fav_button: Button
|
||||
var name_button: Button
|
||||
var panel: PanelContainer
|
||||
|
@ -319,7 +321,7 @@ class CategoryItem extends HBoxContainer:
|
|||
signal favorite_toggled(toggled: bool)
|
||||
|
||||
|
||||
func _init(p_name: String, p_tooltip: String, p_favorite: bool) -> void:
|
||||
func _init(p_name: String, p_tooltip: String, p_favorite: bool, p_library: bool) -> void:
|
||||
fav_button = Button.new()
|
||||
fav_button.icon = FAVORITE_ICON if p_favorite else NON_FAVORITE_ICON
|
||||
fav_button.toggle_mode = true
|
||||
|
@ -354,6 +356,18 @@ class CategoryItem extends HBoxContainer:
|
|||
var inner_hb := HBoxContainer.new()
|
||||
inner_hb.add_child(fav_button)
|
||||
inner_hb.add_child(name_button)
|
||||
|
||||
if p_library:
|
||||
group_texture_rect = TextureRect.new()
|
||||
group_texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
group_texture_rect.custom_minimum_size = Vector2(12, 12)
|
||||
group_texture_rect.texture = GROUP_ICON
|
||||
group_texture_rect.tooltip_text = "This is a library group. It will create a group node."
|
||||
inner_hb.add_child(group_texture_rect)
|
||||
var m := MarginContainer.new()
|
||||
m.add_theme_constant_override(&"margin_right", 6)
|
||||
inner_hb.add_child(m)
|
||||
|
||||
panel.add_child(inner_hb)
|
||||
|
||||
add_child(mc)
|
||||
|
|
|
@ -209,6 +209,9 @@ 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:
|
||||
return
|
||||
|
||||
Util.safe_disconnect(deck.variables_updated, bottom_dock.rebuild_variable_tree)
|
||||
|
||||
var deck_renderer := get_deck_renderer_at_tab(previous_tab)
|
||||
|
@ -219,21 +222,25 @@ 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:
|
||||
return
|
||||
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_AS, is_group)
|
||||
bottom_dock.rebuild_variable_tree(get_active_deck().variable_stack)
|
||||
var deck := get_active_deck()
|
||||
deck.variables_updated.connect(bottom_dock.rebuild_variable_tree.bind(deck.variable_stack))
|
||||
|
||||
sidebar.set_edited_deck(deck.id)
|
||||
get_active_deck_renderer().node_selected.connect(set_sidebar_node)
|
||||
get_active_deck_renderer().node_deselected.connect(set_sidebar_node)
|
||||
#get_active_deck_renderer().nodes_about_to_delete.connect(sidebar.set_edited_node)
|
||||
sidebar.refresh_node_list()
|
||||
|
||||
var batch := Util.batch_begin()
|
||||
batch.add(deck.node_added, refresh_sidebar_node_list)
|
||||
batch.add(deck.node_removed, refresh_sidebar_node_list)
|
||||
#batch.add(get_active_deck_renderer().nodes_about_to_delete, sidebar.set_edited_node)
|
||||
Util.push_batch(batch, &"sidebar_signals")
|
||||
|
||||
|
||||
|
@ -430,7 +437,11 @@ func get_active_deck() -> Deck:
|
|||
|
||||
## Returns the deck at [param tab] in the [member tab_container].
|
||||
func get_deck_at_tab(tab: int) -> Deck:
|
||||
return get_deck_renderer_at_tab(tab).deck
|
||||
var r := get_deck_renderer_at_tab(tab)
|
||||
if is_instance_valid(r.deck):
|
||||
return get_deck_renderer_at_tab(tab).deck
|
||||
else:
|
||||
return null
|
||||
|
||||
|
||||
## Returns the current deck renderer in the [member tab_container].
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
[ext_resource type="PackedScene" uid="uid://cvvkj138fg8jg" path="res://graph_node_renderer/unsaved_changes_dialog.tscn" id="9_4n0q6"]
|
||||
[ext_resource type="PackedScene" uid="uid://bu466w2w3q08c" path="res://graph_node_renderer/about_dialog.tscn" id="11_6ln7n"]
|
||||
[ext_resource type="PackedScene" uid="uid://brfrufvkjwcor" path="res://graph_node_renderer/rpc_setup_dialog.tscn" id="12_1xrfk"]
|
||||
[ext_resource type="PackedScene" uid="uid://dodqetbke5wji" path="res://graph_node_renderer/settings_dialog.tscn" id="16_rktri"]
|
||||
[ext_resource type="PackedScene" uid="uid://dodqetbke5wji" path="res://graph_node_renderer/settings/settings_dialog.tscn" id="16_rktri"]
|
||||
[ext_resource type="PackedScene" uid="uid://cd1t0gvi022gx" path="res://graph_node_renderer/compat_dialog.tscn" id="17_2ndnq"]
|
||||
|
||||
[node name="DeckHolderRenderer" type="Control"]
|
||||
|
|
|
@ -43,7 +43,9 @@ var is_group: bool = false
|
|||
|
||||
var change_dirty: bool = true
|
||||
|
||||
signal dirty_state_changed
|
||||
signal dirty_state_changed()
|
||||
signal nodes_about_to_delete()
|
||||
|
||||
|
||||
## Sets up the [member search_popup_panel] with an instance of [member ADD_NODE_SCENE]
|
||||
## stored in [member add_node_menu]. And sets its size of [member search_popup_panel] to
|
||||
|
@ -90,6 +92,9 @@ func attempt_connection(from_node_name: StringName, from_port: int, to_node_name
|
|||
|
||||
|
||||
func _is_node_hover_valid(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> bool:
|
||||
if deck.is_library:
|
||||
return false
|
||||
|
||||
var from_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(from_node))
|
||||
var to_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(to_node))
|
||||
|
||||
|
@ -99,6 +104,9 @@ func _is_node_hover_valid(from_node: StringName, from_port: int, to_node: String
|
|||
## Receives [signal GraphEdit.disconnection_request] and attempts to disconnect the two [DeckNode]s
|
||||
## involved, utilizes [NodeDB] for accessing them.
|
||||
func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void:
|
||||
if deck.is_library:
|
||||
return
|
||||
|
||||
var from_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(from_node_name))
|
||||
var to_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(to_node_name))
|
||||
|
||||
|
@ -146,7 +154,8 @@ func focus_selection() -> void:
|
|||
|
||||
## Updates [member Deck]s meta property "offset" whenever [member GraphEdit.scroll_offset]
|
||||
func _on_scroll_offset_changed(offset: Vector2) -> void:
|
||||
deck.set_meta("offset", offset)
|
||||
if is_instance_valid(deck):
|
||||
deck.set_meta("offset", offset)
|
||||
|
||||
|
||||
## Setups all the data from the set [member deck] in this [DeckRendererGraphEdit]
|
||||
|
@ -160,10 +169,19 @@ func initialize_from_deck() -> void:
|
|||
var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate()
|
||||
node_renderer.node = deck.get_node(node_id)
|
||||
node_renderer.name = node_id
|
||||
node_renderer.draggable = not deck.is_library
|
||||
add_child(node_renderer)
|
||||
node_renderer.position_offset = node_renderer.node.position_as_vector2()
|
||||
change_dirty = true
|
||||
dirty = false
|
||||
|
||||
right_disconnects = not deck.is_library
|
||||
|
||||
if deck.is_library:
|
||||
connection_drag_started.connect(
|
||||
func(from_node: StringName, from_port: int, is_output: bool):
|
||||
force_connection_drag_end()
|
||||
)
|
||||
|
||||
refresh_connections()
|
||||
|
||||
|
@ -201,14 +219,11 @@ func _on_deck_node_added(node: DeckNode) -> void:
|
|||
## Connected to [signal Deck.node_added], used to remove the specified
|
||||
## [DeckNodeRendererGraphNode] and queue_free it.
|
||||
func _on_deck_node_removed(node: DeckNode) -> void:
|
||||
for renderer: DeckNodeRendererGraphNode in get_children():
|
||||
if renderer.node != node:
|
||||
continue
|
||||
# TODO: when multiple nodes are removed and they are connected, the renderer will break
|
||||
# trying to get an invalid node. (GraphEdit, this is not on us.)
|
||||
# consider a batch removed signal for Deck or a separate signal for grouping nodes in 0.0.6.
|
||||
renderer.queue_free()
|
||||
break
|
||||
# TODO: when multiple nodes are removed and they are connected, the renderer will break
|
||||
# trying to get an invalid node. (GraphEdit, this is not on us.)
|
||||
# consider a batch removed signal for Deck or a separate signal for grouping nodes in 0.0.6.
|
||||
refresh_connections()
|
||||
get_node_renderer(node).queue_free()
|
||||
dirty = true
|
||||
|
||||
|
||||
|
@ -222,55 +237,57 @@ func get_selected_nodes() -> Array:
|
|||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if RendererShortcuts.check_shortcut("group_nodes", event) and get_selected_nodes().size() > 0:
|
||||
clear_connections()
|
||||
var nodes = get_selected_nodes().map(
|
||||
func(x: DeckNodeRendererGraphNode):
|
||||
return x.node
|
||||
)
|
||||
deck.group_nodes(nodes)
|
||||
refresh_connections()
|
||||
accept_event()
|
||||
|
||||
if RendererShortcuts.check_shortcut("add_node", event):
|
||||
var p := get_viewport_rect().position + get_global_mouse_position()
|
||||
p += Vector2(10, 10)
|
||||
var r := Rect2i(p, search_popup_panel.size)
|
||||
search_popup_panel.popup_on_parent(r)
|
||||
add_node_menu.focus_search_bar()
|
||||
popup_position = r.position
|
||||
accept_event()
|
||||
|
||||
if event.is_action_pressed("debug_make_unique") and get_selected_nodes().size() == 1:
|
||||
var node: DeckNode = get_selected_nodes().map(
|
||||
func(x: DeckNodeRendererGraphNode):
|
||||
return x.node
|
||||
)[0]
|
||||
if node.node_type != "group_node":
|
||||
return
|
||||
if not deck.is_library:
|
||||
if RendererShortcuts.check_shortcut("group_nodes", event) and get_selected_nodes().size() > 0:
|
||||
clear_connections()
|
||||
var nodes = get_selected_nodes().map(
|
||||
func(x: DeckNodeRendererGraphNode):
|
||||
return x.node
|
||||
)
|
||||
deck.group_nodes(nodes)
|
||||
refresh_connections()
|
||||
accept_event()
|
||||
|
||||
node.make_unique()
|
||||
if RendererShortcuts.check_shortcut("add_node", event):
|
||||
var p := get_viewport_rect().position + get_global_mouse_position()
|
||||
p += Vector2(10, 10)
|
||||
var r := Rect2i(p, search_popup_panel.size)
|
||||
search_popup_panel.popup_on_parent(r)
|
||||
add_node_menu.focus_search_bar()
|
||||
popup_position = r.position
|
||||
accept_event()
|
||||
|
||||
if event.is_action_pressed("debug_make_unique") and get_selected_nodes().size() == 1:
|
||||
var node: DeckNode = get_selected_nodes().map(
|
||||
func(x: DeckNodeRendererGraphNode):
|
||||
return x.node
|
||||
)[0]
|
||||
if node.node_type != "group_node":
|
||||
return
|
||||
|
||||
node.make_unique()
|
||||
|
||||
if RendererShortcuts.check_shortcut("rename_node", event) and get_selected_nodes().size() == 1:
|
||||
var node: DeckNodeRendererGraphNode = get_selected_nodes()[0]
|
||||
var pos := get_viewport_rect().position + get_global_mouse_position()
|
||||
rename_popup.popup_on_parent(Rect2i(pos, rename_popup_size))
|
||||
rename_popup.le.size.x = rename_popup_size.x
|
||||
rename_popup.set_text(node.title)
|
||||
accept_event()
|
||||
if RendererShortcuts.check_shortcut("rename_node", event) and get_selected_nodes().size() == 1:
|
||||
var node: DeckNodeRendererGraphNode = get_selected_nodes()[0]
|
||||
var pos := get_viewport_rect().position + get_global_mouse_position()
|
||||
rename_popup.popup_on_parent(Rect2i(pos, rename_popup_size))
|
||||
rename_popup.le.size.x = rename_popup_size.x
|
||||
rename_popup.set_text(node.title)
|
||||
accept_event()
|
||||
|
||||
if RendererShortcuts.check_shortcut("duplicate_nodes", event) and get_selected_nodes().size() > 0:
|
||||
_on_duplicate_nodes_request()
|
||||
accept_event()
|
||||
|
||||
if RendererShortcuts.check_shortcut("paste_nodes", event):
|
||||
_on_paste_nodes_request()
|
||||
accept_event()
|
||||
|
||||
# copy/paste/duplicate/etc
|
||||
if RendererShortcuts.check_shortcut("copy_nodes", event) and get_selected_nodes().size() > 0:
|
||||
_on_copy_nodes_request()
|
||||
accept_event()
|
||||
|
||||
if RendererShortcuts.check_shortcut("duplicate_nodes", event) and get_selected_nodes().size() > 0:
|
||||
_on_duplicate_nodes_request()
|
||||
accept_event()
|
||||
|
||||
if RendererShortcuts.check_shortcut("paste_nodes", event):
|
||||
_on_paste_nodes_request()
|
||||
accept_event()
|
||||
|
||||
if RendererShortcuts.check_shortcut("focus_nodes", event):
|
||||
focus_selection()
|
||||
|
@ -300,6 +317,8 @@ func _on_rename_popup_closed() -> void:
|
|||
|
||||
## Opens [member search_popup_panel] at the mouse position. Connected to [signal GraphEdit.popup_request]
|
||||
func _on_popup_request(p_popup_position: Vector2) -> void:
|
||||
if deck.is_library:
|
||||
return
|
||||
var p := get_viewport_rect().position + get_global_mouse_position()
|
||||
p += Vector2(10, 10)
|
||||
var r := Rect2i(p, search_popup_panel.size)
|
||||
|
@ -312,13 +331,19 @@ func _on_popup_request(p_popup_position: Vector2) -> void:
|
|||
## [method NodeDB.instance_node]. Then placing it at the [member scroll_offset] +
|
||||
## [member popup_position] / [member zoom]
|
||||
func _on_add_node_menu_node_selected(type: String) -> void:
|
||||
var node := NodeDB.instance_node(type) as DeckNode
|
||||
deck.add_node_inst(node)
|
||||
var node_pos := ((scroll_offset + popup_position) / zoom)
|
||||
if snapping_enabled:
|
||||
node_pos = node_pos.snapped(Vector2(snapping_distance, snapping_distance))
|
||||
if not NodeDB.is_library(type):
|
||||
# var node := NodeDB.instance_node(type) as DeckNode
|
||||
# deck.add_node_inst(node)
|
||||
var node := deck.add_node_type(type)
|
||||
|
||||
get_node_renderer(node).position_offset = node_pos
|
||||
get_node_renderer(node).position_offset = node_pos
|
||||
else:
|
||||
var node := deck.add_lib_group_node(type)
|
||||
|
||||
get_node_renderer(node).position_offset = node_pos
|
||||
|
||||
search_popup_panel.hide()
|
||||
|
||||
|
@ -347,6 +372,8 @@ func _on_delete_nodes_request(nodes: Array[StringName]) -> void:
|
|||
if node_ids.is_empty():
|
||||
return
|
||||
|
||||
nodes_about_to_delete.emit()
|
||||
|
||||
for node_id in node_ids:
|
||||
deck.remove_node(node_id, true)
|
||||
|
||||
|
|
|
@ -9,3 +9,4 @@ extends DescriptorContainer
|
|||
func _setup(port: Port, node: DeckNode) -> void:
|
||||
button.text = port.label
|
||||
button.pressed.connect(node.press_button.bind(port.index))
|
||||
button.disabled = node._belonging_to.is_library
|
||||
|
|
|
@ -6,7 +6,7 @@ extends DescriptorContainer
|
|||
@onready var check_box: CheckBox = %CheckBox
|
||||
|
||||
|
||||
func _setup(port: Port, _node: DeckNode) -> void:
|
||||
func _setup(port: Port, node: DeckNode) -> void:
|
||||
if descriptor.size() > 1:
|
||||
check_box.button_pressed = true
|
||||
if port.value is bool:
|
||||
|
@ -14,6 +14,7 @@ func _setup(port: Port, _node: DeckNode) -> void:
|
|||
check_box.text = port.label
|
||||
port.value_callback = check_box.is_pressed
|
||||
check_box.toggled.connect(port.set_value)
|
||||
check_box.disabled = node._belonging_to.is_library
|
||||
|
||||
|
||||
func set_value(new_value: Variant) -> void:
|
||||
|
|
|
@ -6,7 +6,7 @@ extends DescriptorContainer
|
|||
@onready var code_edit: CodeEdit = %CodeEdit
|
||||
|
||||
|
||||
func _setup(port: Port, _node: DeckNode) -> void:
|
||||
func _setup(port: Port, node: DeckNode) -> void:
|
||||
if port.value:
|
||||
code_edit.text = str(port.value)
|
||||
code_edit.placeholder_text = port.label
|
||||
|
@ -14,6 +14,7 @@ func _setup(port: Port, _node: DeckNode) -> void:
|
|||
code_edit.text_changed.connect(port.set_value.bind(code_edit.get_text))
|
||||
code_edit.custom_minimum_size = Vector2(200, 100)
|
||||
code_edit.size_flags_vertical = SIZE_EXPAND_FILL
|
||||
code_edit.editable = not node._belonging_to.is_library
|
||||
|
||||
|
||||
func set_value(new_value: Variant) -> void:
|
||||
|
|
|
@ -6,12 +6,13 @@ extends DescriptorContainer
|
|||
@onready var line_edit: LineEdit = %LineEdit
|
||||
|
||||
|
||||
func _setup(port: Port, _node: DeckNode) -> void:
|
||||
func _setup(port: Port, node: DeckNode) -> void:
|
||||
if port.value:
|
||||
line_edit.text = str(port.value)
|
||||
line_edit.placeholder_text = port.label
|
||||
port.value_callback = line_edit.get_text
|
||||
line_edit.text_changed.connect(port.set_value)
|
||||
line_edit.editable = not node._belonging_to.is_library
|
||||
|
||||
|
||||
func set_value(new_value: Variant) -> void:
|
||||
|
|
|
@ -6,7 +6,7 @@ extends DescriptorContainer
|
|||
@onready var box: OptionButton = %Box
|
||||
|
||||
|
||||
func _setup(port: Port, _node: DeckNode) -> void:
|
||||
func _setup(port: Port, node: DeckNode) -> void:
|
||||
if descriptor.slice(1).is_empty():
|
||||
if port.value:
|
||||
box.add_item(port.value)
|
||||
|
@ -27,7 +27,7 @@ func _setup(port: Port, _node: DeckNode) -> void:
|
|||
if box.get_item_text(i) == port.value:
|
||||
box.select(i)
|
||||
break
|
||||
|
||||
box.disabled = node._belonging_to.is_library
|
||||
|
||||
func set_value(new_value: Variant) -> void:
|
||||
if box.has_focus():
|
||||
|
|
|
@ -6,7 +6,7 @@ extends DescriptorContainer
|
|||
@onready var spin_box: SpinBox = %SpinBox
|
||||
|
||||
|
||||
func _setup(port: Port, _node: DeckNode) -> void:
|
||||
func _setup(port: Port, node: DeckNode) -> void:
|
||||
spin_box.tooltip_text = port.label
|
||||
if port.value != null:
|
||||
spin_box.value = float(port.value)
|
||||
|
@ -26,6 +26,7 @@ func _setup(port: Port, _node: DeckNode) -> void:
|
|||
spin_box.step = float(descriptor[4])
|
||||
port.value_callback = spin_box.get_value
|
||||
spin_box.value_changed.connect(port.set_value)
|
||||
spin_box.editable = not node._belonging_to.is_library
|
||||
|
||||
|
||||
func set_value(new_value: Variant) -> void:
|
||||
|
|
176
graph_node_renderer/settings/library_group_paths_editor.gd
Normal file
176
graph_node_renderer/settings/library_group_paths_editor.gd
Normal file
|
@ -0,0 +1,176 @@
|
|||
# (c) 2023-present Eroax
|
||||
# (c) 2023-present Yagich
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
extends PanelContainer
|
||||
|
||||
@onready var paths_container: VBoxContainer = %PathsContainer
|
||||
@onready var folder_dialog: FileDialog = %FolderDialog
|
||||
@onready var add_button: Button = %AddButton
|
||||
|
||||
var _edited: FolderView
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for i in StreamGraphConfig.get_library_search_paths().size():
|
||||
var path: String = StreamGraphConfig.get_library_search_paths()[i]
|
||||
add_folder_view(path, i == 0)
|
||||
|
||||
# calculate_move_buttons()
|
||||
folder_dialog.canceled.connect(_on_folder_dialog_canceled)
|
||||
|
||||
add_button.pressed.connect(
|
||||
func():
|
||||
folder_dialog.dir_selected.connect(_on_folder_dialog_dir_selected_new, CONNECT_ONE_SHOT)
|
||||
folder_dialog.popup_centered()
|
||||
)
|
||||
|
||||
|
||||
func calculate_move_buttons() -> void:
|
||||
await get_tree().process_frame
|
||||
for fv: FolderView in paths_container.get_children():
|
||||
fv.calculate_move_buttons()
|
||||
|
||||
|
||||
func _on_folder_dialog_dir_selected_new(path: String) -> void:
|
||||
add_folder_view(path)
|
||||
StreamGraphConfig.add_library_search_path(path)
|
||||
|
||||
|
||||
func add_folder_view(path: String, default: bool = false) -> void:
|
||||
var fv := FolderView.new(path, default)
|
||||
paths_container.add_child(fv)
|
||||
fv.open_dialog.connect(_on_folder_view_open_dialog.bind(fv))
|
||||
fv.deleted.connect(calculate_move_buttons)
|
||||
fv.moved.connect(calculate_move_buttons)
|
||||
calculate_move_buttons()
|
||||
|
||||
|
||||
func _on_folder_view_open_dialog(path: String, who: FolderView) -> void:
|
||||
folder_dialog.current_dir = path
|
||||
folder_dialog.dir_selected.connect(_on_folder_dialog_dir_selected, CONNECT_ONE_SHOT)
|
||||
_edited = who
|
||||
folder_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_folder_dialog_dir_selected(path: String) -> void:
|
||||
if not _edited:
|
||||
return
|
||||
|
||||
StreamGraphConfig.rename_library_search_path(_edited.path, path)
|
||||
_edited.set_path(path)
|
||||
_edited = null
|
||||
|
||||
|
||||
func _on_folder_dialog_canceled() -> void:
|
||||
Util.safe_disconnect(folder_dialog.dir_selected, _on_folder_dialog_dir_selected)
|
||||
Util.safe_disconnect(folder_dialog.dir_selected, _on_folder_dialog_dir_selected_new)
|
||||
_edited = null
|
||||
|
||||
|
||||
class FolderView extends HBoxContainer:
|
||||
const REMOVE_ICON := preload("res://graph_node_renderer/textures/remove-icon.svg")
|
||||
const LOAD_ICON := preload("res://graph_node_renderer/textures/load-icon.svg")
|
||||
|
||||
const ARROW_DOWN_ICON := preload("res://graph_node_renderer/textures/arrow-down-icon.svg")
|
||||
const ARROW_UP_ICON := preload("res://graph_node_renderer/textures/arrow-up-icon.svg")
|
||||
|
||||
var label: Label
|
||||
var delete_button: Button
|
||||
var open_path_button: Button
|
||||
|
||||
var move_down_button: Button
|
||||
var move_up_button: Button
|
||||
|
||||
var path: String
|
||||
var is_default: bool = false
|
||||
|
||||
signal open_dialog(path: String)
|
||||
signal deleted()
|
||||
signal moved()
|
||||
|
||||
|
||||
func _init(p_path: String = "", p_default: bool = false) -> void:
|
||||
path = p_path
|
||||
is_default = p_default
|
||||
|
||||
label = Label.new()
|
||||
label.text = path
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
label.clip_text = true
|
||||
label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
label.tooltip_text = path
|
||||
|
||||
delete_button = Button.new()
|
||||
delete_button.flat = true
|
||||
delete_button.icon = REMOVE_ICON
|
||||
delete_button.disabled = p_default
|
||||
delete_button.tooltip_text = "Delete"
|
||||
delete_button.pressed.connect(
|
||||
func():
|
||||
deleted.emit()
|
||||
StreamGraphConfig.remove_library_search_path(path)
|
||||
queue_free()
|
||||
)
|
||||
|
||||
open_path_button = Button.new()
|
||||
open_path_button.flat = true
|
||||
open_path_button.icon = LOAD_ICON
|
||||
open_path_button.disabled = p_default
|
||||
open_path_button.tooltip_text = "Select path"
|
||||
open_path_button.pressed.connect(
|
||||
func():
|
||||
open_dialog.emit(path)
|
||||
)
|
||||
|
||||
move_up_button = Button.new()
|
||||
move_up_button.flat = true
|
||||
move_up_button.icon = ARROW_UP_ICON
|
||||
move_up_button.tooltip_text = "Move up"
|
||||
move_up_button.pressed.connect(
|
||||
func():
|
||||
StreamGraphConfig.move_library_path_up(path)
|
||||
get_parent().move_child(self, get_index() - 1)
|
||||
moved.emit()
|
||||
)
|
||||
|
||||
move_down_button = Button.new()
|
||||
move_down_button.flat = true
|
||||
move_down_button.icon = ARROW_DOWN_ICON
|
||||
move_down_button.tooltip_text = "Move down"
|
||||
move_down_button.pressed.connect(
|
||||
func():
|
||||
StreamGraphConfig.move_library_path_down(path)
|
||||
get_parent().move_child(self, get_index() + 1)
|
||||
moved.emit()
|
||||
)
|
||||
|
||||
if p_default:
|
||||
label.tooltip_text += "\nThis is a default path. It cannot be changed or removed."
|
||||
delete_button.tooltip_text = "This is a default path. It cannot be changed or removed."
|
||||
move_up_button.tooltip_text = "This is a default path. It cannot be changed or removed."
|
||||
move_down_button.tooltip_text = "This is a default path. It cannot be changed or removed."
|
||||
open_path_button.tooltip_text = "This is a default path. It cannot be changed or removed."
|
||||
move_up_button.disabled = p_default
|
||||
move_down_button.disabled = p_default
|
||||
|
||||
add_child(label)
|
||||
add_child(delete_button)
|
||||
add_child(open_path_button)
|
||||
add_child(move_down_button)
|
||||
add_child(move_up_button)
|
||||
|
||||
|
||||
func set_path(p_path: String) -> void:
|
||||
path = p_path
|
||||
label.text = path
|
||||
|
||||
|
||||
func calculate_move_buttons() -> void:
|
||||
if is_default:
|
||||
move_up_button.disabled = true
|
||||
move_down_button.disabled = true
|
||||
return
|
||||
|
||||
move_up_button.disabled = get_index() == 1
|
||||
move_down_button.disabled = get_index() == get_parent().get_child_count() - 1
|
||||
|
60
graph_node_renderer/settings/library_group_paths_editor.tscn
Normal file
60
graph_node_renderer/settings/library_group_paths_editor.tscn
Normal file
|
@ -0,0 +1,60 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://dq6hrbd6ev4ls"]
|
||||
|
||||
[ext_resource type="Script" path="res://graph_node_renderer/settings/library_group_paths_editor.gd" id="1_swcyp"]
|
||||
[ext_resource type="Texture2D" uid="uid://drxi5ks3mqbnk" path="res://graph_node_renderer/textures/add_icon.svg" id="2_v1g1h"]
|
||||
|
||||
[node name="LibraryGroupPathsEditor" type="PanelContainer"]
|
||||
offset_right = 272.0
|
||||
offset_bottom = 179.0
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_swcyp")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 4
|
||||
theme_override_constants/margin_top = 4
|
||||
theme_override_constants/margin_right = 4
|
||||
theme_override_constants/margin_bottom = 4
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/PanelContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/PanelContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="PathsContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/PanelContainer/MarginContainer/ScrollContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AddButton" type="Button" parent="MarginContainer/VBoxContainer/CenterContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Add"
|
||||
icon = ExtResource("2_v1g1h")
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="FolderDialog" type="FileDialog" parent="."]
|
||||
unique_name_in_owner = true
|
||||
title = "Open a Directory"
|
||||
initial_position = 2
|
||||
size = Vector2i(760, 500)
|
||||
ok_button_text = "Select Current Folder"
|
||||
file_mode = 2
|
||||
access = 2
|
|
@ -1,7 +1,8 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://dodqetbke5wji"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dodqetbke5wji"]
|
||||
|
||||
[ext_resource type="Script" path="res://graph_node_renderer/settings_dialog.gd" id="1_lh25g"]
|
||||
[ext_resource type="PackedScene" uid="uid://bvjxc2vyx35b1" path="res://graph_node_renderer/shortcuts/shortcuts_editor.tscn" id="2_5tyfb"]
|
||||
[ext_resource type="Script" path="res://graph_node_renderer/settings/settings_dialog.gd" id="1_l08va"]
|
||||
[ext_resource type="PackedScene" uid="uid://dq6hrbd6ev4ls" path="res://graph_node_renderer/settings/library_group_paths_editor.tscn" id="2_f0uh5"]
|
||||
[ext_resource type="PackedScene" uid="uid://bvjxc2vyx35b1" path="res://graph_node_renderer/shortcuts/shortcuts_editor.tscn" id="2_pet1f"]
|
||||
|
||||
[node name="SettingsDialog" type="AcceptDialog"]
|
||||
title = "Settings"
|
||||
|
@ -10,13 +11,13 @@ size = Vector2i(705, 370)
|
|||
min_size = Vector2i(500, 250)
|
||||
ok_button_text = "Close"
|
||||
dialog_close_on_escape = false
|
||||
script = ExtResource("1_lh25g")
|
||||
script = ExtResource("1_l08va")
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="."]
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 697.0
|
||||
offset_bottom = 321.0
|
||||
offset_bottom = 324.0
|
||||
|
||||
[node name="CategoryTree" type="Tree" parent="HSplitContainer"]
|
||||
unique_name_in_owner = true
|
||||
|
@ -29,22 +30,26 @@ unique_name_in_owner = true
|
|||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="General" type="VBoxContainer" parent="HSplitContainer/CategoryContent"]
|
||||
visible = false
|
||||
[node name="General" type="ScrollContainer" parent="HSplitContainer/CategoryContent"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="Label" type="Label" parent="HSplitContainer/CategoryContent/General"]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer/CategoryContent/General"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="HSplitContainer/CategoryContent/General/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Library Group Search Paths"
|
||||
|
||||
[node name="LibraryGroupPaths" type="PanelContainer" parent="HSplitContainer/CategoryContent/General"]
|
||||
[node name="LibraryGroupPathsEditor" parent="HSplitContainer/CategoryContent/General/VBoxContainer" instance=ExtResource("2_f0uh5")]
|
||||
custom_minimum_size = Vector2(0, 290)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Shortcuts" type="VBoxContainer" parent="HSplitContainer/CategoryContent"]
|
||||
visible = false
|
||||
|
@ -54,6 +59,7 @@ anchor_right = 1.0
|
|||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="HSplitContainer/CategoryContent/Shortcuts"]
|
||||
layout_mode = 2
|
||||
|
@ -76,7 +82,7 @@ theme_override_constants/margin_top = 4
|
|||
theme_override_constants/margin_right = 4
|
||||
theme_override_constants/margin_bottom = 4
|
||||
|
||||
[node name="ShortcutsEditor" parent="HSplitContainer/CategoryContent/Shortcuts/PanelContainer/ScrollContainer/MarginContainer" instance=ExtResource("2_5tyfb")]
|
||||
[node name="ShortcutsEditor" parent="HSplitContainer/CategoryContent/Shortcuts/PanelContainer/ScrollContainer/MarginContainer" instance=ExtResource("2_pet1f")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
|
@ -107,6 +107,16 @@ class ShortcutButton extends Button:
|
|||
func _init(p_event: InputEvent) -> void:
|
||||
toggle_mode = true
|
||||
set_event(p_event)
|
||||
button_down.connect(
|
||||
func():
|
||||
text += "..."
|
||||
)
|
||||
|
||||
toggled.connect(
|
||||
func(toggled_on: bool):
|
||||
if not toggled_on:
|
||||
text = _event.as_text()
|
||||
)
|
||||
|
||||
|
||||
## Sets the event this button is storing and updates its text.
|
||||
|
|
|
@ -34,6 +34,11 @@ const COLLAPSE_ICON = preload("res://graph_node_renderer/textures/collapse-icon.
|
|||
const COLLAPSE_ICON_COLLAPSED = preload("res://graph_node_renderer/textures/collapse-icon-collapsed.svg")
|
||||
const BASE_MARGIN := 12
|
||||
var _indent_level := 1
|
||||
var _ignore_child_lines: Array[int]
|
||||
var _title: String
|
||||
|
||||
var _renamed_lambda = func():
|
||||
_set_title(name)
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
|
@ -43,10 +48,10 @@ func _enter_tree() -> void:
|
|||
_collapse_button = CollapseButton.new(collapsed, name)
|
||||
_collapse_button.toggled.connect(set_collapsed)
|
||||
|
||||
renamed.connect(
|
||||
func():
|
||||
_collapse_button.text = name
|
||||
)
|
||||
if _title.is_empty():
|
||||
renamed.connect(_renamed_lambda)
|
||||
else:
|
||||
_collapse_button.text = _title
|
||||
|
||||
add_child(_collapse_button, false, Node.INTERNAL_MODE_FRONT)
|
||||
|
||||
|
@ -55,6 +60,20 @@ func _exit_tree() -> void:
|
|||
_collapse_button.queue_free()
|
||||
|
||||
|
||||
func _set_title(title: String) -> void:
|
||||
_title = title
|
||||
|
||||
if _collapse_button:
|
||||
_collapse_button.text = title
|
||||
|
||||
|
||||
func set_title(title: String) -> void:
|
||||
if renamed.is_connected(_renamed_lambda):
|
||||
renamed.disconnect(_renamed_lambda)
|
||||
|
||||
_set_title(title)
|
||||
|
||||
|
||||
func set_collapsed(v: bool) -> void:
|
||||
collapsed = v
|
||||
if _collapse_button:
|
||||
|
@ -88,6 +107,16 @@ func uncollapse() -> void:
|
|||
update_minimum_size()
|
||||
|
||||
|
||||
func set_ignore_child_lines(idx: int, ignore: bool = true) -> void:
|
||||
if idx not in _ignore_child_lines and ignore:
|
||||
_ignore_child_lines.append(idx)
|
||||
return
|
||||
|
||||
if idx in _ignore_child_lines and not ignore:
|
||||
_ignore_child_lines.erase(idx)
|
||||
return
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PRE_SORT_CHILDREN:
|
||||
for i in get_children(false):
|
||||
|
@ -122,6 +151,9 @@ func _draw() -> void:
|
|||
|
||||
# draw lines going out to each child
|
||||
for child in get_children(false):
|
||||
# skip children marked as ignored
|
||||
if child.get_index(false) in _ignore_child_lines:
|
||||
continue
|
||||
# special case for if the child is also an accordion:
|
||||
# draw the line pointing to the header
|
||||
if child is AccordionMenu:
|
||||
|
|
|
@ -98,17 +98,20 @@ func set_edited_node(id: String = "") -> void:
|
|||
edited_node_id = id
|
||||
|
||||
for i in node_menu.get_children():
|
||||
i.queue_free()
|
||||
#i.queue_free()
|
||||
i.free()
|
||||
|
||||
if id.is_empty():
|
||||
var label := Label.new()
|
||||
label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
label.text = "There is no Node selected (or multiple are selected). Select a single Node to edit its properties."
|
||||
node_menu.add_child(label)
|
||||
node_menu.draw_tree = false
|
||||
node_inspector = null
|
||||
return
|
||||
|
||||
await get_tree().process_frame
|
||||
#await get_tree().process_frame
|
||||
node_menu.draw_tree = true
|
||||
|
||||
node_inspector = NodeInspector.new(edited_deck_id, id)
|
||||
node_inspector.go_to_node_requested.connect(
|
||||
|
@ -118,11 +121,16 @@ func set_edited_node(id: String = "") -> void:
|
|||
for i in node_inspector.nodes:
|
||||
node_menu.add_child(i)
|
||||
|
||||
DeckHolder.get_deck(edited_deck_id).node_removed.connect(
|
||||
func(node: DeckNode):
|
||||
if node._id == edited_node_id:
|
||||
set_edited_node()
|
||||
DeckHolder.get_deck(edited_deck_id).get_node(id).about_to_free.connect(
|
||||
func():
|
||||
set_edited_node()
|
||||
)
|
||||
|
||||
#DeckHolder.get_deck(edited_deck_id).node_removed.connect(
|
||||
#func(node: DeckNode):
|
||||
#if node._id == edited_node_id:
|
||||
#set_edited_node()
|
||||
#)
|
||||
|
||||
|
||||
class Inspector extends HBoxContainer:
|
||||
|
@ -144,19 +152,78 @@ class DeckInspector:
|
|||
|
||||
func add_inspector(text: String, control: Control = null) -> void:
|
||||
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 create_hb_label(text: String, control: Control) -> HBoxContainer:
|
||||
var hb := HBoxContainer.new()
|
||||
|
||||
control.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
hb.add_child(create_label(text))
|
||||
hb.add_child(control)
|
||||
|
||||
return hb
|
||||
|
||||
|
||||
func _init(id: String) -> void:
|
||||
ref = weakref(DeckHolder.get_deck(id))
|
||||
var deck: Deck = ref.get_ref() as Deck
|
||||
if deck.is_group:
|
||||
add_inspector("This deck is a group.")
|
||||
|
||||
var lib_menu := AccordionMenu.new()
|
||||
lib_menu.set_title("Library Group")
|
||||
|
||||
var lib_group_text: String
|
||||
if deck.is_library:
|
||||
lib_group_text = "This deck is open as a library group. You may not edit it here.\nTo edit it, you have to open the file it's in."
|
||||
elif not deck.is_group:
|
||||
lib_group_text = "You may save this deck as a Library Group to reuse it in future decks.\nYou may edit how it will appear."
|
||||
else:
|
||||
lib_group_text = "This deck is a group."
|
||||
|
||||
var l := create_label(lib_group_text)
|
||||
l.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
l.custom_minimum_size.x = 40
|
||||
lib_menu.add_child(l)
|
||||
lib_menu.set_ignore_child_lines(0)
|
||||
|
||||
var name_field := LineEdit.new()
|
||||
name_field.placeholder_text = "Name"
|
||||
name_field.text = deck.lib_name
|
||||
name_field.text_changed.connect(
|
||||
func(new_text: String):
|
||||
deck.lib_name = new_text
|
||||
)
|
||||
name_field.editable = not deck.is_group
|
||||
lib_menu.add_child(create_hb_label("Library name:", name_field))
|
||||
|
||||
var desc_field := TextEdit.new()
|
||||
desc_field.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY
|
||||
desc_field.placeholder_text = "Description"
|
||||
desc_field.text = deck.lib_description
|
||||
desc_field.text_changed.connect(
|
||||
func():
|
||||
deck.lib_description = desc_field.text
|
||||
)
|
||||
desc_field.editable = not deck.is_group
|
||||
desc_field.custom_minimum_size.y = 100
|
||||
desc_field.custom_minimum_size.x = 100
|
||||
lib_menu.add_child(create_hb_label("Library description:", desc_field))
|
||||
|
||||
nodes.append(lib_menu)
|
||||
|
||||
|
||||
class NodeInspector:
|
||||
var ref: WeakRef
|
||||
var nodes: Array[Control]
|
||||
|
||||
var _name_field: LineEdit
|
||||
|
||||
signal go_to_node_requested()
|
||||
|
||||
var DESCRIPTOR_SCENES := {
|
||||
|
@ -181,6 +248,13 @@ class NodeInspector:
|
|||
return l
|
||||
|
||||
|
||||
func _name_field_rename(new_name: String) -> void:
|
||||
if _name_field.has_focus():
|
||||
return
|
||||
|
||||
_name_field.text = new_name
|
||||
|
||||
|
||||
func _init(deck_id: String, id: String) -> void:
|
||||
ref = weakref(DeckHolder.get_deck(deck_id).get_node(id))
|
||||
var node: DeckNode = ref.get_ref() as DeckNode
|
||||
|
@ -201,18 +275,15 @@ class NodeInspector:
|
|||
hb.add_child(copy_button)
|
||||
add_inspector("Node Type:", hb)
|
||||
|
||||
var name_field := LineEdit.new()
|
||||
name_field.placeholder_text = "Node name"
|
||||
name_field.text = node.name
|
||||
name_field.text_changed.connect(node.rename)
|
||||
node.renamed.connect(
|
||||
func(new_name: String) -> void:
|
||||
if name_field.has_focus():
|
||||
return
|
||||
|
||||
name_field.text = new_name
|
||||
)
|
||||
add_inspector("Node name:", name_field)
|
||||
_name_field = LineEdit.new()
|
||||
_name_field.placeholder_text = "Node name"
|
||||
_name_field.text = node.name
|
||||
_name_field.text_changed.connect(node.rename)
|
||||
_name_field.editable = not node._belonging_to.is_library
|
||||
|
||||
node.renamed.connect(_name_field_rename)
|
||||
|
||||
add_inspector("Node name:", _name_field)
|
||||
|
||||
var focus_button := Button.new()
|
||||
focus_button.text = "Focus node"
|
||||
|
@ -233,11 +304,11 @@ class NodeInspector:
|
|||
var ports_menu := AccordionMenu.new()
|
||||
ports_menu.draw_background = true
|
||||
var is_output := ports[0].port_type == DeckNode.PortType.OUTPUT
|
||||
ports_menu.set_name.call_deferred("Output Ports" if is_output else "Input Ports")
|
||||
ports_menu.set_title("Output Ports" if is_output else "Input Ports")
|
||||
for port in ports:
|
||||
var acc := AccordionMenu.new()
|
||||
acc.draw_background = true
|
||||
acc.name = "Port %s" % port.index_of_type
|
||||
acc.set_title("Port %s" % port.index_of_type)
|
||||
|
||||
var label_label := create_label("Name: %s" % port.label)
|
||||
acc.add_child(label_label)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
[ext_resource type="Script" path="res://graph_node_renderer/sidebar/accordion_menu.gd" id="1_q1gqb"]
|
||||
|
||||
[node name="Sidebar" type="PanelContainer"]
|
||||
custom_minimum_size = Vector2(340, 0)
|
||||
offset_right = 439.0
|
||||
offset_bottom = 266.0
|
||||
script = ExtResource("1_bcym7")
|
||||
|
|
1
graph_node_renderer/textures/arrow-down-icon.svg
Normal file
1
graph_node_renderer/textures/arrow-down-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M8 5v7l4-4m-4 4L4 8" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="#e0e0e0"/></svg>
|
After Width: | Height: | Size: 214 B |
37
graph_node_renderer/textures/arrow-down-icon.svg.import
Normal file
37
graph_node_renderer/textures/arrow-down-icon.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cl32inr5ja2kd"
|
||||
path="res://.godot/imported/arrow-down-icon.svg-744ecac080abb04bb3e26e5dc62154de.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://graph_node_renderer/textures/arrow-down-icon.svg"
|
||||
dest_files=["res://.godot/imported/arrow-down-icon.svg-744ecac080abb04bb3e26e5dc62154de.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
graph_node_renderer/textures/arrow-up-icon.svg
Normal file
1
graph_node_renderer/textures/arrow-up-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M8 11V4L4 8m4-4 4 4" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="#e0e0e0"/></svg>
|
After Width: | Height: | Size: 214 B |
37
graph_node_renderer/textures/arrow-up-icon.svg.import
Normal file
37
graph_node_renderer/textures/arrow-up-icon.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bepbho6of3lad"
|
||||
path="res://.godot/imported/arrow-up-icon.svg-e0c45dce0c11c3066f4517489a55f98f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://graph_node_renderer/textures/arrow-up-icon.svg"
|
||||
dest_files=["res://.godot/imported/arrow-up-icon.svg-e0c45dce0c11c3066f4517489a55f98f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
graph_node_renderer/textures/group_icon.svg
Normal file
1
graph_node_renderer/textures/group_icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M7 1v6H1v8h8V9h6V1zm2 2h4v4H9z" fill="#e0e0e0" fill-opacity=".4"/><path d="M1 1v2h2V1H1zm12 0v2h2V1h-2zM1 13v2h2v-2H1zm12 0v2h2v-2h-2z" fill="#fff"/></svg>
|
After Width: | Height: | Size: 248 B |
37
graph_node_renderer/textures/group_icon.svg.import
Normal file
37
graph_node_renderer/textures/group_icon.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ysy33f5r2ji3"
|
||||
path="res://.godot/imported/group_icon.svg-553babd1069b83fbb64a3e7775cbe41e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://graph_node_renderer/textures/group_icon.svg"
|
||||
dest_files=["res://.godot/imported/group_icon.svg-553babd1069b83fbb64a3e7775cbe41e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
graph_node_renderer/textures/load-icon.svg
Normal file
1
graph_node_renderer/textures/load-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M3 2a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9c1.105 0 1.818-.91 2-2l1-6a1 1 0 0 0-1-1H6c-.552 0-.909.455-1 1l-1 6c-.091.545-.448 1-1 1a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1 1 1 0 0 0 1 1h5a1 1 0 0 0-1-1H7a2 2 0 0 0-2-2H3z" fill="#e0e0e0"/></svg>
|
After Width: | Height: | Size: 337 B |
37
graph_node_renderer/textures/load-icon.svg.import
Normal file
37
graph_node_renderer/textures/load-icon.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bbyje126vhdbe"
|
||||
path="res://.godot/imported/load-icon.svg-fefb135ef5d746efaeb8c59f181e864e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://graph_node_renderer/textures/load-icon.svg"
|
||||
dest_files=["res://.godot/imported/load-icon.svg-fefb135ef5d746efaeb8c59f181e864e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
|
@ -16,15 +16,21 @@ const INFO_ICON = preload("res://graph_node_renderer/textures/info_icon.svg")
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
for i in DeckHolder.logger.toast_history.duplicate():
|
||||
_on_logger_toast(i.text, i.type)
|
||||
DeckHolder.logger.log_toast.connect(_on_logger_toast)
|
||||
|
||||
|
||||
func _on_logger_toast(text: String, type: Logger.LogType) -> void:
|
||||
DeckHolder.logger.toast_history.remove_at(0)
|
||||
var panel := PanelContainer.new()
|
||||
panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
panel.theme_type_variation = &"ToastPanel"
|
||||
var hb := HBoxContainer.new()
|
||||
hb.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var icon := TextureRect.new()
|
||||
icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
|
||||
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
match type:
|
||||
Logger.LogType.INFO:
|
||||
|
@ -36,6 +42,7 @@ func _on_logger_toast(text: String, type: Logger.LogType) -> void:
|
|||
|
||||
var label := Label.new()
|
||||
label.text = text
|
||||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
hb.add_child(icon)
|
||||
hb.add_child(label)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
[node name="ToastRenderer" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1_446pb")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
|
@ -21,7 +22,5 @@ offset_right = -32.0
|
|||
offset_bottom = -32.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
mouse_filter = 2
|
||||
alignment = 2
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
|
1
main.gd
1
main.gd
|
@ -27,6 +27,7 @@ func _on_deck_holder_renderer_quit_completed() -> void:
|
|||
# will be used later to make sure both default and rpc are finished processing
|
||||
deck_holder_renderer_finished = true
|
||||
|
||||
DeckHolder.pre_exit_cleanup()
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue