diff --git a/classes/deck/deck.gd b/classes/deck/deck.gd index 7748d5e..5097e6c 100644 --- a/classes/deck/deck.gd +++ b/classes/deck/deck.gd @@ -22,21 +22,24 @@ static var type_assoc: Dictionary = { } var variable_stack: Dictionary = {} +var save_path: String = "" + +var is_group: bool = false +var groups: Dictionary = {} #Dictionary[String -> Deck.id, Deck] +var id: String = "" +var _belonging_to: Deck # for groups +var group_input_node: String +var group_output_node: String +var group_node: String + +signal node_added(node: DeckNode) +signal node_removed(node: DeckNode) -func add_node(node: GDScript, meta: Dictionary = {}) -> DeckNode: - # TODO: accept instances of DeckNode instead of instancing here? - var uuid := UUID.v4() - var node_inst: DeckNode = node.new() as DeckNode - nodes[uuid] = node_inst - node_inst._belonging_to = self - node_inst._id = uuid +func add_node_type(type: String, assign_id: String = "", assign_to_self: bool = true) -> DeckNode: + var node_inst: DeckNode = NodeDB.instance_node(type) - if !meta.is_empty(): - for k in meta: - node_inst.set_meta(k, meta[k]) - - return node_inst + return add_node_inst(node_inst, assign_id, assign_to_self) func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool = true) -> DeckNode: @@ -50,10 +53,7 @@ func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool else: nodes[assign_id] = node - -# if !meta.is_empty(): -# for k in meta: -# node.set_meta(k, meta[k]) + node_added.emit(node) return node @@ -62,10 +62,10 @@ func get_node(uuid: String) -> DeckNode: return nodes.get(uuid) -func connect_nodes(from_node: DeckNode, to_node: DeckNode, from_port: int, to_port: int) -> bool: +func connect_nodes(from_node: DeckNode, to_node: DeckNode, from_output_port: int, to_input_port: int) -> bool: # first, check that we can do the type conversion. - var type_a: Types = from_node.ports[from_port].type - var type_b: Types = to_node.ports[to_port].type + var type_a: Types = from_node.get_output_ports()[from_output_port].type + var type_b: Types = to_node.get_input_ports()[to_input_port].type var err: DeckType = (type_assoc[type_b]).from(type_assoc[type_a].new()) if err is DeckType.DeckTypeError: print(err.error_message) @@ -73,32 +73,106 @@ func connect_nodes(from_node: DeckNode, to_node: DeckNode, from_port: int, to_po # TODO: prevent duplicate connections - from_node.add_outgoing_connection(from_port, to_node._id, to_port) + from_node.add_outgoing_connection(from_output_port, to_node._id, to_input_port) return true -func disconnect_nodes(from_node: DeckNode, to_node: DeckNode, from_port: int, to_port: int) -> void: - var hash = {to_node._id: to_port}.hash() -# prints({to_node._id: to_port}, "-", {to_node._id: to_port}.hash()) -# prints(from_node.outgoing_connections[0][0], "-", from_node.outgoing_connections[0][0].hash()) -# -# var a = {to_node._id: to_port} -# var b = from_node.outgoing_connections[0][0] -# -# print(typeof(a.values()[0])) -# print(typeof(b.values()[0])) -# -# prints(a, b) +func disconnect_nodes(from_node: DeckNode, to_node: DeckNode, from_output_port: int, to_input_port: int) -> void: + var hash = {to_node._id: to_input_port}.hash() - from_node.remove_outgoing_connection(from_port, hash) - to_node.remove_incoming_connection(to_port) + from_node.remove_outgoing_connection(from_output_port, hash) + to_node.remove_incoming_connection(to_input_port) -func to_json(with_meta: bool = true) -> String: - var inner := {"nodes": {}, "variable_stack": variable_stack} +func is_empty() -> bool: + return nodes.is_empty() && variable_stack.is_empty() - for id in nodes.keys(): - inner["nodes"][id] = nodes[id].to_dict(with_meta) + +func remove_node(uuid: String) -> void: + var node = nodes.get(uuid) + nodes.erase(uuid) + + node_removed.emit(node) + + +func group_nodes(nodes_to_group: Array) -> Deck: + if nodes_to_group.is_empty(): + return null + + var node_ids_to_keep := nodes_to_group.map( + func(x: DeckNode): + return x._id + ) + + var group := Deck.new() + group.is_group = true + group._belonging_to = self + var group_id := UUID.v4() + group.id = group_id + + var midpoint := Vector2() + for node: DeckNode in nodes_to_group: + if node.node_type == "group_node": # for recursive grouping + 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 + + for from_port: int in node.outgoing_connections: + for connection: Dictionary in node.outgoing_connections[from_port]: + if !(connection.keys()[0] in node_ids_to_keep): + disconnect_nodes(node, get_node(connection.keys()[0]), from_port, connection.values()[0]) + + midpoint += node.position_as_vector2() + remove_node(node._id) + group.add_node_inst(node, node._id) + + midpoint /= nodes_to_group.size() + + var _group_node := add_node_type("group_node") + _group_node.group_id = group_id + _group_node.position.x = midpoint.x + _group_node.position.y = midpoint.y + _group_node.position_updated.emit(_group_node.position) + group.group_node = _group_node._id + + var input_node := group.add_node_type("group_input") + var output_node := group.add_node_type("group_output") + group.group_input_node = input_node._id + group.group_output_node = output_node._id + + _group_node.input_node = input_node + _group_node.output_node = output_node + _group_node.setup_connections() + + groups[group_id] = group + + return group + + +func get_group(uuid: String) -> Deck: + return groups.get(uuid) + + +func to_dict(with_meta: bool = true) -> Dictionary: + var inner := { + "nodes": {}, + "variable_stack": variable_stack, + "id": id, + "groups": {} + } + + for node_id in nodes.keys(): + inner["nodes"][node_id] = nodes[node_id].to_dict(with_meta) + + for group_id in groups.keys(): + inner["groups"][group_id] = groups[group_id].to_dict(with_meta) + + if is_group: + inner["group_node"] = group_node + inner["group_input_node"] = group_input_node + inner["group_output_node"] = group_output_node var d := {"deck": inner} @@ -106,14 +180,14 @@ func to_json(with_meta: bool = true) -> String: d["meta"] = {} for meta in get_meta_list(): d["meta"][meta] = var_to_str(get_meta(meta)) - return JSON.stringify(d, "\t", false) + return d -static func from_json(json: String) -> Deck: - var data: Dictionary = JSON.parse_string(json) as Dictionary +static func from_dict(data: Dictionary, path: String = "") -> Deck: var deck := Deck.new() - + deck.save_path = path deck.variable_stack = data.deck.variable_stack + deck.id = data.deck.id for key in data.meta: deck.set_meta(key, str_to_var(data.meta[key])) @@ -121,12 +195,17 @@ static func from_json(json: String) -> Deck: var nodes_data: Dictionary = data.deck.nodes as Dictionary for node_id in nodes_data: - var node := deck.add_node_inst(NodeDB.instance_node(nodes_data[node_id].node_type), node_id, false) + var node := deck.add_node_type(nodes_data[node_id].node_type, node_id, false) node._id = node_id node.name = nodes_data[node_id].name node._belonging_to = deck -# node.outgoing_connections = nodes_data[node_id].outgoing_connections -# node.incoming_connections = nodes_data[node_id].incoming_connections + node.position = nodes_data[node_id].position + + for prop in nodes_data[node_id].props: + node.set(prop, nodes_data[node_id].props[prop]) + + node._pre_connection() + for connection_id in nodes_data[node_id].outgoing_connections: var connection_data = nodes_data[node_id].outgoing_connections[connection_id] for connection in connection_data: @@ -139,14 +218,29 @@ static func from_json(json: String) -> Deck: connection_data[connection] = int(connection_data[connection]) node.incoming_connections[int(connection_id)] = connection_data - for i in nodes_data[node_id].port_values.size(): - var port_value = nodes_data[node_id].port_values[i] + for i in node.ports.size(): + var port_value: Variant + if (nodes_data[node_id].port_values as Array).size() <= i: + port_value = null + else: + port_value = (nodes_data[node_id].port_values as Array)[i] node.ports[i].value = port_value for key in nodes_data[node_id].meta: node.set_meta(key, str_to_var(nodes_data[node_id].meta[key])) - for prop in nodes_data[node_id].props: - node.set(prop, nodes_data[node_id].props[prop]) + node._post_load() + + var groups_data: Dictionary = data.deck.groups as Dictionary + + 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 diff --git a/classes/deck/deck_holder.gd b/classes/deck/deck_holder.gd new file mode 100644 index 0000000..0cc160c --- /dev/null +++ b/classes/deck/deck_holder.gd @@ -0,0 +1,27 @@ +class_name DeckHolder + + +static var decks: Array[Deck] + + +static func add_empty_deck() -> Deck: + var deck := Deck.new() + DeckHolder.decks.append(deck) + var uuid := UUID.v4() + deck.id = uuid + return deck + + +static func open_deck_from_file(path: String) -> Deck: + var f := FileAccess.open(path, FileAccess.READ) + if f.get_error() != OK: + return null + + var deck := Deck.from_dict(JSON.parse_string(f.get_as_text()), path) + DeckHolder.decks.append(deck) + + return deck + + +static func close_deck(deck: Deck) -> void: + DeckHolder.decks.erase(deck) diff --git a/classes/deck/deck_node.gd b/classes/deck/deck_node.gd index 2cd748e..ff7b95e 100644 --- a/classes/deck/deck_node.gd +++ b/classes/deck/deck_node.gd @@ -1,9 +1,10 @@ class_name DeckNode var name: String -var input_ports: Array[Port] -var output_ports: Array[Port] + +## [code]Dictionary[int -> output port, Array[Dictionary[String -> DeckNode#_id, int -> input port]]] var outgoing_connections: Dictionary +## [code]Dictionary[int -> input port, [Dictionary[String -> DeckNode#_id, int -> output port]] var incoming_connections: Dictionary var ports: Array[Port] @@ -17,49 +18,63 @@ var aliases: Array[String] var props_to_serialize: Array[StringName] +var position: Dictionary = {"x": 0.0, "y": 0.0} + enum PortType{ INPUT, ## Input port type (slot on the left). OUTPUT, ## Output port type (slot on the right). VIRTUAL, ## Virtual port type (no slot on left [i]or[/i] right). } +signal position_updated(new_position: Dictionary) +signal port_added(port: int) +signal port_removed(port: int) +signal ports_updated() +signal outgoing_connection_added(from_port: int) +signal outgoing_connection_removed(from_port: int) +signal incoming_connection_added(from_port: int) +signal incoming_connection_removed(from_port: int) + func add_input_port(type: Deck.Types, label: String, descriptor: String = "") -> void: - var port := Port.new(type, label, ports.size(), PortType.INPUT, get_input_ports().size(), descriptor) - ports.append(port) + add_port(type, label, PortType.INPUT, get_input_ports().size(), descriptor) func add_output_port(type: Deck.Types, label: String, descriptor: String = "") -> void: - var port := Port.new(type, label, ports.size(), PortType.OUTPUT, get_output_ports().size(), descriptor) - ports.append(port) + add_port(type, label, PortType.OUTPUT, get_output_ports().size(), descriptor) func add_virtual_port(type: Deck.Types, label: String, descriptor: String = "") -> void: - var port := Port.new(type, label, ports.size(), PortType.VIRTUAL, get_virtual_ports().size(), descriptor) + add_port(type, label, PortType.VIRTUAL, get_virtual_ports().size(), descriptor) + + +func add_port(type: Deck.Types, label: String, port_type: PortType, index_of_type: int, descriptor: String = "") -> void: + var port := Port.new(type, label, ports.size(), port_type, index_of_type, descriptor) ports.append(port) + port_added.emit(ports.size() - 1) + ports_updated.emit() -func send_from_output_port(output_port: int, data: DeckType, extra_data: Array = []) -> void: - var global_port := get_global_port_idx_from_output(output_port) - if global_port == -1: +func remove_port(port_idx: int) -> void: + outgoing_connections.erase(port_idx) + incoming_connections.erase(port_idx) + ports.remove_at(port_idx) + port_removed.emit(port_idx) + + +func send(from_output_port: int, data: DeckType, extra_data: Array = []) -> void: + if outgoing_connections.get(from_output_port) == null: return - send(global_port, data, extra_data) - - -func send(from_port: int, data: DeckType, extra_data: Array = []) -> void: - if outgoing_connections.get(from_port) == null: - return - - for connection in outgoing_connections[from_port]: + for connection in outgoing_connections[from_output_port]: connection = connection as Dictionary # key is node uuid - # value is input port on destination node + # value is GLOBAL port on destination node for node in connection: get_node(node)._receive(connection[node], data, extra_data) -func _receive(to_port: int, data: DeckType, extra_data: Array = []) -> void: +func _receive(to_input_port: int, data: DeckType, extra_data: Array = []) -> void: pass @@ -76,11 +91,13 @@ func add_outgoing_connection(from_port: int, to_node: String, to_port: int) -> v port_connections.append({to_node: to_port}) outgoing_connections[from_port] = port_connections get_node(to_node).add_incoming_connection(to_port, _id, from_port) + outgoing_connection_added.emit(from_port) func add_incoming_connection(to_port: int, from_node: String, from_port: int) -> void: var connection := {from_node: from_port} incoming_connections[to_port] = connection + incoming_connection_added.emit(to_port) func request_value(on_port: int) -> Variant: @@ -116,10 +133,12 @@ func remove_outgoing_connection(from_port: int, connection_hash: int) -> void: port_connections.remove_at(to_remove) outgoing_connections[from_port] = port_connections + outgoing_connection_removed.emit(from_port) func remove_incoming_connection(to_port: int) -> void: incoming_connections.erase(to_port) + incoming_connection_removed.emit(to_port) func remove_outgoing_connection_from_port(output_port: int, connection_hash: int) -> void: @@ -168,6 +187,26 @@ func get_global_port_idx_from_virtual(idx: int) -> int: return -1 +func get_port_type_idx_from_global(idx: int) -> int: + return ports[idx].index_of_type + + +func get_outgoing_connection_count(port: int) -> int: + return (outgoing_connections[port] as Array).size() + + +func get_outgoing_connection_count_on_output(port: int) -> int: + return get_outgoing_connection_count(get_global_port_idx_from_output(port)) + + +func has_incoming_connection(port: int) -> bool: + return incoming_connections.has(port) + + +func has_incoming_connection_on_input(port: int) -> bool: + return has_incoming_connection(get_global_port_idx_from_input(port)) + + func get_all_ports() -> Array[Port]: return ports @@ -176,6 +215,16 @@ func get_node(uuid: String) -> DeckNode: return _belonging_to.get_node(uuid) +# override this to do setup before connections are loaded but after props were set +func _pre_connection() -> void: + pass + + +# override this to do extra setup after it's done loading from dictionary +func _post_load() -> void: + pass + + func to_dict(with_meta: bool = true) -> Dictionary: var d := { "_id": _id, @@ -184,7 +233,8 @@ func to_dict(with_meta: bool = true) -> Dictionary: "incoming_connections": incoming_connections, "props": {}, "node_type": node_type, - "port_values": [] + "port_values": [], + "position": position, } for prop in props_to_serialize: @@ -200,3 +250,7 @@ func to_dict(with_meta: bool = true) -> Dictionary: for meta in get_meta_list(): d["meta"][meta] = var_to_str(get_meta(meta)) return d + + +func position_as_vector2() -> Vector2: + return Vector2(position.x, position.y) diff --git a/classes/deck/node_db.gd b/classes/deck/node_db.gd index 21b820e..908e02f 100644 --- a/classes/deck/node_db.gd +++ b/classes/deck/node_db.gd @@ -8,8 +8,8 @@ var nodes: Dictionary = {} func _init() -> void: - if load_node_index(): - return + #if load_node_index(): + #return var dir := DirAccess.open(BASE_NODE_PATH) dir.list_dir_begin() diff --git a/classes/deck/nodes/group_input_node.gd b/classes/deck/nodes/group_input_node.gd new file mode 100644 index 0000000..5881f42 --- /dev/null +++ b/classes/deck/nodes/group_input_node.gd @@ -0,0 +1,71 @@ +extends DeckNode + +var output_count: int: + get: + return get_all_ports().size() - 1 + + +func _init() -> void: + name = "Group input" + node_type = "group_input" + props_to_serialize = [&"output_count"] + + add_output_port( + Deck.Types.STRING, + "Input 0" + ) + outgoing_connection_added.connect(_on_outgoing_connection_added) + outgoing_connection_removed.connect(_on_outgoing_connection_removed) + + +func _on_outgoing_connection_added(port_idx: int) -> void: + if port_idx + 1 < get_all_ports().size(): + return + + add_output_port( + Deck.Types.STRING, + "Input %s" % (get_all_ports().size()) + ) + + +func _on_outgoing_connection_removed(port_idx: int) -> void: + var last_connected_port := 0 + for port: int in outgoing_connections.keys().slice(1): + if !(outgoing_connections[port] as Array).is_empty(): + last_connected_port = port + + prints("l:", last_connected_port, "p:", port_idx) + + if port_idx < last_connected_port: + return + + var s := get_all_ports().slice(0, last_connected_port + 2) + ports.assign(s) + ports_updated.emit() + + +func _pre_connection() -> void: + for i in output_count + 1: + add_output_port( + Deck.Types.STRING, + "Input %s" % (i + 1) + ) + + +func _post_load() -> void: + # ensure we have enough ports after connections + var last_connected_port := 0 + for port: int in outgoing_connections: + last_connected_port = port if outgoing_connections.has(port) else last_connected_port + + if ports.size() <= last_connected_port: + for i in last_connected_port: + add_output_port( + Deck.Types.STRING, + "Input %s" % get_output_ports().size() + ) + + +func _value_request(from_port: int) -> Variant: + var group_node := _belonging_to._belonging_to.get_node(_belonging_to.group_node) + return group_node.request_value(group_node.get_input_ports()[from_port].index_of_type) diff --git a/classes/deck/nodes/group_node.gd b/classes/deck/nodes/group_node.gd new file mode 100644 index 0000000..2942e93 --- /dev/null +++ b/classes/deck/nodes/group_node.gd @@ -0,0 +1,68 @@ +extends DeckNode + +var group_id: String +var input_node: DeckNode +var output_node: DeckNode + +var extra_ports: Array + + +func _init() -> void: + name = "Group" + node_type = "group_node" + props_to_serialize = [&"group_id", &"extra_ports"] + + +func _pre_connection() -> void: + for port_type: PortType in extra_ports: + var index_of_type: int + match port_type: + PortType.OUTPUT: + add_output_port(Deck.Types.STRING, "Output %s" % get_output_ports().size()) + PortType.INPUT: + add_input_port(Deck.Types.STRING, "Input %s" % get_input_ports().size()) + + +func init_io() -> void: + var group: Deck = _belonging_to.groups.get(group_id) as Deck + if !group: + return + + input_node = group.get_node(group.group_input_node) + output_node = group.get_node(group.group_output_node) + + recalculate_ports() + setup_connections() + + +func setup_connections() -> void: + input_node.ports_updated.connect(recalculate_ports) + output_node.ports_updated.connect(recalculate_ports) + + +func recalculate_ports() -> void: + ports.clear() + + for output_port: Port in output_node.get_input_ports(): + add_output_port( + Deck.Types.STRING, + "Output %s" % output_port.index + ) + + for input_port: Port in input_node.get_output_ports(): + add_input_port( + Deck.Types.STRING, + "Input %s" % input_port.index + ) + + extra_ports.clear() + for port in ports: + extra_ports.append(port.port_type) + + +func _receive(to_input_port: int, data: DeckType, extra_data: Array = []): + input_node.send(get_input_ports()[to_input_port].index_of_type, data, extra_data) + + +func _value_request(from_port: int) -> Variant: + return output_node.request_value(from_port) diff --git a/classes/deck/nodes/group_output_node.gd b/classes/deck/nodes/group_output_node.gd new file mode 100644 index 0000000..efcf7d8 --- /dev/null +++ b/classes/deck/nodes/group_output_node.gd @@ -0,0 +1,72 @@ +extends DeckNode + +var input_count: int: + get: + return get_all_ports().size() - 1 + + +func _init() -> void: + name = "Group output" + node_type = "group_output" + props_to_serialize = [&"input_count"] + + add_input_port( + Deck.Types.STRING, + "Output 0" + ) + + incoming_connection_added.connect(_on_incoming_connection_added) + incoming_connection_removed.connect(_on_incoming_connection_removed) + + +func _on_incoming_connection_added(port_idx: int) -> void: + if port_idx + 1 < get_all_ports().size(): + return + + add_input_port( + Deck.Types.STRING, + "Output %s" % (get_all_ports().size()) + ) + + +func _on_incoming_connection_removed(port_idx: int) -> void: + var last_connected_port := 0 + for port: int in incoming_connections.keys().slice(1): + if !(incoming_connections[port] as Dictionary).is_empty(): + last_connected_port = port + + prints("l:", last_connected_port, "p:", port_idx) + + if port_idx < last_connected_port: + return + + var s := get_all_ports().slice(0, last_connected_port + 2) + ports.assign(s) + ports_updated.emit() + + +func _pre_connection() -> void: + for i in input_count + 1: + add_input_port( + Deck.Types.STRING, + "Output %s" % (i + 1) + ) + + +func _post_load() -> void: + # ensure we have enough ports after connections + var last_connected_port := 0 + for port: int in incoming_connections: + last_connected_port = port if incoming_connections.has(port) else last_connected_port + + if ports.size() <= last_connected_port: + for i in last_connected_port: + add_input_port( + Deck.Types.STRING, + "Output %s" % get_input_ports().size() + ) + + +func _receive(to_input_port: int, data: DeckType, extra_data: Array = []) -> void: + var group_node := _belonging_to._belonging_to.get_node(_belonging_to.group_node) + group_node.send(group_node.get_output_ports()[to_input_port].index_of_type, data, extra_data) diff --git a/classes/deck/nodes/print.gd b/classes/deck/nodes/print.gd index 0296121..1e447b6 100644 --- a/classes/deck/nodes/print.gd +++ b/classes/deck/nodes/print.gd @@ -29,15 +29,15 @@ func _init() -> void: ) -func _receive(to_port: int, data: DeckType, extra_data: Array = []) -> void: - if to_port != 1: +func _receive(to_input_port: int, data: DeckType, extra_data: Array = []) -> void: + if to_input_port != 1: return var data_to_print if request_value(0) != null: data_to_print = request_value(0) - elif ports[0].value_callback.call() != "": - data_to_print = ports[0].value_callback.call() + elif get_input_ports()[0].value_callback.get_object() && get_input_ports()[0].value_callback.call() != "": + data_to_print = get_input_ports()[0].value_callback.call() else: data_to_print = data.get_value() @@ -46,4 +46,4 @@ func _receive(to_port: int, data: DeckType, extra_data: Array = []) -> void: # var data_to_print = input_ports[0].value_callback.call() print(data_to_print) print("extra data: ", extra_data) - send(2, DeckType.DeckTypeBool.new(true)) + send(0, DeckType.DeckTypeBool.new(true)) diff --git a/classes/deck/nodes/set_deck_var.gd b/classes/deck/nodes/set_deck_var.gd index 400aabd..a8e43e2 100644 --- a/classes/deck/nodes/set_deck_var.gd +++ b/classes/deck/nodes/set_deck_var.gd @@ -31,8 +31,8 @@ func _init() -> void: ) -func _receive(to_port: int, data: DeckType, extra_data: Array = []) -> void: - if to_port != 2: +func _receive(to_input_port: int, data: DeckType, extra_data: Array = []) -> void: + if to_input_port != 2: return var var_name: String = get_value_for_port(0, data) @@ -41,7 +41,7 @@ func _receive(to_port: int, data: DeckType, extra_data: Array = []) -> void: _belonging_to.variable_stack[var_name] = var_value - send(3, DeckType.DeckTypeString.new(var_value)) + send(0, DeckType.DeckTypeString.new(var_value)) # this can probably go into DeckNode with a different name that makes it clear # that it prioritizes call-time resolution diff --git a/classes/deck/nodes/string_constant.gd b/classes/deck/nodes/string_constant.gd new file mode 100644 index 0000000..c58f41c --- /dev/null +++ b/classes/deck/nodes/string_constant.gd @@ -0,0 +1,17 @@ +extends DeckNode + + +func _init() -> void: + node_type = "string_constant" + name = "String Constant" + add_output_port( + Deck.Types.STRING, + "Text", + "field" + ) + +func _value_request(from_port: int) -> Variant: + if ports[0].value_callback.get_object(): + return ports[0].value_callback.call() + else: + return ports[0].value diff --git a/classes/deck/nodes/test_interleaved_node.gd b/classes/deck/nodes/test_interleaved_node.gd new file mode 100644 index 0000000..1970b2d --- /dev/null +++ b/classes/deck/nodes/test_interleaved_node.gd @@ -0,0 +1,16 @@ +extends DeckNode + + +func _init() -> void: + node_type = "test_interleaved" + name = "Test Interleaved" + + for i in 4: + add_output_port( + Deck.Types.STRING, + "Test" + ) + add_input_port( + Deck.Types.STRING, + "Test" + ) diff --git a/graph_node_renderer/deck_holder_renderer.gd b/graph_node_renderer/deck_holder_renderer.gd new file mode 100644 index 0000000..836c5d5 --- /dev/null +++ b/graph_node_renderer/deck_holder_renderer.gd @@ -0,0 +1,127 @@ +extends Control + +const DECK_SCENE := preload("res://graph_node_renderer/deck_renderer_graph_edit.tscn") + +@onready var tab_container: TabContainerCustom = %TabContainerCustom as TabContainerCustom +@onready var file_dialog: FileDialog = $FileDialog + +enum FileMenuId { + NEW, + OPEN, + SAVE = 3, + SAVE_AS, + CLOSE = 6, +} + +var _deck_to_save: WeakRef + + +func _ready() -> void: + tab_container.add_button_pressed.connect(add_empty_deck) + + tab_container.tab_close_requested.connect( + func(tab: int): + DeckHolder.close_deck(tab_container.get_tab_metadata(tab)) + tab_container.close_tab(tab) + ) + + file_dialog.canceled.connect(disconnect_file_dialog_signals) + + +func _on_file_id_pressed(id: int) -> void: + match id: + FileMenuId.NEW: + add_empty_deck() + FileMenuId.OPEN: + open_open_dialog("res://") + FileMenuId.SAVE: + save_active_deck() + FileMenuId.SAVE_AS: + open_save_dialog("res://") + FileMenuId.CLOSE: + close_current_tab() + + +func add_empty_deck() -> void: + var deck := DeckHolder.add_empty_deck() + var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate() + inst.deck = deck + tab_container.add_content(inst, "Deck %s" % (tab_container.get_tab_count() + 1)) + tab_container.set_tab_metadata(tab_container.get_current_tab(), deck) + inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested.bind(deck)) + + +func close_current_tab() -> void: + tab_container.close_tab(tab_container.get_current_tab()) + + +func open_save_dialog(path: String) -> void: + file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE + file_dialog.title = "Save a Deck" + file_dialog.current_path = path + _deck_to_save = weakref(get_active_deck()) + file_dialog.popup_centered() + file_dialog.file_selected.connect(_on_file_dialog_save_file, CONNECT_ONE_SHOT) + + +func open_open_dialog(path: String) -> void: + file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILES + file_dialog.title = "Open Deck(s)" + file_dialog.current_path = path + file_dialog.popup_centered() + file_dialog.files_selected.connect(_on_file_dialog_open_files, CONNECT_ONE_SHOT) + + +func _on_file_dialog_save_file(path: String) -> void: + var deck: Deck = _deck_to_save.get_ref() as Deck + if !deck: + return + + deck.save_path = path + var json := JSON.stringify(deck.to_dict(), "\t") + var f := FileAccess.open(path, FileAccess.WRITE) + f.store_string(json) + + +func _on_file_dialog_open_files(paths: PackedStringArray) -> void: + for path in paths: + var deck := DeckHolder.open_deck_from_file(path) + var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate() + inst.deck = deck + tab_container.add_content(inst, "Deck %s" % (tab_container.get_tab_count() + 1)) + inst.initialize_from_deck() + inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested.bind(deck)) + + +func get_active_deck() -> Deck: + if tab_container.is_empty(): + return null + + return (tab_container.get_content(tab_container.get_current_tab()) as DeckRendererGraphEdit).deck + + +func save_active_deck() -> void: + if get_active_deck().save_path.is_empty(): + open_save_dialog("res://") + else: + var json := JSON.stringify(get_active_deck().to_dict(), "\t") + var f := FileAccess.open(get_active_deck().save_path, FileAccess.WRITE) + f.store_string(json) + + +func disconnect_file_dialog_signals() -> void: + if file_dialog.file_selected.is_connected(_on_file_dialog_save_file): + file_dialog.file_selected.disconnect(_on_file_dialog_save_file) + + if file_dialog.files_selected.is_connected(_on_file_dialog_open_files): + file_dialog.files_selected.disconnect(_on_file_dialog_open_files) + + +func _on_deck_renderer_group_enter_requested(group_id: String, deck: Deck) -> void: + var group_deck := deck.get_group(group_id) + var deck_renderer: DeckRendererGraphEdit = DECK_SCENE.instantiate() + deck_renderer.deck = group_deck + deck_renderer.initialize_from_deck() + tab_container.add_content(deck_renderer, "Group %s" % (tab_container.get_tab_count() + 1)) + tab_container.set_tab_metadata(tab_container.get_current_tab(), group_deck) + deck_renderer.group_enter_requested.connect(_on_deck_renderer_group_enter_requested.bind(deck_renderer.deck)) diff --git a/graph_node_renderer/deck_holder_renderer.tscn b/graph_node_renderer/deck_holder_renderer.tscn new file mode 100644 index 0000000..fa41b7e --- /dev/null +++ b/graph_node_renderer/deck_holder_renderer.tscn @@ -0,0 +1,73 @@ +[gd_scene load_steps=4 format=3 uid="uid://duaah5x0jhkn6"] + +[ext_resource type="Script" path="res://graph_node_renderer/deck_holder_renderer.gd" id="1_67g2g"] +[ext_resource type="PackedScene" uid="uid://b84f2ngtcm5b8" path="res://graph_node_renderer/tab_container_custom.tscn" id="1_s3ug2"] +[ext_resource type="Theme" uid="uid://dqqdqscid2iem" path="res://graph_node_renderer/default_theme.tres" id="1_tgul2"] + +[node name="DeckHolderRenderer" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_tgul2") +script = ExtResource("1_67g2g") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 2 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 2 +theme_override_constants/margin_bottom = 2 + +[node name="VSplitContainer" type="VSplitContainer" parent="MarginContainer"] +layout_mode = 2 +split_offset = 677 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VSplitContainer"] +layout_mode = 2 + +[node name="MenuBar" type="MenuBar" parent="MarginContainer/VSplitContainer/VBoxContainer"] +layout_mode = 2 + +[node name="File" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"] +item_count = 7 +item_0/text = "New Deck" +item_0/id = 0 +item_1/text = "Open Deck" +item_1/id = 1 +item_2/text = "" +item_2/id = 2 +item_2/separator = true +item_3/text = "Save Deck" +item_3/id = 3 +item_4/text = "Save Deck As" +item_4/id = 4 +item_5/text = "" +item_5/id = 5 +item_5/separator = true +item_6/text = "Close Deck" +item_6/id = 6 + +[node name="Edit" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"] + +[node name="TabContainerCustom" parent="MarginContainer/VSplitContainer/VBoxContainer" instance=ExtResource("1_s3ug2")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="ConsoleContainer" type="PanelContainer" parent="MarginContainer/VSplitContainer"] +layout_mode = 2 + +[node name="FileDialog" type="FileDialog" parent="."] +size = Vector2i(776, 447) +mode_overrides_title = false +access = 2 +use_native_dialog = true + +[connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/File" to="." method="_on_file_id_pressed"] diff --git a/graph_node_renderer/deck_node_renderer_graph_node.gd b/graph_node_renderer/deck_node_renderer_graph_node.gd index ec37e2f..510572a 100644 --- a/graph_node_renderer/deck_node_renderer_graph_node.gd +++ b/graph_node_renderer/deck_node_renderer_graph_node.gd @@ -6,7 +6,10 @@ var node: DeckNode func _ready() -> void: title = node.name - + node.position_updated.connect(_on_node_position_updated) + #node.port_added.connect(_on_node_port_added) + #node.port_removed.connect(_on_node_port_removed) + node.ports_updated.connect(_on_node_ports_updated) for port in node.get_all_ports(): var descriptor_split := port.descriptor.split(":") match descriptor_split[0]: @@ -17,12 +20,12 @@ func _ready() -> void: if port.port_type == DeckNode.PortType.OUTPUT: button.pressed.connect( func(): - node.send(port.index, DeckType.DeckTypeBool.new(true)) + node.send(port.index_of_type, DeckType.DeckTypeBool.new(true)) ) elif port.port_type == DeckNode.PortType.INPUT: button.pressed.connect( func(): - node._receive(port.index, DeckType.DeckTypeBool.new(true)) + node._receive(port.index_of_type, DeckType.DeckTypeBool.new(true)) ) "field": var line_edit := LineEdit.new() @@ -49,4 +52,113 @@ func _ready() -> void: func _on_position_offset_changed() -> void: - node.set_meta("position_offset", position_offset) + node.position.x = position_offset.x + node.position.y = position_offset.y + + +func _on_node_position_updated(new_position: Dictionary) -> void: + position_offset.x = new_position.x + position_offset.y = new_position.y + + +func _on_node_port_added(port_idx: int) -> void: + var port := node.get_all_ports()[port_idx] + + var descriptor_split := port.descriptor.split(":") + match descriptor_split[0]: + "button": + var button := Button.new() + add_child(button) + button.text = port.label + if port.port_type == DeckNode.PortType.OUTPUT: + button.pressed.connect( + func(): + node.send(port.index_of_type, DeckType.DeckTypeBool.new(true)) + ) + elif port.port_type == DeckNode.PortType.INPUT: + button.pressed.connect( + func(): + node._receive(port.index_of_type, DeckType.DeckTypeBool.new(true)) + ) + "field": + var line_edit := LineEdit.new() + add_child(line_edit) + 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) + _: + var label := Label.new() + add_child(label) + label.text = port.label + + set_slot( + port.index, + port.port_type == DeckNode.PortType.INPUT, + 0, + Color.WHITE, + port.port_type == DeckNode.PortType.OUTPUT, + 0, + Color.WHITE, + ) + + +func _on_node_port_removed(port_idx: int) -> void: + set_slot( + port_idx, + false, + 0, + Color.WHITE, + false, + 0, + Color.WHITE, + ) + + get_child(port_idx).queue_free() + + +func _on_node_ports_updated() -> void: + clear_all_slots() + for c in get_children(): + c.queue_free() + + for port in node.get_all_ports(): + var descriptor_split := port.descriptor.split(":") + match descriptor_split[0]: + "button": + var button := Button.new() + add_child(button) + button.text = port.label + if port.port_type == DeckNode.PortType.OUTPUT: + button.pressed.connect( + func(): + node.send(port.index_of_type, DeckType.DeckTypeBool.new(true)) + ) + elif port.port_type == DeckNode.PortType.INPUT: + button.pressed.connect( + func(): + node._receive(port.index_of_type, DeckType.DeckTypeBool.new(true)) + ) + "field": + var line_edit := LineEdit.new() + add_child(line_edit) + 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) + _: + var label := Label.new() + add_child(label) + label.text = port.label + + set_slot( + port.index, + port.port_type == DeckNode.PortType.INPUT, + 0, + Color.WHITE, + port.port_type == DeckNode.PortType.OUTPUT, + 0, + Color.WHITE, + ) diff --git a/graph_node_renderer/deck_renderer_graph_edit.gd b/graph_node_renderer/deck_renderer_graph_edit.gd index 25d12be..116ce3c 100644 --- a/graph_node_renderer/deck_renderer_graph_edit.gd +++ b/graph_node_renderer/deck_renderer_graph_edit.gd @@ -1,12 +1,16 @@ extends GraphEdit +class_name DeckRendererGraphEdit const NODE_SCENE := preload("res://graph_node_renderer/deck_node_renderer_graph_node.tscn") -var deck: Deck = Deck.new() -#var button_node = preload("res://classes/deck/nodes/button.gd") -#var print_node = preload("res://classes/deck/nodes/print.gd") -#var get_var_node = preload("res://classes/deck/nodes/get_deck_var.gd") -#var set_var_node = preload("res://classes/deck/nodes/set_deck_var.gd") +var deck: Deck: + set(v): + deck = v + deck.node_added.connect(_on_deck_node_added) + deck.node_removed.connect(_on_deck_node_removed) + +signal group_enter_requested(group_id: String) + func _ready() -> void: var add_button := Button.new() @@ -17,100 +21,51 @@ func _ready() -> void: get_var.text = "Get Var" var set_var := Button.new() set_var.text = "Set Var" - get_zoom_hbox().add_child(add_button) - get_zoom_hbox().add_child(add_print) - get_zoom_hbox().add_child(get_var) - get_zoom_hbox().add_child(set_var) - - var save_btn := Button.new() - save_btn.text = "Save" - get_zoom_hbox().add_child(save_btn) - var load_btn := Button.new() - load_btn.text = "Load" - get_zoom_hbox().add_child(load_btn) - - save_btn.pressed.connect( - func(): - var t = deck.to_json() - var f := FileAccess.open("user://save_test.json", FileAccess.WRITE) - f.store_string(t) - ) - - load_btn.pressed.connect( - func(): - var f := FileAccess.open("user://save_test.json", FileAccess.READ) - for i in get_children(): - i.queue_free() - - deck = Deck.from_json(f.get_as_text()) - scroll_offset = str_to_vector2(deck.get_meta("offset", "")) - - for node_id in deck.nodes: - var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() - node_renderer.node = deck.nodes[node_id] - add_child(node_renderer) - node_renderer.position_offset = str_to_vector2(deck.nodes[node_id].get_meta("position_offset", "")) - - for node_id in deck.nodes: - var node: DeckNode = deck.nodes[node_id] - var from_node_name = get_children().filter( - func(c: DeckNodeRendererGraphNode): - return c.node._id == node_id - )[0].name -# "outgoing_connections": { -# "0": [ -# { -# "c8cc7dce-2a86-461c-a24f-0a4dbd06f379": 1 -# } -# ] -# }, - for from_port in node.outgoing_connections: - for connection in node.outgoing_connections[from_port]: - var to_node_id = connection.keys()[0] - var to_node_port = connection.values()[0] - var to_node_name = get_children().filter( - func(c: DeckNodeRendererGraphNode): - return c.node._id == to_node_id - )[0].name - prints(from_node_name, to_node_name) - connect_node(from_node_name, from_port, to_node_name, to_node_port) - ) - + var test := Button.new() + test.text = "Interleaved" + var str_const := Button.new() + str_const.text = "String" + get_menu_hbox().add_child(add_button) + get_menu_hbox().add_child(add_print) + get_menu_hbox().add_child(get_var) + get_menu_hbox().add_child(set_var) + get_menu_hbox().add_child(test) + get_menu_hbox().add_child(str_const) add_button.pressed.connect( func(): var node := NodeDB.instance_node("button") deck.add_node_inst(node) - var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() - node_renderer.node = node - add_child(node_renderer) ) add_print.pressed.connect( func(): var node := NodeDB.instance_node("print") deck.add_node_inst(node) - var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() - node_renderer.node = node - add_child(node_renderer) ) get_var.pressed.connect( func(): var node := NodeDB.instance_node("get_deck_var") deck.add_node_inst(node) - var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() - node_renderer.node = node - add_child(node_renderer) ) set_var.pressed.connect( func(): var node := NodeDB.instance_node("set_deck_var") deck.add_node_inst(node) - var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() - node_renderer.node = node - add_child(node_renderer) + ) + + test.pressed.connect( + func(): + var node := NodeDB.instance_node("test_interleaved") + deck.add_node_inst(node) + ) + + str_const.pressed.connect( + func(): + var node := NodeDB.instance_node("string_constant") + deck.add_node_inst(node) ) connection_request.connect(attempt_connection) @@ -118,37 +73,140 @@ func _ready() -> void: func attempt_connection(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void: - var from_node: DeckNode = get_node(NodePath(from_node_name)).node - var to_node: DeckNode = get_node(NodePath(to_node_name)).node + var from_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(from_node_name)) + var to_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(to_node_name)) - var from_output := from_node.get_global_port_idx_from_output(from_port) - var to_input := to_node.get_global_port_idx_from_input(to_port) + #var from_output := from_node_renderer.node.get_global_port_idx_from_output(from_port) + #var to_input := to_node_renderer.node.get_global_port_idx_from_input(to_port) - if deck.connect_nodes(from_node, to_node, from_output, to_input): - connect_node(from_node_name, from_port, to_node_name, to_port) + if deck.connect_nodes(from_node_renderer.node, to_node_renderer.node, from_port, to_port): + connect_node( + from_node_renderer.name, + from_port, + to_node_renderer.name, + to_port + ) func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void: - var from_node: DeckNode = get_node(NodePath(from_node_name)).node - var to_node: DeckNode = get_node(NodePath(to_node_name)).node + var from_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(from_node_name)) + var to_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(to_node_name)) - var from_output := from_node.get_global_port_idx_from_output(from_port) - var to_input := to_node.get_global_port_idx_from_input(to_port) + #var from_output := from_node_renderer.node.get_global_port_idx_from_output(from_port) + #var to_input := to_node_renderer.node.get_global_port_idx_from_input(to_port) - deck.disconnect_nodes(from_node, to_node, from_output, to_input) + deck.disconnect_nodes(from_node_renderer.node, to_node_renderer.node, from_port, to_port) - disconnect_node(from_node_name, from_port, to_node_name, to_port) + disconnect_node( + from_node_renderer.name, + from_port, + to_node_renderer.name, + to_port + ) func _on_scroll_offset_changed(offset: Vector2) -> void: deck.set_meta("offset", offset) -func str_to_vector2(s: String) -> Vector2: - if s.is_empty(): - return Vector2() +func initialize_from_deck() -> void: + # TODO: wait for https://github.com/godotengine/godot/issues/85005 to get merged + # until it is, all calls to GraphEdit#get_children will need to slice off the first element + for i in get_children().slice(1): + i.queue_free() - var san := s.trim_prefix("(").trim_suffix(")").split(",") - var x := float(san[0]) - var y := float(san[1]) - return Vector2(x, y) + scroll_offset = deck.get_meta("offset", Vector2()) + + for node_id in deck.nodes: + var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() + node_renderer.node = deck.nodes[node_id] + add_child(node_renderer) + node_renderer.position_offset = node_renderer.node.position_as_vector2() + + for node_id in deck.nodes: + var node: DeckNode = deck.nodes[node_id] + var from_node = get_children().slice(1).filter( + func(c: DeckNodeRendererGraphNode): + return c.node._id == node_id + )[0] + + refresh_connections() + + +func refresh_connections() -> void: + for node_id in deck.nodes: + var node: DeckNode = deck.nodes[node_id] + var from_node: DeckNodeRendererGraphNode = get_children().slice(1).filter( + func(c: DeckNodeRendererGraphNode): + return c.node._id == node_id + )[0] + + for from_port in node.outgoing_connections: + for connection in node.outgoing_connections[from_port]: + var to_node_id = connection.keys()[0] + var to_node_port = connection.values()[0] + var to_node: DeckNodeRendererGraphNode = get_children().slice(1).filter( + func(c: DeckNodeRendererGraphNode): + return c.node._id == to_node_id + )[0] + #print("***") + #print("calling connect_node with:") + #print(from_node.node.name) + #print(from_node.node.get_port_type_idx_from_global(from_port)) + #print(to_node.node.name) + #print(to_node.node.get_port_type_idx_from_global(to_node_port)) + #print("***") + connect_node( + from_node.name, + from_port, + to_node.name, + to_node_port + ) + + +func _on_deck_node_added(node: DeckNode) -> void: + var inst: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() + inst.node = node + add_child(inst) + inst.position_offset = inst.node.position_as_vector2() + + +func _on_deck_node_removed(node: DeckNode) -> void: + for renderer: DeckNodeRendererGraphNode in get_children().slice(1): + if renderer.node != node: + continue + + renderer.queue_free() + break + + +func get_selected_nodes() -> Array: + return get_children().slice(1).filter( + func(x: DeckNodeRendererGraphNode): + return x.selected + ) + + +func _gui_input(event: InputEvent) -> void: + if event.is_action_pressed("group_nodes") && get_selected_nodes().size() > 0: + print("?") + clear_connections() + var nodes = get_selected_nodes().map( + func(x: DeckNodeRendererGraphNode): + return x.node + ) + deck.group_nodes(nodes) + refresh_connections() + get_viewport().set_input_as_handled() + + +func _input(event: InputEvent) -> void: + if !has_focus(): + return + + if event.is_action_pressed("enter_group") && get_selected_nodes().size() == 1: + if !((get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.node_type == "group_node"): + return + print("tried to enter group") + group_enter_requested.emit((get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.group_id) + get_viewport().set_input_as_handled() diff --git a/graph_node_renderer/default_theme.tres b/graph_node_renderer/default_theme.tres new file mode 100644 index 0000000..3425c99 --- /dev/null +++ b/graph_node_renderer/default_theme.tres @@ -0,0 +1,4 @@ +[gd_resource type="Theme" format=3 uid="uid://dqqdqscid2iem"] + +[resource] +default_font_size = 14 diff --git a/graph_node_renderer/tab_container_custom.gd b/graph_node_renderer/tab_container_custom.gd new file mode 100644 index 0000000..35c9e4a --- /dev/null +++ b/graph_node_renderer/tab_container_custom.gd @@ -0,0 +1,84 @@ +extends VBoxContainer +class_name TabContainerCustom + +@onready var tab_bar: TabBar = %TabBar +@onready var add_tab_button: Button = %Button +@onready var content_container: MarginContainer = %ContentContainer + + +signal add_button_pressed +signal tab_changed(tab: int) +signal tab_closed(tab: int) +signal tab_close_requested(tab: int) +signal tab_rearranged(old: int, new: int) + +var _previous_active_tab: int = -1 + + +func _ready() -> void: + tab_bar.tab_selected.connect( + func(tab: int): + if _previous_active_tab == tab: + return + + if _previous_active_tab > -1: + content_container.get_child(_previous_active_tab).visible = false + content_container.get_child(tab).visible = true + + tab_changed.emit(tab) + _previous_active_tab = tab + ) + + tab_bar.tab_close_pressed.connect(func(tab: int): tab_close_requested.emit(tab)) + + add_tab_button.pressed.connect( + func(): + add_button_pressed.emit() + ) + + tab_bar.active_tab_rearranged.connect( + func(idx_to: int): + tab_rearranged.emit(_previous_active_tab, idx_to) + content_container.move_child(content_container.get_child(_previous_active_tab), idx_to) + _previous_active_tab = idx_to + ) + + +func add_content(c: Node, tab_title: String) -> void: + tab_bar.add_tab(tab_title) + content_container.add_child(c) + tab_bar.set_current_tab(tab_bar.tab_count - 1) + + +func get_tab_count() -> int: + return tab_bar.tab_count + + +func is_empty() -> bool: + return get_tab_count() == 0 + + +func close_tab(tab: int) -> void: + content_container.get_child(tab).queue_free() + if !tab_bar.select_previous_available(): + tab_bar.select_next_available() + tab_bar.remove_tab(tab) + tab_closed.emit(tab) + if tab_bar.tab_count == 0: + _previous_active_tab = -1 + + +func get_current_tab() -> int: + return tab_bar.current_tab + + +func get_content(idx: int) -> Control: + return content_container.get_child(idx) + + +func set_tab_metadata(tab: int, metadata: Variant) -> void: + tab_bar.set_tab_metadata(tab, metadata) + + +func get_tab_metadata(tab: int) -> Variant: + return tab_bar.get_tab_metadata(tab) diff --git a/graph_node_renderer/tab_container_custom.tscn b/graph_node_renderer/tab_container_custom.tscn new file mode 100644 index 0000000..8607576 --- /dev/null +++ b/graph_node_renderer/tab_container_custom.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=2 format=3 uid="uid://b84f2ngtcm5b8"] + +[ext_resource type="Script" path="res://graph_node_renderer/tab_container_custom.gd" id="1_63wbq"] + +[node name="TabContainerCustom" type="VBoxContainer"] +offset_right = 22.0 +offset_bottom = 35.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_63wbq") +metadata/_edit_use_anchors_ = true + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="TabBar" type="TabBar" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +clip_tabs = false +tab_close_display_policy = 1 +drag_to_rearrange_enabled = true + +[node name="Button" type="Button" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 0 +text = "+" + +[node name="ContentContainer" type="MarginContainer" parent="."] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 diff --git a/project.godot b/project.godot index 273b42f..9c05b80 100644 --- a/project.godot +++ b/project.godot @@ -12,10 +12,10 @@ config_version=5 config/name="Re-DotDeck" config/tags=PackedStringArray("dot_deck") -run/main_scene="res://graph_node_renderer/deck_renderer_graph_edit.tscn" +run/main_scene="res://graph_node_renderer/deck_holder_renderer.tscn" config/use_custom_user_dir=true config/custom_user_dir_name="dotdeck" -config/features=PackedStringArray("4.1", "Forward Plus") +config/features=PackedStringArray("4.2", "Forward Plus") run/low_processor_mode=true config/icon="res://icon.svg" @@ -23,6 +23,23 @@ config/icon="res://icon.svg" NodeDB="*res://classes/deck/node_db.gd" +[display] + +window/subwindows/embed_subwindows=false + +[input] + +group_nodes={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":103,"echo":false,"script":null) +] +} +enter_group={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} + [rendering] renderer/rendering_method="gl_compatibility"