diff options
| author | radiohotline <radoihotline@disroot.rg> | 2026-08-25 16:33:56 +0300 |
|---|---|---|
| committer | radiohotline <radoihotline@disroot.rg> | 2026-08-25 16:33:56 +0300 |
| commit | a89e744ed0e8c759caf041a580112365bbfe4303 (patch) | |
| tree | dd61e320904176995727e86a857d00c90d4933ba /addons/gdUnit4/src/ui | |
| parent | 6d53d8cac78250dc888aa49d5f9d4175c6652feb (diff) | |
Diffstat (limited to 'addons/gdUnit4/src/ui')
52 files changed, 4979 insertions, 0 deletions
diff --git a/addons/gdUnit4/src/ui/GdUnitConsole.gd b/addons/gdUnit4/src/ui/GdUnitConsole.gd new file mode 100644 index 0000000..eb1e625 --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitConsole.gd @@ -0,0 +1,91 @@ +@tool +extends Control + +const GdUnitUpdateClient = preload("res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd") +const TITLE = "gdUnit4 ${version} Console" + +@onready var header := $VBoxContainer/Header +@onready var title: RichTextLabel = $VBoxContainer/Header/header_title +@onready var output: RichTextLabel = $VBoxContainer/Console/TextEdit + + +var _test_reporter: GdUnitConsoleTestReporter + + +@warning_ignore("return_value_discarded") +func _ready() -> void: + GdUnitFonts.init_fonts(output) + GdUnit4Version.init_version_label(title) + GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) + GdUnitSignals.instance().gdunit_message.connect(_on_gdunit_message) + GdUnitSignals.instance().gdunit_client_connected.connect(_on_gdunit_client_connected) + GdUnitSignals.instance().gdunit_client_disconnected.connect(_on_gdunit_client_disconnected) + _test_reporter = GdUnitConsoleTestReporter.new(GdUnitRichTextMessageWriter.new(output)) + + +func _notification(what: int) -> void: + if what == EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED: + _test_reporter.init_colors() + if what == NOTIFICATION_PREDELETE: + var instance := GdUnitSignals.instance() + if instance.gdunit_event.is_connected(_on_gdunit_event): + instance.gdunit_event.disconnect(_on_gdunit_event) + if instance.gdunit_message.is_connected(_on_gdunit_event): + instance.gdunit_message.disconnect(_on_gdunit_message) + if instance.gdunit_client_connected.is_connected(_on_gdunit_event): + instance.gdunit_client_connected.disconnect(_on_gdunit_client_connected) + if instance.gdunit_client_disconnected.is_connected(_on_gdunit_event): + instance.gdunit_client_disconnected.disconnect(_on_gdunit_client_disconnected) + + +func setup_update_notification(control: Button) -> void: + if not GdUnitSettings.is_update_notification_enabled(): + _test_reporter.println_message("The search for updates is deactivated.", Color.CORNFLOWER_BLUE) + return + + _test_reporter.print_message("Searching for updates... ", Color.CORNFLOWER_BLUE) + var update_client := GdUnitUpdateClient.new() + add_child(update_client) + var response :GdUnitUpdateClient.HttpResponse = await update_client.request_latest_version() + if response.status() != 200: + _test_reporter.println_message("Information cannot be retrieved from GitHub!", Color.INDIAN_RED) + _test_reporter.println_message("Error: %s" % response.response(), Color.INDIAN_RED) + return + var latest_version := update_client.extract_latest_version(response) + if not latest_version.is_greater(GdUnit4Version.current()): + _test_reporter.println_message("GdUnit4 is up-to-date.", Color.FOREST_GREEN) + return + + _test_reporter.println_message("A new update is available %s" % latest_version, Color.YELLOW) + _test_reporter.println_message("Open the GdUnit4 settings and check the update tab.", Color.YELLOW) + + control.icon = GdUnitUiTools.get_icon("Notification", Color.YELLOW) + var tween := create_tween() + tween.tween_property(control, "self_modulate", Color.VIOLET, .2).set_trans(Tween.TransitionType.TRANS_LINEAR) + tween.tween_property(control, "self_modulate", Color.YELLOW, .2).set_trans(Tween.TransitionType.TRANS_BOUNCE) + tween.parallel() + tween.tween_property(control, "scale", Vector2.ONE*1.05, .4).set_trans(Tween.TransitionType.TRANS_LINEAR) + tween.tween_property(control, "scale", Vector2.ONE, .4).set_trans(Tween.TransitionType.TRANS_BOUNCE) + tween.set_loops(-1) + tween.play() + + +func _on_gdunit_event(event: GdUnitEvent) -> void: + match event.type(): + GdUnitEvent.SESSION_START: + _test_reporter.test_session = GdUnitTestSession.new(GdUnitTestDiscoverGuard.instance().get_discovered_tests(), "") + GdUnitEvent.SESSION_CLOSE: + _test_reporter.test_session = null + + +func _on_gdunit_client_connected(client_id: int) -> void: + _test_reporter.clear() + _test_reporter.println_message("GdUnit Test Client connected with id: %d" % client_id, Color.hex(0x9887c4)) + + +func _on_gdunit_client_disconnected(client_id: int) -> void: + _test_reporter.println_message("GdUnit Test Client disconnected with id: %d" % client_id, Color.hex(0x9887c4)) + + +func _on_gdunit_message(message: String) -> void: + _test_reporter.println_message(message, Color.CORNFLOWER_BLUE) diff --git a/addons/gdUnit4/src/ui/GdUnitConsole.gd.uid b/addons/gdUnit4/src/ui/GdUnitConsole.gd.uid new file mode 100644 index 0000000..f3327c6 --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitConsole.gd.uid @@ -0,0 +1 @@ +uid://yk3dmu2vt5w2 diff --git a/addons/gdUnit4/src/ui/GdUnitConsole.tscn b/addons/gdUnit4/src/ui/GdUnitConsole.tscn new file mode 100644 index 0000000..25f45b5 --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitConsole.tscn @@ -0,0 +1,64 @@ +[gd_scene format=3 uid="uid://dm0wvfyeew7vd"] + +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/GdUnitConsole.gd" id="1"] + +[node name="Control" type="Control"] +use_parent_material = true +clip_contents = true +custom_minimum_size = Vector2(0, 200) +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") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +use_parent_material = true +clip_contents = 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 + +[node name="Header" type="PanelContainer" parent="VBoxContainer"] +auto_translate_mode = 2 +custom_minimum_size = Vector2(0, 32) +layout_mode = 2 +localize_numeral_system = false +mouse_filter = 2 + +[node name="header_title" type="RichTextLabel" parent="VBoxContainer/Header"] +auto_translate_mode = 2 +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +localize_numeral_system = false +mouse_filter = 2 +bbcode_enabled = true +scroll_active = false +autowrap_mode = 0 +shortcut_keys_enabled = false + +[node name="Console" type="ScrollContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="TextEdit" type="RichTextLabel" parent="VBoxContainer/Console"] +use_parent_material = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +focus_mode = 2 +bbcode_enabled = true +scroll_following = true +context_menu_enabled = true +selection_enabled = true diff --git a/addons/gdUnit4/src/ui/GdUnitFonts.gd b/addons/gdUnit4/src/ui/GdUnitFonts.gd new file mode 100644 index 0000000..0cb0f5d --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitFonts.gd @@ -0,0 +1,36 @@ +@tool +class_name GdUnitFonts +extends RefCounted + + +static func init_fonts(item: CanvasItem) -> float: + # set default size + item.set("theme_override_font_sizes/font_size", 16) + + if Engine.is_editor_hint(): + var base_control := EditorInterface.get_base_control() + # source modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs + # https://github.com/godotengine/godot/blob/9ee1873ae1e09c217ac24a5800007f63cb895615/editor/editor_log.cpp#L65 + var output_source_mono := base_control.get_theme_font("output_source_mono", "EditorFonts") + var output_source_bold_italic := base_control.get_theme_font("output_source_bold_italic", "EditorFonts") + var output_source_italic := base_control.get_theme_font("output_source_italic", "EditorFonts") + var output_source_bold := base_control.get_theme_font("output_source_bold", "EditorFonts") + var output_source := base_control.get_theme_font("output_source", "EditorFonts") + var settings := EditorInterface.get_editor_settings() + var scale_factor := EditorInterface.get_editor_scale() + var font_size: float = settings.get_setting("interface/editor/main_font_size") + + font_size *= scale_factor + item.set("theme_override_fonts/normal_font", output_source) + item.set("theme_override_fonts/bold_font", output_source_bold) + item.set("theme_override_fonts/italics_font", output_source_italic) + item.set("theme_override_fonts/bold_italics_font", output_source_bold_italic) + item.set("theme_override_fonts/mono_font", output_source_mono) + item.set("theme_override_font_sizes/font_size", font_size) + item.set("theme_override_font_sizes/normal_font_size", font_size) + item.set("theme_override_font_sizes/bold_font_size", font_size) + item.set("theme_override_font_sizes/italics_font_size", font_size) + item.set("theme_override_font_sizes/bold_italics_font_size", font_size) + item.set("theme_override_font_sizes/mono_font_size", font_size) + return font_size + return 16.0 diff --git a/addons/gdUnit4/src/ui/GdUnitFonts.gd.uid b/addons/gdUnit4/src/ui/GdUnitFonts.gd.uid new file mode 100644 index 0000000..f94bc32 --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitFonts.gd.uid @@ -0,0 +1 @@ +uid://cxol0xus2k25t diff --git a/addons/gdUnit4/src/ui/GdUnitInspector.gd b/addons/gdUnit4/src/ui/GdUnitInspector.gd new file mode 100644 index 0000000..81d896f --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitInspector.gd @@ -0,0 +1,34 @@ +@tool +class_name GdUnitInspecor +extends Panel + + +var _command_handler := GdUnitCommandHandler.instance() +var _wait_time := 0.0 + + +func _ready() -> void: + @warning_ignore("return_value_discarded") + GdUnitSignals.instance().gdunit_event.connect(func(event: GdUnitEvent) -> void: + if event.type() != GdUnitEvent.SESSION_START: + return + + var control: Control = get_parent_control() + # if the tab is floating we dont need to set as current + if control is TabContainer: + var tab_container :TabContainer = control + for tab_index in tab_container.get_tab_count(): + if tab_container.get_tab_title(tab_index) == "GdUnit": + tab_container.set_current_tab(tab_index) + ) + + # propagete the test_counters_changed signal to the progress bar + @warning_ignore("unsafe_property_access", "unsafe_method_access") + %MainPanel.test_counters_changed.connect(%ProgressBar._on_test_counter_changed) + + +func _process(delta: float) -> void: + _wait_time += delta + if _wait_time > 5.0: + _wait_time = 0 + _command_handler._do_process() diff --git a/addons/gdUnit4/src/ui/GdUnitInspector.gd.uid b/addons/gdUnit4/src/ui/GdUnitInspector.gd.uid new file mode 100644 index 0000000..8f4c2ab --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitInspector.gd.uid @@ -0,0 +1 @@ +uid://b26rlm46xtlm6 diff --git a/addons/gdUnit4/src/ui/GdUnitInspector.tscn b/addons/gdUnit4/src/ui/GdUnitInspector.tscn new file mode 100644 index 0000000..cbaf7c9 --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitInspector.tscn @@ -0,0 +1,70 @@ +[gd_scene format=3 uid="uid://mpo5o6d4uybu"] + +[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn" id="1"] +[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn" id="2"] +[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn" id="3"] +[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn" id="4"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/GdUnitInspector.gd" id="5"] +[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn" id="7"] +[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/network/GdUnitServer.tscn" id="7_721no"] + +[node name="GdUnit" type="Panel"] +use_parent_material = true +clip_contents = true +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_horizontal = 11 +size_flags_vertical = 3 +focus_mode = 2 +script = ExtResource("5") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +use_parent_material = true +clip_contents = true +layout_mode = 0 +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_vertical = 11 +theme_override_constants/separation = 0 + +[node name="Header" type="VBoxContainer" parent="VBoxContainer"] +use_parent_material = true +clip_contents = true +layout_mode = 2 +size_flags_horizontal = 9 +size_flags_vertical = 0 + +[node name="ToolBar" parent="VBoxContainer/Header" instance=ExtResource("1")] +layout_mode = 2 +size_flags_vertical = 1 + +[node name="ProgressBar" parent="VBoxContainer/Header" instance=ExtResource("2")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 5 +max_value = 0.0 + +[node name="StatusBar" parent="VBoxContainer/Header" instance=ExtResource("3")] +layout_mode = 2 +size_flags_horizontal = 11 + +[node name="MainPanel" parent="VBoxContainer" instance=ExtResource("7")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Monitor" parent="VBoxContainer" instance=ExtResource("4")] +layout_mode = 2 + +[node name="event_server" parent="." instance=ExtResource("7_721no")] + +[connection signal="select_error_next" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_select_next_item_by_state" binds= [7]] +[connection signal="select_error_prevous" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_select_previous_item_by_state" binds= [7]] +[connection signal="select_failure_next" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_select_next_item_by_state" binds= [6]] +[connection signal="select_failure_prevous" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_select_previous_item_by_state" binds= [6]] +[connection signal="select_flaky_next" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_select_next_item_by_state" binds= [5]] +[connection signal="select_flaky_prevous" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_select_previous_item_by_state" binds= [5]] +[connection signal="select_skipped_next" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_select_next_item_by_state" binds= [2]] +[connection signal="select_skipped_prevous" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_select_previous_item_by_state" binds= [2]] +[connection signal="tree_view_mode_changed" from="VBoxContainer/Header/StatusBar" to="VBoxContainer/MainPanel" method="_on_status_bar_tree_view_mode_changed"] +[connection signal="jump_to_orphan_nodes" from="VBoxContainer/Monitor" to="VBoxContainer/MainPanel" method="select_first_orphan"] diff --git a/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd b/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd new file mode 100644 index 0000000..8dd0265 --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd @@ -0,0 +1,31 @@ +class_name GdUnitInspectorTreeConstants +extends RefCounted + + +# the inspector panel presantation +enum TREE_VIEW_MODE { + TREE, + FLAT +} + + +# The inspector sort modes +enum SORT_MODE { + UNSORTED, + NAME_ASCENDING, + NAME_DESCENDING, + EXECUTION_TIME +} + + +enum STATE { + INITIAL, + RUNNING, + SKIPPED, + SUCCESS, + WARNING, + FLAKY, + FAILED, + ERROR, + ABORDED, +} diff --git a/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd.uid b/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd.uid new file mode 100644 index 0000000..463a301 --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd.uid @@ -0,0 +1 @@ +uid://bfaaukho0a14 diff --git a/addons/gdUnit4/src/ui/GdUnitUiTools.gd b/addons/gdUnit4/src/ui/GdUnitUiTools.gd new file mode 100644 index 0000000..0bcdb1d --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitUiTools.gd @@ -0,0 +1,151 @@ +class_name GdUnitUiTools +extends RefCounted + + +static var _spinner: AnimatedTexture + + +enum ImageFlipMode { + HORIZONTAl, + VERITCAL +} + + +## Returns the icon by name, if it exists. +static func get_icon(icon_name: String, color: = Color.BLACK) -> Texture2D: + if not Engine.is_editor_hint(): + return null + var icon := EditorInterface.get_base_control().get_theme_icon(icon_name, "EditorIcons") + if icon == null: + return null + if color != Color.BLACK: + icon = _modulate_texture(icon, color) + return icon + + +## Returns the icon flipped +static func get_flipped_icon(icon_name: String, mode: = ImageFlipMode.HORIZONTAl) -> Texture2D: + if not Engine.is_editor_hint(): + return null + var icon := EditorInterface.get_base_control().get_theme_icon(icon_name, "EditorIcons") + if icon == null: + return null + return ImageTexture.create_from_image(_flip_image(icon, mode)) + + +static func get_spinner() -> AnimatedTexture: + if _spinner != null: + return _spinner + _spinner = AnimatedTexture.new() + _spinner.frames = 8 + _spinner.speed_scale = 2.5 + for frame in _spinner.frames: + _spinner.set_frame_texture(frame, get_icon("Progress%d" % (frame+1))) + _spinner.set_frame_duration(frame, 0.2) + return _spinner + + +static func get_color_animated_icon(icon_name :String, from :Color, to :Color) -> AnimatedTexture: + if not Engine.is_editor_hint(): + return null + var texture := AnimatedTexture.new() + texture.frames = 8 + texture.speed_scale = 2.5 + var color := from + for frame in texture.frames: + color = lerp(color, to, .2) + texture.set_frame_texture(frame, get_icon(icon_name, color)) + texture.set_frame_duration(frame, 0.2) + return texture + + +static func get_run_overall_icon() -> Texture2D: + if not Engine.is_editor_hint(): + return null + var icon := EditorInterface.get_base_control().get_theme_icon("Play", "EditorIcons") + var image := _merge_images(icon.get_image(), Vector2i(-2, 0), icon.get_image(), Vector2i(3, 0)) + return ImageTexture.create_from_image(image) + + +static func get_GDScript_icon(status: String, color: Color) -> Texture2D: + if not Engine.is_editor_hint(): + return null + var icon_a := EditorInterface.get_base_control().get_theme_icon("GDScript", "EditorIcons") + var icon_b := EditorInterface.get_base_control().get_theme_icon(status, "EditorIcons") + var overlay_image := _modulate_image(icon_b.get_image(), color) + var image := _merge_images_scaled(icon_a.get_image(), Vector2i(0, 0), overlay_image, Vector2i(5, 5)) + return ImageTexture.create_from_image(image) + + +static func get_CSharpScript_icon(status: String, color: Color) -> Texture2D: + if not Engine.is_editor_hint(): + return null + var icon_a := EditorInterface.get_base_control().get_theme_icon("CSharpScript", "EditorIcons") + var icon_b := EditorInterface.get_base_control().get_theme_icon(status, "EditorIcons") + var overlay_image := _modulate_image(icon_b.get_image(), color) + var image := _merge_images_scaled(icon_a.get_image(), Vector2i(0, 0), overlay_image, Vector2i(5, 5)) + return ImageTexture.create_from_image(image) + + +static func _modulate_texture(texture: Texture2D, color: Color) -> Texture2D: + var image := _modulate_image(texture.get_image(), color) + return ImageTexture.create_from_image(image) + + +static func _modulate_image(image: Image, color: Color) -> Image: + var data: PackedByteArray = image.data["data"] + for pixel in range(0, data.size(), 4): + var pixel_a := _to_color(data, pixel) + if pixel_a.a8 != 0: + pixel_a = pixel_a.lerp(color, .9) + data[pixel + 0] = pixel_a.r8 + data[pixel + 1] = pixel_a.g8 + data[pixel + 2] = pixel_a.b8 + data[pixel + 3] = pixel_a.a8 + var output_image := Image.new() + output_image.set_data(image.get_width(), image.get_height(), image.has_mipmaps(), image.get_format(), data) + return output_image + + +static func _merge_images(image1: Image, offset1: Vector2i, image2: Image, offset2: Vector2i) -> Image: + ## we need to fix the image to have the same size to avoid merge conflicts + if image1.get_height() < image2.get_height(): + image1.resize(image2.get_width(), image2.get_height()) + # Create a new Image for the merged result + var merged_image := Image.create(image1.get_width(), image1.get_height(), false, Image.FORMAT_RGBA8) + merged_image.blit_rect_mask(image1, image2, Rect2(Vector2.ZERO, image1.get_size()), offset1) + merged_image.blit_rect_mask(image1, image2, Rect2(Vector2.ZERO, image2.get_size()), offset2) + return merged_image + + +@warning_ignore("narrowing_conversion") +static func _merge_images_scaled(image1: Image, offset1: Vector2i, image2: Image, offset2: Vector2i) -> Image: + ## we need to fix the image to have the same size to avoid merge conflicts + if image1.get_height() < image2.get_height(): + image1.resize(image2.get_width(), image2.get_height()) + # Create a new Image for the merged result + var merged_image := Image.create(image1.get_width(), image1.get_height(), false, image1.get_format()) + merged_image.blend_rect(image1, Rect2(Vector2.ZERO, image1.get_size()), offset1) + @warning_ignore("narrowing_conversion") + image2.resize(image2.get_width()/1.3, image2.get_height()/1.3) + merged_image.blend_rect(image2, Rect2(Vector2.ZERO, image2.get_size()), offset2) + return merged_image + + +static func _flip_image(texture: Texture2D, mode: ImageFlipMode) -> Image: + var flipped_image := Image.new() + flipped_image.copy_from(texture.get_image()) + if mode == ImageFlipMode.VERITCAL: + flipped_image.flip_x() + else: + flipped_image.flip_y() + return flipped_image + + +static func _to_color(data: PackedByteArray, position: int) -> Color: + var pixel_a := Color() + pixel_a.r8 = data[position + 0] + pixel_a.g8 = data[position + 1] + pixel_a.b8 = data[position + 2] + pixel_a.a8 = data[position + 3] + return pixel_a diff --git a/addons/gdUnit4/src/ui/GdUnitUiTools.gd.uid b/addons/gdUnit4/src/ui/GdUnitUiTools.gd.uid new file mode 100644 index 0000000..4d4f2be --- /dev/null +++ b/addons/gdUnit4/src/ui/GdUnitUiTools.gd.uid @@ -0,0 +1 @@ +uid://cv34ysw4mo25c diff --git a/addons/gdUnit4/src/ui/ScriptEditorControls.gd b/addons/gdUnit4/src/ui/ScriptEditorControls.gd new file mode 100644 index 0000000..5e07fe1 --- /dev/null +++ b/addons/gdUnit4/src/ui/ScriptEditorControls.gd @@ -0,0 +1,100 @@ +# A tool to provide extended script editor functionallity +class_name ScriptEditorControls +extends RefCounted + +# https://github.com/godotengine/godot/blob/master/editor/plugins/script_editor_plugin.h +# the Editor menu popup items +enum { + FILE_NEW, + FILE_NEW_TEXTFILE, + FILE_OPEN, + FILE_REOPEN_CLOSED, + FILE_OPEN_RECENT, + FILE_SAVE, + FILE_SAVE_AS, + FILE_SAVE_ALL, + FILE_THEME, + FILE_RUN, + FILE_CLOSE, + CLOSE_DOCS, + CLOSE_ALL, + CLOSE_OTHER_TABS, + TOGGLE_SCRIPTS_PANEL, + SHOW_IN_FILE_SYSTEM, + FILE_COPY_PATH, + FILE_TOOL_RELOAD_SOFT, + SEARCH_IN_FILES, + REPLACE_IN_FILES, + SEARCH_HELP, + SEARCH_WEBSITE, + HELP_SEARCH_FIND, + HELP_SEARCH_FIND_NEXT, + HELP_SEARCH_FIND_PREVIOUS, + WINDOW_MOVE_UP, + WINDOW_MOVE_DOWN, + WINDOW_NEXT, + WINDOW_PREV, + WINDOW_SORT, + WINDOW_SELECT_BASE = 100 +} + + +# Saves the given script and closes if requested by <close=true> +# The script is saved when is opened in the editor. +# The script is closed when <close> is set to true. +static func save_an_open_script(script_path: String, close:=false) -> bool: + #prints("save_an_open_script", script_path, close) + if !Engine.is_editor_hint(): + return false + var editor := EditorInterface.get_script_editor() + var editor_popup := _menu_popup() + # search for the script in all opened editor scrips + for open_script in editor.get_open_scripts(): + if open_script.resource_path == script_path: + # select the script in the editor + EditorInterface.edit_script(open_script, 0); + # save and close + editor_popup.id_pressed.emit(FILE_SAVE) + if close: + editor_popup.id_pressed.emit(FILE_CLOSE) + return true + return false + + +# Saves all opened script +static func save_all_open_script() -> void: + if Engine.is_editor_hint(): + _menu_popup().id_pressed.emit(FILE_SAVE_ALL) + + +static func close_open_editor_scripts() -> void: + if Engine.is_editor_hint(): + _menu_popup().id_pressed.emit(CLOSE_ALL) + + +# Edits the given script. +# The script is openend in the current editor and selected in the file system dock. +# The line and column on which to open the script can also be specified. +# The script will be open with the user-configured editor for the script's language which may be an external editor. +static func edit_script(script_path: String, line_number := -1) -> void: + var file_system := EditorInterface.get_resource_filesystem() + file_system.update_file(script_path) + var file_system_dock := EditorInterface.get_file_system_dock() + file_system_dock.navigate_to_path(script_path) + EditorInterface.select_file(script_path) + var script: GDScript = load(script_path) + EditorInterface.edit_script(script, line_number) + + +static func _menu_popup() -> PopupMenu: + @warning_ignore("unsafe_method_access") + return EditorInterface.get_script_editor().get_child(0).get_child(0).get_child(0).get_popup() + + +static func _print_menu(popup: PopupMenu) -> void: + for itemIndex in popup.item_count: + prints("get_item_id", popup.get_item_id(itemIndex)) + prints("get_item_accelerator", popup.get_item_accelerator(itemIndex)) + prints("get_item_shortcut", popup.get_item_shortcut(itemIndex)) + prints("get_item_text", popup.get_item_text(itemIndex)) + prints() diff --git a/addons/gdUnit4/src/ui/ScriptEditorControls.gd.uid b/addons/gdUnit4/src/ui/ScriptEditorControls.gd.uid new file mode 100644 index 0000000..d9eeb69 --- /dev/null +++ b/addons/gdUnit4/src/ui/ScriptEditorControls.gd.uid @@ -0,0 +1 @@ +uid://bp8qk8bp1l8gm diff --git a/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd b/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd new file mode 100644 index 0000000..3849e31 --- /dev/null +++ b/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd @@ -0,0 +1,45 @@ +class_name GdUnitContextMenuItem + + +var command_id: String: + set(value): + command_id = value + get: + return command_id + +var name: StringName: + set(value): + name = value + get: + return name + +var visible: Callable: + set(value): + visible = value + get: + return visible + +var icon: Texture2D: + get: + return GdUnitCommandHandler.instance().command_icon(command_id) + + +func _init(p_command_id: String, p_name: StringName, p_is_visible: Callable) -> void: + assert(p_command_id != null and not p_command_id.is_empty(), "(%s) missing command id " % p_command_id) + assert(p_is_visible != null, "(%s) missing parameter 'GdUnitCommand'" % p_name) + + self.command_id = p_command_id + self.name = p_name + self.visible = p_is_visible + + +func shortcut() -> Shortcut: + return GdUnitCommandHandler.instance().command_shortcut(command_id) + + +func is_visible(...args: Array) -> bool: + return visible.callv(args) + + +func execute(...args: Array) -> void: + GdUnitCommandHandler.instance().command_execute(command_id, args) diff --git a/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd.uid b/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd.uid new file mode 100644 index 0000000..03b589b --- /dev/null +++ b/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd.uid @@ -0,0 +1 @@ +uid://7xcqjry47b4y diff --git a/addons/gdUnit4/src/ui/menu/GdUnitEditorFileSystemContextMenuHandler.gd b/addons/gdUnit4/src/ui/menu/GdUnitEditorFileSystemContextMenuHandler.gd new file mode 100644 index 0000000..0c92b92 --- /dev/null +++ b/addons/gdUnit4/src/ui/menu/GdUnitEditorFileSystemContextMenuHandler.gd @@ -0,0 +1,37 @@ +@tool +extends EditorContextMenuPlugin + +var _context_menus: Array[GdUnitContextMenuItem] = [] + + +func _init() -> void: + var is_test_suite := func is_visible(script: Script, is_ts: bool) -> bool: + if script == null: + return false + return GdUnitTestSuiteScanner.is_test_suite(script) == is_ts + _context_menus.append(GdUnitContextMenuItem.new( + GdUnitCommandFileSystemRunTests.ID, + "Run Testsuites", + is_test_suite.bind(true) + )) + _context_menus.append(GdUnitContextMenuItem.new( + GdUnitCommandFileSystemDebugTests.ID, + "Debug Testsuites", + is_test_suite.bind(true) + )) + + # setup shortcuts + for menu_item in _context_menus: + if menu_item.shortcut(): + var cb := func call(...files: Array) -> void: + var paths: Array = files[0] + menu_item.execute.callv(paths) + add_menu_shortcut(menu_item.shortcut(), cb) + + +func _popup_menu(paths: PackedStringArray) -> void: + for menu_item in _context_menus: + if menu_item.shortcut(): + add_context_menu_item_from_shortcut(menu_item.name, menu_item.shortcut(), menu_item.icon) + else: + add_context_menu_item(menu_item.name, menu_item.execute.bindv(paths).unbind(1), menu_item.icon) diff --git a/addons/gdUnit4/src/ui/menu/GdUnitEditorFileSystemContextMenuHandler.gd.uid b/addons/gdUnit4/src/ui/menu/GdUnitEditorFileSystemContextMenuHandler.gd.uid new file mode 100644 index 0000000..fde0854 --- /dev/null +++ b/addons/gdUnit4/src/ui/menu/GdUnitEditorFileSystemContextMenuHandler.gd.uid @@ -0,0 +1 @@ +uid://bgas7gt1plcao diff --git a/addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.gd b/addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.gd new file mode 100644 index 0000000..2d6071a --- /dev/null +++ b/addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.gd @@ -0,0 +1,63 @@ +@tool +class_name GdUnitInspectorContextMenu +extends PopupMenu + + +const CONTEXT_MENU_RUN_ID = 0 +const CONTEXT_MENU_DEBUG_ID = 1 +const CONTEXT_MENU_RERUN_UNTIL_ID = 2 +# id 3 is the seperator +const CONTEXT_MENU_COLLAPSE_ALL = 4 +const CONTEXT_MENU_EXPAND_ALL = 5 + + +var command_handler: GdUnitCommandHandler + + +func _ready() -> void: + if not Engine.is_editor_hint(): + return + command_handler = GdUnitCommandHandler.instance() + _setup_item(CONTEXT_MENU_RUN_ID, "Run Tests", GdUnitCommandInspectorRunTests.ID) + _setup_item(CONTEXT_MENU_DEBUG_ID, "Debug Tests", GdUnitCommandInspectorDebugTests.ID) + _setup_item(CONTEXT_MENU_RERUN_UNTIL_ID, "Run Tests Until Fail", GdUnitCommandInspectorRerunTestsUntilFailure.ID) + _setup_item(CONTEXT_MENU_EXPAND_ALL, "Expand All", GdUnitCommandInspectorTreeExpand.ID) + _setup_item(CONTEXT_MENU_COLLAPSE_ALL, "Collapse All", GdUnitCommandInspectorTreeCollapse.ID) + + +func _setup_item(item_id: int, item_name: String, command_id: String) -> void: + set_item_text(item_id, item_name) + set_item_icon(item_id, command_handler.command_icon(command_id)) + set_item_shortcut(item_id, command_handler.command_shortcut(command_id)) + + +func disable_items() -> void: + set_item_disabled(CONTEXT_MENU_RUN_ID, true) + set_item_disabled(CONTEXT_MENU_DEBUG_ID, true) + set_item_disabled(CONTEXT_MENU_RERUN_UNTIL_ID, true) + + +func enable_items() -> void: + set_item_disabled(CONTEXT_MENU_RUN_ID, false) + set_item_disabled(CONTEXT_MENU_DEBUG_ID, false) + set_item_disabled(CONTEXT_MENU_RERUN_UNTIL_ID, false) + + +func _on_tree_item_mouse_selected(mouse_position: Vector2, mouse_button_index: int, source: Tree) -> void: + if mouse_button_index == MOUSE_BUTTON_RIGHT: + position = source.get_screen_position() + mouse_position + popup() + + +func _on_index_pressed(index: int) -> void: + match index: + CONTEXT_MENU_RUN_ID: + command_handler.command_execute(GdUnitCommandInspectorRunTests.ID) + CONTEXT_MENU_DEBUG_ID: + command_handler.command_execute(GdUnitCommandInspectorDebugTests.ID) + CONTEXT_MENU_RERUN_UNTIL_ID: + command_handler.command_execute(GdUnitCommandInspectorRerunTestsUntilFailure.ID) + CONTEXT_MENU_EXPAND_ALL: + command_handler.command_execute(GdUnitCommandInspectorTreeExpand.ID) + CONTEXT_MENU_COLLAPSE_ALL: + command_handler.command_execute(GdUnitCommandInspectorTreeCollapse.ID) diff --git a/addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.gd.uid b/addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.gd.uid new file mode 100644 index 0000000..f1c00de --- /dev/null +++ b/addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.gd.uid @@ -0,0 +1 @@ +uid://ch44lt0cpk77q diff --git a/addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.tscn b/addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.tscn new file mode 100644 index 0000000..5d87a41 --- /dev/null +++ b/addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.tscn @@ -0,0 +1,70 @@ +[gd_scene format=3 uid="uid://bjcc71p2h1trr"] + +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.gd" id="1_0x0vc"] + +[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) +} + +[node name="contextMenu" type="PopupMenu" unique_id=1824556050] +auto_translate_mode = 2 +oversampling_override = 1.0 +size = Vector2i(241, 170) +visible = true +item_count = 6 +item_0/text = "Run Tests" +item_0/icon = SubResource("DPITexture_miuuy") +item_0/id = 0 +item_1/text = "Debug Tests" +item_1/icon = SubResource("DPITexture_ern2r") +item_1/id = 1 +item_2/text = "Run Tests Until Fail" +item_2/icon = SubResource("DPITexture_miuuy") +item_2/id = 2 +item_3/id = 3 +item_3/separator = true +item_4/text = "Collapse All" +item_4/icon = SubResource("DPITexture_qdci2") +item_4/id = 4 +item_5/text = "Expand All" +item_5/icon = SubResource("DPITexture_hed0i") +item_5/id = 5 +script = ExtResource("1_0x0vc") + +[connection signal="index_pressed" from="." to="." method="_on_index_pressed"] diff --git a/addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd b/addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd new file mode 100644 index 0000000..26f5612 --- /dev/null +++ b/addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd @@ -0,0 +1,41 @@ +@tool +extends EditorContextMenuPlugin + + +var _context_menus: Array[GdUnitContextMenuItem] = [] + + +func _init() -> void: + var is_test_suite := func is_visible(script: Script, is_ts: bool) -> bool: + return GdUnitTestSuiteScanner.is_test_suite(script) == is_ts + _context_menus.append(GdUnitContextMenuItem.new( + GdUnitCommandScriptEditorRunTests.ID, + "Run Tests", + is_test_suite.bind(true) + )) + _context_menus.append(GdUnitContextMenuItem.new( + GdUnitCommandScriptEditorDebugTests.ID, + "Debug Tests", + is_test_suite.bind(true) + )) + _context_menus.append(GdUnitContextMenuItem.new( + GdUnitCommandScriptEditorCreateTest.ID, + "Create Test", + is_test_suite.bind(false) + )) + + # setup shortcuts + for menu_item in _context_menus: + if menu_item.shortcut(): + add_menu_shortcut(menu_item.shortcut(), menu_item.execute.unbind(1)) + + +func _popup_menu(_paths: PackedStringArray) -> void: + var current_script := EditorInterface.get_script_editor().get_current_script() + + for menu_item in _context_menus: + if menu_item.is_visible(current_script): + if menu_item.shortcut(): + add_context_menu_item_from_shortcut(menu_item.name, menu_item.shortcut()) + else: + add_context_menu_item(menu_item.name, menu_item.execute.unbind(1)) diff --git a/addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd.uid b/addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd.uid new file mode 100644 index 0000000..8ab0661 --- /dev/null +++ b/addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd.uid @@ -0,0 +1 @@ +uid://f40ddt1acw6h 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"] 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 Binary files differnew file mode 100644 index 0000000..12de79f --- /dev/null +++ b/addons/gdUnit4/src/ui/settings/logo.png 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 diff --git a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd new file mode 100644 index 0000000..be5c352 --- /dev/null +++ b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd @@ -0,0 +1,129 @@ +@tool +extends MarginContainer + +@onready var _template_editor :CodeEdit = $VBoxContainer/EdiorLayout/Editor +@onready var _tags_editor :CodeEdit = $Tags/MarginContainer/TextEdit +@onready var _title_bar :Panel = $VBoxContainer/sub_category +@onready var _save_button :Button = $VBoxContainer/Panel/HBoxContainer/Save +@onready var _selected_type :OptionButton = $VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer/SelectType +@onready var _show_tags :PopupPanel = $Tags + + +var gd_key_words :PackedStringArray = ["extends", "class_name", "const", "var", "onready", "func", "void", "pass"] +var gdunit_key_words :PackedStringArray = ["GdUnitTestSuite", "before", "after", "before_test", "after_test"] +var _selected_template :int + + +func _ready() -> void: + setup_editor_colors() + setup_fonts() + setup_supported_types() + load_template(GdUnitTestSuiteTemplate.TEMPLATE_ID_GD) + setup_tags_help() + + +func _notification(what :int) -> void: + if what == EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED: + setup_fonts() + + +func setup_editor_colors() -> void: + if not Engine.is_editor_hint(): + return + + var background_color := get_editor_color("text_editor/theme/highlighting/background_color", Color(0.1155, 0.132, 0.1595, 1)) + var text_color := get_editor_color("text_editor/theme/highlighting/text_color", Color(0.8025, 0.81, 0.8225, 1)) + var selection_color := get_editor_color("text_editor/theme/highlighting/selection_color", Color(0.44, 0.73, 0.98, 0.4)) + + for e :CodeEdit in [_template_editor, _tags_editor]: + var editor :CodeEdit = e + editor.add_theme_color_override("background_color", background_color) + editor.add_theme_color_override("font_color", text_color) + editor.add_theme_color_override("font_readonly_color", text_color) + editor.add_theme_color_override("font_selected_color", selection_color) + setup_highlighter(editor) + + +func setup_highlighter(editor :CodeEdit) -> void: + var highlighter := CodeHighlighter.new() + editor.set_syntax_highlighter(highlighter) + var number_color := get_editor_color("text_editor/theme/highlighting/number_color", Color(0.63, 1, 0.88, 1)) + var symbol_color := get_editor_color("text_editor/theme/highlighting/symbol_color", Color(0.67, 0.79, 1, 1)) + var function_color := get_editor_color("text_editor/theme/highlighting/function_color", Color(0.34, 0.7, 1, 1)) + var member_variable_color := get_editor_color("text_editor/theme/highlighting/member_variable_color", Color(0.736, 0.88, 1, 1)) + var comment_color := get_editor_color("text_editor/theme/highlighting/comment_color", Color(0.8025, 0.81, 0.8225, 0.5)) + var keyword_color := get_editor_color("text_editor/theme/highlighting/keyword_color", Color(1, 0.44, 0.52, 1)) + var base_type_color := get_editor_color("text_editor/theme/highlighting/base_type_color", Color(0.26, 1, 0.76, 1)) + var annotation_color := get_editor_color("text_editor/theme/highlighting/gdscript/annotation_color", Color(1, 0.7, 0.45, 1)) + + highlighter.clear_color_regions() + highlighter.clear_keyword_colors() + highlighter.add_color_region("#", "", comment_color, true) + highlighter.add_color_region("${", "}", Color.YELLOW) + highlighter.add_color_region("'", "'", Color.YELLOW) + highlighter.add_color_region("\"", "\"", Color.YELLOW) + highlighter.number_color = number_color + highlighter.symbol_color = symbol_color + highlighter.function_color = function_color + highlighter.member_variable_color = member_variable_color + highlighter.add_keyword_color("@", annotation_color) + highlighter.add_keyword_color("warning_ignore", annotation_color) + for word in gd_key_words: + highlighter.add_keyword_color(word, keyword_color) + for word in gdunit_key_words: + highlighter.add_keyword_color(word, base_type_color) + + +## Using this function to avoid null references to colors on inital Godot installations. +## For more details show https://github.com/MikeSchulze/gdUnit4/issues/533 +func get_editor_color(property_name: String, default: Color) -> Color: + var settings := EditorInterface.get_editor_settings() + return settings.get_setting(property_name) if settings.has_setting(property_name) else default + + +func setup_fonts() -> void: + if _template_editor: + @warning_ignore("return_value_discarded") + GdUnitFonts.init_fonts(_template_editor) + var font_size := GdUnitFonts.init_fonts(_tags_editor) + _title_bar.size.y = font_size + 16 + _title_bar.custom_minimum_size.y = font_size + 16 + + +func setup_supported_types() -> void: + _selected_type.clear() + _selected_type.add_item("GD - GDScript", GdUnitTestSuiteTemplate.TEMPLATE_ID_GD) + _selected_type.add_item("C# - CSharpScript", GdUnitTestSuiteTemplate.TEMPLATE_ID_CS) + + +func setup_tags_help() -> void: + _tags_editor.set_text(GdUnitTestSuiteTemplate.load_tags(_selected_template)) + + +func load_template(template_id :int) -> void: + _selected_template = template_id + _template_editor.set_text(GdUnitTestSuiteTemplate.load_template(template_id)) + + +func _on_Restore_pressed() -> void: + _template_editor.set_text(GdUnitTestSuiteTemplate.default_template(_selected_template)) + GdUnitTestSuiteTemplate.reset_to_default(_selected_template) + _save_button.disabled = true + + +func _on_Save_pressed() -> void: + GdUnitTestSuiteTemplate.save_template(_selected_template, _template_editor.get_text()) + _save_button.disabled = true + + +func _on_Tags_pressed() -> void: + _show_tags.popup_centered_ratio(.5) + + +func _on_Editor_text_changed() -> void: + _save_button.disabled = false + + +func _on_SelectType_item_selected(index :int) -> void: + load_template(_selected_type.get_item_id(index)) + setup_tags_help() diff --git a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd.uid b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd.uid new file mode 100644 index 0000000..2c03cb1 --- /dev/null +++ b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd.uid @@ -0,0 +1 @@ +uid://dh7k6amf26gro diff --git a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn new file mode 100644 index 0000000..5ccc443 --- /dev/null +++ b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn @@ -0,0 +1,127 @@ +[gd_scene load_steps=2 format=3 uid="uid://dte0m2endcgtu"] + +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd" id="1"] + +[node name="TestSuiteTemplate" type="MarginContainer"] +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") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="sub_category" type="Panel" parent="VBoxContainer"] +custom_minimum_size = Vector2(0, 30) +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label" type="Label" parent="VBoxContainer/sub_category"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 4.0 +offset_right = 4.0 +offset_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "Test Suite Template +" + +[node name="EdiorLayout" type="VBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Editor" type="CodeEdit" parent="VBoxContainer/EdiorLayout"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/EdiorLayout/Editor"] +layout_mode = 1 +anchors_preset = 12 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = -31.0 +grow_horizontal = 2 +grow_vertical = 0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/EdiorLayout/Editor/MarginContainer"] +layout_mode = 2 +size_flags_vertical = 8 +alignment = 2 + +[node name="Tags" type="Button" parent="VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer"] +layout_mode = 2 +tooltip_text = "Shows supported tags." +text = "Supported Tags" + +[node name="SelectType" type="OptionButton" parent="VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer"] +layout_mode = 2 +tooltip_text = "Select the script type specific template." +item_count = 2 +selected = 0 +popup/item_0/text = "GD - GDScript" +popup/item_0/id = 1000 +popup/item_1/text = "C# - CSharpScript" +popup/item_1/id = 2000 + +[node name="Panel" type="MarginContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Panel"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +alignment = 2 + +[node name="Restore" type="Button" parent="VBoxContainer/Panel/HBoxContainer"] +layout_mode = 2 +text = "Restore" + +[node name="Save" type="Button" parent="VBoxContainer/Panel/HBoxContainer"] +layout_mode = 2 +disabled = true +text = "Save" + +[node name="Tags" type="PopupPanel" parent="."] +size = Vector2i(300, 100) +unresizable = false +content_scale_aspect = 4 + +[node name="MarginContainer" type="MarginContainer" parent="Tags"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 4.0 +offset_top = 4.0 +offset_right = -856.0 +offset_bottom = -552.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="TextEdit" type="CodeEdit" parent="Tags/MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +editable = false +context_menu_enabled = false +shortcut_keys_enabled = false +virtual_keyboard_enabled = false + +[connection signal="text_changed" from="VBoxContainer/EdiorLayout/Editor" to="." method="_on_Editor_text_changed"] +[connection signal="pressed" from="VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer/Tags" to="." method="_on_Tags_pressed"] +[connection signal="item_selected" from="VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer/SelectType" to="." method="_on_SelectType_item_selected"] +[connection signal="pressed" from="VBoxContainer/Panel/HBoxContainer/Restore" to="." method="_on_Restore_pressed"] +[connection signal="pressed" from="VBoxContainer/Panel/HBoxContainer/Save" to="." method="_on_Save_pressed"] |
