summaryrefslogtreecommitdiff
path: root/addons/gdUnit4/src/ui/settings
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/settings
parent6d53d8cac78250dc888aa49d5f9d4175c6652feb (diff)
escoria updateHEADmain
Diffstat (limited to 'addons/gdUnit4/src/ui/settings')
-rw-r--r--addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd56
-rw-r--r--addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd.uid1
-rw-r--r--addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn36
-rw-r--r--addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd327
-rw-r--r--addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd.uid1
-rw-r--r--addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn349
-rw-r--r--addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.gd255
-rw-r--r--addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.gd.uid1
-rw-r--r--addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.tscn148
-rw-r--r--addons/gdUnit4/src/ui/settings/logo.pngbin0 -> 49775 bytes
-rw-r--r--addons/gdUnit4/src/ui/settings/logo.png.import40
11 files changed, 1214 insertions, 0 deletions
diff --git a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd
new file mode 100644
index 0000000..b5cc837
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd
@@ -0,0 +1,56 @@
+@tool
+class_name GdUnitInputCapture
+extends Control
+
+signal input_completed(input_event: InputEventKey)
+
+
+var _tween: Tween
+var _input_event: InputEventKey
+
+
+func _ready() -> void:
+ reset()
+ self_modulate = Color.WHITE
+ _tween = create_tween()
+ @warning_ignore("return_value_discarded")
+ _tween.set_loops()
+ @warning_ignore("return_value_discarded")
+ _tween.tween_property(%Label, "self_modulate", Color(1, 1, 1, .8), 1.0).from_current().set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN_OUT)
+
+
+func reset() -> void:
+ _input_event = InputEventKey.new()
+
+
+func _input(event: InputEvent) -> void:
+ if not is_visible_in_tree():
+ return
+ if event is InputEventKey and event.is_pressed() and not event.is_echo():
+ var _event := event as InputEventKey
+ match _event.keycode:
+ KEY_CTRL:
+ _input_event.ctrl_pressed = true
+ KEY_SHIFT:
+ _input_event.shift_pressed = true
+ KEY_ALT:
+ _input_event.alt_pressed = true
+ KEY_META:
+ _input_event.meta_pressed = true
+ _:
+ _input_event.keycode = _event.keycode
+ _apply_input_modifiers(_event)
+ accept_event()
+
+ if event is InputEventKey and not event.is_pressed():
+ input_completed.emit(_input_event)
+ hide()
+
+
+func _apply_input_modifiers(event: InputEvent) -> void:
+ if event is InputEventWithModifiers:
+ var _event := event as InputEventWithModifiers
+ _input_event.meta_pressed = _event.meta_pressed or _input_event.meta_pressed
+ _input_event.alt_pressed = _event.alt_pressed or _input_event.alt_pressed
+ _input_event.shift_pressed = _event.shift_pressed or _input_event.shift_pressed
+ _input_event.ctrl_pressed = _event.ctrl_pressed or _input_event.ctrl_pressed
diff --git a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd.uid b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd.uid
new file mode 100644
index 0000000..4b9116e
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd.uid
@@ -0,0 +1 @@
+uid://b6rgevbcd0k5n
diff --git a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn
new file mode 100644
index 0000000..e656bf8
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn
@@ -0,0 +1,36 @@
+[gd_scene format=3 uid="uid://pmnkxrhglak5"]
+
+[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd" id="1_gki1u"]
+
+[node name="GdUnitInputMapper" type="Control"]
+modulate = Color(0.929099, 0.929099, 0.929099, 0.936189)
+top_level = true
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("1_gki1u")
+
+[node name="Label" type="Label" parent="."]
+unique_name_in_owner = true
+self_modulate = Color(0.401913, 0.401913, 0.401913, 0.461723)
+top_level = true
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -60.5
+offset_top = -19.5
+offset_right = 60.5
+offset_bottom = 19.5
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_colors/font_color = Color(1, 1, 1, 1)
+theme_override_font_sizes/font_size = 26
+text = "Press keys for shortcut"
diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd
new file mode 100644
index 0000000..14a8bd5
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd
@@ -0,0 +1,327 @@
+@tool
+extends Window
+
+const EAXAMPLE_URL := "https://github.com/MikeSchulze/gdUnit4-examples/archive/refs/heads/master.zip"
+const GdUnitTools := preload ("res://addons/gdUnit4/src/core/GdUnitTools.gd")
+const GdUnitUpdateClient = preload ("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd")
+
+@onready var _update_client: GdUnitUpdateClient = $GdUnitUpdateClient
+@onready var _version_label: RichTextLabel = %version
+@onready var _btn_install: Button = %btn_install_examples
+@onready var _progress_bar: ProgressBar = %ProgressBar
+@onready var _progress_text: Label = %progress_lbl
+@onready var _properties_template: Control = $property_template
+@onready var _properties_common: Control = % "common-content"
+@onready var _properties_ui: Control = % "ui-content"
+@onready var _properties_shortcuts: Control = % "shortcut-content"
+@onready var _properties_report: Control = % "report-content"
+@onready var _input_capture: GdUnitInputCapture = %GdUnitInputCapture
+@onready var _property_error: Window = % "propertyError"
+@onready var _tab_container: TabContainer = %Properties
+@onready var _update_tab: Control = %Update
+
+var _font_size: float
+
+
+func _ready() -> void:
+ set_name("GdUnitSettingsDialog")
+ # initialize for testing
+ if not Engine.is_editor_hint():
+ GdUnitSettings.setup()
+ GdUnit4Version.init_version_label(_version_label)
+ _font_size = GdUnitFonts.init_fonts(_version_label)
+ setup_properties(_properties_common, GdUnitSettings.COMMON_SETTINGS)
+ setup_properties(_properties_ui, GdUnitSettings.UI_SETTINGS)
+ setup_properties(_properties_report, GdUnitSettings.REPORT_SETTINGS)
+ setup_properties(_properties_shortcuts, GdUnitSettings.SHORTCUT_SETTINGS)
+ check_for_update()
+
+
+func _sort_by_key(left: GdUnitProperty, right: GdUnitProperty) -> bool:
+ return left.name() < right.name()
+
+
+func setup_properties(properties_parent: Control, property_category: String) -> void:
+ # Do remove first potential previous added properties (could be happened when the dlg is opened at twice)
+ for child in properties_parent.get_children():
+ properties_parent.remove_child(child)
+
+ var category_properties := GdUnitSettings.list_settings(property_category)
+ # sort by key
+ category_properties.sort_custom(_sort_by_key)
+ var theme_ := Theme.new()
+ theme_.set_constant("h_separation", "GridContainer", 12)
+ var last_category := "!"
+ var min_size_overall := 0.0
+ var labels := []
+ var inputs := []
+ var info_labels := []
+ var grid: GridContainer = null
+ for p in category_properties:
+ var min_size_ := 0.0
+ var property: GdUnitProperty = p
+ var current_category := property.category()
+ if not grid or current_category != last_category:
+ grid = GridContainer.new()
+ grid.columns = 4
+ grid.theme = theme_
+
+ var sub_category: Control = _properties_template.get_child(3).duplicate()
+ var category_label: Label = sub_category.get_child(0)
+ category_label.text = current_category.capitalize()
+ sub_category.custom_minimum_size.y = _font_size + 16
+ properties_parent.add_child(sub_category)
+ properties_parent.add_child(grid)
+ last_category = current_category
+ # property name
+ var label: Label = _properties_template.get_child(0).duplicate()
+ label.text = _to_human_readable(property.name())
+ labels.append(label)
+ grid.add_child(label)
+
+ # property reset btn
+ var reset_btn: Button = _properties_template.get_child(1).duplicate()
+ reset_btn.icon = _get_btn_icon("Reload")
+ reset_btn.disabled = property.value() == property.default()
+ grid.add_child(reset_btn)
+
+ # property type specific input element
+ var input: Node = _create_input_element(property, reset_btn)
+ inputs.append(input)
+ grid.add_child(input)
+ @warning_ignore("return_value_discarded")
+ reset_btn.pressed.connect(_on_btn_property_reset_pressed.bind(property, input, reset_btn))
+ # property help text
+ var info: Label = _properties_template.get_child(2).duplicate()
+ info.text = property.help()
+ info_labels.append(info)
+ grid.add_child(info)
+ if min_size_overall < min_size_:
+ min_size_overall = min_size_
+
+ for controls: Array in [labels, inputs, info_labels]:
+ var _size: float = controls.map(func(c: Control) -> float: return c.size.x).max()
+ min_size_overall += _size
+ for control: Control in controls:
+ control.custom_minimum_size.x = _size
+ properties_parent.custom_minimum_size.x = min_size_overall
+
+
+func _create_input_element(property: GdUnitProperty, reset_btn: Button) -> Node:
+ if property.is_selectable_value():
+ var options := OptionButton.new()
+ options.alignment = HORIZONTAL_ALIGNMENT_CENTER
+ for value in property.value_set():
+ options.add_item(value)
+ options.item_selected.connect(_on_option_selected.bind(property, reset_btn))
+ options.select(property.int_value())
+ return options
+ if property.type() == TYPE_BOOL:
+ var check_btn := CheckButton.new()
+ check_btn.toggled.connect(_on_property_text_changed.bind(property, reset_btn))
+ check_btn.button_pressed = property.value()
+ return check_btn
+ if property.type() in [TYPE_INT, TYPE_STRING]:
+ var input := LineEdit.new()
+ input.text_changed.connect(_on_property_text_changed.bind(property, reset_btn))
+ input.set_context_menu_enabled(false)
+ input.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER)
+ input.set_expand_to_text_length_enabled(true)
+ input.text = str(property.value())
+ return input
+ if property.type() == TYPE_PACKED_INT32_ARRAY:
+ var key_input_button := Button.new()
+ var value:PackedInt32Array = property.value()
+ key_input_button.text = to_shortcut(value)
+ key_input_button.pressed.connect(_on_shortcut_change.bind(key_input_button, property, reset_btn))
+ return key_input_button
+ return Control.new()
+
+
+func to_shortcut(keys: PackedInt32Array) -> String:
+ var input_event := InputEventKey.new()
+ for key in keys:
+ match key:
+ KEY_CTRL: input_event.ctrl_pressed = true
+ KEY_SHIFT: input_event.shift_pressed = true
+ KEY_ALT: input_event.alt_pressed = true
+ KEY_META: input_event.meta_pressed = true
+ _:
+ input_event.keycode = key as Key
+ return input_event.as_text()
+
+
+func to_keys(input_event: InputEventKey) -> PackedInt32Array:
+ var keys := PackedInt32Array()
+ if input_event.ctrl_pressed:
+ keys.append(KEY_CTRL)
+ if input_event.shift_pressed:
+ keys.append(KEY_SHIFT)
+ if input_event.alt_pressed:
+ keys.append(KEY_ALT)
+ if input_event.meta_pressed:
+ keys.append(KEY_META)
+ keys.append(input_event.keycode)
+ return keys
+
+
+func _to_human_readable(value: String) -> String:
+ return value.split("/")[-1].capitalize()
+
+
+func _get_btn_icon(p_name: String) -> Texture2D:
+ if not Engine.is_editor_hint():
+ var placeholder := PlaceholderTexture2D.new()
+ placeholder.size = Vector2(8, 8)
+ return placeholder
+ return GdUnitUiTools.get_icon(p_name)
+
+
+func _install_examples() -> void:
+ _init_progress(5)
+ update_progress("Downloading examples")
+ await get_tree().process_frame
+ var tmp_path := GdUnitFileAccess.create_temp_dir("download")
+ var zip_file := tmp_path + "/examples.zip"
+ var response: GdUnitUpdateClient.HttpResponse = await _update_client.request_zip_package(EAXAMPLE_URL, zip_file)
+ if response.status() != 200:
+ push_warning("Examples cannot be retrieved from GitHub! \n Error code: %d : %s" % [response.status(), response.response()])
+ update_progress("Install examples failed! Try it later again.")
+ await get_tree().create_timer(3).timeout
+ stop_progress()
+ return
+ # extract zip to tmp
+ update_progress("Install examples into project")
+ var result := GdUnitFileAccess.extract_zip(zip_file, "res://gdUnit4-examples/")
+ if result.is_error():
+ update_progress("Install examples failed! %s" % result.error_message())
+ await get_tree().create_timer(3).timeout
+ stop_progress()
+ return
+ update_progress("Refresh project")
+ await rescan()
+ await reimport("res://gdUnit4-examples/")
+
+ update_progress("Examples successfully installed")
+ await get_tree().create_timer(3).timeout
+ stop_progress()
+
+
+func rescan() -> void:
+ await get_tree().process_frame
+ var fs := EditorInterface.get_resource_filesystem()
+ fs.scan_sources()
+ while fs.is_scanning():
+ await get_tree().create_timer(1).timeout
+
+
+func reimport(path: String) -> void:
+ await get_tree().process_frame
+ var files := DirAccess.get_files_at(path)
+ EditorInterface.get_resource_filesystem().reimport_files(files)
+ for directory in DirAccess.get_directories_at(path):
+ reimport(directory)
+
+
+func check_for_update() -> void:
+ if not GdUnitSettings.is_update_notification_enabled():
+ return
+ var response :GdUnitUpdateClient.HttpResponse = await _update_client.request_latest_version()
+ if response.status() != 200:
+ printerr("Latest version information cannot be retrieved from GitHub!")
+ printerr("Error: %s" % response.response())
+ return
+ var latest_version := _update_client.extract_latest_version(response)
+ if latest_version.is_greater(GdUnit4Version.current()):
+ var tab_index := _tab_container.get_tab_idx_from_control(_update_tab)
+ _tab_container.set_tab_button_icon(tab_index, GdUnitUiTools.get_icon("Notification", Color.YELLOW))
+ _tab_container.set_tab_tooltip(tab_index, "An new update is available.")
+
+
+func _on_btn_report_bug_pressed() -> void:
+ @warning_ignore("return_value_discarded")
+ OS.shell_open("https://github.com/MikeSchulze/gdUnit4/issues/new?assignees=MikeSchulze&labels=bug&projects=projects%2F5&template=bug_report.yml&title=GD-XXX%3A+Describe+the+issue+briefly")
+
+
+func _on_btn_request_feature_pressed() -> void:
+ @warning_ignore("return_value_discarded")
+ OS.shell_open("https://github.com/MikeSchulze/gdUnit4/issues/new?assignees=MikeSchulze&labels=enhancement&projects=&template=feature_request.md&title=")
+
+
+func _on_btn_install_examples_pressed() -> void:
+ _btn_install.disabled = true
+ await _install_examples()
+ _btn_install.disabled = false
+
+
+func _on_btn_close_pressed() -> void:
+ hide()
+
+
+func _on_btn_property_reset_pressed(property: GdUnitProperty, input: Node, reset_btn: Button) -> void:
+ if input is CheckButton:
+ var is_default_pressed: bool = property.default()
+ (input as CheckButton).button_pressed = is_default_pressed
+ elif input is LineEdit:
+ (input as LineEdit).text = str(property.default())
+ # we have to update manually for text input fields because of no change event is emited
+ _on_property_text_changed(property.default(), property, reset_btn)
+ elif input is OptionButton:
+ (input as OptionButton).select(0)
+ _on_option_selected(0, property, reset_btn)
+ elif input is Button:
+ var value: PackedInt32Array = property.default()
+ (input as Button).text = to_shortcut(value)
+ _on_property_text_changed(value, property, reset_btn)
+
+
+func _on_property_text_changed(new_value: Variant, property: GdUnitProperty, reset_btn: Button) -> void:
+ property.set_value(new_value)
+ reset_btn.disabled = property.value() == property.default()
+ var error: Variant = GdUnitSettings.update_property(property)
+ if error:
+ var label: Label = _property_error.get_child(0) as Label
+ label.set_text(str(error))
+ var control := gui_get_focus_owner()
+ _property_error.show()
+ if control != null:
+ _property_error.position = control.global_position + Vector2(self.position) + Vector2(40, 40)
+
+
+func _on_option_selected(index: int, property: GdUnitProperty, reset_btn: Button) -> void:
+ property.set_value(index)
+ reset_btn.disabled = property.value() == property.default()
+ GdUnitSettings.update_property(property)
+
+
+func _on_shortcut_change(input_button: Button, property: GdUnitProperty, reset_btn: Button) -> void:
+ _input_capture.set_custom_minimum_size(_properties_shortcuts.get_size())
+ _input_capture.visible = true
+ _input_capture.show()
+ _properties_shortcuts.visible = false
+ set_process_input(false)
+ _input_capture.reset()
+ var input_event: InputEventKey = await _input_capture.input_completed
+ input_button.text = input_event.as_text()
+ _on_property_text_changed(to_keys(input_event), property, reset_btn)
+ _properties_shortcuts.visible = true
+ set_process_input(true)
+
+
+func _init_progress(max_value: int) -> void:
+ _progress_bar.visible = true
+ _progress_bar.max_value = max_value
+ _progress_bar.value = 0
+
+
+func _progress() -> void:
+ _progress_bar.value += 1
+
+
+func stop_progress() -> void:
+ _progress_bar.visible = false
+
+
+func update_progress(message: String) -> void:
+ _progress_text.text = message
+ _progress_bar.value += 1
diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd.uid b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd.uid
new file mode 100644
index 0000000..d92d2e4
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd.uid
@@ -0,0 +1 @@
+uid://eqhu8casglok
diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn
new file mode 100644
index 0000000..08afee4
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn
@@ -0,0 +1,349 @@
+[gd_scene load_steps=9 format=3]
+
+[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd" id="2"]
+[ext_resource type="Texture2D" path="res://addons/gdUnit4/src/ui/settings/logo.png" id="3_isfyl"]
+[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn" id="4"]
+[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.tscn" id="4_nf72w"]
+[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn" id="5_n1jtv"]
+[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn" id="5_xu3j8"]
+[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd" id="8_2ggr0"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hbbq5"]
+content_margin_left = 10.0
+content_margin_right = 10.0
+bg_color = Color(0.172549, 0.113725, 0.141176, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+border_color = Color(0.87451, 0.0705882, 0.160784, 1)
+border_blend = true
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+shadow_color = Color(0, 0, 0, 0.756863)
+shadow_size = 10
+shadow_offset = Vector2(10, 10)
+
+[node name="GdUnitSettingsDialog" type="Window"]
+disable_3d = true
+gui_embed_subwindows = true
+title = "GdUnit4 Settings"
+initial_position = 1
+size = Vector2i(1400, 600)
+visible = false
+wrap_controls = true
+exclusive = true
+extend_to_title = true
+script = ExtResource("2")
+
+[node name="property_template" type="Control" parent="."]
+visible = false
+layout_mode = 3
+anchors_preset = 0
+offset_left = 4.0
+offset_top = 4.0
+offset_right = 4.0
+offset_bottom = 4.0
+size_flags_horizontal = 0
+
+[node name="Label" type="Label" parent="property_template"]
+layout_mode = 1
+anchors_preset = 4
+anchor_top = 0.5
+anchor_bottom = 0.5
+offset_top = -11.5
+offset_right = 1.0
+offset_bottom = 11.5
+grow_vertical = 2
+
+[node name="btn_reset" type="Button" parent="property_template"]
+layout_mode = 0
+offset_right = 12.0
+offset_bottom = 40.0
+tooltip_text = "Reset to default value"
+clip_text = true
+
+[node name="info" type="Label" parent="property_template"]
+clip_contents = true
+layout_mode = 1
+anchors_preset = 4
+anchor_top = 0.5
+anchor_bottom = 0.5
+offset_top = -11.5
+offset_right = 316.0
+offset_bottom = 11.5
+grow_vertical = 2
+size_flags_horizontal = 3
+max_lines_visible = 1
+
+[node name="sub_category" type="Panel" parent="property_template"]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+offset_right = -220.0
+grow_horizontal = 2
+size_flags_horizontal = 3
+
+[node name="Label" type="Label" parent="property_template/sub_category"]
+layout_mode = 1
+anchors_preset = 4
+anchor_top = 0.5
+anchor_bottom = 0.5
+offset_left = 4.0
+offset_top = -11.5
+offset_right = 5.0
+offset_bottom = 11.5
+grow_vertical = 2
+theme_override_colors/font_color = Color(0.439216, 0.45098, 1, 1)
+
+[node name="GdUnitUpdateClient" type="Node" parent="."]
+script = ExtResource("8_2ggr0")
+
+[node name="Panel" type="Panel" parent="."]
+use_parent_material = true
+clip_contents = true
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="PanelContainer" type="PanelContainer" parent="Panel"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="v" type="VBoxContainer" parent="Panel/PanelContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="MarginContainer" type="MarginContainer" parent="Panel/PanelContainer/v"]
+use_parent_material = true
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/margin_left = 4
+theme_override_constants/margin_top = 4
+theme_override_constants/margin_right = 4
+
+[node name="GridContainer" type="HBoxContainer" parent="Panel/PanelContainer/v/MarginContainer"]
+use_parent_material = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="PanelContainer" type="MarginContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer"]
+use_parent_material = true
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="Panel" type="VBoxContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer"]
+use_parent_material = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="CenterContainer" type="CenterContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/Panel"]
+use_parent_material = true
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="logo" type="TextureRect" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/Panel/CenterContainer"]
+custom_minimum_size = Vector2(120, 120)
+layout_mode = 2
+size_flags_horizontal = 0
+size_flags_vertical = 4
+texture = ExtResource("3_isfyl")
+expand_mode = 1
+stretch_mode = 5
+
+[node name="CenterContainer2" type="MarginContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/Panel"]
+use_parent_material = true
+custom_minimum_size = Vector2(0, 30)
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="version" type="RichTextLabel" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/Panel/CenterContainer2"]
+unique_name_in_owner = true
+auto_translate_mode = 2
+use_parent_material = true
+clip_contents = false
+layout_mode = 2
+size_flags_horizontal = 3
+localize_numeral_system = false
+bbcode_enabled = true
+scroll_active = false
+meta_underlined = false
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer"]
+custom_minimum_size = Vector2(200, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+alignment = 2
+
+[node name="btn_report_bug" type="Button" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+tooltip_text = "Press to create a bug report"
+text = "Report Bug"
+
+[node name="btn_request_feature" type="Button" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+tooltip_text = "Press to create a feature request"
+text = "Request Feature"
+
+[node name="btn_install_examples" type="Button" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+tooltip_text = "Press to install the advanced test examples"
+disabled = true
+text = "Install Examples"
+
+[node name="Properties" type="TabContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+current_tab = 0
+
+[node name="Common" type="ScrollContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties"]
+layout_mode = 2
+metadata/_tab_index = 0
+
+[node name="common-content" type="VBoxContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties/Common"]
+unique_name_in_owner = true
+clip_contents = true
+custom_minimum_size = Vector2(1026, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Hooks" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties" instance=ExtResource("4_nf72w")]
+visible = false
+layout_mode = 2
+
+[node name="UI" type="ScrollContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties"]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 2
+
+[node name="ui-content" type="VBoxContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties/UI"]
+unique_name_in_owner = true
+clip_contents = true
+custom_minimum_size = Vector2(741, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Shortcuts" type="ScrollContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties"]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 3
+
+[node name="shortcut-content" type="VBoxContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties/Shortcuts"]
+unique_name_in_owner = true
+clip_contents = true
+custom_minimum_size = Vector2(683, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Report" type="ScrollContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties"]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 4
+
+[node name="report-content" type="VBoxContainer" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties/Report"]
+unique_name_in_owner = true
+clip_contents = true
+custom_minimum_size = Vector2(667, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Templates" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties" instance=ExtResource("4")]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 5
+
+[node name="Update" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties" instance=ExtResource("5_n1jtv")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+metadata/_tab_index = 6
+
+[node name="GdUnitInputCapture" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties" instance=ExtResource("5_xu3j8")]
+unique_name_in_owner = true
+visible = false
+modulate = Color(1.54884e-09, 1.54884e-09, 1.54884e-09, 0.1)
+z_index = 1
+z_as_relative = false
+layout_mode = 2
+size_flags_horizontal = 1
+size_flags_vertical = 1
+
+[node name="propertyError" type="PopupPanel" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties"]
+unique_name_in_owner = true
+initial_position = 1
+size = Vector2i(400, 100)
+theme_override_styles/panel = SubResource("StyleBoxFlat_hbbq5")
+
+[node name="Label" type="Label" parent="Panel/PanelContainer/v/MarginContainer/GridContainer/Properties/propertyError"]
+offset_left = 10.0
+offset_top = 4.0
+offset_right = 390.0
+offset_bottom = 96.0
+theme_override_colors/font_color = Color(0.858824, 0, 0.109804, 1)
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="MarginContainer2" type="MarginContainer" parent="Panel/PanelContainer/v"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_constants/margin_left = 4
+theme_override_constants/margin_right = 4
+theme_override_constants/margin_bottom = 4
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Panel/PanelContainer/v/MarginContainer2"]
+layout_mode = 2
+size_flags_horizontal = 3
+alignment = 2
+
+[node name="ProgressBar" type="ProgressBar" parent="Panel/PanelContainer/v/MarginContainer2/HBoxContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="progress_lbl" type="Label" parent="Panel/PanelContainer/v/MarginContainer2/HBoxContainer/ProgressBar"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+clip_text = true
+
+[node name="btn_close" type="Button" parent="Panel/PanelContainer/v/MarginContainer2/HBoxContainer"]
+custom_minimum_size = Vector2(200, 0)
+layout_mode = 2
+text = "Close"
+
+[connection signal="close_requested" from="." to="." method="_on_btn_close_pressed"]
+[connection signal="pressed" from="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer/btn_report_bug" to="." method="_on_btn_report_bug_pressed"]
+[connection signal="pressed" from="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer/btn_request_feature" to="." method="_on_btn_request_feature_pressed"]
+[connection signal="pressed" from="Panel/PanelContainer/v/MarginContainer/GridContainer/PanelContainer/VBoxContainer/btn_install_examples" to="." method="_on_btn_install_examples_pressed"]
+[connection signal="pressed" from="Panel/PanelContainer/v/MarginContainer2/HBoxContainer/btn_close" to="." method="_on_btn_close_pressed"]
diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.gd b/addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.gd
new file mode 100644
index 0000000..3b01f34
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.gd
@@ -0,0 +1,255 @@
+@tool
+extends ScrollContainer
+
+
+@onready var _hooks_tree: Tree = %hooks_tree
+@onready var _hook_description: RichTextLabel = %hook_description
+@onready var _btn_move_up: Button = %hook_actions/btn_move_up
+@onready var _btn_move_down: Button = %hook_actions/btn_move_down
+@onready var _btn_delete: Button = %hook_actions/btn_delete_hook
+@onready var _select_hook_dlg: FileDialog = %select_hook_dlg
+@onready var _error_msg_popup :AcceptDialog = %error_msg_popup
+
+var _selected_hook_item: TreeItem = null
+var _root: TreeItem
+var _system_box_style: StyleBoxFlat
+var _priority_box_style: StyleBoxFlat
+
+func _ready() -> void:
+ _setup_styles()
+ _setup_buttons()
+ _setup_tree()
+ _load_registered_hooks()
+
+
+func _setup_styles() -> void:
+ _system_box_style = StyleBoxFlat.new()
+ _system_box_style.bg_color = Color(1.0, 0.76, 0.03, 1)
+ _system_box_style.corner_radius_top_left = 6
+ _system_box_style.corner_radius_top_right = 6
+ _system_box_style.corner_radius_bottom_left = 6
+ _system_box_style.corner_radius_bottom_right = 6
+ _priority_box_style = _system_box_style.duplicate()
+ _priority_box_style.bg_color = Color(0.26, 0.54, 0.89, 1)
+
+
+func _setup_buttons() -> void:
+ #if Engine.is_editor_hint():
+ # _btn_move_up.icon = GdUnitUiTools.get_icon("MoveUp")
+ # _btn_move_down.icon = GdUnitUiTools.get_icon("MoveDown")
+ # _btn_add.icon = GdUnitUiTools.get_icon("Add")
+ # _btn_delete.icon = GdUnitUiTools.get_icon("Remove")
+ pass
+
+
+func _setup_tree() -> void:
+ _hooks_tree.clear()
+ _root = _hooks_tree.create_item()
+ _hooks_tree.set_columns(2)
+ _hooks_tree.set_column_custom_minimum_width(1, 32)
+ _hooks_tree.set_column_expand(1, false)
+ _hooks_tree.set_hide_root(true)
+ _hooks_tree.set_hide_folding(true)
+ _hooks_tree.set_select_mode(Tree.SELECT_SINGLE)
+ _hooks_tree.item_selected.connect(_on_hook_selected)
+ _hooks_tree.item_edited.connect(_on_item_edited)
+
+
+func _load_registered_hooks() -> void:
+ var hook_service := GdUnitTestSessionHookService.instance()
+ for hook: GdUnitTestSessionHook in hook_service.enigne_hooks:
+ _create_hook_tree_item(hook)
+
+ # Select first item if any
+ if _root.get_child_count() > 0:
+ var first_item: TreeItem = _root.get_first_child()
+ first_item.select(0)
+ _on_hook_selected()
+
+
+func _create_hook_tree_item(hook: GdUnitTestSessionHook) -> TreeItem:
+ var item: TreeItem = _hooks_tree.create_item(_root)
+ item.set_custom_minimum_height(26)
+ # Column 0: Hook info with custom drawing
+ item.set_cell_mode(0, TreeItem.CELL_MODE_CUSTOM)
+ item.set_custom_draw_callback(0, _draw_hook_item)
+ item.set_editable(0, false)
+ item.set_metadata(0, hook)
+ # Column 1: Checkbox for enable/disable
+ item.set_cell_mode(1, TreeItem.CELL_MODE_CHECK)
+ item.set_checked(1, GdUnitTestSessionHookService.is_enabled(hook))
+ item.set_editable(1, true)
+ item.set_custom_bg_color(1, _hook_bg_color(hook))
+ item.set_tooltip_text(1, "Enable/Disable the Hook")
+ item.propagate_check(1)
+
+ if _is_system_hook(hook):
+ item.set_tooltip_text(0, "System hook - (Read-only)")
+ else:
+ item.set_tooltip_text(0, "User hook")
+ return item
+
+
+func _hook_bg_color(hook: GdUnitTestSessionHook) -> Color:
+ if _is_system_hook(hook):
+ return Color(0.133, 0.118, 0.090, 1) # Brownish background for system hooks
+ return Color(0.176, 0.196, 0.235, 1) # Dark background #2d3142
+
+
+func _draw_hook_item(item: TreeItem, rect: Rect2) -> void:
+ var hook := _get_hook(item)
+ var is_system := _is_system_hook(hook)
+ var is_selected := item == _selected_hook_item
+
+ # Draw background
+ var bg_color := _hook_bg_color(hook) # Dark background #2d3142
+ if is_selected:
+ bg_color = bg_color.lerp(Color(0.2, 0.4, 0.6, 0.3), 0.5) # Blue tint for selection
+ _hooks_tree.draw_rect(rect, bg_color)
+
+ # Draw left border for system hooks
+ if is_system:
+ var border_rect := Rect2(rect.position.x, rect.position.y, 3, rect.size.y)
+ _hooks_tree.draw_rect(border_rect, Color(1.0, 0.76, 0.03, 1)) # Yellow border
+
+ var font := _hooks_tree.get_theme_default_font()
+
+ # Draw hook name
+ var hook_name := hook.name
+ var text_pos := Vector2(rect.position.x + ( 15 if is_system else 12), rect.position.y + 18)
+ var text_color := Color(0.95, 0.95, 0.95, 1)
+ _hooks_tree.draw_string(font, text_pos, hook_name, HORIZONTAL_ALIGNMENT_LEFT, -1, 14, text_color)
+
+ # Draw system badge if needed
+ if is_system:
+ var badge_x := rect.position.x + rect.end.x - 100
+ var badge_y := rect.position.y + 14
+ var system_badge_rect := Rect2(badge_x, badge_y-8, 48, 16)
+ _hooks_tree.draw_style_box(_system_box_style, system_badge_rect)
+
+ var system_text_pos := Vector2(badge_x + 4, badge_y + 4)
+ var system_font_size := 10
+ _hooks_tree.draw_string(font, system_text_pos, "SYSTEM", HORIZONTAL_ALIGNMENT_CENTER, -1, system_font_size, Color(0.1, 0.1, 0.1, 1))
+
+
+func _create_hook_display_text(hook_name: String, priority: int, is_system: bool) -> String:
+ var text := hook_name + "\n"
+ text += "Priority: [color=#4299e1][bgcolor=#4299e1] " + str(priority) + " [/bgcolor][/color]"
+
+ if is_system:
+ text += " [color=#1a202c][bgcolor=#ffc107] SYSTEM [/bgcolor][/color]"
+
+ return text
+
+
+func _update_hook_description() -> void:
+ if _selected_hook_item == null:
+ _hook_description.text = "[i]Select a hook to view its description[/i]"
+ return
+ _hook_description.text = _get_hook(_selected_hook_item).description
+
+
+func _update_hook_buttons() -> void:
+ # Is nothing selected disable the move and delete buttons
+ if _selected_hook_item == null:
+ _btn_move_up.disabled = true
+ _btn_move_down.disabled = true
+ _btn_delete.disabled = true
+ return
+
+ var hook := _get_hook(_selected_hook_item)
+ var is_system := _is_system_hook(hook)
+
+ # Disable the move and delete buttons for system hooks by default
+ if is_system:
+ _btn_move_up.disabled = true
+ _btn_move_down.disabled = true
+ _btn_delete.disabled = true
+ return
+
+ var prev_item: TreeItem = _selected_hook_item.get_prev()
+ var next_item: TreeItem = _selected_hook_item.get_next()
+
+ if prev_item != null:
+ var prev_hook := _get_hook(prev_item)
+ _btn_move_up.disabled = _is_system_hook(prev_hook)
+
+ _btn_move_down.disabled = next_item == null
+ _btn_delete.disabled = false
+
+
+static func _get_hook(item: TreeItem) -> GdUnitTestSessionHook:
+ return item.get_metadata(0)
+
+
+static func _is_system_hook(hook: GdUnitTestSessionHook) -> bool:
+ if hook == null:
+ return false
+ return hook.get_meta("SYSTEM_HOOK")
+
+
+func _on_hook_selected() -> void:
+ _selected_hook_item = _hooks_tree.get_selected()
+ _update_hook_buttons()
+ _update_hook_description()
+
+
+func _on_item_edited() -> void:
+ var selected_hook_item := _hooks_tree.get_selected()
+ if selected_hook_item != null:
+ var hook := _get_hook(selected_hook_item)
+ var is_enabled := selected_hook_item.is_checked(1)
+ GdUnitTestSessionHookService.instance().enable_hook(hook, is_enabled)
+
+
+func _on_btn_add_hook_pressed() -> void:
+ _select_hook_dlg.show()
+
+
+func _on_select_hook_dlg_file_selected(path: String) -> void:
+ _select_hook_dlg.set_current_path(path)
+ _on_select_hook_dlg_confirmed()
+
+
+func _on_select_hook_dlg_confirmed() -> void:
+ _select_hook_dlg.hide()
+ var result := GdUnitTestSessionHookService.instance().load_hook(_select_hook_dlg.get_current_path())
+ if result.is_error():
+ _error_msg_popup.dialog_text = result.error_message()
+ _error_msg_popup.show()
+ return
+
+ var hook: GdUnitTestSessionHook = result.value()
+ result = GdUnitTestSessionHookService.instance().register(hook)
+ if result.is_error():
+ _error_msg_popup.dialog_text = result.error_message()
+ _error_msg_popup.show()
+ return
+
+ var hook_added := _create_hook_tree_item(hook)
+ _hooks_tree.set_selected(hook_added, 0)
+
+
+func _on_btn_delete_hook_pressed() -> void:
+ if _selected_hook_item != null:
+ _root.remove_child(_selected_hook_item)
+ GdUnitTestSessionHookService.instance()\
+ .unregister(_get_hook(_selected_hook_item))
+ _selected_hook_item = null
+ _update_hook_buttons()
+
+
+func _on_btn_move_up_pressed() -> void:
+ var prev := _selected_hook_item.get_prev()
+ _selected_hook_item.move_before(prev)
+ GdUnitTestSessionHookService.instance()\
+ .move_before(_get_hook(_selected_hook_item), _get_hook(prev))
+ _update_hook_buttons()
+
+
+func _on_btn_move_down_pressed() -> void:
+ var next := _selected_hook_item.get_next()
+ _selected_hook_item.move_after(next)
+ GdUnitTestSessionHookService.instance()\
+ .move_after(_get_hook(_selected_hook_item), _get_hook(next))
+ _update_hook_buttons()
diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.gd.uid b/addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.gd.uid
new file mode 100644
index 0000000..cbb5b86
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.gd.uid
@@ -0,0 +1 @@
+uid://cmclisg3m8k2n
diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.tscn b/addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.tscn
new file mode 100644
index 0000000..8eec309
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.tscn
@@ -0,0 +1,148 @@
+[gd_scene format=3 uid="uid://41l7a46fol5m"]
+
+[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/settings/GdUnitSettingsTabHooks.gd" id="1_8yffn"]
+
+[sub_resource type="Image" id="Image_h5sr5"]
+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, 234, 234, 234, 12, 224, 224, 224, 255, 224, 224, 224, 255, 234, 234, 234, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 1, 225, 225, 225, 174, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 173, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 123, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 74, 224, 224, 224, 253, 224, 224, 224, 253, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 253, 224, 224, 224, 253, 224, 224, 224, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 228, 228, 228, 37, 224, 224, 224, 240, 224, 224, 224, 255, 224, 224, 224, 122, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 123, 224, 224, 224, 255, 224, 224, 224, 239, 228, 228, 228, 37, 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, 200, 224, 224, 224, 255, 224, 224, 224, 172, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 1, 224, 224, 224, 173, 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, 224, 224, 224, 180, 224, 224, 224, 193, 234, 234, 234, 12, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 234, 234, 234, 12, 224, 224, 224, 193, 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, 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, 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, 224, 224, 224, 180, 224, 224, 224, 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 181, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 180, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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_77fm0"]
+image = SubResource("Image_h5sr5")
+
+[sub_resource type="Image" id="Image_77fm0"]
+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, 224, 224, 224, 181, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 180, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 181, 224, 224, 224, 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 181, 224, 224, 224, 193, 234, 234, 234, 12, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 234, 234, 234, 12, 224, 224, 224, 193, 224, 224, 224, 180, 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, 200, 224, 224, 224, 255, 224, 224, 224, 173, 255, 255, 255, 1, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 1, 225, 225, 225, 174, 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, 228, 228, 228, 37, 224, 224, 224, 239, 224, 224, 224, 255, 224, 224, 224, 122, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 123, 224, 224, 224, 255, 224, 224, 224, 239, 227, 227, 227, 36, 0, 0, 0, 0, 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, 74, 224, 224, 224, 253, 224, 224, 224, 253, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 253, 224, 224, 224, 253, 224, 224, 224, 73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 123, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 1, 224, 224, 224, 173, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 234, 234, 234, 12, 224, 224, 224, 255, 224, 224, 224, 255, 234, 234, 234, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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_rewru"]
+image = SubResource("Image_77fm0")
+
+[sub_resource type="Image" id="Image_kppp6"]
+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, 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, 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, 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, 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, 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, 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, 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, 224, 224, 224, 255, 224, 224, 224, 255, 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, 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, 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, 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, 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, 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, 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, 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, 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_manhx"]
+image = SubResource("Image_kppp6")
+
+[sub_resource type="Image" id="Image_rewru"]
+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, 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, 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, 224, 224, 224, 255, 224, 224, 224, 255, 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, 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, 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, 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, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 227, 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, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 73, 224, 224, 224, 226, 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, 225, 226, 226, 226, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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_4h4u1"]
+image = SubResource("Image_rewru")
+
+[node name="Hooks" type="ScrollContainer"]
+custom_minimum_size = Vector2(400, 300)
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("1_8yffn")
+metadata/_tab_index = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="hooks_content" type="VBoxContainer" parent="HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="hooks_tree" type="Tree" parent="HBoxContainer/hooks_content"]
+unique_name_in_owner = true
+layout_direction = 2
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+hide_folding = true
+hide_root = true
+
+[node name="hook_description" type="RichTextLabel" parent="HBoxContainer/hooks_content"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 120)
+layout_mode = 2
+size_flags_vertical = 2
+bbcode_enabled = true
+text = "The test result Html reporting hook."
+scroll_active = false
+
+[node name="hook_actions" type="VBoxContainer" parent="HBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(80, 0)
+layout_mode = 2
+size_flags_horizontal = 0
+theme_override_constants/separation = 5
+
+[node name="btn_move_up" type="Button" parent="HBoxContainer/hook_actions"]
+layout_mode = 2
+tooltip_text = "Move hook up in priority"
+disabled = true
+icon = SubResource("ImageTexture_77fm0")
+icon_alignment = 1
+
+[node name="btn_move_down" type="Button" parent="HBoxContainer/hook_actions"]
+layout_mode = 2
+tooltip_text = "Move hook down in priority"
+disabled = true
+icon = SubResource("ImageTexture_rewru")
+icon_alignment = 1
+
+[node name="btn_add_hook" type="Button" parent="HBoxContainer/hook_actions"]
+layout_mode = 2
+tooltip_text = "Add new hook"
+icon = SubResource("ImageTexture_manhx")
+icon_alignment = 1
+
+[node name="btn_delete_hook" type="Button" parent="HBoxContainer/hook_actions"]
+layout_mode = 2
+tooltip_text = "Delete selected hook"
+disabled = true
+icon = SubResource("ImageTexture_4h4u1")
+icon_alignment = 1
+
+[node name="select_hook_dlg" type="FileDialog" parent="."]
+unique_name_in_owner = true
+disable_3d = true
+title = "Open a File"
+initial_position = 3
+current_screen = 0
+ok_button_text = "Open"
+file_mode = 0
+filters = PackedStringArray("*.gd")
+
+[node name="error_msg_popup" type="AcceptDialog" parent="."]
+unique_name_in_owner = true
+initial_position = 3
+current_screen = 0
+
+[connection signal="pressed" from="HBoxContainer/hook_actions/btn_move_up" to="." method="_on_btn_move_up_pressed"]
+[connection signal="pressed" from="HBoxContainer/hook_actions/btn_move_down" to="." method="_on_btn_move_down_pressed"]
+[connection signal="pressed" from="HBoxContainer/hook_actions/btn_add_hook" to="." method="_on_btn_add_hook_pressed"]
+[connection signal="pressed" from="HBoxContainer/hook_actions/btn_delete_hook" to="." method="_on_btn_delete_hook_pressed"]
+[connection signal="confirmed" from="select_hook_dlg" to="." method="_on_select_hook_dlg_confirmed"]
+[connection signal="file_selected" from="select_hook_dlg" to="." method="_on_select_hook_dlg_file_selected"]
diff --git a/addons/gdUnit4/src/ui/settings/logo.png b/addons/gdUnit4/src/ui/settings/logo.png
new file mode 100644
index 0000000..12de79f
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/logo.png
Binary files differ
diff --git a/addons/gdUnit4/src/ui/settings/logo.png.import b/addons/gdUnit4/src/ui/settings/logo.png.import
new file mode 100644
index 0000000..0141c5e
--- /dev/null
+++ b/addons/gdUnit4/src/ui/settings/logo.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bo6npdrbk02dk"
+path="res://.godot/imported/logo.png-deda0e4ba02a0b9e4e4a830029a5817f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/gdUnit4/src/ui/settings/logo.png"
+dest_files=["res://.godot/imported/logo.png-deda0e4ba02a0b9e4e4a830029a5817f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1