diff --git a/classes/deck/deck.gd b/classes/deck/deck.gd
index e802616..9a322b2 100644
--- a/classes/deck/deck.gd
+++ b/classes/deck/deck.gd
@@ -367,37 +367,37 @@ func copy_nodes(nodes_to_copy: Array[String]) -> Dictionary:
for node_id: String in nodes_to_copy:
d.nodes[node_id] = get_node(node_id).to_dict()
- for node: String in d.nodes:
- var outgoing_connections: Dictionary = d.nodes[node].outgoing_connections.duplicate(true)
-
- for from_port: int in outgoing_connections:
- for to_node: String in outgoing_connections[from_port]:
- if to_node not in nodes_to_copy:
- (d.nodes[node].outgoing_connections[from_port] as Dictionary).erase(to_node)
-
- var keys_to_erase := []
- for from_port: int in d.nodes[node].outgoing_connections:
- if (d.nodes[node].outgoing_connections[from_port] as Dictionary).is_empty():
- keys_to_erase.append(from_port)
-
- for key in keys_to_erase:
- (d.nodes[node].outgoing_connections as Dictionary).erase(key)
-
- var incoming_connections: Dictionary = d.nodes[node].incoming_connections.duplicate(true)
-
- for to_port: int in incoming_connections:
- for from_node: String in incoming_connections[to_port]:
- if from_node not in nodes_to_copy:
- (d.nodes[node].incoming_connections[to_port] as Dictionary).erase(from_node)
-
- keys_to_erase.clear()
- for to_port: int in d.nodes[node].incoming_connections:
- if (d.nodes[node].incoming_connections[to_port] as Dictionary).is_empty():
- keys_to_erase.append(to_port)
-
- for key in keys_to_erase:
- (d.nodes[node].incoming_connections as Dictionary).erase(key)
- keys_to_erase.clear()
+ #for node: String in d.nodes:
+ #var outgoing_connections: Dictionary = d.nodes[node].outgoing_connections.duplicate(true)
+#
+ #for from_port: int in outgoing_connections:
+ #for to_node: String in outgoing_connections[from_port]:
+ #if to_node not in nodes_to_copy:
+ #(d.nodes[node].outgoing_connections[from_port] as Dictionary).erase(to_node)
+#
+ #var keys_to_erase := []
+ #for from_port: int in d.nodes[node].outgoing_connections:
+ #if (d.nodes[node].outgoing_connections[from_port] as Dictionary).is_empty():
+ #keys_to_erase.append(from_port)
+#
+ #for key in keys_to_erase:
+ #(d.nodes[node].outgoing_connections as Dictionary).erase(key)
+#
+ #var incoming_connections: Dictionary = d.nodes[node].incoming_connections.duplicate(true)
+#
+ #for to_port: int in incoming_connections:
+ #for from_node: String in incoming_connections[to_port]:
+ #if from_node not in nodes_to_copy:
+ #(d.nodes[node].incoming_connections[to_port] as Dictionary).erase(from_node)
+ #
+ #keys_to_erase.clear()
+ #for to_port: int in d.nodes[node].incoming_connections:
+ #if (d.nodes[node].incoming_connections[to_port] as Dictionary).is_empty():
+ #keys_to_erase.append(to_port)
+#
+ #for key in keys_to_erase:
+ #(d.nodes[node].incoming_connections as Dictionary).erase(key)
+ #keys_to_erase.clear()
for node: Dictionary in d.nodes.values().slice(1):
node.position.x = node.position.x - d.nodes.values()[0].position.x
@@ -468,10 +468,15 @@ func paste_nodes_from_dict(nodes_to_paste: Dictionary, position: Vector2 = Vecto
add_node_inst(node, ids_map[node_id])
if group_needs_unique:
node.make_unique()
+ DeckHolder.logger.toast_warn("Group was made unique.")
func paste_nodes_from_json(json: String, position: Vector2 = Vector2()) -> void:
- paste_nodes_from_dict(JSON.parse_string(json), position)
+ var d = JSON.parse_string(json)
+ if not d:
+ DeckHolder.logger.toast_error("Paste failed.")
+ return
+ paste_nodes_from_dict(d, position)
func duplicate_nodes(nodes_to_copy: Array[String]) -> void:
@@ -595,7 +600,7 @@ static func from_dict(data: Dictionary, path: String = "") -> Deck:
var nodes_data: Dictionary = data.deck.nodes as Dictionary
for node_id in nodes_data:
- var node := DeckNode.from_dict(nodes_data[node_id])
+ var node := DeckNode.from_dict(nodes_data[node_id], deck.connections)
deck.add_node_inst(node, node_id)
var groups_data: Dictionary = data.deck.groups as Dictionary
diff --git a/classes/deck/deck_node.gd b/classes/deck/deck_node.gd
index b234306..1070b6d 100644
--- a/classes/deck/deck_node.gd
+++ b/classes/deck/deck_node.gd
@@ -239,7 +239,7 @@ func _pre_port_load() -> void:
## Virtual function that's called after the node has been deserialized.
-func _post_load() -> void:
+func _post_load(connections: Deck.NodeConnections) -> void:
pass
@@ -289,7 +289,7 @@ func to_dict(with_meta: bool = true) -> Dictionary:
return d
-static func from_dict(data: Dictionary) -> DeckNode:
+static func from_dict(data: Dictionary, connections: Deck.NodeConnections = null) -> DeckNode:
var node := NodeDB.instance_node(data.node_type)
#node._id = data._id
node.name = data.name
@@ -326,7 +326,7 @@ static func from_dict(data: Dictionary) -> DeckNode:
for key in data.meta:
node.set_meta(key, str_to_var(data.meta[key]))
- node._post_load()
+ node._post_load(connections)
return node
diff --git a/classes/deck/logger.gd b/classes/deck/logger.gd
index f71a287..c219650 100644
--- a/classes/deck/logger.gd
+++ b/classes/deck/logger.gd
@@ -17,6 +17,7 @@ enum LogCategory {
}
signal log_message(text: String, type: LogType, category: LogCategory)
+signal log_toast(text: String, type: LogType)
func log_node(text: Variant, type: LogType = LogType.INFO) -> void:
@@ -40,3 +41,22 @@ func log(text: String, type: LogType, category: LogCategory) -> void:
if OS.has_feature("editor"):
prints(LogType.keys()[type].capitalize(), LogCategory.keys()[category].capitalize(), text)
+
+
+func toast_info(text: String) -> void:
+ toast(text, LogType.INFO)
+
+
+func toast_warn(text: String) -> void:
+ toast(text, LogType.WARN)
+
+
+func toast_error(text: String) -> void:
+ toast(text, LogType.ERROR)
+
+
+func toast(text: String, type: LogType) -> void:
+ log_toast.emit(text, type)
+
+ if OS.has_feature("editor"):
+ prints("(t)", LogType.keys()[type].capitalize(), text)
diff --git a/classes/deck/nodes/group/group_input_node.gd b/classes/deck/nodes/group/group_input_node.gd
index 982631f..221e317 100644
--- a/classes/deck/nodes/group/group_input_node.gd
+++ b/classes/deck/nodes/group/group_input_node.gd
@@ -59,10 +59,10 @@ func _pre_port_load() -> void:
)
-func _post_load() -> void:
+func _post_load(connections: Deck.NodeConnections) -> void:
# ensure we have enough ports after connections
var last_connected_port := 0
- var outgoing_connections = _belonging_to.connections.get_all_outgoing_connections(_id)
+ var outgoing_connections = connections.get_all_outgoing_connections(_id)
for port: int in outgoing_connections:
last_connected_port = port if outgoing_connections.has(port) else last_connected_port
diff --git a/classes/deck/nodes/group/group_output_node.gd b/classes/deck/nodes/group/group_output_node.gd
index 10781a8..e04606d 100644
--- a/classes/deck/nodes/group/group_output_node.gd
+++ b/classes/deck/nodes/group/group_output_node.gd
@@ -60,10 +60,10 @@ func _pre_port_load() -> void:
)
-func _post_load() -> void:
+func _post_load(connections: Deck.NodeConnections) -> void:
# ensure we have enough ports after connections
var last_connected_port := 0
- var incoming_connections := _belonging_to.connections.get_all_incoming_connections(_id)
+ var incoming_connections := connections.get_all_incoming_connections(_id)
for port: int in incoming_connections:
last_connected_port = port if incoming_connections.has(port) else last_connected_port
diff --git a/graph_node_renderer/deck_holder_renderer.tscn b/graph_node_renderer/deck_holder_renderer.tscn
index 548e50f..c072686 100644
--- a/graph_node_renderer/deck_holder_renderer.tscn
+++ b/graph_node_renderer/deck_holder_renderer.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=18 format=3 uid="uid://duaah5x0jhkn6"]
+[gd_scene load_steps=19 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"]
@@ -9,6 +9,7 @@
[ext_resource type="Script" path="res://addons/no_twitch/twitch_connection.gd" id="5_3n36q"]
[ext_resource type="PackedScene" uid="uid://cddfyvpf5nqq7" path="res://graph_node_renderer/debug_nodes_list.tscn" id="5_pnfg8"]
[ext_resource type="PackedScene" uid="uid://eioso6jb42jy" path="res://graph_node_renderer/obs_websocket_setup_dialog.tscn" id="5_uo2gj"]
+[ext_resource type="PackedScene" uid="uid://234k3tn8ny01" path="res://graph_node_renderer/toast_renderer.tscn" id="6_5532r"]
[ext_resource type="PackedScene" uid="uid://b56hjad0ih0gu" path="res://graph_node_renderer/sidebar/sidebar.tscn" id="7_0lv5b"]
[ext_resource type="PackedScene" uid="uid://bq2lxmbnic4lc" path="res://graph_node_renderer/twitch_setup_dialog.tscn" id="7_7rhap"]
[ext_resource type="PackedScene" uid="uid://cuwou2aa7qfc2" path="res://graph_node_renderer/unsaved_changes_dialog_single_deck.tscn" id="8_qf6ve"]
@@ -128,6 +129,9 @@ item_1/id = 1
unique_name_in_owner = true
layout_mode = 2
+[node name="ToastRenderer" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer" instance=ExtResource("6_5532r")]
+layout_mode = 2
+
[node name="BottomDock" parent="MarginContainer/SidebarSplit/BottomSplit" instance=ExtResource("4_gwnhy")]
unique_name_in_owner = true
visible = false
diff --git a/graph_node_renderer/default_theme.tres b/graph_node_renderer/default_theme.tres
index 55509cb..0a26704 100644
--- a/graph_node_renderer/default_theme.tres
+++ b/graph_node_renderer/default_theme.tres
@@ -1,4 +1,4 @@
-[gd_resource type="Theme" load_steps=5 format=3 uid="uid://dqqdqscid2iem"]
+[gd_resource type="Theme" load_steps=6 format=3 uid="uid://dqqdqscid2iem"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dt61u"]
content_margin_left = 4.0
@@ -36,6 +36,18 @@ corner_radius_bottom_left = 4
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q56mu"]
bg_color = Color(0.623529, 0.564706, 0.909804, 0.0588235)
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_w8gqp"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+bg_color = Color(0.12549, 0.12549, 0.12549, 0.627451)
+border_color = Color(0.545098, 0.545098, 0.545098, 0.627451)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
[resource]
default_font_size = 14
AccordionButton/base_type = &"Button"
@@ -43,3 +55,5 @@ AccordionButton/styles/hover = SubResource("StyleBoxFlat_dt61u")
AccordionButton/styles/normal = SubResource("StyleBoxFlat_fmm5o")
AccordionButton/styles/pressed = SubResource("StyleBoxFlat_5q5t0")
AccordionMenu/styles/background = SubResource("StyleBoxFlat_q56mu")
+ToastPanel/base_type = &"PanelContainer"
+ToastPanel/styles/panel = SubResource("StyleBoxFlat_w8gqp")
diff --git a/graph_node_renderer/textures/error_icon_color.svg b/graph_node_renderer/textures/error_icon_color.svg
new file mode 100644
index 0000000..d6e3ebe
--- /dev/null
+++ b/graph_node_renderer/textures/error_icon_color.svg
@@ -0,0 +1 @@
+
diff --git a/graph_node_renderer/textures/error_icon_color.svg.import b/graph_node_renderer/textures/error_icon_color.svg.import
new file mode 100644
index 0000000..2a1aa75
--- /dev/null
+++ b/graph_node_renderer/textures/error_icon_color.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://t3s83w2wkyqp"
+path="res://.godot/imported/error_icon_color.svg-36e8290c5e271c0fa763289e90b46d3d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://graph_node_renderer/textures/error_icon_color.svg"
+dest_files=["res://.godot/imported/error_icon_color.svg-36e8290c5e271c0fa763289e90b46d3d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/graph_node_renderer/textures/info_icon.svg b/graph_node_renderer/textures/info_icon.svg
new file mode 100644
index 0000000..732bccf
--- /dev/null
+++ b/graph_node_renderer/textures/info_icon.svg
@@ -0,0 +1 @@
+
diff --git a/graph_node_renderer/textures/info_icon.svg.import b/graph_node_renderer/textures/info_icon.svg.import
new file mode 100644
index 0000000..beb5920
--- /dev/null
+++ b/graph_node_renderer/textures/info_icon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bdv8ejbjaujvm"
+path="res://.godot/imported/info_icon.svg-c3f8f29486a0a0cd1cfd29f9859897eb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://graph_node_renderer/textures/info_icon.svg"
+dest_files=["res://.godot/imported/info_icon.svg-c3f8f29486a0a0cd1cfd29f9859897eb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/graph_node_renderer/textures/warning_icon_color.svg b/graph_node_renderer/textures/warning_icon_color.svg
new file mode 100644
index 0000000..1f7b900
--- /dev/null
+++ b/graph_node_renderer/textures/warning_icon_color.svg
@@ -0,0 +1 @@
+
diff --git a/graph_node_renderer/textures/warning_icon_color.svg.import b/graph_node_renderer/textures/warning_icon_color.svg.import
new file mode 100644
index 0000000..8c6e94e
--- /dev/null
+++ b/graph_node_renderer/textures/warning_icon_color.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cmqrl3e53unmv"
+path="res://.godot/imported/warning_icon_color.svg-944b7caf1a46684fe55272e6b2f36f61.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://graph_node_renderer/textures/warning_icon_color.svg"
+dest_files=["res://.godot/imported/warning_icon_color.svg-944b7caf1a46684fe55272e6b2f36f61.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/graph_node_renderer/toast_renderer.gd b/graph_node_renderer/toast_renderer.gd
new file mode 100644
index 0000000..3c1b8ef
--- /dev/null
+++ b/graph_node_renderer/toast_renderer.gd
@@ -0,0 +1,75 @@
+# (c) 2023-present Eroax
+# (c) 2023-present Yagich
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+extends Control
+class_name ToastRenderer
+
+
+const ERROR_ICON = preload("res://graph_node_renderer/textures/error_icon_color.svg")
+const WARNING_ICON = preload("res://graph_node_renderer/textures/warning_icon_color.svg")
+const INFO_ICON = preload("res://graph_node_renderer/textures/info_icon.svg")
+
+@export var hold_time: float = 1.5
+@export var limit: int = 5
+
+@onready var vbox: VBoxContainer = %VBoxContainer
+
+
+func _ready() -> void:
+ DeckHolder.logger.log_toast.connect(_on_logger_toast)
+
+
+func _on_logger_toast(text: String, type: Logger.LogType) -> void:
+ var panel := PanelContainer.new()
+ panel.theme_type_variation = &"ToastPanel"
+ var hb := HBoxContainer.new()
+ var icon := TextureRect.new()
+ icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
+
+ match type:
+ Logger.LogType.INFO:
+ icon.texture = INFO_ICON
+ Logger.LogType.WARN:
+ icon.texture = WARNING_ICON
+ Logger.LogType.ERROR:
+ icon.texture = ERROR_ICON
+
+ var label := Label.new()
+ label.text = text
+
+ hb.add_child(icon)
+ hb.add_child(label)
+
+ panel.add_child(hb)
+
+ move_and_remove()
+
+ panel.modulate.a = 0.0
+ vbox.add_child(panel)
+ var t := panel.create_tween()
+ t.tween_property(panel, ^"modulate:a", 1.0, 0.1)
+ t.tween_interval(hold_time)
+ t.tween_property(panel, ^"modulate:a", 0.0, 1.0)
+ t.finished.connect(
+ func():
+ panel.queue_free()
+ )
+
+
+func move_and_remove() -> void:
+ if not (vbox.get_child_count() + 1 > limit):
+ return
+
+ for i: Node in vbox.get_children().slice(0, limit):
+ if i.get_meta(&"queued", false):
+ continue
+
+ i.set_meta(&"queued", true)
+ var t := i.create_tween()
+ t.tween_property(i, ^"position:x", i.position.x + i.size.x + 32, 0.4).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN)
+ t.finished.connect(
+ func():
+ i.queue_free()
+ )
+ break
+
diff --git a/graph_node_renderer/toast_renderer.tscn b/graph_node_renderer/toast_renderer.tscn
new file mode 100644
index 0000000..969d00e
--- /dev/null
+++ b/graph_node_renderer/toast_renderer.tscn
@@ -0,0 +1,27 @@
+[gd_scene load_steps=2 format=3 uid="uid://234k3tn8ny01"]
+
+[ext_resource type="Script" path="res://graph_node_renderer/toast_renderer.gd" id="1_446pb"]
+
+[node name="ToastRenderer" type="Control"]
+layout_mode = 3
+anchors_preset = 0
+script = ExtResource("1_446pb")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -490.0
+offset_top = -312.0
+offset_right = -32.0
+offset_bottom = -32.0
+grow_horizontal = 0
+grow_vertical = 0
+alignment = 2
+
+[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"]
+layout_mode = 2