summaryrefslogtreecommitdiff
path: root/addons/gdUnit4/src/ui/parts
diff options
context:
space:
mode:
authorradiohotline <radoihotline@disroot.rg>2026-08-25 16:33:56 +0300
committerradiohotline <radoihotline@disroot.rg>2026-08-25 16:33:56 +0300
commita89e744ed0e8c759caf041a580112365bbfe4303 (patch)
treedd61e320904176995727e86a857d00c90d4933ba /addons/gdUnit4/src/ui/parts
parent6d53d8cac78250dc888aa49d5f9d4175c6652feb (diff)
escoria updateHEADmain
Diffstat (limited to 'addons/gdUnit4/src/ui/parts')
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorMonitor.gd54
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorMonitor.gd.uid1
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn94
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd49
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd.uid1
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn33
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd209
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd.uid1
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn465
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorToolBar.gd104
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorToolBar.gd.uid1
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn202
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd1210
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd.uid1
-rw-r--r--addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn240
15 files changed, 2665 insertions, 0 deletions
diff --git a/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd b/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd
new file mode 100644
index 0000000..c36b3ec
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd
@@ -0,0 +1,54 @@
+@tool
+extends PanelContainer
+
+signal jump_to_orphan_nodes()
+
+@onready var ICON_GREEN := GdUnitUiTools.get_icon("Unlinked", Color.WEB_GREEN)
+@onready var ICON_RED := GdUnitUiTools.get_color_animated_icon("Unlinked", Color.YELLOW, Color.ORANGE_RED)
+
+@onready var _button_time: Button = %btn_time
+@onready var _time: Label = %time_value
+@onready var _orphans: Label = %orphan_value
+@onready var _orphan_button: Button = %btn_orphan
+
+var total_elapsed_time := 0
+var total_orphans := 0
+
+
+func _ready() -> void:
+ @warning_ignore("return_value_discarded")
+ GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event)
+ _time.text = ""
+ _orphans.text = "0"
+ _button_time.icon = GdUnitUiTools.get_icon("Time")
+ _orphan_button.icon = ICON_GREEN
+
+
+func status_changed(elapsed_time: int, orphan_nodes: int) -> void:
+ total_elapsed_time += elapsed_time
+ total_orphans += orphan_nodes
+ _time.text = LocalTime.elapsed(total_elapsed_time)
+ _orphans.text = str(total_orphans)
+ if total_orphans > 0:
+ _orphan_button.icon = ICON_RED
+
+
+func _on_gdunit_event(event: GdUnitEvent) -> void:
+ match event.type():
+ GdUnitEvent.INIT:
+ _orphan_button.icon = ICON_GREEN
+ total_elapsed_time = 0
+ total_orphans = 0
+ status_changed(0, 0)
+ GdUnitEvent.TESTCASE_BEFORE:
+ pass
+ GdUnitEvent.TESTCASE_AFTER:
+ status_changed(0, event.orphan_nodes())
+ GdUnitEvent.TESTSUITE_BEFORE:
+ pass
+ GdUnitEvent.TESTSUITE_AFTER:
+ status_changed(event.elapsed_time(), event.orphan_nodes())
+
+
+func _on_ToolButton_pressed() -> void:
+ jump_to_orphan_nodes.emit()
diff --git a/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd.uid b/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd.uid
new file mode 100644
index 0000000..ea72742
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd.uid
@@ -0,0 +1 @@
+uid://buiiqjcucwy8g
diff --git a/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn b/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn
new file mode 100644
index 0000000..0262b8c
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn
@@ -0,0 +1,94 @@
+[gd_scene load_steps=6 format=3 uid="uid://djp8ait0bxpsc"]
+
+[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorMonitor.gd" id="3"]
+
+[sub_resource type="Image" id="Image_sx31i"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 4, 227, 227, 227, 36, 227, 227, 227, 36, 255, 255, 255, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 131, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 5, 225, 225, 225, 76, 224, 224, 224, 255, 224, 224, 224, 255, 226, 226, 226, 77, 255, 255, 255, 5, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 99, 224, 224, 224, 232, 224, 224, 224, 244, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 244, 224, 224, 224, 233, 224, 224, 224, 97, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 135, 224, 224, 224, 247, 224, 224, 224, 115, 234, 234, 234, 12, 224, 224, 224, 130, 224, 224, 224, 130, 234, 234, 234, 12, 225, 225, 225, 116, 224, 224, 224, 248, 224, 224, 224, 132, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 77, 224, 224, 224, 251, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 66, 224, 224, 224, 252, 225, 225, 225, 75, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 201, 224, 224, 224, 146, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 2, 224, 224, 224, 146, 224, 224, 224, 106, 255, 255, 255, 0, 225, 225, 225, 150, 224, 224, 224, 195, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 24, 224, 224, 224, 255, 226, 226, 226, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 233, 233, 233, 23, 225, 225, 225, 166, 224, 224, 224, 237, 228, 228, 228, 47, 255, 255, 255, 0, 225, 225, 225, 51, 224, 224, 224, 255, 224, 224, 224, 16, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 67, 224, 224, 224, 255, 225, 225, 225, 215, 227, 227, 227, 9, 255, 255, 255, 0, 255, 255, 255, 0, 223, 223, 223, 239, 224, 224, 224, 253, 224, 224, 224, 49, 255, 255, 255, 0, 230, 230, 230, 30, 224, 224, 224, 230, 224, 224, 224, 255, 224, 224, 224, 49, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 41, 224, 224, 224, 255, 225, 225, 225, 101, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 139, 224, 224, 224, 139, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 5, 225, 225, 225, 117, 224, 224, 224, 255, 224, 224, 224, 33, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 6, 224, 224, 224, 240, 226, 226, 226, 87, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 96, 224, 224, 224, 236, 255, 255, 255, 3, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 143, 224, 224, 224, 211, 224, 224, 224, 8, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 232, 232, 232, 11, 224, 224, 224, 216, 225, 225, 225, 141, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 238, 238, 238, 15, 224, 224, 224, 220, 224, 224, 224, 178, 238, 238, 238, 15, 255, 255, 255, 0, 225, 225, 225, 51, 225, 225, 225, 51, 255, 255, 255, 0, 227, 227, 227, 18, 224, 224, 224, 184, 224, 224, 224, 218, 238, 238, 238, 15, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 227, 227, 227, 36, 224, 224, 224, 212, 224, 224, 224, 232, 225, 225, 225, 133, 224, 224, 224, 251, 224, 224, 224, 240, 225, 225, 225, 135, 224, 224, 224, 234, 224, 224, 224, 208, 225, 225, 225, 34, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 230, 230, 230, 10, 224, 224, 224, 107, 224, 224, 224, 197, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 196, 224, 224, 224, 104, 224, 224, 224, 8, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_ugpqy"]
+image = SubResource("Image_sx31i")
+
+[sub_resource type="Image" id="Image_gkq5u"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 251, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 234, 22, 138, 22, 247, 22, 138, 22, 253, 22, 138, 22, 253, 22, 138, 22, 247, 22, 138, 22, 233, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 251, 22, 138, 22, 236, 255, 255, 255, 0, 22, 138, 22, 255, 255, 255, 255, 0, 23, 138, 23, 233, 22, 138, 22, 254, 22, 138, 22, 255, 22, 138, 22, 255, 22, 138, 22, 255, 22, 138, 22, 255, 22, 138, 22, 253, 23, 138, 23, 233, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 236, 22, 138, 22, 253, 22, 138, 22, 236, 22, 138, 22, 251, 255, 255, 255, 0, 22, 138, 22, 247, 22, 138, 22, 255, 22, 138, 22, 248, 22, 138, 22, 233, 23, 138, 23, 233, 22, 138, 22, 249, 22, 138, 22, 255, 22, 138, 22, 246, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 236, 22, 138, 22, 251, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 249, 22, 138, 22, 253, 23, 138, 23, 232, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 234, 22, 138, 22, 255, 22, 138, 22, 253, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 251, 22, 138, 22, 255, 22, 138, 22, 251, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 24, 139, 24, 231, 23, 138, 23, 231, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 23, 138, 23, 234, 22, 138, 22, 255, 22, 138, 22, 253, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 23, 138, 23, 231, 23, 138, 23, 234, 22, 138, 22, 249, 22, 138, 22, 255, 22, 138, 22, 246, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 23, 138, 23, 233, 22, 138, 22, 247, 22, 138, 22, 249, 24, 139, 24, 231, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 23, 138, 23, 245, 22, 138, 22, 255, 22, 138, 22, 255, 22, 138, 22, 255, 22, 138, 22, 253, 23, 138, 23, 233, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 234, 22, 138, 22, 254, 22, 138, 22, 255, 22, 138, 22, 253, 23, 138, 23, 231, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 23, 138, 23, 241, 22, 138, 22, 253, 22, 138, 22, 253, 22, 138, 22, 246, 22, 138, 22, 233, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 247, 22, 138, 22, 255, 22, 138, 22, 248, 23, 138, 23, 232, 255, 255, 255, 0, 255, 255, 255, 0, 23, 138, 23, 245, 23, 138, 23, 241, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 253, 22, 138, 22, 255, 22, 138, 22, 233, 255, 255, 255, 0, 255, 255, 255, 0, 23, 138, 23, 231, 22, 138, 22, 255, 22, 138, 22, 253, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 253, 22, 138, 22, 255, 23, 138, 23, 233, 255, 255, 255, 0, 255, 255, 255, 0, 23, 138, 23, 234, 22, 138, 22, 255, 22, 138, 22, 253, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 247, 22, 138, 22, 255, 22, 138, 22, 249, 22, 138, 22, 234, 23, 138, 23, 234, 22, 138, 22, 249, 22, 138, 22, 255, 22, 138, 22, 246, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 22, 138, 22, 233, 22, 138, 22, 253, 22, 138, 22, 255, 22, 138, 22, 255, 22, 138, 22, 255, 22, 138, 22, 255, 22, 138, 22, 253, 22, 138, 22, 233, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 23, 138, 23, 233, 22, 138, 22, 246, 22, 138, 22, 253, 22, 138, 22, 253, 22, 138, 22, 246, 23, 138, 23, 233, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_nj5du"]
+image = SubResource("Image_gkq5u")
+
+[node name="Monitor" type="PanelContainer"]
+clip_contents = true
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_right = -793.0
+offset_bottom = -564.0
+size_flags_horizontal = 9
+size_flags_vertical = 9
+script = ExtResource("3")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_vertical = 4
+
+[node name="timer" type="HBoxContainer" parent="HBoxContainer"]
+layout_mode = 2
+
+[node name="btn_time" type="Button" parent="HBoxContainer/timer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+auto_translate = false
+localize_numeral_system = false
+tooltip_text = "Shows the total elapsed time of test execution."
+mouse_force_pass_scroll_events = false
+button_mask = 0
+shortcut_feedback = false
+shortcut_in_tooltip = false
+text = "Time"
+icon = SubResource("ImageTexture_ugpqy")
+flat = true
+
+[node name="time_value" type="Label" parent="HBoxContainer/timer"]
+unique_name_in_owner = true
+use_parent_material = true
+layout_mode = 2
+size_flags_horizontal = 3
+auto_translate = false
+localize_numeral_system = false
+max_lines_visible = 1
+
+[node name="orphan" type="HBoxContainer" parent="HBoxContainer/timer"]
+layout_mode = 2
+
+[node name="btn_orphan" type="Button" parent="HBoxContainer/timer/orphan"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+auto_translate = false
+localize_numeral_system = false
+tooltip_text = "Shows the total orphan nodes detected."
+text = "Orphans"
+icon = SubResource("ImageTexture_nj5du")
+
+[node name="orphan_value" type="Label" parent="HBoxContainer/timer/orphan"]
+unique_name_in_owner = true
+use_parent_material = true
+layout_mode = 2
+size_flags_horizontal = 3
+auto_translate = false
+localize_numeral_system = false
+text = "0"
+max_lines_visible = 1
diff --git a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd
new file mode 100644
index 0000000..d368ed3
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd
@@ -0,0 +1,49 @@
+@tool
+extends ProgressBar
+
+
+@onready var status: Label = $Label
+@onready var style: StyleBoxFlat = get("theme_override_styles/fill")
+
+var _state: GdUnitInspectorTreeConstants.STATE
+
+func _ready() -> void:
+ style.bg_color = Color.DARK_GREEN
+ value = 0
+ max_value = 0
+ update_text()
+
+
+func update_text() -> void:
+ status.text = "%d:%d" % [value, max_value]
+
+
+func _on_test_counter_changed(index: int, total: int, state: GdUnitInspectorTreeConstants.STATE) -> void:
+ value = index
+ max_value = total
+ update_text()
+
+ # inital state
+ if index == 0:
+ style.bg_color = Color.DARK_GREEN
+
+ # do only update the state is higher prio than current state
+ if state <= _state:
+ return
+ _state = state
+
+ if is_flaky(state):
+ style.bg_color = Color.WEB_GREEN
+ if is_failed(state):
+ style.bg_color = Color.DARK_RED
+
+
+func is_failed(state: GdUnitInspectorTreeConstants.STATE) -> bool:
+ return state in [
+ GdUnitInspectorTreeConstants.STATE.FAILED,
+ GdUnitInspectorTreeConstants.STATE.ERROR,
+ GdUnitInspectorTreeConstants.STATE.ABORDED]
+
+
+func is_flaky(state: GdUnitInspectorTreeConstants.STATE) -> bool:
+ return state == GdUnitInspectorTreeConstants.STATE.FLAKY
diff --git a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd.uid b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd.uid
new file mode 100644
index 0000000..61df203
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd.uid
@@ -0,0 +1 @@
+uid://de8bjgxoj10u3
diff --git a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn
new file mode 100644
index 0000000..1824230
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn
@@ -0,0 +1,33 @@
+[gd_scene load_steps=3 format=3 uid="uid://dva3tonxsxrlk"]
+
+[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd" id="1"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ayfir"]
+bg_color = Color(0, 0.392157, 0, 1)
+
+[node name="ProgressBar" type="ProgressBar"]
+custom_minimum_size = Vector2(0, 20)
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 9
+theme_override_styles/fill = SubResource("StyleBoxFlat_ayfir")
+rounded = true
+allow_greater = true
+show_percentage = false
+script = ExtResource("1")
+
+[node name="Label" type="Label" parent="."]
+use_parent_material = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+horizontal_alignment = 1
+vertical_alignment = 1
+max_lines_visible = 1
diff --git a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd
new file mode 100644
index 0000000..1863459
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd
@@ -0,0 +1,209 @@
+@tool
+extends PanelContainer
+
+signal select_failure_next()
+signal select_failure_prevous()
+signal select_error_next()
+signal select_error_prevous()
+signal select_flaky_next()
+signal select_flaky_prevous()
+signal select_skipped_next()
+signal select_skipped_prevous()
+
+@warning_ignore("unused_signal")
+signal tree_view_mode_changed(flat :bool)
+
+@onready var _errors: Label = %error_value
+@onready var _failures: Label = %failure_value
+@onready var _flaky_value: Label = %flaky_value
+@onready var _skipped_value: Label = %skipped_value
+#@onready var _button_failure_up: Button = %btn_failure_up
+#@onready var _button_failure_down: Button = %btn_failure_down
+@onready var _button_sync: Button = %btn_tree_sync
+@onready var _button_view_mode: MenuButton = %btn_tree_mode
+@onready var _button_sort_mode: MenuButton = %btn_tree_sort
+
+@onready var _icon_errors: TextureRect = %icon_errors
+@onready var _icon_failures: TextureRect = %icon_failures
+@onready var _icon_flaky: TextureRect = %icon_flaky
+@onready var _icon_skipped: TextureRect = %icon_skipped
+
+var total_failed := 0
+var total_errors := 0
+var total_flaky := 0
+var total_skipped := 0
+
+
+var icon_mappings := {
+ # tree sort modes
+ 0x100 + GdUnitInspectorTreeConstants.SORT_MODE.UNSORTED : GdUnitUiTools.get_icon("TripleBar"),
+ 0x100 + GdUnitInspectorTreeConstants.SORT_MODE.NAME_ASCENDING : GdUnitUiTools.get_icon("Sort"),
+ 0x100 + GdUnitInspectorTreeConstants.SORT_MODE.NAME_DESCENDING : GdUnitUiTools.get_flipped_icon("Sort"),
+ 0x100 + GdUnitInspectorTreeConstants.SORT_MODE.EXECUTION_TIME : GdUnitUiTools.get_icon("History"),
+ # tree view modes
+ 0x200 + GdUnitInspectorTreeConstants.TREE_VIEW_MODE.TREE : GdUnitUiTools.get_icon("Tree", Color.GHOST_WHITE),
+ 0x200 + GdUnitInspectorTreeConstants.TREE_VIEW_MODE.FLAT : GdUnitUiTools.get_icon("AnimationTrackGroup", Color.GHOST_WHITE)
+}
+
+
+@warning_ignore("return_value_discarded")
+func _ready() -> void:
+ _failures.text = "0"
+ _errors.text = "0"
+ _flaky_value.text = "0"
+ _skipped_value.text = "0"
+ _icon_failures.texture = GdUnitUiTools.get_icon("StatusError", Color.SKY_BLUE)
+ _icon_errors.texture = GdUnitUiTools.get_icon("StatusError", Color.DARK_RED)
+ _icon_flaky.texture = GdUnitUiTools.get_icon("CheckBox", Color.GREEN_YELLOW)
+ _icon_skipped.texture = GdUnitUiTools.get_icon("CheckBox", Color.WEB_GRAY)
+
+ #_button_failure_up.icon = GdUnitUiTools.get_icon("ArrowUp")
+ #_button_failure_down.icon = GdUnitUiTools.get_icon("ArrowDown")
+ _button_sync.icon = GdUnitUiTools.get_icon("Loop")
+ _set_sort_mode_menu_options()
+ _set_view_mode_menu_options()
+ GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event)
+ GdUnitSignals.instance().gdunit_settings_changed.connect(_on_settings_changed)
+
+
+func _set_sort_mode_menu_options() -> void:
+ _button_sort_mode.icon = GdUnitUiTools.get_icon("Sort")
+ # construct context sort menu according to the available modes
+ var context_menu :PopupMenu = _button_sort_mode.get_popup()
+ context_menu.clear()
+
+ if not context_menu.index_pressed.is_connected(_on_sort_mode_changed):
+ @warning_ignore("return_value_discarded")
+ context_menu.index_pressed.connect(_on_sort_mode_changed)
+
+ var configured_sort_mode := GdUnitSettings.get_inspector_tree_sort_mode()
+ for sort_mode: String in GdUnitInspectorTreeConstants.SORT_MODE.keys():
+ var enum_value :int = GdUnitInspectorTreeConstants.SORT_MODE.get(sort_mode)
+ var icon :Texture2D = icon_mappings[0x100 + enum_value]
+ context_menu.add_icon_check_item(icon, normalise(sort_mode), enum_value)
+ context_menu.set_item_checked(enum_value, configured_sort_mode == enum_value)
+
+
+func _set_view_mode_menu_options() -> void:
+ _button_view_mode.icon = GdUnitUiTools.get_icon("Tree", Color.GHOST_WHITE)
+ # construct context tree view menu according to the available modes
+ var context_menu :PopupMenu = _button_view_mode.get_popup()
+ context_menu.clear()
+
+ if not context_menu.index_pressed.is_connected(_on_tree_view_mode_changed):
+ @warning_ignore("return_value_discarded")
+ context_menu.index_pressed.connect(_on_tree_view_mode_changed)
+
+ var configured_tree_view_mode := GdUnitSettings.get_inspector_tree_view_mode()
+ for tree_view_mode: String in GdUnitInspectorTreeConstants.TREE_VIEW_MODE.keys():
+ var enum_value :int = GdUnitInspectorTreeConstants.TREE_VIEW_MODE.get(tree_view_mode)
+ var icon :Texture2D = icon_mappings[0x200 + enum_value]
+ context_menu.add_icon_check_item(icon, normalise(tree_view_mode), enum_value)
+ context_menu.set_item_checked(enum_value, configured_tree_view_mode == enum_value)
+
+
+func normalise(value: String) -> String:
+ var parts := value.to_lower().split("_")
+ parts[0] = parts[0].capitalize()
+ return " ".join(parts)
+
+
+func status_changed(errors: int, failed: int, flaky: int, skipped: int) -> void:
+ total_failed += failed
+ total_errors += errors
+ total_flaky += flaky
+ total_skipped += skipped
+ _failures.text = str(total_failed)
+ _errors.text = str(total_errors)
+ _flaky_value.text = str(total_flaky)
+ _skipped_value.text = str(total_skipped)
+
+
+func disable_buttons(value :bool) -> void:
+ _button_sync.set_disabled(value)
+ _button_sort_mode.set_disabled(value)
+ _button_view_mode.set_disabled(value)
+
+
+func _on_gdunit_event(event: GdUnitEvent) -> void:
+ match event.type():
+ GdUnitEvent.DISCOVER_START:
+ disable_buttons(true)
+
+ GdUnitEvent.DISCOVER_END:
+ disable_buttons(false)
+
+ GdUnitEvent.INIT:
+ total_errors = 0
+ total_failed = 0
+ total_flaky = 0
+ total_skipped = 0
+ status_changed(total_errors, total_failed, total_flaky, total_skipped)
+
+ GdUnitEvent.TESTCASE_AFTER:
+ status_changed(event.error_count(), event.failed_count(), event.is_flaky(), event.is_skipped())
+
+ GdUnitEvent.TESTSUITE_AFTER:
+ status_changed(event.error_count(), event.failed_count(), event.is_flaky(), 0)
+
+ GdUnitEvent.SESSION_START:
+ disable_buttons(true)
+
+ GdUnitEvent.SESSION_CLOSE:
+ disable_buttons(false)
+
+
+func _on_btn_error_up_pressed() -> void:
+ select_error_prevous.emit()
+
+
+func _on_btn_error_down_pressed() -> void:
+ select_error_next.emit()
+
+
+func _on_failure_up_pressed() -> void:
+ select_failure_prevous.emit()
+
+
+func _on_failure_down_pressed() -> void:
+ select_failure_next.emit()
+
+
+func _on_btn_flaky_up_pressed() -> void:
+ select_flaky_prevous.emit()
+
+
+func _on_btn_flaky_down_pressed() -> void:
+ select_flaky_next.emit()
+
+
+func _on_btn_skipped_up_pressed() -> void:
+ select_skipped_prevous.emit()
+
+
+func _on_btn_skipped_down_pressed() -> void:
+ select_skipped_next.emit()
+
+
+func _on_btn_tree_sync_pressed() -> void:
+ await GdUnitTestDiscoverer.run()
+
+
+func _on_sort_mode_changed(index: int) -> void:
+ var selected_sort_mode :GdUnitInspectorTreeConstants.SORT_MODE = GdUnitInspectorTreeConstants.SORT_MODE.values()[index]
+ GdUnitSettings.set_inspector_tree_sort_mode(selected_sort_mode)
+
+
+func _on_tree_view_mode_changed(index: int) ->void:
+ var selected_tree_mode :GdUnitInspectorTreeConstants.TREE_VIEW_MODE = GdUnitInspectorTreeConstants.TREE_VIEW_MODE.values()[index]
+ GdUnitSettings.set_inspector_tree_view_mode(selected_tree_mode)
+
+
+################################################################################
+# external signal receiver
+################################################################################
+func _on_settings_changed(property :GdUnitProperty) -> void:
+ if property.name() == GdUnitSettings.INSPECTOR_TREE_SORT_MODE:
+ _set_sort_mode_menu_options()
+ if property.name() == GdUnitSettings.INSPECTOR_TREE_VIEW_MODE:
+ _set_view_mode_menu_options()
diff --git a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd.uid b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd.uid
new file mode 100644
index 0000000..e4c2c25
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd.uid
@@ -0,0 +1 @@
+uid://c0whlxhcst61i
diff --git a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn
new file mode 100644
index 0000000..d4ed816
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn
@@ -0,0 +1,465 @@
+[gd_scene load_steps=26 format=3 uid="uid://c22l4odk7qesc"]
+
+[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd" id="3"]
+
+[sub_resource type="DPITexture" id="DPITexture_mb3ih"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M8 1v2H6a5 5 0 0 0-4 8l1.414-1.414A3 3 0 0 1 6 5h2v2l4-3-4-3zm6 4-1.414 1.414A3 3 0 0 1 10 11H8V9l-4 3 4 3v-2h2a5 5 0 0 0 4-8z\"/></svg>
+"
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_wo03e"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M9 1v2h6V1zM4 1a1 1 0 0 0-.691.291l-2 2a1 1 0 0 0 1.414 1.414l.293-.293v7.172l-.293-.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l2-2a1 1 0 0 0-1.414-1.414l-.293.293V4.412l.293.293a1 1 0 0 0 1.414-1.414l-2-2A1 1 0 0 0 4 1zm5 6v2h4V7zm0 6v2h2v-2z\"/></svg>
+"
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_ixycx"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"none\" stroke=\"#e0e0e0\" d=\"M2 4h12zm0 4h12zm0 4h12z\"/></svg>
+"
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)
+}
+
+[sub_resource type="Image" id="Image_c80wp"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 231, 231, 231, 21, 224, 224, 224, 194, 224, 224, 224, 196, 232, 232, 232, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 231, 231, 231, 21, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 212, 232, 232, 232, 22, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 194, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 197, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 176, 224, 224, 224, 200, 224, 224, 224, 253, 224, 224, 224, 255, 225, 225, 225, 199, 224, 224, 224, 179, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 252, 224, 224, 224, 255, 255, 255, 255, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 252, 224, 224, 224, 255, 255, 255, 255, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 252, 224, 224, 224, 255, 255, 255, 255, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 252, 224, 224, 224, 255, 255, 255, 255, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 252, 224, 224, 224, 255, 255, 255, 255, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 252, 224, 224, 224, 255, 255, 255, 255, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 171, 224, 224, 224, 195, 224, 224, 224, 253, 224, 224, 224, 255, 224, 224, 224, 195, 225, 225, 225, 175, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 196, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 199, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 233, 233, 233, 23, 224, 224, 224, 213, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 215, 224, 224, 224, 24, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 233, 233, 233, 23, 224, 224, 224, 198, 224, 224, 224, 201, 224, 224, 224, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_eis20"]
+image = SubResource("Image_c80wp")
+
+[sub_resource type="DPITexture" id="DPITexture_t2qd7"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M5 8a4 4 0 1 1 4 4v2a6 6 0 1 0-6-6H1l3 4 3-4zm3-3h2v2h2v2H8z\"/></svg>
+"
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)
+}
+
+[sub_resource type="Image" id="Image_jh28t"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 248, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 248, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_1mh1t"]
+image = SubResource("Image_jh28t")
+
+[sub_resource type="Image" id="Image_lpjla"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 248, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 237, 247, 245, 248, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 237, 247, 245, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_bq8kn"]
+image = SubResource("Image_lpjla")
+
+[sub_resource type="Image" id="Image_bwbka"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 249, 249, 255, 230, 246, 246, 252, 230, 249, 249, 255, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 246, 252, 237, 246, 246, 252, 255, 246, 246, 252, 248, 0, 0, 0, 0, 246, 246, 252, 254, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 246, 252, 236, 246, 246, 252, 254, 246, 246, 252, 247, 0, 0, 0, 0, 246, 246, 252, 254, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 246, 253, 231, 246, 246, 253, 232, 246, 246, 252, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 246, 252, 243, 246, 246, 252, 255, 246, 246, 252, 242, 246, 246, 252, 230, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 246, 252, 242, 246, 246, 252, 253, 246, 246, 252, 241, 246, 246, 252, 230, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 246, 252, 244, 246, 246, 252, 255, 246, 246, 252, 241, 246, 246, 252, 230, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 246, 252, 244, 246, 246, 252, 255, 246, 246, 252, 241, 246, 246, 252, 230, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 246, 246, 252, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_8lbfl"]
+image = SubResource("Image_bwbka")
+
+[sub_resource type="Image" id="Image_ki3oo"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 232, 151, 12, 11, 242, 151, 12, 11, 250, 151, 12, 11, 254, 151, 12, 11, 254, 151, 12, 11, 250, 151, 12, 11, 242, 151, 12, 10, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 10, 238, 151, 12, 11, 254, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 151, 12, 11, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 237, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 254, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 254, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 10, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 10, 232, 151, 12, 11, 253, 151, 12, 11, 255, 151, 12, 11, 240, 151, 12, 10, 234, 151, 12, 11, 253, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 151, 12, 11, 234, 151, 12, 11, 241, 151, 12, 11, 255, 151, 12, 11, 253, 151, 11, 10, 232, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 242, 151, 12, 11, 255, 151, 12, 11, 254, 151, 12, 10, 234, 0, 0, 0, 0, 151, 12, 10, 234, 151, 12, 11, 253, 151, 12, 11, 253, 151, 12, 11, 234, 0, 0, 0, 0, 151, 12, 11, 234, 151, 12, 11, 254, 151, 12, 11, 255, 151, 12, 11, 241, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 250, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 151, 12, 10, 234, 0, 0, 0, 0, 151, 12, 10, 234, 151, 12, 10, 234, 0, 0, 0, 0, 151, 12, 11, 234, 151, 12, 11, 253, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 250, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 254, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 151, 12, 10, 234, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 234, 151, 12, 11, 253, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 254, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 151, 12, 10, 234, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 10, 234, 151, 12, 11, 253, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 250, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 151, 12, 11, 234, 0, 0, 0, 0, 151, 12, 11, 234, 151, 12, 10, 234, 0, 0, 0, 0, 151, 12, 11, 234, 151, 12, 11, 253, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 250, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 242, 151, 12, 11, 255, 151, 12, 11, 254, 151, 12, 11, 234, 0, 0, 0, 0, 151, 12, 11, 234, 151, 12, 11, 253, 151, 12, 11, 253, 151, 12, 11, 234, 0, 0, 0, 0, 151, 12, 11, 234, 151, 12, 11, 254, 151, 12, 11, 255, 151, 12, 10, 241, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 10, 232, 151, 12, 11, 253, 151, 12, 11, 255, 151, 12, 11, 241, 151, 12, 11, 234, 151, 12, 11, 253, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 151, 12, 11, 234, 151, 12, 11, 241, 151, 12, 11, 255, 151, 12, 11, 253, 151, 11, 10, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 237, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 254, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 254, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 237, 151, 12, 11, 253, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 255, 151, 12, 11, 253, 151, 12, 11, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151, 12, 11, 232, 151, 12, 11, 242, 151, 12, 11, 250, 151, 12, 11, 253, 151, 12, 11, 253, 151, 12, 11, 250, 151, 12, 11, 241, 151, 11, 10, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_ivm1h"]
+image = SubResource("Image_ki3oo")
+
+[sub_resource type="Image" id="Image_uqb0l"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 255, 224, 224, 224, 255, 231, 231, 231, 21, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 211, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 211, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 211, 224, 224, 224, 255, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 195, 224, 224, 224, 255, 224, 224, 224, 210, 230, 230, 230, 20, 224, 224, 224, 255, 224, 224, 224, 255, 231, 231, 231, 21, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 194, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 178, 224, 224, 224, 194, 230, 230, 230, 20, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 194, 224, 224, 224, 179, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 180, 224, 224, 224, 180, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_1oriu"]
+image = SubResource("Image_uqb0l")
+
+[sub_resource type="Image" id="Image_j00vj"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 181, 224, 224, 224, 180, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 180, 224, 224, 224, 195, 231, 231, 231, 21, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 195, 224, 224, 224, 178, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 195, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 224, 224, 224, 255, 224, 224, 224, 255, 231, 231, 231, 21, 224, 224, 224, 211, 224, 224, 224, 255, 224, 224, 224, 194, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 211, 224, 224, 224, 255, 224, 224, 224, 210, 230, 230, 230, 20, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 210, 230, 230, 230, 20, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 210, 230, 230, 230, 20, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 255, 224, 224, 224, 255, 230, 230, 230, 20, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_ikyhk"]
+image = SubResource("Image_j00vj")
+
+[sub_resource type="Image" id="Image_0oden"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 198, 223, 232, 147, 197, 222, 242, 147, 197, 222, 250, 147, 197, 222, 254, 147, 197, 222, 254, 147, 197, 222, 250, 147, 197, 222, 242, 147, 197, 222, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 238, 147, 197, 222, 254, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 147, 197, 222, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 198, 222, 237, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 254, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 254, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 232, 147, 197, 222, 253, 147, 197, 222, 255, 147, 197, 222, 240, 147, 198, 222, 234, 147, 197, 222, 253, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 147, 198, 222, 234, 147, 197, 222, 241, 147, 197, 222, 255, 147, 197, 222, 253, 147, 197, 222, 232, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 242, 147, 197, 222, 255, 147, 197, 222, 254, 147, 198, 222, 234, 0, 0, 0, 0, 147, 198, 222, 234, 147, 197, 222, 253, 147, 197, 222, 253, 147, 197, 222, 234, 0, 0, 0, 0, 147, 197, 222, 234, 147, 197, 222, 254, 147, 197, 222, 255, 147, 197, 222, 241, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 250, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 147, 198, 222, 234, 0, 0, 0, 0, 147, 198, 222, 234, 147, 198, 222, 234, 0, 0, 0, 0, 147, 197, 222, 234, 147, 197, 222, 253, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 250, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 254, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 147, 198, 222, 234, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 234, 147, 197, 222, 253, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 254, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 147, 198, 222, 234, 0, 0, 0, 0, 0, 0, 0, 0, 147, 198, 222, 234, 147, 197, 222, 253, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 250, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 147, 197, 222, 234, 0, 0, 0, 0, 147, 197, 222, 234, 147, 198, 222, 234, 0, 0, 0, 0, 147, 197, 222, 234, 147, 197, 222, 253, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 250, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 242, 147, 197, 222, 255, 147, 197, 222, 254, 147, 198, 222, 234, 0, 0, 0, 0, 147, 197, 222, 234, 147, 197, 222, 253, 147, 197, 222, 253, 147, 197, 222, 234, 0, 0, 0, 0, 147, 197, 222, 234, 147, 197, 222, 254, 147, 197, 222, 255, 147, 197, 222, 241, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 232, 147, 197, 222, 253, 147, 197, 222, 255, 147, 197, 222, 241, 147, 197, 222, 234, 147, 197, 222, 253, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 147, 197, 222, 234, 147, 197, 222, 241, 147, 197, 222, 255, 147, 197, 222, 253, 147, 197, 221, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 198, 222, 237, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 254, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 254, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 197, 222, 237, 147, 197, 222, 253, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 255, 147, 197, 222, 253, 147, 197, 222, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 198, 222, 232, 147, 197, 222, 242, 147, 197, 222, 250, 147, 197, 222, 253, 147, 197, 222, 253, 147, 197, 222, 250, 147, 197, 222, 241, 147, 197, 222, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_suo5c"]
+image = SubResource("Image_0oden")
+
+[sub_resource type="Image" id="Image_ipq44"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 237, 170, 253, 57, 252, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 254, 58, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 252, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 254, 58, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 58, 234, 170, 253, 57, 247, 171, 255, 57, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 58, 234, 170, 253, 57, 253, 170, 253, 57, 255, 170, 254, 57, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 58, 234, 170, 253, 57, 253, 170, 253, 57, 255, 170, 254, 57, 247, 171, 255, 58, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 170, 254, 58, 232, 170, 254, 57, 232, 0, 0, 0, 0, 170, 253, 58, 234, 170, 253, 57, 253, 170, 253, 57, 255, 170, 254, 57, 247, 171, 255, 58, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 170, 254, 58, 232, 170, 253, 57, 251, 170, 253, 57, 251, 170, 254, 58, 236, 170, 253, 57, 253, 170, 253, 57, 255, 170, 254, 57, 247, 171, 255, 58, 230, 0, 0, 0, 0, 170, 254, 58, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 170, 254, 57, 232, 170, 253, 57, 251, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 254, 57, 247, 171, 255, 58, 230, 0, 0, 0, 0, 170, 254, 58, 242, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 170, 254, 57, 232, 170, 253, 57, 251, 170, 253, 57, 255, 170, 254, 57, 247, 171, 255, 58, 230, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 254, 57, 232, 170, 253, 57, 244, 171, 255, 58, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 255, 170, 253, 57, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 253, 57, 252, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 254, 57, 237, 170, 253, 57, 252, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 255, 170, 253, 57, 252, 170, 254, 57, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_d5kq4"]
+image = SubResource("Image_ipq44")
+
+[sub_resource type="Image" id="Image_8d0da"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 237, 129, 139, 130, 252, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 252, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 131, 234, 129, 139, 130, 247, 130, 141, 130, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 131, 234, 129, 139, 130, 253, 129, 139, 130, 255, 129, 139, 130, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 131, 234, 129, 139, 130, 253, 129, 139, 130, 255, 129, 139, 130, 247, 131, 141, 131, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 130, 140, 131, 232, 129, 140, 130, 232, 0, 0, 0, 0, 129, 139, 131, 234, 129, 139, 130, 253, 129, 139, 130, 255, 129, 139, 130, 247, 131, 141, 131, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 130, 140, 131, 232, 129, 139, 130, 251, 129, 139, 130, 251, 129, 139, 130, 236, 129, 139, 130, 253, 129, 139, 130, 255, 129, 139, 130, 247, 131, 141, 131, 230, 0, 0, 0, 0, 129, 139, 130, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 129, 140, 130, 232, 129, 139, 130, 251, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 247, 131, 141, 131, 230, 0, 0, 0, 0, 129, 139, 130, 242, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 130, 140, 130, 232, 129, 139, 130, 251, 129, 139, 130, 255, 129, 139, 130, 247, 131, 141, 131, 230, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 130, 140, 130, 232, 129, 139, 130, 244, 131, 141, 131, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 255, 129, 139, 130, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 139, 130, 252, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 130, 139, 130, 237, 129, 139, 130, 252, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 255, 129, 139, 130, 252, 129, 139, 130, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_qagbu"]
+image = SubResource("Image_8d0da")
+
+[node name="StatusBar" type="PanelContainer"]
+clip_contents = true
+anchors_preset = 10
+anchor_right = 1.0
+offset_right = -807.0
+offset_bottom = 31.0
+grow_horizontal = 2
+size_flags_horizontal = 3
+size_flags_vertical = 0
+script = ExtResource("3")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+layout_mode = 2
+size_flags_vertical = 0
+
+[node name="tree_tools" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 0
+
+[node name="Label" type="Label" parent="VBoxContainer/tree_tools"]
+layout_mode = 2
+size_flags_horizontal = 0
+text = "Statistics"
+
+[node name="tree_buttons" type="HBoxContainer" parent="VBoxContainer/tree_tools"]
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+alignment = 2
+
+[node name="VSeparator" type="VSeparator" parent="VBoxContainer/tree_tools/tree_buttons"]
+layout_mode = 2
+
+[node name="btn_tree_sync" type="Button" parent="VBoxContainer/tree_tools/tree_buttons"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Run discover tests."
+icon = SubResource("DPITexture_mb3ih")
+
+[node name="btn_tree_sort" type="MenuButton" parent="VBoxContainer/tree_tools/tree_buttons"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Sets tree sorting mode."
+icon = SubResource("DPITexture_wo03e")
+flat = false
+item_count = 4
+popup/item_0/text = "Unsorted"
+popup/item_0/icon = SubResource("DPITexture_ixycx")
+popup/item_0/checkable = 1
+popup/item_0/id = 0
+popup/item_1/text = "Name ascending"
+popup/item_1/icon = SubResource("DPITexture_wo03e")
+popup/item_1/checkable = 1
+popup/item_1/checked = true
+popup/item_1/id = 1
+popup/item_2/text = "Name descending"
+popup/item_2/icon = SubResource("ImageTexture_eis20")
+popup/item_2/checkable = 1
+popup/item_2/id = 2
+popup/item_3/text = "Execution time"
+popup/item_3/icon = SubResource("DPITexture_t2qd7")
+popup/item_3/checkable = 1
+popup/item_3/id = 3
+
+[node name="btn_tree_mode" type="MenuButton" parent="VBoxContainer/tree_tools/tree_buttons"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Sets tree presentation mode."
+icon = SubResource("ImageTexture_1mh1t")
+flat = false
+item_count = 2
+popup/item_0/text = "Tree"
+popup/item_0/icon = SubResource("ImageTexture_bq8kn")
+popup/item_0/checkable = 1
+popup/item_0/checked = true
+popup/item_0/id = 0
+popup/item_1/text = "Flat"
+popup/item_1/icon = SubResource("ImageTexture_8lbfl")
+popup/item_1/checkable = 1
+popup/item_1/id = 1
+
+[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="status_bar" type="HFlowContainer" parent="VBoxContainer"]
+layout_direction = 2
+layout_mode = 2
+size_flags_vertical = 2
+
+[node name="error" type="VBoxContainer" parent="VBoxContainer/status_bar"]
+custom_minimum_size = Vector2(0, 48)
+layout_mode = 2
+size_flags_vertical = 0
+theme_override_constants/separation = -2
+
+[node name="icon" type="HBoxContainer" parent="VBoxContainer/status_bar/error"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="icon_errors" type="TextureRect" parent="VBoxContainer/status_bar/error/icon"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+tooltip_text = "Error Tests"
+texture = SubResource("ImageTexture_ivm1h")
+stretch_mode = 3
+
+[node name="btn_up" type="Button" parent="VBoxContainer/status_bar/error/icon"]
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+tooltip_text = "Jump to the previous error test"
+icon = SubResource("ImageTexture_1oriu")
+
+[node name="counter" type="HBoxContainer" parent="VBoxContainer/status_bar/error"]
+auto_translate_mode = 2
+layout_mode = 2
+localize_numeral_system = false
+
+[node name="error_value" type="Label" parent="VBoxContainer/status_bar/error/counter"]
+unique_name_in_owner = true
+use_parent_material = true
+custom_minimum_size = Vector2(32, 0)
+layout_mode = 2
+size_flags_horizontal = 10
+text = "0"
+horizontal_alignment = 2
+justification_flags = 0
+visible_characters = 3
+visible_ratio = 3.0
+
+[node name="btn_down" type="Button" parent="VBoxContainer/status_bar/error/counter"]
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+tooltip_text = "Jump to the next error test"
+icon = SubResource("ImageTexture_ikyhk")
+
+[node name="VSeparator" type="VSeparator" parent="VBoxContainer/status_bar"]
+layout_mode = 2
+
+[node name="failure" type="VBoxContainer" parent="VBoxContainer/status_bar"]
+custom_minimum_size = Vector2(0, 48)
+layout_mode = 2
+theme_override_constants/separation = -2
+
+[node name="icon" type="HBoxContainer" parent="VBoxContainer/status_bar/failure"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="icon_failures" type="TextureRect" parent="VBoxContainer/status_bar/failure/icon"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+tooltip_text = "Failed Tests"
+texture = SubResource("ImageTexture_suo5c")
+stretch_mode = 3
+
+[node name="btn_up" type="Button" parent="VBoxContainer/status_bar/failure/icon"]
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+tooltip_text = "Jump to the previous failed test"
+icon = SubResource("ImageTexture_1oriu")
+
+[node name="counter" type="HBoxContainer" parent="VBoxContainer/status_bar/failure"]
+auto_translate_mode = 2
+layout_mode = 2
+localize_numeral_system = false
+
+[node name="failure_value" type="Label" parent="VBoxContainer/status_bar/failure/counter"]
+unique_name_in_owner = true
+use_parent_material = true
+custom_minimum_size = Vector2(32, 0)
+layout_mode = 2
+size_flags_horizontal = 10
+text = "0"
+horizontal_alignment = 2
+justification_flags = 0
+visible_characters = 3
+visible_ratio = 3.0
+
+[node name="btn_down" type="Button" parent="VBoxContainer/status_bar/failure/counter"]
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+tooltip_text = "Jump to the next failed test"
+icon = SubResource("ImageTexture_ikyhk")
+
+[node name="VSeparator2" type="VSeparator" parent="VBoxContainer/status_bar"]
+layout_mode = 2
+
+[node name="flaky" type="VBoxContainer" parent="VBoxContainer/status_bar"]
+custom_minimum_size = Vector2(0, 48)
+layout_mode = 2
+theme_override_constants/separation = -2
+
+[node name="icon" type="HBoxContainer" parent="VBoxContainer/status_bar/flaky"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="icon_flaky" type="TextureRect" parent="VBoxContainer/status_bar/flaky/icon"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+tooltip_text = "Flaky Tests"
+texture = SubResource("ImageTexture_d5kq4")
+stretch_mode = 3
+
+[node name="btn_up" type="Button" parent="VBoxContainer/status_bar/flaky/icon"]
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+tooltip_text = "Jump to the previous flaky test"
+icon = SubResource("ImageTexture_1oriu")
+
+[node name="counter" type="HBoxContainer" parent="VBoxContainer/status_bar/flaky"]
+auto_translate_mode = 2
+layout_mode = 2
+localize_numeral_system = false
+
+[node name="flaky_value" type="Label" parent="VBoxContainer/status_bar/flaky/counter"]
+unique_name_in_owner = true
+use_parent_material = true
+custom_minimum_size = Vector2(32, 0)
+layout_mode = 2
+size_flags_horizontal = 10
+text = "0"
+horizontal_alignment = 2
+justification_flags = 0
+visible_characters = 3
+visible_ratio = 3.0
+
+[node name="btn_down" type="Button" parent="VBoxContainer/status_bar/flaky/counter"]
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+tooltip_text = "Jump to the next flaky test"
+icon = SubResource("ImageTexture_ikyhk")
+
+[node name="VSeparator3" type="VSeparator" parent="VBoxContainer/status_bar"]
+layout_mode = 2
+
+[node name="skipped" type="VBoxContainer" parent="VBoxContainer/status_bar"]
+custom_minimum_size = Vector2(0, 48)
+layout_mode = 2
+theme_override_constants/separation = -2
+
+[node name="icon" type="HBoxContainer" parent="VBoxContainer/status_bar/skipped"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="icon_skipped" type="TextureRect" parent="VBoxContainer/status_bar/skipped/icon"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+tooltip_text = "Skipped Tests"
+texture = SubResource("ImageTexture_qagbu")
+stretch_mode = 3
+
+[node name="btn_up" type="Button" parent="VBoxContainer/status_bar/skipped/icon"]
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+tooltip_text = "Jump to the previous skipped test"
+icon = SubResource("ImageTexture_1oriu")
+
+[node name="counter" type="HBoxContainer" parent="VBoxContainer/status_bar/skipped"]
+auto_translate_mode = 2
+layout_mode = 2
+localize_numeral_system = false
+
+[node name="skipped_value" type="Label" parent="VBoxContainer/status_bar/skipped/counter"]
+unique_name_in_owner = true
+use_parent_material = true
+custom_minimum_size = Vector2(32, 0)
+layout_mode = 2
+size_flags_horizontal = 10
+text = "0"
+horizontal_alignment = 2
+justification_flags = 0
+visible_characters = 3
+visible_ratio = 3.0
+
+[node name="btn_down" type="Button" parent="VBoxContainer/status_bar/skipped/counter"]
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+tooltip_text = "Jump to the next skipped test"
+icon = SubResource("ImageTexture_ikyhk")
+
+[connection signal="pressed" from="VBoxContainer/tree_tools/tree_buttons/btn_tree_sync" to="." method="_on_btn_tree_sync_pressed"]
+[connection signal="pressed" from="VBoxContainer/status_bar/error/icon/btn_up" to="." method="_on_btn_error_up_pressed"]
+[connection signal="pressed" from="VBoxContainer/status_bar/error/counter/btn_down" to="." method="_on_btn_error_down_pressed"]
+[connection signal="pressed" from="VBoxContainer/status_bar/failure/icon/btn_up" to="." method="_on_failure_up_pressed"]
+[connection signal="pressed" from="VBoxContainer/status_bar/failure/counter/btn_down" to="." method="_on_failure_down_pressed"]
+[connection signal="pressed" from="VBoxContainer/status_bar/flaky/icon/btn_up" to="." method="_on_btn_flaky_up_pressed"]
+[connection signal="pressed" from="VBoxContainer/status_bar/flaky/counter/btn_down" to="." method="_on_btn_flaky_down_pressed"]
+[connection signal="pressed" from="VBoxContainer/status_bar/skipped/icon/btn_up" to="." method="_on_btn_skipped_up_pressed"]
+[connection signal="pressed" from="VBoxContainer/status_bar/skipped/counter/btn_down" to="." method="_on_btn_skipped_down_pressed"]
diff --git a/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd b/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd
new file mode 100644
index 0000000..4a92c52
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd
@@ -0,0 +1,104 @@
+@tool
+extends PanelContainer
+
+
+const InspectorTreeMainPanel := preload("res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd")
+
+@onready var _version_label: Control = %version
+@onready var _button_wiki: Button = %help
+@onready var _tool_button: Button = %tool
+@onready var _button_run_overall: Button = %run_overall
+@onready var _button_run: Button = %run
+@onready var _button_run_debug: Button = %debug
+@onready var _button_stop: Button = %stop
+
+
+var inspector: InspectorTreeMainPanel
+var command_handler: GdUnitCommandHandler
+
+
+func _ready() -> void:
+ command_handler = GdUnitCommandHandler.instance()
+ inspector = get_parent().get_parent().find_child("MainPanel", false, false)
+ if inspector == null:
+ push_error("Internal error, can't connect to the test inspector!")
+ else:
+ inspector.tree_item_selected.connect(_on_inspector_selected)
+
+ GdUnit4Version.init_version_label(_version_label)
+
+ GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event)
+ GdUnitSignals.instance().gdunit_settings_changed.connect(_on_settings_changed)
+ _init_buttons()
+
+
+func _init_buttons() -> void:
+ _init_button(_button_run_overall, GdUnitCommandRunTestsOverall.ID)
+ _init_button(_button_run, GdUnitCommandInspectorRunTests.ID)
+ _init_button(_button_run_debug, GdUnitCommandInspectorDebugTests.ID)
+ _init_button(_button_stop, GdUnitCommandStopTestSession.ID)
+
+ _button_stop.icon = command_handler.command_icon(GdUnitCommandStopTestSession.ID)
+ _tool_button.icon = GdUnitUiTools.get_icon("Tools")
+ _button_wiki.icon = GdUnitUiTools.get_icon("HelpSearch")
+ # Set run buttons initial disabled
+ _button_run.disabled = true
+ _button_run_debug.disabled = true
+
+
+func _init_button(button: Button, comand_id: String) -> void:
+ button.set_meta("GdUnitCommand", comand_id)
+ button.icon = command_handler.command_icon(comand_id)
+ button.shortcut = command_handler.command_shortcut(comand_id)
+ if button == _button_run_overall:
+ button.visible = GdUnitSettings.is_inspector_toolbar_button_show()
+
+
+func _on_inspector_selected(item: TreeItem) -> void:
+ var button_disabled := item == null
+ _button_run.disabled = button_disabled
+ _button_run_debug.disabled = button_disabled
+
+
+func _on_gdunit_event(event: GdUnitEvent) -> void:
+ if event.type() == GdUnitEvent.SESSION_START:
+ _button_run_overall.disabled = true
+ _button_run.disabled = true
+ _button_run_debug.disabled = true
+ _button_stop.disabled = false
+ return
+ if event.type() == GdUnitEvent.SESSION_CLOSE:
+ _button_run_overall.disabled = false
+ _button_stop.disabled = true
+
+
+func _on_button_pressed(source: BaseButton) -> void:
+ var command_id: String = source.get_meta("GdUnitCommand")
+ await command_handler.command_execute(command_id)
+
+
+func _on_wiki_pressed() -> void:
+ var status := OS.shell_open("https://godot-gdunit-labs.github.io/gdUnit4/latest")
+ if status != OK:
+ push_error("Can't open GdUnit4 documentaion page: %s" % error_string(status))
+
+
+func _on_btn_tool_pressed() -> void:
+ var settings_dlg: Window = EditorInterface.get_base_control().find_child("GdUnitSettingsDialog", false, false)
+ if settings_dlg == null:
+ settings_dlg = preload("res://addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn").instantiate()
+ EditorInterface.get_base_control().add_child(settings_dlg, true)
+ settings_dlg.popup_centered_ratio(.60)
+
+
+func _on_settings_changed(property: GdUnitProperty) -> void:
+ # needs to wait a frame to be command handler notified first for settings changes
+ await get_tree().process_frame
+
+ _button_run_overall.visible = GdUnitSettings.is_inspector_toolbar_button_show()
+
+ if property.name().begins_with(GdUnitSettings.GROUP_SHORTCUT_INSPECTOR):
+ _button_run.shortcut = command_handler.command_shortcut(GdUnitCommandInspectorRunTests.ID)
+ _button_run_debug.shortcut = command_handler.command_shortcut(GdUnitCommandInspectorDebugTests.ID)
+ _button_run_overall.shortcut = command_handler.command_shortcut(GdUnitCommandRunTestsOverall.ID)
+ _button_stop.shortcut = command_handler.command_shortcut(GdUnitCommandStopTestSession.ID)
diff --git a/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd.uid b/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd.uid
new file mode 100644
index 0000000..69c8748
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd.uid
@@ -0,0 +1 @@
+uid://bepy3y6bvaume
diff --git a/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn b/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn
new file mode 100644
index 0000000..8cd529e
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn
@@ -0,0 +1,202 @@
+[gd_scene load_steps=17 format=3 uid="uid://dx7xy4dgi3wwb"]
+
+[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorToolBar.gd" id="3"]
+
+[sub_resource type="DPITexture" id="DPITexture_c7rhl"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M6.973 7.441a4 4 0 1 0-1.414-1.414L3.293 8.293l1.414 1.414zM9 2a2 2 0 0 1 0 4 2 2 0 0 1 0-4zm-9 8v6h2a3 3 0 1 0 0-6zm5 3a3 3 0 1 0 6 0 3 3 0 0 0-6 0zm6 0a3 3 0 0 0 3 3h1v-2h-1a1 1 0 0 1 0-2h1v-2h-1a3 3 0 0 0-3 3zm-9-1a1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2z\"/></svg>
+"
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_3erui"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M4 1 3 3l1 2v4H2v3.5a2.5 2.5 0 0 0 5 0V9H5V5l1-2-1-2zm6 .174a3 3 0 0 0 0 5.652V14a1 1 0 0 0 2 0V6.824a3 3 0 0 0 0-5.648V4a1 1 0 0 1-2 0z\"/></svg>
+"
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)
+}
+
+[sub_resource type="InputEventKey" id="InputEventKey_p22nw"]
+alt_pressed = true
+pressed = true
+keycode = 4194338
+physical_keycode = 4194338
+
+[sub_resource type="Shortcut" id="Shortcut_3lcek"]
+events = [SubResource("InputEventKey_p22nw")]
+
+[sub_resource type="Image" id="Image_ndw0i"]
+data = {
+"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 195, 224, 224, 224, 210, 224, 224, 224, 56, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 195, 224, 224, 224, 210, 224, 224, 224, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 253, 224, 224, 224, 139, 224, 224, 224, 8, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 253, 224, 224, 224, 139, 224, 224, 224, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 215, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 215, 224, 224, 224, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 253, 224, 224, 224, 139, 224, 224, 224, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 183, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 182, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 252, 225, 225, 225, 134, 255, 255, 255, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 212, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 212, 226, 226, 226, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 252, 225, 225, 225, 134, 255, 255, 255, 6, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 252, 225, 225, 225, 134, 255, 255, 255, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 225, 225, 225, 191, 224, 224, 224, 206, 226, 226, 226, 52, 0, 0, 0, 0, 0, 0, 0, 0, 225, 225, 225, 191, 224, 224, 224, 206, 226, 226, 226, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_eoihf"]
+image = SubResource("Image_ndw0i")
+
+[sub_resource type="InputEventKey" id="InputEventKey_6gwbs"]
+alt_pressed = true
+pressed = true
+keycode = 4194336
+physical_keycode = 4194336
+
+[sub_resource type="Shortcut" id="Shortcut_2i8uq"]
+events = [SubResource("InputEventKey_6gwbs")]
+
+[sub_resource type="DPITexture" id="DPITexture_87jj1"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M4 12a1 1 0 0 0 1.555.832l6-4a1 1 0 0 0 0-1.664l-6-4A1 1 0 0 0 4 4z\"/></svg>
+"
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)
+}
+
+[sub_resource type="InputEventKey" id="InputEventKey_fewwl"]
+alt_pressed = true
+pressed = true
+keycode = 4194337
+physical_keycode = 4194337
+
+[sub_resource type="Shortcut" id="Shortcut_f3lkx"]
+events = [SubResource("InputEventKey_fewwl")]
+
+[sub_resource type="DPITexture" id="DPITexture_7oobd"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M3 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm4.975 0A1 1 0 0 0 7 3.998v8a1 1 0 0 0 1.625.781l5-4a1 1 0 0 0 0-1.56l-5-4A1 1 0 0 0 7.975 3z\"/></svg>
+"
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)
+}
+
+[sub_resource type="InputEventKey" id="InputEventKey_lvbnb"]
+alt_pressed = true
+pressed = true
+keycode = 4194339
+physical_keycode = 4194339
+
+[sub_resource type="Shortcut" id="Shortcut_6idxu"]
+events = [SubResource("InputEventKey_lvbnb")]
+
+[sub_resource type="DPITexture" id="DPITexture_qf2s1"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><rect width=\"10\" height=\"10\" x=\"3\" y=\"3\" fill=\"#e0e0e0\" rx=\"1\"/></svg>
+"
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(1, 0.87, 0.4, 1)
+}
+
+[node name="ToolBar" type="PanelContainer"]
+anchors_preset = 10
+anchor_right = 1.0
+offset_right = -894.0
+offset_bottom = 24.0
+grow_horizontal = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+script = ExtResource("3")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="tools" type="HBoxContainer" parent="HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 0
+size_flags_vertical = 4
+
+[node name="help" type="Button" parent="HBoxContainer/tools"]
+unique_name_in_owner = true
+layout_mode = 2
+icon = SubResource("DPITexture_c7rhl")
+
+[node name="tool" type="Button" parent="HBoxContainer/tools"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "GdUnit Settings"
+icon = SubResource("DPITexture_3erui")
+
+[node name="controls" type="HBoxContainer" parent="HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 6
+size_flags_vertical = 4
+alignment = 1
+
+[node name="VSeparator3" type="VSeparator" parent="HBoxContainer/controls"]
+layout_mode = 2
+
+[node name="run_overall" type="Button" parent="HBoxContainer/controls"]
+unique_name_in_owner = true
+use_parent_material = true
+layout_mode = 2
+tooltip_text = "Run overall tests"
+shortcut = SubResource("Shortcut_3lcek")
+icon = SubResource("ImageTexture_eoihf")
+metadata/GdUnitCommand = "Run Tests Overall"
+
+[node name="run" type="Button" parent="HBoxContainer/controls"]
+unique_name_in_owner = true
+use_parent_material = true
+layout_mode = 2
+tooltip_text = "Rerun unit tests"
+disabled = true
+shortcut = SubResource("Shortcut_2i8uq")
+icon = SubResource("DPITexture_87jj1")
+metadata/GdUnitCommand = "Run Inspector Tests"
+
+[node name="debug" type="Button" parent="HBoxContainer/controls"]
+unique_name_in_owner = true
+use_parent_material = true
+layout_mode = 2
+tooltip_text = "Rerun unit tests (Debug)"
+disabled = true
+shortcut = SubResource("Shortcut_f3lkx")
+icon = SubResource("DPITexture_7oobd")
+metadata/GdUnitCommand = "Debug Inspector Tests"
+
+[node name="stop" type="Button" parent="HBoxContainer/controls"]
+unique_name_in_owner = true
+use_parent_material = true
+layout_mode = 2
+tooltip_text = "Stops runing unit tests"
+disabled = true
+shortcut = SubResource("Shortcut_6idxu")
+icon = SubResource("DPITexture_qf2s1")
+metadata/GdUnitCommand = "Stop Test Session"
+
+[node name="VSeparator4" type="VSeparator" parent="HBoxContainer/controls"]
+layout_mode = 2
+
+[node name="CenterContainer" type="HBoxContainer" parent="HBoxContainer"]
+use_parent_material = true
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+alignment = 2
+
+[node name="version" type="Label" parent="HBoxContainer/CenterContainer"]
+unique_name_in_owner = true
+auto_translate_mode = 2
+use_parent_material = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 13
+localize_numeral_system = false
+text = "gdUnit4 6.1.0"
+horizontal_alignment = 1
+justification_flags = 160
+
+[connection signal="pressed" from="HBoxContainer/tools/help" to="." method="_on_wiki_pressed"]
+[connection signal="pressed" from="HBoxContainer/tools/tool" to="." method="_on_btn_tool_pressed"]
+[connection signal="pressed" from="HBoxContainer/controls/run_overall" to="." method="_on_button_pressed" flags=18]
+[connection signal="pressed" from="HBoxContainer/controls/run" to="." method="_on_button_pressed" flags=18]
+[connection signal="pressed" from="HBoxContainer/controls/debug" to="." method="_on_button_pressed" flags=18]
+[connection signal="pressed" from="HBoxContainer/controls/stop" to="." method="_on_button_pressed" flags=18]
diff --git a/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd
new file mode 100644
index 0000000..f55dbee
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd
@@ -0,0 +1,1210 @@
+@tool
+extends VSplitContainer
+
+## Will be emitted when the test index counter is changed
+signal test_counters_changed(index: int, total: int, state: GdUnitInspectorTreeConstants.STATE)
+signal tree_item_selected(item: TreeItem)
+
+
+@onready var _tree: Tree = $Panel/Tree
+@onready var _report_list: Node = $report/ScrollContainer/list
+@onready var _report_template: RichTextLabel = $report/report_template
+@onready var _context_menu: GdUnitInspectorContextMenu = $contextMenu
+@onready var _discover_hint: Control = %discover_hint
+@onready var _spinner: Button = %spinner
+
+# loading tree icons
+@onready var ICON_SPINNER := GdUnitUiTools.get_spinner()
+@onready var ICON_FOLDER := GdUnitUiTools.get_icon("Folder")
+# gdscript icons
+@onready var ICON_GDSCRIPT_TEST_DEFAULT := GdUnitUiTools.get_icon("GDScript", Color.LIGHT_GRAY)
+@onready var ICON_GDSCRIPT_TEST_SUCCESS := GdUnitUiTools.get_GDScript_icon("StatusSuccess", Color.DARK_GREEN)
+@onready var ICON_GDSCRIPT_TEST_FLAKY := GdUnitUiTools.get_GDScript_icon("CheckBox", Color.GREEN_YELLOW)
+@onready var ICON_GDSCRIPT_TEST_FAILED := GdUnitUiTools.get_GDScript_icon("StatusError", Color.SKY_BLUE)
+@onready var ICON_GDSCRIPT_TEST_ERROR := GdUnitUiTools.get_GDScript_icon("StatusError", Color.DARK_RED)
+@onready var ICON_GDSCRIPT_TEST_SUCCESS_ORPHAN := GdUnitUiTools.get_GDScript_icon("Unlinked", Color.DARK_GREEN)
+@onready var ICON_GDSCRIPT_TEST_FAILED_ORPHAN := GdUnitUiTools.get_GDScript_icon("Unlinked", Color.SKY_BLUE)
+@onready var ICON_GDSCRIPT_TEST_ERRORS_ORPHAN := GdUnitUiTools.get_GDScript_icon("Unlinked", Color.DARK_RED)
+# csharp script icons
+@onready var ICON_CSSCRIPT_TEST_DEFAULT := GdUnitUiTools.get_icon("CSharpScript", Color.LIGHT_GRAY)
+@onready var ICON_CSSCRIPT_TEST_SUCCESS := GdUnitUiTools.get_CSharpScript_icon("StatusSuccess", Color.DARK_GREEN)
+@onready var ICON_CSSCRIPT_TEST_FAILED := GdUnitUiTools.get_CSharpScript_icon("StatusError", Color.SKY_BLUE)
+@onready var ICON_CSSCRIPT_TEST_ERROR := GdUnitUiTools.get_CSharpScript_icon("StatusError", Color.DARK_RED)
+@onready var ICON_CSSCRIPT_TEST_SUCCESS_ORPHAN := GdUnitUiTools.get_CSharpScript_icon("Unlinked", Color.DARK_GREEN)
+@onready var ICON_CSSCRIPT_TEST_FAILED_ORPHAN := GdUnitUiTools.get_CSharpScript_icon("Unlinked", Color.SKY_BLUE)
+@onready var ICON_CSSCRIPT_TEST_ERRORS_ORPHAN := GdUnitUiTools.get_CSharpScript_icon("Unlinked", Color.DARK_RED)
+
+
+enum GdUnitType {
+ FOLDER,
+ TEST_SUITE,
+ TEST_CASE,
+ TEST_GROUP
+}
+
+const META_GDUNIT_PROGRESS_COUNT_MAX := "gdUnit_progress_count_max"
+const META_GDUNIT_PROGRESS_INDEX := "gdUnit_progress_index"
+const META_TEST_CASE := "gdunit_test_case"
+const META_GDUNIT_NAME := "gdUnit_name"
+const META_GDUNIT_STATE := "gdUnit_state"
+const META_GDUNIT_TYPE := "gdUnit_type"
+const META_GDUNIT_SUCCESS_TESTS := "gdUnit_suite_success_tests"
+const META_GDUNIT_REPORT := "gdUnit_report"
+const META_GDUNIT_ORPHAN := "gdUnit_orphan"
+const META_GDUNIT_EXECUTION_TIME := "gdUnit_execution_time"
+const META_GDUNIT_ORIGINAL_INDEX = "gdunit_original_index"
+const STATE = GdUnitInspectorTreeConstants.STATE
+
+
+var _tree_root: TreeItem
+var _current_selected_item: TreeItem = null
+var _current_tree_view_mode := GdUnitSettings.get_inspector_tree_view_mode()
+var _run_test_recovery := true
+
+
+## Used for debugging purposes only
+func print_tree_item_ids(parent: TreeItem) -> TreeItem:
+ for child in parent.get_children():
+ if child.has_meta(META_TEST_CASE):
+ var test_case: GdUnitTestCase = child.get_meta(META_TEST_CASE)
+ prints(test_case.guid, test_case.test_name)
+
+ if child.get_child_count() > 0:
+ print_tree_item_ids(child)
+
+ return null
+
+
+func _find_tree_item(parent: TreeItem, item_name: String) -> TreeItem:
+ for child in parent.get_children():
+ if child.get_meta(META_GDUNIT_NAME) == item_name:
+ return child
+ return null
+
+
+func _find_tree_item_by_id(parent: TreeItem, id: GdUnitGUID) -> TreeItem:
+ for child in parent.get_children():
+ if is_test_id(child, id):
+ return child
+ if child.get_child_count() > 0:
+ var item := _find_tree_item_by_id(child, id)
+ if item != null:
+ return item
+
+ return null
+
+
+func _find_tree_item_by_test_suite(parent: TreeItem, suite_path: String, suite_name: String) -> TreeItem:
+ for child in parent.get_children():
+ if child.get_meta(META_GDUNIT_TYPE) == GdUnitType.TEST_SUITE:
+ var test_case: GdUnitTestCase = child.get_meta(META_TEST_CASE)
+ if test_case.suite_resource_path == suite_path and test_case.suite_name == suite_name:
+ return child
+ if child.get_child_count() > 0:
+ var item := _find_tree_item_by_test_suite(child, suite_path, suite_name)
+ if item != null:
+ return item
+ return null
+
+
+func _find_first_item_by_state(parent: TreeItem, item_state: STATE, reverse := false) -> TreeItem:
+ var itmes := parent.get_children()
+ if reverse:
+ itmes.reverse()
+ for item in itmes:
+ if is_test_case(item) and (is_item_state(item, item_state)):
+ return item
+ var failure_item := _find_first_item_by_state(item, item_state, reverse)
+ if failure_item != null:
+ return failure_item
+ return null
+
+
+func _find_last_item_by_state(parent: TreeItem, item_state: STATE) -> TreeItem:
+ return _find_first_item_by_state(parent, item_state, true)
+
+
+func _find_item_by_state(current: TreeItem, item_state: STATE, prev := false) -> TreeItem:
+ var next := current.get_prev_in_tree() if prev else current.get_next_in_tree()
+ if next == null or next == _tree_root:
+ return null
+ if is_test_case(next) and is_item_state(next, item_state):
+ return next
+ return _find_item_by_state(next, item_state, prev)
+
+
+func is_item_state(item: TreeItem, item_state: STATE) -> bool:
+ return item.has_meta(META_GDUNIT_STATE) and item.get_meta(META_GDUNIT_STATE) == item_state
+
+
+func is_state_running(item: TreeItem) -> bool:
+ return is_item_state(item, STATE.RUNNING)
+
+
+func is_state_success(item: TreeItem) -> bool:
+ return is_item_state(item, STATE.SUCCESS)
+
+
+func is_state_warning(item: TreeItem) -> bool:
+ return is_item_state(item, STATE.WARNING)
+
+
+func is_state_failed(item: TreeItem) -> bool:
+ return is_item_state(item, STATE.FAILED)
+
+
+func is_state_error(item: TreeItem) -> bool:
+ return is_item_state(item, STATE.ERROR) or is_item_state(item, STATE.ABORDED)
+
+
+func is_item_state_orphan(item: TreeItem) -> bool:
+ return item.has_meta(META_GDUNIT_ORPHAN)
+
+
+func is_test_suite(item: TreeItem) -> bool:
+ return item.has_meta(META_GDUNIT_TYPE) and item.get_meta(META_GDUNIT_TYPE) == GdUnitType.TEST_SUITE
+
+
+func is_test_case(item: TreeItem) -> bool:
+ return item.has_meta(META_GDUNIT_TYPE) and item.get_meta(META_GDUNIT_TYPE) == GdUnitType.TEST_CASE
+
+
+func is_folder(item: TreeItem) -> bool:
+ return item.has_meta(META_GDUNIT_TYPE) and item.get_meta(META_GDUNIT_TYPE) == GdUnitType.FOLDER
+
+
+func is_test_id(item: TreeItem, id: GdUnitGUID) -> bool:
+ if not item.has_meta(META_TEST_CASE):
+ return false
+
+ var test_case: GdUnitTestCase = item.get_meta(META_TEST_CASE)
+ return test_case.guid.equals(id)
+
+
+func disable_test_recovery() -> void:
+ _run_test_recovery = false
+
+
+@warning_ignore("return_value_discarded")
+func _ready() -> void:
+ if Engine.is_editor_hint():
+ var base_control := EditorInterface.get_base_control()
+ base_control.set_meta("GdUnit4Inspector", self)
+
+ _spinner.icon = GdUnitUiTools.get_spinner()
+ init_tree()
+ GdUnitSignals.instance().gdunit_settings_changed.connect(_on_settings_changed)
+ GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event)
+ GdUnitSignals.instance().gdunit_test_discover_added.connect(on_test_case_discover_added)
+ GdUnitSignals.instance().gdunit_test_discover_deleted.connect(on_test_case_discover_deleted)
+ GdUnitSignals.instance().gdunit_test_discover_modified.connect(on_test_case_discover_modified)
+ if _run_test_recovery:
+ GdUnitTestDiscoverer.restore_last_session()
+
+
+# we need current to manually redraw bacause of the animation bug
+# https://github.com/godotengine/godot/issues/69330
+func _process(_delta: float) -> void:
+ if is_visible_in_tree():
+ queue_redraw()
+
+
+func init_tree() -> void:
+ cleanup_tree()
+ _tree.deselect_all()
+ _tree.set_hide_root(true)
+ _tree.ensure_cursor_is_visible()
+ _tree.set_allow_reselect(true)
+ _tree.set_allow_rmb_select(true)
+ _tree.set_columns(2)
+ _tree.set_column_clip_content(0, true)
+ _tree.set_column_expand_ratio(0, 1)
+ _tree.set_column_custom_minimum_width(0, 240)
+ _tree.set_column_expand_ratio(1, 0)
+ _tree.set_column_custom_minimum_width(1, 100)
+ _tree_root = _tree.create_item()
+ _tree_root.set_text(0, "tree_root")
+ _tree_root.set_meta(META_GDUNIT_NAME, "tree_root")
+ _tree_root.set_meta(META_GDUNIT_PROGRESS_COUNT_MAX, 0)
+ _tree_root.set_meta(META_GDUNIT_PROGRESS_INDEX, 0)
+ _tree_root.set_meta(META_GDUNIT_STATE, STATE.INITIAL)
+ _tree_root.set_meta(META_GDUNIT_SUCCESS_TESTS, 0)
+ # fix tree icon scaling
+ var scale_factor := EditorInterface.get_editor_scale() if Engine.is_editor_hint() else 1.0
+ _tree.set("theme_override_constants/icon_max_width", 16 * scale_factor)
+
+
+func cleanup_tree() -> void:
+ clear_reports()
+ if not _tree_root:
+ return
+ _free_recursive()
+ _tree.clear()
+ _current_selected_item = null
+
+
+func _free_recursive(items:=_tree_root.get_children()) -> void:
+ for item in items:
+ _free_recursive(item.get_children())
+ item.call_deferred("free")
+
+
+func sort_tree_items(parent: TreeItem) -> void:
+ _sort_tree_items(parent, GdUnitSettings.get_inspector_tree_sort_mode())
+ _tree.queue_redraw()
+
+
+static func _sort_tree_items(parent: TreeItem, sort_mode: GdUnitInspectorTreeConstants.SORT_MODE) -> void:
+ parent.visible = false
+ var items := parent.get_children()
+ # first remove all childs before sorting
+ for item in items:
+ parent.remove_child(item)
+
+ # do sort by selected sort mode
+ match sort_mode:
+ GdUnitInspectorTreeConstants.SORT_MODE.UNSORTED:
+ items.sort_custom(sort_items_by_original_index)
+
+ GdUnitInspectorTreeConstants.SORT_MODE.NAME_ASCENDING:
+ items.sort_custom(sort_items_by_name.bind(true))
+
+ GdUnitInspectorTreeConstants.SORT_MODE.NAME_DESCENDING:
+ items.sort_custom(sort_items_by_name.bind(false))
+
+ GdUnitInspectorTreeConstants.SORT_MODE.EXECUTION_TIME:
+ items.sort_custom(sort_items_by_execution_time)
+
+ # readding sorted childs
+ for item in items:
+ parent.add_child(item)
+ if item.get_child_count() > 0:
+ _sort_tree_items(item, sort_mode)
+ parent.visible = true
+
+
+static func sort_items_by_name(a: TreeItem, b: TreeItem, ascending: bool) -> bool:
+ var type_a: GdUnitType = a.get_meta(META_GDUNIT_TYPE)
+ var type_b: GdUnitType = b.get_meta(META_GDUNIT_TYPE)
+
+ # Sort folders to the top
+ if type_a == GdUnitType.FOLDER and type_b != GdUnitType.FOLDER:
+ return true
+ if type_b == GdUnitType.FOLDER and type_a != GdUnitType.FOLDER:
+ return false
+
+ # sort by name
+ var name_a: String = a.get_meta(META_GDUNIT_NAME)
+ var name_b: String = b.get_meta(META_GDUNIT_NAME)
+ var comparison := name_a.naturalnocasecmp_to(name_b)
+
+ return comparison < 0 if ascending else comparison > 0
+
+
+static func sort_items_by_execution_time(a: TreeItem, b: TreeItem) -> bool:
+ var type_a: GdUnitType = a.get_meta(META_GDUNIT_TYPE)
+ var type_b: GdUnitType = b.get_meta(META_GDUNIT_TYPE)
+
+ # Sort folders to the top
+ if type_a == GdUnitType.FOLDER and type_b != GdUnitType.FOLDER:
+ return true
+ if type_b == GdUnitType.FOLDER and type_a != GdUnitType.FOLDER:
+ return false
+
+ var execution_time_a :int = a.get_meta(META_GDUNIT_EXECUTION_TIME)
+ var execution_time_b :int = b.get_meta(META_GDUNIT_EXECUTION_TIME)
+ # if has same execution time sort by name
+ if execution_time_a == execution_time_b:
+ var name_a :String = a.get_meta(META_GDUNIT_NAME)
+ var name_b :String = b.get_meta(META_GDUNIT_NAME)
+ return name_a.naturalnocasecmp_to(name_b) > 0
+ return execution_time_a > execution_time_b
+
+
+static func sort_items_by_original_index(a: TreeItem, b: TreeItem) -> bool:
+ var type_a: GdUnitType = a.get_meta(META_GDUNIT_TYPE)
+ var type_b: GdUnitType = b.get_meta(META_GDUNIT_TYPE)
+
+ # Sort folders to the top
+ if type_a == GdUnitType.FOLDER and type_b != GdUnitType.FOLDER:
+ return true
+ if type_b == GdUnitType.FOLDER and type_a != GdUnitType.FOLDER:
+ return false
+
+ var index_a :int = a.get_meta(META_GDUNIT_ORIGINAL_INDEX)
+ var index_b :int = b.get_meta(META_GDUNIT_ORIGINAL_INDEX)
+
+ # Sorting by index
+ return index_a < index_b
+
+
+func restructure_tree(parent: TreeItem, tree_mode: GdUnitInspectorTreeConstants.TREE_VIEW_MODE) -> void:
+ _current_tree_view_mode = tree_mode
+
+ match tree_mode:
+ GdUnitInspectorTreeConstants.TREE_VIEW_MODE.FLAT:
+ restructure_tree_to_flat(parent)
+ GdUnitInspectorTreeConstants.TREE_VIEW_MODE.TREE:
+ restructure_tree_to_tree(parent)
+ recalculate_counters(_tree_root)
+ # finally apply actual sort mode
+ sort_tree_items(_tree_root)
+
+
+# Restructure into flat mode
+func restructure_tree_to_flat(parent: TreeItem) -> void:
+ var folders := flatmap_folders(parent)
+ # Store current folder paths and their test suites
+ for folder_path: String in folders:
+ var test_suites: Array[TreeItem] = folders[folder_path]
+ if test_suites.is_empty():
+ continue
+
+ # Create flat folder and move test suites into it
+ var folder := _tree.create_item(parent)
+ folder.set_meta(META_GDUNIT_NAME, folder_path)
+ update_item_total_counter(folder)
+ set_state_initial(folder, GdUnitType.FOLDER)
+
+ # Move test suites under the flat folder
+ for test_suite in test_suites:
+ var old_parent := test_suite.get_parent()
+ old_parent.remove_child(test_suite)
+ folder.add_child(test_suite)
+
+ # Cleanup old folder structure
+ cleanup_empty_folders(parent)
+
+
+# Restructure into hierarchical tree mode
+func restructure_tree_to_tree(parent: TreeItem) -> void:
+ var items_to_process := parent.get_children().duplicate()
+
+ for item: TreeItem in items_to_process:
+ if is_folder(item):
+ var folder_path: String = item.get_meta(META_GDUNIT_NAME)
+ var parts := folder_path.split("/")
+
+ if parts.size() > 1:
+ var current_parent := parent
+ # Build folder hierarchy
+ for part in parts:
+ var next := _find_tree_item(current_parent, part)
+ if not next:
+ next = _tree.create_item(current_parent)
+ next.set_meta(META_GDUNIT_NAME, part)
+ set_state_initial(next, GdUnitType.FOLDER)
+ current_parent = next
+
+ # Move test suites to deepest folder
+ var test_suites := item.get_children()
+ for test_suite in test_suites:
+ item.remove_child(test_suite)
+ current_parent.add_child(test_suite)
+
+ # Remove the flat folder
+ item.get_parent().remove_child(item)
+ item.free()
+
+
+func flatmap_folders(parent: TreeItem) -> Dictionary:
+ var folder_map := {}
+
+ for item in parent.get_children():
+ if is_folder(item):
+ var current_path: String = item.get_meta(META_GDUNIT_NAME)
+ # Get parent folder paths
+ var parent_path := get_parent_folder_path(item)
+ if parent_path:
+ current_path = parent_path + "/" + current_path
+
+ # Collect direct children of this folder
+ var children: Array[TreeItem] = []
+ for child in item.get_children():
+ if is_test_suite(child):
+ children.append(child)
+
+ # Add children to existing path or create new entry
+ if not children.is_empty():
+ if folder_map.has(current_path):
+ @warning_ignore("unsafe_method_access")
+ folder_map[current_path].append_array(children)
+ else:
+ folder_map[current_path] = children
+
+ # Recursively process subfolders
+ var sub_folders := flatmap_folders(item)
+ for path: String in sub_folders.keys():
+ if folder_map.has(path):
+ @warning_ignore("unsafe_method_access")
+ folder_map[path].append_array(sub_folders[path])
+ else:
+ folder_map[path] = sub_folders[path]
+ return folder_map
+
+
+func get_parent_folder_path(item: TreeItem) -> String:
+ var path := ""
+ var parent := item.get_parent()
+
+ while parent != _tree_root:
+ if is_folder(parent):
+ path = parent.get_meta(META_GDUNIT_NAME) + ("/" + path if path else "")
+ parent = parent.get_parent()
+
+ return path
+
+
+func cleanup_empty_folders(parent: TreeItem) -> void:
+ var folders: Array[TreeItem] = []
+ # First collect all folders to avoid modification during iteration
+ for item in parent.get_children():
+ if is_folder(item):
+ folders.append(item)
+
+ # Process collected folders
+ for folder in folders:
+ cleanup_empty_folders(folder)
+ # Remove folder if it has no children after cleanup
+ if folder.get_child_count() == 0:
+ parent.remove_child(folder)
+ folder.free()
+
+
+func reset_tree_state(parent: TreeItem) -> void:
+ if parent == _tree_root:
+ _tree_root.set_meta(META_GDUNIT_PROGRESS_INDEX, 0)
+ _tree_root.set_meta(META_GDUNIT_STATE, STATE.INITIAL)
+ test_counters_changed.emit(0, 0, STATE.INITIAL)
+
+ for item in parent.get_children():
+ set_state_initial(item, get_item_type(item))
+ reset_tree_state(item)
+
+
+func select_item(item: TreeItem) -> TreeItem:
+ if item != null:
+ # enshure the parent is collapsed
+ do_collapse_parent(item)
+ item.select(0)
+ _tree.ensure_cursor_is_visible()
+ _tree.scroll_to_item(item, true)
+ return item
+
+
+func do_collapse_parent(item: TreeItem) -> void:
+ if item != null:
+ item.collapsed = false
+ do_collapse_parent(item.get_parent())
+
+
+func do_collapse_all(collapse: bool, parent := _tree_root) -> void:
+ for item in parent.get_children():
+ item.collapsed = collapse
+ if not collapse:
+ do_collapse_all(collapse, item)
+
+
+func set_state_initial(item: TreeItem, type: GdUnitType) -> void:
+ item.set_text(0, str(item.get_meta(META_GDUNIT_NAME)))
+ item.set_custom_color(0, Color.LIGHT_GRAY)
+ item.set_tooltip_text(0, "")
+ item.set_text_overrun_behavior(0, TextServer.OVERRUN_TRIM_CHAR)
+ item.set_expand_right(0, true)
+
+ item.set_custom_color(1, Color.LIGHT_GRAY)
+ item.set_text(1, "")
+ item.set_expand_right(1, true)
+ item.set_tooltip_text(1, "")
+
+ item.set_meta(META_GDUNIT_STATE, STATE.INITIAL)
+ item.set_meta(META_GDUNIT_TYPE, type)
+ item.set_meta(META_GDUNIT_SUCCESS_TESTS, 0)
+ item.set_meta(META_GDUNIT_EXECUTION_TIME, 0)
+ if item.has_meta(META_GDUNIT_PROGRESS_COUNT_MAX) and item.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX) > 0:
+ item.set_text(0, "(0/%d) %s" % [item.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX), item.get_meta(META_GDUNIT_NAME)])
+ item.remove_meta(META_GDUNIT_REPORT)
+ item.remove_meta(META_GDUNIT_ORPHAN)
+
+ set_item_icon_by_state(item)
+
+
+func set_state_running(item: TreeItem, is_running: bool) -> void:
+ if is_state_running(item):
+ return
+ if is_item_state(item, STATE.INITIAL):
+ item.set_custom_color(0, Color.DARK_GREEN)
+ item.set_custom_color(1, Color.DARK_GREEN)
+ item.set_meta(META_GDUNIT_STATE, STATE.RUNNING)
+ item.collapsed = false
+
+ if is_running:
+ item.set_icon(0, ICON_SPINNER)
+ else:
+ set_item_icon_by_state(item)
+ for child in item.get_children():
+ set_item_icon_by_state(child)
+
+ var parent := item.get_parent()
+ if parent != _tree_root:
+ set_state_running(parent, is_running)
+
+
+func set_state_succeded(item: TreeItem) -> void:
+ # Do not overwrite higher states
+ if is_state_error(item) or is_state_failed(item):
+ return
+ if item == _tree_root:
+ return
+ item.set_custom_color(0, Color.GREEN)
+ item.set_custom_color(1, Color.GREEN)
+ item.set_meta(META_GDUNIT_STATE, STATE.SUCCESS)
+ item.collapsed = GdUnitSettings.is_inspector_node_collapse()
+ set_item_icon_by_state(item)
+
+
+func set_state_flaky(item: TreeItem, event: GdUnitEvent) -> void:
+ # Do not overwrite higher states
+ if is_state_error(item):
+ return
+ var retry_count := event.statistic(GdUnitEvent.RETRY_COUNT)
+ item.set_meta(META_GDUNIT_STATE, STATE.FLAKY)
+ if retry_count > 1:
+ var item_text: String = item.get_meta(META_GDUNIT_NAME)
+ if item.has_meta(META_GDUNIT_PROGRESS_COUNT_MAX):
+ var success_count: int = item.get_meta(META_GDUNIT_SUCCESS_TESTS)
+ item_text = "(%d/%d) %s" % [success_count, item.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX), item.get_meta(META_GDUNIT_NAME)]
+ item.set_text(0, "%s (%s retries)" % [item_text, retry_count])
+ item.set_custom_color(0, Color.GREEN_YELLOW)
+ item.set_custom_color(1, Color.GREEN_YELLOW)
+ item.collapsed = false
+ set_item_icon_by_state(item)
+
+
+func set_state_skipped(item: TreeItem) -> void:
+ item.set_meta(META_GDUNIT_STATE, STATE.SKIPPED)
+ item.set_text(1, "(skipped)")
+ item.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT)
+ item.set_custom_color(0, Color.DARK_GRAY)
+ item.set_custom_color(1, Color.DARK_GRAY)
+ item.collapsed = false
+ set_item_icon_by_state(item)
+
+
+func set_state_warnings(item: TreeItem) -> void:
+ # Do not overwrite higher states
+ if is_state_error(item) or is_state_failed(item):
+ return
+ item.set_meta(META_GDUNIT_STATE, STATE.WARNING)
+ item.set_custom_color(0, Color.YELLOW)
+ item.set_custom_color(1, Color.YELLOW)
+ item.collapsed = false
+ set_item_icon_by_state(item)
+
+
+func set_state_failed(item: TreeItem, event: GdUnitEvent) -> void:
+ # Do not overwrite higher states
+ if is_state_error(item):
+ return
+ var retry_count := event.statistic(GdUnitEvent.RETRY_COUNT)
+ if retry_count > 1:
+ var item_text: String = item.get_meta(META_GDUNIT_NAME)
+ if item.has_meta(META_GDUNIT_PROGRESS_COUNT_MAX):
+ var success_count: int = item.get_meta(META_GDUNIT_SUCCESS_TESTS)
+ item_text = "(%d/%d) %s" % [success_count, item.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX), item.get_meta(META_GDUNIT_NAME)]
+ item.set_text(0, "%s (%s retries)" % [item_text, retry_count])
+ item.set_meta(META_GDUNIT_STATE, STATE.FAILED)
+ item.set_custom_color(0, Color.LIGHT_BLUE)
+ item.set_custom_color(1, Color.LIGHT_BLUE)
+ item.collapsed = false
+ set_item_icon_by_state(item)
+
+
+func set_state_error(item: TreeItem) -> void:
+ item.set_meta(META_GDUNIT_STATE, STATE.ERROR)
+ item.set_custom_color(0, Color.ORANGE_RED)
+ item.set_custom_color(1, Color.ORANGE_RED)
+ set_item_icon_by_state(item)
+ item.collapsed = false
+
+
+func set_state_aborted(item: TreeItem) -> void:
+ item.set_meta(META_GDUNIT_STATE, STATE.ABORDED)
+ item.set_custom_color(0, Color.ORANGE_RED)
+ item.set_custom_color(1, Color.ORANGE_RED)
+ item.clear_custom_bg_color(0)
+ item.set_text(1, "(aborted)")
+ item.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT)
+ set_item_icon_by_state(item)
+ item.collapsed = false
+
+
+func set_state_orphan(item: TreeItem, event: GdUnitEvent) -> void:
+ var orphan_count := event.statistic(GdUnitEvent.ORPHAN_NODES)
+ if orphan_count == 0:
+ return
+ if item.has_meta(META_GDUNIT_ORPHAN):
+ orphan_count += item.get_meta(META_GDUNIT_ORPHAN)
+ item.set_meta(META_GDUNIT_ORPHAN, orphan_count)
+ if item.get_meta(META_GDUNIT_STATE) != STATE.FAILED:
+ item.set_custom_color(0, Color.YELLOW)
+ item.set_custom_color(1, Color.YELLOW)
+ item.set_tooltip_text(0, "Total <%d> orphan nodes detected." % orphan_count)
+ set_item_icon_by_state(item)
+
+
+func update_state(item: TreeItem, event: GdUnitEvent, add_reports := true) -> void:
+ # we do not show the root
+ if item == null:
+ return
+
+ if event.is_skipped():
+ set_state_skipped(item)
+ elif event.is_success() and event.is_flaky():
+ set_state_flaky(item, event)
+ elif event.is_success():
+ set_state_succeded(item)
+ elif event.is_error():
+ set_state_error(item)
+ elif event.is_failed():
+ set_state_failed(item, event)
+ elif event.is_warning():
+ set_state_warnings(item)
+ if add_reports:
+ for report in event.reports():
+ add_report(item, report)
+ set_state_orphan(item, event)
+
+ var parent := item.get_parent()
+ if parent == null:
+ return
+
+ var item_state: int = item.get_meta(META_GDUNIT_STATE)
+ var parent_state: int = parent.get_meta(META_GDUNIT_STATE)
+ if item_state <= parent_state:
+ return
+ update_state(item.get_parent(), event, false)
+
+
+func add_report(item: TreeItem, report: GdUnitReport) -> void:
+ var reports: Array[GdUnitReport] = []
+ if item.has_meta(META_GDUNIT_REPORT):
+ reports = get_item_reports(item)
+ reports.append(report)
+ item.set_meta(META_GDUNIT_REPORT, reports)
+
+
+func abort_running(items:=_tree_root.get_children()) -> void:
+ for item in items:
+ if item.get_icon(0) == ICON_SPINNER:
+ set_state_aborted(item)
+ abort_running(item.get_children())
+
+
+func _on_select_next_item_by_state(item_state: int) -> TreeItem:
+ var current_selected := _tree.get_selected()
+ # If nothing is selected, the first error is selected or the next one in the vicinity of the current selection is found
+ current_selected = _find_first_item_by_state(_tree_root, item_state) if current_selected == null else _find_item_by_state(current_selected, item_state)
+ # If no next failure found, then we try to select first
+ if current_selected == null:
+ current_selected = _find_first_item_by_state(_tree_root, item_state)
+ return select_item(current_selected)
+
+
+func _on_select_previous_item_by_state(item_state: int) -> TreeItem:
+ var current_selected := _tree.get_selected()
+ # If nothing is selected, the first error is selected or the next one in the vicinity of the current selection is found
+ current_selected = _find_last_item_by_state(_tree_root, item_state) if current_selected == null else _find_item_by_state(current_selected, item_state, true)
+ # If no next failure found, then we try to select first last
+ if current_selected == null:
+ current_selected = _find_last_item_by_state(_tree_root, item_state)
+ return select_item(current_selected)
+
+
+func select_first_orphan() -> void:
+ for parent in _tree_root.get_children():
+ if not is_state_success(parent):
+ for item in parent.get_children():
+ if is_item_state_orphan(item):
+ parent.set_collapsed(false)
+ @warning_ignore("return_value_discarded")
+ select_item(item)
+ return
+
+
+func clear_reports() -> void:
+ for child in _report_list.get_children():
+ _report_list.remove_child(child)
+ child.queue_free()
+
+
+func show_failed_report(selected_item: TreeItem) -> void:
+ clear_reports()
+ if selected_item == null or not selected_item.has_meta(META_GDUNIT_REPORT):
+ return
+ # add new reports
+ for report in get_item_reports(selected_item):
+ var reportNode: RichTextLabel = _report_template.duplicate()
+ _report_list.add_child(reportNode)
+ reportNode.push_color(Color.DARK_TURQUOISE)
+ reportNode.append_text(report.to_string())
+ reportNode.pop()
+ reportNode.visible = true
+
+
+func update_test_suite(event: GdUnitEvent) -> void:
+ var item := _find_tree_item_by_test_suite(_tree_root, event.resource_path(), event.suite_name())
+ if not item:
+ push_error("[InspectorTreeMainPanel#update_test_suite] Internal Error: Can't find test suite item '{_suite_name}' for {_resource_path} ".format(event))
+ return
+ if event.type() == GdUnitEvent.TESTSUITE_AFTER:
+ update_item_elapsed_time_counter(item, event.elapsed_time())
+ update_state(item, event)
+ set_state_running(item, false)
+
+
+func update_test_case(event: GdUnitEvent) -> void:
+ var item := _find_tree_item_by_id(_tree_root, event.guid())
+ if not item:
+ #push_error("Internal Error: Can't find test id %s" % [event.guid()])
+ return
+ if event.type() == GdUnitEvent.TESTCASE_BEFORE:
+ set_state_running(item, true)
+ # force scrolling to current test case
+ _tree.scroll_to_item(item, true)
+ return
+
+ if event.type() == GdUnitEvent.TESTCASE_AFTER:
+ update_item_elapsed_time_counter(item, event.elapsed_time())
+ if event.is_success() or event.is_warning():
+ update_item_processed_counter(item)
+ update_state(item, event)
+ update_progress_counters(item)
+
+
+func create_item(parent: TreeItem, test: GdUnitTestCase, item_name: String, type: GdUnitType) -> TreeItem:
+ var item := _tree.create_item(parent)
+ item.collapsed = true
+ item.set_meta(META_GDUNIT_ORIGINAL_INDEX, item.get_index())
+ item.set_text(0, item_name)
+ match type:
+ GdUnitType.TEST_CASE:
+ item.set_meta(META_TEST_CASE, test)
+ GdUnitType.TEST_GROUP:
+ # We need to create a copy of the test record meta with a new uniqe guid
+ item.set_meta(META_TEST_CASE, GdUnitTestCase.from(test.suite_resource_path, test.source_file, test.line_number, test.test_name))
+ GdUnitType.TEST_SUITE:
+ # We need to create a copy of the test record meta with a new uniqe guid
+ item.set_meta(META_TEST_CASE, GdUnitTestCase.from(test.suite_resource_path, test.source_file, test.line_number, test.suite_name))
+
+ item.set_meta(META_GDUNIT_NAME, item_name)
+ set_state_initial(item, type)
+ update_item_total_counter(item)
+ return item
+
+
+func set_item_icon_by_state(item :TreeItem) -> void:
+ if item == _tree_root:
+ return
+ var state :STATE = item.get_meta(META_GDUNIT_STATE)
+ var is_orphan := is_item_state_orphan(item)
+ var resource_path := get_item_source_file(item)
+ item.set_icon(0, get_icon_by_file_type(resource_path, state, is_orphan))
+ if item.get_meta(META_GDUNIT_TYPE) == GdUnitType.FOLDER:
+ item.set_icon_modulate(0, Color.SKY_BLUE)
+
+
+func update_item_total_counter(item: TreeItem) -> void:
+ if item == null:
+ return
+
+ var child_count := get_total_child_count(item)
+ if child_count > 0:
+ item.set_meta(META_GDUNIT_PROGRESS_COUNT_MAX, child_count)
+ item.set_text(0, "(0/%d) %s" % [child_count, item.get_meta(META_GDUNIT_NAME)])
+
+ update_item_total_counter(item.get_parent())
+
+
+func get_total_child_count(item: TreeItem) -> int:
+ var total_count := 0
+ for child in item.get_children():
+ total_count += child.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX) if child.has_meta(META_GDUNIT_PROGRESS_COUNT_MAX) else 1
+ return total_count
+
+
+func update_item_processed_counter(item: TreeItem, add_count := 1) -> void:
+ if item == _tree_root:
+ return
+
+ var success_count: int = item.get_meta(META_GDUNIT_SUCCESS_TESTS) + add_count
+ item.set_meta(META_GDUNIT_SUCCESS_TESTS, success_count)
+ if item.has_meta(META_GDUNIT_PROGRESS_COUNT_MAX):
+ item.set_text(0, "(%d/%d) %s" % [success_count, item.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX), item.get_meta(META_GDUNIT_NAME)])
+
+ update_item_processed_counter(item.get_parent(), add_count)
+
+
+func update_progress_counters(item: TreeItem) -> void:
+ var index: int = _tree_root.get_meta(META_GDUNIT_PROGRESS_INDEX) + 1
+ var total_test: int = _tree_root.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX)
+ var state: STATE = item.get_meta(META_GDUNIT_STATE)
+ test_counters_changed.emit(index, total_test, state)
+ _tree_root.set_meta(META_GDUNIT_PROGRESS_INDEX, index)
+
+
+func recalculate_counters(parent: TreeItem) -> void:
+ # Reset the counter first
+ if parent.has_meta(META_GDUNIT_PROGRESS_COUNT_MAX):
+ parent.set_meta(META_GDUNIT_PROGRESS_COUNT_MAX, 0)
+ if parent.has_meta(META_GDUNIT_PROGRESS_INDEX):
+ parent.set_meta(META_GDUNIT_PROGRESS_INDEX, 0)
+ if parent.has_meta(META_GDUNIT_SUCCESS_TESTS):
+ parent.set_meta(META_GDUNIT_SUCCESS_TESTS, 0)
+
+ # Calculate new count based on children
+ var total_count := 0
+ var success_count := 0
+ var progress_index := 0
+
+ for child in parent.get_children():
+ if child.get_child_count() > 0:
+ # Recursively update child counters first
+ recalculate_counters(child)
+ # Add child's counters to parent
+ if child.has_meta(META_GDUNIT_PROGRESS_COUNT_MAX):
+ total_count += child.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX)
+ if child.has_meta(META_GDUNIT_SUCCESS_TESTS):
+ success_count += child.get_meta(META_GDUNIT_SUCCESS_TESTS)
+ if child.has_meta(META_GDUNIT_PROGRESS_INDEX):
+ progress_index += child.get_meta(META_GDUNIT_PROGRESS_INDEX)
+ elif is_test_case(child):
+ # Count individual test cases
+ total_count += 1
+ # Count completed tests
+ if is_state_success(child) or is_state_warning(child) or is_state_failed(child) or is_state_error(child):
+ progress_index += 1
+ if is_state_success(child) or is_state_warning(child):
+ success_count += 1
+
+ # Update the counters
+ if total_count > 0:
+ parent.set_meta(META_GDUNIT_PROGRESS_COUNT_MAX, total_count)
+ parent.set_meta(META_GDUNIT_PROGRESS_INDEX, progress_index)
+ parent.set_meta(META_GDUNIT_SUCCESS_TESTS, success_count)
+
+ # Update the display text
+ parent.set_text(0, "(%d/%d) %s" % [success_count, total_count, parent.get_meta(META_GDUNIT_NAME)])
+
+
+func update_item_elapsed_time_counter(item: TreeItem, time: int) -> void:
+ item.set_text(1, "%s" % LocalTime.elapsed(time))
+ item.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT)
+ item.set_meta(META_GDUNIT_EXECUTION_TIME, time)
+
+ var parent := item.get_parent()
+ if parent == _tree_root:
+ return
+ var elapsed_time :int = parent.get_meta(META_GDUNIT_EXECUTION_TIME) + time
+ var type :GdUnitType = item.get_meta(META_GDUNIT_TYPE)
+ match type:
+ GdUnitType.TEST_CASE:
+ return
+ GdUnitType.TEST_SUITE:
+ update_item_elapsed_time_counter(parent, elapsed_time)
+ #GdUnitType.FOLDER:
+ # update_item_elapsed_time_counter(parent, elapsed_time)
+
+
+func get_icon_by_file_type(path: String, state: STATE, orphans: bool) -> Texture2D:
+ if path.get_extension() == "gd":
+ match state:
+ STATE.INITIAL:
+ return ICON_GDSCRIPT_TEST_DEFAULT
+ STATE.SUCCESS:
+ return ICON_GDSCRIPT_TEST_SUCCESS_ORPHAN if orphans else ICON_GDSCRIPT_TEST_SUCCESS
+ STATE.ERROR:
+ return ICON_GDSCRIPT_TEST_ERRORS_ORPHAN if orphans else ICON_GDSCRIPT_TEST_ERROR
+ STATE.FAILED:
+ return ICON_GDSCRIPT_TEST_FAILED_ORPHAN if orphans else ICON_GDSCRIPT_TEST_FAILED
+ STATE.WARNING:
+ return ICON_GDSCRIPT_TEST_SUCCESS_ORPHAN if orphans else ICON_GDSCRIPT_TEST_DEFAULT
+ STATE.FLAKY:
+ return ICON_GDSCRIPT_TEST_SUCCESS_ORPHAN if orphans else ICON_GDSCRIPT_TEST_FLAKY
+ _:
+ return ICON_GDSCRIPT_TEST_DEFAULT
+ if path.get_extension() == "cs":
+ match state:
+ STATE.INITIAL:
+ return ICON_CSSCRIPT_TEST_DEFAULT
+ STATE.SUCCESS:
+ return ICON_CSSCRIPT_TEST_SUCCESS_ORPHAN if orphans else ICON_CSSCRIPT_TEST_SUCCESS
+ STATE.ERROR:
+ return ICON_CSSCRIPT_TEST_ERRORS_ORPHAN if orphans else ICON_CSSCRIPT_TEST_ERROR
+ STATE.FAILED:
+ return ICON_CSSCRIPT_TEST_FAILED_ORPHAN if orphans else ICON_CSSCRIPT_TEST_FAILED
+ STATE.WARNING:
+ return ICON_CSSCRIPT_TEST_SUCCESS_ORPHAN if orphans else ICON_CSSCRIPT_TEST_DEFAULT
+ _:
+ return ICON_CSSCRIPT_TEST_DEFAULT
+ match state:
+ STATE.INITIAL:
+ return ICON_FOLDER
+ STATE.ERROR:
+ return ICON_FOLDER
+ STATE.FAILED:
+ return ICON_FOLDER
+ _:
+ return ICON_FOLDER
+
+
+func on_test_case_discover_added(test_case: GdUnitTestCase) -> void:
+ var test_root_folder := GdUnitSettings.test_root_folder().replace("res://", "")
+ var fully_qualified_name := test_case.fully_qualified_name.trim_suffix(test_case.display_name)
+ var parts := fully_qualified_name.split(".", false)
+ parts.append(test_case.display_name)
+ # Skip tree structure until test root folder
+ var index := parts.find(test_root_folder)
+ if index != -1:
+ parts = parts.slice(index+1)
+
+ match _current_tree_view_mode:
+ GdUnitInspectorTreeConstants.TREE_VIEW_MODE.FLAT:
+ create_items_tree_mode_flat(test_case, parts)
+ GdUnitInspectorTreeConstants.TREE_VIEW_MODE.TREE:
+ create_items_tree_mode_tree(test_case, parts)
+
+
+func create_items_tree_mode_tree(test_case: GdUnitTestCase, parts: PackedStringArray) -> void:
+ var parent := _tree_root
+ var is_suite_assigned := false
+ var suite_name := test_case.suite_name.split(".")[-1]
+ for item_name in parts:
+ var next := _find_tree_item(parent, item_name)
+ if next != null:
+ parent = next
+ continue
+
+ if not is_suite_assigned and suite_name == item_name:
+ next = create_item(parent, test_case, item_name, GdUnitType.TEST_SUITE)
+ is_suite_assigned = true
+ elif item_name == test_case.display_name:
+ next = create_item(parent, test_case, item_name, GdUnitType.TEST_CASE)
+ # On grouped tests (parameterized tests)
+ elif item_name == test_case.test_name:
+ next = create_item(parent, test_case, item_name, GdUnitType.TEST_GROUP)
+ else:
+ next = create_item(parent, test_case, item_name, GdUnitType.FOLDER)
+ parent = next
+
+
+func create_items_tree_mode_flat(test_case: GdUnitTestCase, parts: PackedStringArray) -> void:
+ # All parts except the last two (suite name and test name/display name)
+ var slice_index := -2 if parts[-1] == test_case.test_name else -3
+ var path_parts := parts.slice(0, slice_index)
+ var folder_path := "/".join(path_parts)
+
+ # Find or create flat folder
+ var folder_item: TreeItem
+ if folder_path.is_empty():
+ folder_item = _tree_root
+ else:
+ folder_item = _find_tree_item(_tree_root, folder_path)
+ if folder_item == null:
+ folder_item = create_item(_tree_root, test_case, folder_path, GdUnitType.FOLDER)
+
+ # Find suite under the flat folder (second to last part)
+ var suite_item := _find_tree_item(folder_item, test_case.suite_name)
+ if suite_item == null:
+ suite_item = create_item(folder_item, test_case, test_case.suite_name, GdUnitType.TEST_SUITE)
+
+ # Add test case or group under the suite
+ if test_case.test_name != test_case.display_name:
+ # It's a parameterized test group
+ var group_item := _find_tree_item(suite_item, test_case.test_name)
+ if group_item == null:
+ group_item = create_item(suite_item, test_case, test_case.test_name, GdUnitType.TEST_GROUP)
+ create_item(group_item, test_case, test_case.display_name, GdUnitType.TEST_CASE)
+ else:
+ create_item(suite_item, test_case, test_case.display_name, GdUnitType.TEST_CASE)
+
+
+func on_test_case_discover_deleted(test_case: GdUnitTestCase) -> void:
+ var item := _find_tree_item_by_id(_tree_root, test_case.guid)
+ if item != null:
+ var parent := item.get_parent()
+ parent.remove_child(item)
+
+ # update the cached counters
+ var item_success_count: int = item.get_meta(META_GDUNIT_SUCCESS_TESTS)
+ var item_total_test_count: int = item.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX, 0)
+ var total_test_count: int = parent.get_meta(META_GDUNIT_PROGRESS_COUNT_MAX, 0)
+ parent.set_meta(META_GDUNIT_PROGRESS_COUNT_MAX, total_test_count-item_total_test_count)
+
+ # propagate counter update to all parents
+ update_item_total_counter(parent)
+ update_item_processed_counter(parent, -item_success_count)
+
+
+func on_test_case_discover_modified(test_case: GdUnitTestCase) -> void:
+ var item := _find_tree_item_by_id(_tree_root, test_case.guid)
+ if item != null:
+ item.set_meta(META_TEST_CASE, test_case)
+ item.set_text(0, test_case.display_name)
+ item.set_meta(META_GDUNIT_NAME, test_case.display_name)
+
+
+func get_item_reports(item: TreeItem) -> Array[GdUnitReport]:
+ return item.get_meta(META_GDUNIT_REPORT)
+
+
+func get_item_test_line_number(item: TreeItem) -> int:
+ if item == null or not item.has_meta(META_TEST_CASE):
+ return -1
+
+ var test_case: GdUnitTestCase = item.get_meta(META_TEST_CASE)
+ return test_case.line_number
+
+
+func get_item_source_file(item: TreeItem) -> String:
+ if item == null or not item.has_meta(META_TEST_CASE):
+ return ""
+
+ var test_case: GdUnitTestCase = item.get_meta(META_TEST_CASE)
+ return test_case.source_file
+
+
+func get_item_type(item: TreeItem) -> GdUnitType:
+ if item == null or not item.has_meta(META_GDUNIT_TYPE):
+ return GdUnitType.FOLDER
+ return item.get_meta(META_GDUNIT_TYPE)
+
+
+func _dump_tree_as_json(dump_name: String) -> void:
+ var dict := _to_json(_tree_root)
+ var file := FileAccess.open("res://%s.json" % dump_name, FileAccess.WRITE)
+ file.store_string(JSON.stringify(dict, "\t"))
+
+
+func _to_json(parent :TreeItem) -> Dictionary:
+ var item_as_dict := GdObjects.obj2dict(parent)
+ item_as_dict["TreeItem"]["childrens"] = parent.get_children().map(func(item: TreeItem) -> Dictionary:
+ return _to_json(item))
+ return item_as_dict
+
+
+func extract_resource_path(event: GdUnitEvent) -> String:
+ return ProjectSettings.localize_path(event.resource_path())
+
+
+func collect_test_cases(item: TreeItem, tests: Array[GdUnitTestCase] = []) -> Array[GdUnitTestCase]:
+ for next in item.get_children():
+ collect_test_cases(next, tests)
+
+ if is_test_case(item):
+ var test: GdUnitTestCase = item.get_meta(META_TEST_CASE)
+ if not tests.has(test):
+ tests.append(test)
+
+ return tests
+
+
+func test_session_start() -> void:
+ _context_menu.disable_items()
+ reset_tree_state(_tree_root)
+ clear_reports()
+
+
+func test_session_stop() -> void:
+ _context_menu.enable_items()
+ abort_running()
+ sort_tree_items(_tree_root)
+ # wait until the tree redraw
+ await get_tree().process_frame
+ var failure_item := _find_first_item_by_state(_tree_root, STATE.FAILED)
+ select_item( failure_item if failure_item else _current_selected_item)
+
+
+################################################################################
+# Tree signal receiver
+################################################################################
+func _on_tree_item_mouse_selected(mouse_position: Vector2, mouse_button_index: int) -> void:
+ if mouse_button_index == MOUSE_BUTTON_RIGHT:
+ _context_menu.position = get_screen_position() + mouse_position
+ _context_menu.popup()
+
+
+func _on_Tree_item_selected() -> void:
+ _current_selected_item = _tree.get_selected()
+ show_failed_report(_current_selected_item)
+ tree_item_selected.emit(_current_selected_item)
+
+
+# Opens the test suite
+func _on_Tree_item_activated() -> void:
+ var selected_item := _tree.get_selected()
+ var line_number := get_item_test_line_number(selected_item)
+ if line_number != -1:
+ var script_path := ProjectSettings.localize_path(get_item_source_file(selected_item))
+ var resource: Script = load(script_path)
+
+ if selected_item.has_meta(META_GDUNIT_REPORT):
+ var reports := get_item_reports(selected_item)
+ var report_line_number := reports[0].line_number()
+ # if number -1 we use original stored line number of the test case
+ # in non debug mode the line number is not available
+ if report_line_number != -1:
+ line_number = report_line_number
+
+ EditorInterface.get_file_system_dock().navigate_to_path(script_path)
+ EditorInterface.edit_script(resource, line_number)
+ elif selected_item.get_meta(META_GDUNIT_TYPE) == GdUnitType.FOLDER:
+ # Toggle collapse if dir
+ selected_item.collapsed = not selected_item.collapsed
+
+
+################################################################################
+# external signal receiver
+################################################################################
+
+func _on_gdunit_event(event: GdUnitEvent) -> void:
+ match event.type():
+ GdUnitEvent.DISCOVER_START:
+ _tree_root.visible = false
+ _discover_hint.visible = true
+ init_tree()
+
+ GdUnitEvent.DISCOVER_END:
+ sort_tree_items(_tree_root)
+ select_item(_tree_root.get_first_child())
+ _discover_hint.visible = false
+ _tree_root.visible = true
+ #_dump_tree_as_json("tree_example_discovered")
+
+ GdUnitEvent.TESTCASE_BEFORE:
+ update_test_case(event)
+
+ GdUnitEvent.TESTCASE_AFTER:
+ update_test_case(event)
+
+ GdUnitEvent.TESTSUITE_BEFORE:
+ update_test_suite(event)
+
+ GdUnitEvent.TESTSUITE_AFTER:
+ update_test_suite(event)
+
+ GdUnitEvent.SESSION_START:
+ test_session_start()
+
+ GdUnitEvent.SESSION_CLOSE:
+ await test_session_stop()
+
+
+func _on_settings_changed(property :GdUnitProperty) -> void:
+ match property.name():
+ GdUnitSettings.INSPECTOR_TREE_SORT_MODE:
+ sort_tree_items(_tree_root)
+ #_dump_tree_as_json("tree_sorted_by_%s" % GdUnitInspectorTreeConstants.SORT_MODE.keys()[property.value()])
+
+ GdUnitSettings.INSPECTOR_TREE_VIEW_MODE:
+ restructure_tree(_tree_root, GdUnitSettings.get_inspector_tree_view_mode())
diff --git a/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd.uid b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd.uid
new file mode 100644
index 0000000..587b139
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd.uid
@@ -0,0 +1 @@
+uid://m0w5xmvdst0k
diff --git a/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn b/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn
new file mode 100644
index 0000000..2754f74
--- /dev/null
+++ b/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn
@@ -0,0 +1,240 @@
+[gd_scene format=3 uid="uid://bqfpidewtpeg0"]
+
+[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd" id="1"]
+[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.tscn" id="2_o6s0p"]
+
+[sub_resource type="DPITexture" id="DPITexture_miuuy"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M4 12a1 1 0 0 0 1.555.832l6-4a1 1 0 0 0 0-1.664l-6-4A1 1 0 0 0 4 4z\"/></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_ern2r"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M3 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm4.975 0A1 1 0 0 0 7 3.998v8a1 1 0 0 0 1.625.781l5-4a1 1 0 0 0 0-1.56l-5-4A1 1 0 0 0 7.975 3z\"/></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_qdci2"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"m8 9.669-3.536 2.583H7v2.537h2v-2.537h2.536zm0-3.314L4.464 3.772H7V1.235h2v2.537h2.536zm-7.296.73h14.591v1.831H.704z\"/></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_hed0i"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"m8 16-3.5-2.5H7V11h2v2.5h2.5zM8 0 4.5 2.5H7V5h2V2.5h2.5zM1 7h14v2H1z\"/></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_8v04w"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><g fill=\"#e0e0e0\"><path d=\"M9 1v3.055a4 4 0 0 1 1.027.426l2.159-2.159A7 7 0 0 0 9 1z\"/><path fill-opacity=\".2\" d=\"M7 1.08A7 7 0 0 0 3.814 2.4l2.159 2.159A4 4 0 0 1 7 4.13V1.08zM2.4 3.814A7 7 0 0 0 1.078 7h3.055a4 4 0 0 1 .426-1.027L2.4 3.815zm11.2 0-2.159 2.159A4 4 0 0 1 11.869 7h3.05a7 7 0 0 0-1.32-3.186zM1.08 9a7 7 0 0 0 1.32 3.186l2.158-2.159A4 4 0 0 1 4.13 9H1.08zm10.786 0a4 4 0 0 1-.425 1.027l2.158 2.159A7 7 0 0 0 14.921 9h-3.055zm-5.894 2.441L3.814 13.6a7 7 0 0 0 3.185 1.322v-3.055a4 4 0 0 1-1.027-.426zm4.055 0a4 4 0 0 1-1.028.428v3.05a7 7 0 0 0 3.186-1.32l-2.158-2.158z\"/></g></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_arwmg"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><g fill=\"#e0e0e0\"><path fill-opacity=\".2\" d=\"M9 1.078v3.055a4 4 0 0 1 1.027.426L12.186 2.4A7 7 0 0 0 9 1.078zM7 1.08A7 7 0 0 0 3.814 2.4l2.159 2.159A4 4 0 0 1 7 4.13V1.079zM2.4 3.814A7 7 0 0 0 1.078 7h3.055a4 4 0 0 1 .426-1.027L2.4 3.814zM1.08 9a7 7 0 0 0 1.32 3.185l2.159-2.158A4 4 0 0 1 4.131 9H1.08zm10.787 0a4 4 0 0 1-.426 1.027l2.159 2.158A7 7 0 0 0 14.922 9h-3.055zm-5.894 2.441L3.814 13.6A7 7 0 0 0 7 14.922v-3.055a4 4 0 0 1-1.027-.426zm4.054 0A4 4 0 0 1 9 11.869v3.05a7 7 0 0 0 3.186-1.32l-2.159-2.158z\"/><path d=\"m13.6 3.8-2.158 2.158a4 4 0 0 1 .428 1.028h3.05A7 7 0 0 0 13.6 3.8z\"/></g></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_rqglq"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><g fill=\"#e0e0e0\"><path fill-opacity=\".2\" d=\"M9 1.078v3.055a4 4 0 0 1 1.027.426L12.186 2.4A7 7 0 0 0 9 1.078zM7 1.08A7 7 0 0 0 3.814 2.4l2.159 2.159A4 4 0 0 1 7 4.13V1.079zM2.4 3.814A7 7 0 0 0 1.078 7h3.055a4 4 0 0 1 .426-1.027L2.4 3.814zm11.2 0-2.159 2.159A4 4 0 0 1 11.869 7h3.05a7 7 0 0 0-1.32-3.186zM1.08 9a7 7 0 0 0 1.32 3.185l2.158-2.158A4 4 0 0 1 4.13 9H1.08zm4.892 2.441L3.814 13.6a7 7 0 0 0 3.185 1.322v-3.055a4 4 0 0 1-1.027-.426zm4.055 0a4 4 0 0 1-1.028.428v3.05a7 7 0 0 0 3.186-1.32l-2.158-2.158z\"/><path d=\"M11.867 9a4 4 0 0 1-.426 1.027l2.158 2.159A7 7 0 0 0 14.922 9h-3.055z\"/></g></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_ltb1l"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><g fill=\"#e0e0e0\"><path fill-opacity=\".2\" d=\"M9 1.078v3.055a4 4 0 0 1 1.027.426L12.186 2.4A7 7 0 0 0 9 1.078zM7 1.08A7 7 0 0 0 3.814 2.4l2.159 2.159A4 4 0 0 1 7 4.13V1.079zM2.4 3.814A7 7 0 0 0 1.078 7h3.055a4 4 0 0 1 .426-1.027L2.4 3.814zm11.2 0-2.159 2.159A4 4 0 0 1 11.869 7h3.05a7 7 0 0 0-1.32-3.186zM1.08 9a7 7 0 0 0 1.32 3.185l2.158-2.158A4 4 0 0 1 4.13 9H1.08zm10.786 0a4 4 0 0 1-.425 1.027l2.158 2.158A7 7 0 0 0 14.921 9h-3.055zm-5.894 2.441L3.814 13.6a7 7 0 0 0 3.185 1.322v-3.055a4 4 0 0 1-1.027-.426z\"/><path d=\"M10.027 11.4A4 4 0 0 1 9 11.828v3.05a7 7 0 0 0 3.185-1.32z\"/></g></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_2lq8w"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><g fill=\"#e0e0e0\"><path fill-opacity=\".2\" d=\"M9 1.078v3.055a4 4 0 0 1 1.027.426L12.186 2.4A7 7 0 0 0 9 1.078zM7 1.08A7 7 0 0 0 3.814 2.4l2.159 2.159A4 4 0 0 1 7 4.13V1.079zM2.4 3.814A7 7 0 0 0 1.078 7h3.055a4 4 0 0 1 .426-1.027L2.4 3.814zm11.2 0-2.159 2.159A4 4 0 0 1 11.869 7h3.05a7 7 0 0 0-1.32-3.186zM1.08 9a7 7 0 0 0 1.32 3.185l2.158-2.158A4 4 0 0 1 4.13 9H1.08zm10.786 0a4 4 0 0 1-.425 1.027l2.158 2.158A7 7 0 0 0 14.921 9h-3.055zm-1.84 2.441a4 4 0 0 1-1.027.428v3.05a7 7 0 0 0 3.186-1.32l-2.158-2.158z\"/><path d=\"m5.973 11.4-2.159 2.158A7 7 0 0 0 7 14.88v-3.054a4 4 0 0 1-1.027-.426z\"/></g></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_kwwmp"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><g fill=\"#e0e0e0\"><path fill-opacity=\".2\" d=\"M9 1.078v3.055a4 4 0 0 1 1.027.426L12.186 2.4A7 7 0 0 0 9 1.078zM7 1.08A7 7 0 0 0 3.814 2.4l2.159 2.159A4 4 0 0 1 7 4.13V1.079zM2.4 3.814A7 7 0 0 0 1.078 7h3.055a4 4 0 0 1 .426-1.027L2.4 3.814zm11.2 0-2.159 2.159A4 4 0 0 1 11.869 7h3.05a7 7 0 0 0-1.32-3.186zM11.867 9a4 4 0 0 1-.426 1.027l2.158 2.158A7 7 0 0 0 14.922 9h-3.055zm-5.895 2.441L3.814 13.6A7 7 0 0 0 7 14.922v-3.055a4 4 0 0 1-1.028-.426zm4.055 0A4 4 0 0 1 9 11.869v3.05a7 7 0 0 0 3.185-1.32l-2.158-2.158z\"/><path d=\"M1.08 9a7 7 0 0 0 1.32 3.186l2.159-2.159A4 4 0 0 1 4.131 9H1.08z\"/></g></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_t1rin"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><g fill=\"#e0e0e0\"><path fill-opacity=\".2\" d=\"M9 1.078v3.055a4 4 0 0 1 1.027.426L12.186 2.4A7 7 0 0 0 9 1.078zM7 1.08A7 7 0 0 0 3.814 2.4l2.159 2.159A4 4 0 0 1 7 4.13V1.079zm6.6 2.734-2.159 2.159A4 4 0 0 1 11.869 7h3.05a7 7 0 0 0-1.32-3.186zM1.08 9a7 7 0 0 0 1.32 3.185l2.158-2.158A4 4 0 0 1 4.13 9H1.08zm10.787 0a4 4 0 0 1-.426 1.027l2.158 2.158A7 7 0 0 0 14.921 9h-3.054zm-5.895 2.441L3.814 13.6a7 7 0 0 0 3.185 1.322v-3.055a4 4 0 0 1-1.027-.426zm4.055 0A4 4 0 0 1 9 11.869v3.05a7 7 0 0 0 3.185-1.32l-2.158-2.158z\"/><path d=\"M2.4 3.8a7 7 0 0 0-1.322 3.186h3.055a4 4 0 0 1 .426-1.028L2.4 3.8z\"/></g></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="DPITexture" id="DPITexture_pi1kv"]
+_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><g fill=\"#e0e0e0\"><path fill-opacity=\".2\" d=\"M9 1.078v3.055a4 4 0 0 1 1.027.426L12.186 2.4A7 7 0 0 0 9 1.078zM2.4 3.814A7 7 0 0 0 1.078 7h3.055a4 4 0 0 1 .426-1.027L2.4 3.814zm11.2 0-2.159 2.159A4 4 0 0 1 11.869 7h3.05a7 7 0 0 0-1.32-3.186zM1.08 9a7 7 0 0 0 1.32 3.185l2.158-2.158A4 4 0 0 1 4.13 9H1.08zm10.786 0a4 4 0 0 1-.425 1.027l2.158 2.158A7 7 0 0 0 14.921 9h-3.055zm-5.894 2.441L3.814 13.6a7 7 0 0 0 3.185 1.322v-3.055a4 4 0 0 1-1.027-.426zm4.055 0a4 4 0 0 1-1.028.428v3.05a7 7 0 0 0 3.186-1.32l-2.158-2.158z\"/><path d=\"M7 1a7 7 0 0 0-3.186 1.32l2.159 2.159A4 4 0 0 1 7 4.05z\"/></g></svg>
+"
+saturation = 2.0
+color_map = {
+Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
+Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
+Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
+}
+
+[sub_resource type="AnimatedTexture" id="AnimatedTexture_eor1x"]
+frames = 8
+speed_scale = 2.5
+frame_0/texture = SubResource("DPITexture_8v04w")
+frame_0/duration = 0.2
+frame_1/texture = SubResource("DPITexture_arwmg")
+frame_1/duration = 0.2
+frame_2/texture = SubResource("DPITexture_rqglq")
+frame_2/duration = 0.2
+frame_3/texture = SubResource("DPITexture_ltb1l")
+frame_3/duration = 0.2
+frame_4/texture = SubResource("DPITexture_2lq8w")
+frame_4/duration = 0.2
+frame_5/texture = SubResource("DPITexture_kwwmp")
+frame_5/duration = 0.2
+frame_6/texture = SubResource("DPITexture_t1rin")
+frame_6/duration = 0.2
+frame_7/texture = SubResource("DPITexture_pi1kv")
+frame_7/duration = 0.2
+
+[node name="MainPanel" type="VSplitContainer" unique_id=1044515559]
+use_parent_material = true
+clip_contents = true
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_right = -924.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+split_offsets = PackedInt32Array(200)
+split_offset = 200
+script = ExtResource("1")
+
+[node name="contextMenu" parent="." unique_id=73764904 instance=ExtResource("2_o6s0p")]
+visible = false
+item_0/icon = SubResource("DPITexture_miuuy")
+item_1/icon = SubResource("DPITexture_ern2r")
+item_2/text = "Run Tests Until Fail"
+item_2/icon = SubResource("DPITexture_miuuy")
+item_4/icon = SubResource("DPITexture_qdci2")
+item_5/icon = SubResource("DPITexture_hed0i")
+
+[node name="Panel" type="PanelContainer" parent="." unique_id=641912870]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Tree" type="Tree" parent="Panel" unique_id=741772884]
+use_parent_material = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/icon_max_width = 16
+columns = 2
+allow_reselect = true
+allow_rmb_select = true
+hide_root = true
+select_mode = 1
+
+[node name="discover_hint" type="HBoxContainer" parent="Panel" unique_id=555683311]
+unique_name_in_owner = true
+visible = false
+use_parent_material = true
+layout_mode = 2
+alignment = 1
+
+[node name="spinner" type="Button" parent="Panel/discover_hint" unique_id=200472082]
+unique_name_in_owner = true
+clip_contents = true
+custom_minimum_size = Vector2(64, 64)
+layout_mode = 2
+size_flags_stretch_ratio = 1.94
+disabled = true
+button_mask = 0
+text = "Discover Tests"
+icon = SubResource("AnimatedTexture_eor1x")
+flat = true
+alignment = 2
+
+[node name="report" type="PanelContainer" parent="." unique_id=503703488]
+clip_contents = true
+layout_mode = 2
+size_flags_horizontal = 11
+size_flags_vertical = 11
+
+[node name="report_template" type="RichTextLabel" parent="report" unique_id=817912164]
+auto_translate_mode = 2
+use_parent_material = true
+clip_contents = false
+layout_mode = 2
+size_flags_horizontal = 3
+localize_numeral_system = false
+focus_mode = 2
+bbcode_enabled = true
+fit_content = true
+selection_enabled = true
+
+[node name="ScrollContainer" type="ScrollContainer" parent="report" unique_id=1479075289]
+use_parent_material = true
+custom_minimum_size = Vector2(0, 80)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 11
+
+[node name="list" type="VBoxContainer" parent="report/ScrollContainer" unique_id=1104634565]
+clip_contents = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[connection signal="item_activated" from="Panel/Tree" to="." method="_on_Tree_item_activated"]
+[connection signal="item_mouse_selected" from="Panel/Tree" to="." method="_on_tree_item_mouse_selected"]
+[connection signal="item_selected" from="Panel/Tree" to="." method="_on_Tree_item_selected"]