merge groups (#2)

here we go

Reviewed-on: https://codeberg.org/Eroax/Re-DotDeck/pulls/2
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
This commit is contained in:
Lera Elvoé 2023-11-22 04:26:11 +00:00 committed by yagich
parent b25f6ce6c7
commit fa5e9997ef
19 changed files with 1109 additions and 181 deletions

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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"
)

View file

@ -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))

View file

@ -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"]

View file

@ -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,
)

View file

@ -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()

View file

@ -0,0 +1,4 @@
[gd_resource type="Theme" format=3 uid="uid://dqqdqscid2iem"]
[resource]
default_font_size = 14

View file

@ -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)

View file

@ -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

View file

@ -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"