Merge pull request 'its time' (#1) from scrapheap into main

Reviewed-on: https://codeberg.org/Eroax/Re-DotDeck/pulls/1
This commit is contained in:
yagich 2023-08-07 03:59:52 +00:00
commit 48bf02d22a
19 changed files with 1247 additions and 0 deletions

88
classes/UUID.gd Normal file
View file

@ -0,0 +1,88 @@
# MIT License
#
# Copyright (c) 2023 Xavier Sellier
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Note: The code might not be as pretty it could be, since it's written
# in a way that maximizes performance. Methods are inlined and loops are avoided.
class_name UUID
const BYTE_MASK: int = 0b11111111
static func uuidbin() -> Array:
randomize()
# 16 random bytes with the bytes on index 6 and 8 modified
return [
randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
randi() & BYTE_MASK, randi() & BYTE_MASK, ((randi() & BYTE_MASK) & 0x0f) | 0x40, randi() & BYTE_MASK,
((randi() & BYTE_MASK) & 0x3f) | 0x80, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
]
static func uuidbinrng(rng: RandomNumberGenerator) -> Array:
rng.randomize()
return [
rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, ((rng.randi() & BYTE_MASK) & 0x0f) | 0x40, rng.randi() & BYTE_MASK,
((rng.randi() & BYTE_MASK) & 0x3f) | 0x80, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
]
static func v4() -> String:
# 16 random bytes with the bytes on index 6 and 8 modified
var b = uuidbin()
return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
# low
b[0], b[1], b[2], b[3],
# mid
b[4], b[5],
# hi
b[6], b[7],
# clock
b[8], b[9],
# clock
b[10], b[11], b[12], b[13], b[14], b[15]
]
static func v4_rng(rng: RandomNumberGenerator) -> String:
# 16 random bytes with the bytes on index 6 and 8 modified
var b = uuidbinrng(rng)
return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
# low
b[0], b[1], b[2], b[3],
# mid
b[4], b[5],
# hi
b[6], b[7],
# clock
b[8], b[9],
# clock
b[10], b[11], b[12], b[13], b[14], b[15]
]

152
classes/deck/deck.gd Normal file
View file

@ -0,0 +1,152 @@
class_name Deck
var nodes: Dictionary
enum Types{
ERROR = -1,
BOOL,
NUMERIC,
STRING,
ARRAY,
DICTIONARY,
}
static var type_assoc: Dictionary = {
Types.ERROR: DeckType.DeckTypeError,
Types.BOOL: DeckType.DeckTypeBool,
Types.NUMERIC: DeckType.DeckTypeNumeric,
Types.STRING: DeckType.DeckTypeString,
Types.ARRAY: DeckType.DeckTypeArray,
Types.DICTIONARY: DeckType.DeckTypeDictionary,
}
var variable_stack: Dictionary = {}
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
if !meta.is_empty():
for k in meta:
node_inst.set_meta(k, meta[k])
return node_inst
func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool = true) -> DeckNode:
if assign_to_self:
node._belonging_to = self
if assign_id == "":
var uuid := UUID.v4()
nodes[uuid] = node
node._id = uuid
else:
nodes[assign_id] = node
# if !meta.is_empty():
# for k in meta:
# node.set_meta(k, meta[k])
return node
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:
# 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 err: DeckType = (type_assoc[type_b]).from(type_assoc[type_a].new())
if err is DeckType.DeckTypeError:
print(err.error_message)
return false
# TODO: prevent duplicate connections
from_node.add_outgoing_connection(from_port, to_node._id, to_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)
from_node.remove_outgoing_connection(from_port, hash)
to_node.remove_incoming_connection(to_port)
func to_json(with_meta: bool = true) -> String:
var inner := {"nodes": {}, "variable_stack": variable_stack}
for id in nodes.keys():
inner["nodes"][id] = nodes[id].to_dict(with_meta)
var d := {"deck": inner}
if with_meta:
d["meta"] = {}
for meta in get_meta_list():
d["meta"][meta] = get_meta(meta)
return JSON.stringify(d, "\t", false)
static func from_json(json: String) -> Deck:
var data: Dictionary = JSON.parse_string(json) as Dictionary
var deck := Deck.new()
deck.variable_stack = data.deck.variable_stack
for key in data.meta:
deck.set_meta(key, data.meta[key])
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)
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
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:
connection[connection.keys()[0]] = int(connection.values()[0])
node.outgoing_connections[int(connection_id)] = connection_data
for connection_id in nodes_data[node_id].incoming_connections:
var connection_data = nodes_data[node_id].incoming_connections[connection_id]
for connection in connection_data:
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]
node.ports[i].value = port_value
for key in nodes_data[node_id].meta:
node.set_meta(key, nodes_data[node_id].meta[key])
for prop in nodes_data[node_id].props:
node.set(prop, nodes_data[node_id].props[prop])
return deck

202
classes/deck/deck_node.gd Normal file
View file

@ -0,0 +1,202 @@
class_name DeckNode
var name: String
var input_ports: Array[Port]
var output_ports: Array[Port]
var outgoing_connections: Dictionary
var incoming_connections: Dictionary
var ports: Array[Port]
var _belonging_to: Deck
var _id: String
var node_type: String
var description: String
var aliases: Array[String]
var props_to_serialize: Array[StringName]
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).
}
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)
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)
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)
ports.append(port)
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:
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]:
connection = connection as Dictionary
# key is node uuid
# value is input 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:
pass
func add_outgoing_connection_to_port(output_port: int, to_node: String, to_input_port: int) -> void:
add_outgoing_connection(
get_global_port_idx_from_output(output_port),
to_node,
get_node(to_node).get_global_port_idx_from_input(to_input_port)
)
func add_outgoing_connection(from_port: int, to_node: String, to_port: int) -> void:
var port_connections: Array = outgoing_connections.get(from_port, [])
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)
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
func request_value(on_port: int) -> Variant:
if !incoming_connections.has(on_port):
return null
var connection: Dictionary = incoming_connections[on_port]
var node := get_node(connection.keys()[0])
return node._value_request(connection.values()[0])
# override this
func _value_request(from_port: int) -> Variant:
return null
func remove_outgoing_connection(from_port: int, connection_hash: int) -> void:
var port_connections: Array = (outgoing_connections.get(from_port, []) as Array).duplicate(true)
if port_connections.is_empty():
return
var incoming_connection := {}
var to_remove: int = -1
for i in port_connections.size():
if port_connections[i].hash() == connection_hash:
to_remove = i
if to_remove == -1:
print('nothing to remove')
return
port_connections.remove_at(to_remove)
outgoing_connections[from_port] = port_connections
func remove_incoming_connection(to_port: int) -> void:
incoming_connections.erase(to_port)
func remove_outgoing_connection_from_port(output_port: int, connection_hash: int) -> void:
remove_outgoing_connection(get_global_port_idx_from_output(output_port), connection_hash)
func get_input_ports() -> Array[Port]:
return ports.filter(
func(port: Port) -> bool:
return port.port_type == PortType.INPUT
)
func get_output_ports() -> Array[Port]:
return ports.filter(
func(port: Port) -> bool:
return port.port_type == PortType.OUTPUT
)
func get_virtual_ports() -> Array[Port]:
return ports.filter(
func(port: Port) -> bool:
return port.port_type == PortType.VIRTUAL
)
func get_global_port_idx_from_input(idx: int) -> int:
if get_input_ports().size() > idx:
return get_input_ports()[idx].index
else:
return -1
func get_global_port_idx_from_output(idx: int) -> int:
if get_output_ports().size() > idx:
return get_output_ports()[idx].index
else:
return -1
func get_global_port_idx_from_virtual(idx: int) -> int:
if get_virtual_ports().size() > idx:
return get_virtual_ports()[idx].index
else:
return -1
func get_all_ports() -> Array[Port]:
return ports
func get_node(uuid: String) -> DeckNode:
return _belonging_to.get_node(uuid)
func to_dict(with_meta: bool = true) -> Dictionary:
var d := {
"_id": _id,
"name": name,
"outgoing_connections": outgoing_connections,
"incoming_connections": incoming_connections,
"props": {},
"node_type": node_type,
"port_values": []
}
for prop in props_to_serialize:
d.props[prop] = get(prop)
ports.map(
func(port: Port) -> void:
d.port_values.append(port.value)
)
if with_meta:
d["meta"] = {}
for meta in get_meta_list():
d["meta"][meta] = get_meta(meta)
return d

104
classes/deck/node_db.gd Normal file
View file

@ -0,0 +1,104 @@
extends Node
const BASE_NODE_PATH := "res://classes/deck/nodes/"
const NODE_INDEX_CACHE_PATH := "user://nodes_index.json"
# Dictionary[node_type, NodeDescriptor]
var nodes: Dictionary = {}
func _init() -> void:
if load_node_index():
return
var dir := DirAccess.open(BASE_NODE_PATH)
dir.list_dir_begin()
var current_file := dir.get_next()
while current_file != "":
print(current_file)
if current_file.ends_with(".gd"):
var script_path := BASE_NODE_PATH.path_join(current_file)
var node: DeckNode = load(script_path).new() as DeckNode
var aliases: String = node.aliases.reduce(
func(accum, el) -> void:
accum += el
, "")
var descriptor := NodeDescriptor.new(script_path, node.name, node.description, aliases)
nodes[node.node_type] = descriptor
current_file = dir.get_next()
save_node_index()
func instance_node(type: String) -> DeckNode:
if !nodes.has(type):
return null
return load(nodes[type]["script_path"]).new()
func save_node_index() -> void:
var d := {}
for node_type in nodes:
var nd: NodeDescriptor = nodes[node_type] as NodeDescriptor
d[node_type] = nd.to_dictionary()
var json := JSON.stringify(d, "\t")
var f := FileAccess.open(NODE_INDEX_CACHE_PATH, FileAccess.WRITE)
f.store_string(json)
func load_node_index() -> bool:
var f := FileAccess.open(NODE_INDEX_CACHE_PATH, FileAccess.READ)
if f == null:
print("node index file does not exist")
return false
var data: Dictionary = JSON.parse_string(f.get_as_text()) as Dictionary
if data.is_empty():
print("node index file exists, but is empty")
return false
for node_type in data:
var nd_dict: Dictionary = data[node_type]
var nd := NodeDescriptor.from_dictionary(nd_dict)
nodes[node_type] = nd
print("node index file exists, loaded")
return true
class NodeDescriptor:
var name: String
var description: String
var aliases: String
var script_path: String
func _init(p_script_path: String, p_name: String, p_description: String, p_aliases: String) -> void:
script_path = p_script_path
name = p_name
description = p_description
aliases = p_aliases
func to_dictionary() -> Dictionary:
var d := {
"name": name,
"description": description,
"aliases": aliases,
"script_path": script_path
}
return d
static func from_dictionary(data: Dictionary) -> NodeDescriptor:
var nd := NodeDescriptor.new(
data.get("script_path", ""),
data.get("name", ""),
data.get("description", ""),
data.get("aliases", ""))
return nd

View file

@ -0,0 +1,13 @@
extends DeckNode
func _init() -> void:
name = "Button"
node_type = "button"
description = "a button"
add_output_port(
Deck.Types.BOOL,
"Press me",
"button"
)

View file

@ -0,0 +1,18 @@
extends DeckNode
func _init() -> void:
name = "Get Deck Var"
node_type = name.to_snake_case()
description = "retrieve a deck variable"
add_output_port(
Deck.Types.STRING,
"Variable",
"field"
)
func _value_request(from_port: int) -> Variant:
var key = ports[0].value_callback.call()
return _belonging_to.variable_stack[key]

View file

@ -0,0 +1,49 @@
extends DeckNode
var times_activated := 0
func _init() -> void:
name = "Print"
node_type = name.to_snake_case()
description = "print a value"
props_to_serialize = [&"times_activated"]
add_input_port(
Deck.Types.STRING,
"Text to print",
"field"
)
add_input_port(
Deck.Types.BOOL,
"Trigger",
"button"
)
add_output_port(
Deck.Types.BOOL,
"On Trigger",
"label"
)
func _receive(to_port: int, data: DeckType, extra_data: Array = []) -> void:
if to_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()
else:
data_to_print = data.get_value()
times_activated += 1
# 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))

View file

@ -0,0 +1,54 @@
extends DeckNode
func _init() -> void:
name = "Set Deck Var"
node_type = name.to_snake_case()
description = "set deck variable"
add_input_port(
Deck.Types.STRING,
"Variable Name",
"field"
)
add_input_port(
Deck.Types.STRING,
"Value",
"field"
)
add_input_port(
Deck.Types.BOOL,
"Set",
"button"
)
add_output_port(
Deck.Types.STRING,
"Value",
"label"
)
func _receive(to_port: int, data: DeckType, extra_data: Array = []) -> void:
if to_port != 2:
return
var var_name: String = get_value_for_port(0, data)
# String for now, until i figure out an Any type
var var_value: String = get_value_for_port(1, data)
_belonging_to.variable_stack[var_name] = var_value
send(3, 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
func get_value_for_port(port: int, data: DeckType) -> Variant:
if request_value(port) != null:
return request_value(port)
elif ports[port].value_callback.call() != "":
return ports[port].value_callback.call()
else:
return data.get_value()

35
classes/deck/port.gd Normal file
View file

@ -0,0 +1,35 @@
class_name Port
var type: Deck.Types
var label: String
var descriptor: String
var value_callback: Callable
var port_type: DeckNode.PortType
var index_of_type: int
var index: int
var value: Variant: set = set_value
func _init(
p_type: Deck.Types,
p_label: String,
p_index: int,
p_port_type: DeckNode.PortType,
p_index_of_type: int,
p_descriptor: String = "",
# p_value_callback: Callable = Callable(),
) -> void:
type = p_type
label = p_label
descriptor = p_descriptor
# value_callback = p_value_callback
port_type = p_port_type
index_of_type = p_index_of_type
index = p_index
func set_value(v: Variant) -> void:
value = v

130
classes/types/deck_type.gd Normal file
View file

@ -0,0 +1,130 @@
class_name DeckType
var _value: Variant
var _success: bool = true
func is_valid() -> bool:
return _success
func get_value() -> Variant:
return _value
func set_value(new_value: Variant) -> void:
_value = new_value
static func from(other: DeckType):
return null
class DeckTypeError extends DeckType:
var error_message: String
func _init(p_error_message: String = ""):
_value = self
_success = false
error_message = p_error_message
static func from(other: DeckType) -> DeckTypeError:
return DeckTypeError.new()
class DeckTypeNumeric extends DeckType:
func _init(value: float = 0.0) -> void:
_value = value
static func from(other: DeckType):
if other is DeckTypeNumeric:
return other
if other is DeckTypeString:
if (other.get_value() as String).is_valid_float():
var value: float = float(other.get_value() as String)
var inst := DeckTypeNumeric.new()
inst._value = value
return inst
else:
var err: DeckTypeError = DeckTypeError.from(other)
err.error_message = "Conversion from String to Numeric failed, check the number"
return err
if other is DeckTypeBool:
var inst := DeckTypeNumeric.new()
inst._value = float(other.get_value() as bool)
return inst
var err: DeckTypeError = DeckTypeError.from(other)
err.error_message = "Conversion to Numeric is only possible from String or Bool."
return err
class DeckTypeString extends DeckType:
func _init(value: String = "") -> void:
_value = value
static func from(other: DeckType):
if other is DeckTypeString:
return other
var inst := DeckTypeString.new()
inst._value = var_to_str(other.get_value())
class DeckTypeBool extends DeckType:
func _init(value: bool = false) -> void:
_value = value
static func from(other: DeckType):
if other is DeckTypeBool:
return other
if other is DeckTypeNumeric:
var inst := DeckTypeBool.new()
inst._value = bool(other.get_value())
return inst
if other is DeckTypeDictionary or other is DeckTypeArray:
var inst := DeckTypeBool.new()
inst._value = !other.get_value().is_empty()
return inst
class DeckTypeArray extends DeckType:
func _init(value: Array = []) -> void:
_value = value
static func from(other: DeckType):
if other is DeckTypeString:
var inst := DeckTypeArray.new()
inst._value = str_to_var(other.get_value())
return inst
var err: DeckTypeError = DeckTypeError.from(other)
err.error_message = "conversions to Array is only possible from String"
return err
class DeckTypeDictionary extends DeckType:
func _init(value: Dictionary = {}) -> void:
_value = value
static func from(other: DeckType):
if other is DeckTypeString:
var inst := DeckTypeDictionary.new()
inst._value = str_to_var(other.get_value())
return inst
var err: DeckTypeError = DeckTypeError.from(other)
err.error_message = "conversions to Dictionary is only possible from String"
return err

View file

@ -0,0 +1,52 @@
extends GraphNode
class_name DeckNodeRendererGraphNode
var node: DeckNode
func _ready() -> void:
title = node.name
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, DeckType.DeckTypeBool.new(true))
)
elif port.port_type == DeckNode.PortType.INPUT:
button.pressed.connect(
func():
node._receive(port.index, 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_position_offset_changed() -> void:
node.set_meta("position_offset", position_offset)

View file

@ -0,0 +1,11 @@
[gd_scene load_steps=2 format=3 uid="uid://vjpj2074hiex"]
[ext_resource type="Script" path="res://graph_node_renderer/deck_node_renderer_graph_node.gd" id="1_pos0w"]
[node name="DeckNodeRendererGraphNode" type="GraphNode"]
offset_right = 280.0
offset_bottom = 217.0
title = "Deck Node"
script = ExtResource("1_pos0w")
[connection signal="position_offset_changed" from="." to="." method="_on_position_offset_changed"]

View file

@ -0,0 +1,154 @@
extends GraphEdit
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")
func _ready() -> void:
var add_button := Button.new()
add_button.text = "Button"
var add_print := Button.new()
add_print.text = "Print"
var get_var := Button.new()
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)
)
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)
)
connection_request.connect(attempt_connection)
disconnection_request.connect(attempt_disconnect)
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_output := from_node.get_global_port_idx_from_output(from_port)
var to_input := to_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)
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_output := from_node.get_global_port_idx_from_output(from_port)
var to_input := to_node.get_global_port_idx_from_input(to_port)
deck.disconnect_nodes(from_node, to_node, from_output, to_input)
disconnect_node(from_node_name, from_port, to_node_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()
var san := s.trim_prefix("(").trim_suffix(")").split(",")
var x := float(san[0])
var y := float(san[1])
return Vector2(x, y)

View file

@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3 uid="uid://b18qpb48df14l"]
[ext_resource type="Script" path="res://graph_node_renderer/deck_renderer_graph_edit.gd" id="1_pojfs"]
[node name="DeckRendererGraphEdit" type="GraphEdit"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
right_disconnects = true
script = ExtResource("1_pojfs")
[connection signal="scroll_offset_changed" from="." to="." method="_on_scroll_offset_changed"]

54
port_drawer.gd Normal file
View file

@ -0,0 +1,54 @@
extends HBoxContainer
@onready var left_slot: ColorRect = $LeftSlot
@onready var right_slot: ColorRect = $RightSlot
var text: String
signal button_pressed
func set_input_enabled(enabled: bool) -> void:
left_slot.visible = enabled
func set_output_enabled(enabled: bool) -> void:
right_slot.visible = enabled
func add_label(text: String) -> void:
var l := Label.new()
add_child(l)
l.text = text
move_child(l, 1)
l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
func add_field(placeholder: String = "") -> void:
var le := LineEdit.new()
add_child(le)
move_child(le, 1)
le.size_flags_horizontal = Control.SIZE_EXPAND_FILL
le.placeholder_text = placeholder
le.text_changed.connect(
func(new_text: String):
text = new_text
)
func get_text() -> String:
return text
func add_button(text: String) -> void:
var b := Button.new()
b.text = text
add_child(b)
move_child(b, 1)
b.size_flags_horizontal = Control.SIZE_EXPAND_FILL
b.pressed.connect(
func():
button_pressed.emit()
)

16
port_drawer.tscn Normal file
View file

@ -0,0 +1,16 @@
[gd_scene load_steps=2 format=3 uid="uid://ddqtmahfxel26"]
[ext_resource type="Script" path="res://port_drawer.gd" id="1_wot5w"]
[node name="PortDrawer" type="HBoxContainer"]
script = ExtResource("1_wot5w")
[node name="LeftSlot" type="ColorRect" parent="."]
visible = false
custom_minimum_size = Vector2(12, 12)
layout_mode = 2
[node name="RightSlot" type="ColorRect" parent="."]
visible = false
custom_minimum_size = Vector2(12, 12)
layout_mode = 2

View file

@ -11,5 +11,14 @@ config_version=5
[application]
config/name="Re-DotDeck"
config/tags=PackedStringArray("dot_deck")
run/main_scene="res://graph_node_renderer/deck_renderer_graph_edit.tscn"
config/use_custom_user_dir=true
config/custom_user_dir_name="dotdeck"
config/features=PackedStringArray("4.1", "Forward Plus")
run/low_processor_mode=true
config/icon="res://icon.svg"
[autoload]
NodeDB="*res://classes/deck/node_db.gd"

53
test_node_renderer.gd Normal file
View file

@ -0,0 +1,53 @@
extends PanelContainer
@onready var name_label: Label = %NameLabel
@onready var elements_container: VBoxContainer = %ElementsContainer
var node: DeckNode
const PortDrawer := preload("res://port_drawer.gd")
var port_drawer_scene := preload("res://port_drawer.tscn")
# THIS IS SUPER JANK AND A HACK FOR DEMONSTRATION PURPOSES
# PLEASE DO NOT ACTUALLY DO ANYTHING THIS CLASS DOES
# IN THE REAL PROJECT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
func _ready() -> void:
name_label.text = node.name
for i in node.input_ports.size():
var input_port := node.input_ports[i]
var port_drawer: PortDrawer = port_drawer_scene.instantiate()
elements_container.add_child(port_drawer)
port_drawer.set_input_enabled(true)
match input_port.descriptor:
"field":
port_drawer.add_field(input_port.label)
input_port.value_callback = port_drawer.get_text
"button":
port_drawer.add_button(input_port.label)
port_drawer.button_pressed.connect(func():
node._receive(i, DeckType.DeckTypeString.new("memes"))
)
_:
port_drawer.add_label(input_port.label)
for i in node.output_ports.size():
if elements_container.get_child_count() - 1 < i:
var pd: PortDrawer = port_drawer_scene.instantiate()
elements_container.add_child(pd)
var port_drawer: PortDrawer = elements_container.get_child(i)
port_drawer.set_output_enabled(true)
var output_port := node.output_ports[i]
match output_port.descriptor:
"field":
port_drawer.add_field(output_port.label)
output_port.value_callback = port_drawer.get_text
"button":
port_drawer.add_button(output_port.label)
port_drawer.button_pressed.connect(func():
node.send(i, DeckType.DeckTypeBool.new(true))
)
_:
port_drawer.add_label(output_port.label)

39
test_node_renderer.tscn Normal file
View file

@ -0,0 +1,39 @@
[gd_scene load_steps=2 format=3 uid="uid://ch8s1d7vobhi4"]
[ext_resource type="Script" path="res://test_node_renderer.gd" id="1_85wy1"]
[node name="TestNodeRenderer" type="PanelContainer"]
custom_minimum_size = Vector2(300, 0)
anchors_preset = -1
anchor_right = 0.26
anchor_bottom = 0.34
offset_right = 0.47998
offset_bottom = -0.320007
script = ExtResource("1_85wy1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="ColorRect" type="ColorRect" parent="VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(4, 0)
layout_mode = 2
color = Color(1, 1, 0.34902, 1)
[node name="NameLabel" type="Label" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Name"
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
layout_mode = 2
[node name="ElementsContainer" type="VBoxContainer" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3