From 8d84dcd300dff77f10e2d324a930bbe7ceba0948 Mon Sep 17 00:00:00 2001 From: Atomic Junky <67459553+atomic-junky@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:52:17 +0200 Subject: [PATCH 001/103] Base rework --- common/layouts/editor/editor_section.gd | 98 ++ common/layouts/editor/editor_section.gd.uid | 1 + common/layouts/editor/editor_section.tscn | 42 + .../inspector_panel.gd} | 26 +- .../inspector_panel.gd.uid} | 0 .../inspector_panel/inspector_panel.tscn | 56 + .../language_switcher/language_switcher.tscn | 9 +- common/layouts/side_panel/side_panel.tscn | 52 - common/ui/buttons/delete_button.tscn | 5 +- .../monologue_character_field.gd | 4 + .../monologue_character_field.tscn | 7 +- .../fields/check_box/monologue_check_box.gd | 6 - .../fields/check_box/monologue_check_box.tscn | 8 +- .../collapsible_field/collapsible_field.tscn | 1 - .../ui/fields/dropdown/monolgue_dropdown.gd | 5 - .../fields/dropdown/monologue_dropdown.tscn | 6 +- common/ui/fields/file_picker/file_picker.gd | 11 +- .../file_picker/monologue_file_picker.tscn | 37 +- .../fields/line_edit/monologue_line_edit.gd | 16 - .../fields/line_edit/monologue_line_edit.tscn | 6 +- common/ui/fields/list/monologue_list.gd | 7 +- common/ui/fields/monologue_field.gd | 13 +- .../fields/portrait_option/portrait_option.gd | 4 + common/ui/fields/property.gd | 15 +- common/ui/fields/slider/monologue_slider.gd | 11 +- common/ui/fields/slider/monologue_slider.tscn | 23 +- .../ui/fields/spin_box/monologue_spin_box.gd | 5 - .../fields/spin_box/monologue_spin_box.tscn | 6 +- common/ui/fields/text/monologue_text.gd | 11 +- common/ui/fields/text/monologue_text.tscn | 25 +- .../ui/fields/timeline/monologue_timeline.gd | 4 + common/ui/fields/toggle/monologue_toggle.gd | 5 - common/ui/fields/toggle/monologue_toggle.tscn | 6 +- common/ui/fields/vector/monologue_vector.gd | 5 - common/ui/fields/vector/monologue_vector.tscn | 28 +- .../graph_node_picker/graph_node_picker.gd | 6 +- nodes/choice_node/choice_node.gd | 2 +- scenes/main/app.tscn | 55 +- scenes/main/characters_section.gd | 14 + scenes/main/characters_section.gd.uid | 1 + scenes/main/editor.tscn | 293 ++++ scenes/main/graph.tscn | 229 --- scenes/main/graph_edit_switcher.gd | 28 +- ...nologue_control.gd => monologue_editor.gd} | 23 +- ...control.gd.uid => monologue_editor.gd.uid} | 0 scenes/main/variables_section.gd | 3 + scenes/main/variables_section.gd.uid | 1 + ui/assets/icons/object.svg | 3 + ui/assets/icons/object.svg.import | 37 + ui/theme_default/main.tres | 1371 +++++++++-------- ui/theme_default/theme_default.gd | 133 +- unit/test_graph_edit_switcher.gd | 16 +- 52 files changed, 1578 insertions(+), 1201 deletions(-) create mode 100644 common/layouts/editor/editor_section.gd create mode 100644 common/layouts/editor/editor_section.gd.uid create mode 100644 common/layouts/editor/editor_section.tscn rename common/layouts/{side_panel/side_panel.gd => inspector_panel/inspector_panel.gd} (91%) rename common/layouts/{side_panel/side_panel.gd.uid => inspector_panel/inspector_panel.gd.uid} (100%) create mode 100644 common/layouts/inspector_panel/inspector_panel.tscn delete mode 100644 common/layouts/side_panel/side_panel.tscn create mode 100644 scenes/main/characters_section.gd create mode 100644 scenes/main/characters_section.gd.uid create mode 100644 scenes/main/editor.tscn delete mode 100644 scenes/main/graph.tscn rename scenes/main/{monologue_control.gd => monologue_editor.gd} (90%) rename scenes/main/{monologue_control.gd.uid => monologue_editor.gd.uid} (100%) create mode 100644 scenes/main/variables_section.gd create mode 100644 scenes/main/variables_section.gd.uid create mode 100644 ui/assets/icons/object.svg create mode 100644 ui/assets/icons/object.svg.import diff --git a/common/layouts/editor/editor_section.gd b/common/layouts/editor/editor_section.gd new file mode 100644 index 00000000..56ae4231 --- /dev/null +++ b/common/layouts/editor/editor_section.gd @@ -0,0 +1,98 @@ +@tool +class_name EditorSection extends TabContainer + +@export var default_size: int = 0 : + set(value): + var parent: Node = get_parent() + if not parent is SplitContainer: return + + if parent.get_children().find(self) == 0: + parent.split_offset = value + else: + parent.split_offset = -value + + default_size = value +@export var lock_size: bool = false : + set(value): + var parent: Node = get_parent() + if not parent is SplitContainer: return + if value: + parent.dragging_enabled = false + + lock_size = value + +var have_focus: bool = false + + +func _ready() -> void: + set_process_input(true) + update_style(false) + get_viewport().gui_focus_changed.connect(_on_gui_focus_changed) + + for child in get_children(): + var icon: Variant = child.get("section_icon") + var child_idx: int = get_children().find(child) + if icon is Texture2D: + set_tab_icon(child_idx, icon) + + + +func _on_gui_focus_changed(node: Control) -> void: + if Engine.is_editor_hint(): + return + + update_style(_is_child(node)) + + +func update_style(focus: bool) -> void: + var child_count := get_child_count() + visible = child_count > 0 + tabs_visible = child_count != 1 + + var panel_style_name: String = "panel" + var tabbar_style_name: String = "tabbar_background" + have_focus = focus + if focus: + panel_style_name += "_focus" + tabbar_style_name += "_focus" + else: + panel_style_name += "_unfocus" + tabbar_style_name += "_unfocus" + + if tabs_visible: + panel_style_name = "tab_" + panel_style_name + + add_theme_stylebox_override("panel", get_theme_stylebox(panel_style_name, "EditorSection")) + add_theme_stylebox_override("tabbar_background", get_theme_stylebox(tabbar_style_name, "EditorSection")) + + +func _is_child(node: Control) -> bool: + var parent: Node = node + for i in range(256): + var next_parent = parent.get_parent() + if next_parent == null: + return false + elif parent == self: + return true + parent = next_parent + return false + + +func _input(event: InputEvent) -> void: + if event is InputEventMouseButton and event.is_pressed(): + var mouse_hovering: bool = is_mouse_on_section() + update_style(mouse_hovering) + + +func is_mouse_on_section() -> bool: + var local_mouse_pos := get_global_mouse_position() - global_position + return not (local_mouse_pos.x < 0 or local_mouse_pos.y < 0 or \ + local_mouse_pos.x > size.x or local_mouse_pos.y > size.y) + + +func _on_child_entered_tree(_node: Node) -> void: + update_style(have_focus) + + +func _on_child_exiting_tree(_node: Node) -> void: + update_style(have_focus) diff --git a/common/layouts/editor/editor_section.gd.uid b/common/layouts/editor/editor_section.gd.uid new file mode 100644 index 00000000..45796522 --- /dev/null +++ b/common/layouts/editor/editor_section.gd.uid @@ -0,0 +1 @@ +uid://ijejsk35dal diff --git a/common/layouts/editor/editor_section.tscn b/common/layouts/editor/editor_section.tscn new file mode 100644 index 00000000..152e3b8a --- /dev/null +++ b/common/layouts/editor/editor_section.tscn @@ -0,0 +1,42 @@ +[gd_scene load_steps=4 format=3 uid="uid://dfkqf3wjdnj0m"] + +[ext_resource type="Script" uid="uid://ijejsk35dal" path="res://common/layouts/editor/editor_section.gd" id="1_1u3ep"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1u3ep"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.186303, 0.183362, 0.215088, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qe3vh"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) +border_width_left = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[node name="EditorSection" type="TabContainer"] +visible = false +offset_right = 40.0 +offset_bottom = 40.0 +focus_mode = 1 +theme_type_variation = &"EditorSection" +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_qe3vh") +script = ExtResource("1_1u3ep") + +[connection signal="child_entered_tree" from="." to="." method="_on_child_entered_tree"] +[connection signal="child_exiting_tree" from="." to="." method="_on_child_exiting_tree"] diff --git a/common/layouts/side_panel/side_panel.gd b/common/layouts/inspector_panel/inspector_panel.gd similarity index 91% rename from common/layouts/side_panel/side_panel.gd rename to common/layouts/inspector_panel/inspector_panel.gd index 7e07ab0c..35668c7f 100644 --- a/common/layouts/side_panel/side_panel.gd +++ b/common/layouts/inspector_panel/inspector_panel.gd @@ -1,12 +1,11 @@ ## Side panel which displays graph node details. This panel should not contain -## references to MonologueControl or GraphEditSwitcher. -class_name SidePanel extends PanelContainer +## references to MonologueEditor or GraphEditSwitcher. +class_name InspectorPanel extends PanelContainer @onready var fields_container = %Fields @onready var topbox = %TopBox @onready var ribbon_scene = preload("res://common/ui/ribbon/ribbon.tscn") -@onready -var collapsible_field = preload("res://common/ui/fields/collapsible_field/collapsible_field.tscn") +@onready var collapsible_field = preload("res://common/ui/fields/collapsible_field/collapsible_field.tscn") var collapsibles: Dictionary[String, CollapsibleField] var id_field_container: Control @@ -15,7 +14,6 @@ var selected_node: MonologueGraphNode func _ready(): GlobalSignal.add_listener("close_panel", _on_close_button_pressed) - hide() func clear(): @@ -26,14 +24,17 @@ func clear(): collapsibles.clear() -func on_graph_node_deselected(_node): - hide.call_deferred() +func on_graph_node_deselected(_node: MonologueGraphNode) -> void: + pass -func on_graph_node_selected(node: MonologueGraphNode, bypass: bool = false): +func on_graph_node_selected(node: MonologueGraphNode, bypass: bool = false) -> void: + if not node: + return + if not bypass: var graph_edit = node.get_parent() - await get_tree().create_timer(0.1).timeout + await get_tree().process_frame if ( is_instance_valid(node) and not graph_edit.moving_mode @@ -81,9 +82,10 @@ func on_graph_node_selected(node: MonologueGraphNode, bypass: bool = false): continue if property_name == "id": - var field = node.get(property_name) - field.show(topbox, 0, false) - id_field_container = field.field_container + var property = node.get(property_name) + var field = property.show(topbox, 0, false) + field.set_label_visible(false) + id_field_container = property.field_container else: var field = node.get(property_name).show(fields_container) field.set_label_text(property_name.capitalize()) diff --git a/common/layouts/side_panel/side_panel.gd.uid b/common/layouts/inspector_panel/inspector_panel.gd.uid similarity index 100% rename from common/layouts/side_panel/side_panel.gd.uid rename to common/layouts/inspector_panel/inspector_panel.gd.uid diff --git a/common/layouts/inspector_panel/inspector_panel.tscn b/common/layouts/inspector_panel/inspector_panel.tscn new file mode 100644 index 00000000..6d566a44 --- /dev/null +++ b/common/layouts/inspector_panel/inspector_panel.tscn @@ -0,0 +1,56 @@ +[gd_scene load_steps=3 format=3 uid="uid://dgvhvxdrd58qp"] + +[ext_resource type="Script" uid="uid://dtf4ge38njewp" path="res://common/layouts/inspector_panel/inspector_panel.gd" id="1_haagr"] +[ext_resource type="Texture2D" uid="uid://b272tbdmvxj20" path="res://ui/assets/icons/play.svg" id="2_34x8o"] + +[node name="InspectorPanel" type="PanelContainer"] +offset_right = 158.0 +offset_bottom = 121.0 +theme_type_variation = &"InspectorPanel" +script = ExtResource("1_haagr") + +[node name="VBox" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="VBox" type="VBoxContainer" parent="VBox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBox/VBox"] +layout_mode = 2 +theme_type_variation = &"HeaderSmall" +text = "Inspector" +horizontal_alignment = 1 + +[node name="TopBox" type="HBoxContainer" parent="VBox/VBox"] +unique_name_in_owner = true +layout_mode = 2 +theme_type_variation = &"HBoxContainer_Small" +alignment = 2 + +[node name="RFHButton" type="Button" parent="VBox/VBox/TopBox"] +custom_minimum_size = Vector2(34, 29) +layout_mode = 2 +icon = ExtResource("2_34x8o") +icon_alignment = 1 +expand_icon = true + +[node name="FieldPanel" type="PanelContainer" parent="VBox"] +clip_contents = true +layout_mode = 2 +size_flags_vertical = 3 +theme_type_variation = &"FieldPanel" + +[node name="Scroller" type="ScrollContainer" parent="VBox/FieldPanel"] +clip_contents = false +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Fields" type="VBoxContainer" parent="VBox/FieldPanel/Scroller"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[connection signal="pressed" from="VBox/VBox/TopBox/RFHButton" to="." method="_on_rfh_button_pressed"] diff --git a/common/layouts/language_switcher/language_switcher.tscn b/common/layouts/language_switcher/language_switcher.tscn index cc85123d..e878e6bc 100644 --- a/common/layouts/language_switcher/language_switcher.tscn +++ b/common/layouts/language_switcher/language_switcher.tscn @@ -25,14 +25,15 @@ grow_vertical = 2 mouse_filter = 2 [node name="PanelContainer" type="PanelContainer" parent="Dropdown"] +z_index = 2 layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -offset_left = -10.0 -offset_top = -220.0 -offset_right = 10.0 -offset_bottom = -60.0 +offset_left = -3.0 +offset_top = 42.0 +offset_right = 3.0 +offset_bottom = 202.0 grow_horizontal = 2 grow_vertical = 2 theme_type_variation = &"OuterPanel" diff --git a/common/layouts/side_panel/side_panel.tscn b/common/layouts/side_panel/side_panel.tscn deleted file mode 100644 index b39ff815..00000000 --- a/common/layouts/side_panel/side_panel.tscn +++ /dev/null @@ -1,52 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://dgvhvxdrd58qp"] - -[ext_resource type="Script" uid="uid://dtf4ge38njewp" path="res://common/layouts/side_panel/side_panel.gd" id="1_haagr"] -[ext_resource type="Texture2D" uid="uid://b272tbdmvxj20" path="res://ui/assets/icons/play.svg" id="2_34x8o"] - -[node name="SidePanel" type="PanelContainer"] -offset_right = 158.0 -offset_bottom = 121.0 -theme_type_variation = &"EditorSidePanel" -script = ExtResource("1_haagr") - -[node name="VBox" type="VBoxContainer" parent="."] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="PanelContainer" type="PanelContainer" parent="VBox"] -z_index = 1 -layout_mode = 2 -theme_type_variation = &"EditorSidePanelTopBox" - -[node name="VBox" type="VBoxContainer" parent="VBox/PanelContainer"] -layout_mode = 2 - -[node name="TopBox" type="HBoxContainer" parent="VBox/PanelContainer/VBox"] -unique_name_in_owner = true -layout_mode = 2 -theme_type_variation = &"HBoxContainer_Small" - -[node name="RFHButton" type="Button" parent="VBox/PanelContainer/VBox/TopBox"] -custom_minimum_size = Vector2(34, 29) -layout_mode = 2 -icon = ExtResource("2_34x8o") -icon_alignment = 1 -expand_icon = true - -[node name="HSeparator" type="HSeparator" parent="VBox/PanelContainer/VBox"] -layout_mode = 2 -theme_type_variation = &"HSeparatorGrow" - -[node name="Scroller" type="ScrollContainer" parent="VBox"] -clip_contents = false -layout_mode = 2 -size_flags_vertical = 3 - -[node name="Fields" type="VBoxContainer" parent="VBox/Scroller"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[connection signal="pressed" from="VBox/PanelContainer/VBox/TopBox/RFHButton" to="." method="_on_rfh_button_pressed"] diff --git a/common/ui/buttons/delete_button.tscn b/common/ui/buttons/delete_button.tscn index e2a40158..2b52e5bc 100644 --- a/common/ui/buttons/delete_button.tscn +++ b/common/ui/buttons/delete_button.tscn @@ -3,10 +3,11 @@ [ext_resource type="Texture2D" uid="uid://hmjhxdsk3pwj" path="res://ui/assets/icons/trash.svg" id="1_xsgmh"] [node name="DeleteButton" type="Button"] -custom_minimum_size = Vector2(34, 29) -offset_right = 34.0 +custom_minimum_size = Vector2(29, 29) +offset_right = 29.0 offset_bottom = 29.0 size_flags_vertical = 0 theme_type_variation = &"ButtonWarning" icon = ExtResource("1_xsgmh") +icon_alignment = 1 expand_icon = true diff --git a/common/ui/fields/character_field/monologue_character_field.gd b/common/ui/fields/character_field/monologue_character_field.gd index 8ad612df..e1d36ebf 100644 --- a/common/ui/fields/character_field/monologue_character_field.gd +++ b/common/ui/fields/character_field/monologue_character_field.gd @@ -24,3 +24,7 @@ func _on_name_edit_text_submitted(new_text: String) -> void: func _on_edit_button_pressed() -> void: GlobalSignal.emit("open_character_edit", [graph_edit, character_index]) + + +func use_custom_field_label() -> bool: + return true diff --git a/common/ui/fields/character_field/monologue_character_field.tscn b/common/ui/fields/character_field/monologue_character_field.tscn index e7cba915..2d044730 100644 --- a/common/ui/fields/character_field/monologue_character_field.tscn +++ b/common/ui/fields/character_field/monologue_character_field.tscn @@ -20,16 +20,15 @@ script = ExtResource("1_no1me") layout_mode = 2 [node name="CenterContainer" type="CenterContainer" parent="HBoxContainer"] -custom_minimum_size = Vector2(100, 100) layout_mode = 2 [node name="TextureRect" type="TextureRect" parent="HBoxContainer/CenterContainer"] material = SubResource("ShaderMaterial_q7tom") clip_contents = true -custom_minimum_size = Vector2(100, 100) +custom_minimum_size = Vector2(65, 0) layout_mode = 2 texture = ExtResource("2_jkh4y") -expand_mode = 1 +expand_mode = 4 stretch_mode = 6 [node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] @@ -41,7 +40,7 @@ layout_mode = 2 size_flags_vertical = 3 [node name="FieldLabel" type="Label" parent="HBoxContainer/VBoxContainer/NameContainer"] -custom_minimum_size = Vector2(100, 0) +custom_minimum_size = Vector2(75, 0) layout_mode = 2 size_flags_vertical = 0 text = "Name" diff --git a/common/ui/fields/check_box/monologue_check_box.gd b/common/ui/fields/check_box/monologue_check_box.gd index d3d5c5f0..cf9c9305 100644 --- a/common/ui/fields/check_box/monologue_check_box.gd +++ b/common/ui/fields/check_box/monologue_check_box.gd @@ -1,12 +1,6 @@ class_name MonologueCheckBox extends MonologueField @onready var check_box = $VBox/CheckBox -@onready var label = $Label - - -func set_label_text(text: String) -> void: - label.text = text - func propagate(value: Variant) -> void: super.propagate(value) diff --git a/common/ui/fields/check_box/monologue_check_box.tscn b/common/ui/fields/check_box/monologue_check_box.tscn index 917031d9..2246bc36 100644 --- a/common/ui/fields/check_box/monologue_check_box.tscn +++ b/common/ui/fields/check_box/monologue_check_box.tscn @@ -1,11 +1,10 @@ -[gd_scene load_steps=3 format=3 uid="uid://71sq1ohwv8cn"] +[gd_scene load_steps=2 format=3 uid="uid://71sq1ohwv8cn"] [ext_resource type="Script" uid="uid://m2a68q1fce52" path="res://common/ui/fields/check_box/monologue_check_box.gd" id="1_awprp"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="2_8ij76"] [node name="MonologueCheckBox" type="HBoxContainer"] custom_minimum_size = Vector2(0, 32) -offset_right = 81.0 +offset_right = 15.0 offset_bottom = 32.0 theme_type_variation = &"HBoxContainer_Small" script = ExtResource("1_awprp") @@ -18,7 +17,4 @@ alignment = 1 layout_mode = 2 expand_icon = true -[node name="Label" parent="." instance=ExtResource("2_8ij76")] -layout_mode = 2 - [connection signal="toggled" from="VBox/CheckBox" to="." method="_on_check_box_toggled"] diff --git a/common/ui/fields/collapsible_field/collapsible_field.tscn b/common/ui/fields/collapsible_field/collapsible_field.tscn index 7ee4ade7..f92bde23 100644 --- a/common/ui/fields/collapsible_field/collapsible_field.tscn +++ b/common/ui/fields/collapsible_field/collapsible_field.tscn @@ -12,7 +12,6 @@ script = ExtResource("1_tmui1") [node name="Button" type="Button" parent="."] layout_mode = 2 -theme_type_variation = &"Button_Flat_NoCorner" icon = ExtResource("2_357t1") alignment = 0 icon_alignment = 2 diff --git a/common/ui/fields/dropdown/monolgue_dropdown.gd b/common/ui/fields/dropdown/monolgue_dropdown.gd index 151a1aca..8a606cb2 100644 --- a/common/ui/fields/dropdown/monolgue_dropdown.gd +++ b/common/ui/fields/dropdown/monolgue_dropdown.gd @@ -4,7 +4,6 @@ class_name MonologueDropdown extends MonologueField ## Usefull when items are set after the value is set. @export var late_items: bool -@onready var label: Label = $Label @onready var option_button: OptionButton = $OptionButton var backup_value: Variant @@ -88,10 +87,6 @@ func set_items( validate() -func set_label_text(text: String) -> void: - label.text = text - - func validate(): var is_out = option_button.selected >= option_button.item_count var is_negative = option_button.selected < 0 diff --git a/common/ui/fields/dropdown/monologue_dropdown.tscn b/common/ui/fields/dropdown/monologue_dropdown.tscn index 39011579..944d58a6 100644 --- a/common/ui/fields/dropdown/monologue_dropdown.tscn +++ b/common/ui/fields/dropdown/monologue_dropdown.tscn @@ -1,16 +1,12 @@ -[gd_scene load_steps=3 format=3 uid="uid://csunin0yg3ay0"] +[gd_scene load_steps=2 format=3 uid="uid://csunin0yg3ay0"] [ext_resource type="Script" uid="uid://ci7ipnx47b6fx" path="res://common/ui/fields/dropdown/monolgue_dropdown.gd" id="1_jtdjo"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="2_ahvj0"] [node name="MonologueDropdown" type="HBoxContainer"] offset_right = 7.0 offset_bottom = 29.0 script = ExtResource("1_jtdjo") -[node name="Label" parent="." instance=ExtResource("2_ahvj0")] -layout_mode = 2 - [node name="OptionButton" type="OptionButton" parent="."] layout_mode = 2 size_flags_horizontal = 3 diff --git a/common/ui/fields/file_picker/file_picker.gd b/common/ui/fields/file_picker/file_picker.gd index 994993ec..7a249e95 100644 --- a/common/ui/fields/file_picker/file_picker.gd +++ b/common/ui/fields/file_picker/file_picker.gd @@ -6,14 +6,9 @@ const IMAGE = ["*.bmp,*.jpg,*.jpeg,*.png,*.svg,*.webp;Image Files"] @export var base_path: String @export var filters: PackedStringArray -@onready var label: Label = $Label -@onready var line_edit: LineEdit = $VBox/HBox/LineEdit -@onready var picker_button: Button = $VBox/HBox/FilePickerButton -@onready var warn_label: Label = $VBox/WarnLabel - - -func set_label_text(text: String) -> void: - label.text = text +@onready var line_edit: LineEdit = $HBox/LineEdit +@onready var picker_button: Button = $HBox/FilePickerButton +@onready var warn_label: Label = $WarnLabel func propagate(value: Variant) -> void: diff --git a/common/ui/fields/file_picker/monologue_file_picker.tscn b/common/ui/fields/file_picker/monologue_file_picker.tscn index 5e7b6f0f..502e53eb 100644 --- a/common/ui/fields/file_picker/monologue_file_picker.tscn +++ b/common/ui/fields/file_picker/monologue_file_picker.tscn @@ -1,35 +1,31 @@ -[gd_scene load_steps=5 format=3 uid="uid://o5dt5106rohh"] +[gd_scene load_steps=4 format=3 uid="uid://o5dt5106rohh"] [ext_resource type="Script" uid="uid://bqlktpo5qx1ps" path="res://common/ui/fields/file_picker/file_picker.gd" id="1_siiu8"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="2_hph74"] [ext_resource type="Texture2D" uid="uid://t1i3wy037vsu" path="res://ui/assets/icons/folder_icon.png" id="2_plad0"] -[sub_resource type="LabelSettings" id="LabelSettings_nr3ee"] +[sub_resource type="LabelSettings" id="LabelSettings_51u22"] font_color = Color(0.768627, 0.180392, 0.25098, 1) -[node name="FilePickerLineEdit" type="HBoxContainer"] -offset_right = 300.0 -offset_bottom = 59.0 -script = ExtResource("1_siiu8") - -[node name="Label" parent="." instance=ExtResource("2_hph74")] -layout_mode = 2 - -[node name="VBox" type="VBoxContainer" parent="."] -layout_mode = 2 +[node name="FilePicker" type="VBoxContainer"] +anchors_preset = -1 +anchor_right = 0.276 +anchor_bottom = 0.111 +offset_right = 0.599976 +offset_bottom = 0.199989 size_flags_horizontal = 3 +script = ExtResource("1_siiu8") -[node name="HBox" type="HBoxContainer" parent="VBox"] +[node name="HBox" type="HBoxContainer" parent="."] layout_mode = 2 theme_override_constants/separation = 0 alignment = 2 -[node name="LineEdit" type="LineEdit" parent="VBox/HBox"] +[node name="LineEdit" type="LineEdit" parent="HBox"] layout_mode = 2 size_flags_horizontal = 3 structured_text_bidi_override = 2 -[node name="FilePickerButton" type="Button" parent="VBox/HBox"] +[node name="FilePickerButton" type="Button" parent="HBox"] custom_minimum_size = Vector2(33, 25) layout_mode = 2 theme_type_variation = &"FlatButton" @@ -37,14 +33,9 @@ icon = ExtResource("2_plad0") icon_alignment = 1 expand_icon = true -[node name="WarnLabel" type="Label" parent="VBox"] +[node name="WarnLabel" type="Label" parent="."] custom_minimum_size = Vector2(100, 0) layout_mode = 2 text = "File path not found!" -label_settings = SubResource("LabelSettings_nr3ee") +label_settings = SubResource("LabelSettings_51u22") autowrap_mode = 2 - -[connection signal="focus_exited" from="VBox/HBox/LineEdit" to="." method="_on_focus_exited"] -[connection signal="text_changed" from="VBox/HBox/LineEdit" to="." method="_on_text_changed"] -[connection signal="text_submitted" from="VBox/HBox/LineEdit" to="." method="_on_text_submitted"] -[connection signal="pressed" from="VBox/HBox/FilePickerButton" to="." method="_on_picker_button_pressed"] diff --git a/common/ui/fields/line_edit/monologue_line_edit.gd b/common/ui/fields/line_edit/monologue_line_edit.gd index 4dcceceb..6814ba96 100644 --- a/common/ui/fields/line_edit/monologue_line_edit.gd +++ b/common/ui/fields/line_edit/monologue_line_edit.gd @@ -11,7 +11,6 @@ var revert_text: String var validator: Callable = func(_text): return true @onready var copy_button = $HBox/InnerVBox/LineEdit/HBoxContainer/CopyButton -@onready var label = $HBox/FieldLabel @onready var line_edit = $HBox/InnerVBox/LineEdit @onready var warning = $HBox/InnerVBox/WarnLabel @onready var note = $NoteLabel @@ -20,7 +19,6 @@ var validator: Callable = func(_text): return true # Called when the node enters the scene tree for the first time. func _ready() -> void: copy_button.visible = copyable - label.add_theme_font_size_override("font_size", font_size) line_edit.add_theme_font_size_override("font_size", font_size) warning.add_theme_font_size_override("font_size", font_size) warning.hide() @@ -28,20 +26,6 @@ func _ready() -> void: note.text = note_text -func set_label_text(text: String) -> void: - if is_sublabel: - label.custom_minimum_size.x = 140 - add_theme_constant_override("margin_left", 25) - label.add_theme_color_override("font_color", Color("858585")) - label.text = sublabel_prefix + text - else: - label.text = text - - -func set_label_visible(can_see: bool) -> void: - label.visible = can_see - - func propagate(value: Variant) -> void: super.propagate(value) line_edit.text = str(value) diff --git a/common/ui/fields/line_edit/monologue_line_edit.tscn b/common/ui/fields/line_edit/monologue_line_edit.tscn index 8c82947a..78236a17 100644 --- a/common/ui/fields/line_edit/monologue_line_edit.tscn +++ b/common/ui/fields/line_edit/monologue_line_edit.tscn @@ -1,8 +1,7 @@ -[gd_scene load_steps=4 format=3 uid="uid://bw7thqdhujl41"] +[gd_scene load_steps=3 format=3 uid="uid://bw7thqdhujl41"] [ext_resource type="Script" uid="uid://cdx8c5vk7f3jb" path="res://common/ui/fields/line_edit/monologue_line_edit.gd" id="1_toqtt"] [ext_resource type="Texture2D" uid="uid://dm2u0xqmmcorj" path="res://ui/assets/icons/copy.png" id="2_lbcco"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="2_v8fwd"] [node name="MonologueLineEdit" type="VBoxContainer"] offset_right = 40.0 @@ -14,9 +13,6 @@ sublabel_prefix = "┗ " [node name="HBox" type="HBoxContainer" parent="."] layout_mode = 2 -[node name="FieldLabel" parent="HBox" instance=ExtResource("2_v8fwd")] -layout_mode = 2 - [node name="InnerVBox" type="VBoxContainer" parent="HBox"] layout_mode = 2 size_flags_horizontal = 3 diff --git a/common/ui/fields/list/monologue_list.gd b/common/ui/fields/list/monologue_list.gd index 7312cfc7..9f750663 100644 --- a/common/ui/fields/list/monologue_list.gd +++ b/common/ui/fields/list/monologue_list.gd @@ -4,8 +4,7 @@ class_name MonologueList extends MonologueField @onready var button := $CollapsibleField/Button @onready var collapsible_container := $CollapsibleField/CollapsibleContainer @onready var vbox := $CollapsibleField/CollapsibleContainer/PanelContainer/VBox -@onready -var field_container := $CollapsibleField/CollapsibleContainer/PanelContainer/VBox/FieldContainer +@onready var field_container := $CollapsibleField/CollapsibleContainer/PanelContainer/VBox/FieldContainer var delete_scene = preload("res://common/ui/buttons/delete_button.tscn") @@ -97,6 +96,10 @@ func set_label_visible(_can_see: bool) -> void: pass +func use_custom_field_label() -> bool: + return true + + func propagate(data: Variant) -> void: super.propagate(data) clear_list() diff --git a/common/ui/fields/monologue_field.gd b/common/ui/fields/monologue_field.gd index 8c24897b..a6b8a127 100644 --- a/common/ui/fields/monologue_field.gd +++ b/common/ui/fields/monologue_field.gd @@ -11,6 +11,7 @@ signal field_updated(value: Variant) var collapsible_field: CollapsibleField: set = set_collapsible_field +var field_label: Label ## Set the collapsible control that this MonologueField belongs to. @@ -19,13 +20,14 @@ func set_collapsible_field(collapsible: CollapsibleField): ## Called by node panel to set field label text, if applicable. -func set_label_text(_text: String) -> void: - pass +func set_label_text(text: String) -> void: + if not field_label: return + field_label.text = text ## Set the field's label visibility. -func set_label_visible(_can_see: bool) -> void: - pass +func set_label_visible(can_see: bool) -> void: + field_label.visible = can_see ## Meant to propagate the value set in [MonologueProperty] to this Field. @@ -33,3 +35,6 @@ func set_label_visible(_can_see: bool) -> void: func propagate(_value: Variant) -> void: if collapsible_field: collapsible_field.open() + +func use_custom_field_label() -> bool: + return false diff --git a/common/ui/fields/portrait_option/portrait_option.gd b/common/ui/fields/portrait_option/portrait_option.gd index 2fd14f54..7ad050f7 100644 --- a/common/ui/fields/portrait_option/portrait_option.gd +++ b/common/ui/fields/portrait_option/portrait_option.gd @@ -96,3 +96,7 @@ func _on_button_gui_input(event: InputEvent) -> void: func _on_line_edit_text_submitted(_new_text: String) -> void: line_edit_unfocus() + + +func use_custom_field_label() -> bool: + return true diff --git a/common/ui/fields/property.gd b/common/ui/fields/property.gd index 24c5b7ba..de3195e7 100644 --- a/common/ui/fields/property.gd +++ b/common/ui/fields/property.gd @@ -37,6 +37,9 @@ var visible: bool: var custom_label: Variant +var field_label := preload("res://common/ui/fields/field_label.tscn") + + func _init( ui_scene: PackedScene, ui_setters: Dictionary = {}, @@ -112,10 +115,20 @@ func show(panel: Control, child_index: int = -1, auto_margin: bool = true) -> Mo field_container.add_theme_constant_override("margin_left", 0) field_container.add_theme_constant_override("margin_top", 0) + var field_box := HBoxContainer.new() + field_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL + field.size_flags_horizontal = Control.SIZE_EXPAND_FILL + field_box.add_theme_constant_override("separation", 0) + if field is MonologueField and not field.use_custom_field_label(): + var label := field_label.instantiate() + field.field_label = label + field_box.add_child(label) + for property in setters.keys(): field.set(property, setters.get(property)) - field_container.add_child(field) + field_box.add_child(field) + field_container.add_child(field_box) panel.add_child(field_container) _check_visibility() diff --git a/common/ui/fields/slider/monologue_slider.gd b/common/ui/fields/slider/monologue_slider.gd index 72547fc3..8a91584f 100644 --- a/common/ui/fields/slider/monologue_slider.gd +++ b/common/ui/fields/slider/monologue_slider.gd @@ -6,10 +6,9 @@ class_name MonologueSlider extends MonologueField @export var step: float @export var suffix: String -@onready var control_label = $FieldLabel -@onready var spin_box = $HBoxContainer/SpinBox -@onready var reset_button = $HBoxContainer/ResetButton -@onready var slider = $HBoxContainer/HSlider +@onready var spin_box = $SpinBox +@onready var reset_button = $ResetButton +@onready var slider = $HSlider var skip_spin_box_update: bool = false @@ -25,10 +24,6 @@ func _ready(): spin_box.suffix = suffix -func set_label_text(text: String) -> void: - control_label.text = text - - func propagate(value: Variant) -> void: super.propagate(value) slider.value = value if (value is float or value is int) else default diff --git a/common/ui/fields/slider/monologue_slider.tscn b/common/ui/fields/slider/monologue_slider.tscn index 5ab0d24e..b0609525 100644 --- a/common/ui/fields/slider/monologue_slider.tscn +++ b/common/ui/fields/slider/monologue_slider.tscn @@ -1,7 +1,6 @@ -[gd_scene load_steps=12 format=3 uid="uid://cndkr1vq6ab1o"] +[gd_scene load_steps=11 format=3 uid="uid://cndkr1vq6ab1o"] [ext_resource type="Script" uid="uid://bfviu4cmsm4jo" path="res://common/ui/fields/slider/monologue_slider.gd" id="1_waj5i"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="2_ntngi"] [sub_resource type="Texture2DRD" id="Texture2DRD_ipp53"] @@ -26,20 +25,14 @@ offset_right = 291.0 offset_bottom = 29.0 script = ExtResource("1_waj5i") -[node name="FieldLabel" parent="." instance=ExtResource("2_ntngi")] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="HSlider" type="HSlider" parent="HBoxContainer"] +[node name="HSlider" type="HSlider" parent="."] custom_minimum_size = Vector2(100, 0) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 4 min_value = -100.0 -[node name="SpinBox" type="SpinBox" parent="HBoxContainer"] +[node name="SpinBox" type="SpinBox" parent="."] custom_minimum_size = Vector2(60, 0) layout_mode = 2 theme_override_constants/set_min_buttons_width_from_icons = 0 @@ -59,14 +52,14 @@ alignment = 1 update_on_text_changed = true select_all_on_focus = true -[node name="ResetButton" type="Button" parent="HBoxContainer"] +[node name="ResetButton" type="Button" parent="."] visible = false layout_mode = 2 size_flags_vertical = 4 theme_type_variation = &"Button_Outline" text = "reset" -[connection signal="drag_ended" from="HBoxContainer/HSlider" to="." method="_on_drag_ended"] -[connection signal="value_changed" from="HBoxContainer/HSlider" to="." method="_on_value_changed"] -[connection signal="value_changed" from="HBoxContainer/SpinBox" to="." method="_on_spin_box_value_changed"] -[connection signal="pressed" from="HBoxContainer/ResetButton" to="." method="_on_reset"] +[connection signal="drag_ended" from="HSlider" to="." method="_on_drag_ended"] +[connection signal="value_changed" from="HSlider" to="." method="_on_value_changed"] +[connection signal="value_changed" from="SpinBox" to="." method="_on_spin_box_value_changed"] +[connection signal="pressed" from="ResetButton" to="." method="_on_reset"] diff --git a/common/ui/fields/spin_box/monologue_spin_box.gd b/common/ui/fields/spin_box/monologue_spin_box.gd index e58b97aa..c0543a9f 100644 --- a/common/ui/fields/spin_box/monologue_spin_box.gd +++ b/common/ui/fields/spin_box/monologue_spin_box.gd @@ -6,7 +6,6 @@ class_name MonologueSpinBox extends MonologueField @export var step: float = 1 @export var suffix: String -@onready var label = $Label @onready var spin_box = $CustomSpinBox @@ -18,10 +17,6 @@ func _ready(): spin_box._update_settings() -func set_label_text(text: String) -> void: - label.text = text - - func propagate(value: Variant) -> void: super.propagate(value) spin_box.value = value if (value is float or value is int) else 0 diff --git a/common/ui/fields/spin_box/monologue_spin_box.tscn b/common/ui/fields/spin_box/monologue_spin_box.tscn index b0fb7313..252b5995 100644 --- a/common/ui/fields/spin_box/monologue_spin_box.tscn +++ b/common/ui/fields/spin_box/monologue_spin_box.tscn @@ -1,15 +1,11 @@ -[gd_scene load_steps=4 format=3 uid="uid://c0d8fac8so0p0"] +[gd_scene load_steps=3 format=3 uid="uid://c0d8fac8so0p0"] [ext_resource type="Script" uid="uid://catei6iaaw77f" path="res://common/ui/fields/spin_box/monologue_spin_box.gd" id="1_wmtop"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="2_1ivi1"] [ext_resource type="PackedScene" uid="uid://wiapsnoaoc44" path="res://common/ui/custom_spinbox/custom_spinbox.tscn" id="3_a42se"] [node name="MonologueSpinBox" type="HBoxContainer"] script = ExtResource("1_wmtop") -[node name="Label" parent="." instance=ExtResource("2_1ivi1")] -layout_mode = 2 - [node name="CustomSpinBox" parent="." instance=ExtResource("3_a42se")] layout_mode = 2 diff --git a/common/ui/fields/text/monologue_text.gd b/common/ui/fields/text/monologue_text.gd index d060898b..2889ba5d 100644 --- a/common/ui/fields/text/monologue_text.gd +++ b/common/ui/fields/text/monologue_text.gd @@ -2,20 +2,15 @@ class_name MonologueText extends MonologueField @export var minimum_size := Vector2(200, 200) -@onready var label = $Label -@onready var text_edit = $HBoxContainer/TextEdit -@onready var expand_container = $HBoxContainer/TextEdit/Button -@onready var expand_button = $HBoxContainer/TextEdit/Button +@onready var text_edit = $TextEdit +@onready var expand_container = $TextEdit/Button +@onready var expand_button = $TextEdit/Button func _ready(): text_edit.custom_minimum_size = minimum_size -func set_label_text(text: String) -> void: - label.text = text - - func propagate(value: Variant) -> void: super.propagate(value) text_edit.text = str(value) diff --git a/common/ui/fields/text/monologue_text.tscn b/common/ui/fields/text/monologue_text.tscn index 1cd7bc50..4b56de7d 100644 --- a/common/ui/fields/text/monologue_text.tscn +++ b/common/ui/fields/text/monologue_text.tscn @@ -1,29 +1,20 @@ -[gd_scene load_steps=4 format=3 uid="uid://durq2yowmkr60"] +[gd_scene load_steps=3 format=3 uid="uid://durq2yowmkr60"] [ext_resource type="Script" uid="uid://laakj3xm565t" path="res://common/ui/fields/text/monologue_text.gd" id="1_m7tlj"] [ext_resource type="Texture2D" uid="uid://bu603ytypk2jb" path="res://ui/assets/icons/expand.svg" id="2_mkdom"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="2_nsrvi"] [node name="MonologueText" type="HBoxContainer"] size_flags_horizontal = 3 script = ExtResource("1_m7tlj") -[node name="Label" parent="." instance=ExtResource("2_nsrvi")] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_constants/separation = 0 - -[node name="TextEdit" type="TextEdit" parent="HBoxContainer"] +[node name="TextEdit" type="TextEdit" parent="."] custom_minimum_size = Vector2(200, 200) layout_mode = 2 size_flags_horizontal = 3 wrap_mode = 1 caret_blink = true -[node name="Button" type="Button" parent="HBoxContainer/TextEdit"] +[node name="Button" type="Button" parent="TextEdit"] custom_minimum_size = Vector2(22, 22) layout_mode = 1 anchors_preset = 1 @@ -35,7 +26,7 @@ offset_right = -2.0 offset_bottom = 22.0 grow_horizontal = 0 -[node name="CenterContainer" type="CenterContainer" parent="HBoxContainer/TextEdit/Button"] +[node name="CenterContainer" type="CenterContainer" parent="TextEdit/Button"] layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 @@ -50,7 +41,7 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 -[node name="TextureRect" type="TextureRect" parent="HBoxContainer/TextEdit/Button/CenterContainer"] +[node name="TextureRect" type="TextureRect" parent="TextEdit/Button/CenterContainer"] custom_minimum_size = Vector2(14, 14) layout_mode = 2 mouse_filter = 2 @@ -58,6 +49,6 @@ texture = ExtResource("2_mkdom") expand_mode = 4 stretch_mode = 5 -[connection signal="focus_exited" from="HBoxContainer/TextEdit" to="." method="_on_focus_exited"] -[connection signal="text_changed" from="HBoxContainer/TextEdit" to="." method="_on_text_changed"] -[connection signal="pressed" from="HBoxContainer/TextEdit/Button" to="." method="_on_button_pressed"] +[connection signal="focus_exited" from="TextEdit" to="." method="_on_focus_exited"] +[connection signal="text_changed" from="TextEdit" to="." method="_on_text_changed"] +[connection signal="pressed" from="TextEdit/Button" to="." method="_on_button_pressed"] diff --git a/common/ui/fields/timeline/monologue_timeline.gd b/common/ui/fields/timeline/monologue_timeline.gd index a2b8b44e..f4b575d5 100644 --- a/common/ui/fields/timeline/monologue_timeline.gd +++ b/common/ui/fields/timeline/monologue_timeline.gd @@ -60,6 +60,10 @@ func propagate(value: Variant) -> void: _from_dict(value) +func use_custom_field_label() -> bool: + return true + + func _from_dict(dict: Dictionary) -> void: _clear() cell_count = dict.get("FrameCount", 1) diff --git a/common/ui/fields/toggle/monologue_toggle.gd b/common/ui/fields/toggle/monologue_toggle.gd index 0146dca8..b9000bc0 100644 --- a/common/ui/fields/toggle/monologue_toggle.gd +++ b/common/ui/fields/toggle/monologue_toggle.gd @@ -1,13 +1,8 @@ class_name MonologueToggle extends MonologueField -@onready var label = $Label @onready var check_button = %CheckButton -func set_label_text(text: String) -> void: - label.text = text - - func propagate(value: Variant) -> void: super.propagate(value) check_button.set_pressed_no_signal(value if value is bool else false) diff --git a/common/ui/fields/toggle/monologue_toggle.tscn b/common/ui/fields/toggle/monologue_toggle.tscn index 76a379ce..60f164e8 100644 --- a/common/ui/fields/toggle/monologue_toggle.tscn +++ b/common/ui/fields/toggle/monologue_toggle.tscn @@ -1,16 +1,12 @@ -[gd_scene load_steps=3 format=3 uid="uid://dh7yuosc0hhpp"] +[gd_scene load_steps=2 format=3 uid="uid://dh7yuosc0hhpp"] [ext_resource type="Script" uid="uid://ctumcy1k8si7o" path="res://common/ui/fields/toggle/monologue_toggle.gd" id="1_0u5l0"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="2_tj6ps"] [node name="MonologueCheckButton" type="HBoxContainer"] offset_right = 43.0 offset_bottom = 29.0 script = ExtResource("1_0u5l0") -[node name="Label" parent="." instance=ExtResource("2_tj6ps")] -layout_mode = 2 - [node name="VBoxContainer" type="VBoxContainer" parent="."] layout_mode = 2 alignment = 1 diff --git a/common/ui/fields/vector/monologue_vector.gd b/common/ui/fields/vector/monologue_vector.gd index 98096304..1f863382 100644 --- a/common/ui/fields/vector/monologue_vector.gd +++ b/common/ui/fields/vector/monologue_vector.gd @@ -4,7 +4,6 @@ class_name MonologueVector extends MonologueField @export var maximum: float = 9999999999 @export var step: float = 1 -@onready var label = $FieldLabel @onready var x_spin_box := %XSpinBox @onready var y_spin_box := %YSpinBox @@ -20,10 +19,6 @@ func _ready() -> void: y_spin_box.step = step -func set_label_text(text: String) -> void: - label.text = text - - func propagate(value: Variant) -> void: super.propagate(value) x_spin_box.value = value[0] if (value[0] is float or value[0] is int) else 0 diff --git a/common/ui/fields/vector/monologue_vector.tscn b/common/ui/fields/vector/monologue_vector.tscn index b7f17d93..cac4ffe1 100644 --- a/common/ui/fields/vector/monologue_vector.tscn +++ b/common/ui/fields/vector/monologue_vector.tscn @@ -1,7 +1,6 @@ -[gd_scene load_steps=4 format=3 uid="uid://5ciquxsl5tw5"] +[gd_scene load_steps=3 format=3 uid="uid://5ciquxsl5tw5"] [ext_resource type="Script" uid="uid://37empx7gweoa" path="res://common/ui/fields/vector/monologue_vector.gd" id="1_qpl0j"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="2_8olwg"] [ext_resource type="PackedScene" uid="uid://wiapsnoaoc44" path="res://common/ui/custom_spinbox/custom_spinbox.tscn" id="2_034ks"] [node name="MonologueVector" type="HBoxContainer"] @@ -9,39 +8,32 @@ offset_right = 282.0 offset_bottom = 29.0 script = ExtResource("1_qpl0j") -[node name="FieldLabel" parent="." instance=ExtResource("2_8olwg")] +[node name="HBoxContainer2" type="HBoxContainer" parent="."] layout_mode = 2 -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer"] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer/HBoxContainer"] +[node name="Label" type="Label" parent="HBoxContainer2"] layout_mode = 2 text = "x" -[node name="XSpinBox" parent="HBoxContainer/HBoxContainer" instance=ExtResource("2_034ks")] +[node name="XSpinBox" parent="HBoxContainer2" instance=ExtResource("2_034ks")] unique_name_in_owner = true layout_mode = 2 suffix = "px" -[node name="VSeparator" type="VSeparator" parent="HBoxContainer"] +[node name="VSeparator" type="VSeparator" parent="."] layout_mode = 2 -[node name="HBoxContainer2" type="HBoxContainer" parent="HBoxContainer"] +[node name="HBoxContainer3" type="HBoxContainer" parent="."] layout_mode = 2 -[node name="Label" type="Label" parent="HBoxContainer/HBoxContainer2"] +[node name="Label" type="Label" parent="HBoxContainer3"] layout_mode = 2 text = "y" -[node name="YSpinBox" parent="HBoxContainer/HBoxContainer2" instance=ExtResource("2_034ks")] +[node name="YSpinBox" parent="HBoxContainer3" instance=ExtResource("2_034ks")] unique_name_in_owner = true layout_mode = 2 suffix = "px" -[connection signal="value_changed" from="HBoxContainer/HBoxContainer/XSpinBox" to="." method="_on_spin_box_value_changed"] -[connection signal="value_changed" from="HBoxContainer/HBoxContainer2/YSpinBox" to="." method="_on_spin_box_value_changed"] +[connection signal="value_changed" from="HBoxContainer2/XSpinBox" to="." method="_on_spin_box_value_changed"] +[connection signal="value_changed" from="HBoxContainer3/YSpinBox" to="." method="_on_spin_box_value_changed"] diff --git a/common/windows/graph_node_picker/graph_node_picker.gd b/common/windows/graph_node_picker/graph_node_picker.gd index f54a5215..6b2a225e 100644 --- a/common/windows/graph_node_picker/graph_node_picker.gd +++ b/common/windows/graph_node_picker/graph_node_picker.gd @@ -1,9 +1,7 @@ class_name GraphNodePicker extends Window ## Reference to the tab switcher so that the picker knows which tab it is in. -@export var switcher: GraphEditSwitcher - -@onready var dimmer := $"../Dimmer" +@onready var switcher := %GraphEditSwitcher ## The node in which the picker was spawned/dragged from. var from_node: String @@ -26,7 +24,7 @@ func _ready(): func _on_enable_picker_mode( node: String = "", port: int = -1, mouse_pos = null, graph_release_pos = null, center_pos = null, center_window: bool = false ): - if switcher.current.file_path and (not dimmer or not dimmer.visible): + if switcher.current.file_path: from_node = node from_port = port release = mouse_pos diff --git a/nodes/choice_node/choice_node.gd b/nodes/choice_node/choice_node.gd index 21c6e801..871dc422 100644 --- a/nodes/choice_node/choice_node.gd +++ b/nodes/choice_node/choice_node.gd @@ -111,7 +111,7 @@ func _from_dict(dict: Dictionary) -> void: func _load_connections(_data: Dictionary, _key: String = "") -> void: - # called after _load_nodes() in MonologueControl, this is used to + # called after _load_nodes() in MonologueEditor, this is used to # load embedded OptionNodes which automatically forms the connections for option_id in _base_id_list: var reference = get_parent().base_options[option_id] diff --git a/scenes/main/app.tscn b/scenes/main/app.tscn index 4263e148..c0ba939e 100644 --- a/scenes/main/app.tscn +++ b/scenes/main/app.tscn @@ -1,14 +1,10 @@ -[gd_scene load_steps=14 format=3 uid="uid://bim51g1aibuw0"] +[gd_scene load_steps=10 format=3 uid="uid://bim51g1aibuw0"] -[ext_resource type="Texture2D" uid="uid://dd8v3hpxxk33d" path="res://ui/assets/icons/logo_white.svg" id="1_1u5fg"] -[ext_resource type="PackedScene" uid="uid://bqjfdabrxujp7" path="res://scenes/main/graph.tscn" id="1_kiov6"] -[ext_resource type="Texture2D" uid="uid://hlck6y4i3l5q" path="res://ui/assets/icons/plus.svg" id="3_gcdaj"] -[ext_resource type="Script" uid="uid://m1wxne1g6kju" path="res://scenes/main/main_menu.gd" id="4_f1t0i"] +[ext_resource type="PackedScene" uid="uid://bqjfdabrxujp7" path="res://scenes/main/editor.tscn" id="1_kiov6"] [ext_resource type="Shader" uid="uid://de1ql8ahad2ts" path="res://common/blur.gdshader" id="5_1u5fg"] [ext_resource type="PackedScene" uid="uid://bcs6s2yuf374j" path="res://common/layouts/expanded_text_edit/expanded_text_edit_container.tscn" id="6_cqyrh"] [ext_resource type="Script" uid="uid://d0xgy6gflipxs" path="res://common/layouts/dimmer/dimmer.gd" id="6_nw038"] [ext_resource type="PackedScene" uid="uid://c7cf7rfwnhf77" path="res://common/layouts/character_edit/character_edit.tscn" id="7_7qdje"] -[ext_resource type="PackedScene" uid="uid://cmpsaafag7cwl" path="res://common/windows/graph_node_picker/graph_node_picker.tscn" id="7_nw038"] [ext_resource type="PackedScene" uid="uid://d3f7d4bb40iht" path="res://common/windows/preview_window/preview_window.tscn" id="8_7qdje"] [ext_resource type="PackedScene" uid="uid://bqqcww601rcx5" path="res://common/windows/welcome_window/welcome_window.tscn" id="9_fp7qq"] [ext_resource type="Script" uid="uid://b2l7hjfyrnr3x" path="res://common/windows/file_dialog/file_dialog.gd" id="10_r77hi"] @@ -44,45 +40,9 @@ theme_type_variation = &"EditorBackground" layout_mode = 2 theme_override_constants/separation = 0 -[node name="CustomTitleBar" type="HBoxContainer" parent="Frame/VBoxContainer"] -custom_minimum_size = Vector2(0, 40) -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="MainPopupMenu" type="MenuButton" parent="Frame/VBoxContainer/CustomTitleBar"] -custom_minimum_size = Vector2(43, 0) -layout_mode = 2 -icon = ExtResource("1_1u5fg") -icon_alignment = 1 -expand_icon = true -script = ExtResource("4_f1t0i") - -[node name="VSeparator" type="VSeparator" parent="Frame/VBoxContainer/CustomTitleBar"] -layout_mode = 2 - -[node name="HBoxContainer2" type="HBoxContainer" parent="Frame/VBoxContainer/CustomTitleBar"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_constants/separation = 0 - -[node name="TabBar" type="TabBar" parent="Frame/VBoxContainer/CustomTitleBar/HBoxContainer2"] -layout_mode = 2 -current_tab = 0 -clip_tabs = false -tab_count = 1 -tab_0/icon = ExtResource("3_gcdaj") - -[node name="HSeparator" type="HSeparator" parent="Frame/VBoxContainer"] -layout_mode = 2 -theme_override_constants/separation = 1 - -[node name="MonologueControl" parent="Frame/VBoxContainer" node_paths=PackedStringArray("welcome_window", "graph_node_picker") instance=ExtResource("1_kiov6")] +[node name="MonologueEditor" parent="Frame/VBoxContainer" node_paths=PackedStringArray("welcome_window") instance=ExtResource("1_kiov6")] layout_mode = 2 welcome_window = NodePath("../../../WelcomeWindow") -graph_node_picker = NodePath("../../../GraphNodePicker") - -[node name="GraphEditSwitcher" parent="Frame/VBoxContainer/MonologueControl/MainContainer/GraphEditsArea" index="0" node_paths=PackedStringArray("tab_bar")] -tab_bar = NodePath("../../../../CustomTitleBar/HBoxContainer2/TabBar") [node name="Dimmer" type="ColorRect" parent="."] visible = false @@ -106,13 +66,6 @@ visible = false z_index = 2 layout_mode = 1 -[node name="GraphNodePicker" parent="." node_paths=PackedStringArray("switcher") instance=ExtResource("7_nw038")] -visible = false -keep_title_visible = false -content_scale_mode = 2 -content_scale_aspect = 1 -switcher = NodePath("../Frame/VBoxContainer/MonologueControl/MainContainer/GraphEditsArea/GraphEditSwitcher") - [node name="PreviewWindow" parent="." instance=ExtResource("8_7qdje")] visible = false @@ -130,4 +83,4 @@ script = ExtResource("10_r77hi") [connection signal="visibility_changed" from="CharacterEditContainer" to="CharacterEditContainer" method="_on_visibility_changed"] [connection signal="file_selected" from="FileDialog" to="FileDialog" method="_on_file_selected"] -[editable path="Frame/VBoxContainer/MonologueControl"] +[editable path="Frame/VBoxContainer/MonologueEditor"] diff --git a/scenes/main/characters_section.gd b/scenes/main/characters_section.gd new file mode 100644 index 00000000..6ee93626 --- /dev/null +++ b/scenes/main/characters_section.gd @@ -0,0 +1,14 @@ +extends VBoxContainer + +var section_icon := preload("res://ui/assets/icons/character.svg") + + +func clear() -> void: + for child in get_children(): + child.queue_free() + + +func load_items(property: Property) -> void: + clear() + property.setters["flat"] = true + property.show(self) diff --git a/scenes/main/characters_section.gd.uid b/scenes/main/characters_section.gd.uid new file mode 100644 index 00000000..40a85d71 --- /dev/null +++ b/scenes/main/characters_section.gd.uid @@ -0,0 +1 @@ +uid://yglbu25x1rsy diff --git a/scenes/main/editor.tscn b/scenes/main/editor.tscn new file mode 100644 index 00000000..3b72e8fc --- /dev/null +++ b/scenes/main/editor.tscn @@ -0,0 +1,293 @@ +[gd_scene load_steps=24 format=3 uid="uid://bqjfdabrxujp7"] + +[ext_resource type="Script" uid="uid://q6eg6rid6xqd" path="res://scenes/main/monologue_editor.gd" id="1_ovo1o"] +[ext_resource type="PackedScene" uid="uid://dfkqf3wjdnj0m" path="res://common/layouts/editor/editor_section.tscn" id="2_q62vd"] +[ext_resource type="Texture2D" uid="uid://dd8v3hpxxk33d" path="res://ui/assets/icons/logo_white.svg" id="3_mei7o"] +[ext_resource type="Script" uid="uid://m1wxne1g6kju" path="res://scenes/main/main_menu.gd" id="4_pwbil"] +[ext_resource type="Texture2D" uid="uid://hlck6y4i3l5q" path="res://ui/assets/icons/plus.svg" id="5_4jbkx"] +[ext_resource type="Script" uid="uid://yglbu25x1rsy" path="res://scenes/main/characters_section.gd" id="6_mei7o"] +[ext_resource type="Script" uid="uid://bmku341x5gaoe" path="res://scenes/main/add_node_button.gd" id="6_sr1k3"] +[ext_resource type="Script" uid="uid://44fcg1a4cs5" path="res://scenes/main/variables_section.gd" id="7_pwbil"] +[ext_resource type="Texture2D" uid="uid://bfmsxfn26cvfn" path="res://ui/assets/icons/character.svg" id="7_xxwqg"] +[ext_resource type="Texture2D" uid="uid://b46sqb5g0spae" path="res://ui/assets/icons/variables.svg" id="8_uvwqm"] +[ext_resource type="PackedScene" uid="uid://cb3se7h7akt47" path="res://common/layouts/language_switcher/language_switcher.tscn" id="9_kkjrq"] +[ext_resource type="Texture2D" uid="uid://d4fesqfd2v8fd" path="res://ui/assets/icons/settings.svg" id="10_svwnc"] +[ext_resource type="Texture2D" uid="uid://dd6wdpndndufl" path="res://ui/assets/icons/sparkles.svg" id="11_g5pcf"] +[ext_resource type="Texture2D" uid="uid://b272tbdmvxj20" path="res://ui/assets/icons/play.svg" id="12_mvfhp"] +[ext_resource type="Script" uid="uid://nxistnt1yhxc" path="res://scenes/main/graph_edit_switcher.gd" id="13_armd7"] +[ext_resource type="PackedScene" uid="uid://dgvhvxdrd58qp" path="res://common/layouts/inspector_panel/inspector_panel.tscn" id="14_craj7"] +[ext_resource type="Script" uid="uid://q0oqx6butjwn" path="res://scenes/main/search_bar_container.gd" id="15_35jwg"] +[ext_resource type="PackedScene" uid="uid://cmpsaafag7cwl" path="res://common/windows/graph_node_picker/graph_node_picker.tscn" id="15_q62vd"] +[ext_resource type="PackedScene" uid="uid://cvum3eaenloix" path="res://common/layouts/search_bar/search_bar.tscn" id="16_n4eqx"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1u3ep"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.186303, 0.183362, 0.215088, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pwbil"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qe3vh"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) +border_width_left = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="GDScript" id="GDScript_lenro"] +script/source = "extends Button + + +func _on_pressed() -> void: + GlobalSignal.emit(\"test_trigger\") +" + +[node name="MonologueEditor" type="Control"] +clip_contents = 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_ovo1o") + +[node name="MainContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 15 + +[node name="VSplitContainer" type="VSplitContainer" parent="MainContainer"] +layout_mode = 2 +size_flags_vertical = 3 +dragging_enabled = false + +[node name="EditorSection" parent="MainContainer/VSplitContainer" instance=ExtResource("2_q62vd")] +visible = true +layout_mode = 2 +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_pwbil") +current_tab = 0 +tabs_visible = false + +[node name="TabBar" type="HBoxContainer" parent="MainContainer/VSplitContainer/EditorSection"] +custom_minimum_size = Vector2(0, 30) +layout_mode = 2 +theme_override_constants/separation = 0 +metadata/_tab_index = 0 + +[node name="MainPopupMenu" type="MenuButton" parent="MainContainer/VSplitContainer/EditorSection/TabBar"] +custom_minimum_size = Vector2(30, 0) +layout_mode = 2 +icon = ExtResource("3_mei7o") +icon_alignment = 1 +expand_icon = true +script = ExtResource("4_pwbil") + +[node name="VSeparator" type="VSeparator" parent="MainContainer/VSplitContainer/EditorSection/TabBar"] +layout_mode = 2 +theme_type_variation = &"VSeparatorGrow" + +[node name="HBox" type="HBoxContainer" parent="MainContainer/VSplitContainer/EditorSection/TabBar"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 0 + +[node name="TabBar" type="TabBar" parent="MainContainer/VSplitContainer/EditorSection/TabBar/HBox"] +unique_name_in_owner = true +layout_mode = 2 +current_tab = 0 +clip_tabs = false +tab_count = 1 +tab_0/icon = ExtResource("5_4jbkx") + +[node name="HSplitContainer" type="HSplitContainer" parent="MainContainer/VSplitContainer"] +layout_mode = 2 +size_flags_vertical = 3 +split_offset = 250 + +[node name="EditorSection" parent="MainContainer/VSplitContainer/HSplitContainer" instance=ExtResource("2_q62vd")] +visible = true +custom_minimum_size = Vector2(250, 0) +layout_mode = 2 +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_qe3vh") +current_tab = 0 + +[node name="Characters" type="VBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/EditorSection"] +layout_mode = 2 +script = ExtResource("6_mei7o") +metadata/_tab_index = 0 + +[node name="Variables" type="VBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/EditorSection"] +visible = false +layout_mode = 2 +script = ExtResource("7_pwbil") +metadata/_tab_index = 1 + +[node name="HSplitContainer" type="HSplitContainer" parent="MainContainer/VSplitContainer/HSplitContainer"] +layout_mode = 2 +size_flags_vertical = 3 +split_offset = -250 + +[node name="GraphEditorSection" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer" instance=ExtResource("2_q62vd")] +visible = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_pwbil") +current_tab = 0 +tabs_visible = false + +[node name="VBoxContainer" type="VBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection"] +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="ToolBar" type="HBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer"] +layout_mode = 2 + +[node name="AddNodeBtn" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +layout_mode = 2 +theme_type_variation = &"Button_Flat" +text = "Add a node..." +script = ExtResource("6_sr1k3") + +[node name="ButtonCharacters" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +visible = false +layout_mode = 2 +theme_type_variation = &"Button_Flat" +icon = ExtResource("7_xxwqg") +icon_alignment = 1 + +[node name="ButtonVariables" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +visible = false +layout_mode = 2 +theme_type_variation = &"Button_Flat" +icon = ExtResource("8_uvwqm") +icon_alignment = 1 + +[node name="LanguageSwitcher" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar" instance=ExtResource("9_kkjrq")] +unique_name_in_owner = true +layout_mode = 2 +theme_type_variation = &"Button_Flat" +disabled = true + +[node name="ButtonSettings" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +visible = false +layout_mode = 2 +theme_type_variation = &"Button_Flat" +icon = ExtResource("10_svwnc") +icon_alignment = 1 + +[node name="ButtonSparkle" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +visible = false +layout_mode = 2 +theme_type_variation = &"Button_Flat" +icon = ExtResource("11_g5pcf") + +[node name="RunButton" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +layout_mode = 2 +theme_type_variation = &"Button_Flat" +icon = ExtResource("12_mvfhp") +script = SubResource("GDScript_lenro") + +[node name="GraphEditSwitcher" type="VBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +mouse_filter = 2 +theme_override_constants/separation = 0 +script = ExtResource("13_armd7") + +[node name="GraphEditZone" type="Control" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/GraphEditSwitcher"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="GraphEdits" type="Control" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/GraphEditSwitcher/GraphEditZone"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_vertical = 3 + +[node name="EditorSection" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer" instance=ExtResource("2_q62vd")] +visible = true +layout_mode = 2 +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_pwbil") +current_tab = 0 +tabs_visible = false + +[node name="InspectorPanel" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/EditorSection" instance=ExtResource("14_craj7")] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +size_flags_horizontal = 3 +metadata/_tab_index = 0 + +[node name="GraphNodePicker" parent="." instance=ExtResource("15_q62vd")] +unique_name_in_owner = true +visible = false +keep_title_visible = false +content_scale_mode = 2 +content_scale_aspect = 1 + +[node name="SearchBarContainer" type="CenterContainer" parent="."] +layout_mode = 1 +anchors_preset = -1 +anchor_right = 1.0 +anchor_bottom = 0.5 +offset_left = 4.0 +offset_top = 4.0 +offset_right = 4.0 +offset_bottom = 4.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +script = ExtResource("15_35jwg") + +[node name="SearchBar" parent="SearchBarContainer" node_paths=PackedStringArray("graph_edit_switcher") instance=ExtResource("16_n4eqx")] +visible = false +layout_mode = 2 +graph_edit_switcher = NodePath("../../MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/GraphEditSwitcher") + +[connection signal="pressed" from="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/AddNodeBtn" to="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/AddNodeBtn" method="_on_pressed"] +[connection signal="pressed" from="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/ButtonSettings" to="." method="_on_button_settings_pressed"] +[connection signal="pressed" from="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/ButtonSparkle" to="." method="_on_button_sparkle_pressed"] +[connection signal="pressed" from="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/RunButton" to="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/RunButton" method="_on_pressed"] diff --git a/scenes/main/graph.tscn b/scenes/main/graph.tscn deleted file mode 100644 index 9f74833e..00000000 --- a/scenes/main/graph.tscn +++ /dev/null @@ -1,229 +0,0 @@ -[gd_scene load_steps=14 format=3 uid="uid://bqjfdabrxujp7"] - -[ext_resource type="Script" uid="uid://q6eg6rid6xqd" path="res://scenes/main/monologue_control.gd" id="1_r00lk"] -[ext_resource type="Texture2D" uid="uid://bfmsxfn26cvfn" path="res://ui/assets/icons/character.svg" id="5_1pnba"] -[ext_resource type="PackedScene" uid="uid://dgvhvxdrd58qp" path="res://common/layouts/side_panel/side_panel.tscn" id="6_3jc6d"] -[ext_resource type="Texture2D" uid="uid://b46sqb5g0spae" path="res://ui/assets/icons/variables.svg" id="6_bfi1l"] -[ext_resource type="Texture2D" uid="uid://dd6wdpndndufl" path="res://ui/assets/icons/sparkles.svg" id="6_l6ili"] -[ext_resource type="PackedScene" uid="uid://cb3se7h7akt47" path="res://common/layouts/language_switcher/language_switcher.tscn" id="6_nqv8k"] -[ext_resource type="Script" uid="uid://bmku341x5gaoe" path="res://scenes/main/add_node_button.gd" id="6_tdvn8"] -[ext_resource type="Script" uid="uid://q0oqx6butjwn" path="res://scenes/main/search_bar_container.gd" id="7_eu0e0"] -[ext_resource type="PackedScene" uid="uid://cvum3eaenloix" path="res://common/layouts/search_bar/search_bar.tscn" id="8_tvorm"] -[ext_resource type="Texture2D" uid="uid://d4fesqfd2v8fd" path="res://ui/assets/icons/settings.svg" id="8_yubrq"] -[ext_resource type="Script" uid="uid://nxistnt1yhxc" path="res://scenes/main/graph_edit_switcher.gd" id="11_k843q"] -[ext_resource type="Texture2D" uid="uid://b272tbdmvxj20" path="res://ui/assets/icons/play.svg" id="15_hn16p"] - -[sub_resource type="GDScript" id="GDScript_lenro"] -script/source = "extends Button - - -func _on_pressed() -> void: - GlobalSignal.emit(\"test_trigger\") -" - -[node name="MonologueControl" type="Control"] -clip_contents = 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_r00lk") - -[node name="MainContainer" type="VBoxContainer" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -theme_override_constants/separation = 15 - -[node name="GraphEditsArea" type="Control" parent="MainContainer"] -layout_mode = 2 -size_flags_vertical = 3 -mouse_filter = 2 - -[node name="GraphEditSwitcher" type="VBoxContainer" parent="MainContainer/GraphEditsArea" node_paths=PackedStringArray("side_panel")] -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 -mouse_filter = 2 -theme_override_constants/separation = 0 -script = ExtResource("11_k843q") -side_panel = NodePath("../MarginContainer/HSplitContainer/SidePanel") - -[node name="GraphEditZone" type="Control" parent="MainContainer/GraphEditsArea/GraphEditSwitcher"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="GraphEdits" type="Control" parent="MainContainer/GraphEditsArea/GraphEditSwitcher/GraphEditZone"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_vertical = 3 - -[node name="MarginContainer" type="MarginContainer" parent="MainContainer/GraphEditsArea"] -layout_mode = 1 -anchors_preset = -1 -anchor_left = 0.5 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 0 -grow_vertical = 2 -mouse_filter = 2 -theme_override_constants/margin_left = 0 -theme_override_constants/margin_top = 0 -theme_override_constants/margin_right = 0 -theme_override_constants/margin_bottom = 0 - -[node name="HSplitContainer" type="HSplitContainer" parent="MainContainer/GraphEditsArea/MarginContainer"] -layout_mode = 2 -mouse_filter = 2 - -[node name="Container" type="Container" parent="MainContainer/GraphEditsArea/MarginContainer/HSplitContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -mouse_filter = 2 - -[node name="SidePanel" parent="MainContainer/GraphEditsArea/MarginContainer/HSplitContainer" instance=ExtResource("6_3jc6d")] -unique_name_in_owner = true -custom_minimum_size = Vector2(450, 0) -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="ToolBarContainer" type="MarginContainer" parent="MainContainer/GraphEditsArea"] -layout_mode = 1 -anchors_preset = 12 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_top = -80.0 -grow_horizontal = 2 -grow_vertical = 0 -mouse_filter = 2 -theme_type_variation = &"MarginContainer_Medium" - -[node name="VBoxContainer" type="VBoxContainer" parent="MainContainer/GraphEditsArea/ToolBarContainer"] -layout_mode = 2 -mouse_filter = 2 -alignment = 2 - -[node name="CenterContainer" type="CenterContainer" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer"] -layout_mode = 2 -mouse_filter = 2 - -[node name="ToolBar" type="HBoxContainer" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer"] -layout_mode = 2 -theme_type_variation = &"HBoxContainer_Medium" - -[node name="Left" type="PanelContainer" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar"] -layout_mode = 2 -mouse_filter = 1 -theme_type_variation = &"OuterPanel" - -[node name="HBoxContainer" type="HBoxContainer" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left"] -layout_mode = 2 -theme_type_variation = &"HBoxContainer_Medium" -alignment = 1 - -[node name="AddNodeBtn" type="Button" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer"] -layout_mode = 2 -theme_type_variation = &"Button_Flat" -text = "Add a node..." -script = ExtResource("6_tdvn8") - -[node name="ButtonCharacters" type="Button" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer"] -visible = false -layout_mode = 2 -theme_type_variation = &"Button_Flat" -icon = ExtResource("5_1pnba") -icon_alignment = 1 - -[node name="ButtonVariables" type="Button" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer"] -visible = false -layout_mode = 2 -theme_type_variation = &"Button_Flat" -icon = ExtResource("6_bfi1l") -icon_alignment = 1 - -[node name="LanguageSwitcher" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer" instance=ExtResource("6_nqv8k")] -unique_name_in_owner = true -layout_mode = 2 -theme_type_variation = &"Button_Flat" -disabled = true - -[node name="ButtonSettings" type="Button" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer"] -layout_mode = 2 -theme_type_variation = &"Button_Flat" -icon = ExtResource("8_yubrq") -icon_alignment = 1 - -[node name="ButtonSparkle" type="Button" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer"] -visible = false -layout_mode = 2 -theme_type_variation = &"Button_Flat" -icon = ExtResource("6_l6ili") - -[node name="Right" type="PanelContainer" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar"] -layout_mode = 2 -mouse_filter = 1 -theme_type_variation = &"OuterPanel" - -[node name="HBoxContainer" type="HBoxContainer" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Right"] -layout_mode = 2 -theme_override_constants/separation = 0 -alignment = 1 - -[node name="RunButton" type="Button" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Right/HBoxContainer"] -custom_minimum_size = Vector2(34, 34) -layout_mode = 2 -theme_type_variation = &"Button_Flat" -script = SubResource("GDScript_lenro") - -[node name="CenterContainer" type="CenterContainer" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Right/HBoxContainer/RunButton"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -rotation = -0.00318988 - -[node name="TextureRect" type="TextureRect" parent="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Right/HBoxContainer/RunButton/CenterContainer"] -custom_minimum_size = Vector2(24, 24) -layout_mode = 2 -tooltip_text = "Run the project from the RootNode." -texture = ExtResource("15_hn16p") -expand_mode = 3 -stretch_mode = 5 - -[node name="SearchBarContainer" type="CenterContainer" parent="MainContainer/GraphEditsArea"] -layout_mode = 1 -anchors_preset = -1 -anchor_right = 1.0 -anchor_bottom = 0.5 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 -script = ExtResource("7_eu0e0") - -[node name="SearchBar" parent="MainContainer/GraphEditsArea/SearchBarContainer" node_paths=PackedStringArray("graph_edit_switcher") instance=ExtResource("8_tvorm")] -visible = false -layout_mode = 2 -graph_edit_switcher = NodePath("../../GraphEditSwitcher") - -[connection signal="pressed" from="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer/AddNodeBtn" to="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer/AddNodeBtn" method="_on_pressed"] -[connection signal="pressed" from="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer/ButtonSettings" to="." method="_on_button_settings_pressed"] -[connection signal="pressed" from="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Left/HBoxContainer/ButtonSparkle" to="." method="_on_button_sparkle_pressed"] -[connection signal="pressed" from="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Right/HBoxContainer/RunButton" to="MainContainer/GraphEditsArea/ToolBarContainer/VBoxContainer/CenterContainer/ToolBar/Right/HBoxContainer/RunButton" method="_on_pressed"] diff --git a/scenes/main/graph_edit_switcher.gd b/scenes/main/graph_edit_switcher.gd index ac523fc3..da2264bf 100644 --- a/scenes/main/graph_edit_switcher.gd +++ b/scenes/main/graph_edit_switcher.gd @@ -1,13 +1,8 @@ ## Consists of a TabBar which allows the user to switch between GraphEdits. -## Saving and loading of GraphEdit data is handled by MonologueControl. +## Saving and loading of GraphEdit data is handled by MonologueEditor. class_name GraphEditSwitcher extends VBoxContainer -## Reference to the side panel control to connect graph edits to. -@export var side_panel: SidePanel -## Reference to the tab bar for switching graph edits. -@export var tab_bar: TabBar - var current: MonologueGraphEdit: get = get_current_graph_edit var graph_edit_scene = preload("res://common/layouts/graph_edit/monologue_graph_edit.tscn") var is_closing_all_tabs: bool @@ -17,6 +12,8 @@ var root_scene = Constants.NODE_SCENES.get("Root") var last_selected_tab: int = 0 var prevent_switching: bool = false +@onready var inspector_panel: InspectorPanel = %InspectorPanel +@onready var tab_bar: TabBar = %TabBar @onready var graph_edits: Control = $GraphEditZone/GraphEdits @@ -35,7 +32,7 @@ func _input(event: InputEvent) -> void: current.trigger_redo() elif event.is_action_pressed("Undo"): current.trigger_undo() - elif event.is_action_pressed("Delete") and not side_panel.visible: + elif event.is_action_pressed("Delete") and not inspector_panel.visible: current.trigger_delete() @@ -54,14 +51,14 @@ func add_tab(filename: String) -> void: tab_bar.current_tab = tab_bar.tab_count - 2 -func connect_side_panel(graph_edit: MonologueGraphEdit) -> void: - graph_edit.connect("node_selected", side_panel.on_graph_node_selected) - graph_edit.connect("node_deselected", side_panel.on_graph_node_deselected) +func connect_inspector_panel(graph_edit: MonologueGraphEdit) -> void: + graph_edit.connect("node_selected", inspector_panel.on_graph_node_selected) + graph_edit.connect("node_deselected", inspector_panel.on_graph_node_deselected) graph_edit.undo_redo.connect("version_changed", update_save_state) -func commit_side_panel(node: MonologueGraphNode) -> void: - side_panel.refocus(node) +func commit_inspector_panel(node: MonologueGraphNode) -> void: + inspector_panel.refocus(node) func get_current_graph_edit() -> MonologueGraphEdit: @@ -81,7 +78,7 @@ func new_graph_edit() -> MonologueGraphEdit: var root_node = root_scene.instantiate() graph_edit.add_child(root_node) - connect_side_panel(graph_edit) + connect_inspector_panel(graph_edit) graph_edits.add_child(graph_edit) for ge in graph_edits.get_children(): @@ -165,9 +162,7 @@ func _on_tab_changed(tab: int) -> void: ge.visible = true GlobalSignal.emit("load_languages", [ge.languages, ge]) if ge.active_graphnode: - side_panel.on_graph_node_selected(ge.active_graphnode, true) - else: - side_panel.hide() + inspector_panel.on_graph_node_selected(ge.active_graphnode, true) else: ge.visible = false last_selected_tab = tab @@ -176,4 +171,3 @@ func _on_tab_changed(tab: int) -> void: pending_new_graph = new_graph_edit() GlobalSignal.emit("show_welcome") GlobalSignal.emit("disable_language_switcher") - side_panel.hide() diff --git a/scenes/main/monologue_control.gd b/scenes/main/monologue_editor.gd similarity index 90% rename from scenes/main/monologue_control.gd rename to scenes/main/monologue_editor.gd index 27f5a4b2..51082c87 100644 --- a/scenes/main/monologue_control.gd +++ b/scenes/main/monologue_editor.gd @@ -1,10 +1,10 @@ -class_name MonologueControl extends Control +class_name MonologueEditor extends Control @export var welcome_window: WelcomeWindow -@export var graph_node_picker: GraphNodePicker +@onready var graph_node_picker: GraphNodePicker = %GraphNodePicker @onready var graph_switcher: GraphEditSwitcher = %GraphEditSwitcher -@onready var side_panel_node: SidePanel = %SidePanel +@onready var inspector_panel_node: InspectorPanel = %InspectorPanel @onready var run_window := preload("res://scenes/run/run_window.tscn") @onready var dimmer := $"../../../Dimmer" @@ -40,7 +40,7 @@ func _to_dict() -> Dictionary: # if side panel is still open, release the focus so that some # text controls trigger the focus_exited() signal to update - if side_panel_node.visible and side_panel_node.selected_node == node: + if inspector_panel_node.visible and inspector_panel_node.selected_node == node: var refocus = get_viewport().gui_get_focus_owner() if refocus: refocus.release_focus() @@ -116,21 +116,20 @@ func load_project(path: String, new_graph: bool = false) -> void: _connect_nodes(node_list) graph_switcher.add_root() graph_switcher.current.update_node_positions() - graph_switcher.current.grab_focus() GlobalSignal.emit("load_successful", [path]) -## Reload the current graph edit and side panel values. +## Reload the current graph edit and inspector panel. func refresh(node: MonologueGraphNode = null, affected_properties: PackedStringArray = []) -> void: # if there is a given node, refresh only the parts that were specified if node: node.reload_preview() if node is not OptionNode: node._update.call_deferred() - if side_panel_node.visible: + if inspector_panel_node.visible: # actual property value updates are handled by PropertyHistory for property_name in affected_properties: - var field = side_panel_node.collapsibles.get(property_name) + var field = inspector_panel_node.collapsibles.get(property_name) if is_instance_valid(field): field.open() else: @@ -141,9 +140,11 @@ func refresh(node: MonologueGraphNode = null, affected_properties: PackedStringA for each_node in graph_switcher.current.get_nodes(): each_node.reload_preview() #each_node._update.call_deferred() - if side_panel_node.visible: - var current_node = side_panel_node.selected_node - side_panel_node.on_graph_node_selected(current_node, true) + if inspector_panel_node.visible: + var current_node = inspector_panel_node.selected_node + inspector_panel_node.on_graph_node_selected(current_node, true) + + $MainContainer/VSplitContainer/HSplitContainer/EditorSection/Characters.load_items(graph_switcher.current.get_root_node().characters) func save(): diff --git a/scenes/main/monologue_control.gd.uid b/scenes/main/monologue_editor.gd.uid similarity index 100% rename from scenes/main/monologue_control.gd.uid rename to scenes/main/monologue_editor.gd.uid diff --git a/scenes/main/variables_section.gd b/scenes/main/variables_section.gd new file mode 100644 index 00000000..a2da93cf --- /dev/null +++ b/scenes/main/variables_section.gd @@ -0,0 +1,3 @@ +extends VBoxContainer + +var section_icon := preload("res://ui/assets/icons/variables.svg") diff --git a/scenes/main/variables_section.gd.uid b/scenes/main/variables_section.gd.uid new file mode 100644 index 00000000..8bc10a17 --- /dev/null +++ b/scenes/main/variables_section.gd.uid @@ -0,0 +1 @@ +uid://44fcg1a4cs5 diff --git a/ui/assets/icons/object.svg b/ui/assets/icons/object.svg new file mode 100644 index 00000000..a709768e --- /dev/null +++ b/ui/assets/icons/object.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/assets/icons/object.svg.import b/ui/assets/icons/object.svg.import new file mode 100644 index 00000000..2a3bccb6 --- /dev/null +++ b/ui/assets/icons/object.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxyieo7jnlvkm" +path="res://.godot/imported/object.svg-e3a0713805ea8ed4ba90e3101bf0d51d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ui/assets/icons/object.svg" +dest_files=["res://.godot/imported/object.svg-e3a0713805ea8ed4ba90e3101bf0d51d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/ui/theme_default/main.tres b/ui/theme_default/main.tres index 774210c9..91777c64 100644 --- a/ui/theme_default/main.tres +++ b/ui/theme_default/main.tres @@ -1,4 +1,4 @@ -[gd_resource type="Theme" script_class="MonologueTheme" load_steps=92 format=3 uid="uid://budfhk1hudcfj"] +[gd_resource type="Theme" script_class="MonologueTheme" load_steps=103 format=3 uid="uid://budfhk1hudcfj"] [ext_resource type="Texture2D" uid="uid://cfk4fuhncchxg" path="res://ui/theme_default/assets/checked.svg" id="1_ks7w5"] [ext_resource type="Texture2D" uid="uid://dsb6opwewlipc" path="res://ui/assets/icons/slot.svg" id="1_yjacf"] @@ -17,396 +17,499 @@ [ext_resource type="Texture2D" uid="uid://br60sxpbxl5hm" path="res://ui/theme_default/assets/radio_unchecked_disabled.svg" id="10_ks7w5"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ks7w5"] -content_margin_left = 8.0 +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 -bg_color = Color(0.403922, 0.384314, 0.470588, 0.05) +bg_color = Color(0.662745, 0.658824, 0.752941, 0.05) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uqgmq"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rb0lk"] -content_margin_left = 8.0 +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 -bg_color = Color(0.403922, 0.384314, 0.470588, 0.2) +bg_color = Color(0.662745, 0.658824, 0.752941, 0.2) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_30fyb"] -content_margin_left = 8.0 +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 -bg_color = Color(0.403922, 0.384314, 0.470588, 0.075) +bg_color = Color(0.662745, 0.658824, 0.752941, 0.075) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hxw5v"] -content_margin_left = 8.0 +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 -bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) +bg_color = Color(0.662745, 0.658824, 0.752941, 0.15) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jwwfe"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 bg_color = Color(0.819608, 0.313726, 0.313726, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_isxcr"] -content_margin_left = 8.0 +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 bg_color = Color(0.768627, 0.180392, 0.25098, 0.05) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pnw2l"] -content_margin_left = 8.0 +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 bg_color = Color(0.768627, 0.180392, 0.25098, 0.2) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qr2ui"] -content_margin_left = 8.0 +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 bg_color = Color(0.768627, 0.180392, 0.25098, 0.25) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nxpu0"] -content_margin_left = 8.0 +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 bg_color = Color(0.768627, 0.180392, 0.25098, 0.15) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8t50g"] content_margin_left = 0.0 content_margin_top = 0.0 content_margin_right = 0.0 content_margin_bottom = 0.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_00o8r"] content_margin_left = 0.0 content_margin_top = 0.0 content_margin_right = 0.0 content_margin_bottom = 0.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_a5c6f"] content_margin_left = 0.0 content_margin_top = 0.0 content_margin_right = 0.0 content_margin_bottom = 0.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_vsni6"] content_margin_left = 0.0 content_margin_top = 0.0 content_margin_right = 0.0 content_margin_bottom = 0.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2fovc"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.403922, 0.384314, 0.470588, 0.075) +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.662745, 0.658824, 0.752941, 0.075) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_25yn7"] -content_margin_left = 0.0 -content_margin_top = 0.0 -content_margin_right = 0.0 -content_margin_bottom = 0.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_vvgve"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_width_left = 1 -border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.819608, 0.313726, 0.313726, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7fun3"] -content_margin_left = 0.0 -content_margin_top = 0.0 -content_margin_right = 0.0 -content_margin_bottom = 0.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -expand_margin_left = 7.0 -expand_margin_top = 8.0 -expand_margin_right = 8.0 -expand_margin_bottom = 8.0 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wlc2a"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) +draw_center = false +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0ta60"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) +border_width_left = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.819608, 0.313726, 0.313726, 1) +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1uy7d"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) +border_width_left = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6hq17"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) +border_width_top = 1 +border_color = Color(1, 1, 1, 0) +corner_radius_top_left = 2 +corner_radius_top_right = 2 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8qegs"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.186303, 0.183362, 0.215088, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_color = Color(0.819608, 0.313726, 0.313726, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qd482"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.186303, 0.183362, 0.215088, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_85xuj"] content_margin_left = 8.0 content_margin_top = 8.0 content_margin_right = 8.0 content_margin_bottom = 8.0 -bg_color = Color(0.403922, 0.384314, 0.470588, 0.1) +bg_color = Color(0.1479, 0.1479, 0.17, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qccfg"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.662745, 0.658824, 0.752941, 0.1) border_width_left = 1 border_width_top = 1 border_width_right = 1 border_width_bottom = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.25) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0ta60"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_a8gsr"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 bg_color = Color(1, 1, 1, 0) border_width_left = 1 border_width_top = 1 border_width_right = 1 border_width_bottom = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.15) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1uy7d"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8epah"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 bg_color = Color(1, 1, 1, 0) border_width_left = 1 border_width_top = 1 border_width_right = 1 border_width_bottom = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.15) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6hq17"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j2kio"] content_margin_left = 0.0 content_margin_top = 0.0 content_margin_right = 0.0 content_margin_bottom = 0.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +bg_color = Color(0.186303, 0.183362, 0.215088, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8qegs"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q56mj"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_width_left = 1 border_width_right = 1 border_width_bottom = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 shadow_color = Color(0, 0, 0, 0.15) shadow_size = 30 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qd482"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hvbmk"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_width_left = 1 border_width_right = 1 border_width_bottom = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.3) -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 shadow_color = Color(0, 0, 0, 0.15) shadow_size = 30 -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_85xuj"] +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_lvjcx"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qccfg"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qi7n2"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) border_width_left = 1 border_width_top = 1 border_width_right = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 shadow_color = Color(0, 0, 0, 0.15) shadow_size = 30 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_a8gsr"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kp0xv"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) border_width_left = 1 border_width_top = 1 border_width_right = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.3) -corner_radius_top_left = 6 -corner_radius_top_right = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 shadow_color = Color(0, 0, 0, 0.15) shadow_size = 30 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8epah"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sdjsm"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_j2kio"] +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_eudc8"] texture = ExtResource("6_6ojxv") texture_margin_top = 1.0 axis_stretch_horizontal = 2 axis_stretch_vertical = 2 modulate_color = Color(0.890196, 0.894118, 0.921569, 0.2) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q56mj"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.324235, 0.324549, 0.349333, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xm1xm"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.358141, 0.358454, 0.390267, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) corner_radius_top_left = 5 corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hvbmk"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qhjh4"] content_margin_left = 2.0 content_margin_top = 2.0 content_margin_right = 2.0 content_margin_bottom = 2.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false -border_color = Color(0.324235, 0.324549, 0.349333, 1) +border_color = Color(0.358141, 0.358454, 0.390267, 1) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lvjcx"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_txkog"] content_margin_left = 2.0 content_margin_top = 2.0 content_margin_right = 2.0 content_margin_bottom = 2.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) -border_color = Color(0.324235, 0.324549, 0.349333, 1) +bg_color = Color(0.1479, 0.1479, 0.17, 1) +border_color = Color(0.358141, 0.358454, 0.390267, 1) -[sub_resource type="StyleBoxLine" id="StyleBoxLine_qi7n2"] +[sub_resource type="StyleBoxLine" id="StyleBoxLine_axeq1"] color = Color(0.890196, 0.894118, 0.921569, 0.2) grow_begin = 0.0 grow_end = 0.0 -[sub_resource type="StyleBoxLine" id="StyleBoxLine_kp0xv"] +[sub_resource type="StyleBoxLine" id="StyleBoxLine_qd60x"] color = Color(0.890196, 0.894118, 0.921569, 0.2) -grow_begin = 8.0 -grow_end = 8.0 +grow_begin = 4.0 +grow_end = 4.0 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sdjsm"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_poo5t"] content_margin_top = 5.0 bg_color = Color(0.819608, 0.313726, 0.313726, 1) corner_radius_top_left = 5 @@ -414,7 +517,7 @@ corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_eudc8"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_blikl"] content_margin_top = 5.0 bg_color = Color(0.662745, 0.658824, 0.752941, 0.15) corner_radius_top_left = 5 @@ -422,269 +525,293 @@ corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xm1xm"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) -draw_center = false +[sub_resource type="Texture2D" id="Texture2D_v58ty"] +resource_local_to_scene = false +resource_name = "" + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yj5pq"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qhjh4"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y6go5"] content_margin_left = 0.0 content_margin_top = 0.0 content_margin_right = 0.0 content_margin_bottom = 0.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) -draw_center = false +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +expand_margin_left = 3.0 +expand_margin_top = 4.0 +expand_margin_right = 4.0 +expand_margin_bottom = 4.0 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_txkog"] -content_margin_left = 8.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kik2p"] +content_margin_left = 4.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 4.0 content_margin_bottom = 4.0 -bg_color = Color(0.662745, 0.658824, 0.752941, 0.05) +bg_color = Color(0.1479, 0.1479, 0.17, 1) +draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_axeq1"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 -bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) -draw_center = false +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_huxsq"] +content_margin_left = 0.0 +content_margin_top = 0.0 +content_margin_right = 0.0 +content_margin_bottom = 0.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) +draw_center = false +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6yqil"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 +bg_color = Color(0.662745, 0.658824, 0.752941, 0.05) +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j1x70"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 +bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) +draw_center = false border_width_left = 1 border_width_top = 1 border_width_right = 1 border_width_bottom = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qd60x"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5eo3a"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_poo5t"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_l5wh6"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 bg_color = Color(0.662745, 0.658824, 0.752941, 0.05) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_blikl"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c4a6a"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) border_width_left = 1 border_width_top = 1 border_width_right = 1 border_width_bottom = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v58ty"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xlkkc"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yj5pq"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qdp3q"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y6go5"] -content_margin_left = 8.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s45bh"] +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.05) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kik2p"] -content_margin_left = 8.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_raufu"] +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.2) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_huxsq"] -content_margin_left = 8.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e7orh"] +content_margin_left = 6.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 6.0 content_margin_bottom = 4.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.25) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6yqil"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_eggpx"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j1x70"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tm0m6"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_width_left = 1 border_width_top = 1 border_width_right = 1 border_width_bottom = 1 -border_color = Color(0.256471, 0.257255, 0.272157, 1) -corner_radius_top_left = 14 -corner_radius_top_right = 14 -corner_radius_bottom_right = 14 -corner_radius_bottom_left = 14 +border_color = Color(0.296359, 0.297144, 0.320314, 1) +corner_radius_top_left = 7 +corner_radius_top_right = 7 +corner_radius_bottom_right = 7 +corner_radius_bottom_left = 7 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5eo3a"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) -border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_l5wh6"] -content_margin_left = 8.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c1m33"] +content_margin_left = 4.0 content_margin_top = 4.0 -content_margin_right = 8.0 +content_margin_right = 4.0 content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3f21d"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c4a6a"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j02or"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_width_left = 1 border_width_top = 1 border_width_right = 1 border_width_bottom = 1 -border_color = Color(0.256471, 0.257255, 0.272157, 1) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +border_color = Color(0.296359, 0.297144, 0.320314, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxLine" id="StyleBoxLine_xlkkc"] +[sub_resource type="StyleBoxLine" id="StyleBoxLine_78rkj"] color = Color(0.890196, 0.894118, 0.921569, 1) grow_begin = 0.0 grow_end = 0.0 vertical = true -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qdp3q"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_61iib"] +content_margin_left = 2.0 +content_margin_top = 2.0 +content_margin_right = 2.0 +content_margin_bottom = 2.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s45bh"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ydnyu"] +content_margin_left = 2.0 +content_margin_top = 2.0 +content_margin_right = 2.0 +content_margin_bottom = 2.0 bg_color = Color(0.662745, 0.658824, 0.752941, 0.15) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_raufu"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_onp0e"] +content_margin_left = 2.0 +content_margin_top = 2.0 +content_margin_right = 2.0 +content_margin_bottom = 2.0 bg_color = Color(0.662745, 0.658824, 0.752941, 0.15) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e7orh"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_olm6c"] content_margin_left = 0.0 content_margin_top = 0.0 content_margin_right = 0.0 @@ -693,7 +820,7 @@ bg_color = Color(0.662745, 0.658824, 0.752941, 0.15) draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_eggpx"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k4pvp"] content_margin_left = 0.0 content_margin_top = 0.0 content_margin_right = 0.0 @@ -701,81 +828,81 @@ content_margin_bottom = 0.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tm0m6"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bdyw7"] content_margin_left = 0.0 content_margin_top = 0.0 content_margin_right = 0.0 content_margin_bottom = 0.0 bg_color = Color(0.662745, 0.658824, 0.752941, 0.075) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_c1m33"] +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_5aedr"] -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_3f21d"] +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ibb5s"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j02or"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_341sr"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_width_right = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_78rkj"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xrd2a"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_width_right = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_61iib"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_muvro"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_width_right = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ydnyu"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tafav"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 bg_color = Color(0.819608, 0.313726, 0.313726, 1) border_width_right = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_onp0e"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hcwh5"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_width_right = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_olm6c"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_20gek"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) draw_center = false border_width_left = 1 @@ -783,92 +910,96 @@ border_width_top = 1 border_width_right = 1 border_width_bottom = 1 border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k4pvp"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wxysu"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 bg_color = Color(0.403922, 0.384314, 0.470588, 0.15) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bdyw7"] -content_margin_left = 8.0 -content_margin_top = 4.0 -content_margin_right = 8.0 -content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bb1tf"] +content_margin_left = 4.0 +content_margin_top = 2.0 +content_margin_right = 4.0 +content_margin_bottom = 2.0 bg_color = Color(0.662745, 0.658824, 0.752941, 0.05) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5aedr"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bn5ll"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_width_right = 1 border_color = Color(0, 0, 0, 1) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ibb5s"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_l4tsf"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_width_bottom = 1 border_color = Color(0, 0, 0, 1) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_341sr"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cfrtq"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) draw_center = false border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xrd2a"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.0980392, 0.0980392, 0.109804, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wl3tv"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1479, 0.1479, 0.17, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 6 -corner_radius_top_right = 6 -corner_radius_bottom_right = 6 -corner_radius_bottom_left = 6 +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_muvro"] -content_margin_left = 8.0 -content_margin_top = 8.0 -content_margin_right = 8.0 -content_margin_bottom = 8.0 -bg_color = Color(0.182745, 0.182157, 0.206275, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nj65x"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -[sub_resource type="StyleBoxLine" id="StyleBoxLine_tafav"] +[sub_resource type="StyleBoxLine" id="StyleBoxLine_wlq5o"] color = Color(0.890196, 0.894118, 0.921569, 0.2) grow_begin = 0.0 grow_end = 0.0 vertical = true +[sub_resource type="Texture2D" id="Texture2D_l8c38"] +resource_local_to_scene = false +resource_name = "" + [resource] Button/colors/font_color = Color(0.890196, 0.894118, 0.921569, 0.8) Button/colors/font_disabled_color = Color(0.890196, 0.894118, 0.921569, 0.3) @@ -878,6 +1009,8 @@ Button/colors/font_hover_pressed_color = Color(0.890196, 0.894118, 0.921569, 1) Button/colors/font_pressed_color = Color(0.890196, 0.894118, 0.921569, 1) Button/colors/icon_disabled_color = Color(0.890196, 0.894118, 0.921569, 0.3) Button/colors/icon_normal_color = Color(0.890196, 0.894118, 0.921569, 0.8) +Button/constants/h_separation = 4 +Button/constants/icon_max_width = 15 Button/constants/outline_size = 0 Button/styles/disabled = SubResource("StyleBoxFlat_ks7w5") Button/styles/disabled_mirrored = SubResource("StyleBoxFlat_ks7w5") @@ -917,7 +1050,7 @@ ButtonWarning/styles/pressed = SubResource("StyleBoxFlat_qr2ui") ButtonWarning/styles/pressed_mirrored = SubResource("StyleBoxFlat_qr2ui") CheckBox/colors/font_hover_pressed_color = Color(0.890196, 0.894118, 0.921569, 1) CheckBox/colors/font_pressed_color = Color(0.890196, 0.894118, 0.921569, 0.7) -CheckBox/constants/h_separation = 8 +CheckBox/constants/h_separation = 4 CheckBox/icons/checked = ExtResource("1_ks7w5") CheckBox/icons/checked_disabled = ExtResource("2_uqgmq") CheckBox/icons/radio_checked = ExtResource("7_tydhh") @@ -957,12 +1090,28 @@ CollapsibleFieldPanel/base_type = &"PanelContainer" CollapsibleFieldPanel/styles/panel = SubResource("StyleBoxFlat_2fovc") EditorBackground/base_type = &"PanelContainer" EditorBackground/styles/panel = SubResource("StyleBoxFlat_25yn7") -EditorSidePanel/base_type = &"PanelContainer" -EditorSidePanel/styles/panel = SubResource("StyleBoxFlat_vvgve") -EditorSidePanelTopBox/base_type = &"PanelContainer" -EditorSidePanelTopBox/styles/panel = SubResource("StyleBoxFlat_7fun3") +EditorSection/base_type = &"TabContainer" +EditorSection/colors/font_disabled_color = Color(0.890196, 0.894118, 0.921569, 0.3) +EditorSection/colors/font_hover_color = Color(0.890196, 0.894118, 0.921569, 1) +EditorSection/colors/font_selected_color = Color(0.890196, 0.894118, 0.921569, 1) +EditorSection/colors/font_unselected_color = Color(0.890196, 0.894118, 0.921569, 0.8) +EditorSection/constants/side_margin = 1 +EditorSection/font_sizes/font_size = 14 +EditorSection/styles/panel_focus = SubResource("StyleBoxFlat_vvgve") +EditorSection/styles/panel_unfocus = SubResource("StyleBoxFlat_7fun3") +EditorSection/styles/tab_disabled = SubResource("StyleBoxFlat_wlc2a") +EditorSection/styles/tab_focus = SubResource("StyleBoxFlat_wlc2a") +EditorSection/styles/tab_hovered = SubResource("StyleBoxFlat_wlc2a") +EditorSection/styles/tab_panel_focus = SubResource("StyleBoxFlat_0ta60") +EditorSection/styles/tab_panel_unfocus = SubResource("StyleBoxFlat_1uy7d") +EditorSection/styles/tab_selected = SubResource("StyleBoxFlat_6hq17") +EditorSection/styles/tab_unselected = SubResource("StyleBoxFlat_wlc2a") +EditorSection/styles/tabbar_background_focus = SubResource("StyleBoxFlat_8qegs") +EditorSection/styles/tabbar_background_unfocus = SubResource("StyleBoxFlat_qd482") FieldContainer/base_type = &"VBoxContainer" -FieldContainer/constants/separation = 4 +FieldContainer/constants/separation = 2 +FieldPanel/base_type = &"PanelContainer" +FieldPanel/styles/panel = SubResource("StyleBoxFlat_85xuj") FlatButton/colors/font_color = Color(0.890196, 0.894118, 0.921569, 0.8) FlatButton/colors/font_disabled_color = Color(0.890196, 0.894118, 0.921569, 0.3) FlatButton/colors/font_focus_color = Color(0.890196, 0.894118, 0.921569, 1) @@ -974,90 +1123,96 @@ FlatButton/colors/icon_normal_color = Color(0.890196, 0.894118, 0.921569, 0.8) FlatButton/constants/outline_size = 0 FlatButton/styles/disabled = SubResource("StyleBoxFlat_ks7w5") FlatButton/styles/disabled_mirrored = SubResource("StyleBoxFlat_ks7w5") -FlatButton/styles/hover = SubResource("StyleBoxFlat_wlc2a") -FlatButton/styles/hover_mirrored = SubResource("StyleBoxFlat_wlc2a") -FlatButton/styles/hover_pressed = SubResource("StyleBoxFlat_0ta60") -FlatButton/styles/hover_pressed_mirrored = SubResource("StyleBoxFlat_0ta60") -FlatButton/styles/normal = SubResource("StyleBoxFlat_1uy7d") -FlatButton/styles/normal_mirrored = SubResource("StyleBoxFlat_1uy7d") -FlatButton/styles/pressed = SubResource("StyleBoxFlat_0ta60") -FlatButton/styles/pressed_mirrored = SubResource("StyleBoxFlat_0ta60") +FlatButton/styles/hover = SubResource("StyleBoxFlat_qccfg") +FlatButton/styles/hover_mirrored = SubResource("StyleBoxFlat_qccfg") +FlatButton/styles/hover_pressed = SubResource("StyleBoxFlat_a8gsr") +FlatButton/styles/hover_pressed_mirrored = SubResource("StyleBoxFlat_a8gsr") +FlatButton/styles/normal = SubResource("StyleBoxFlat_8epah") +FlatButton/styles/normal_mirrored = SubResource("StyleBoxFlat_8epah") +FlatButton/styles/pressed = SubResource("StyleBoxFlat_a8gsr") +FlatButton/styles/pressed_mirrored = SubResource("StyleBoxFlat_a8gsr") GraphEdit/colors/grid_major = Color(0.890196, 0.894118, 0.921569, 0.15) GraphEdit/colors/grid_minor = Color(0.890196, 0.894118, 0.921569, 0.15) -GraphEdit/styles/panel = SubResource("StyleBoxFlat_6hq17") -GraphNode/constants/separation = 8 +GraphEdit/styles/panel = SubResource("StyleBoxFlat_j2kio") +GraphNode/constants/separation = 4 GraphNode/icons/port = ExtResource("1_yjacf") -GraphNode/styles/panel = SubResource("StyleBoxFlat_8qegs") -GraphNode/styles/panel_selected = SubResource("StyleBoxFlat_qd482") -GraphNode/styles/slot = SubResource("StyleBoxEmpty_85xuj") -GraphNode/styles/titlebar = SubResource("StyleBoxFlat_qccfg") -GraphNode/styles/titlebar_selected = SubResource("StyleBoxFlat_a8gsr") +GraphNode/styles/panel = SubResource("StyleBoxFlat_q56mj") +GraphNode/styles/panel_selected = SubResource("StyleBoxFlat_hvbmk") +GraphNode/styles/slot = SubResource("StyleBoxEmpty_lvjcx") +GraphNode/styles/titlebar = SubResource("StyleBoxFlat_qi7n2") +GraphNode/styles/titlebar_selected = SubResource("StyleBoxFlat_kp0xv") GraphNodePicker/base_type = &"PanelContainer" -GraphNodePicker/styles/panel = SubResource("StyleBoxFlat_8epah") -HBoxContainer/constants/separation = 8 +GraphNodePicker/styles/panel = SubResource("StyleBoxFlat_sdjsm") +HBoxContainer/constants/separation = 4 HDottedSeparator/base_type = &"HSeparator" HDottedSeparator/constants/separation = 1 -HDottedSeparator/styles/separator = SubResource("StyleBoxTexture_j2kio") -HScrollBar/styles/grabber = SubResource("StyleBoxFlat_q56mj") -HScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_q56mj") -HScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_q56mj") -HScrollBar/styles/scroll = SubResource("StyleBoxFlat_hvbmk") -HScrollBar/styles/scroll_focus = SubResource("StyleBoxFlat_lvjcx") +HDottedSeparator/styles/separator = SubResource("StyleBoxTexture_eudc8") +HScrollBar/styles/grabber = SubResource("StyleBoxFlat_xm1xm") +HScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_xm1xm") +HScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_xm1xm") +HScrollBar/styles/scroll = SubResource("StyleBoxFlat_qhjh4") +HScrollBar/styles/scroll_focus = SubResource("StyleBoxFlat_txkog") HSeparator/constants/separation = 1 -HSeparator/styles/separator = SubResource("StyleBoxLine_qi7n2") +HSeparator/styles/separator = SubResource("StyleBoxLine_axeq1") HSeparatorGrow/base_type = &"HSeparator" HSeparatorGrow/constants/separation = 1 -HSeparatorGrow/styles/separator = SubResource("StyleBoxLine_kp0xv") +HSeparatorGrow/styles/separator = SubResource("StyleBoxLine_qd60x") HSlider/icons/grabber = ExtResource("4_gye7x") HSlider/icons/grabber_disabled = ExtResource("4_gye7x") HSlider/icons/grabber_highlight = ExtResource("4_gye7x") -HSlider/styles/grabber_area = SubResource("StyleBoxFlat_sdjsm") -HSlider/styles/grabber_area_highlight = SubResource("StyleBoxFlat_sdjsm") -HSlider/styles/slider = SubResource("StyleBoxFlat_eudc8") +HSlider/styles/grabber_area = SubResource("StyleBoxFlat_poo5t") +HSlider/styles/grabber_area_highlight = SubResource("StyleBoxFlat_poo5t") +HSlider/styles/slider = SubResource("StyleBoxFlat_blikl") +HSplitContainer/constants/separation = 4 +HSplitContainer/icons/grabber = SubResource("Texture2D_v58ty") +InspectorPanel/base_type = &"PanelContainer" +InspectorPanel/styles/panel = SubResource("StyleBoxFlat_yj5pq") +InspectorPanelTopBox/base_type = &"PanelContainer" +InspectorPanelTopBox/styles/panel = SubResource("StyleBoxFlat_y6go5") ItemContainer/base_type = &"PanelContainer" -ItemContainer/styles/panel = SubResource("StyleBoxFlat_xm1xm") +ItemContainer/styles/panel = SubResource("StyleBoxFlat_kik2p") ItemContainerFlat/base_type = &"PanelContainer" -ItemContainerFlat/styles/panel = SubResource("StyleBoxFlat_qhjh4") +ItemContainerFlat/styles/panel = SubResource("StyleBoxFlat_huxsq") Label/colors/font_color = Color(0.890196, 0.894118, 0.921569, 1) -LineEdit/styles/disabled = SubResource("StyleBoxFlat_txkog") -LineEdit/styles/focus = SubResource("StyleBoxFlat_axeq1") -LineEdit/styles/normal = SubResource("StyleBoxFlat_qd60x") +LineEdit/styles/disabled = SubResource("StyleBoxFlat_6yqil") +LineEdit/styles/focus = SubResource("StyleBoxFlat_j1x70") +LineEdit/styles/normal = SubResource("StyleBoxFlat_5eo3a") LineEditPortraitOption/base_type = &"LineEdit" LineEditPortraitOption/colors/font_color = Color(0.890196, 0.894118, 0.921569, 1) LineEditPortraitOption/colors/font_uneditable_color = Color(0.890196, 0.894118, 0.921569, 1) -LineEditPortraitOption/styles/disabled = SubResource("StyleBoxFlat_poo5t") -LineEditPortraitOption/styles/focus = SubResource("StyleBoxFlat_blikl") -LineEditPortraitOption/styles/normal = SubResource("StyleBoxFlat_v58ty") -MarginContainer/constants/margin_bottom = 8 -MarginContainer/constants/margin_left = 8 -MarginContainer/constants/margin_right = 8 -MarginContainer/constants/margin_top = 8 +LineEditPortraitOption/styles/disabled = SubResource("StyleBoxFlat_l5wh6") +LineEditPortraitOption/styles/focus = SubResource("StyleBoxFlat_c4a6a") +LineEditPortraitOption/styles/normal = SubResource("StyleBoxFlat_xlkkc") +MarginContainer/constants/margin_bottom = 4 +MarginContainer/constants/margin_left = 4 +MarginContainer/constants/margin_right = 4 +MarginContainer/constants/margin_top = 4 NodeValue/base_type = &"Label" NodeValue/colors/font_color = Color(0.890196, 0.894118, 0.921569, 1) -NodeValue/styles/normal = SubResource("StyleBoxFlat_yj5pq") +NodeValue/styles/normal = SubResource("StyleBoxFlat_qdp3q") NoteLabel/base_type = &"Label" NoteLabel/colors/font_color = Color(0.890196, 0.894118, 0.921569, 0.6) -OptionButton/constants/arrow_margin = 8 -OptionButton/constants/h_separation = 8 -OptionButton/styles/disabled = SubResource("StyleBoxFlat_y6go5") -OptionButton/styles/disabled_mirrored = SubResource("StyleBoxFlat_y6go5") +OptionButton/constants/arrow_margin = 4 +OptionButton/constants/h_separation = 4 +OptionButton/styles/disabled = SubResource("StyleBoxFlat_s45bh") +OptionButton/styles/disabled_mirrored = SubResource("StyleBoxFlat_s45bh") OptionButton/styles/focus = SubResource("StyleBoxFlat_uqgmq") -OptionButton/styles/hover = SubResource("StyleBoxFlat_kik2p") -OptionButton/styles/hover_mirrored = SubResource("StyleBoxFlat_kik2p") -OptionButton/styles/hover_pressed = SubResource("StyleBoxFlat_huxsq") -OptionButton/styles/hover_pressed_mirrored = SubResource("StyleBoxFlat_huxsq") -OptionButton/styles/normal = SubResource("StyleBoxFlat_6yqil") -OptionButton/styles/normal_mirrored = SubResource("StyleBoxFlat_6yqil") -OptionButton/styles/pressed = SubResource("StyleBoxFlat_huxsq") -OptionButton/styles/pressed_mirrored = SubResource("StyleBoxFlat_huxsq") +OptionButton/styles/hover = SubResource("StyleBoxFlat_raufu") +OptionButton/styles/hover_mirrored = SubResource("StyleBoxFlat_raufu") +OptionButton/styles/hover_pressed = SubResource("StyleBoxFlat_e7orh") +OptionButton/styles/hover_pressed_mirrored = SubResource("StyleBoxFlat_e7orh") +OptionButton/styles/normal = SubResource("StyleBoxFlat_eggpx") +OptionButton/styles/normal_mirrored = SubResource("StyleBoxFlat_eggpx") +OptionButton/styles/pressed = SubResource("StyleBoxFlat_e7orh") +OptionButton/styles/pressed_mirrored = SubResource("StyleBoxFlat_e7orh") OuterPanel/base_type = &"PanelContainer" -OuterPanel/styles/panel = SubResource("StyleBoxFlat_j1x70") -Panel/styles/panel = SubResource("StyleBoxFlat_5eo3a") -PanelContainer/styles/panel = SubResource("StyleBoxFlat_5eo3a") -PopupMenu/constants/h_separation = 8 +OuterPanel/styles/panel = SubResource("StyleBoxFlat_tm0m6") +Panel/styles/panel = SubResource("StyleBoxFlat_c1m33") +PanelContainer/styles/panel = SubResource("StyleBoxFlat_c1m33") +PopupMenu/constants/h_separation = 4 PopupMenu/constants/icon_max_width = 14 -PopupMenu/constants/item_end_padding = 8 -PopupMenu/constants/item_start_padding = 8 +PopupMenu/constants/item_end_padding = 4 +PopupMenu/constants/item_start_padding = 4 PopupMenu/constants/v_separation = 4 PopupMenu/font_sizes/font_size = 16 PopupMenu/icons/checked = ExtResource("1_ks7w5") @@ -1068,88 +1223,90 @@ PopupMenu/icons/radio_unchecked = ExtResource("9_2ase2") PopupMenu/icons/radio_unchecked_disabled = ExtResource("10_ks7w5") PopupMenu/icons/unchecked = ExtResource("7_rb0lk") PopupMenu/icons/unchecked_disabled = ExtResource("8_30fyb") -PopupMenu/styles/hover = SubResource("StyleBoxFlat_l5wh6") -PopupMenu/styles/panel = SubResource("StyleBoxFlat_c4a6a") -PopupMenu/styles/separator = SubResource("StyleBoxLine_xlkkc") +PopupMenu/styles/hover = SubResource("StyleBoxFlat_3f21d") +PopupMenu/styles/panel = SubResource("StyleBoxFlat_j02or") +PopupMenu/styles/separator = SubResource("StyleBoxLine_78rkj") SpinBoxButtonLeft/base_type = &"Button" -SpinBoxButtonLeft/styles/disabled = SubResource("StyleBoxFlat_qdp3q") -SpinBoxButtonLeft/styles/focus = SubResource("StyleBoxFlat_qdp3q") -SpinBoxButtonLeft/styles/hover = SubResource("StyleBoxFlat_qdp3q") -SpinBoxButtonLeft/styles/normal = SubResource("StyleBoxFlat_qdp3q") -SpinBoxButtonLeft/styles/pressed = SubResource("StyleBoxFlat_s45bh") +SpinBoxButtonLeft/styles/disabled = SubResource("StyleBoxFlat_61iib") +SpinBoxButtonLeft/styles/focus = SubResource("StyleBoxFlat_61iib") +SpinBoxButtonLeft/styles/hover = SubResource("StyleBoxFlat_61iib") +SpinBoxButtonLeft/styles/normal = SubResource("StyleBoxFlat_61iib") +SpinBoxButtonLeft/styles/pressed = SubResource("StyleBoxFlat_ydnyu") SpinBoxButtonRight/base_type = &"Button" -SpinBoxButtonRight/styles/disabled = SubResource("StyleBoxFlat_qdp3q") -SpinBoxButtonRight/styles/focus = SubResource("StyleBoxFlat_qdp3q") -SpinBoxButtonRight/styles/hover = SubResource("StyleBoxFlat_qdp3q") -SpinBoxButtonRight/styles/normal = SubResource("StyleBoxFlat_qdp3q") -SpinBoxButtonRight/styles/pressed = SubResource("StyleBoxFlat_raufu") +SpinBoxButtonRight/styles/disabled = SubResource("StyleBoxFlat_61iib") +SpinBoxButtonRight/styles/focus = SubResource("StyleBoxFlat_61iib") +SpinBoxButtonRight/styles/hover = SubResource("StyleBoxFlat_61iib") +SpinBoxButtonRight/styles/normal = SubResource("StyleBoxFlat_61iib") +SpinBoxButtonRight/styles/pressed = SubResource("StyleBoxFlat_onp0e") SpinBoxLineEdit/base_type = &"LineEdit" -SpinBoxLineEdit/styles/focus = SubResource("StyleBoxFlat_e7orh") -SpinBoxLineEdit/styles/normal = SubResource("StyleBoxFlat_eggpx") -SpinBoxLineEdit/styles/read_only = SubResource("StyleBoxFlat_eggpx") +SpinBoxLineEdit/styles/focus = SubResource("StyleBoxFlat_olm6c") +SpinBoxLineEdit/styles/normal = SubResource("StyleBoxFlat_k4pvp") +SpinBoxLineEdit/styles/read_only = SubResource("StyleBoxFlat_k4pvp") SpinBoxPanel/base_type = &"PanelContainer" -SpinBoxPanel/styles/panel = SubResource("StyleBoxFlat_tm0m6") +SpinBoxPanel/styles/panel = SubResource("StyleBoxFlat_bdyw7") TabBar/colors/font_disabled_color = Color(0.890196, 0.894118, 0.921569, 0.3) TabBar/colors/font_hovered_color = Color(0.890196, 0.894118, 0.921569, 1) TabBar/colors/font_selected_color = Color(0.890196, 0.894118, 0.921569, 1) TabBar/colors/font_unselected_color = Color(0.890196, 0.894118, 0.921569, 0.8) -TabBar/constants/h_separation = 8 +TabBar/constants/h_separation = 4 TabBar/font_sizes/font_size = 16 -TabBar/styles/button_highlight = SubResource("StyleBoxEmpty_c1m33") -TabBar/styles/button_pressed = SubResource("StyleBoxEmpty_3f21d") -TabBar/styles/tab_disabled = SubResource("StyleBoxFlat_j02or") -TabBar/styles/tab_focus = SubResource("StyleBoxFlat_78rkj") -TabBar/styles/tab_hovered = SubResource("StyleBoxFlat_61iib") -TabBar/styles/tab_selected = SubResource("StyleBoxFlat_ydnyu") -TabBar/styles/tab_unselected = SubResource("StyleBoxFlat_onp0e") +TabBar/styles/button_highlight = SubResource("StyleBoxEmpty_5aedr") +TabBar/styles/button_pressed = SubResource("StyleBoxEmpty_ibb5s") +TabBar/styles/tab_disabled = SubResource("StyleBoxFlat_341sr") +TabBar/styles/tab_focus = SubResource("StyleBoxFlat_xrd2a") +TabBar/styles/tab_hovered = SubResource("StyleBoxFlat_muvro") +TabBar/styles/tab_selected = SubResource("StyleBoxFlat_tafav") +TabBar/styles/tab_unselected = SubResource("StyleBoxFlat_hcwh5") TextEdit/font_sizes/font_size = 16 TextEdit/fonts/font = ExtResource("4_flt0i") -TextEdit/styles/focus = SubResource("StyleBoxFlat_olm6c") -TextEdit/styles/normal = SubResource("StyleBoxFlat_k4pvp") -TextEdit/styles/read_only = SubResource("StyleBoxFlat_bdyw7") +TextEdit/styles/focus = SubResource("StyleBoxFlat_20gek") +TextEdit/styles/normal = SubResource("StyleBoxFlat_wxysu") +TextEdit/styles/read_only = SubResource("StyleBoxFlat_bb1tf") TimelineCellNumber/base_type = &"PanelContainer" -TimelineCellNumber/styles/panel = SubResource("StyleBoxFlat_5aedr") +TimelineCellNumber/styles/panel = SubResource("StyleBoxFlat_bn5ll") TimelineLayerPanel/base_type = &"PanelContainer" -TimelineLayerPanel/styles/panel = SubResource("StyleBoxFlat_ibb5s") +TimelineLayerPanel/styles/panel = SubResource("StyleBoxFlat_l4tsf") Tree/colors/relashion_ship_line_color = Color(0.890196, 0.894118, 0.921569, 0.1) Tree/constants/children_hl_line_width = 0 Tree/constants/draw_guides = 0 Tree/constants/draw_relationship_lines = 1 -Tree/constants/h_separation = 8 +Tree/constants/h_separation = 4 Tree/constants/icon_max_width = 14 -Tree/constants/inner_item_margin_bottom = 8 -Tree/constants/inner_item_margin_left = 8 -Tree/constants/inner_item_margin_right = 8 -Tree/constants/inner_item_margin_top = 8 +Tree/constants/inner_item_margin_bottom = 4 +Tree/constants/inner_item_margin_left = 4 +Tree/constants/inner_item_margin_right = 4 +Tree/constants/inner_item_margin_top = 4 Tree/constants/parent_hl_line_width = 1 Tree/constants/relationship_line_width = 0 -Tree/constants/v_separation = 4 +Tree/constants/v_separation = 2 Tree/icons/checked = ExtResource("1_ks7w5") Tree/icons/checked_disabled = ExtResource("2_uqgmq") Tree/icons/unchecked = ExtResource("7_rb0lk") Tree/icons/unchecked_disabled = ExtResource("8_30fyb") -Tree/styles/focus = SubResource("StyleBoxFlat_341sr") +Tree/styles/focus = SubResource("StyleBoxFlat_cfrtq") Tree/styles/hovered = SubResource("StyleBoxFlat_rb0lk") Tree/styles/hovered_dimmed = SubResource("StyleBoxFlat_rb0lk") -Tree/styles/panel = SubResource("StyleBoxFlat_xrd2a") +Tree/styles/panel = SubResource("StyleBoxFlat_wl3tv") Tree/styles/selected = SubResource("StyleBoxFlat_30fyb") Tree/styles/selected_focus = SubResource("StyleBoxFlat_30fyb") TreeContainer/base_type = &"PanelContainer" -TreeContainer/styles/panel = SubResource("StyleBoxFlat_muvro") -VBoxContainer/constants/separation = 8 +TreeContainer/styles/panel = SubResource("StyleBoxFlat_nj65x") +VBoxContainer/constants/separation = 4 VDottedSeparator/base_type = &"VSeparator" VDottedSeparator/constants/separation = 1 -VDottedSeparator/styles/separator = SubResource("StyleBoxTexture_j2kio") -VScrollBar/styles/grabber = SubResource("StyleBoxFlat_q56mj") -VScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_q56mj") -VScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_q56mj") -VScrollBar/styles/scroll = SubResource("StyleBoxFlat_hvbmk") -VScrollBar/styles/scroll_focus = SubResource("StyleBoxFlat_lvjcx") +VDottedSeparator/styles/separator = SubResource("StyleBoxTexture_eudc8") +VScrollBar/styles/grabber = SubResource("StyleBoxFlat_xm1xm") +VScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_xm1xm") +VScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_xm1xm") +VScrollBar/styles/scroll = SubResource("StyleBoxFlat_qhjh4") +VScrollBar/styles/scroll_focus = SubResource("StyleBoxFlat_txkog") VSeparator/constants/separation = 1 -VSeparator/styles/separator = SubResource("StyleBoxLine_tafav") +VSeparator/styles/separator = SubResource("StyleBoxLine_wlq5o") VSeparatorGrow/base_type = &"VSeparator" VSeparatorGrow/constants/separation = 1 -VSeparatorGrow/styles/separator = SubResource("StyleBoxLine_xlkkc") +VSeparatorGrow/styles/separator = SubResource("StyleBoxLine_78rkj") +VSplitContainer/constants/separation = 4 +VSplitContainer/icons/grabber = SubResource("Texture2D_l8c38") WarnLabel/base_type = &"Label" WarnLabel/colors/font_color = Color(0.768627, 0.180392, 0.25098, 1) script = ExtResource("5_gye7x") diff --git a/ui/theme_default/theme_default.gd b/ui/theme_default/theme_default.gd index 37ffcc81..a1b53011 100644 --- a/ui/theme_default/theme_default.gd +++ b/ui/theme_default/theme_default.gd @@ -6,17 +6,17 @@ var dark_theme: bool = true var contrast: float = 0.15 # Colors var text_color: Color = Color("e3e4eb") -var background_color: Color = Color("19191c") +var background_color: Color = Color(0.1479, 0.1479, 0.17, 1.0) var primary_color: Color = Color("a9a8c0") var secondary_color: Color = Color("676278") var accent_color: Color = Color("d15050") var warn_color: Color = Color("c42e40") # Constants -var base_spacing: int = 8 -var corner_radius: int = 6 +var base_spacing: int = 4 +var corner_radius: int = 3 var relationship_line_opacity: float = 0.2 var border_width: int = 1 - +var base_font_size: int = 14 func _init() -> void: scale = 1.0 @@ -47,20 +47,20 @@ func _generate_theme() -> void: base_field_sb.bg_color = _get_secondary_color(contrast) var button_sb: StyleBoxFlat = base_sb.duplicate() - button_sb.bg_color = _get_secondary_color(contrast) - button_sb.content_margin_left = base_margin - button_sb.content_margin_top = base_margin * 0.5 - button_sb.content_margin_right = base_margin - button_sb.content_margin_bottom = base_margin * 0.5 + button_sb.bg_color = _get_primary_color(contrast) + button_sb.content_margin_left = base_margin * 1.5 + button_sb.content_margin_top = base_margin + button_sb.content_margin_right = base_margin * 1.5 + button_sb.content_margin_bottom = base_margin var button_hover_sb: StyleBoxFlat = button_sb.duplicate() - button_hover_sb.bg_color = _get_secondary_color(contrast + 0.05) + button_hover_sb.bg_color = _get_primary_color(contrast + 0.05) var button_pressed_sb: StyleBoxFlat = button_sb.duplicate() - button_pressed_sb.bg_color = _get_secondary_color(contrast + 0.1) + button_pressed_sb.bg_color = _get_primary_color(contrast + 0.1) var button_disabled_sb: StyleBoxFlat = button_sb.duplicate() - button_disabled_sb.bg_color = _get_secondary_color(0.05) + button_disabled_sb.bg_color = _get_primary_color(0.05) var flat_button_sb: StyleBoxFlat = base_sb.duplicate() flat_button_sb.bg_color = Color.TRANSPARENT @@ -68,11 +68,11 @@ func _generate_theme() -> void: _set_border(flat_button_sb, _get_text_color(contrast)) var flat_button_hover_sb: StyleBoxFlat = flat_button_sb.duplicate() - flat_button_hover_sb.bg_color = _get_secondary_color(0.1) + flat_button_hover_sb.bg_color = _get_primary_color(0.1) _set_border(flat_button_hover_sb, _get_text_color(contrast + 0.05)) var flat_button_pressed_sb: StyleBoxFlat = flat_button_sb.duplicate() - button_pressed_sb.bg_color = _get_secondary_color(contrast / 2) + button_pressed_sb.bg_color = _get_primary_color(contrast / 2) _set_border(flat_button_hover_sb, _get_text_color(contrast + 0.1)) # Button @@ -86,6 +86,8 @@ func _generate_theme() -> void: set_color("icon_disabled_color", "Button", _get_text_color(0.3)) set_color("icon_normal_color", "Button", _get_text_color(0.8)) set_constant("outline_size", "Button", 0) + set_constant("icon_max_width", "Button", 15) + set_constant("h_separation", "Button", base_spacing) set_stylebox("disabled", "Button", button_disabled_sb) set_stylebox("disabled_mirrored", "Button", button_disabled_sb) set_stylebox("focus", "Button", base_empty_sb) @@ -226,31 +228,29 @@ func _generate_theme() -> void: set_type_variation("CollapsibleFieldPanel", "PanelContainer") var sb: StyleBoxFlat = base_sb.duplicate() - sb.bg_color = _get_secondary_color(contrast / 2) + sb.bg_color = _get_primary_color(contrast/2) set_stylebox("panel", "CollapsibleFieldPanel", sb) # EditorBackground set_type_variation("EditorBackground", "PanelContainer") sb = base_sb.duplicate() - sb.bg_color = _get_primary_color(contrast, false) + sb.bg_color = background_color sb.set_corner_radius_all(0) - sb.set_content_margin_all(0) set_stylebox("panel", "EditorBackground", sb) - # EditorSidePanel + # InspectorPanel - set_type_variation("EditorSidePanel", "PanelContainer") + set_type_variation("InspectorPanel", "PanelContainer") sb = base_sb.duplicate() sb.bg_color = _get_primary_color(contrast, false) sb.set_corner_radius_all(0) sb.set_border_width_all(0) - sb.border_width_left = 1 - set_stylebox("panel", "EditorSidePanel", sb) + set_stylebox("panel", "InspectorPanel", sb) - # EditorSidePanelTopBox + # InspectorPanelTopBox - set_type_variation("EditorSidePanelTopBox", "PanelContainer") + set_type_variation("InspectorPanelTopBox", "PanelContainer") sb = base_sb.duplicate() sb.bg_color = _get_primary_color(contrast, false) sb.set_corner_radius_all(0) @@ -258,7 +258,71 @@ func _generate_theme() -> void: sb.set_content_margin_all(0) sb.set_expand_margin_all(base_spacing) sb.expand_margin_left -= 1 - set_stylebox("panel", "EditorSidePanelTopBox", sb) + set_stylebox("panel", "InspectorPanelTopBox", sb) + + # EditorSection + set_type_variation("EditorSection", "TabContainer") + sb = base_sb.duplicate() + sb.bg_color = _get_primary_color(contrast, false) + sb.set_corner_radius_all(corner_radius) + sb.set_content_margin_all(base_margin) + sb.set_border_width_all(border_width) + sb.border_color = base_border_color + set_stylebox("panel_unfocus", "EditorSection", sb) + + sb = sb.duplicate() + sb.border_color = accent_color + set_stylebox("panel_focus", "EditorSection", sb) + + sb = sb.duplicate() + sb.border_width_top = 0 + sb.corner_radius_top_left = 0 + sb.corner_radius_top_right = 0 + sb.border_color = base_border_color + set_stylebox("tab_panel_unfocus", "EditorSection", sb) + + sb = sb.duplicate() + sb.border_color = accent_color + set_stylebox("tab_panel_focus", "EditorSection", sb) + + sb = base_sb.duplicate() + sb.bg_color = _get_secondary_color(contrast, false) + sb.set_corner_radius_all(0) + sb.corner_radius_top_left = corner_radius + sb.corner_radius_top_right = corner_radius + sb.set_border_width_all(border_width) + sb.border_width_bottom = 0 + sb.border_color = base_border_color + set_stylebox("tabbar_background_unfocus", "EditorSection", sb) + + sb = sb.duplicate() + sb.border_color = accent_color + set_stylebox("tabbar_background_focus", "EditorSection", sb) + + sb = base_sb.duplicate() + sb.set_corner_radius_all(0) + sb.corner_radius_top_left = corner_radius-1 + sb.corner_radius_top_right = corner_radius-1 + sb.bg_color = _get_primary_color(contrast, false) + sb.border_color = Color.TRANSPARENT + sb.border_width_top = 1 + set_stylebox("tab_selected", "EditorSection", sb) + + sb = base_sb.duplicate() + sb.draw_center = false + set_stylebox("tab_unselected", "EditorSection", sb) + set_stylebox("tab_focus", "EditorSection", sb) + set_stylebox("tab_hovered", "EditorSection", sb) + set_stylebox("tab_disabled", "EditorSection", sb) + + set_color("font_unselected_color", "EditorSection", _get_text_color(0.8)) + set_color("font_disabled_color", "EditorSection", _get_text_color(0.3)) + set_color("font_hover_color", "EditorSection", text_color) + set_color("font_selected_color", "EditorSection", text_color) + set_constant("side_margin", "EditorSection", 1) + set_constant("icon_separation", "EditorSection", int(base_margin)) + set_constant("icon_max_width", "EditorSection", base_font_size) + set_font_size("font_size", "EditorSection", base_font_size) # FlatButton @@ -281,6 +345,15 @@ func _generate_theme() -> void: set_stylebox("hover_pressed_mirrored", "FlatButton", flat_button_pressed_sb) set_stylebox("pressed", "FlatButton", flat_button_pressed_sb) set_stylebox("pressed_mirrored", "FlatButton", flat_button_pressed_sb) + + # FieldPanel + + set_type_variation("FieldPanel", "PanelContainer") + sb = base_sb.duplicate() + sb.bg_color = background_color + sb.set_border_width_all(border_width) + sb.set_content_margin_all(base_margin*2) + set_stylebox("panel", "FieldPanel", sb) # GraphEdit @@ -288,6 +361,7 @@ func _generate_theme() -> void: sb.set_content_margin_all(0) sb.set_corner_radius_all(0) sb.set_border_width_all(0) + sb.bg_color = _get_secondary_color(contrast, false) set_color("grid_major", "GraphEdit", _get_text_color(contrast)) set_color("grid_minor", "GraphEdit", _get_text_color(contrast)) set_stylebox("panel", "GraphEdit", sb) @@ -354,6 +428,13 @@ func _generate_theme() -> void: set_constant("separation", "VDottedSeparator", 1) set_stylebox("separator", "HDottedSeparator", dotted_sb) set_stylebox("separator", "VDottedSeparator", dotted_sb) + + # HSplitContainer & VSplitContainer + + set_constant("separation", "HSplitContainer", int(base_margin)) + set_constant("separation", "VSplitContainer", int(base_margin)) + set_icon("grabber", "HSplitContainer", Texture2D.new()) + set_icon("grabber", "VSplitContainer", Texture2D.new()) # HSeparator & VSeparator @@ -528,9 +609,9 @@ func _generate_theme() -> void: set_stylebox("separator", "PopupMenu", separator_sb) # ScrollBar - var _side_panel_bg_color: Color = _get_primary_color(contrast, false) + var _inspector_panel_bg_color: Color = _get_primary_color(contrast, false) var _scroll_bar_color: Color = _get_color( - base_border_color, base_border_color.a, false, _side_panel_bg_color + base_border_color, base_border_color.a, false, _inspector_panel_bg_color ) var scroll_sb: StyleBoxFlat = base_empty_sb.duplicate() diff --git a/unit/test_graph_edit_switcher.gd b/unit/test_graph_edit_switcher.gd index 4a626409..52b68183 100644 --- a/unit/test_graph_edit_switcher.gd +++ b/unit/test_graph_edit_switcher.gd @@ -9,7 +9,7 @@ func before_test(): switcher.graph_edits.add_child(auto_free(Control.new())) switcher.tab_bar = auto_free(TabBar.new()) switcher.tab_bar.add_tab("+") - switcher.side_panel = mock(SidePanel) + switcher.inspector_panel = mock(InspectorPanel) func test_add_root(): @@ -42,21 +42,21 @@ func test_add_tab_with_previous(): assert_str(switcher.tab_bar.get_tab_title(2)).is_equal("two") -func test_connect_side_panel(): +func test_connect_inspector_panel(): var ge = mock(MonologueGraphEdit, CALL_REAL_FUNC) - switcher.connect_side_panel(ge) - var select = switcher.side_panel.on_graph_node_selected + switcher.connect_inspector_panel(ge) + var select = switcher.inspector_panel.on_graph_node_selected assert_bool(ge.is_connected("node_selected", select)).is_true() - var deselect = switcher.side_panel.on_graph_node_deselected + var deselect = switcher.inspector_panel.on_graph_node_deselected assert_bool(ge.is_connected("node_deselected", deselect)).is_true() var save = switcher.update_save_state assert_bool(ge.undo_redo.is_connected("version_changed", save)).is_true() -func test_commit_side_panel(): +func test_commit_inspector_panel(): var node = mock(MonologueGraphNode) - switcher.commit_side_panel(node) - verify(switcher.side_panel, 1).refocus(node) + switcher.commit_inspector_panel(node) + verify(switcher.inspector_panel, 1).refocus(node) func test_get_current_graph_edit(): From 93e50b4c8f4a3051098ea917adf22cbac727b9c3 Mon Sep 17 00:00:00 2001 From: Atomic Junky <67459553+atomic-junky@users.noreply.github.com> Date: Mon, 28 Jul 2025 16:08:56 +0200 Subject: [PATCH 002/103] ListEditorSection --- common/layouts/editor/editor_section.tscn | 8 +-- .../editor/list_section/list_section.gd | 29 ++++++++ .../editor/list_section/list_section.gd.uid | 0 .../editor/list_section/list_section.tscn | 31 ++++++++ common/monologue_graph_node.gd | 16 +++-- .../monologue_character_field.gd | 1 - .../monologue_character_field.tscn | 10 +-- .../collapsible_field/collapsible_field.gd | 1 + .../collapsible_field/collapsible_field.tscn | 8 +-- common/ui/fields/list/monologue_list.gd | 8 +++ common/ui/fields/property_list.gd | 28 ++++++++ common/ui/fields/property_list.gd.uid | 1 + common/ui/fields/toggle/monologue_toggle.tscn | 2 + .../windows/prompt_window/prompt_window.tscn | 52 ++++++++------ .../welcome_window/welcome_window.tscn | 23 +++--- project.godot | 2 +- scenes/main/characters_section.gd | 14 ---- scenes/main/editor.tscn | 55 ++++---------- scenes/main/monologue_editor.gd | 71 +++++++++++-------- scenes/main/variables_section.gd | 3 - scenes/main/variables_section.gd.uid | 1 - ui/theme_default/main.tres | 2 + 22 files changed, 226 insertions(+), 140 deletions(-) create mode 100644 common/layouts/editor/list_section/list_section.gd rename scenes/main/characters_section.gd.uid => common/layouts/editor/list_section/list_section.gd.uid (100%) create mode 100644 common/layouts/editor/list_section/list_section.tscn create mode 100644 common/ui/fields/property_list.gd create mode 100644 common/ui/fields/property_list.gd.uid delete mode 100644 scenes/main/characters_section.gd delete mode 100644 scenes/main/variables_section.gd delete mode 100644 scenes/main/variables_section.gd.uid diff --git a/common/layouts/editor/editor_section.tscn b/common/layouts/editor/editor_section.tscn index 152e3b8a..444e10e0 100644 --- a/common/layouts/editor/editor_section.tscn +++ b/common/layouts/editor/editor_section.tscn @@ -2,7 +2,7 @@ [ext_resource type="Script" uid="uid://ijejsk35dal" path="res://common/layouts/editor/editor_section.gd" id="1_1u3ep"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1u3ep"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qd482"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 @@ -15,7 +15,7 @@ border_color = Color(0.890196, 0.894118, 0.921569, 0.2) corner_radius_top_left = 3 corner_radius_top_right = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qe3vh"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1uy7d"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 @@ -34,8 +34,8 @@ offset_right = 40.0 offset_bottom = 40.0 focus_mode = 1 theme_type_variation = &"EditorSection" -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_qe3vh") +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_qd482") +theme_override_styles/panel = SubResource("StyleBoxFlat_1uy7d") script = ExtResource("1_1u3ep") [connection signal="child_entered_tree" from="." to="." method="_on_child_entered_tree"] diff --git a/common/layouts/editor/list_section/list_section.gd b/common/layouts/editor/list_section/list_section.gd new file mode 100644 index 00000000..d855eb7d --- /dev/null +++ b/common/layouts/editor/list_section/list_section.gd @@ -0,0 +1,29 @@ +extends VBoxContainer + +@export var section_icon: Texture2D + +@onready var vbox := $ScrollContainer/VBox +@onready var search_bar: LineEdit = $ToolBar/LineEdit + +var add_func: Callable + + +func _ready() -> void: + search_bar.placeholder_text = "Filter %s" % name + + +func clear() -> void: + for child in vbox.get_children(): + child.queue_free() + + +func load_items(property: Property) -> void: + clear() + property.setters["is_section"] = true + var field := property.show(vbox) + add_func = field._on_add_button_pressed + + +func _on_add_button_pressed() -> void: + if add_func and add_func.is_valid(): + add_func.call() diff --git a/scenes/main/characters_section.gd.uid b/common/layouts/editor/list_section/list_section.gd.uid similarity index 100% rename from scenes/main/characters_section.gd.uid rename to common/layouts/editor/list_section/list_section.gd.uid diff --git a/common/layouts/editor/list_section/list_section.tscn b/common/layouts/editor/list_section/list_section.tscn new file mode 100644 index 00000000..7f6ca90a --- /dev/null +++ b/common/layouts/editor/list_section/list_section.tscn @@ -0,0 +1,31 @@ +[gd_scene load_steps=3 format=3 uid="uid://mfdu320oy6ex"] + +[ext_resource type="Script" uid="uid://yglbu25x1rsy" path="res://common/layouts/editor/list_section/list_section.gd" id="1_coa1v"] +[ext_resource type="Texture2D" uid="uid://hlck6y4i3l5q" path="res://ui/assets/icons/plus.svg" id="3_j4mj2"] + +[node name="ListSection" type="VBoxContainer"] +script = ExtResource("1_coa1v") +metadata/_tab_index = 0 + +[node name="ToolBar" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="LineEdit" type="LineEdit" parent="ToolBar"] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Filter" + +[node name="AddButton" type="Button" parent="ToolBar"] +layout_mode = 2 +icon = ExtResource("3_j4mj2") + +[node name="ScrollContainer" type="ScrollContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBox" type="VBoxContainer" parent="ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[connection signal="pressed" from="ToolBar/AddButton" to="." method="_on_add_button_pressed"] diff --git a/common/monologue_graph_node.gd b/common/monologue_graph_node.gd index 57381a2c..9554e506 100644 --- a/common/monologue_graph_node.gd +++ b/common/monologue_graph_node.gd @@ -192,11 +192,6 @@ func _to_dict() -> Dictionary: var base_dict = {"$type": node_type, "ID": id.value, "EditorPosition": editor_position.value} _to_next(base_dict) _to_fields(base_dict) - - #base_dict["EditorPosition"] = { - #"x": int(position_offset.x), - #"y": int(position_offset.y) - #} return base_dict @@ -225,3 +220,14 @@ func _validate_id(text: String) -> bool: func _get_field_groups() -> Array: return [] + + +func _get_inspector_property_list() -> Array: + return [ + {"name": "ID", "property": "id", "type": LINE}, + {"name": "EditorPosition", "property": "editor_position", "type": LINE}, + ] + get_inspector_property_list() + + +func get_inspector_property_list() -> Array: + return [] diff --git a/common/ui/fields/character_field/monologue_character_field.gd b/common/ui/fields/character_field/monologue_character_field.gd index e1d36ebf..19fe4f9a 100644 --- a/common/ui/fields/character_field/monologue_character_field.gd +++ b/common/ui/fields/character_field/monologue_character_field.gd @@ -1,6 +1,5 @@ class_name MonologueCharacterField extends MonologueField - @onready var name_edit := %NameEdit @onready var delete_button := $HBoxContainer/VBoxContainer/VBoxContainer/HBoxContainer/DeleteButton diff --git a/common/ui/fields/character_field/monologue_character_field.tscn b/common/ui/fields/character_field/monologue_character_field.tscn index 2d044730..00ae4823 100644 --- a/common/ui/fields/character_field/monologue_character_field.tscn +++ b/common/ui/fields/character_field/monologue_character_field.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=6 format=3 uid="uid://rmul5j0wm0l8"] +[gd_scene load_steps=7 format=3 uid="uid://rmul5j0wm0l8"] [ext_resource type="PackedScene" uid="uid://dfwf55ovgwir3" path="res://common/ui/buttons/delete_button.tscn" id="1_eutnh"] [ext_resource type="Script" uid="uid://cmvo3t5bngju6" path="res://common/ui/fields/character_field/monologue_character_field.gd" id="1_no1me"] [ext_resource type="Texture2D" uid="uid://ddah0eo1qhki4" path="res://common/ui/fields/character_field/assets/portrait_placeholder.svg" id="2_jkh4y"] [ext_resource type="Shader" uid="uid://bsso8dloc4bce" path="res://logic/shaders/texture_rect_clip.tres" id="2_nva25"] +[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="4_uw7v4"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_q7tom"] shader = ExtResource("2_nva25") @@ -39,10 +40,9 @@ size_flags_horizontal = 3 layout_mode = 2 size_flags_vertical = 3 -[node name="FieldLabel" type="Label" parent="HBoxContainer/VBoxContainer/NameContainer"] -custom_minimum_size = Vector2(75, 0) +[node name="FieldLabel" parent="HBoxContainer/VBoxContainer/NameContainer" instance=ExtResource("4_uw7v4")] +custom_minimum_size = Vector2(50, 31) layout_mode = 2 -size_flags_vertical = 0 text = "Name" [node name="InnerVBox" type="VBoxContainer" parent="HBoxContainer/VBoxContainer/NameContainer"] @@ -51,8 +51,8 @@ size_flags_horizontal = 3 [node name="NameEdit" type="LineEdit" parent="HBoxContainer/VBoxContainer/NameContainer/InnerVBox"] unique_name_in_owner = true -custom_minimum_size = Vector2(100, 0) layout_mode = 2 +placeholder_text = "Character name" [node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/VBoxContainer"] layout_mode = 2 diff --git a/common/ui/fields/collapsible_field/collapsible_field.gd b/common/ui/fields/collapsible_field/collapsible_field.gd index 355c820a..d6463a53 100644 --- a/common/ui/fields/collapsible_field/collapsible_field.gd +++ b/common/ui/fields/collapsible_field/collapsible_field.gd @@ -37,6 +37,7 @@ func add_item(item: Control, force_readable_name: bool = false) -> void: func _update(): var can_see: bool = show_add_button + %AddButtonContainer.visible = can_see for child in vbox.get_children(): if not child.visible: diff --git a/common/ui/fields/collapsible_field/collapsible_field.tscn b/common/ui/fields/collapsible_field/collapsible_field.tscn index f92bde23..e577ef69 100644 --- a/common/ui/fields/collapsible_field/collapsible_field.tscn +++ b/common/ui/fields/collapsible_field/collapsible_field.tscn @@ -39,11 +39,11 @@ size_flags_horizontal = 3 size_flags_vertical = 3 theme_type_variation = &"FieldContainer" -[node name="MarginContainer" type="MarginContainer" parent="CollapsibleContainer/PanelContainer/VBox"] +[node name="AddButtonContainer" type="MarginContainer" parent="CollapsibleContainer/PanelContainer/VBox"] +unique_name_in_owner = true layout_mode = 2 -theme_type_variation = &"MarginContainer_Medium" -[node name="AddButton" type="Button" parent="CollapsibleContainer/PanelContainer/VBox/MarginContainer"] +[node name="AddButton" type="Button" parent="CollapsibleContainer/PanelContainer/VBox/AddButtonContainer"] unique_name_in_owner = true visible = false layout_mode = 2 @@ -52,4 +52,4 @@ theme_type_variation = &"Button_Outline" text = "+" [connection signal="pressed" from="Button" to="." method="_on_button_pressed"] -[connection signal="pressed" from="CollapsibleContainer/PanelContainer/VBox/MarginContainer/AddButton" to="." method="_on_add_button_pressed"] +[connection signal="pressed" from="CollapsibleContainer/PanelContainer/VBox/AddButtonContainer/AddButton" to="." method="_on_add_button_pressed"] diff --git a/common/ui/fields/list/monologue_list.gd b/common/ui/fields/list/monologue_list.gd index 9f750663..6b8cf482 100644 --- a/common/ui/fields/list/monologue_list.gd +++ b/common/ui/fields/list/monologue_list.gd @@ -14,6 +14,7 @@ var get_callback: Callable = Constants.empty_callback var data_list: Array = [] var flat: bool = false var expand: bool = false +var is_section: bool = false func _ready() -> void: @@ -21,6 +22,13 @@ func _ready() -> void: collapsible_field.add_pressed.connect(_on_add_button_pressed) collapsible_field.expand = expand post_ready.call_deferred() + + if is_section: + collapsible_field.show_add_button = false + collapsible_field.expand = true + button.hide() + collapsible_container.add_theme_constant_override("margin_left", 0) + field_container.add_theme_constant_override("margin_left", 0) if flat: collapsible_field.separate_items = false diff --git a/common/ui/fields/property_list.gd b/common/ui/fields/property_list.gd new file mode 100644 index 00000000..9ff5796e --- /dev/null +++ b/common/ui/fields/property_list.gd @@ -0,0 +1,28 @@ +## Represents a graph node property list and its UI controls in Monologue. +class_name PropertyList extends Property + +## Scene used to instantiate the item field's UI control. +var item_scene: PackedScene + + +func _init( + ui_item_scene: PackedScene, + ui_setters: Dictionary = {}, + default: Variant = "", + ui_custom_label: Variant = null +) -> void: + item_scene = ui_item_scene + scene = MonologueGraphNode.LIST + setters = ui_setters + value = default + default_value = default + custom_label = ui_custom_label + visible = true + + +func add_item() -> Control: + return Control.new() + + +func get_items() -> Array: + return [] diff --git a/common/ui/fields/property_list.gd.uid b/common/ui/fields/property_list.gd.uid new file mode 100644 index 00000000..2a62388c --- /dev/null +++ b/common/ui/fields/property_list.gd.uid @@ -0,0 +1 @@ +uid://f2xkawigtf41 diff --git a/common/ui/fields/toggle/monologue_toggle.tscn b/common/ui/fields/toggle/monologue_toggle.tscn index 60f164e8..8e5f30e7 100644 --- a/common/ui/fields/toggle/monologue_toggle.tscn +++ b/common/ui/fields/toggle/monologue_toggle.tscn @@ -8,11 +8,13 @@ offset_bottom = 29.0 script = ExtResource("1_0u5l0") [node name="VBoxContainer" type="VBoxContainer" parent="."] +custom_minimum_size = Vector2(9.385, 0) layout_mode = 2 alignment = 1 [node name="CheckButton" type="CheckButton" parent="VBoxContainer"] unique_name_in_owner = true layout_mode = 2 +theme_override_constants/icon_max_width = 0 [connection signal="toggled" from="VBoxContainer/CheckButton" to="." method="_on_check_button_toggled"] diff --git a/common/windows/prompt_window/prompt_window.tscn b/common/windows/prompt_window/prompt_window.tscn index b0e7177b..2e27a423 100644 --- a/common/windows/prompt_window/prompt_window.tscn +++ b/common/windows/prompt_window/prompt_window.tscn @@ -6,7 +6,7 @@ [node name="PromptWindow" type="Window"] transparent_bg = true initial_position = 2 -size = Vector2i(650, 206) +size = Vector2i(798, 226) wrap_controls = true unresizable = true borderless = true @@ -16,38 +16,50 @@ popup_window = true script = ExtResource("1_u0ucq") [node name="PanelContainer" type="PanelContainer" parent="."] -anchors_preset = -1 -anchor_right = 1.0 -anchor_bottom = 0.903 -offset_bottom = -0.0180054 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -331.5 +offset_top = -73.0 +offset_right = 331.5 +offset_bottom = 73.0 grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 3 size_flags_vertical = 3 theme_type_variation = &"OuterPanel" -[node name="HBox" type="HBoxContainer" parent="PanelContainer"] +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"] layout_mode = 2 -theme_type_variation = &"HBoxContainer_Big" +theme_override_constants/margin_left = 25 +theme_override_constants/margin_top = 25 +theme_override_constants/margin_right = 25 +theme_override_constants/margin_bottom = 25 + +[node name="HBox" type="HBoxContainer" parent="PanelContainer/MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 25 -[node name="TextureRect" type="TextureRect" parent="PanelContainer/HBox"] +[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/HBox"] custom_minimum_size = Vector2(80, 0) layout_mode = 2 texture = ExtResource("2_kx1ym") expand_mode = 1 stretch_mode = 5 -[node name="VBox" type="VBoxContainer" parent="PanelContainer/HBox"] +[node name="VBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/HBox"] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 4 theme_type_variation = &"VBoxContainer_Big" -[node name="LabelHBox" type="VBoxContainer" parent="PanelContainer/HBox/VBox"] +[node name="LabelHBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/HBox/VBox"] layout_mode = 2 theme_type_variation = &"VBoxContainer_Medium" -[node name="TitleLabel" type="Label" parent="PanelContainer/HBox/VBox/LabelHBox"] +[node name="TitleLabel" type="Label" parent="PanelContainer/MarginContainer/HBox/VBox/LabelHBox"] unique_name_in_owner = true layout_mode = 2 theme_type_variation = &"HeaderSmall" @@ -55,37 +67,37 @@ text = "Save changes?" clip_text = true text_overrun_behavior = 2 -[node name="DescriptionLabel" type="Label" parent="PanelContainer/HBox/VBox/LabelHBox"] +[node name="DescriptionLabel" type="Label" parent="PanelContainer/MarginContainer/HBox/VBox/LabelHBox"] unique_name_in_owner = true custom_minimum_size = Vector2(500, 75) layout_mode = 2 -theme_type_variation = &"Label_Secondary" +theme_type_variation = &"NoteLabel" text = "The document you have opened will be closed. Do you want to save the changes?" autowrap_mode = 3 -[node name="HBox" type="HBoxContainer" parent="PanelContainer/HBox/VBox"] +[node name="HBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/HBox/VBox"] layout_mode = 2 theme_type_variation = &"HBoxContainer_Big" -[node name="ConfirmButton" type="Button" parent="PanelContainer/HBox/VBox/HBox"] +[node name="ConfirmButton" type="Button" parent="PanelContainer/MarginContainer/HBox/VBox/HBox"] unique_name_in_owner = true custom_minimum_size = Vector2(125, 0) layout_mode = 2 text = "Yes" -[node name="DenyButton" type="Button" parent="PanelContainer/HBox/VBox/HBox"] +[node name="DenyButton" type="Button" parent="PanelContainer/MarginContainer/HBox/VBox/HBox"] unique_name_in_owner = true custom_minimum_size = Vector2(125, 0) layout_mode = 2 text = "No" -[node name="CancelButton" type="Button" parent="PanelContainer/HBox/VBox/HBox"] +[node name="CancelButton" type="Button" parent="PanelContainer/MarginContainer/HBox/VBox/HBox"] unique_name_in_owner = true custom_minimum_size = Vector2(125, 0) layout_mode = 2 text = "Cancel" [connection signal="tree_exited" from="." to="." method="_on_tree_exited"] -[connection signal="pressed" from="PanelContainer/HBox/VBox/HBox/ConfirmButton" to="." method="_on_confirm_button_pressed"] -[connection signal="pressed" from="PanelContainer/HBox/VBox/HBox/DenyButton" to="." method="_on_deny_button_pressed"] -[connection signal="pressed" from="PanelContainer/HBox/VBox/HBox/CancelButton" to="." method="_on_cancel_button_pressed"] +[connection signal="pressed" from="PanelContainer/MarginContainer/HBox/VBox/HBox/ConfirmButton" to="." method="_on_confirm_button_pressed"] +[connection signal="pressed" from="PanelContainer/MarginContainer/HBox/VBox/HBox/DenyButton" to="." method="_on_deny_button_pressed"] +[connection signal="pressed" from="PanelContainer/MarginContainer/HBox/VBox/HBox/CancelButton" to="." method="_on_cancel_button_pressed"] diff --git a/common/windows/welcome_window/welcome_window.tscn b/common/windows/welcome_window/welcome_window.tscn index 7a4bc96d..d6798cad 100644 --- a/common/windows/welcome_window/welcome_window.tscn +++ b/common/windows/welcome_window/welcome_window.tscn @@ -11,7 +11,7 @@ auto_translate_mode = 1 transparent_bg = true initial_position = 2 -size = Vector2i(450, 297) +size = Vector2i(1000, 300) wrap_controls = true transient = true transient_to_focused = true @@ -26,13 +26,12 @@ script = ExtResource("1_hscvo") [node name="PanelContainer" type="PanelContainer" parent="."] clip_children = 2 -anchors_preset = 15 -anchor_right = 1.0 +custom_minimum_size = Vector2(400, 300) +anchors_preset = -1 +anchor_right = 0.4 anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 +size_flags_horizontal = 4 +size_flags_vertical = 0 theme_type_variation = &"OuterPanel" [node name="MarginContainer" type="MarginContainer" parent="PanelContainer"] @@ -42,8 +41,14 @@ layout_mode = 2 layout_mode = 2 theme_type_variation = &"VBoxContainer_Medium" -[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer"] -custom_minimum_size = Vector2(0, 100) +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 + +[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/MarginContainer"] layout_mode = 2 texture = ExtResource("2_ked33") expand_mode = 5 diff --git a/project.godot b/project.godot index 3cb34d31..810df349 100644 --- a/project.godot +++ b/project.godot @@ -55,7 +55,7 @@ window/size/fullscreen=true [editor_plugins] -enabled=PackedStringArray("res://addons/Todo_Manager/plugin.cfg", "res://addons/gdUnit4/plugin.cfg", "res://addons/monologue/plugin.cfg") +enabled=PackedStringArray("res://addons/ColorPreview/plugin.cfg", "res://addons/Todo_Manager/plugin.cfg", "res://addons/gdUnit4/plugin.cfg") [file_customization] diff --git a/scenes/main/characters_section.gd b/scenes/main/characters_section.gd deleted file mode 100644 index 6ee93626..00000000 --- a/scenes/main/characters_section.gd +++ /dev/null @@ -1,14 +0,0 @@ -extends VBoxContainer - -var section_icon := preload("res://ui/assets/icons/character.svg") - - -func clear() -> void: - for child in get_children(): - child.queue_free() - - -func load_items(property: Property) -> void: - clear() - property.setters["flat"] = true - property.show(self) diff --git a/scenes/main/editor.tscn b/scenes/main/editor.tscn index 3b72e8fc..625eac8d 100644 --- a/scenes/main/editor.tscn +++ b/scenes/main/editor.tscn @@ -1,13 +1,12 @@ -[gd_scene load_steps=24 format=3 uid="uid://bqjfdabrxujp7"] +[gd_scene load_steps=21 format=3 uid="uid://bqjfdabrxujp7"] [ext_resource type="Script" uid="uid://q6eg6rid6xqd" path="res://scenes/main/monologue_editor.gd" id="1_ovo1o"] [ext_resource type="PackedScene" uid="uid://dfkqf3wjdnj0m" path="res://common/layouts/editor/editor_section.tscn" id="2_q62vd"] [ext_resource type="Texture2D" uid="uid://dd8v3hpxxk33d" path="res://ui/assets/icons/logo_white.svg" id="3_mei7o"] [ext_resource type="Script" uid="uid://m1wxne1g6kju" path="res://scenes/main/main_menu.gd" id="4_pwbil"] [ext_resource type="Texture2D" uid="uid://hlck6y4i3l5q" path="res://ui/assets/icons/plus.svg" id="5_4jbkx"] -[ext_resource type="Script" uid="uid://yglbu25x1rsy" path="res://scenes/main/characters_section.gd" id="6_mei7o"] +[ext_resource type="PackedScene" uid="uid://mfdu320oy6ex" path="res://common/layouts/editor/list_section/list_section.tscn" id="6_mei7o"] [ext_resource type="Script" uid="uid://bmku341x5gaoe" path="res://scenes/main/add_node_button.gd" id="6_sr1k3"] -[ext_resource type="Script" uid="uid://44fcg1a4cs5" path="res://scenes/main/variables_section.gd" id="7_pwbil"] [ext_resource type="Texture2D" uid="uid://bfmsxfn26cvfn" path="res://ui/assets/icons/character.svg" id="7_xxwqg"] [ext_resource type="Texture2D" uid="uid://b46sqb5g0spae" path="res://ui/assets/icons/variables.svg" id="8_uvwqm"] [ext_resource type="PackedScene" uid="uid://cb3se7h7akt47" path="res://common/layouts/language_switcher/language_switcher.tscn" id="9_kkjrq"] @@ -20,20 +19,7 @@ [ext_resource type="PackedScene" uid="uid://cmpsaafag7cwl" path="res://common/windows/graph_node_picker/graph_node_picker.tscn" id="15_q62vd"] [ext_resource type="PackedScene" uid="uid://cvum3eaenloix" path="res://common/layouts/search_bar/search_bar.tscn" id="16_n4eqx"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1u3ep"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 -bg_color = Color(0.186303, 0.183362, 0.215088, 1) -border_width_left = 1 -border_width_top = 1 -border_width_right = 1 -border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 3 -corner_radius_top_right = 3 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pwbil"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7fun3"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 @@ -49,19 +35,6 @@ corner_radius_top_right = 3 corner_radius_bottom_right = 3 corner_radius_bottom_left = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qe3vh"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 -bg_color = Color(0.225127, 0.224539, 0.257441, 1) -border_width_left = 1 -border_width_right = 1 -border_width_bottom = 1 -border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 - [sub_resource type="GDScript" id="GDScript_lenro"] script/source = "extends Button @@ -99,8 +72,7 @@ dragging_enabled = false [node name="EditorSection" parent="MainContainer/VSplitContainer" instance=ExtResource("2_q62vd")] visible = true layout_mode = 2 -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_pwbil") +theme_override_styles/panel = SubResource("StyleBoxFlat_7fun3") current_tab = 0 tabs_visible = false @@ -144,19 +116,18 @@ split_offset = 250 visible = true custom_minimum_size = Vector2(250, 0) layout_mode = 2 -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_qe3vh") current_tab = 0 -[node name="Characters" type="VBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/EditorSection"] +[node name="Characters" parent="MainContainer/VSplitContainer/HSplitContainer/EditorSection" instance=ExtResource("6_mei7o")] +unique_name_in_owner = true layout_mode = 2 -script = ExtResource("6_mei7o") -metadata/_tab_index = 0 +section_icon = ExtResource("7_xxwqg") -[node name="Variables" type="VBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/EditorSection"] +[node name="Variables" parent="MainContainer/VSplitContainer/HSplitContainer/EditorSection" instance=ExtResource("6_mei7o")] +unique_name_in_owner = true visible = false layout_mode = 2 -script = ExtResource("7_pwbil") +section_icon = ExtResource("8_uvwqm") metadata/_tab_index = 1 [node name="HSplitContainer" type="HSplitContainer" parent="MainContainer/VSplitContainer/HSplitContainer"] @@ -168,8 +139,7 @@ split_offset = -250 visible = true layout_mode = 2 size_flags_horizontal = 3 -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_pwbil") +theme_override_styles/panel = SubResource("StyleBoxFlat_7fun3") current_tab = 0 tabs_visible = false @@ -249,8 +219,7 @@ size_flags_vertical = 3 [node name="EditorSection" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer" instance=ExtResource("2_q62vd")] visible = true layout_mode = 2 -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_pwbil") +theme_override_styles/panel = SubResource("StyleBoxFlat_7fun3") current_tab = 0 tabs_visible = false diff --git a/scenes/main/monologue_editor.gd b/scenes/main/monologue_editor.gd index 51082c87..4552e546 100644 --- a/scenes/main/monologue_editor.gd +++ b/scenes/main/monologue_editor.gd @@ -89,34 +89,38 @@ func get_root_dict(node_list: Array) -> Dictionary: func load_project(path: String, new_graph: bool = false) -> void: var file = FileAccess.open(path, FileAccess.READ) - if file and not graph_switcher.is_file_opened(path): - if new_graph: - graph_switcher.new_graph_edit() - graph_switcher.current.file_path = path # set path first before tab creation - - var data = {} - var text = file.get_as_text() - if text: - data = JSON.parse_string(text) - if not data: - data = _to_dict() - save() - - var converter := NodeConverter.new() - graph_switcher.current.languages = data.get("Languages", []) # load language before tab - graph_switcher.add_tab(path.get_file()) - graph_switcher.current.clear() - graph_switcher.current.name = path.get_file().trim_suffix(".json") - graph_switcher.current.characters = converter.convert_characters(data.get("Characters")) - graph_switcher.current.variables = data.get("Variables") - graph_switcher.current.data = data - - var node_list = data.get("ListNodes") - _load_nodes(node_list) - _connect_nodes(node_list) - graph_switcher.add_root() - graph_switcher.current.update_node_positions() - GlobalSignal.emit("load_successful", [path]) + if not file or graph_switcher.is_file_opened(path): + return + + if new_graph: + graph_switcher.new_graph_edit() + graph_switcher.current.file_path = path # set path first before tab creation + + var data = {} + var text = file.get_as_text() + if text: + data = JSON.parse_string(text) + if not data: + data = _to_dict() + save() + + var converter := NodeConverter.new() + graph_switcher.current.languages = data.get("Languages", []) # load language before tab + graph_switcher.add_tab(path.get_file()) + graph_switcher.current.clear() + graph_switcher.current.name = path.get_file().trim_suffix(".json") + graph_switcher.current.characters = converter.convert_characters(data.get("Characters")) + graph_switcher.current.variables = data.get("Variables") + graph_switcher.current.data = data + + var node_list = data.get("ListNodes") + _load_nodes(node_list) + _connect_nodes(node_list) + graph_switcher.add_root() + graph_switcher.current.update_node_positions() + GlobalSignal.emit("load_successful", [path]) + + load_editor_sections() ## Reload the current graph edit and inspector panel. @@ -143,8 +147,15 @@ func refresh(node: MonologueGraphNode = null, affected_properties: PackedStringA if inspector_panel_node.visible: var current_node = inspector_panel_node.selected_node inspector_panel_node.on_graph_node_selected(current_node, true) - - $MainContainer/VSplitContainer/HSplitContainer/EditorSection/Characters.load_items(graph_switcher.current.get_root_node().characters) + + await get_tree().process_frame + load_editor_sections() + + +func load_editor_sections() -> void: + var root_node: RootNode = graph_switcher.current.get_root_node() + %Characters.load_items(root_node.characters) + %Variables.load_items(root_node.variables) func save(): diff --git a/scenes/main/variables_section.gd b/scenes/main/variables_section.gd deleted file mode 100644 index a2da93cf..00000000 --- a/scenes/main/variables_section.gd +++ /dev/null @@ -1,3 +0,0 @@ -extends VBoxContainer - -var section_icon := preload("res://ui/assets/icons/variables.svg") diff --git a/scenes/main/variables_section.gd.uid b/scenes/main/variables_section.gd.uid deleted file mode 100644 index 8bc10a17..00000000 --- a/scenes/main/variables_section.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://44fcg1a4cs5 diff --git a/ui/theme_default/main.tres b/ui/theme_default/main.tres index 91777c64..183677d1 100644 --- a/ui/theme_default/main.tres +++ b/ui/theme_default/main.tres @@ -1095,6 +1095,8 @@ EditorSection/colors/font_disabled_color = Color(0.890196, 0.894118, 0.921569, 0 EditorSection/colors/font_hover_color = Color(0.890196, 0.894118, 0.921569, 1) EditorSection/colors/font_selected_color = Color(0.890196, 0.894118, 0.921569, 1) EditorSection/colors/font_unselected_color = Color(0.890196, 0.894118, 0.921569, 0.8) +EditorSection/constants/icon_max_width = 14 +EditorSection/constants/icon_separation = 4 EditorSection/constants/side_margin = 1 EditorSection/font_sizes/font_size = 14 EditorSection/styles/panel_focus = SubResource("StyleBoxFlat_vvgve") From 9af9ed0b25f00d2f7823b74a613b2fa46717557b Mon Sep 17 00:00:00 2001 From: Atomic Junky <67459553+atomic-junky@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:55:43 +0200 Subject: [PATCH 003/103] [BUGGED] Placing characters and variables in the GraphEdit --- common/layouts/editor/editor_section.tscn | 8 +- .../layouts/graph_edit/custom_graph_edit.gd | 419 ++++++++++++++++ .../graph_edit/custom_graph_edit.gd.uid | 1 + .../graph_edit/monologue_graph_edit.gd | 452 ++---------------- common/ui/fields/monologue_argument.gd | 10 +- common/ui/fields/monologue_variable.gd | 21 +- .../abstract_character/abstract_character.gd | 21 +- nodes/abstract_variable/abstract_variable.gd | 10 +- nodes/character_node/character_node.gd | 4 +- nodes/event_node/event_node.gd | 2 +- nodes/root_node/root_node.gd | 74 --- nodes/sentence_node/sentence_node.gd | 10 +- scenes/main/editor.tscn | 41 +- scenes/main/monologue_editor.gd | 14 +- 14 files changed, 547 insertions(+), 540 deletions(-) create mode 100644 common/layouts/graph_edit/custom_graph_edit.gd create mode 100644 common/layouts/graph_edit/custom_graph_edit.gd.uid diff --git a/common/layouts/editor/editor_section.tscn b/common/layouts/editor/editor_section.tscn index 444e10e0..152e3b8a 100644 --- a/common/layouts/editor/editor_section.tscn +++ b/common/layouts/editor/editor_section.tscn @@ -2,7 +2,7 @@ [ext_resource type="Script" uid="uid://ijejsk35dal" path="res://common/layouts/editor/editor_section.gd" id="1_1u3ep"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qd482"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1u3ep"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 @@ -15,7 +15,7 @@ border_color = Color(0.890196, 0.894118, 0.921569, 0.2) corner_radius_top_left = 3 corner_radius_top_right = 3 -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1uy7d"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qe3vh"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 @@ -34,8 +34,8 @@ offset_right = 40.0 offset_bottom = 40.0 focus_mode = 1 theme_type_variation = &"EditorSection" -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_qd482") -theme_override_styles/panel = SubResource("StyleBoxFlat_1uy7d") +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_qe3vh") script = ExtResource("1_1u3ep") [connection signal="child_entered_tree" from="." to="." method="_on_child_entered_tree"] diff --git a/common/layouts/graph_edit/custom_graph_edit.gd b/common/layouts/graph_edit/custom_graph_edit.gd new file mode 100644 index 00000000..f8065c01 --- /dev/null +++ b/common/layouts/graph_edit/custom_graph_edit.gd @@ -0,0 +1,419 @@ +## Represents the graph area which creates and connects MonologueGraphNodes. +class_name CustomGraphEdit extends GraphEdit + +var close_button_scene = preload("res://common/ui/buttons/close_button.tscn") +var base_options = {} +var data: Dictionary +var file_path: String +var undo_redo := HistoryHandler.new() +var version = undo_redo.get_version() + +var languages = [] + +var active_graphnode: MonologueGraphNode # for tab-switching purpose +var connecting_mode: bool +var current_language_index: int +var moving_mode: bool +var recorded_positions: Dictionary = {} # for undo/redo positoning purpose +var selected_nodes: Array[MonologueGraphNode] = [] # for group delete +var mouse_hovering: bool = false + + +func _ready() -> void: + var auto_arrange_button = get_menu_hbox().get_children().back() + auto_arrange_button.connect("pressed", _on_auto_arrange_nodes) + + center_offset.call_deferred() + + # Hide scroll bar + for child in get_children(true): + if child is GraphNode: + continue + + for subchild in child.get_children(true): + if subchild is not ScrollBar: + continue + + for sb_name in ["grabber", "scroll"]: + subchild.add_theme_stylebox_override(sb_name, StyleBoxEmpty.new()) + +func _on_add_btn() -> void: + GlobalSignal.emit("select_new_node") + + +func center_offset(): + var base_offset = Vector2.ZERO + var root_node: RootNode = get_root_node() + if root_node: + base_offset = root_node.position_offset + (root_node.size / 2) * zoom + + scroll_offset = -size / 2 + base_offset + + +func _input(event: InputEvent) -> void: + moving_mode = ( + Input.is_action_pressed("Select") + and event is InputEventMouseMotion + and not selected_nodes.is_empty() + ) + + +func _gui_input(_event: InputEvent) -> void: + if not mouse_hovering: + return + + var cursor_drag: bool = false + var cursor_hand_closed: bool = false + + if Input.is_action_pressed("Spacebar"): + cursor_drag = true + if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): + cursor_hand_closed = true + + if Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE): + cursor_hand_closed = true + + if cursor_hand_closed: + DisplayServer.cursor_set_custom_image(Cursor.closed_hand) + elif cursor_drag: + DisplayServer.cursor_set_custom_image(Cursor.hand) + else: + DisplayServer.cursor_set_custom_image(Cursor.arrow) + + +## Adds a node of the given type to this graph. +func add_node( + node_type, record: bool = true, picker: GraphNodePicker = null +) -> Array[MonologueGraphNode]: + # if adding from picker, track existing to_nodes of the picker_from_node + var picker_to_names = [] + if picker: + for picker_to_node in get_all_connections_from_slot(picker.from_node, picker.from_port): + picker_to_names.append(picker_to_node.name) + + var node_scene = Constants.NODE_SCENES.get(node_type) + var new_node = node_scene.instantiate() + + # created_nodes include auxilliary nodes from new_node, such as BridgeOut + var created_nodes = new_node.add_to(self) + + # if enabled, track the addition of created_nodes into the graph history + if record: + var addition = AddNodeHistory.new(self, created_nodes) + if not picker_to_names.is_empty(): + addition.picker_from_node = picker.from_node + addition.picker_from_port = picker.from_port + addition.picker_to_names = picker_to_names + + undo_redo.create_action("Add new %s" % [new_node.node_type]) + undo_redo.add_prepared_history(addition) + undo_redo.commit_action(false) + + return created_nodes + + +func clear(): + for node in get_nodes(): + node.queue_free() + clear_connections() + + +## Disconnect all outbound connections of the given graphnode and port. +func disconnect_outbound_from_node(from_node: StringName, from_port: int) -> void: + for connection in get_connection_list(): + if connection.get("from_node") == from_node: + var to_node = connection.get("to_node") + var to_port = connection.get("to_port") + disconnect_node(from_node, from_port, to_node, to_port) + + +## Deletes the given graphnode and return its dictionary data. +func free_graphnode(node: MonologueGraphNode) -> Dictionary: + var inbound_connections = get_all_inbound_connections(node.name) + var outbound_connections = get_all_outbound_connections(node.name) + for c in inbound_connections + outbound_connections: + disconnect_node(c.get("from_node"), c.get("from_port"), c.get("to_node"), c.get("to_port")) + + var node_data = node._to_dict() + if "options" in node: + # tag options into the node_data + node_data.merge({"Options": node.options.value}) + if active_graphnode == node: + active_graphnode = null + + selected_nodes.erase(node) + recorded_positions.erase(node) + node.queue_free() + # if side panel is showing this node, close it since it's gone + GlobalSignal.emit("close_panel", [node]) + return node_data + + +## Find all other connections that connect to the given graphnode. +func get_all_inbound_connections(from_node: StringName) -> Array: + var node_connections = [] + for connection in get_connection_list(): + if connection.get("to_node") == from_node: + node_connections.append(connection) + return node_connections + + +## Find all connections that originate from the given graphnode. +func get_all_outbound_connections(from_node: StringName) -> Array: + var node_connections = [] + for connection in get_connection_list(): + if connection.get("from_node") == from_node: + node_connections.append(connection) + return node_connections + + +## Find connections of the given [param from_node] at its [param from_port]. +func get_all_connections_from_slot(from_node: StringName, from_port: int) -> Array: + var node_connections = [] + for connection in get_connection_list(): + if connection.get("from_node") == from_node and connection.get("from_port") == from_port: + var to = get_node_or_null(NodePath(connection.get("to_node"))) + node_connections.append(to) + return node_connections + + +func get_free_bridge_number(_n = 1, lp_max = 50) -> int: + for node in get_nodes(): + if ( + (node.node_type == "NodeBridgeOut" or node.node_type == "NodeBridgeIn") + and node.number_selector.value == _n + ): + if lp_max <= 0: + return _n + + return get_free_bridge_number(_n + 1, lp_max - 1) + return _n + + +func get_linked_bridge_node(target_number) -> MonologueGraphNode: + for node in get_nodes(): + if node.node_type == "NodeBridgeOut" and node.number_selector.value == target_number: + return node + return null + + +func get_root_node() -> RootNode: + for node in get_nodes(): + if node is RootNode: + return node + return null + + +## Find a graph node by ID. Includes OptionNodes. +func get_node_by_id(id: String) -> MonologueGraphNode: + if not id.is_empty(): + for node in get_nodes(): + if node.id.value == id: + return node + elif node is ChoiceNode: + var option = node.get_option_by_id(id) + if option: + return option + return null + + +func is_unsaved() -> bool: + return version != undo_redo.get_version() + + +## Connect picker_from_node to [param node] if needed, reposition nodes. +func pick_and_center( + nodes: Array[MonologueGraphNode], picker: GraphNodePicker +) -> PackedStringArray: + var to_names = [] + var offset = (scroll_offset + size/2) / zoom # center of graph + + if picker.from_node and picker.from_port != -1: + if nodes[0].get_input_port_count() > 0: + var from_node = picker.from_node + var from_port = picker.from_port + disconnect_outbound_from_node(from_node, from_port) + propagate_connection(from_node, from_port, nodes[0].name, 0) + if picker.graph_release: + offset = (picker.release + scroll_offset) / zoom + + picker.flush() + + for node in nodes: + node.position_offset = offset + + post_node_offset.call_deferred(nodes) + return to_names + + +func post_node_offset(nodes: Array[MonologueGraphNode]) -> void: + for node in nodes: + node.position_offset -= node.size/2 + + if not nodes[0].is_slot_enabled_left(0): + return + + var first_port_pos = nodes[0].get_input_port_position(0) + for node in nodes: + node.position_offset -= first_port_pos + node.position_offset = round(node.position_offset / snapping_distance) * snapping_distance + + +## Connects/disconnects and updates a given connection's NextID if possible. +## If [param next] is true, establish connection and propagate NextIDs. +## If it is false, destroy connection and clear all linked NextIDs. +func propagate_connection(from_node, from_port, to_node, to_port, next = true) -> void: + if next: + connect_node(from_node, from_port, to_node, to_port) + + else: + disconnect_node(from_node, from_port, to_node, to_port) + + var graph_node = get_node_or_null(NodePath(from_node)) + if graph_node and graph_node.has_method("update_next_id"): + if next: + var next_node = get_node_or_null(NodePath(to_node)) + graph_node.update_next_id(from_port, next_node) + else: + graph_node.update_next_id(from_port, null) + + +func trigger_delete(): + if not active_graphnode and selected_nodes: + var root_filter = func(n): return n is not RootNode + var selected_copy = selected_nodes.duplicate().filter(root_filter) + var delete_history = DeleteNodeHistory.new(self, selected_copy) + undo_redo.create_action("Delete %s" % str(selected_copy)) + undo_redo.add_prepared_history(delete_history) + undo_redo.commit_action() + + +## Checks and ensure graph is ready before triggering undo. +func trigger_undo() -> void: + if not connecting_mode: + undo_redo.undo() + + +## Checks and ensure graph is ready before triggering redo. +func trigger_redo() -> void: + if not connecting_mode: + undo_redo.redo() + + +func update_node_positions() -> void: + var affected_nodes = selected_nodes if selected_nodes else get_nodes() + for node in affected_nodes: + recorded_positions[node] = node.position_offset + + +func update_version() -> void: + version = undo_redo.get_version() + + +func _on_auto_arrange_nodes() -> void: + var affected = selected_nodes if selected_nodes else get_nodes() + var changed = affected.filter(func(n): return n.position_offset != recorded_positions[n]) + if changed and affected.size() > 1: + undo_redo.create_action("Auto arrange nodes") + for node in changed: + undo_redo.add_do_property(node, "position_offset", node.position_offset) + undo_redo.add_undo_property(node, "position_offset", recorded_positions[node]) + undo_redo.commit_action(false) + update_node_positions() + + +func _on_child_entered_tree(node: Node) -> void: + if node is MonologueGraphNode: + if node is RootNode: + return + + if not node.show_close_button: + return + + var node_header = node.get_children(true)[0] + var close_button: TextureButton = close_button_scene.instantiate() + + var close_callback = func(): + var delete_history = DeleteNodeHistory.new(self, [node]) + var message = "Delete %s (id: %s)" + undo_redo.create_action(message % [node.node_type, node.id.value]) + undo_redo.add_prepared_history(delete_history) + undo_redo.commit_action(false) + selected_nodes.erase(node) + recorded_positions.erase(node) + free_graphnode(node) + + close_button.connect("pressed", close_callback) + node_header.add_child(close_button) + + +func _on_connection_drag_started(_from_node, _from_port, _is_output) -> void: + connecting_mode = true + + +func _on_connection_drag_ended() -> void: + connecting_mode = false + + +func _on_connection_request(from_node, from_port, to_node, to_port) -> void: + # so check to make sure there are no other connections before connecting + if get_all_connections_from_slot(from_node, from_port).size() <= 0: + var arguments = [from_node, from_port, to_node, to_port] + var message = "Connect %s port %d to %s port %d" + undo_redo.create_action(message % arguments) + undo_redo.add_do_method(propagate_connection.bindv(arguments)) + undo_redo.add_undo_method(propagate_connection.bindv(arguments + [false])) + undo_redo.commit_action() + + +func _on_disconnection_request(from_node, from_port, to_node, to_port) -> void: + var arguments = [from_node, from_port, to_node, to_port] + var message = "Disconnect %s from %s port %d" + undo_redo.create_action(message % [to_node, from_node, from_port]) + undo_redo.add_do_method(propagate_connection.bindv(arguments + [false])) + undo_redo.add_undo_method(propagate_connection.bindv(arguments)) + undo_redo.commit_action() + + +func _on_connection_to_empty(node: String, port: int, release: Vector2) -> void: + var center = (get_local_mouse_position() + scroll_offset) / zoom + var graph_release = (release + scroll_offset) / zoom + GlobalSignal.emit("enable_picker_mode", [node, port, release, graph_release, center]) + + +func _on_gui_input(event: InputEvent) -> void: + # when the user clicks on the graph edit, close unnecessary stuff + if ( + event is InputEventMouseButton + and event.is_pressed() + and event.button_index == MOUSE_BUTTON_LEFT + ): + GlobalSignal.emit("show_languages", [false]) + + +func _on_node_selected(node) -> void: + if node is MonologueGraphNode: + selected_nodes.append(node) + + +func _on_node_deselected(node) -> void: + recorded_positions[node] = node.position_offset + selected_nodes.erase(node) + active_graphnode = null # when a deselection happens, clear active node + + +func get_nodes() -> Array[MonologueGraphNode]: + var list: Array[MonologueGraphNode] = [] + for node in get_children(): + if node is MonologueGraphNode: + list.append(node) + return list + + +func _on_mouse_entered() -> void: + mouse_hovering = true + + +func _on_mouse_exited() -> void: + DisplayServer.cursor_set_custom_image(null) + mouse_hovering = false diff --git a/common/layouts/graph_edit/custom_graph_edit.gd.uid b/common/layouts/graph_edit/custom_graph_edit.gd.uid new file mode 100644 index 00000000..4401c67f --- /dev/null +++ b/common/layouts/graph_edit/custom_graph_edit.gd.uid @@ -0,0 +1 @@ +uid://dfy1ptnvfis7m diff --git a/common/layouts/graph_edit/monologue_graph_edit.gd b/common/layouts/graph_edit/monologue_graph_edit.gd index 2a24a655..a89c29e2 100644 --- a/common/layouts/graph_edit/monologue_graph_edit.gd +++ b/common/layouts/graph_edit/monologue_graph_edit.gd @@ -1,422 +1,70 @@ -## Represents the graph area which creates and connects MonologueGraphNodes. -class_name MonologueGraphEdit extends GraphEdit +class_name MonologueGraphEdit extends CustomGraphEdit -var close_button_scene = preload("res://common/ui/buttons/close_button.tscn") -var base_options = {} -var data: Dictionary -var file_path: String -var undo_redo := HistoryHandler.new() -var version = undo_redo.get_version() - -var languages = [] -var characters = [] -var variables = [] - -var active_graphnode: MonologueGraphNode # for tab-switching purpose -var connecting_mode: bool -var current_language_index: int -var moving_mode: bool -var recorded_positions: Dictionary = {} # for undo/redo positoning purpose -var selected_nodes: Array[MonologueGraphNode] = [] # for group delete -var mouse_hovering: bool = false +var characters := Property.new(MonologueGraphNode.LIST, {}, []) +var variables := Property.new(MonologueGraphNode.LIST, {}, []) +var _character_references = [] +var _variable_references = [] func _ready() -> void: - var auto_arrange_button = get_menu_hbox().get_children().back() - auto_arrange_button.connect("pressed", _on_auto_arrange_nodes) - - center_offset.call_deferred() - - # Hide scroll bar - for child in get_children(true): - if child is GraphNode: - continue - - for subchild in child.get_children(true): - if subchild is not ScrollBar: - continue - - for sb_name in ["grabber", "scroll"]: - subchild.add_theme_stylebox_override(sb_name, StyleBoxEmpty.new()) - - -func _on_add_btn() -> void: - GlobalSignal.emit("select_new_node") - - -func center_offset(): - var base_offset = Vector2.ZERO - var root_node: RootNode = get_root_node() - if root_node: - base_offset = root_node.position_offset + (root_node.size / 2) * zoom - - scroll_offset = -size / 2 + base_offset - - -func _input(event: InputEvent) -> void: - moving_mode = ( - Input.is_action_pressed("Select") - and event is InputEventMouseMotion - and not selected_nodes.is_empty() - ) - - -func _gui_input(_event: InputEvent) -> void: - if not mouse_hovering: - return - - var cursor_drag: bool = false - var cursor_hand_closed: bool = false - - if Input.is_action_pressed("Spacebar"): - cursor_drag = true - if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): - cursor_hand_closed = true - - if Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE): - cursor_hand_closed = true - - if cursor_hand_closed: - DisplayServer.cursor_set_custom_image(Cursor.closed_hand) - elif cursor_drag: - DisplayServer.cursor_set_custom_image(Cursor.hand) - else: - DisplayServer.cursor_set_custom_image(Cursor.arrow) - - -## Adds a node of the given type to this graph. -func add_node( - node_type, record: bool = true, picker: GraphNodePicker = null -) -> Array[MonologueGraphNode]: - # if adding from picker, track existing to_nodes of the picker_from_node - var picker_to_names = [] - if picker: - for picker_to_node in get_all_connections_from_slot(picker.from_node, picker.from_port): - picker_to_names.append(picker_to_node.name) - - var node_scene = Constants.NODE_SCENES.get(node_type) - var new_node = node_scene.instantiate() - - # created_nodes include auxilliary nodes from new_node, such as BridgeOut - var created_nodes = new_node.add_to(self) - - # if enabled, track the addition of created_nodes into the graph history - if record: - var addition = AddNodeHistory.new(self, created_nodes) - if not picker_to_names.is_empty(): - addition.picker_from_node = picker.from_node - addition.picker_from_port = picker.from_port - addition.picker_to_names = picker_to_names - - undo_redo.create_action("Add new %s" % [new_node.node_type]) - undo_redo.add_prepared_history(addition) - undo_redo.commit_action(false) - - return created_nodes - - -func clear(): - for node in get_nodes(): - node.queue_free() - clear_connections() - - -## Disconnect all outbound connections of the given graphnode and port. -func disconnect_outbound_from_node(from_node: StringName, from_port: int) -> void: - for connection in get_connection_list(): - if connection.get("from_node") == from_node: - var to_node = connection.get("to_node") - var to_port = connection.get("to_port") - disconnect_node(from_node, from_port, to_node, to_port) - - -## Deletes the given graphnode and return its dictionary data. -func free_graphnode(node: MonologueGraphNode) -> Dictionary: - var inbound_connections = get_all_inbound_connections(node.name) - var outbound_connections = get_all_outbound_connections(node.name) - for c in inbound_connections + outbound_connections: - disconnect_node(c.get("from_node"), c.get("from_port"), c.get("to_node"), c.get("to_port")) - - var node_data = node._to_dict() - if "options" in node: - # tag options into the node_data - node_data.merge({"Options": node.options.value}) - if active_graphnode == node: - active_graphnode = null - - selected_nodes.erase(node) - recorded_positions.erase(node) - node.queue_free() - # if side panel is showing this node, close it since it's gone - GlobalSignal.emit("close_panel", [node]) - return node_data - - -## Find all other connections that connect to the given graphnode. -func get_all_inbound_connections(from_node: StringName) -> Array: - var node_connections = [] - for connection in get_connection_list(): - if connection.get("to_node") == from_node: - node_connections.append(connection) - return node_connections - - -## Find all connections that originate from the given graphnode. -func get_all_outbound_connections(from_node: StringName) -> Array: - var node_connections = [] - for connection in get_connection_list(): - if connection.get("from_node") == from_node: - node_connections.append(connection) - return node_connections - - -## Find connections of the given [param from_node] at its [param from_port]. -func get_all_connections_from_slot(from_node: StringName, from_port: int) -> Array: - var node_connections = [] - for connection in get_connection_list(): - if connection.get("from_node") == from_node and connection.get("from_port") == from_port: - var to = get_node_or_null(NodePath(connection.get("to_node"))) - node_connections.append(to) - return node_connections - - -func get_free_bridge_number(_n = 1, lp_max = 50) -> int: - for node in get_nodes(): - if ( - (node.node_type == "NodeBridgeOut" or node.node_type == "NodeBridgeIn") - and node.number_selector.value == _n - ): - if lp_max <= 0: - return _n - - return get_free_bridge_number(_n + 1, lp_max - 1) - return _n - - -func get_linked_bridge_node(target_number) -> MonologueGraphNode: - for node in get_nodes(): - if node.node_type == "NodeBridgeOut" and node.number_selector.value == target_number: - return node - return null + super._ready() + characters.setters["add_callback"] = add_character + characters.setters["get_callback"] = get_characters + characters.connect("preview", load_character) -func get_root_node() -> RootNode: - for node in get_nodes(): - if node is RootNode: - return node - return null + variables.setters["add_callback"] = add_variable + variables.setters["get_callback"] = get_variables + variables.connect("preview", load_variables) -## Find a graph node by ID. Includes OptionNodes. -func get_node_by_id(id: String) -> MonologueGraphNode: - if not id.is_empty(): - for node in get_nodes(): - if node.id.value == id: - return node - elif node is ChoiceNode: - var option = node.get_option_by_id(id) - if option: - return option - return null +func add_character(data: Dictionary = {}) -> MonologueCharacter: + var character = MonologueCharacter.new(self) + if data: + character._from_dict(data) + character.idx.value = _character_references.size() + character.character.setters["character_index"] = character.idx.value + _character_references.append(character) + return character -func is_unsaved() -> bool: - return version != undo_redo.get_version() +func add_variable(data: Dictionary = {}) -> MonologueVariable: + var variable = MonologueVariable.new(self) + if data: + variable._from_dict(data) + variable.index = _variable_references.size() + _variable_references.append(variable) + return variable -## Connect picker_from_node to [param node] if needed, reposition nodes. -func pick_and_center( - nodes: Array[MonologueGraphNode], picker: GraphNodePicker -) -> PackedStringArray: - var to_names = [] - var offset = (scroll_offset + size/2) / zoom # center of graph - if picker.from_node and picker.from_port != -1: - if nodes[0].get_input_port_count() > 0: - var from_node = picker.from_node - var from_port = picker.from_port - disconnect_outbound_from_node(from_node, from_port) - propagate_connection(from_node, from_port, nodes[0].name, 0) - if picker.graph_release: - offset = (picker.release + scroll_offset) / zoom - - picker.flush() - - for node in nodes: - node.position_offset = offset - - post_node_offset.call_deferred(nodes) - return to_names - - -func post_node_offset(nodes: Array[MonologueGraphNode]) -> void: - for node in nodes: - node.position_offset -= node.size/2 +func get_characters() -> Array: + return _character_references - if not nodes[0].is_slot_enabled_left(0): - return - - var first_port_pos = nodes[0].get_input_port_position(0) - for node in nodes: - node.position_offset -= first_port_pos - node.position_offset = round(node.position_offset / snapping_distance) * snapping_distance - - -## Connects/disconnects and updates a given connection's NextID if possible. -## If [param next] is true, establish connection and propagate NextIDs. -## If it is false, destroy connection and clear all linked NextIDs. -func propagate_connection(from_node, from_port, to_node, to_port, next = true) -> void: - if next: - connect_node(from_node, from_port, to_node, to_port) - - else: - disconnect_node(from_node, from_port, to_node, to_port) - - var graph_node = get_node_or_null(NodePath(from_node)) - if graph_node and graph_node.has_method("update_next_id"): - if next: - var next_node = get_node_or_null(NodePath(to_node)) - graph_node.update_next_id(from_port, next_node) - else: - graph_node.update_next_id(from_port, null) - - -func trigger_delete(): - if not active_graphnode and selected_nodes: - var root_filter = func(n): return n is not RootNode - var selected_copy = selected_nodes.duplicate().filter(root_filter) - var delete_history = DeleteNodeHistory.new(self, selected_copy) - undo_redo.create_action("Delete %s" % str(selected_copy)) - undo_redo.add_prepared_history(delete_history) - undo_redo.commit_action() - - -## Checks and ensure graph is ready before triggering undo. -func trigger_undo() -> void: - if not connecting_mode: - undo_redo.undo() - - -## Checks and ensure graph is ready before triggering redo. -func trigger_redo() -> void: - if not connecting_mode: - undo_redo.redo() - - -func update_node_positions() -> void: - var affected_nodes = selected_nodes if selected_nodes else get_nodes() - for node in affected_nodes: - recorded_positions[node] = node.position_offset - - -func update_version() -> void: - version = undo_redo.get_version() - - -func _on_auto_arrange_nodes() -> void: - var affected = selected_nodes if selected_nodes else get_nodes() - var changed = affected.filter(func(n): return n.position_offset != recorded_positions[n]) - if changed and affected.size() > 1: - undo_redo.create_action("Auto arrange nodes") - for node in changed: - undo_redo.add_do_property(node, "position_offset", node.position_offset) - undo_redo.add_undo_property(node, "position_offset", recorded_positions[node]) - undo_redo.commit_action(false) - update_node_positions() - - -func _on_child_entered_tree(node: Node) -> void: - if node is MonologueGraphNode: - if node is RootNode: - return - - if not node.show_close_button: - return - - var node_header = node.get_children(true)[0] - var close_button: TextureButton = close_button_scene.instantiate() - - var close_callback = func(): - var delete_history = DeleteNodeHistory.new(self, [node]) - var message = "Delete %s (id: %s)" - undo_redo.create_action(message % [node.node_type, node.id.value]) - undo_redo.add_prepared_history(delete_history) - undo_redo.commit_action(false) - selected_nodes.erase(node) - recorded_positions.erase(node) - free_graphnode(node) - - close_button.connect("pressed", close_callback) - node_header.add_child(close_button) - - -func _on_connection_drag_started(_from_node, _from_port, _is_output) -> void: - connecting_mode = true - - -func _on_connection_drag_ended() -> void: - connecting_mode = false - - -func _on_connection_request(from_node, from_port, to_node, to_port) -> void: - # so check to make sure there are no other connections before connecting - if get_all_connections_from_slot(from_node, from_port).size() <= 0: - var arguments = [from_node, from_port, to_node, to_port] - var message = "Connect %s port %d to %s port %d" - undo_redo.create_action(message % arguments) - undo_redo.add_do_method(propagate_connection.bindv(arguments)) - undo_redo.add_undo_method(propagate_connection.bindv(arguments + [false])) - undo_redo.commit_action() - - -func _on_disconnection_request(from_node, from_port, to_node, to_port) -> void: - var arguments = [from_node, from_port, to_node, to_port] - var message = "Disconnect %s from %s port %d" - undo_redo.create_action(message % [to_node, from_node, from_port]) - undo_redo.add_do_method(propagate_connection.bindv(arguments + [false])) - undo_redo.add_undo_method(propagate_connection.bindv(arguments)) - undo_redo.commit_action() - - -func _on_connection_to_empty(node: String, port: int, release: Vector2) -> void: - var center = (get_local_mouse_position() + scroll_offset) / zoom - var graph_release = (release + scroll_offset) / zoom - GlobalSignal.emit("enable_picker_mode", [node, port, release, graph_release, center]) - - -func _on_gui_input(event: InputEvent) -> void: - # when the user clicks on the graph edit, close unnecessary stuff - if ( - event is InputEventMouseButton - and event.is_pressed() - and event.button_index == MOUSE_BUTTON_LEFT - ): - GlobalSignal.emit("show_languages", [false]) - - -func _on_node_selected(node) -> void: - if node is MonologueGraphNode: - selected_nodes.append(node) - - -func _on_node_deselected(node) -> void: - recorded_positions[node] = node.position_offset - selected_nodes.erase(node) - active_graphnode = null # when a deselection happens, clear active node + +func get_variables() -> Array: + return _variable_references -func get_nodes() -> Array[MonologueGraphNode]: - var list: Array[MonologueGraphNode] = [] - for node in get_children(): - if node is MonologueGraphNode: - list.append(node) - return list +## Perform initial loading of speakers and set indexes correctly. +func load_character(new_character_list: Array): + _character_references.clear() + var ascending = func(a, b): return a.get("EditorIndex") < b.get("EditorIndex") + new_character_list.sort_custom(ascending) + for speaker_data in new_character_list: + add_character(speaker_data) + if _character_references.is_empty(): + var narrator = add_character() + narrator.character.value["Name"] = "_NARRATOR" + narrator.protected.value = true + new_character_list.append(narrator._to_dict()) -func _on_mouse_entered() -> void: - mouse_hovering = true + characters.value = new_character_list -func _on_mouse_exited() -> void: - DisplayServer.cursor_set_custom_image(null) - mouse_hovering = false +func load_variables(new_variable_list: Array): + _variable_references.clear() + for variable in new_variable_list: + add_variable(variable) + variables.value = new_variable_list diff --git a/common/ui/fields/monologue_argument.gd b/common/ui/fields/monologue_argument.gd index 4471f18d..6d2ca256 100644 --- a/common/ui/fields/monologue_argument.gd +++ b/common/ui/fields/monologue_argument.gd @@ -7,21 +7,21 @@ var last_string: String func _init(node: MonologueGraphNode): - super(node) + super(node.get_parent()) type.callers["set_items"][0].append({"id": 3, "text": "Variable"}) #type.callers["set_icons"][0][3] = load("res://ui/assets/icons/bool_icon.png") value.connect("preview", record_morph) func change(_old_value: Variant, new_value: Variant, property: String) -> void: - var old_list = bound_node.arguments.value.duplicate(true) - var new_list = bound_node.arguments.value.duplicate(true) + var old_list = graph.arguments.value.duplicate(true) + var new_list = graph.arguments.value.duplicate(true) new_list[index][property.capitalize()] = new_value # bound_node can be deleted, so we need to use PropertyChange here graph.undo_redo.create_action("Arguments => %s" % new_value) var pc = PropertyChange.new("arguments", old_list, new_list) - var ph = PropertyHistory.new(graph, graph.get_path_to(bound_node), [pc]) + var ph = PropertyHistory.new(graph, graph.get_path(), [pc]) graph.undo_redo.add_prepared_history(ph) graph.undo_redo.commit_action() @@ -79,7 +79,7 @@ func _type_morph(selected_type: String = type.value): if selected_type == "Variable": value.callers["set_items"] = [graph.variables, "Name", "ID", "Type"] if graph.variables and value.value is not String: - value.value = graph.variables[0].get("Name") + value.value = graph.variables.value[0].get("Name") value.morph(MonologueGraphNode.DROPDOWN) else: value.callers.erase("set_items") diff --git a/common/ui/fields/monologue_variable.gd b/common/ui/fields/monologue_variable.gd index be7a9069..3179bac6 100644 --- a/common/ui/fields/monologue_variable.gd +++ b/common/ui/fields/monologue_variable.gd @@ -5,13 +5,11 @@ var type := Property.new(MonologueGraphNode.DROPDOWN, {}, "Boolean") var value := Property.new(MonologueGraphNode.TOGGLE, {}, false) var index: int = -1 -var bound_node: MonologueGraphNode var graph: MonologueGraphEdit -func _init(node: MonologueGraphNode) -> void: - bound_node = node - graph = node.get_graph_edit() +func _init(graph_edit: MonologueGraphEdit) -> void: + graph = graph_edit type.callers["set_items"] = [ [ @@ -30,23 +28,20 @@ func _init(node: MonologueGraphNode) -> void: type.connect("shown", _type_morph) type.connect("change", change.bind("type")) - type.connect("display", graph.set_selected.bind(bound_node)) name.connect("change", change.bind("name")) - name.connect("display", graph.set_selected.bind(bound_node)) value.connect("change", change.bind("value")) - value.connect("display", graph.set_selected.bind(bound_node)) func change(_old_value: Variant, new_value: Variant, property: String) -> void: - var old_list = bound_node.variables.value.duplicate(true) - var new_list = bound_node.variables.value.duplicate(true) + var old_list = graph.variables.value.duplicate(true) + var new_list = graph.variables.value.duplicate(true) new_list[index][property.capitalize()] = new_value graph.undo_redo.create_action("Variables => %s" % new_value) - graph.undo_redo.add_do_property(bound_node.variables, "value", new_list) - graph.undo_redo.add_do_method(bound_node.variables.propagate.bind(new_list)) - graph.undo_redo.add_undo_property(bound_node.variables, "value", old_list) - graph.undo_redo.add_undo_method(bound_node.variables.propagate.bind(old_list)) + graph.undo_redo.add_do_property(graph.variables, "value", new_list) + graph.undo_redo.add_do_method(graph.variables.propagate.bind(new_list)) + graph.undo_redo.add_undo_property(graph.variables, "value", old_list) + graph.undo_redo.add_undo_method(graph.variables.propagate.bind(old_list)) graph.undo_redo.commit_action() diff --git a/nodes/abstract_character/abstract_character.gd b/nodes/abstract_character/abstract_character.gd index 3f42d28f..1cbc0299 100644 --- a/nodes/abstract_character/abstract_character.gd +++ b/nodes/abstract_character/abstract_character.gd @@ -15,14 +15,11 @@ var custom_delete_button: return character.field.delete_button var graph: MonologueGraphEdit -var root: RootNode -func _init(node: RootNode): - root = node - graph = node.get_parent() +func _init(graph_edit: MonologueGraphEdit): + graph = graph_edit character.connect("change", update_character) - character.connect("display", graph.set_selected.bind(root)) character.connect("shown", _on_character_field_shown) character.setters["graph_edit"] = graph @@ -33,18 +30,16 @@ func _on_character_field_shown() -> void: func update_character(old_value: Variant, new_value: Variant): - var old_list = root.characters.value.duplicate(true) - var new_list = root.characters.value.duplicate(true) + var old_list = graph.characters.value.duplicate(true) + var new_list = graph.characters.value.duplicate(true) new_list[idx.value]["Character"] = new_value graph.undo_redo.create_action("Character %s => %s" % [str(old_value), str(new_value)]) - graph.undo_redo.add_do_property(root.characters, "value", new_list) - graph.undo_redo.add_do_method(root.characters.propagate.bind(new_list)) - graph.undo_redo.add_do_method(graph.set_selected.bind(root)) + graph.undo_redo.add_do_property(graph.characters, "value", new_list) + graph.undo_redo.add_do_method(graph.characters.propagate.bind(new_list)) graph.undo_redo.add_do_method(GlobalSignal.emit.bind("close_character_edit")) - graph.undo_redo.add_undo_property(root.characters, "value", old_list) - graph.undo_redo.add_undo_method(root.characters.propagate.bind(old_list)) - graph.undo_redo.add_undo_method(graph.set_selected.bind(root)) + graph.undo_redo.add_undo_property(graph.characters, "value", old_list) + graph.undo_redo.add_undo_method(graph.characters.propagate.bind(old_list)) graph.undo_redo.add_undo_method(GlobalSignal.emit.bind("close_character_edit")) graph.undo_redo.commit_action() diff --git a/nodes/abstract_variable/abstract_variable.gd b/nodes/abstract_variable/abstract_variable.gd index 90142122..47db6b81 100644 --- a/nodes/abstract_variable/abstract_variable.gd +++ b/nodes/abstract_variable/abstract_variable.gd @@ -30,7 +30,7 @@ func get_variable_label() -> Label: ## Returns the variable's typestring from the graph edit. func get_variable_type(variable_name: String) -> String: - for data in get_graph_edit().variables: + for data in get_graph_edit().variables.value: if data.get("Name") == variable_name: return data.get("Type") return "" @@ -60,9 +60,9 @@ func get_value_label() -> Label: ## Reset the variable dropdown to the first value and return its type. func reset_variable() -> String: - if get_graph_edit().variables: - variable.value = get_graph_edit().variables[0].get("Name") - return get_graph_edit().variables[0].get("Type") + if get_graph_edit().variables.value: + variable.value = get_graph_edit().variables.value[0].get("Name") + return get_graph_edit().variables.value[0].get("Type") else: variable.value = "" return "" @@ -111,7 +111,7 @@ func value_morph(selected_name: Variant = variable.value) -> void: func _update() -> void: - variable.callers["set_items"] = [get_graph_edit().variables, "Name", "ID", "Type"] + variable.callers["set_items"] = [get_graph_edit().variables.value, "Name", "ID", "Type"] value_morph(variable.value) get_variable_label().text = get_default_text(variable.value, "variable") get_operator_label().text = get_default_text(operator.value, "operator") diff --git a/nodes/character_node/character_node.gd b/nodes/character_node/character_node.gd index 0e8a12ea..d9b7422f 100644 --- a/nodes/character_node/character_node.gd +++ b/nodes/character_node/character_node.gd @@ -28,7 +28,7 @@ var _control_groups = { func _ready(): node_type = "NodeCharacter" - var characters: Array = get_graph_edit().characters + var characters: Array = get_graph_edit().characters.value character.callers["set_items"] = [characters, "Character/Name", "EditorIndex"] character.connect("preview", _update) @@ -101,7 +101,7 @@ func _update(_value: Variant = null) -> void: super._update() var action: Variant = action_type.value - var characters: Array = get_graph_edit().characters + var characters: Array = get_graph_edit().characters.value character.callers["set_items"] = [characters, "Character/Name", "EditorIndex"] if characters[character.value] and characters[character.value]["Character"].has("Portraits"): if portrait.field: diff --git a/nodes/event_node/event_node.gd b/nodes/event_node/event_node.gd index 13f32b85..f091bfd4 100644 --- a/nodes/event_node/event_node.gd +++ b/nodes/event_node/event_node.gd @@ -36,7 +36,7 @@ func _from_dict(dict: Dictionary): var condition = dict.get("Condition", {}) var morphing_value = dict.get("Value", "") if condition: - for v in get_graph_edit().variables: + for v in get_graph_edit().variables.value: if v.get("Name") == condition.get("Variable"): variable.value = condition.get("Variable") break diff --git a/nodes/root_node/root_node.gd b/nodes/root_node/root_node.gd index 12ad5d29..ccd14df3 100644 --- a/nodes/root_node/root_node.gd +++ b/nodes/root_node/root_node.gd @@ -1,80 +1,6 @@ @icon("res://ui/assets/icons/root.svg") class_name RootNode extends MonologueGraphNode -var characters := Property.new(LIST, {}, []) -var variables := Property.new(LIST, {}, []) - -var _character_references = [] -var _variable_references = [] - - func _ready(): node_type = "NodeRoot" super._ready() - - load_character(get_parent().characters) - characters.setters["add_callback"] = add_character - characters.setters["get_callback"] = get_speakers - characters.connect("preview", load_character) - - load_variables(get_parent().variables) - variables.setters["add_callback"] = add_variable - variables.setters["get_callback"] = get_variables - variables.connect("preview", load_variables) - - -func add_character(data: Dictionary = {}) -> MonologueCharacter: - var character = MonologueCharacter.new(self) - if data: - character._from_dict(data) - character.idx.value = _character_references.size() - character.character.setters["character_index"] = character.idx.value - _character_references.append(character) - return character - - -func add_variable(data: Dictionary = {}) -> MonologueVariable: - var variable = MonologueVariable.new(self) - if data: - variable._from_dict(data) - variable.index = _variable_references.size() - _variable_references.append(variable) - return variable - - -func get_speakers(): - return _character_references - - -func get_variables(): - return _variable_references - - -## Perform initial loading of speakers and set indexes correctly. -func load_character(new_character_list: Array): - _character_references.clear() - var ascending = func(a, b): return a.get("EditorIndex") < b.get("EditorIndex") - new_character_list.sort_custom(ascending) - for speaker_data in new_character_list: - add_character(speaker_data) - - if _character_references.is_empty(): - var narrator = add_character() - narrator.character.value["Name"] = "_NARRATOR" - narrator.protected.value = true - new_character_list.append(narrator._to_dict()) - - characters.value = new_character_list - get_graph_edit().characters = new_character_list - - -func load_variables(new_variable_list: Array): - _variable_references.clear() - for variable in new_variable_list: - add_variable(variable) - variables.value = new_variable_list - get_graph_edit().variables = new_variable_list - - -func _to_fields(_dict: Dictionary) -> void: - pass # speakers and variables are stored outside of root node diff --git a/nodes/sentence_node/sentence_node.gd b/nodes/sentence_node/sentence_node.gd index 81b29dbf..cfbe2647 100644 --- a/nodes/sentence_node/sentence_node.gd +++ b/nodes/sentence_node/sentence_node.gd @@ -21,14 +21,6 @@ func reload_preview() -> void: _preview.text = sentence.value -func _from_dict(dict: Dictionary): - # special handling for backwards compatibility v2.x - speaker.value = dict.get("SpeakerID", 0) - display_name.value = dict.get("DisplaySpeakerName", "") - voiceline.value = dict.get("VoicelinePath", "") - super._from_dict(dict) - - func _on_text_preview(text: Variant): _preview.text = str(text) @@ -36,7 +28,7 @@ func _on_text_preview(text: Variant): func _update(): super._update() - var characters: Array = get_graph_edit().characters + var characters: Array = get_graph_edit().get_characters() speaker.callers["set_items"] = [characters, "Character/Name", "EditorIndex"] if speaker.value is String: speaker.value = 0 diff --git a/scenes/main/editor.tscn b/scenes/main/editor.tscn index 625eac8d..946de67a 100644 --- a/scenes/main/editor.tscn +++ b/scenes/main/editor.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=21 format=3 uid="uid://bqjfdabrxujp7"] +[gd_scene load_steps=23 format=3 uid="uid://bqjfdabrxujp7"] [ext_resource type="Script" uid="uid://q6eg6rid6xqd" path="res://scenes/main/monologue_editor.gd" id="1_ovo1o"] [ext_resource type="PackedScene" uid="uid://dfkqf3wjdnj0m" path="res://common/layouts/editor/editor_section.tscn" id="2_q62vd"] @@ -19,7 +19,20 @@ [ext_resource type="PackedScene" uid="uid://cmpsaafag7cwl" path="res://common/windows/graph_node_picker/graph_node_picker.tscn" id="15_q62vd"] [ext_resource type="PackedScene" uid="uid://cvum3eaenloix" path="res://common/layouts/search_bar/search_bar.tscn" id="16_n4eqx"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7fun3"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1u3ep"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.186303, 0.183362, 0.215088, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_top_left = 3 +corner_radius_top_right = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mei7o"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 @@ -35,6 +48,19 @@ corner_radius_top_right = 3 corner_radius_bottom_right = 3 corner_radius_bottom_left = 3 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qe3vh"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225127, 0.224539, 0.257441, 1) +border_width_left = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.890196, 0.894118, 0.921569, 0.2) +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + [sub_resource type="GDScript" id="GDScript_lenro"] script/source = "extends Button @@ -72,7 +98,8 @@ dragging_enabled = false [node name="EditorSection" parent="MainContainer/VSplitContainer" instance=ExtResource("2_q62vd")] visible = true layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_7fun3") +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_mei7o") current_tab = 0 tabs_visible = false @@ -116,6 +143,8 @@ split_offset = 250 visible = true custom_minimum_size = Vector2(250, 0) layout_mode = 2 +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_qe3vh") current_tab = 0 [node name="Characters" parent="MainContainer/VSplitContainer/HSplitContainer/EditorSection" instance=ExtResource("6_mei7o")] @@ -139,7 +168,8 @@ split_offset = -250 visible = true layout_mode = 2 size_flags_horizontal = 3 -theme_override_styles/panel = SubResource("StyleBoxFlat_7fun3") +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_mei7o") current_tab = 0 tabs_visible = false @@ -219,7 +249,8 @@ size_flags_vertical = 3 [node name="EditorSection" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer" instance=ExtResource("2_q62vd")] visible = true layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_7fun3") +theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") +theme_override_styles/panel = SubResource("StyleBoxFlat_mei7o") current_tab = 0 tabs_visible = false diff --git a/scenes/main/monologue_editor.gd b/scenes/main/monologue_editor.gd index 4552e546..78c0b521 100644 --- a/scenes/main/monologue_editor.gd +++ b/scenes/main/monologue_editor.gd @@ -52,7 +52,7 @@ func _to_dict() -> Dictionary: list_nodes.append(child._to_dict()) # build data for dialogue characters - var characters = graph_switcher.current.characters + var characters = graph_switcher.current.characters.value if characters.size() <= 0: characters.append( { @@ -68,7 +68,7 @@ func _to_dict() -> Dictionary: "RootNodeID": get_root_dict(list_nodes).get("ID"), "ListNodes": list_nodes, "Characters": characters, - "Variables": graph_switcher.current.variables, + "Variables": graph_switcher.current.variables.value, "Languages": GlobalVariables.language_switcher.get_languages().keys() } @@ -109,8 +109,8 @@ func load_project(path: String, new_graph: bool = false) -> void: graph_switcher.add_tab(path.get_file()) graph_switcher.current.clear() graph_switcher.current.name = path.get_file().trim_suffix(".json") - graph_switcher.current.characters = converter.convert_characters(data.get("Characters")) - graph_switcher.current.variables = data.get("Variables") + graph_switcher.current.load_character(converter.convert_characters(data.get("Characters"))) + graph_switcher.current.load_variables(data.get("Variables")) graph_switcher.current.data = data var node_list = data.get("ListNodes") @@ -153,9 +153,9 @@ func refresh(node: MonologueGraphNode = null, affected_properties: PackedStringA func load_editor_sections() -> void: - var root_node: RootNode = graph_switcher.current.get_root_node() - %Characters.load_items(root_node.characters) - %Variables.load_items(root_node.variables) + var graph_edit: MonologueGraphEdit = graph_switcher.current + %Characters.load_items(graph_edit.characters) + %Variables.load_items(graph_edit.variables) func save(): From 83f2739218b509475c0fe89c38c3acd79659dc19 Mon Sep 17 00:00:00 2001 From: Atomic Junky <67459553+atomic-junky@users.noreply.github.com> Date: Thu, 31 Jul 2025 00:11:47 +0200 Subject: [PATCH 004/103] Fix characters/variables things --- common/layouts/character_edit/character_edit.gd | 4 ++-- common/layouts/character_edit/portrait_list_section.gd | 2 +- common/monologue_graph_node.gd | 4 ++-- logic/history/character_history.gd | 2 +- logic/history/portrait_history.gd | 2 +- nodes/sentence_node/sentence_node.gd | 6 +++++- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/common/layouts/character_edit/character_edit.gd b/common/layouts/character_edit/character_edit.gd index 87f59a03..3567a091 100644 --- a/common/layouts/character_edit/character_edit.gd +++ b/common/layouts/character_edit/character_edit.gd @@ -19,7 +19,7 @@ func open(graph: MonologueGraphEdit, index: int) -> void: character_index = index %PortraitSettingsSection.base_path = graph.file_path %PortraitListSection.selected = -1 - _from_dict(graph.characters[index]) + _from_dict(graph.characters.value[index]) show() else: close() @@ -28,7 +28,7 @@ func open(graph: MonologueGraphEdit, index: int) -> void: func close() -> void: # also triggered when 'Done' button is pressed if graph_edit: - graph_edit.characters[character_index]["Character"].merge(_to_dict(), true) + graph_edit.characters.value[character_index]["Character"].merge(_to_dict(), true) hide() graph_edit = null character_index = -1 diff --git a/common/layouts/character_edit/portrait_list_section.gd b/common/layouts/character_edit/portrait_list_section.gd index 4974edb3..130beee7 100644 --- a/common/layouts/character_edit/portrait_list_section.gd +++ b/common/layouts/character_edit/portrait_list_section.gd @@ -167,5 +167,5 @@ func _update_portrait() -> void: preview_section.visible = show_portrait_sections timeline_section.visible = show_portrait_sections if show_portrait_sections: - var character_dict = graph_edit.characters[character_index]["Character"] + var character_dict = graph_edit.characters.value[character_index]["Character"] portrait_settings_section._from_dict(character_dict) diff --git a/common/monologue_graph_node.gd b/common/monologue_graph_node.gd index 9554e506..1b8b1cba 100644 --- a/common/monologue_graph_node.gd +++ b/common/monologue_graph_node.gd @@ -224,8 +224,8 @@ func _get_field_groups() -> Array: func _get_inspector_property_list() -> Array: return [ - {"name": "ID", "property": "id", "type": LINE}, - {"name": "EditorPosition", "property": "editor_position", "type": LINE}, + {"name": "id", "property": "id", "type": LINE}, + {"name": "editor_position", "property": "editor_position", "type": LINE}, ] + get_inspector_property_list() diff --git a/logic/history/character_history.gd b/logic/history/character_history.gd index e35bf3d6..bffbe480 100644 --- a/logic/history/character_history.gd +++ b/logic/history/character_history.gd @@ -42,4 +42,4 @@ func _hide_unrelated_windows() -> void: func _update_character(property: String, value: Variant) -> void: var key = Util.to_key_name(property) - graph_edit.characters[character_index]["Character"][key] = value + graph_edit.characters.value[character_index]["Character"][key] = value diff --git a/logic/history/portrait_history.gd b/logic/history/portrait_history.gd index 65203ae7..8cb572e6 100644 --- a/logic/history/portrait_history.gd +++ b/logic/history/portrait_history.gd @@ -27,7 +27,7 @@ func revert_properties() -> void: func set_property(node: Variant, property: String, value: Variant) -> void: super.set_property(node, property, value) - var character_dict = graph_edit.characters[character_index]["Character"] + var character_dict = graph_edit.characters.value[character_index]["Character"] var key = Util.to_key_name(property) character_dict["Portraits"][portrait_index]["Portrait"][key] = value diff --git a/nodes/sentence_node/sentence_node.gd b/nodes/sentence_node/sentence_node.gd index cfbe2647..7c8c6231 100644 --- a/nodes/sentence_node/sentence_node.gd +++ b/nodes/sentence_node/sentence_node.gd @@ -28,12 +28,16 @@ func _on_text_preview(text: Variant): func _update(): super._update() - var characters: Array = get_graph_edit().get_characters() + var characters := get_graph_edit().characters.value as Array speaker.callers["set_items"] = [characters, "Character/Name", "EditorIndex"] if speaker.value is String: speaker.value = 0 reload_preview() +func get_inspector_property_list() -> Array: + return [] + + func _get_field_groups() -> Array: return [{"Speaker": ["speaker", "display_name"]}] From c615468b793f4e430e0aaf8194b27f0b32a23888 Mon Sep 17 00:00:00 2001 From: Atomic Junky <67459553+atomic-junky@users.noreply.github.com> Date: Fri, 1 Aug 2025 20:20:37 +0200 Subject: [PATCH 005/103] Dockable interface --- addons/beautify_code_on_save/plugin.cfg | 7 + addons/beautify_code_on_save/plugin.gd | 210 +++++++ addons/beautify_code_on_save/plugin.gd.uid | 1 + addons/dockable_container/LICENSE | 121 ++++ .../dockable_container/dockable_container.gd | 524 ++++++++++++++++++ .../dockable_container.gd.uid | 1 + addons/dockable_container/dockable_panel.gd | 168 ++++++ .../dockable_container/dockable_panel.gd.uid | 1 + .../dockable_panel_reference_control.gd | 49 ++ .../dockable_panel_reference_control.gd.uid | 1 + .../dockable_container/drag_n_drop_panel.gd | 82 +++ .../drag_n_drop_panel.gd.uid | 1 + addons/dockable_container/floating_window.gd | 81 +++ .../dockable_container/floating_window.gd.uid | 1 + addons/dockable_container/icon.svg | 10 + addons/dockable_container/icon.svg.import | 37 ++ .../editor_inspector_plugin.gd | 22 + .../editor_inspector_plugin.gd.uid | 1 + .../layout_editor_property.gd | 71 +++ .../layout_editor_property.gd.uid | 1 + addons/dockable_container/layout.gd | 260 +++++++++ addons/dockable_container/layout.gd.uid | 1 + addons/dockable_container/layout_node.gd | 29 + addons/dockable_container/layout_node.gd.uid | 1 + addons/dockable_container/layout_panel.gd | 89 +++ addons/dockable_container/layout_panel.gd.uid | 1 + addons/dockable_container/layout_split.gd | 111 ++++ addons/dockable_container/layout_split.gd.uid | 1 + addons/dockable_container/plugin.cfg | 13 + addons/dockable_container/plugin.gd | 19 + addons/dockable_container/plugin.gd.uid | 1 + .../dockable_container/samples/TestScene.gd | 63 +++ .../samples/TestScene.gd.uid | 1 + .../dockable_container/samples/TestScene.tscn | 181 ++++++ addons/dockable_container/split_handle.gd | 127 +++++ addons/dockable_container/split_handle.gd.uid | 1 + common/layouts/editor/editor_section.gd | 98 ---- common/layouts/editor/editor_section.gd.uid | 1 - common/layouts/editor/editor_section.tscn | 42 -- .../editor/list_section/list_section.gd | 8 +- .../editor/list_section/list_section.tscn | 23 +- .../graph_edit/monologue_graph_edit.gd | 12 +- .../inspector_panel.gd | 11 + .../inspector_panel.gd.uid | 0 .../inspector_panel.tscn | 3 +- .../layouts/inspector/monologue_inspector.gd | 44 ++ .../inspector/monologue_inspector.gd.uid | 1 + common/monologue_graph_node.gd | 15 +- nodes/background_node/background_node.gd | 27 + project.godot | 2 +- scenes/main/editor.tscn | 248 ++++----- scenes/main/graph_edit_switcher.gd | 2 +- scenes/main/monologue_editor.gd | 14 +- ui/theme_default/theme_default.gd | 51 +- 54 files changed, 2565 insertions(+), 326 deletions(-) create mode 100644 addons/beautify_code_on_save/plugin.cfg create mode 100644 addons/beautify_code_on_save/plugin.gd create mode 100644 addons/beautify_code_on_save/plugin.gd.uid create mode 100644 addons/dockable_container/LICENSE create mode 100644 addons/dockable_container/dockable_container.gd create mode 100644 addons/dockable_container/dockable_container.gd.uid create mode 100644 addons/dockable_container/dockable_panel.gd create mode 100644 addons/dockable_container/dockable_panel.gd.uid create mode 100644 addons/dockable_container/dockable_panel_reference_control.gd create mode 100644 addons/dockable_container/dockable_panel_reference_control.gd.uid create mode 100644 addons/dockable_container/drag_n_drop_panel.gd create mode 100644 addons/dockable_container/drag_n_drop_panel.gd.uid create mode 100644 addons/dockable_container/floating_window.gd create mode 100644 addons/dockable_container/floating_window.gd.uid create mode 100644 addons/dockable_container/icon.svg create mode 100644 addons/dockable_container/icon.svg.import create mode 100644 addons/dockable_container/inspector_plugin/editor_inspector_plugin.gd create mode 100644 addons/dockable_container/inspector_plugin/editor_inspector_plugin.gd.uid create mode 100644 addons/dockable_container/inspector_plugin/layout_editor_property.gd create mode 100644 addons/dockable_container/inspector_plugin/layout_editor_property.gd.uid create mode 100644 addons/dockable_container/layout.gd create mode 100644 addons/dockable_container/layout.gd.uid create mode 100644 addons/dockable_container/layout_node.gd create mode 100644 addons/dockable_container/layout_node.gd.uid create mode 100644 addons/dockable_container/layout_panel.gd create mode 100644 addons/dockable_container/layout_panel.gd.uid create mode 100644 addons/dockable_container/layout_split.gd create mode 100644 addons/dockable_container/layout_split.gd.uid create mode 100644 addons/dockable_container/plugin.cfg create mode 100644 addons/dockable_container/plugin.gd create mode 100644 addons/dockable_container/plugin.gd.uid create mode 100644 addons/dockable_container/samples/TestScene.gd create mode 100644 addons/dockable_container/samples/TestScene.gd.uid create mode 100644 addons/dockable_container/samples/TestScene.tscn create mode 100644 addons/dockable_container/split_handle.gd create mode 100644 addons/dockable_container/split_handle.gd.uid delete mode 100644 common/layouts/editor/editor_section.gd delete mode 100644 common/layouts/editor/editor_section.gd.uid delete mode 100644 common/layouts/editor/editor_section.tscn rename common/layouts/{inspector_panel => inspector}/inspector_panel.gd (93%) rename common/layouts/{inspector_panel => inspector}/inspector_panel.gd.uid (100%) rename common/layouts/{inspector_panel => inspector}/inspector_panel.tscn (95%) create mode 100644 common/layouts/inspector/monologue_inspector.gd create mode 100644 common/layouts/inspector/monologue_inspector.gd.uid diff --git a/addons/beautify_code_on_save/plugin.cfg b/addons/beautify_code_on_save/plugin.cfg new file mode 100644 index 00000000..ac305754 --- /dev/null +++ b/addons/beautify_code_on_save/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Beautify Code on Save" +description="Automatically formats and lints GDScript files on save using gdformat and gdlint." +author="nuevocharrua" +version="1.1.1" +script="plugin.gd" diff --git a/addons/beautify_code_on_save/plugin.gd b/addons/beautify_code_on_save/plugin.gd new file mode 100644 index 00000000..8a951812 --- /dev/null +++ b/addons/beautify_code_on_save/plugin.gd @@ -0,0 +1,210 @@ +@tool +extends EditorPlugin + +const SUCCESS: int = 0 +const AUTO_RELOAD_SETTING = "text_editor/behavior/files/auto_reload_scripts_on_external_change" +const ENABLE_EXTERNAL_EDITOR = "text_editor/external/use_external_editor" +# Plugin custom settings +const GDFORMAT_PATH_SETTING = "beautify_code_on_save/paths/gdformat_path" +const GDLINT_PATH_SETTING = "beautify_code_on_save/paths/gdlint_path" + +var original_reload_setting: bool +var original_external_editor: bool + + +func _enter_tree() -> void: + var settings = get_editor_interface().get_editor_settings() + + # Save original settings + original_reload_setting = settings.get_setting(AUTO_RELOAD_SETTING) + original_external_editor = settings.get_setting(ENABLE_EXTERNAL_EDITOR) + + # Configure auto-reload and external editor + settings.set_setting(AUTO_RELOAD_SETTING, true) + settings.set_setting(ENABLE_EXTERNAL_EDITOR, false) + settings.set_initial_value(AUTO_RELOAD_SETTING, true, false) + settings.set_initial_value(ENABLE_EXTERNAL_EDITOR, false, false) + + # Register plugin settings + _setup_plugin_settings(settings) + + resource_saved.connect(_on_resource_saved_deferred) + + +func _exit_tree() -> void: + resource_saved.disconnect(_on_resource_saved_deferred) + + var settings = get_editor_interface().get_editor_settings() + settings.set_setting(AUTO_RELOAD_SETTING, original_reload_setting) + settings.set_setting(ENABLE_EXTERNAL_EDITOR, original_external_editor) + + +func _setup_plugin_settings(settings: EditorSettings) -> void: + # Detect default paths + var default_gdformat = _detect_default_path("gdformat") + var default_gdlint = _detect_default_path("gdlint") + + # Register settings + if not settings.has_setting(GDFORMAT_PATH_SETTING): + settings.set_setting(GDFORMAT_PATH_SETTING, default_gdformat) + if not settings.has_setting(GDLINT_PATH_SETTING): + settings.set_setting(GDLINT_PATH_SETTING, default_gdlint) + + # Configure settings metadata + ( + settings + .add_property_info( + { + "name": GDFORMAT_PATH_SETTING, + "type": TYPE_STRING, + "hint": PROPERTY_HINT_GLOBAL_FILE, + "hint_string": "", + } + ) + ) + ( + settings + .add_property_info( + { + "name": GDLINT_PATH_SETTING, + "type": TYPE_STRING, + "hint": PROPERTY_HINT_GLOBAL_FILE, + "hint_string": "", + } + ) + ) + + +func _detect_default_path(command: String) -> String: + # Try to detect command path using 'which' + var output = [] + var exit_code = OS.execute("which", [command], output, true) + + if exit_code == SUCCESS and not output.is_empty(): + return output[0].strip_edges() + + # Common paths as fallback + var common_paths = [ + "/usr/local/bin/" + command, + "/usr/bin/" + command, + OS.get_environment("HOME") + "/.local/bin/" + command + ] + + for path in common_paths: + if FileAccess.file_exists(path): + return path + + return "" + + +func _on_resource_saved_deferred(script: Resource) -> void: + call_deferred("_on_resource_saved", script) + + +func _on_resource_saved(script: Resource) -> void: + if not script is Script: + return + + var current_script = get_editor_interface().get_script_editor().get_current_script() + if current_script != script: + return + + var text_edit = ( + get_editor_interface().get_script_editor().get_current_editor().get_base_editor() + ) + var file_path = ProjectSettings.globalize_path(script.resource_path) + + # Get paths from settings + var settings = get_editor_interface().get_editor_settings() + var gdformat_path = settings.get_setting(GDFORMAT_PATH_SETTING) + var gdlint_path = settings.get_setting(GDLINT_PATH_SETTING) + + # Add date time + var date_time = Time.get_datetime_string_from_system() + print("\n%s" % date_time) + + # First run gdformat + if not gdformat_path or not FileAccess.file_exists(gdformat_path): + push_warning("❌ GDFormat not found. Please configure the path in Editor Settings.") + else: + var gdformat_output = [] + var gdformat_exit_code = OS.execute(gdformat_path, [file_path], gdformat_output, true) + + if gdformat_exit_code == SUCCESS: + await get_tree().process_frame + var formatted_source = FileAccess.get_file_as_string(script.resource_path) + _reload_script(text_edit, formatted_source) + print("✓ GDFormat: Successfully formatted: '%s'" % script.resource_path) + else: + push_error("❌ GDFormat Error: " + str(gdformat_output)) + return # If formatting fails, don't continue with linting + + # Then run gdlint on the formatted code + if not gdlint_path or not FileAccess.file_exists(gdlint_path): + push_warning("❌ GDLint not found. Please configure the path in Editor Settings.") + else: + var gdlint_output = [] + var gdlint_exit_code = OS.execute(gdlint_path, [file_path], gdlint_output, true) + + if gdlint_exit_code != SUCCESS: + _show_lint_errors(gdlint_output[0], file_path) + else: + print("✓ GDLint : Successfully linted : '%s'" % script.resource_path) + + +func _reload_script(text_edit: TextEdit, source_code: String) -> void: + # Save cursor and scroll position + var column = text_edit.get_caret_column() + var row = text_edit.get_caret_line() + var scroll_position_h = text_edit.scroll_horizontal + var scroll_position_v = text_edit.scroll_vertical + + # Update text + text_edit.text = source_code + + # Restore cursor and scroll position + + text_edit.set_caret_column(column) + text_edit.set_caret_line(row) + text_edit.scroll_horizontal = scroll_position_h + text_edit.scroll_vertical = scroll_position_v + + # Mark as saved + text_edit.tag_saved_version() + + +func _show_lint_errors(output: String, path: String) -> void: + print("\n⚠ GDLint found the following issues:") + print("File: %s" % _get_res_path(path)) + push_error("❌ GDLint Error: Please see output with more information.") + var lines = output.split("\n") + var error_count := 0 + + for line in lines: + if line.is_empty() or line.begins_with("Failure:"): + continue + + var error_parts = line.split(": Error: ") + if error_parts.size() >= 2: + error_count += 1 + var location = error_parts[0].get_file() + var line_number = ( + error_parts[0].split(":")[2] + if OS.get_name() == "Windows" + else error_parts[0].split(":")[1] + ) + var error_msg = error_parts[1] + + print("%d) Line %s: %s" % [error_count, line_number, error_msg]) + + print("\nTotal: %d issues found" % error_count) + + +func _get_res_path(absolute_path: String) -> String: + var project_root = ProjectSettings.globalize_path("res://") + if absolute_path.begins_with(project_root): + return "res://" + absolute_path.substr(project_root.length()) + var addons_pos = absolute_path.find("/addons/") + if addons_pos != -1: + return "res://" + absolute_path.substr(addons_pos + 1) + return absolute_path diff --git a/addons/beautify_code_on_save/plugin.gd.uid b/addons/beautify_code_on_save/plugin.gd.uid new file mode 100644 index 00000000..fb83ed2f --- /dev/null +++ b/addons/beautify_code_on_save/plugin.gd.uid @@ -0,0 +1 @@ +uid://bd1mkvvfjhvfr diff --git a/addons/dockable_container/LICENSE b/addons/dockable_container/LICENSE new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/addons/dockable_container/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/addons/dockable_container/dockable_container.gd b/addons/dockable_container/dockable_container.gd new file mode 100644 index 00000000..cbb95c8c --- /dev/null +++ b/addons/dockable_container/dockable_container.gd @@ -0,0 +1,524 @@ +@tool +class_name DockableContainer +extends Container + +const SplitHandle := preload("split_handle.gd") +const DockablePanel := preload("dockable_panel.gd") +const DragNDropPanel := preload("drag_n_drop_panel.gd") + +@export var tab_alignment := TabBar.ALIGNMENT_CENTER: + get: + return _tab_align + set(value): + _tab_align = value + for i in range(1, _panel_container.get_child_count()): + var panel := _panel_container.get_child(i) as DockablePanel + panel.tab_alignment = value +@export var use_hidden_tabs_for_min_size := false: + get: + return _use_hidden_tabs_for_min_size + set(value): + _use_hidden_tabs_for_min_size = value + for i in range(1, _panel_container.get_child_count()): + var panel := _panel_container.get_child(i) as DockablePanel + panel.use_hidden_tabs_for_min_size = value +@export var tabs_visible := true: + get: + return _tabs_visible + set(value): + _tabs_visible = value + for i in range(1, _panel_container.get_child_count()): + var panel := _panel_container.get_child(i) as DockablePanel + panel.show_tabs = _tabs_visible +## If [code]true[/code] and a panel only has one tab, it keeps that tab hidden even if +## [member tabs_visible] is [code]true[/code]. +## Only takes effect is [member tabs_visible] is [code]true[/code]. +@export var hide_single_tab := false: + get: + return _hide_single_tab + set(value): + _hide_single_tab = value + for i in range(1, _panel_container.get_child_count()): + var panel := _panel_container.get_child(i) as DockablePanel + panel.hide_single_tab = _hide_single_tab + queue_sort() +@export var rearrange_group := 0 +@export var layout := DockableLayout.new(): + get: + return _layout + set(value): + set_layout(value) +## If `clone_layout_on_ready` is true, `layout` will be cloned checked `_ready`. +## This is useful for leaving layout Resources untouched in case you want to +## restore layout to its default later. +@export var clone_layout_on_ready := true + +var _layout := DockableLayout.new() +var _panel_container := Container.new() +var _windows_container := Container.new() +var _split_container := Container.new() +var _drag_n_drop_panel := DragNDropPanel.new() +var _drag_panel: DockablePanel +var _tab_align := TabBar.ALIGNMENT_CENTER +var _tabs_visible := true +var _use_hidden_tabs_for_min_size := false +var _hide_single_tab := false +var _current_panel_index := 0 +var _current_split_index := 0 +var _children_names := {} +var _layout_dirty := false + + +func _init() -> void: + child_entered_tree.connect(_child_entered_tree) + child_exiting_tree.connect(_child_exiting_tree) + + +func _ready() -> void: + set_process_input(false) + _panel_container.name = "_panel_container" + add_child(_panel_container) + move_child(_panel_container, 0) + _split_container.name = "_split_container" + _split_container.mouse_filter = MOUSE_FILTER_PASS + _panel_container.add_child(_split_container) + _windows_container.name = "_windows_container" + get_parent().call_deferred("add_child", _windows_container) + + _drag_n_drop_panel.name = "_drag_n_drop_panel" + _drag_n_drop_panel.mouse_filter = MOUSE_FILTER_PASS + _drag_n_drop_panel.visible = false + add_child(_drag_n_drop_panel) + + if not _layout: + set_layout(null) + elif clone_layout_on_ready and not Engine.is_editor_hint(): + set_layout(_layout.clone()) + + +func _notification(what: int) -> void: + if what == NOTIFICATION_SORT_CHILDREN: + _resort() + elif ( + what == NOTIFICATION_DRAG_BEGIN + and _can_handle_drag_data(get_viewport().gui_get_drag_data()) + ): + _drag_n_drop_panel.set_enabled(true, not _layout.root.is_empty()) + set_process_input(true) + elif what == NOTIFICATION_DRAG_END: + _drag_n_drop_panel.set_enabled(false) + set_process_input(false) + + +func _input(event: InputEvent) -> void: + assert(get_viewport().gui_is_dragging(), "FIXME: should only be called when dragging") + if event is InputEventMouseMotion: + var local_position := get_local_mouse_position() + var panel: DockablePanel + for i in range(1, _panel_container.get_child_count()): + var p := _panel_container.get_child(i) as DockablePanel + if p.get_rect().has_point(local_position): + panel = p + break + _drag_panel = panel + if not panel: + return + + var current_tab: DockableReferenceControl = panel.get_current_tab_control() + var tab_name: String = current_tab.reference_to.name + if tab_name.begins_with("_"): + return + + fit_child_in_rect(_drag_n_drop_panel, panel.get_child_rect()) + + +func _child_entered_tree(node: Node) -> void: + if node == _panel_container or node == _drag_n_drop_panel: + return + _drag_n_drop_panel.move_to_front() + _track_and_add_node(node) + + +func _child_exiting_tree(node: Node) -> void: + if node == _panel_container or node == _drag_n_drop_panel: + return + _untrack_node(node) + + +func _can_drop_data(_position: Vector2, data) -> bool: + return _can_handle_drag_data(data) + + +func _drop_data(_position: Vector2, data) -> void: + var from_node := get_node(data.from_path) + if from_node is TabBar: + from_node = from_node.get_parent() + if from_node == _drag_panel and _drag_panel.get_child_count() == 1: + return + var tab_index = data.tabc_element if data.has("tabc_element") else data.tab_index + var moved_tab = from_node.get_tab_control(tab_index) + if moved_tab is DockableReferenceControl: + moved_tab = moved_tab.reference_to + if not _is_managed_node(moved_tab): + moved_tab.get_parent().remove_child(moved_tab) + add_child(moved_tab) + + if _drag_panel != null: + var margin := _drag_n_drop_panel.get_hover_margin() + _layout.split_leaf_with_node(_drag_panel.leaf, moved_tab, margin) + + _layout_dirty = true + queue_sort() + + +func _add_floating_options(tab_container: DockablePanel) -> void: + var options := PopupMenu.new() + options.add_item("Make Floating") + options.id_pressed.connect(_toggle_floating.bind(tab_container)) + options.size.y = 0 + _windows_container.add_child(options) + tab_container.set_popup(options) + + +## Required when converting a window back to panel. +func _refresh_tabs_visible() -> void: + if tabs_visible: + tabs_visible = false + await get_tree().process_frame + await get_tree().process_frame + tabs_visible = true + + +func _toggle_floating(_id: int, tab_container: DockablePanel) -> void: + var node_name := tab_container.get_tab_title(tab_container.current_tab) + var node := get_node(node_name) + if is_instance_valid(node): + var tab_position := maxi(tab_container.leaf.find_child(node), 0) + _convert_to_window(node, {"tab_position": tab_position, "tab_container": tab_container}) + else: + print("Node ", node_name, " not found!") + + +## Converts a panel to floating window. +func _convert_to_window(content: Control, previous_data := {}) -> void: + var old_owner := content.owner + var data := {} + if content.name in layout.windows: + data = layout.windows[content.name] + var window := FloatingWindow.new(content, data) + _windows_container.add_child(window) + window.show() + _refresh_tabs_visible() + window.close_requested.connect(_convert_to_panel.bind(window, old_owner, previous_data)) + window.data_changed.connect(layout.save_window_properties) + + +## Converts a floating window into a panel. +func _convert_to_panel(window: FloatingWindow, old_owner: Node, previous_data := {}) -> void: + var content := window.window_content + window.remove_child(content) + window.destroy() + add_child(content) + content.owner = old_owner + if previous_data.has("tab_container") and is_instance_valid(previous_data["tab_container"]): + var tab_position := previous_data.get("tab_position", 0) as int + previous_data["tab_container"].leaf.insert_node(tab_position, content) + _refresh_tabs_visible() + + +func set_control_as_current_tab(control: Control) -> void: + assert( + control.get_parent_control() == self, + "Trying to focus a control not managed by this container" + ) + if is_control_hidden(control): + push_warning("Trying to focus a hidden control") + return + var leaf := _layout.get_leaf_for_node(control) + if not leaf: + return + var position_in_leaf := leaf.find_child(control) + if position_in_leaf < 0: + return + var panel: DockablePanel + for i in range(1, _panel_container.get_child_count()): + var p := _panel_container.get_child(i) as DockablePanel + if p.leaf == leaf: + panel = p + break + if not panel: + return + panel.current_tab = clampi(position_in_leaf, 0, panel.get_tab_count() - 1) + + +func set_layout(value: DockableLayout) -> void: + if value == null: + value = DockableLayout.new() + if value == _layout: + return + if _layout and _layout.changed.is_connected(queue_sort): + _layout.changed.disconnect(queue_sort) + _layout = value + _layout.changed.connect(queue_sort) + for window in _windows_container.get_children(): + if not window.name in _layout.windows and window is FloatingWindow: + window.prevent_data_erasure = true # We don't want to delete data. + window.close_requested.emit() # Removes the window. + continue + for window: String in _layout.windows.keys(): + var panel := find_child(window, false) + # Only those windows get created which were not previously created. + if panel: + _convert_to_window(panel) + _layout_dirty = true + queue_sort() + + +func set_use_hidden_tabs_for_min_size(value: bool) -> void: + _use_hidden_tabs_for_min_size = value + for i in range(1, _panel_container.get_child_count()): + var panel := _panel_container.get_child(i) as DockablePanel + panel.use_hidden_tabs_for_min_size = value + + +func get_use_hidden_tabs_for_min_size() -> bool: + return _use_hidden_tabs_for_min_size + + +func set_control_hidden(child: Control, is_hidden: bool) -> void: + _layout.set_node_hidden(child, is_hidden) + + +func is_control_hidden(child: Control) -> bool: + return _layout.is_node_hidden(child) + + +func get_tabs() -> Array[Control]: + var tabs: Array[Control] = [] + for i in get_child_count(): + var child := get_child(i) + if _is_managed_node(child): + tabs.append(child) + return tabs + + +func get_tab_count() -> int: + var count := 0 + for i in get_child_count(): + var child := get_child(i) + if _is_managed_node(child): + count += 1 + return count + + +func _can_handle_drag_data(data) -> bool: + if data is Dictionary and data.get("type") in ["tab_container_tab", "tabc_element"]: + var tabc := get_node_or_null(data.get("from_path")) + return ( + tabc + and tabc.has_method("get_tabs_rearrange_group") + and tabc.get_tabs_rearrange_group() == rearrange_group + ) + return false + + +func _is_managed_node(node: Node) -> bool: + return ( + node.get_parent() == self + and node != _panel_container + and node != _drag_n_drop_panel + and node is Control + and not node.top_level + ) + + +func _update_layout_with_children() -> void: + var names := PackedStringArray() + _children_names.clear() + for i in range(1, get_child_count() - 1): + var c := get_child(i) + if _track_node(c): + names.append(c.name) + _layout.update_nodes(names) + _layout_dirty = false + + +func _track_node(node: Node) -> bool: + if not _is_managed_node(node): + return false + _children_names[node] = node.name + _children_names[node.name] = node + if not node.renamed.is_connected(_on_child_renamed): + node.renamed.connect(_on_child_renamed.bind(node)) + if not node.tree_exiting.is_connected(_untrack_node): + node.tree_exiting.connect(_untrack_node.bind(node)) + return true + + +func _track_and_add_node(node: Node) -> void: + var tracked_name = _children_names.get(node) + if not _track_node(node): + return + if tracked_name and tracked_name != node.name: + _layout.rename_node(tracked_name, node.name) + _layout_dirty = true + + +func _untrack_node(node: Node) -> void: + _children_names.erase(node) + _children_names.erase(node.name) + if node.renamed.is_connected(_on_child_renamed): + node.renamed.disconnect(_on_child_renamed) + if node.tree_exiting.is_connected(_untrack_node): + node.tree_exiting.disconnect(_untrack_node) + _layout_dirty = true + + +func _resort() -> void: + assert(_panel_container, "FIXME: resorting without _panel_container") + if _panel_container.get_index() != 0: + move_child(_panel_container, 0) + if _drag_n_drop_panel.get_index() < get_child_count() - 1: + _drag_n_drop_panel.move_to_front() + + if _layout_dirty: + _update_layout_with_children() + + var rect := Rect2(Vector2.ZERO, size) + fit_child_in_rect(_panel_container, rect) + _panel_container.fit_child_in_rect(_split_container, rect) + + _current_panel_index = 1 + _current_split_index = 0 + + var children_list := [] + _calculate_panel_and_split_list(children_list, _layout.root) + _fit_panel_and_split_list_to_rect(children_list, rect) + + _untrack_children_after(_panel_container, _current_panel_index) + _untrack_children_after(_split_container, _current_split_index) + + +## Calculate DockablePanel and SplitHandle minimum sizes, skipping empty +## branches. +## +## Returns a DockablePanel checked non-empty leaves, a SplitHandle checked non-empty +## splits, `null` if the whole branch is empty and no space should be used. +## +## `result` will be filled with the non-empty nodes in this post-order tree +## traversal. +func _calculate_panel_and_split_list(result: Array, layout_node: DockableLayoutNode): + if layout_node is DockableLayoutPanel: + var nodes: Array[Control] = [] + for n in layout_node.names: + var node: Control = _children_names.get(n) + if node: + assert(node is Control, "FIXME: node is not a control %s" % node) + assert( + node.get_parent_control() == self, + "FIXME: node is not child of container %s" % node + ) + if is_control_hidden(node): + node.visible = false + else: + nodes.append(node) + if nodes.is_empty(): + return null + else: + var panel := _get_panel(_current_panel_index) + _current_panel_index += 1 + panel.track_nodes(nodes, layout_node) + result.append(panel) + return panel + elif layout_node is DockableLayoutSplit: + # by processing `second` before `first`, traversing `result` from back + # to front yields a nice pre-order tree traversal + var second_result = _calculate_panel_and_split_list(result, layout_node.second) + var first_result = _calculate_panel_and_split_list(result, layout_node.first) + if first_result and second_result: + var split := _get_split(_current_split_index) + _current_split_index += 1 + split.layout_split = layout_node + split.first_minimum_size = first_result.get_layout_minimum_size() + split.second_minimum_size = second_result.get_layout_minimum_size() + result.append(split) + return split + elif first_result: + return first_result + else: # NOTE: this returns null if `second_result` is null + return second_result + else: + push_warning("FIXME: invalid Resource, should be branch or leaf, found %s" % layout_node) + + +## Traverse list from back to front fitting controls where they belong. +## +## Be sure to call this with the result from `_calculate_split_minimum_sizes`. +func _fit_panel_and_split_list_to_rect(panel_and_split_list: Array, rect: Rect2) -> void: + var control = panel_and_split_list.pop_back() + if control is DockablePanel: + _panel_container.fit_child_in_rect(control, rect) + elif control is SplitHandle: + var split_rects = control.get_split_rects(rect) + _split_container.fit_child_in_rect(control, split_rects["self"]) + _fit_panel_and_split_list_to_rect(panel_and_split_list, split_rects["first"]) + _fit_panel_and_split_list_to_rect(panel_and_split_list, split_rects["second"]) + + +## Get the idx'th DockablePanel, reusing an instanced one if possible +func _get_panel(idx: int) -> DockablePanel: + assert(_panel_container, "FIXME: creating panel without _panel_container") + if idx < _panel_container.get_child_count(): + return _panel_container.get_child(idx) + var panel := DockablePanel.new() + panel.tab_alignment = _tab_align + panel.show_tabs = _tabs_visible + panel.hide_single_tab = _hide_single_tab + panel.use_hidden_tabs_for_min_size = _use_hidden_tabs_for_min_size + panel.set_tabs_rearrange_group(maxi(0, rearrange_group)) + _add_floating_options(panel) + _panel_container.add_child(panel) + panel.tab_layout_changed.connect(_on_panel_tab_layout_changed.bind(panel)) + return panel + + +## Get the idx'th SplitHandle, reusing an instanced one if possible +func _get_split(idx: int) -> SplitHandle: + assert(_split_container, "FIXME: creating split without _split_container") + if idx < _split_container.get_child_count(): + return _split_container.get_child(idx) + var split := SplitHandle.new() + _split_container.add_child(split) + return split + + +## Helper for removing and freeing all remaining children from node +func _untrack_children_after(node: Control, idx: int) -> void: + for i in range(idx, node.get_child_count()): + var child := node.get_child(idx) + node.remove_child(child) + child.queue_free() + + +## Handler for `DockablePanel.tab_layout_changed`, update its DockableLayoutPanel +func _on_panel_tab_layout_changed(tab: int, panel: DockablePanel) -> void: + _layout_dirty = true + var control := panel.get_tab_control(tab) + if control is DockableReferenceControl: + control = control.reference_to + if not _is_managed_node(control): + control.get_parent().remove_child(control) + add_child(control) + _layout.move_node_to_leaf(control, panel.leaf, tab) + queue_sort() + + +## Handler for `Node.renamed` signal, updates tracked name for node +func _on_child_renamed(child: Node) -> void: + var old_name: String = _children_names.get(child) + if old_name == str(child.name): + return + _children_names.erase(old_name) + _children_names[child] = child.name + _children_names[child.name] = child + _layout.rename_node(old_name, child.name) diff --git a/addons/dockable_container/dockable_container.gd.uid b/addons/dockable_container/dockable_container.gd.uid new file mode 100644 index 00000000..d3304fc5 --- /dev/null +++ b/addons/dockable_container/dockable_container.gd.uid @@ -0,0 +1 @@ +uid://k2o7qui1lr6l diff --git a/addons/dockable_container/dockable_panel.gd b/addons/dockable_container/dockable_panel.gd new file mode 100644 index 00000000..bd064113 --- /dev/null +++ b/addons/dockable_container/dockable_panel.gd @@ -0,0 +1,168 @@ +@tool +extends TabContainer + +signal tab_layout_changed(tab) + +var leaf: DockableLayoutPanel: + get: + return get_leaf() + set(value): + set_leaf(value) +var show_tabs := true: + get: + return _show_tabs + set(value): + _show_tabs = value + _handle_tab_visibility() +var hide_single_tab := false: + get: + return _hide_single_tab + set(value): + _hide_single_tab = value + _handle_tab_visibility() + +var _leaf: DockableLayoutPanel +var _show_tabs := true +var _hide_single_tab := false +var _have_focus := false + + +func _ready() -> void: + drag_to_rearrange_enabled = true + theme_type_variation = "EditorSection" + + _update_style() + + +func _enter_tree() -> void: + active_tab_rearranged.connect(_on_tab_changed) + tab_selected.connect(_on_tab_selected) + tab_changed.connect(_on_tab_changed) + + _update_style() + + +func _exit_tree() -> void: + active_tab_rearranged.disconnect(_on_tab_changed) + tab_selected.disconnect(_on_tab_selected) + tab_changed.disconnect(_on_tab_changed) + if is_instance_valid(get_popup()): + get_popup().queue_free() + + _update_style() + + +func track_nodes(nodes: Array[Control], new_leaf: DockableLayoutPanel) -> void: + _leaf = null # avoid using previous leaf in tab_changed signals + var min_size := mini(nodes.size(), get_child_count()) + # remove spare children + for i in range(min_size, get_child_count()): + var child := get_child(min_size) as DockableReferenceControl + child.reference_to = null + remove_child(child) + child.queue_free() + # add missing children + for i in range(min_size, nodes.size()): + var ref_control := DockableReferenceControl.new() + add_child(ref_control) + assert(nodes.size() == get_child_count(), "FIXME") + # setup children + for i in nodes.size(): + var ref_control := get_child(i) as DockableReferenceControl + ref_control.reference_to = nodes[i] + set_tab_title(i, nodes[i].name) + set_leaf(new_leaf) + _handle_tab_visibility() + + +func get_child_rect() -> Rect2: + var control := get_current_tab_control() + return Rect2(position + control.position, control.size) + + +func set_leaf(value: DockableLayoutPanel) -> void: + if get_tab_count() > 0 and value: + current_tab = clampi(value.current_tab, 0, get_tab_count() - 1) + _leaf = value + + _update_style() + + +func get_leaf() -> DockableLayoutPanel: + return _leaf + + +func get_layout_minimum_size() -> Vector2: + hide() + show() + return get_combined_minimum_size() + + +func _on_tab_selected(tab: int) -> void: + if _leaf: + _leaf.current_tab = tab + + +func _on_tab_changed(tab: int) -> void: + if not _leaf: + return + var control := get_tab_control(tab) + if not control: + return + var tab_name := control.name + var name_index_in_leaf := _leaf.find_name(tab_name) + if name_index_in_leaf != tab: # NOTE: this handles added tabs (index == -1) + tab_layout_changed.emit(tab) + + +func _handle_tab_visibility() -> void: + if _hide_single_tab and get_tab_count() == 1: + tabs_visible = false + else: + tabs_visible = _show_tabs + + if is_node_ready(): + _update_style() + + +func _update_style() -> void: + var mouse_hovering: bool = is_mouse_on_section() + + if leaf: + for name in leaf.get_names(): + if not name.begins_with("_"): + continue + tabs_visible = false + + var panel_style_name: String = "panel" + var tabbar_style_name: String = "tabbar_background" + _have_focus = mouse_hovering + if mouse_hovering: + panel_style_name += "_focus" + tabbar_style_name += "_focus" + else: + panel_style_name += "_unfocus" + tabbar_style_name += "_unfocus" + + if tabs_visible: + panel_style_name = "tab_" + panel_style_name + + add_theme_stylebox_override("panel", get_theme_stylebox(panel_style_name, "EditorSection")) + add_theme_stylebox_override( + "tabbar_background", get_theme_stylebox(tabbar_style_name, "EditorSection") + ) + + +func _input(event: InputEvent) -> void: + if event is InputEventMouseButton and event.is_pressed(): + _update_style() + + +func is_mouse_on_section() -> bool: + var local_mouse_pos: Vector2 = get_global_mouse_position() - global_position + return not ( + local_mouse_pos.x < 0 + or local_mouse_pos.y < 0 + or local_mouse_pos.x > size.x + or local_mouse_pos.y > size.y + ) diff --git a/addons/dockable_container/dockable_panel.gd.uid b/addons/dockable_container/dockable_panel.gd.uid new file mode 100644 index 00000000..aa5a5aff --- /dev/null +++ b/addons/dockable_container/dockable_panel.gd.uid @@ -0,0 +1 @@ +uid://dca5brtlainkk diff --git a/addons/dockable_container/dockable_panel_reference_control.gd b/addons/dockable_container/dockable_panel_reference_control.gd new file mode 100644 index 00000000..06dc11b9 --- /dev/null +++ b/addons/dockable_container/dockable_panel_reference_control.gd @@ -0,0 +1,49 @@ +@tool +class_name DockableReferenceControl +extends Container +## Control that mimics its own visibility and rect into another Control. + +var reference_to: Control: + get: + return _reference_to + set(control): + if _reference_to != control: + if is_instance_valid(_reference_to): + _reference_to.renamed.disconnect(_on_reference_to_renamed) + _reference_to.minimum_size_changed.disconnect(update_minimum_size) + _reference_to = control + + minimum_size_changed.emit() + if not is_instance_valid(_reference_to): + return + _reference_to.renamed.connect(_on_reference_to_renamed) + _reference_to.minimum_size_changed.connect(update_minimum_size) + _reference_to.visible = visible + _reposition_reference() + +var _reference_to: Control = null + + +func _ready() -> void: + mouse_filter = MOUSE_FILTER_IGNORE + set_notify_transform(true) + + +func _notification(what: int) -> void: + if what == NOTIFICATION_VISIBILITY_CHANGED and _reference_to: + _reference_to.visible = visible + elif what == NOTIFICATION_TRANSFORM_CHANGED and _reference_to: + _reposition_reference() + + +func _get_minimum_size() -> Vector2: + return _reference_to.get_combined_minimum_size() if _reference_to else Vector2.ZERO + + +func _reposition_reference() -> void: + _reference_to.global_position = global_position + _reference_to.size = size + + +func _on_reference_to_renamed() -> void: + name = _reference_to.name diff --git a/addons/dockable_container/dockable_panel_reference_control.gd.uid b/addons/dockable_container/dockable_panel_reference_control.gd.uid new file mode 100644 index 00000000..8ff02d12 --- /dev/null +++ b/addons/dockable_container/dockable_panel_reference_control.gd.uid @@ -0,0 +1 @@ +uid://8fcumdc7ut8 diff --git a/addons/dockable_container/drag_n_drop_panel.gd b/addons/dockable_container/drag_n_drop_panel.gd new file mode 100644 index 00000000..7e5d7714 --- /dev/null +++ b/addons/dockable_container/drag_n_drop_panel.gd @@ -0,0 +1,82 @@ +@tool +extends Control + +enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER } + +const DRAW_NOTHING := -1 +const DRAW_CENTERED := -2 +const MARGIN_NONE := -1 + +var _draw_margin := DRAW_NOTHING +var _should_split := false + + +func _notification(what: int) -> void: + if what == NOTIFICATION_MOUSE_EXIT: + _draw_margin = DRAW_NOTHING + queue_redraw() + elif what == NOTIFICATION_MOUSE_ENTER and not _should_split: + _draw_margin = DRAW_CENTERED + queue_redraw() + + +func _gui_input(event: InputEvent) -> void: + if _should_split and event is InputEventMouseMotion: + _draw_margin = _find_hover_margin(event.position) + queue_redraw() + + +func _draw() -> void: + var rect: Rect2 + if _draw_margin == DRAW_NOTHING: + return + elif _draw_margin == DRAW_CENTERED: + rect = Rect2(Vector2.ZERO, size) + elif _draw_margin == MARGIN_LEFT: + rect = Rect2(0, 0, size.x * 0.5, size.y) + elif _draw_margin == MARGIN_TOP: + rect = Rect2(0, 0, size.x, size.y * 0.5) + elif _draw_margin == MARGIN_RIGHT: + var half_width = size.x * 0.5 + rect = Rect2(half_width, 0, half_width, size.y) + elif _draw_margin == MARGIN_BOTTOM: + var half_height = size.y * 0.5 + rect = Rect2(0, half_height, size.x, half_height) + var stylebox := get_theme_stylebox("panel", "TooltipPanel") + draw_style_box(stylebox, rect) + + +func set_enabled(enabled: bool, should_split: bool = true) -> void: + visible = enabled + _should_split = should_split + if enabled: + _draw_margin = DRAW_NOTHING + queue_redraw() + + +func get_hover_margin() -> int: + return _draw_margin + + +func _find_hover_margin(point: Vector2) -> int: + var half_size := size * 0.5 + + var left := point.distance_squared_to(Vector2(0, half_size.y)) + var lesser := left + var lesser_margin := MARGIN_LEFT + + var top := point.distance_squared_to(Vector2(half_size.x, 0)) + if lesser > top: + lesser = top + lesser_margin = MARGIN_TOP + + var right := point.distance_squared_to(Vector2(size.x, half_size.y)) + if lesser > right: + lesser = right + lesser_margin = MARGIN_RIGHT + + var bottom := point.distance_squared_to(Vector2(half_size.x, size.y)) + if lesser > bottom: + #lesser = bottom # unused result + lesser_margin = MARGIN_BOTTOM + return lesser_margin diff --git a/addons/dockable_container/drag_n_drop_panel.gd.uid b/addons/dockable_container/drag_n_drop_panel.gd.uid new file mode 100644 index 00000000..535296f3 --- /dev/null +++ b/addons/dockable_container/drag_n_drop_panel.gd.uid @@ -0,0 +1 @@ +uid://btfkmp6r4s6kb diff --git a/addons/dockable_container/floating_window.gd b/addons/dockable_container/floating_window.gd new file mode 100644 index 00000000..99f5c7ca --- /dev/null +++ b/addons/dockable_container/floating_window.gd @@ -0,0 +1,81 @@ +class_name FloatingWindow +extends Window + +## Emitted when the window's position or size changes, or when it's closed. +signal data_changed + +var window_content: Control +var prevent_data_erasure := false +var _is_initialized := false + + +func _init(content: Control, data := {}) -> void: + hide() + window_content = content + title = window_content.name + name = window_content.name + min_size = window_content.get_minimum_size() + unresizable = false + wrap_controls = true + always_on_top = false + force_native = true + ready.connect(_deserialize.bind(data)) + + +func _ready() -> void: + set_deferred(&"size", Vector2(300, 300)) + current_screen = get_window().current_screen + await get_tree().process_frame + await get_tree().process_frame + # Enable always_on_top for all child windows, + # to fix a bug where the child windows of floating windows appear behind them. + # TODO: Remove the loop when this bug gets fixed in Godot's side. + # Probably when https://github.com/godotengine/godot/issues/92848 is closed. + for dialog_child in find_children("", "Window", true, false): + if dialog_child is Window: + dialog_child.always_on_top = always_on_top + + show() + + +func _input(event: InputEvent) -> void: + if event is InputEventMouse: + # Emit `data_changed` when the window is being moved. + if not window_content.get_rect().has_point(event.position) and _is_initialized: + data_changed.emit(name, serialize()) + + +func serialize() -> Dictionary: + return {"size": size, "position": position} + + +func _deserialize(data: Dictionary) -> void: + window_content.get_parent().remove_child(window_content) + window_content.visible = true + window_content.global_position = Vector2.ZERO + add_child(window_content) + size_changed.connect(window_size_changed) + if "position" in data: + await get_tree().process_frame + await get_tree().process_frame + position = data["position"] + if "size" in data: + set_deferred(&"size", data["size"]) + _is_initialized = true + + +func window_size_changed() -> void: + window_content.size = size + window_content.position = Vector2.ZERO + if _is_initialized: + data_changed.emit(name, serialize()) + + +func destroy() -> void: + size_changed.disconnect(window_size_changed) + queue_free() + + +func _exit_tree() -> void: + if _is_initialized and !prevent_data_erasure: + data_changed.emit(name, {}) diff --git a/addons/dockable_container/floating_window.gd.uid b/addons/dockable_container/floating_window.gd.uid new file mode 100644 index 00000000..ab69fc16 --- /dev/null +++ b/addons/dockable_container/floating_window.gd.uid @@ -0,0 +1 @@ +uid://bj1b6rdd8u8dy diff --git a/addons/dockable_container/icon.svg b/addons/dockable_container/icon.svg new file mode 100644 index 00000000..d87d598d --- /dev/null +++ b/addons/dockable_container/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/addons/dockable_container/icon.svg.import b/addons/dockable_container/icon.svg.import new file mode 100644 index 00000000..595e5730 --- /dev/null +++ b/addons/dockable_container/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dy25danh2am23" +path="res://.godot/imported/icon.svg-35635e7bbda4487d4b2942da1d987df8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dockable_container/icon.svg" +dest_files=["res://.godot/imported/icon.svg-35635e7bbda4487d4b2942da1d987df8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/dockable_container/inspector_plugin/editor_inspector_plugin.gd b/addons/dockable_container/inspector_plugin/editor_inspector_plugin.gd new file mode 100644 index 00000000..73d0372b --- /dev/null +++ b/addons/dockable_container/inspector_plugin/editor_inspector_plugin.gd @@ -0,0 +1,22 @@ +extends EditorInspectorPlugin + +const LayoutEditorProperty := preload("layout_editor_property.gd") + + +func _can_handle(object: Object) -> bool: + return object is DockableContainer + + +func _parse_property( + _object: Object, + _type: Variant.Type, + name: String, + _hint: PropertyHint, + _hint_text: String, + _usage: int, + _wide: bool +) -> bool: + if name == "layout": + var editor_property := LayoutEditorProperty.new() + add_property_editor("layout", editor_property) + return false diff --git a/addons/dockable_container/inspector_plugin/editor_inspector_plugin.gd.uid b/addons/dockable_container/inspector_plugin/editor_inspector_plugin.gd.uid new file mode 100644 index 00000000..db6dd318 --- /dev/null +++ b/addons/dockable_container/inspector_plugin/editor_inspector_plugin.gd.uid @@ -0,0 +1 @@ +uid://st877dcaad8x diff --git a/addons/dockable_container/inspector_plugin/layout_editor_property.gd b/addons/dockable_container/inspector_plugin/layout_editor_property.gd new file mode 100644 index 00000000..eb00134b --- /dev/null +++ b/addons/dockable_container/inspector_plugin/layout_editor_property.gd @@ -0,0 +1,71 @@ +extends EditorProperty + +var _container := DockableContainer.new() +var _hidden_menu_button := MenuButton.new() +var _hidden_menu_popup: PopupMenu +var _hidden_menu_list: PackedStringArray + + +func _ready() -> void: + custom_minimum_size = Vector2(128, 256) + + _hidden_menu_button.text = "Visible nodes" + add_child(_hidden_menu_button) + _hidden_menu_popup = _hidden_menu_button.get_popup() + _hidden_menu_popup.hide_on_checkable_item_selection = false + _hidden_menu_popup.about_to_popup.connect(_on_hidden_menu_popup_about_to_show) + _hidden_menu_popup.id_pressed.connect(_on_hidden_menu_popup_id_pressed) + + _container.clone_layout_on_ready = false + _container.custom_minimum_size = custom_minimum_size + + var value := _get_layout().clone() # The layout gets reset when selecting it without clone + for n in value.get_names(): + var child := _create_child_control(n) + _container.add_child(child) + _container.set(get_edited_property(), value) + add_child(_container) + set_bottom_editor(_container) + + +func _exit_tree() -> void: # Not sure if this is needed, but just to be sure + queue_free() + + +func _update_property() -> void: + var value := _get_layout() + _container.set(get_edited_property(), value) + + +func _get_layout() -> DockableLayout: + var original_container := get_edited_object() as DockableContainer + return original_container.get(get_edited_property()) + + +func _create_child_control(named: String) -> Label: + var new_control := Label.new() + new_control.name = named + new_control.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + new_control.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + new_control.clip_text = true + new_control.text = named + return new_control + + +func _on_hidden_menu_popup_about_to_show() -> void: + var layout := _get_layout().clone() + _hidden_menu_popup.clear() + _hidden_menu_list = layout.get_names() + for i in _hidden_menu_list.size(): + var tab_name := _hidden_menu_list[i] + _hidden_menu_popup.add_check_item(tab_name, i) + _hidden_menu_popup.set_item_checked(i, not layout.is_tab_hidden(tab_name)) + + +func _on_hidden_menu_popup_id_pressed(id: int) -> void: + var layout := _get_layout().clone() + var tab_name := _hidden_menu_list[id] + var new_hidden := not layout.is_tab_hidden(tab_name) + _get_layout().set_tab_hidden(tab_name, new_hidden) + _hidden_menu_popup.set_item_checked(id, not new_hidden) + emit_changed(get_edited_property(), _get_layout()) # This line may not be needed diff --git a/addons/dockable_container/inspector_plugin/layout_editor_property.gd.uid b/addons/dockable_container/inspector_plugin/layout_editor_property.gd.uid new file mode 100644 index 00000000..56aa173f --- /dev/null +++ b/addons/dockable_container/inspector_plugin/layout_editor_property.gd.uid @@ -0,0 +1 @@ +uid://b10vbk0p47buq diff --git a/addons/dockable_container/layout.gd b/addons/dockable_container/layout.gd new file mode 100644 index 00000000..b02df10f --- /dev/null +++ b/addons/dockable_container/layout.gd @@ -0,0 +1,260 @@ +@tool +class_name DockableLayout +extends Resource +## DockableLayout Resource definition, holding the root DockableLayoutNode and hidden tabs. +## +## DockableLayoutSplit are binary trees with nested DockableLayoutSplit subtrees +## and DockableLayoutPanel leaves. Both of them inherit from DockableLayoutNode to help with +## type annotation and define common functionality. +## +## Hidden tabs are marked in the `hidden_tabs` Dictionary by name. + +enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER } + +@export var root: DockableLayoutNode = DockableLayoutPanel.new(): + get: + return _root + set(value): + set_root(value) +@export var hidden_tabs := {}: + get: + return _hidden_tabs + set(value): + if value != _hidden_tabs: + _hidden_tabs = value + changed.emit() +## A [Dictionary] of [StringName] and [Dictionary], containing data such as position and size. +@export var windows := {}: + get: + return _windows + set(value): + if value != _windows: + _windows = value + changed.emit() + +var _changed_signal_queued := false +var _first_leaf: DockableLayoutPanel +var _hidden_tabs: Dictionary +var _windows: Dictionary +var _leaf_by_node_name: Dictionary +var _root: DockableLayoutNode = DockableLayoutPanel.new() + + +func _init() -> void: + resource_name = "Layout" + + +func set_root(value: DockableLayoutNode, should_emit_changed := true) -> void: + if not value: + value = DockableLayoutPanel.new() + if _root == value: + return + if _root and _root.changed.is_connected(_on_root_changed): + _root.changed.disconnect(_on_root_changed) + _root = value + _root.parent = null + _root.changed.connect(_on_root_changed) + if should_emit_changed: + _on_root_changed() + + +func get_root() -> DockableLayoutNode: + return _root + + +func clone() -> DockableLayout: + return duplicate(true) + + +func get_names() -> PackedStringArray: + return _root.get_names() + + +## Add missing nodes on first leaf and remove nodes outside indices from leaves. +## +## _leaf_by_node_name = { +## (string keys) = respective Leaf that holds the node name, +## } +func update_nodes(names: PackedStringArray) -> void: + _leaf_by_node_name.clear() + _first_leaf = null + var empty_leaves: Array[DockableLayoutPanel] = [] + _ensure_names_in_node(_root, names, empty_leaves) # Changes _leaf_by_node_name and empty_leaves + for l in empty_leaves: + _remove_leaf(l) + if not _first_leaf: + _first_leaf = DockableLayoutPanel.new() + set_root(_first_leaf) + for n in names: + if not _leaf_by_node_name.has(n): + _first_leaf.push_name(n) + _leaf_by_node_name[n] = _first_leaf + _on_root_changed() + + +func move_node_to_leaf(node: Node, leaf: DockableLayoutPanel, relative_position: int) -> void: + var node_name := node.name + var previous_leaf: DockableLayoutPanel = _leaf_by_node_name.get(node_name) + if previous_leaf: + previous_leaf.remove_node(node) + if previous_leaf.is_empty(): + _remove_leaf(previous_leaf) + + leaf.insert_node(relative_position, node) + _leaf_by_node_name[node_name] = leaf + _on_root_changed() + + +func get_leaf_for_node(node: Node) -> DockableLayoutPanel: + return _leaf_by_node_name.get(node.name) + + +func split_leaf_with_node(leaf: DockableLayoutPanel, node: Node, margin: int) -> void: + var root_branch := leaf.parent + var new_leaf := DockableLayoutPanel.new() + var new_branch := DockableLayoutSplit.new() + if margin == MARGIN_LEFT or margin == MARGIN_RIGHT: + new_branch.direction = DockableLayoutSplit.Direction.HORIZONTAL + else: + new_branch.direction = DockableLayoutSplit.Direction.VERTICAL + if margin == MARGIN_LEFT or margin == MARGIN_TOP: + new_branch.first = new_leaf + new_branch.second = leaf + else: + new_branch.first = leaf + new_branch.second = new_leaf + if _root == leaf: + set_root(new_branch, false) + elif root_branch: + if leaf == root_branch.first: + root_branch.first = new_branch + else: + root_branch.second = new_branch + + move_node_to_leaf(node, new_leaf, 0) + + +func add_node(node: Node) -> void: + var node_name := node.name + if _leaf_by_node_name.has(node_name): + return + _first_leaf.push_name(node_name) + _leaf_by_node_name[node_name] = _first_leaf + _on_root_changed() + + +func remove_node(node: Node) -> void: + var node_name := node.name + var leaf: DockableLayoutPanel = _leaf_by_node_name.get(node_name) + if not leaf: + return + leaf.remove_node(node) + _leaf_by_node_name.erase(node_name) + if leaf.is_empty(): + _remove_leaf(leaf) + _on_root_changed() + + +func rename_node(previous_name: String, new_name: String) -> void: + var leaf: DockableLayoutPanel = _leaf_by_node_name.get(previous_name) + if not leaf: + return + leaf.rename_node(previous_name, new_name) + _leaf_by_node_name.erase(previous_name) + _leaf_by_node_name[new_name] = leaf + _on_root_changed() + + +func set_tab_hidden(name: String, hidden: bool) -> void: + if not _leaf_by_node_name.has(name): + return + if hidden: + _hidden_tabs[name] = true + else: + _hidden_tabs.erase(name) + _on_root_changed() + + +func save_window_properties(window_name: StringName, data: Dictionary) -> void: + var new_windows = windows.duplicate(true) + if data.is_empty(): + new_windows.erase(window_name) + else: + new_windows[window_name] = data + windows = new_windows + + +func is_tab_hidden(name: String) -> bool: + return _hidden_tabs.get(name, false) + + +func set_node_hidden(node: Node, hidden: bool) -> void: + set_tab_hidden(node.name, hidden) + + +func is_node_hidden(node: Node) -> bool: + return is_tab_hidden(node.name) + + +func _on_root_changed() -> void: + if _changed_signal_queued: + return + _changed_signal_queued = true + set_deferred("_changed_signal_queued", false) + emit_changed.call_deferred() + + +func _ensure_names_in_node( + node: DockableLayoutNode, names: PackedStringArray, empty_leaves: Array[DockableLayoutPanel] +) -> void: + if node is DockableLayoutPanel: + node.update_nodes(names, _leaf_by_node_name) # This changes _leaf_by_node_name + if node.is_empty(): + empty_leaves.append(node) + if not _first_leaf: + _first_leaf = node + elif node is DockableLayoutSplit: + _ensure_names_in_node(node.first, names, empty_leaves) + _ensure_names_in_node(node.second, names, empty_leaves) + else: + assert(false, "Invalid Resource, should be branch or leaf, found %s" % node) + + +func _remove_leaf(leaf: DockableLayoutPanel) -> void: + assert(leaf.is_empty(), "FIXME: trying to remove_at a leaf with nodes") + if _root == leaf: + return + var collapsed_branch := leaf.parent + assert(collapsed_branch is DockableLayoutSplit, "FIXME: leaf is not a child of branch") + var kept_branch: DockableLayoutNode = ( + collapsed_branch.first if leaf == collapsed_branch.second else collapsed_branch.second + ) + var root_branch := collapsed_branch.parent #HERE + if collapsed_branch == _root: + set_root(kept_branch, true) + elif root_branch: + if collapsed_branch == root_branch.first: + root_branch.first = kept_branch + else: + root_branch.second = kept_branch + + +func _print_tree() -> void: + print("TREE") + _print_tree_step(_root, 0, 0) + print("") + + +func _print_tree_step(tree_or_leaf: DockableLayoutNode, level: int, idx: int) -> void: + if tree_or_leaf is DockableLayoutPanel: + print(" |".repeat(level), "- (%d) = " % idx, tree_or_leaf.names) + elif tree_or_leaf is DockableLayoutSplit: + print( + " |".repeat(level), + "-+ (%d) = " % idx, + tree_or_leaf.direction, + " ", + tree_or_leaf.percent + ) + _print_tree_step(tree_or_leaf.first, level + 1, 1) + _print_tree_step(tree_or_leaf.second, level + 1, 2) diff --git a/addons/dockable_container/layout.gd.uid b/addons/dockable_container/layout.gd.uid new file mode 100644 index 00000000..43f1ab21 --- /dev/null +++ b/addons/dockable_container/layout.gd.uid @@ -0,0 +1 @@ +uid://cylirj261q6ru diff --git a/addons/dockable_container/layout_node.gd b/addons/dockable_container/layout_node.gd new file mode 100644 index 00000000..ba3accb4 --- /dev/null +++ b/addons/dockable_container/layout_node.gd @@ -0,0 +1,29 @@ +@tool +class_name DockableLayoutNode +extends Resource +## Base class for DockableLayout tree nodes + +var parent: DockableLayoutSplit: + get: + return _parent_ref.get_ref() + set(value): + _parent_ref = weakref(value) + +var _parent_ref := WeakRef.new() + + +func emit_tree_changed() -> void: + var node := self + while node: + node.emit_changed() + node = node.parent + + +## Returns whether there are any nodes +func is_empty() -> bool: + return true + + +## Returns all tab names in this node +func get_names() -> PackedStringArray: + return PackedStringArray() diff --git a/addons/dockable_container/layout_node.gd.uid b/addons/dockable_container/layout_node.gd.uid new file mode 100644 index 00000000..1d581b5d --- /dev/null +++ b/addons/dockable_container/layout_node.gd.uid @@ -0,0 +1 @@ +uid://v07flmpq7k2r diff --git a/addons/dockable_container/layout_panel.gd b/addons/dockable_container/layout_panel.gd new file mode 100644 index 00000000..e15201b1 --- /dev/null +++ b/addons/dockable_container/layout_panel.gd @@ -0,0 +1,89 @@ +@tool +class_name DockableLayoutPanel +extends DockableLayoutNode +## DockableLayout leaf nodes, defining tabs + +@export var names: PackedStringArray: + get: + return get_names() + set(value): + _names = value + emit_tree_changed() +@export var current_tab: int: + get: + return int(clamp(_current_tab, 0, _names.size() - 1)) + set(value): + if value != _current_tab: + _current_tab = value + emit_tree_changed() + +var _names := PackedStringArray() +var _current_tab := 0 + + +func _init() -> void: + resource_name = "Tabs" + + +## Returns all tab names in this node +func get_names() -> PackedStringArray: + return _names + + +func push_name(name: String) -> void: + _names.append(name) + emit_tree_changed() + + +func insert_node(position: int, node: Node) -> void: + _names.insert(position, node.name) + emit_tree_changed() + + +func find_name(node_name: String) -> int: + for i in _names.size(): + if _names[i] == node_name: + return i + return -1 + + +func find_child(node: Node) -> int: + return find_name(node.name) + + +func remove_node(node: Node) -> void: + var i := find_child(node) + if i >= 0: + _names.remove_at(i) + emit_tree_changed() + else: + push_warning("Remove failed, node '%s' was not found" % node) + + +func rename_node(previous_name: String, new_name: String) -> void: + var i := find_name(previous_name) + if i >= 0: + _names.set(i, new_name) + emit_tree_changed() + else: + push_warning("Rename failed, name '%s' was not found" % previous_name) + + +## Returns whether there are any nodes +func is_empty() -> bool: + return _names.is_empty() + + +func update_nodes(node_names: PackedStringArray, data: Dictionary) -> void: + var i := 0 + var removed_any := false + while i < _names.size(): + var current := _names[i] + if not current in node_names or data.has(current): + _names.remove_at(i) + removed_any = true + else: + data[current] = self + i += 1 + if removed_any: + emit_tree_changed() diff --git a/addons/dockable_container/layout_panel.gd.uid b/addons/dockable_container/layout_panel.gd.uid new file mode 100644 index 00000000..1edb035a --- /dev/null +++ b/addons/dockable_container/layout_panel.gd.uid @@ -0,0 +1 @@ +uid://bh202gagkdkar diff --git a/addons/dockable_container/layout_split.gd b/addons/dockable_container/layout_split.gd new file mode 100644 index 00000000..45f1f78b --- /dev/null +++ b/addons/dockable_container/layout_split.gd @@ -0,0 +1,111 @@ +@tool +class_name DockableLayoutSplit +extends DockableLayoutNode +## DockableLayout binary tree nodes, defining subtrees and leaf panels + +enum Direction { HORIZONTAL, VERTICAL } + +@export var direction := Direction.HORIZONTAL: + get: + return get_direction() + set(value): + set_direction(value) +@export_range(0, 1) var percent := 0.5: + get = get_percent, + set = set_percent +@export var first: DockableLayoutNode = DockableLayoutPanel.new(): + get: + return get_first() + set(value): + set_first(value) +@export var second: DockableLayoutNode = DockableLayoutPanel.new(): + get: + return get_second() + set(value): + set_second(value) + +var _direction := Direction.HORIZONTAL +var _percent := 0.5 +var _first: DockableLayoutNode +var _second: DockableLayoutNode + + +func _init() -> void: + resource_name = "Split" + + +func set_first(value: DockableLayoutNode) -> void: + if value == null: + _first = DockableLayoutPanel.new() + else: + _first = value + _first.parent = self + emit_tree_changed() + + +func get_first() -> DockableLayoutNode: + return _first + + +func set_second(value: DockableLayoutNode) -> void: + if value == null: + _second = DockableLayoutPanel.new() + else: + _second = value + emit_tree_changed() + _second.parent = self + + +func get_second() -> DockableLayoutNode: + return _second + + +func set_direction(value: Direction) -> void: + if value != _direction: + _direction = value + emit_tree_changed() + + +func get_direction() -> Direction: + return _direction + + +func set_percent(value: float) -> void: + var clamped_value := clampf(value, 0, 1) + if not is_equal_approx(_percent, clamped_value): + _percent = clamped_value + emit_tree_changed() + + +func get_percent() -> float: + return _percent + + +func get_names() -> PackedStringArray: + var names := _first.get_names() + names.append_array(_second.get_names()) + return names + + +## Returns whether there are any nodes +func is_empty() -> bool: + return _first.is_empty() and _second.is_empty() + + +func is_horizontal() -> bool: + return _direction == Direction.HORIZONTAL + + +func is_vertical() -> bool: + return _direction == Direction.VERTICAL + + +func _is_draggable() -> bool: + for dl: DockableLayoutNode in [first, second]: + if dl is not DockableLayoutPanel: + continue + + for name in dl.get_names(): + if name.begins_with("_"): + return false + return true diff --git a/addons/dockable_container/layout_split.gd.uid b/addons/dockable_container/layout_split.gd.uid new file mode 100644 index 00000000..3b8363b3 --- /dev/null +++ b/addons/dockable_container/layout_split.gd.uid @@ -0,0 +1 @@ +uid://dos02hjhrf1ws diff --git a/addons/dockable_container/plugin.cfg b/addons/dockable_container/plugin.cfg new file mode 100644 index 00000000..b3595919 --- /dev/null +++ b/addons/dockable_container/plugin.cfg @@ -0,0 +1,13 @@ +[plugin] + +name="Dockable Container" +description="Container script that manages docking/tiling UI panels. + +Panels are composed of tabs that can be dragged around and dropped to split another panel or compose its tabs. + +Layout information is stored in Resource objects, so they can be saved/loaded from disk easily. + +This plugin also offers a replica of the Container layout to be edited directly in the inspector." +author="gilzoide" +version="1.1.2" +script="plugin.gd" diff --git a/addons/dockable_container/plugin.gd b/addons/dockable_container/plugin.gd new file mode 100644 index 00000000..e93e0101 --- /dev/null +++ b/addons/dockable_container/plugin.gd @@ -0,0 +1,19 @@ +@tool +extends EditorPlugin + +const LayoutInspectorPlugin := preload("inspector_plugin/editor_inspector_plugin.gd") +const Icon := preload("icon.svg") + +var _layout_inspector_plugin: LayoutInspectorPlugin + + +func _enter_tree() -> void: + _layout_inspector_plugin = LayoutInspectorPlugin.new() + add_custom_type("DockableContainer", "Container", DockableContainer, Icon) + add_inspector_plugin(_layout_inspector_plugin) + + +func _exit_tree() -> void: + remove_inspector_plugin(_layout_inspector_plugin) + remove_custom_type("DockableContainer") + _layout_inspector_plugin = null diff --git a/addons/dockable_container/plugin.gd.uid b/addons/dockable_container/plugin.gd.uid new file mode 100644 index 00000000..ca216da1 --- /dev/null +++ b/addons/dockable_container/plugin.gd.uid @@ -0,0 +1 @@ +uid://cfkem7gta5wke diff --git a/addons/dockable_container/samples/TestScene.gd b/addons/dockable_container/samples/TestScene.gd new file mode 100644 index 00000000..f94ac972 --- /dev/null +++ b/addons/dockable_container/samples/TestScene.gd @@ -0,0 +1,63 @@ +extends VBoxContainer + +const SAVED_LAYOUT_PATH := "user://layout.tres" + +@onready var _container := $DockableContainers/DockableContainer as DockableContainer +@onready var _clone_control := $HBoxContainer/ControlPrefab as ColorRect +@onready var _checkbox_container := $HBoxContainer as HBoxContainer + + +func _ready() -> void: + if not OS.is_userfs_persistent(): + $HBoxContainer/SaveLayoutButton.visible = false + $HBoxContainer/LoadLayoutButton.visible = false + + var tabs := _container.get_tabs() + for i in tabs.size(): + var checkbox := CheckBox.new() + checkbox.text = str(i) + checkbox.button_pressed = not _container.is_control_hidden(tabs[i]) + checkbox.toggled.connect(_on_CheckButton_toggled.bind(tabs[i])) + _checkbox_container.add_child(checkbox) + + +func _on_add_pressed() -> void: + var control := _clone_control.duplicate() + control.get_node("Buttons/Rename").pressed.connect( + _on_control_rename_button_pressed.bind(control) + ) + control.get_node("Buttons/Remove").pressed.connect( + _on_control_remove_button_pressed.bind(control) + ) + control.color = Color(randf(), randf(), randf()) + control.name = "Control0" + + _container.add_child(control, true) + await _container.sort_children + _container.set_control_as_current_tab(control) + + +func _on_save_pressed() -> void: + if ResourceSaver.save(_container.layout, SAVED_LAYOUT_PATH) != OK: + print("ERROR") + + +func _on_load_pressed() -> void: + var res = load(SAVED_LAYOUT_PATH) + if res: + _container.set_layout(res.clone()) + else: + print("Error") + + +func _on_control_rename_button_pressed(control: Control) -> void: + control.name = StringName(str(control.name) + " =D") + + +func _on_control_remove_button_pressed(control: Control) -> void: + control.get_parent().remove_child(control) + control.queue_free() + + +func _on_CheckButton_toggled(button_pressed: bool, tab: Control) -> void: + _container.set_control_hidden(tab, not button_pressed) diff --git a/addons/dockable_container/samples/TestScene.gd.uid b/addons/dockable_container/samples/TestScene.gd.uid new file mode 100644 index 00000000..a8525dc2 --- /dev/null +++ b/addons/dockable_container/samples/TestScene.gd.uid @@ -0,0 +1 @@ +uid://57n31bygo7cy diff --git a/addons/dockable_container/samples/TestScene.tscn b/addons/dockable_container/samples/TestScene.tscn new file mode 100644 index 00000000..311440da --- /dev/null +++ b/addons/dockable_container/samples/TestScene.tscn @@ -0,0 +1,181 @@ +[gd_scene load_steps=16 format=3 uid="uid://drlvhuchtk6if"] + +[ext_resource type="Script" path="res://addons/dockable_container/dockable_container.gd" id="1"] +[ext_resource type="Script" path="res://addons/dockable_container/layout.gd" id="2"] +[ext_resource type="Script" path="res://addons/dockable_container/samples/TestScene.gd" id="4"] +[ext_resource type="Script" path="res://addons/dockable_container/layout_split.gd" id="4_yhgfb"] +[ext_resource type="Script" path="res://addons/dockable_container/layout_panel.gd" id="5"] + +[sub_resource type="Resource" id="Resource_8aoc2"] +resource_name = "Tabs" +script = ExtResource("5") +names = PackedStringArray("Control0") +current_tab = 0 + +[sub_resource type="Resource" id="Resource_6kjom"] +resource_name = "Tabs" +script = ExtResource("5") +names = PackedStringArray("Control1", "Control2") +current_tab = 0 + +[sub_resource type="Resource" id="Resource_hl8y1"] +resource_name = "Split" +script = ExtResource("4_yhgfb") +direction = 1 +percent = 0.5 +first = SubResource("Resource_8aoc2") +second = SubResource("Resource_6kjom") + +[sub_resource type="Resource" id="Resource_ybwqe"] +resource_name = "Layout" +script = ExtResource("2") +root = SubResource("Resource_hl8y1") +hidden_tabs = {} +windows = {} +save_on_change = false + +[sub_resource type="Resource" id="Resource_ntwfj"] +resource_name = "Tabs" +script = ExtResource("5") +names = PackedStringArray("Control3") +current_tab = 0 + +[sub_resource type="Resource" id="Resource_dmyvf"] +resource_name = "Tabs" +script = ExtResource("5") +names = PackedStringArray("Control4") +current_tab = 0 + +[sub_resource type="Resource" id="Resource_vag66"] +resource_name = "Split" +script = ExtResource("4_yhgfb") +direction = 1 +percent = 0.281 +first = SubResource("Resource_ntwfj") +second = SubResource("Resource_dmyvf") + +[sub_resource type="Resource" id="Resource_4q660"] +resource_name = "Tabs" +script = ExtResource("5") +names = PackedStringArray("Control5") +current_tab = 0 + +[sub_resource type="Resource" id="Resource_jhibs"] +resource_name = "Split" +script = ExtResource("4_yhgfb") +direction = 0 +percent = 0.5 +first = SubResource("Resource_vag66") +second = SubResource("Resource_4q660") + +[sub_resource type="Resource" id="Resource_xhxpg"] +resource_name = "Layout" +script = ExtResource("2") +root = SubResource("Resource_jhibs") +hidden_tabs = {} +windows = {} +save_on_change = false + +[node name="SampleScene" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource("4") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 +alignment = 1 + +[node name="AddControlButton" type="Button" parent="HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 +text = "(+) ADD CONTROL" + +[node name="SaveLayoutButton" type="Button" parent="HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 +text = "Save Layout" + +[node name="LoadLayoutButton" type="Button" parent="HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 +text = "Load Layout" + +[node name="ControlPrefab" type="ColorRect" parent="HBoxContainer"] +visible = false +layout_mode = 2 +color = Color(0.129412, 0.121569, 0.121569, 1) + +[node name="Buttons" type="VBoxContainer" parent="HBoxContainer/ControlPrefab"] +layout_mode = 0 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -65.5 +offset_top = -22.0 +offset_right = 65.5 +offset_bottom = 22.0 + +[node name="Rename" type="Button" parent="HBoxContainer/ControlPrefab/Buttons"] +layout_mode = 2 +text = "Rename" + +[node name="Remove" type="Button" parent="HBoxContainer/ControlPrefab/Buttons"] +layout_mode = 2 +text = "REMOVE" + +[node name="DockableContainers" type="HBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="DockableContainer" type="Container" parent="DockableContainers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1") +layout = SubResource("Resource_ybwqe") + +[node name="Control0" type="ColorRect" parent="DockableContainers/DockableContainer"] +layout_mode = 2 + +[node name="Control1" type="ColorRect" parent="DockableContainers/DockableContainer"] +layout_mode = 2 +color = Color(0.141176, 0.0745098, 0.603922, 1) + +[node name="Control2" type="ColorRect" parent="DockableContainers/DockableContainer"] +visible = false +layout_mode = 2 +color = Color(0.533333, 0.380392, 0.380392, 1) + +[node name="Separator" type="ColorRect" parent="DockableContainers"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +color = Color(0, 0, 0, 1) + +[node name="DockableContainer2" type="Container" parent="DockableContainers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1") +layout = SubResource("Resource_xhxpg") + +[node name="Control3" type="ColorRect" parent="DockableContainers/DockableContainer2"] +layout_mode = 2 +color = Color(0, 1, 0.905882, 1) + +[node name="Control4" type="ColorRect" parent="DockableContainers/DockableContainer2"] +layout_mode = 2 +color = Color(0, 0.698039, 0.0588235, 1) + +[node name="Control5" type="ColorRect" parent="DockableContainers/DockableContainer2"] +layout_mode = 2 +color = Color(1, 0.937255, 0, 1) + +[connection signal="pressed" from="HBoxContainer/AddControlButton" to="." method="_on_add_pressed"] +[connection signal="pressed" from="HBoxContainer/SaveLayoutButton" to="." method="_on_save_pressed"] +[connection signal="pressed" from="HBoxContainer/LoadLayoutButton" to="." method="_on_load_pressed"] diff --git a/addons/dockable_container/split_handle.gd b/addons/dockable_container/split_handle.gd new file mode 100644 index 00000000..2dfc6163 --- /dev/null +++ b/addons/dockable_container/split_handle.gd @@ -0,0 +1,127 @@ +@tool +extends Control + +const SPLIT_THEME_CLASS: PackedStringArray = [ + "HSplitContainer", # SPLIT_THEME_CLASS[DockableLayoutSplit.Direction.HORIZONTAL] + "VSplitContainer", # SPLIT_THEME_CLASS[DockableLayoutSplit.Direction.VERTICAL] +] + +const SPLIT_MOUSE_CURSOR_SHAPE: Array[Control.CursorShape] = [ + Control.CURSOR_HSPLIT, # SPLIT_MOUSE_CURSOR_SHAPE[DockableLayoutSplit.Direction.HORIZONTAL] + Control.CURSOR_VSPLIT, # SPLIT_MOUSE_CURSOR_SHAPE[DockableLayoutSplit.Direction.VERTICAL] +] + +var layout_split: DockableLayoutSplit +var first_minimum_size: Vector2 +var second_minimum_size: Vector2 + +var _parent_rect: Rect2 +var _mouse_hovering := false +var _dragging := false +var _draggable: bool: + get(): + return layout_split._is_draggable() + + +func _draw() -> void: + var theme_class := SPLIT_THEME_CLASS[layout_split.direction] + var icon := get_theme_icon("grabber", theme_class) + var autohide := bool(get_theme_constant("autohide", theme_class)) + if not icon or (autohide and not _mouse_hovering): + return + + draw_texture(icon, (size - icon.get_size()) * 0.5) + + +func _gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and _draggable: + _dragging = event.is_pressed() + if event.double_click: + layout_split.percent = 0.5 + elif _dragging and event is InputEventMouseMotion and _draggable: + if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): + _dragging = false + return + + var mouse_in_parent := get_parent_control().get_local_mouse_position() + if layout_split.is_horizontal(): + layout_split.percent = ( + (mouse_in_parent.x - _parent_rect.position.x) / _parent_rect.size.x + ) + else: + layout_split.percent = ( + (mouse_in_parent.y - _parent_rect.position.y) / _parent_rect.size.y + ) + + +func _notification(what: int) -> void: + if what == NOTIFICATION_MOUSE_ENTER: + _mouse_hovering = true + set_split_cursor(true) + if bool(get_theme_constant("autohide", SPLIT_THEME_CLASS[layout_split.direction])): + queue_redraw() + elif what == NOTIFICATION_MOUSE_EXIT: + _mouse_hovering = false + set_split_cursor(false) + if bool(get_theme_constant("autohide", SPLIT_THEME_CLASS[layout_split.direction])): + queue_redraw() + elif what == NOTIFICATION_FOCUS_EXIT: + _dragging = false + + +func get_layout_minimum_size() -> Vector2: + if not layout_split: + return Vector2.ZERO + var separation := get_theme_constant("separation", SPLIT_THEME_CLASS[layout_split.direction]) + if layout_split.is_horizontal(): + return Vector2( + first_minimum_size.x + separation + second_minimum_size.x, + maxf(first_minimum_size.y, second_minimum_size.y) + ) + else: + return Vector2( + maxf(first_minimum_size.x, second_minimum_size.x), + first_minimum_size.y + separation + second_minimum_size.y + ) + + +func set_split_cursor(value: bool) -> void: + if value and _draggable: + mouse_default_cursor_shape = SPLIT_MOUSE_CURSOR_SHAPE[layout_split.direction] + else: + mouse_default_cursor_shape = CURSOR_ARROW + + +func get_split_rects(rect: Rect2) -> Dictionary: + _parent_rect = rect + var separation := get_theme_constant("separation", SPLIT_THEME_CLASS[layout_split.direction]) + var origin := rect.position + var percent := layout_split.percent + if layout_split.is_horizontal(): + var split_offset := clampf( + rect.size.x * percent - separation * 0.5, + first_minimum_size.x, + rect.size.x - second_minimum_size.x - separation + ) + var second_width := rect.size.x - split_offset - separation + + return { + "first": Rect2(origin.x, origin.y, split_offset, rect.size.y), + "self": Rect2(origin.x + split_offset, origin.y, separation, rect.size.y), + "second": + Rect2(origin.x + split_offset + separation, origin.y, second_width, rect.size.y), + } + else: + var split_offset := clampf( + rect.size.y * percent - separation * 0.5, + first_minimum_size.y, + rect.size.y - second_minimum_size.y - separation + ) + var second_height := rect.size.y - split_offset - separation + + return { + "first": Rect2(origin.x, origin.y, rect.size.x, split_offset), + "self": Rect2(origin.x, origin.y + split_offset, rect.size.x, separation), + "second": + Rect2(origin.x, origin.y + split_offset + separation, rect.size.x, second_height), + } diff --git a/addons/dockable_container/split_handle.gd.uid b/addons/dockable_container/split_handle.gd.uid new file mode 100644 index 00000000..3bd28a98 --- /dev/null +++ b/addons/dockable_container/split_handle.gd.uid @@ -0,0 +1 @@ +uid://c3g628ohy6cus diff --git a/common/layouts/editor/editor_section.gd b/common/layouts/editor/editor_section.gd deleted file mode 100644 index 56ae4231..00000000 --- a/common/layouts/editor/editor_section.gd +++ /dev/null @@ -1,98 +0,0 @@ -@tool -class_name EditorSection extends TabContainer - -@export var default_size: int = 0 : - set(value): - var parent: Node = get_parent() - if not parent is SplitContainer: return - - if parent.get_children().find(self) == 0: - parent.split_offset = value - else: - parent.split_offset = -value - - default_size = value -@export var lock_size: bool = false : - set(value): - var parent: Node = get_parent() - if not parent is SplitContainer: return - if value: - parent.dragging_enabled = false - - lock_size = value - -var have_focus: bool = false - - -func _ready() -> void: - set_process_input(true) - update_style(false) - get_viewport().gui_focus_changed.connect(_on_gui_focus_changed) - - for child in get_children(): - var icon: Variant = child.get("section_icon") - var child_idx: int = get_children().find(child) - if icon is Texture2D: - set_tab_icon(child_idx, icon) - - - -func _on_gui_focus_changed(node: Control) -> void: - if Engine.is_editor_hint(): - return - - update_style(_is_child(node)) - - -func update_style(focus: bool) -> void: - var child_count := get_child_count() - visible = child_count > 0 - tabs_visible = child_count != 1 - - var panel_style_name: String = "panel" - var tabbar_style_name: String = "tabbar_background" - have_focus = focus - if focus: - panel_style_name += "_focus" - tabbar_style_name += "_focus" - else: - panel_style_name += "_unfocus" - tabbar_style_name += "_unfocus" - - if tabs_visible: - panel_style_name = "tab_" + panel_style_name - - add_theme_stylebox_override("panel", get_theme_stylebox(panel_style_name, "EditorSection")) - add_theme_stylebox_override("tabbar_background", get_theme_stylebox(tabbar_style_name, "EditorSection")) - - -func _is_child(node: Control) -> bool: - var parent: Node = node - for i in range(256): - var next_parent = parent.get_parent() - if next_parent == null: - return false - elif parent == self: - return true - parent = next_parent - return false - - -func _input(event: InputEvent) -> void: - if event is InputEventMouseButton and event.is_pressed(): - var mouse_hovering: bool = is_mouse_on_section() - update_style(mouse_hovering) - - -func is_mouse_on_section() -> bool: - var local_mouse_pos := get_global_mouse_position() - global_position - return not (local_mouse_pos.x < 0 or local_mouse_pos.y < 0 or \ - local_mouse_pos.x > size.x or local_mouse_pos.y > size.y) - - -func _on_child_entered_tree(_node: Node) -> void: - update_style(have_focus) - - -func _on_child_exiting_tree(_node: Node) -> void: - update_style(have_focus) diff --git a/common/layouts/editor/editor_section.gd.uid b/common/layouts/editor/editor_section.gd.uid deleted file mode 100644 index 45796522..00000000 --- a/common/layouts/editor/editor_section.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ijejsk35dal diff --git a/common/layouts/editor/editor_section.tscn b/common/layouts/editor/editor_section.tscn deleted file mode 100644 index 152e3b8a..00000000 --- a/common/layouts/editor/editor_section.tscn +++ /dev/null @@ -1,42 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://dfkqf3wjdnj0m"] - -[ext_resource type="Script" uid="uid://ijejsk35dal" path="res://common/layouts/editor/editor_section.gd" id="1_1u3ep"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1u3ep"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 -bg_color = Color(0.186303, 0.183362, 0.215088, 1) -border_width_left = 1 -border_width_top = 1 -border_width_right = 1 -border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 3 -corner_radius_top_right = 3 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qe3vh"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 -bg_color = Color(0.225127, 0.224539, 0.257441, 1) -border_width_left = 1 -border_width_right = 1 -border_width_bottom = 1 -border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 - -[node name="EditorSection" type="TabContainer"] -visible = false -offset_right = 40.0 -offset_bottom = 40.0 -focus_mode = 1 -theme_type_variation = &"EditorSection" -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_qe3vh") -script = ExtResource("1_1u3ep") - -[connection signal="child_entered_tree" from="." to="." method="_on_child_entered_tree"] -[connection signal="child_exiting_tree" from="." to="." method="_on_child_exiting_tree"] diff --git a/common/layouts/editor/list_section/list_section.gd b/common/layouts/editor/list_section/list_section.gd index d855eb7d..a801c1d5 100644 --- a/common/layouts/editor/list_section/list_section.gd +++ b/common/layouts/editor/list_section/list_section.gd @@ -1,15 +1,15 @@ -extends VBoxContainer +extends PanelContainer @export var section_icon: Texture2D -@onready var vbox := $ScrollContainer/VBox -@onready var search_bar: LineEdit = $ToolBar/LineEdit +@onready var vbox := %VBox +@onready var search_bar: LineEdit = %SearchLine var add_func: Callable func _ready() -> void: - search_bar.placeholder_text = "Filter %s" % name + search_bar.placeholder_text = "Filter %s" % name.to_lower() func clear() -> void: diff --git a/common/layouts/editor/list_section/list_section.tscn b/common/layouts/editor/list_section/list_section.tscn index 7f6ca90a..f48a6937 100644 --- a/common/layouts/editor/list_section/list_section.tscn +++ b/common/layouts/editor/list_section/list_section.tscn @@ -3,29 +3,34 @@ [ext_resource type="Script" uid="uid://yglbu25x1rsy" path="res://common/layouts/editor/list_section/list_section.gd" id="1_coa1v"] [ext_resource type="Texture2D" uid="uid://hlck6y4i3l5q" path="res://ui/assets/icons/plus.svg" id="3_j4mj2"] -[node name="ListSection" type="VBoxContainer"] +[node name="ListSection" type="PanelContainer"] +custom_minimum_size = Vector2(250, 0) +offset_right = 8.0 +offset_bottom = 8.0 script = ExtResource("1_coa1v") -metadata/_tab_index = 0 -[node name="ToolBar" type="HBoxContainer" parent="."] +[node name="VBox" type="VBoxContainer" parent="."] layout_mode = 2 -[node name="LineEdit" type="LineEdit" parent="ToolBar"] +[node name="ToolBar" type="HBoxContainer" parent="VBox"] +layout_mode = 2 + +[node name="SearchLine" type="LineEdit" parent="VBox/ToolBar"] +unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 placeholder_text = "Filter" -[node name="AddButton" type="Button" parent="ToolBar"] +[node name="AddButton" type="Button" parent="VBox/ToolBar"] layout_mode = 2 icon = ExtResource("3_j4mj2") -[node name="ScrollContainer" type="ScrollContainer" parent="."] +[node name="ScrollContainer" type="ScrollContainer" parent="VBox"] layout_mode = 2 size_flags_vertical = 3 -[node name="VBox" type="VBoxContainer" parent="ScrollContainer"] +[node name="VBox" type="VBoxContainer" parent="VBox/ScrollContainer"] +unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 - -[connection signal="pressed" from="ToolBar/AddButton" to="." method="_on_add_button_pressed"] diff --git a/common/layouts/graph_edit/monologue_graph_edit.gd b/common/layouts/graph_edit/monologue_graph_edit.gd index a89c29e2..4463c2b7 100644 --- a/common/layouts/graph_edit/monologue_graph_edit.gd +++ b/common/layouts/graph_edit/monologue_graph_edit.gd @@ -19,20 +19,20 @@ func _ready() -> void: -func add_character(data: Dictionary = {}) -> MonologueCharacter: +func add_character(dict: Dictionary = {}) -> MonologueCharacter: var character = MonologueCharacter.new(self) - if data: - character._from_dict(data) + if dict: + character._from_dict(dict) character.idx.value = _character_references.size() character.character.setters["character_index"] = character.idx.value _character_references.append(character) return character -func add_variable(data: Dictionary = {}) -> MonologueVariable: +func add_variable(dict: Dictionary = {}) -> MonologueVariable: var variable = MonologueVariable.new(self) - if data: - variable._from_dict(data) + if dict: + variable._from_dict(dict) variable.index = _variable_references.size() _variable_references.append(variable) return variable diff --git a/common/layouts/inspector_panel/inspector_panel.gd b/common/layouts/inspector/inspector_panel.gd similarity index 93% rename from common/layouts/inspector_panel/inspector_panel.gd rename to common/layouts/inspector/inspector_panel.gd index 35668c7f..6777b685 100644 --- a/common/layouts/inspector_panel/inspector_panel.gd +++ b/common/layouts/inspector/inspector_panel.gd @@ -44,6 +44,9 @@ func on_graph_node_selected(node: MonologueGraphNode, bypass: bool = false) -> v else: graph_edit.active_graphnode = null return + + if node is BackgroundNode: + return on_new_graph_node_selected(node, bypass) # hack to preserve focus if the side panel contains the same node paths var focus_owner = get_viewport().gui_get_focus_owner() @@ -96,6 +99,14 @@ func on_graph_node_selected(node: MonologueGraphNode, bypass: bool = false) -> v restore_focus(refocus_path, refocus_line, refocus_column) +func on_new_graph_node_selected(node: MonologueGraphNode, bypass: bool = false) -> void: + var properties := node._get_inspector_property_list() + print(properties) + # Create a field in the inspector + # Link the field to the property and if any changes to the property is made, update the value of the property + # + Undo/Redo + + func _load_groups(item, graph_node: MonologueGraphNode, already_invoke) -> void: if item is String: var property = graph_node.get(item) diff --git a/common/layouts/inspector_panel/inspector_panel.gd.uid b/common/layouts/inspector/inspector_panel.gd.uid similarity index 100% rename from common/layouts/inspector_panel/inspector_panel.gd.uid rename to common/layouts/inspector/inspector_panel.gd.uid diff --git a/common/layouts/inspector_panel/inspector_panel.tscn b/common/layouts/inspector/inspector_panel.tscn similarity index 95% rename from common/layouts/inspector_panel/inspector_panel.tscn rename to common/layouts/inspector/inspector_panel.tscn index 6d566a44..e4579b5b 100644 --- a/common/layouts/inspector_panel/inspector_panel.tscn +++ b/common/layouts/inspector/inspector_panel.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://dgvhvxdrd58qp"] -[ext_resource type="Script" uid="uid://dtf4ge38njewp" path="res://common/layouts/inspector_panel/inspector_panel.gd" id="1_haagr"] +[ext_resource type="Script" uid="uid://dtf4ge38njewp" path="res://common/layouts/inspector/inspector_panel.gd" id="1_haagr"] [ext_resource type="Texture2D" uid="uid://b272tbdmvxj20" path="res://ui/assets/icons/play.svg" id="2_34x8o"] [node name="InspectorPanel" type="PanelContainer"] @@ -18,6 +18,7 @@ size_flags_vertical = 3 layout_mode = 2 [node name="Label" type="Label" parent="VBox/VBox"] +visible = false layout_mode = 2 theme_type_variation = &"HeaderSmall" text = "Inspector" diff --git a/common/layouts/inspector/monologue_inspector.gd b/common/layouts/inspector/monologue_inspector.gd new file mode 100644 index 00000000..b3b767f2 --- /dev/null +++ b/common/layouts/inspector/monologue_inspector.gd @@ -0,0 +1,44 @@ +class_name MonologueInspector extends Node + +var _properties: Array = [] +var _node: Node + + +func bind_node(node: Node) -> void: + _node = node + + +## Create a property +## +## +## +func add_property(property_name: String, property_data: Dictionary) -> void: + if has_property(property_name): + push_error("Property %s already exist." % property_name) + return + + property_data.merge({"name": property_name}) + _properties.append(property_data) + + +func has_property(property_name: String) -> bool: + var candidates: Array = _properties.filter(func(p): return p.get("name") == property_name) + return candidates.size() <= 0 + + +func get_property(property_name: String) -> Variant: + var candidates: Array = _properties.filter(func(p): return p.get("name") == property_name) + if candidates.size() <= 0: + return null + + return candidates.get(0) + + +func update_property(property_name: String, property_data: Dictionary) -> void: + if not has_property(property_name): + push_warning("Cannot find property %s." % property_name) + return + + +func _get_inspector_property_list() -> Array: + return _properties diff --git a/common/layouts/inspector/monologue_inspector.gd.uid b/common/layouts/inspector/monologue_inspector.gd.uid new file mode 100644 index 00000000..1a7f83cf --- /dev/null +++ b/common/layouts/inspector/monologue_inspector.gd.uid @@ -0,0 +1 @@ +uid://cfys1iyoy6jjw diff --git a/common/monologue_graph_node.gd b/common/monologue_graph_node.gd index 1b8b1cba..02a52208 100644 --- a/common/monologue_graph_node.gd +++ b/common/monologue_graph_node.gd @@ -223,11 +223,18 @@ func _get_field_groups() -> Array: func _get_inspector_property_list() -> Array: - return [ - {"name": "id", "property": "id", "type": LINE}, - {"name": "editor_position", "property": "editor_position", "type": LINE}, - ] + get_inspector_property_list() + return ( + [ + {"property": "id", "type": LINE}, + {"property": "editor_position", "type": VECTOR}, + ] + + get_inspector_property_list() + ) func get_inspector_property_list() -> Array: return [] + + +func _setup_inspector(inspector: MonologueInspector) -> void: + pass diff --git a/nodes/background_node/background_node.gd b/nodes/background_node/background_node.gd index 34441d6a..be725453 100644 --- a/nodes/background_node/background_node.gd +++ b/nodes/background_node/background_node.gd @@ -56,3 +56,30 @@ func _update(_value: Variant = null): func _get_field_groups() -> Array: return ["image", {"Transition": ["transition", "duration"]}] + + +func get_node_type() -> String: + return "NodeBackground" + + +func get_editor_property_list() -> Array: + return [ + {"property": "image", "type": FILE, "default": "", + "setters": {"filters": FilePicker.IMAGE} + }, + {"property": "transition", "type": DROPDOWN, "default": 0, + "setters": { + "set_items": [[ + {"text": "No Transition"}, + {"text": "Push Down"}, + {"text": "Push Left"}, + {"text": "Push Right"}, + {"text": "Push Up"}, + {"text": "Simple Fade"}, + ]] + } + }, + {"property": "duration", "type": SPINBOX, "default": 0.0, + "setters": {"step": 0.1, "minimum": 0.0} + }, + ] diff --git a/project.godot b/project.godot index 810df349..5b688c43 100644 --- a/project.godot +++ b/project.godot @@ -55,7 +55,7 @@ window/size/fullscreen=true [editor_plugins] -enabled=PackedStringArray("res://addons/ColorPreview/plugin.cfg", "res://addons/Todo_Manager/plugin.cfg", "res://addons/gdUnit4/plugin.cfg") +enabled=PackedStringArray("res://addons/ColorPreview/plugin.cfg", "res://addons/Todo_Manager/plugin.cfg", "res://addons/beautify_code_on_save/plugin.cfg", "res://addons/dockable_container/plugin.cfg", "res://addons/gdUnit4/plugin.cfg") [file_customization] diff --git a/scenes/main/editor.tscn b/scenes/main/editor.tscn index 946de67a..145ca218 100644 --- a/scenes/main/editor.tscn +++ b/scenes/main/editor.tscn @@ -1,10 +1,13 @@ -[gd_scene load_steps=23 format=3 uid="uid://bqjfdabrxujp7"] +[gd_scene load_steps=33 format=3 uid="uid://bqjfdabrxujp7"] [ext_resource type="Script" uid="uid://q6eg6rid6xqd" path="res://scenes/main/monologue_editor.gd" id="1_ovo1o"] -[ext_resource type="PackedScene" uid="uid://dfkqf3wjdnj0m" path="res://common/layouts/editor/editor_section.tscn" id="2_q62vd"] +[ext_resource type="Script" uid="uid://k2o7qui1lr6l" path="res://addons/dockable_container/dockable_container.gd" id="2_xxwqg"] [ext_resource type="Texture2D" uid="uid://dd8v3hpxxk33d" path="res://ui/assets/icons/logo_white.svg" id="3_mei7o"] +[ext_resource type="Script" uid="uid://bh202gagkdkar" path="res://addons/dockable_container/layout_panel.gd" id="3_uvwqm"] +[ext_resource type="Script" uid="uid://dos02hjhrf1ws" path="res://addons/dockable_container/layout_split.gd" id="4_kkjrq"] [ext_resource type="Script" uid="uid://m1wxne1g6kju" path="res://scenes/main/main_menu.gd" id="4_pwbil"] [ext_resource type="Texture2D" uid="uid://hlck6y4i3l5q" path="res://ui/assets/icons/plus.svg" id="5_4jbkx"] +[ext_resource type="Script" uid="uid://cylirj261q6ru" path="res://addons/dockable_container/layout.gd" id="5_svwnc"] [ext_resource type="PackedScene" uid="uid://mfdu320oy6ex" path="res://common/layouts/editor/list_section/list_section.tscn" id="6_mei7o"] [ext_resource type="Script" uid="uid://bmku341x5gaoe" path="res://scenes/main/add_node_button.gd" id="6_sr1k3"] [ext_resource type="Texture2D" uid="uid://bfmsxfn26cvfn" path="res://ui/assets/icons/character.svg" id="7_xxwqg"] @@ -14,52 +17,73 @@ [ext_resource type="Texture2D" uid="uid://dd6wdpndndufl" path="res://ui/assets/icons/sparkles.svg" id="11_g5pcf"] [ext_resource type="Texture2D" uid="uid://b272tbdmvxj20" path="res://ui/assets/icons/play.svg" id="12_mvfhp"] [ext_resource type="Script" uid="uid://nxistnt1yhxc" path="res://scenes/main/graph_edit_switcher.gd" id="13_armd7"] -[ext_resource type="PackedScene" uid="uid://dgvhvxdrd58qp" path="res://common/layouts/inspector_panel/inspector_panel.tscn" id="14_craj7"] +[ext_resource type="PackedScene" uid="uid://dgvhvxdrd58qp" path="res://common/layouts/inspector/inspector_panel.tscn" id="14_craj7"] [ext_resource type="Script" uid="uid://q0oqx6butjwn" path="res://scenes/main/search_bar_container.gd" id="15_35jwg"] [ext_resource type="PackedScene" uid="uid://cmpsaafag7cwl" path="res://common/windows/graph_node_picker/graph_node_picker.tscn" id="15_q62vd"] [ext_resource type="PackedScene" uid="uid://cvum3eaenloix" path="res://common/layouts/search_bar/search_bar.tscn" id="16_n4eqx"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1u3ep"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 -bg_color = Color(0.186303, 0.183362, 0.215088, 1) -border_width_left = 1 -border_width_top = 1 -border_width_right = 1 -border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 3 -corner_radius_top_right = 3 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mei7o"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 -bg_color = Color(0.225127, 0.224539, 0.257441, 1) -border_width_left = 1 -border_width_top = 1 -border_width_right = 1 -border_width_bottom = 1 -border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_top_left = 3 -corner_radius_top_right = 3 -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qe3vh"] -content_margin_left = 4.0 -content_margin_top = 4.0 -content_margin_right = 4.0 -content_margin_bottom = 4.0 -bg_color = Color(0.225127, 0.224539, 0.257441, 1) -border_width_left = 1 -border_width_right = 1 -border_width_bottom = 1 -border_color = Color(0.890196, 0.894118, 0.921569, 0.2) -corner_radius_bottom_right = 3 -corner_radius_bottom_left = 3 +[sub_resource type="Resource" id="Resource_g5pcf"] +resource_name = "Tabs" +script = ExtResource("3_uvwqm") +names = PackedStringArray("_Tabs") +current_tab = 0 + +[sub_resource type="Resource" id="Resource_xxwqg"] +resource_name = "Tabs" +script = ExtResource("3_uvwqm") +names = PackedStringArray("Characters", "Variables") +current_tab = 0 + +[sub_resource type="Resource" id="Resource_uvwqm"] +resource_name = "Tabs" +script = ExtResource("3_uvwqm") +names = PackedStringArray("Graph") +current_tab = 0 + +[sub_resource type="Resource" id="Resource_kkjrq"] +resource_name = "Split" +script = ExtResource("4_kkjrq") +direction = 0 +percent = 0.0 +first = SubResource("Resource_xxwqg") +second = SubResource("Resource_uvwqm") + +[sub_resource type="Resource" id="Resource_svwnc"] +resource_name = "Tabs" +script = ExtResource("3_uvwqm") +names = PackedStringArray("Inspector") +current_tab = 0 + +[sub_resource type="Resource" id="Resource_mvfhp"] +resource_name = "Split" +script = ExtResource("4_kkjrq") +direction = 0 +percent = 1.0 +first = SubResource("Resource_kkjrq") +second = SubResource("Resource_svwnc") + +[sub_resource type="Resource" id="Resource_e6y6g"] +resource_name = "Split" +script = ExtResource("4_kkjrq") +direction = 1 +percent = 0.0 +first = SubResource("Resource_g5pcf") +second = SubResource("Resource_mvfhp") + +[sub_resource type="Resource" id="Resource_jojum"] +resource_name = "Layout" +script = ExtResource("5_svwnc") +root = SubResource("Resource_e6y6g") +hidden_tabs = {} +windows = {} + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_sr1k3"] + +[sub_resource type="GDScript" id="GDScript_sr1k3"] +script/source = "extends PanelContainer + +var _static_container: bool = true +" [sub_resource type="GDScript" id="GDScript_lenro"] script/source = "extends Button @@ -81,35 +105,32 @@ size_flags_horizontal = 3 size_flags_vertical = 3 script = ExtResource("1_ovo1o") -[node name="MainContainer" type="VBoxContainer" parent="."] +[node name="DockableContainer" type="Container" parent="."] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -theme_override_constants/separation = 15 +script = ExtResource("2_xxwqg") +tab_alignment = 0 +use_hidden_tabs_for_min_size = true +layout = SubResource("Resource_jojum") +metadata/_custom_type_script = "uid://k2o7qui1lr6l" -[node name="VSplitContainer" type="VSplitContainer" parent="MainContainer"] +[node name="_Tabs" type="PanelContainer" parent="DockableContainer"] layout_mode = 2 -size_flags_vertical = 3 -dragging_enabled = false +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/panel = SubResource("StyleBoxEmpty_sr1k3") +script = SubResource("GDScript_sr1k3") -[node name="EditorSection" parent="MainContainer/VSplitContainer" instance=ExtResource("2_q62vd")] -visible = true -layout_mode = 2 -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_mei7o") -current_tab = 0 -tabs_visible = false - -[node name="TabBar" type="HBoxContainer" parent="MainContainer/VSplitContainer/EditorSection"] +[node name="TabBar" type="HBoxContainer" parent="DockableContainer/_Tabs"] custom_minimum_size = Vector2(0, 30) layout_mode = 2 theme_override_constants/separation = 0 -metadata/_tab_index = 0 -[node name="MainPopupMenu" type="MenuButton" parent="MainContainer/VSplitContainer/EditorSection/TabBar"] +[node name="MainPopupMenu" type="MenuButton" parent="DockableContainer/_Tabs/TabBar"] custom_minimum_size = Vector2(30, 0) layout_mode = 2 icon = ExtResource("3_mei7o") @@ -117,16 +138,11 @@ icon_alignment = 1 expand_icon = true script = ExtResource("4_pwbil") -[node name="VSeparator" type="VSeparator" parent="MainContainer/VSplitContainer/EditorSection/TabBar"] +[node name="VSeparator" type="VSeparator" parent="DockableContainer/_Tabs/TabBar"] layout_mode = 2 theme_type_variation = &"VSeparatorGrow" -[node name="HBox" type="HBoxContainer" parent="MainContainer/VSplitContainer/EditorSection/TabBar"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_constants/separation = 0 - -[node name="TabBar" type="TabBar" parent="MainContainer/VSplitContainer/EditorSection/TabBar/HBox"] +[node name="TabBar" type="TabBar" parent="DockableContainer/_Tabs/TabBar"] unique_name_in_owner = true layout_mode = 2 current_tab = 0 @@ -134,98 +150,61 @@ clip_tabs = false tab_count = 1 tab_0/icon = ExtResource("5_4jbkx") -[node name="HSplitContainer" type="HSplitContainer" parent="MainContainer/VSplitContainer"] -layout_mode = 2 -size_flags_vertical = 3 -split_offset = 250 - -[node name="EditorSection" parent="MainContainer/VSplitContainer/HSplitContainer" instance=ExtResource("2_q62vd")] -visible = true -custom_minimum_size = Vector2(250, 0) -layout_mode = 2 -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_qe3vh") -current_tab = 0 - -[node name="Characters" parent="MainContainer/VSplitContainer/HSplitContainer/EditorSection" instance=ExtResource("6_mei7o")] -unique_name_in_owner = true -layout_mode = 2 -section_icon = ExtResource("7_xxwqg") - -[node name="Variables" parent="MainContainer/VSplitContainer/HSplitContainer/EditorSection" instance=ExtResource("6_mei7o")] -unique_name_in_owner = true -visible = false -layout_mode = 2 -section_icon = ExtResource("8_uvwqm") -metadata/_tab_index = 1 - -[node name="HSplitContainer" type="HSplitContainer" parent="MainContainer/VSplitContainer/HSplitContainer"] +[node name="Graph" type="PanelContainer" parent="DockableContainer"] layout_mode = 2 -size_flags_vertical = 3 -split_offset = -250 -[node name="GraphEditorSection" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer" instance=ExtResource("2_q62vd")] -visible = true +[node name="VBoxContainer" type="VBoxContainer" parent="DockableContainer/Graph"] layout_mode = 2 -size_flags_horizontal = 3 -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_mei7o") -current_tab = 0 -tabs_visible = false -[node name="VBoxContainer" type="VBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection"] +[node name="ToolBar" type="HBoxContainer" parent="DockableContainer/Graph/VBoxContainer"] layout_mode = 2 -metadata/_tab_index = 0 -[node name="ToolBar" type="HBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer"] -layout_mode = 2 - -[node name="AddNodeBtn" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +[node name="AddNodeBtn" type="Button" parent="DockableContainer/Graph/VBoxContainer/ToolBar"] layout_mode = 2 theme_type_variation = &"Button_Flat" text = "Add a node..." script = ExtResource("6_sr1k3") -[node name="ButtonCharacters" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +[node name="ButtonCharacters" type="Button" parent="DockableContainer/Graph/VBoxContainer/ToolBar"] visible = false layout_mode = 2 theme_type_variation = &"Button_Flat" icon = ExtResource("7_xxwqg") icon_alignment = 1 -[node name="ButtonVariables" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +[node name="ButtonVariables" type="Button" parent="DockableContainer/Graph/VBoxContainer/ToolBar"] visible = false layout_mode = 2 theme_type_variation = &"Button_Flat" icon = ExtResource("8_uvwqm") icon_alignment = 1 -[node name="LanguageSwitcher" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar" instance=ExtResource("9_kkjrq")] +[node name="LanguageSwitcher" parent="DockableContainer/Graph/VBoxContainer/ToolBar" instance=ExtResource("9_kkjrq")] unique_name_in_owner = true layout_mode = 2 theme_type_variation = &"Button_Flat" disabled = true -[node name="ButtonSettings" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +[node name="ButtonSettings" type="Button" parent="DockableContainer/Graph/VBoxContainer/ToolBar"] visible = false layout_mode = 2 theme_type_variation = &"Button_Flat" icon = ExtResource("10_svwnc") icon_alignment = 1 -[node name="ButtonSparkle" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +[node name="ButtonSparkle" type="Button" parent="DockableContainer/Graph/VBoxContainer/ToolBar"] visible = false layout_mode = 2 theme_type_variation = &"Button_Flat" icon = ExtResource("11_g5pcf") -[node name="RunButton" type="Button" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar"] +[node name="RunButton" type="Button" parent="DockableContainer/Graph/VBoxContainer/ToolBar"] layout_mode = 2 theme_type_variation = &"Button_Flat" icon = ExtResource("12_mvfhp") script = SubResource("GDScript_lenro") -[node name="GraphEditSwitcher" type="VBoxContainer" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer"] +[node name="GraphEditSwitcher" type="VBoxContainer" parent="DockableContainer/Graph/VBoxContainer"] unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 3 @@ -233,11 +212,11 @@ mouse_filter = 2 theme_override_constants/separation = 0 script = ExtResource("13_armd7") -[node name="GraphEditZone" type="Control" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/GraphEditSwitcher"] +[node name="GraphEditZone" type="Control" parent="DockableContainer/Graph/VBoxContainer/GraphEditSwitcher"] layout_mode = 2 size_flags_vertical = 3 -[node name="GraphEdits" type="Control" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/GraphEditSwitcher/GraphEditZone"] +[node name="GraphEdits" type="Control" parent="DockableContainer/Graph/VBoxContainer/GraphEditSwitcher/GraphEditZone"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -246,20 +225,20 @@ grow_horizontal = 2 grow_vertical = 2 size_flags_vertical = 3 -[node name="EditorSection" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer" instance=ExtResource("2_q62vd")] -visible = true +[node name="Inspector" parent="DockableContainer" instance=ExtResource("14_craj7")] +unique_name_in_owner = true +custom_minimum_size = Vector2(250, 0) layout_mode = 2 -theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_1u3ep") -theme_override_styles/panel = SubResource("StyleBoxFlat_mei7o") -current_tab = 0 -tabs_visible = false +size_flags_horizontal = 3 -[node name="InspectorPanel" parent="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/EditorSection" instance=ExtResource("14_craj7")] +[node name="Characters" parent="DockableContainer" instance=ExtResource("6_mei7o")] unique_name_in_owner = true -custom_minimum_size = Vector2(200, 0) layout_mode = 2 -size_flags_horizontal = 3 -metadata/_tab_index = 0 + +[node name="Variables" parent="DockableContainer" instance=ExtResource("6_mei7o")] +unique_name_in_owner = true +visible = false +layout_mode = 2 [node name="GraphNodePicker" parent="." instance=ExtResource("15_q62vd")] unique_name_in_owner = true @@ -285,9 +264,14 @@ script = ExtResource("15_35jwg") [node name="SearchBar" parent="SearchBarContainer" node_paths=PackedStringArray("graph_edit_switcher") instance=ExtResource("16_n4eqx")] visible = false layout_mode = 2 -graph_edit_switcher = NodePath("../../MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/GraphEditSwitcher") +graph_edit_switcher = NodePath("../../DockableContainer/Graph/VBoxContainer/GraphEditSwitcher") + +[node name="SplitContainer" type="SplitContainer" parent="."] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 -[connection signal="pressed" from="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/AddNodeBtn" to="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/AddNodeBtn" method="_on_pressed"] -[connection signal="pressed" from="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/ButtonSettings" to="." method="_on_button_settings_pressed"] -[connection signal="pressed" from="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/ButtonSparkle" to="." method="_on_button_sparkle_pressed"] -[connection signal="pressed" from="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/RunButton" to="MainContainer/VSplitContainer/HSplitContainer/HSplitContainer/GraphEditorSection/VBoxContainer/ToolBar/RunButton" method="_on_pressed"] +[connection signal="pressed" from="DockableContainer/Graph/VBoxContainer/ToolBar/AddNodeBtn" to="DockableContainer/Graph/VBoxContainer/ToolBar/AddNodeBtn" method="_on_pressed"] +[connection signal="pressed" from="DockableContainer/Graph/VBoxContainer/ToolBar/ButtonSettings" to="." method="_on_button_settings_pressed"] +[connection signal="pressed" from="DockableContainer/Graph/VBoxContainer/ToolBar/ButtonSparkle" to="." method="_on_button_sparkle_pressed"] +[connection signal="pressed" from="DockableContainer/Graph/VBoxContainer/ToolBar/RunButton" to="DockableContainer/Graph/VBoxContainer/ToolBar/RunButton" method="_on_pressed"] diff --git a/scenes/main/graph_edit_switcher.gd b/scenes/main/graph_edit_switcher.gd index da2264bf..f72908a5 100644 --- a/scenes/main/graph_edit_switcher.gd +++ b/scenes/main/graph_edit_switcher.gd @@ -12,7 +12,7 @@ var root_scene = Constants.NODE_SCENES.get("Root") var last_selected_tab: int = 0 var prevent_switching: bool = false -@onready var inspector_panel: InspectorPanel = %InspectorPanel +@onready var inspector_panel: InspectorPanel = %Inspector @onready var tab_bar: TabBar = %TabBar @onready var graph_edits: Control = $GraphEditZone/GraphEdits diff --git a/scenes/main/monologue_editor.gd b/scenes/main/monologue_editor.gd index 78c0b521..6b6f802e 100644 --- a/scenes/main/monologue_editor.gd +++ b/scenes/main/monologue_editor.gd @@ -4,9 +4,11 @@ class_name MonologueEditor extends Control @onready var graph_node_picker: GraphNodePicker = %GraphNodePicker @onready var graph_switcher: GraphEditSwitcher = %GraphEditSwitcher -@onready var inspector_panel_node: InspectorPanel = %InspectorPanel +@onready var inspector_panel_node: InspectorPanel = %Inspector @onready var run_window := preload("res://scenes/run/run_window.tscn") @onready var dimmer := $"../../../Dimmer" +@onready var characters_section := %Characters +@onready var variables_section := %Variables func _ready(): @@ -91,7 +93,7 @@ func load_project(path: String, new_graph: bool = false) -> void: var file = FileAccess.open(path, FileAccess.READ) if not file or graph_switcher.is_file_opened(path): return - + if new_graph: graph_switcher.new_graph_edit() graph_switcher.current.file_path = path # set path first before tab creation @@ -119,7 +121,7 @@ func load_project(path: String, new_graph: bool = false) -> void: graph_switcher.add_root() graph_switcher.current.update_node_positions() GlobalSignal.emit("load_successful", [path]) - + load_editor_sections() @@ -147,15 +149,15 @@ func refresh(node: MonologueGraphNode = null, affected_properties: PackedStringA if inspector_panel_node.visible: var current_node = inspector_panel_node.selected_node inspector_panel_node.on_graph_node_selected(current_node, true) - + await get_tree().process_frame load_editor_sections() func load_editor_sections() -> void: var graph_edit: MonologueGraphEdit = graph_switcher.current - %Characters.load_items(graph_edit.characters) - %Variables.load_items(graph_edit.variables) + characters_section.load_items(graph_edit.characters) + variables_section.load_items(graph_edit.variables) func save(): diff --git a/ui/theme_default/theme_default.gd b/ui/theme_default/theme_default.gd index a1b53011..d51bc618 100644 --- a/ui/theme_default/theme_default.gd +++ b/ui/theme_default/theme_default.gd @@ -18,6 +18,7 @@ var relationship_line_opacity: float = 0.2 var border_width: int = 1 var base_font_size: int = 14 + func _init() -> void: scale = 1.0 var _use_high_ppi: bool = scale >= 1.0 @@ -228,7 +229,7 @@ func _generate_theme() -> void: set_type_variation("CollapsibleFieldPanel", "PanelContainer") var sb: StyleBoxFlat = base_sb.duplicate() - sb.bg_color = _get_primary_color(contrast/2) + sb.bg_color = _get_primary_color(contrast / 2) set_stylebox("panel", "CollapsibleFieldPanel", sb) # EditorBackground @@ -269,22 +270,22 @@ func _generate_theme() -> void: sb.set_border_width_all(border_width) sb.border_color = base_border_color set_stylebox("panel_unfocus", "EditorSection", sb) - + sb = sb.duplicate() sb.border_color = accent_color - set_stylebox("panel_focus", "EditorSection", sb) - + set_stylebox("panel_focus", "EditorSection", sb) + sb = sb.duplicate() sb.border_width_top = 0 sb.corner_radius_top_left = 0 sb.corner_radius_top_right = 0 sb.border_color = base_border_color set_stylebox("tab_panel_unfocus", "EditorSection", sb) - + sb = sb.duplicate() sb.border_color = accent_color set_stylebox("tab_panel_focus", "EditorSection", sb) - + sb = base_sb.duplicate() sb.bg_color = _get_secondary_color(contrast, false) sb.set_corner_radius_all(0) @@ -294,31 +295,31 @@ func _generate_theme() -> void: sb.border_width_bottom = 0 sb.border_color = base_border_color set_stylebox("tabbar_background_unfocus", "EditorSection", sb) - + sb = sb.duplicate() sb.border_color = accent_color set_stylebox("tabbar_background_focus", "EditorSection", sb) - + sb = base_sb.duplicate() sb.set_corner_radius_all(0) - sb.corner_radius_top_left = corner_radius-1 - sb.corner_radius_top_right = corner_radius-1 + sb.corner_radius_top_left = corner_radius - 1 + sb.corner_radius_top_right = corner_radius - 1 sb.bg_color = _get_primary_color(contrast, false) sb.border_color = Color.TRANSPARENT sb.border_width_top = 1 set_stylebox("tab_selected", "EditorSection", sb) - + sb = base_sb.duplicate() sb.draw_center = false set_stylebox("tab_unselected", "EditorSection", sb) set_stylebox("tab_focus", "EditorSection", sb) set_stylebox("tab_hovered", "EditorSection", sb) set_stylebox("tab_disabled", "EditorSection", sb) - + set_color("font_unselected_color", "EditorSection", _get_text_color(0.8)) - set_color("font_disabled_color", "EditorSection", _get_text_color(0.3)) - set_color("font_hover_color", "EditorSection", text_color) - set_color("font_selected_color", "EditorSection", text_color) + set_color("font_disabled_color", "EditorSection", _get_text_color(0.3)) + set_color("font_hover_color", "EditorSection", text_color) + set_color("font_selected_color", "EditorSection", text_color) set_constant("side_margin", "EditorSection", 1) set_constant("icon_separation", "EditorSection", int(base_margin)) set_constant("icon_max_width", "EditorSection", base_font_size) @@ -345,14 +346,14 @@ func _generate_theme() -> void: set_stylebox("hover_pressed_mirrored", "FlatButton", flat_button_pressed_sb) set_stylebox("pressed", "FlatButton", flat_button_pressed_sb) set_stylebox("pressed_mirrored", "FlatButton", flat_button_pressed_sb) - + # FieldPanel - + set_type_variation("FieldPanel", "PanelContainer") sb = base_sb.duplicate() sb.bg_color = background_color sb.set_border_width_all(border_width) - sb.set_content_margin_all(base_margin*2) + sb.set_content_margin_all(base_margin * 2) set_stylebox("panel", "FieldPanel", sb) # GraphEdit @@ -428,9 +429,9 @@ func _generate_theme() -> void: set_constant("separation", "VDottedSeparator", 1) set_stylebox("separator", "HDottedSeparator", dotted_sb) set_stylebox("separator", "VDottedSeparator", dotted_sb) - + # HSplitContainer & VSplitContainer - + set_constant("separation", "HSplitContainer", int(base_margin)) set_constant("separation", "VSplitContainer", int(base_margin)) set_icon("grabber", "HSplitContainer", Texture2D.new()) @@ -566,6 +567,7 @@ func _generate_theme() -> void: _set_border(sb, _get_color(base_border_color, base_border_color.a, false)) var popup_menu_hover_sb: StyleBoxFlat = base_field_sb.duplicate() popup_menu_hover_sb.bg_color = _get_secondary_color(contrast) + separator_sb = separator_sb.duplicate() separator_sb.color = text_color separator_sb.vertical = true separator_sb.grow_begin = 0 @@ -737,7 +739,7 @@ func _generate_theme() -> void: set_stylebox("normal", "TextEdit", text_edit_sb) set_stylebox("focus", "TextEdit", text_edit_focus_sb) set_stylebox("read_only", "TextEdit", text_edit_disabled_sb) - + # TimelineCellNumber set_type_variation("TimelineCellNumber", "PanelContainer") @@ -809,6 +811,13 @@ func _generate_theme() -> void: sb.bg_color = _get_primary_color(contrast, false) set_stylebox("panel", "TreeContainer", sb) + # TooltipPanel + + sb = base_sb.duplicate() + sb.set_corner_radius_all(0) + sb.bg_color = Color(background_color, 0.5) + set_stylebox("panel", "TooltipPanel", sb) + # OptionButton var option_button_sb = base_field_sb.duplicate() From ddef9596a64ec6663c6cd3ad727d772d6347dfac Mon Sep 17 00:00:00 2001 From: Atomic Junky <67459553+atomic-junky@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:00:17 -0400 Subject: [PATCH 006/103] [WIP] Refactor editor UI and add new node/field system Major refactor of editor layouts, inspector panels, and field components. Introduces a new node and field architecture, removes legacy inspector and field files, and adds new autoloads and common utilities. Updates project and scene files to support the new structure and includes new splash assets. --- addons/gdUnit4/GdUnitRunner.cfg | 1060 ----------------- addons/gdUnit4/bin/GdUnitCmdTool.gd.uid | 2 +- addons/gdUnit4/bin/GdUnitCopyLog.gd.uid | 2 +- addons/gdUnit4/plugin.cfg | 2 +- addons/gdUnit4/plugin.gd | 4 +- addons/gdUnit4/plugin.gd.uid | 2 +- addons/gdUnit4/src/Comparator.gd.uid | 2 +- addons/gdUnit4/src/Fuzzers.gd.uid | 2 +- addons/gdUnit4/src/GdUnitArrayAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitAwaiter.gd.uid | 2 +- addons/gdUnit4/src/GdUnitBoolAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitConstants.gd.uid | 2 +- .../gdUnit4/src/GdUnitDictionaryAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitFailureAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitFileAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitFloatAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitFuncAssert.gd.uid | 2 +- .../gdUnit4/src/GdUnitGodotErrorAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitIntAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitObjectAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitResultAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitSceneRunner.gd.uid | 2 +- addons/gdUnit4/src/GdUnitSignalAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitStringAssert.gd.uid | 2 +- addons/gdUnit4/src/GdUnitTestSuite.gd.uid | 2 +- addons/gdUnit4/src/GdUnitTuple.gd.uid | 2 +- .../gdUnit4/src/GdUnitValueExtractor.gd.uid | 2 +- addons/gdUnit4/src/GdUnitVectorAssert.gd.uid | 2 +- .../src/asserts/CallBackValueProvider.gd.uid | 2 +- .../src/asserts/DefaultValueProvider.gd.uid | 2 +- .../src/asserts/GdAssertMessages.gd.uid | 2 +- .../src/asserts/GdAssertReports.gd.uid | 2 +- .../src/asserts/GdUnitArrayAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitAssertions.gd.uid | 2 +- .../src/asserts/GdUnitBoolAssertImpl.gd.uid | 2 +- .../asserts/GdUnitDictionaryAssertImpl.gd.uid | 2 +- .../asserts/GdUnitFailureAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitFileAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitFloatAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitFuncAssertImpl.gd.uid | 2 +- .../asserts/GdUnitGodotErrorAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitIntAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitObjectAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitResultAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitSignalAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitStringAssertImpl.gd.uid | 2 +- .../src/asserts/GdUnitVectorAssertImpl.gd.uid | 2 +- .../gdUnit4/src/asserts/ValueProvider.gd.uid | 2 +- .../gdUnit4/src/cmd/CmdArgumentParser.gd.uid | 2 +- addons/gdUnit4/src/cmd/CmdCommand.gd.uid | 2 +- .../gdUnit4/src/cmd/CmdCommandHandler.gd.uid | 2 +- addons/gdUnit4/src/cmd/CmdOption.gd.uid | 2 +- addons/gdUnit4/src/cmd/CmdOptions.gd.uid | 2 +- addons/gdUnit4/src/core/GdArrayTools.gd.uid | 2 +- addons/gdUnit4/src/core/GdDiffTool.gd.uid | 2 +- addons/gdUnit4/src/core/GdObjects.gd.uid | 2 +- addons/gdUnit4/src/core/GdUnit4Version.gd.uid | 2 +- .../gdUnit4/src/core/GdUnitFileAccess.gd.uid | 2 +- addons/gdUnit4/src/core/GdUnitProperty.gd | 3 + addons/gdUnit4/src/core/GdUnitProperty.gd.uid | 2 +- addons/gdUnit4/src/core/GdUnitResult.gd.uid | 2 +- addons/gdUnit4/src/core/GdUnitRunnerConfig.gd | 2 +- .../src/core/GdUnitRunnerConfig.gd.uid | 2 +- .../gdUnit4/src/core/GdUnitSceneRunnerImpl.gd | 4 +- .../src/core/GdUnitSceneRunnerImpl.gd.uid | 2 +- addons/gdUnit4/src/core/GdUnitSettings.gd.uid | 2 +- .../src/core/GdUnitSignalAwaiter.gd.uid | 2 +- .../src/core/GdUnitSignalCollector.gd.uid | 2 +- addons/gdUnit4/src/core/GdUnitSignals.gd.uid | 2 +- .../gdUnit4/src/core/GdUnitSingleton.gd.uid | 2 +- .../src/core/GdUnitTestResourceLoader.gd | 4 +- .../src/core/GdUnitTestResourceLoader.gd.uid | 2 +- .../src/core/GdUnitTestSuiteBuilder.gd.uid | 2 +- .../src/core/GdUnitTestSuiteScanner.gd | 2 +- .../src/core/GdUnitTestSuiteScanner.gd.uid | 2 +- addons/gdUnit4/src/core/GdUnitTools.gd.uid | 2 +- .../src/core/GodotVersionFixures.gd.uid | 2 +- addons/gdUnit4/src/core/LocalTime.gd.uid | 2 +- addons/gdUnit4/src/core/_TestCase.gd.uid | 2 +- .../core/attributes/TestCaseAttribute.gd.uid | 2 +- .../src/core/command/GdUnitCommand.gd.uid | 2 +- .../core/command/GdUnitCommandHandler.gd.uid | 2 +- .../src/core/command/GdUnitShortcut.gd.uid | 2 +- .../core/command/GdUnitShortcutAction.gd.uid | 2 +- .../src/core/discovery/GdUnitGUID.gd.uid | 2 +- .../src/core/discovery/GdUnitTestCase.gd | 14 +- .../src/core/discovery/GdUnitTestCase.gd.uid | 2 +- .../core/discovery/GdUnitTestDiscoverGuard.gd | 4 + .../discovery/GdUnitTestDiscoverGuard.gd.uid | 2 +- .../discovery/GdUnitTestDiscoverSink.gd.uid | 2 +- .../core/discovery/GdUnitTestDiscoverer.gd | 55 +- .../discovery/GdUnitTestDiscoverer.gd.uid | 2 +- .../gdUnit4/src/core/event/GdUnitEvent.gd.uid | 2 +- .../src/core/event/GdUnitEventInit.gd.uid | 2 +- .../src/core/event/GdUnitEventStop.gd.uid | 2 +- .../event/GdUnitEventTestDiscoverEnd.gd.uid | 2 +- .../event/GdUnitEventTestDiscoverStart.gd.uid | 2 +- .../execution/GdUnitExecutionContext.gd.uid | 2 +- .../execution/GdUnitMemoryObserver.gd.uid | 2 +- .../GdUnitTestReportCollector.gd.uid | 2 +- .../core/execution/GdUnitTestSuiteExecutor.gd | 4 +- .../execution/GdUnitTestSuiteExecutor.gd.uid | 2 +- .../stages/GdUnitTestCaseAfterStage.gd.uid | 2 +- .../stages/GdUnitTestCaseBeforeStage.gd.uid | 2 +- .../GdUnitTestCaseExecutionStage.gd.uid | 2 +- .../stages/GdUnitTestSuiteAfterStage.gd.uid | 2 +- .../stages/GdUnitTestSuiteBeforeStage.gd.uid | 2 +- .../GdUnitTestSuiteExecutionStage.gd.uid | 2 +- .../stages/IGdUnitExecutionStage.gd.uid | 2 +- .../GdUnitTestCaseFuzzedExecutionStage.gd.uid | 2 +- .../GdUnitTestCaseFuzzedTestStage.gd.uid | 2 +- .../GdUnitTestCaseSingleExecutionStage.gd.uid | 2 +- .../GdUnitTestCaseSingleTestStage.gd.uid | 2 +- .../src/core/parse/GdClassDescriptor.gd.uid | 2 +- .../src/core/parse/GdDefaultValueDecoder.gd | 6 + .../core/parse/GdDefaultValueDecoder.gd.uid | 2 +- .../src/core/parse/GdFunctionArgument.gd | 4 +- .../src/core/parse/GdFunctionArgument.gd.uid | 2 +- .../core/parse/GdFunctionDescriptor.gd.uid | 2 +- .../parse/GdFunctionParameterSetResolver.gd | 10 +- .../GdFunctionParameterSetResolver.gd.uid | 2 +- .../gdUnit4/src/core/parse/GdScriptParser.gd | 12 +- .../src/core/parse/GdScriptParser.gd.uid | 2 +- .../core/parse/GdUnitExpressionRunner.gd.uid | 2 +- .../GdUnitTestParameterSetResolver.gd.uid | 2 +- .../src/core/report/GdUnitReport.gd.uid | 2 +- .../core/runners/GdUnitBaseTestRunner.gd.uid | 2 +- .../core/runners/GdUnitTestCIRunner.gd.uid | 2 +- .../src/core/runners/GdUnitTestRunner.gd.uid | 2 +- .../src/core/runners/GdUnitTestRunner.tscn | 4 +- .../GdUnitTestSuiteDefaultTemplate.gd.uid | 2 +- .../test_suite/GdUnitTestSuiteTemplate.gd.uid | 2 +- .../core/thread/GdUnitThreadContext.gd.uid | 2 +- .../core/thread/GdUnitThreadManager.gd.uid | 2 +- .../writers/GdUnitCSIMessageWriter.gd.uid | 2 +- .../core/writers/GdUnitMessageWriter.gd.uid | 2 +- .../GdUnitRichTextMessageWriter.gd.uid | 2 +- addons/gdUnit4/src/dotnet/GdUnit4CSharpApi.cs | 344 +++--- .../src/dotnet/GdUnit4CSharpApiLoader.gd | 26 +- .../src/dotnet/GdUnit4CSharpApiLoader.gd.uid | 2 +- .../src/doubler/CallableDoubler.gd.uid | 2 +- .../gdUnit4/src/doubler/GdFunctionDoubler.gd | 8 +- .../src/doubler/GdFunctionDoubler.gd.uid | 2 +- .../src/doubler/GdUnitClassDoubler.gd.uid | 2 +- .../doubler/GdUnitObjectInteractions.gd.uid | 2 +- .../GdUnitObjectInteractionsVerifier.gd.uid | 2 +- .../GdUnitFuncValueExtractor.gd.uid | 2 +- addons/gdUnit4/src/fuzzers/FloatFuzzer.gd.uid | 2 +- addons/gdUnit4/src/fuzzers/Fuzzer.gd.uid | 2 +- addons/gdUnit4/src/fuzzers/IntFuzzer.gd.uid | 2 +- .../gdUnit4/src/fuzzers/StringFuzzer.gd.uid | 2 +- .../gdUnit4/src/fuzzers/Vector2Fuzzer.gd.uid | 2 +- .../gdUnit4/src/fuzzers/Vector3Fuzzer.gd.uid | 2 +- .../src/matchers/AnyArgumentMatcher.gd.uid | 2 +- .../AnyBuildInTypeArgumentMatcher.gd.uid | 2 +- .../matchers/AnyClazzArgumentMatcher.gd.uid | 2 +- .../matchers/ChainedArgumentMatcher.gd.uid | 2 +- .../src/matchers/EqualsArgumentMatcher.gd.uid | 2 +- .../src/matchers/GdUnitArgumentMatcher.gd.uid | 2 +- .../matchers/GdUnitArgumentMatchers.gd.uid | 2 +- addons/gdUnit4/src/mocking/GdUnitMock.gd.uid | 2 +- .../src/mocking/GdUnitMockBuilder.gd.uid | 2 +- .../mocking/GdUnitMockFunctionDoubler.gd.uid | 2 +- .../gdUnit4/src/mocking/GdUnitMockImpl.gd.uid | 2 +- .../gdUnit4/src/monitor/ErrorLogEntry.gd.uid | 2 +- .../gdUnit4/src/monitor/GdUnitMonitor.gd.uid | 2 +- .../monitor/GdUnitOrphanNodesMonitor.gd.uid | 2 +- .../src/monitor/GodotGdErrorMonitor.gd | 14 + .../src/monitor/GodotGdErrorMonitor.gd.uid | 2 +- .../gdUnit4/src/network/GdUnitServer.gd.uid | 2 +- addons/gdUnit4/src/network/GdUnitServer.tscn | 4 +- .../src/network/GdUnitServerConstants.gd.uid | 2 +- addons/gdUnit4/src/network/GdUnitTask.gd.uid | 2 +- .../src/network/GdUnitTcpClient.gd.uid | 2 +- .../gdUnit4/src/network/GdUnitTcpNode.gd.uid | 2 +- .../src/network/GdUnitTcpServer.gd.uid | 2 +- addons/gdUnit4/src/network/rpc/RPC.gd.uid | 2 +- .../src/network/rpc/RPCClientConnect.gd.uid | 2 +- .../network/rpc/RPCClientDisconnect.gd.uid | 2 +- .../src/network/rpc/RPCGdUnitEvent.gd.uid | 2 +- .../gdUnit4/src/network/rpc/RPCMessage.gd.uid | 2 +- .../GdUnitConsoleTestReporter.gd.uid | 2 +- .../reporters/GdUnitHtmlTestReporter.gd.uid | 2 +- .../src/reporters/GdUnitTestReporter.gd.uid | 2 +- .../src/reporters/JUnitXmlReport.gd.uid | 2 +- .../gdUnit4/src/reporters/XmlElement.gd.uid | 2 +- .../reporters/html/GdUnitByPathReport.gd.uid | 2 +- .../reporters/html/GdUnitHtmlPatterns.gd.uid | 2 +- .../reporters/html/GdUnitHtmlReport.gd.uid | 2 +- .../reporters/html/GdUnitReportSummary.gd.uid | 2 +- .../html/GdUnitTestCaseReport.gd.uid | 2 +- .../html/GdUnitTestSuiteReport.gd.uid | 2 +- .../src/reporters/html/template/.gdignore | 0 .../gdUnit4/src/spy/GdUnitSpyBuilder.gd.uid | 2 +- .../src/spy/GdUnitSpyFunctionDoubler.gd.uid | 2 +- addons/gdUnit4/src/spy/GdUnitSpyImpl.gd.uid | 2 +- addons/gdUnit4/src/ui/GdUnitConsole.gd.uid | 2 +- addons/gdUnit4/src/ui/GdUnitConsole.tscn | 2 +- addons/gdUnit4/src/ui/GdUnitFonts.gd.uid | 2 +- addons/gdUnit4/src/ui/GdUnitInspector.gd.uid | 2 +- addons/gdUnit4/src/ui/GdUnitInspector.tscn | 14 +- .../ui/GdUnitInspectorTreeConstants.gd.uid | 2 +- addons/gdUnit4/src/ui/GdUnitUiTools.gd.uid | 2 +- addons/gdUnit4/src/ui/ScriptEditorControls.gd | 6 +- .../src/ui/ScriptEditorControls.gd.uid | 2 +- .../EditorFileSystemContextMenuHandler.gd.uid | 2 +- .../src/ui/menu/GdUnitContextMenuItem.gd.uid | 2 +- .../ScriptEditorContextMenuHandler.gd.uid | 2 +- .../src/ui/parts/InspectorMonitor.gd.uid | 2 +- .../src/ui/parts/InspectorMonitor.tscn | 2 +- .../src/ui/parts/InspectorProgressBar.gd.uid | 2 +- .../src/ui/parts/InspectorProgressBar.tscn | 2 +- .../src/ui/parts/InspectorStatusBar.gd | 4 +- .../src/ui/parts/InspectorStatusBar.gd.uid | 2 +- .../src/ui/parts/InspectorStatusBar.tscn | 2 +- .../gdUnit4/src/ui/parts/InspectorToolBar.gd | 24 +- .../src/ui/parts/InspectorToolBar.gd.uid | 2 +- .../src/ui/parts/InspectorToolBar.tscn | 2 +- .../src/ui/parts/InspectorTreeMainPanel.gd | 99 +- .../ui/parts/InspectorTreeMainPanel.gd.uid | 2 +- .../src/ui/parts/InspectorTreePanel.tscn | 2 +- .../src/ui/settings/GdUnitInputCapture.gd.uid | 2 +- .../src/ui/settings/GdUnitInputCapture.tscn | 2 +- .../src/ui/settings/GdUnitSettingsDialog.gd | 64 +- .../ui/settings/GdUnitSettingsDialog.gd.uid | 2 +- .../src/ui/settings/GdUnitSettingsDialog.tscn | 12 +- .../src/ui/templates/TestSuiteTemplate.gd.uid | 2 +- .../src/ui/templates/TestSuiteTemplate.tscn | 2 +- addons/gdUnit4/src/update/GdMarkDownReader.gd | 4 +- .../src/update/GdMarkDownReader.gd.uid | 2 +- addons/gdUnit4/src/update/GdUnitPatch.gd.uid | 2 +- addons/gdUnit4/src/update/GdUnitPatcher.gd | 3 +- .../gdUnit4/src/update/GdUnitPatcher.gd.uid | 2 +- addons/gdUnit4/src/update/GdUnitUpdate.gd | 61 +- addons/gdUnit4/src/update/GdUnitUpdate.gd.uid | 2 +- addons/gdUnit4/src/update/GdUnitUpdate.tscn | 2 +- .../src/update/GdUnitUpdateClient.gd.uid | 2 +- .../src/update/GdUnitUpdateNotify.gd.uid | 2 +- .../src/update/GdUnitUpdateNotify.tscn | 6 +- autoloads/constants.gd | 36 +- autoloads/field.gd | 1 + autoloads/field.gd.uid | 1 + autoloads/storyline_manager.gd | 59 + autoloads/storyline_manager.gd.uid | 1 + autoloads/undo_redo_service.gd | 36 + autoloads/undo_redo_service.gd.uid | 1 + common/graph_node_row.gd | 15 + common/graph_node_row.gd.uid | 1 + common/inspectable_object.gd.gd | 76 ++ common/inspectable_object.gd.gd.uid | 1 + common/inspectable_storyline_object.gd | 10 + common/inspectable_storyline_object.gd.uid | 1 + .../layouts/character_edit/character_edit.gd | 6 +- .../character_edit/portrait_list_section.gd | 4 +- .../portrait_settings_section.gd | 12 +- .../character_edit/timeline_section.gd | 2 +- .../dimmer.gd => editor/editor_dimmer.gd} | 0 .../editor_dimmer.gd.uid} | 0 ...list_section.gd => editor_list_section.gd} | 6 +- ...tion.gd.uid => editor_list_section.gd.uid} | 0 ..._section.tscn => editor_list_section.tscn} | 6 +- .../inspector/inspector_category_container.gd | 11 + .../inspector_category_container.gd.uid | 1 + .../inspector_category_container.tscn | 13 + .../editor/inspector/inspector_panel.gd | 90 ++ .../inspector/inspector_panel.gd.uid | 0 .../inspector/inspector_panel.tscn | 2 +- .../layouts/graph_edit/custom_graph_edit.gd | 456 ++++--- .../graph_edit/monologue_graph_edit.gd | 92 +- common/layouts/inspector/inspector_panel.gd | 213 ---- .../layouts/inspector/monologue_inspector.gd | 44 - .../inspector/monologue_inspector.gd.uid | 1 - common/monologue_graph_node.gd | 244 +--- common/monologue_indexer.gd | 8 + common/monologue_indexer.gd.uid | 1 + common/storyline.gd | 27 + common/storyline.gd.uid | 1 + common/ui/buttons/close_button.tscn | 22 +- .../ui/editor_properties/editor_property.gd | 21 + .../editor_properties/editor_property.gd.uid | 1 + common/ui/editor_properties/field_bridge.gd | 1 + .../ui/editor_properties/field_bridge.gd.uid | 1 + common/ui/editor_properties/field_bucket.gd | 40 + .../ui/editor_properties/field_bucket.gd.uid | 1 + common/ui/editor_properties/text/index.gd | 9 + common/ui/editor_properties/text/index.gd.uid | 1 + .../ui/editor_properties/text/text_field.gd | 1 + .../editor_properties/text/text_field.gd.uid | 1 + .../ui/editor_properties/text/text_field.tscn | 5 + .../assets/portrait_placeholder.svg | 12 - .../monologue_character_field.gd | 29 - .../monologue_character_field.gd.uid | 1 - .../monologue_character_field.tscn | 77 -- .../fields/check_box/monologue_check_box.gd | 11 - .../check_box/monologue_check_box.gd.uid | 1 - .../fields/check_box/monologue_check_box.tscn | 20 - .../collapsible_field/collapsible_field.gd | 98 -- .../collapsible_field.gd.uid | 1 - .../collapsible_field/collapsible_field.tscn | 55 - .../ui/fields/dropdown/monolgue_dropdown.gd | 104 -- .../fields/dropdown/monolgue_dropdown.gd.uid | 1 - .../fields/dropdown/monologue_dropdown.tscn | 16 - common/ui/fields/field_label.tscn | 7 - common/ui/fields/file_picker/file_picker.gd | 73 -- .../ui/fields/file_picker/file_picker.gd.uid | 1 - .../file_picker/monologue_file_picker.tscn | 41 - .../fields/line_edit/monologue_line_edit.gd | 54 - .../line_edit/monologue_line_edit.gd.uid | 1 - .../fields/line_edit/monologue_line_edit.tscn | 59 - common/ui/fields/list/monologue_list.gd | 147 --- common/ui/fields/list/monologue_list.gd.uid | 1 - common/ui/fields/list/monologue_list.tscn | 19 - common/ui/fields/localizable.gd | 65 - common/ui/fields/localizable.gd.uid | 1 - common/ui/fields/monologue_argument.gd | 89 -- common/ui/fields/monologue_argument.gd.uid | 1 - common/ui/fields/monologue_field.gd | 40 - common/ui/fields/monologue_field.gd.uid | 1 - common/ui/fields/monologue_variable.gd | 70 -- common/ui/fields/monologue_variable.gd.uid | 1 - .../portrait_option/abstract_portrait.gd | 70 -- .../portrait_option/abstract_portrait.gd.uid | 1 - .../fields/portrait_option/portrait_option.gd | 102 -- .../portrait_option/portrait_option.gd.uid | 1 - .../portrait_option/portrait_option.tscn | 80 -- common/ui/fields/property.gd | 157 --- common/ui/fields/property.gd.uid | 1 - common/ui/fields/property_list.gd | 28 - common/ui/fields/property_list.gd.uid | 1 - common/ui/fields/slider/monologue_slider.gd | 54 - .../ui/fields/slider/monologue_slider.gd.uid | 1 - common/ui/fields/slider/monologue_slider.tscn | 65 - .../ui/fields/spin_box/monologue_spin_box.gd | 26 - .../fields/spin_box/monologue_spin_box.gd.uid | 1 - .../fields/spin_box/monologue_spin_box.tscn | 12 - common/ui/fields/text/monologue_text.gd | 28 - common/ui/fields/text/monologue_text.gd.uid | 1 - common/ui/fields/text/monologue_text.tscn | 54 - .../ui/fields/timeline/monologue_timeline.gd | 331 ----- .../fields/timeline/monologue_timeline.gd.uid | 1 - .../fields/timeline/monologue_timeline.tscn | 366 ------ common/ui/fields/timeline/timeline_cell.gd | 105 -- .../ui/fields/timeline/timeline_cell.gd.uid | 1 - common/ui/fields/timeline/timeline_cell.tscn | 152 --- .../ui/fields/timeline/timeline_cell_layer.gd | 230 ---- .../timeline/timeline_cell_layer.gd.uid | 1 - .../fields/timeline/timeline_cell_layer.tscn | 44 - .../fields/timeline/timeline_cell_number.tscn | 100 -- common/ui/fields/timeline/timeline_layer.gd | 10 - .../ui/fields/timeline/timeline_layer.gd.uid | 1 - common/ui/fields/timeline/timeline_layer.tscn | 88 -- .../timeline/timeline_layer_cell_container.gd | 38 - .../timeline_layer_cell_container.gd.uid | 1 - common/ui/fields/toggle/monologue_toggle.gd | 12 - .../ui/fields/toggle/monologue_toggle.gd.uid | 1 - common/ui/fields/toggle/monologue_toggle.tscn | 20 - common/ui/fields/vector/monologue_vector.gd | 33 - .../ui/fields/vector/monologue_vector.gd.uid | 1 - common/ui/fields/vector/monologue_vector.tscn | 39 - icon.png | Bin 20073 -> 5340967 bytes icon.png.import | 2 +- icon_transparent.png | Bin 0 -> 3418922 bytes ....png.import => icon_transparent.png.import | 8 +- .../abstract_character/abstract_character.gd | 66 - .../abstract_character.gd.uid | 1 - .../asbtract_character.gd.uid | 1 - nodes/abstract_variable/abstract_variable.gd | 119 -- .../abstract_variable.gd.uid | 1 - nodes/action_node/action_node.gd | 68 -- nodes/action_node/action_node.gd.uid | 1 - nodes/action_node/action_node.tscn | 62 - nodes/audio_node/audio_node.gd | 32 - nodes/audio_node/audio_node.gd.uid | 1 - nodes/audio_node/audio_node.tscn | 48 - nodes/background_node/background_node.gd | 85 -- nodes/background_node/background_node.gd.uid | 1 - nodes/background_node/background_node.tscn | 53 - nodes/bridge_in_node/bridge_in_node.gd | 46 - nodes/bridge_in_node/bridge_in_node.gd.uid | 1 - nodes/bridge_in_node/bridge_in_node.tscn | 34 - nodes/bridge_out_node/bridge_out_node.gd | 19 - nodes/bridge_out_node/bridge_out_node.gd.uid | 1 - nodes/bridge_out_node/bridge_out_node.tscn | 35 - nodes/character_node/character_node.gd | 152 --- nodes/character_node/character_node.gd.uid | 1 - nodes/character_node/character_node.tscn | 83 -- nodes/choice_node/choice_node.gd | 142 --- nodes/choice_node/choice_node.gd.uid | 1 - nodes/choice_node/choice_node.tscn | 12 - nodes/comment_node/comment_node.gd | 19 - nodes/comment_node/comment_node.gd.uid | 1 - nodes/comment_node/comment_node.tscn | 31 - nodes/condition_node/condition_node.gd | 25 - nodes/condition_node/condition_node.gd.uid | 1 - nodes/condition_node/condition_node.tscn | 73 -- nodes/end_path_node/end_path_node.gd | 20 - nodes/end_path_node/end_path_node.gd.uid | 1 - nodes/end_path_node/end_path_node.tscn | 30 - nodes/event_node/event_node.gd | 48 - nodes/event_node/event_node.gd.uid | 1 - nodes/event_node/event_node.tscn | 57 - nodes/option_node/option_node.gd | 72 -- nodes/option_node/option_node.gd.uid | 1 - nodes/option_node/option_node.tscn | 80 -- nodes/random_node/output_line.tscn | 17 - nodes/random_node/random_node.gd | 150 --- nodes/random_node/random_node.gd.uid | 1 - nodes/random_node/random_node.tscn | 10 - nodes/random_node/random_output.gd | 61 - nodes/random_node/random_output.gd.uid | 1 - nodes/reroute_node/reroute_node.gd | 20 - nodes/reroute_node/reroute_node.gd.uid | 1 - nodes/reroute_node/reroute_node.tscn | 87 -- nodes/root_node/root_node.gd | 32 +- nodes/root_node/root_node.tscn | 2 - nodes/sentence_node/sentence_node.gd | 46 +- nodes/setter_node/setter_node.gd | 63 - nodes/setter_node/setter_node.gd.uid | 1 - nodes/setter_node/setter_node.tscn | 81 -- nodes/wait_node/wait_node.gd | 20 - nodes/wait_node/wait_node.gd.uid | 1 - nodes/wait_node/wait_node.tscn | 44 - project.godot | 12 +- scenes/main/app.tscn | 2 +- scenes/main/editor.tscn | 65 +- scenes/main/graph_edit_switcher.gd | 202 +--- scenes/main/monologue_editor.gd | 45 +- scenes/splash/dino/Render0001.png | Bin 0 -> 162355 bytes .../splash/dino/Render0001.png.import | 11 +- scenes/splash/dino/Render0002.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0002.png.import | 34 + scenes/splash/dino/Render0003.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0003.png.import | 34 + scenes/splash/dino/Render0004.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0004.png.import | 34 + scenes/splash/dino/Render0005.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0005.png.import | 34 + scenes/splash/dino/Render0006.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0006.png.import | 34 + scenes/splash/dino/Render0007.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0007.png.import | 34 + scenes/splash/dino/Render0008.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0008.png.import | 34 + scenes/splash/dino/Render0009.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0009.png.import | 34 + scenes/splash/dino/Render0010.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0010.png.import | 34 + scenes/splash/dino/Render0011.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0011.png.import | 34 + scenes/splash/dino/Render0012.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0012.png.import | 34 + scenes/splash/dino/Render0013.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0013.png.import | 34 + scenes/splash/dino/Render0014.png | Bin 0 -> 164107 bytes scenes/splash/dino/Render0014.png.import | 34 + scenes/splash/dino/Render0015.png | Bin 0 -> 162521 bytes scenes/splash/dino/Render0015.png.import | 34 + scenes/splash/dino/Render0016.png | Bin 0 -> 162521 bytes scenes/splash/dino/Render0016.png.import | 34 + scenes/splash/dino/Render0017.png | Bin 0 -> 164107 bytes scenes/splash/dino/Render0017.png.import | 34 + scenes/splash/dino/Render0018.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0018.png.import | 34 + scenes/splash/dino/Render0019.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0019.png.import | 34 + scenes/splash/dino/Render0020.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0020.png.import | 34 + scenes/splash/dino/Render0021.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0021.png.import | 34 + scenes/splash/dino/Render0022.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0022.png.import | 34 + scenes/splash/dino/Render0023.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0023.png.import | 34 + scenes/splash/dino/Render0024.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0024.png.import | 34 + scenes/splash/dino/Render0025.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0025.png.import | 34 + scenes/splash/dino/Render0026.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0026.png.import | 34 + scenes/splash/dino/Render0027.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0027.png.import | 34 + scenes/splash/dino/Render0028.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0028.png.import | 34 + scenes/splash/dino/Render0029.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0029.png.import | 34 + scenes/splash/dino/Render0030.png | Bin 0 -> 162355 bytes scenes/splash/dino/Render0030.png.import | 34 + scenes/splash/splash.gd | 22 +- scenes/splash/splash.tscn | 148 ++- src/common/property.gd | 48 + src/common/property.gd.uid | 1 + ui/assets/icons/px_character.png | Bin 0 -> 139 bytes ui/assets/icons/px_character.png.import | 34 + ui/theme_default/main.tres | 342 +++--- ui/theme_default/theme_default.gd | 722 +++++------ 497 files changed, 3319 insertions(+), 8734 deletions(-) delete mode 100644 addons/gdUnit4/GdUnitRunner.cfg create mode 100644 addons/gdUnit4/src/reporters/html/template/.gdignore create mode 100644 autoloads/field.gd create mode 100644 autoloads/field.gd.uid create mode 100644 autoloads/storyline_manager.gd create mode 100644 autoloads/storyline_manager.gd.uid create mode 100644 autoloads/undo_redo_service.gd create mode 100644 autoloads/undo_redo_service.gd.uid create mode 100644 common/graph_node_row.gd create mode 100644 common/graph_node_row.gd.uid create mode 100644 common/inspectable_object.gd.gd create mode 100644 common/inspectable_object.gd.gd.uid create mode 100644 common/inspectable_storyline_object.gd create mode 100644 common/inspectable_storyline_object.gd.uid rename common/layouts/{dimmer/dimmer.gd => editor/editor_dimmer.gd} (100%) rename common/layouts/{dimmer/dimmer.gd.uid => editor/editor_dimmer.gd.uid} (100%) rename common/layouts/editor/{list_section/list_section.gd => editor_list_section.gd} (80%) rename common/layouts/editor/{list_section/list_section.gd.uid => editor_list_section.gd.uid} (100%) rename common/layouts/editor/{list_section/list_section.tscn => editor_list_section.tscn} (90%) create mode 100644 common/layouts/editor/inspector/inspector_category_container.gd create mode 100644 common/layouts/editor/inspector/inspector_category_container.gd.uid create mode 100644 common/layouts/editor/inspector/inspector_category_container.tscn create mode 100644 common/layouts/editor/inspector/inspector_panel.gd rename common/layouts/{ => editor}/inspector/inspector_panel.gd.uid (100%) rename common/layouts/{ => editor}/inspector/inspector_panel.tscn (96%) delete mode 100644 common/layouts/inspector/inspector_panel.gd delete mode 100644 common/layouts/inspector/monologue_inspector.gd delete mode 100644 common/layouts/inspector/monologue_inspector.gd.uid create mode 100644 common/monologue_indexer.gd create mode 100644 common/monologue_indexer.gd.uid create mode 100644 common/storyline.gd create mode 100644 common/storyline.gd.uid create mode 100644 common/ui/editor_properties/editor_property.gd create mode 100644 common/ui/editor_properties/editor_property.gd.uid create mode 100644 common/ui/editor_properties/field_bridge.gd create mode 100644 common/ui/editor_properties/field_bridge.gd.uid create mode 100644 common/ui/editor_properties/field_bucket.gd create mode 100644 common/ui/editor_properties/field_bucket.gd.uid create mode 100644 common/ui/editor_properties/text/index.gd create mode 100644 common/ui/editor_properties/text/index.gd.uid create mode 100644 common/ui/editor_properties/text/text_field.gd create mode 100644 common/ui/editor_properties/text/text_field.gd.uid create mode 100644 common/ui/editor_properties/text/text_field.tscn delete mode 100644 common/ui/fields/character_field/assets/portrait_placeholder.svg delete mode 100644 common/ui/fields/character_field/monologue_character_field.gd delete mode 100644 common/ui/fields/character_field/monologue_character_field.gd.uid delete mode 100644 common/ui/fields/character_field/monologue_character_field.tscn delete mode 100644 common/ui/fields/check_box/monologue_check_box.gd delete mode 100644 common/ui/fields/check_box/monologue_check_box.gd.uid delete mode 100644 common/ui/fields/check_box/monologue_check_box.tscn delete mode 100644 common/ui/fields/collapsible_field/collapsible_field.gd delete mode 100644 common/ui/fields/collapsible_field/collapsible_field.gd.uid delete mode 100644 common/ui/fields/collapsible_field/collapsible_field.tscn delete mode 100644 common/ui/fields/dropdown/monolgue_dropdown.gd delete mode 100644 common/ui/fields/dropdown/monolgue_dropdown.gd.uid delete mode 100644 common/ui/fields/dropdown/monologue_dropdown.tscn delete mode 100644 common/ui/fields/field_label.tscn delete mode 100644 common/ui/fields/file_picker/file_picker.gd delete mode 100644 common/ui/fields/file_picker/file_picker.gd.uid delete mode 100644 common/ui/fields/file_picker/monologue_file_picker.tscn delete mode 100644 common/ui/fields/line_edit/monologue_line_edit.gd delete mode 100644 common/ui/fields/line_edit/monologue_line_edit.gd.uid delete mode 100644 common/ui/fields/line_edit/monologue_line_edit.tscn delete mode 100644 common/ui/fields/list/monologue_list.gd delete mode 100644 common/ui/fields/list/monologue_list.gd.uid delete mode 100644 common/ui/fields/list/monologue_list.tscn delete mode 100644 common/ui/fields/localizable.gd delete mode 100644 common/ui/fields/localizable.gd.uid delete mode 100644 common/ui/fields/monologue_argument.gd delete mode 100644 common/ui/fields/monologue_argument.gd.uid delete mode 100644 common/ui/fields/monologue_field.gd delete mode 100644 common/ui/fields/monologue_field.gd.uid delete mode 100644 common/ui/fields/monologue_variable.gd delete mode 100644 common/ui/fields/monologue_variable.gd.uid delete mode 100644 common/ui/fields/portrait_option/abstract_portrait.gd delete mode 100644 common/ui/fields/portrait_option/abstract_portrait.gd.uid delete mode 100644 common/ui/fields/portrait_option/portrait_option.gd delete mode 100644 common/ui/fields/portrait_option/portrait_option.gd.uid delete mode 100644 common/ui/fields/portrait_option/portrait_option.tscn delete mode 100644 common/ui/fields/property.gd delete mode 100644 common/ui/fields/property.gd.uid delete mode 100644 common/ui/fields/property_list.gd delete mode 100644 common/ui/fields/property_list.gd.uid delete mode 100644 common/ui/fields/slider/monologue_slider.gd delete mode 100644 common/ui/fields/slider/monologue_slider.gd.uid delete mode 100644 common/ui/fields/slider/monologue_slider.tscn delete mode 100644 common/ui/fields/spin_box/monologue_spin_box.gd delete mode 100644 common/ui/fields/spin_box/monologue_spin_box.gd.uid delete mode 100644 common/ui/fields/spin_box/monologue_spin_box.tscn delete mode 100644 common/ui/fields/text/monologue_text.gd delete mode 100644 common/ui/fields/text/monologue_text.gd.uid delete mode 100644 common/ui/fields/text/monologue_text.tscn delete mode 100644 common/ui/fields/timeline/monologue_timeline.gd delete mode 100644 common/ui/fields/timeline/monologue_timeline.gd.uid delete mode 100644 common/ui/fields/timeline/monologue_timeline.tscn delete mode 100644 common/ui/fields/timeline/timeline_cell.gd delete mode 100644 common/ui/fields/timeline/timeline_cell.gd.uid delete mode 100644 common/ui/fields/timeline/timeline_cell.tscn delete mode 100644 common/ui/fields/timeline/timeline_cell_layer.gd delete mode 100644 common/ui/fields/timeline/timeline_cell_layer.gd.uid delete mode 100644 common/ui/fields/timeline/timeline_cell_layer.tscn delete mode 100644 common/ui/fields/timeline/timeline_cell_number.tscn delete mode 100644 common/ui/fields/timeline/timeline_layer.gd delete mode 100644 common/ui/fields/timeline/timeline_layer.gd.uid delete mode 100644 common/ui/fields/timeline/timeline_layer.tscn delete mode 100644 common/ui/fields/timeline/timeline_layer_cell_container.gd delete mode 100644 common/ui/fields/timeline/timeline_layer_cell_container.gd.uid delete mode 100644 common/ui/fields/toggle/monologue_toggle.gd delete mode 100644 common/ui/fields/toggle/monologue_toggle.gd.uid delete mode 100644 common/ui/fields/toggle/monologue_toggle.tscn delete mode 100644 common/ui/fields/vector/monologue_vector.gd delete mode 100644 common/ui/fields/vector/monologue_vector.gd.uid delete mode 100644 common/ui/fields/vector/monologue_vector.tscn create mode 100644 icon_transparent.png rename addons/gdUnit4/src/reporters/html/template/css/logo.png.import => icon_transparent.png.import (67%) delete mode 100644 nodes/abstract_character/abstract_character.gd delete mode 100644 nodes/abstract_character/abstract_character.gd.uid delete mode 100644 nodes/abstract_character/asbtract_character.gd.uid delete mode 100644 nodes/abstract_variable/abstract_variable.gd delete mode 100644 nodes/abstract_variable/abstract_variable.gd.uid delete mode 100644 nodes/action_node/action_node.gd delete mode 100644 nodes/action_node/action_node.gd.uid delete mode 100644 nodes/action_node/action_node.tscn delete mode 100644 nodes/audio_node/audio_node.gd delete mode 100644 nodes/audio_node/audio_node.gd.uid delete mode 100644 nodes/audio_node/audio_node.tscn delete mode 100644 nodes/background_node/background_node.gd delete mode 100644 nodes/background_node/background_node.gd.uid delete mode 100644 nodes/background_node/background_node.tscn delete mode 100644 nodes/bridge_in_node/bridge_in_node.gd delete mode 100644 nodes/bridge_in_node/bridge_in_node.gd.uid delete mode 100644 nodes/bridge_in_node/bridge_in_node.tscn delete mode 100644 nodes/bridge_out_node/bridge_out_node.gd delete mode 100644 nodes/bridge_out_node/bridge_out_node.gd.uid delete mode 100644 nodes/bridge_out_node/bridge_out_node.tscn delete mode 100644 nodes/character_node/character_node.gd delete mode 100644 nodes/character_node/character_node.gd.uid delete mode 100644 nodes/character_node/character_node.tscn delete mode 100644 nodes/choice_node/choice_node.gd delete mode 100644 nodes/choice_node/choice_node.gd.uid delete mode 100644 nodes/choice_node/choice_node.tscn delete mode 100644 nodes/comment_node/comment_node.gd delete mode 100644 nodes/comment_node/comment_node.gd.uid delete mode 100644 nodes/comment_node/comment_node.tscn delete mode 100644 nodes/condition_node/condition_node.gd delete mode 100644 nodes/condition_node/condition_node.gd.uid delete mode 100644 nodes/condition_node/condition_node.tscn delete mode 100644 nodes/end_path_node/end_path_node.gd delete mode 100644 nodes/end_path_node/end_path_node.gd.uid delete mode 100644 nodes/end_path_node/end_path_node.tscn delete mode 100644 nodes/event_node/event_node.gd delete mode 100644 nodes/event_node/event_node.gd.uid delete mode 100644 nodes/event_node/event_node.tscn delete mode 100644 nodes/option_node/option_node.gd delete mode 100644 nodes/option_node/option_node.gd.uid delete mode 100644 nodes/option_node/option_node.tscn delete mode 100644 nodes/random_node/output_line.tscn delete mode 100644 nodes/random_node/random_node.gd delete mode 100644 nodes/random_node/random_node.gd.uid delete mode 100644 nodes/random_node/random_node.tscn delete mode 100644 nodes/random_node/random_output.gd delete mode 100644 nodes/random_node/random_output.gd.uid delete mode 100644 nodes/reroute_node/reroute_node.gd delete mode 100644 nodes/reroute_node/reroute_node.gd.uid delete mode 100644 nodes/reroute_node/reroute_node.tscn delete mode 100644 nodes/setter_node/setter_node.gd delete mode 100644 nodes/setter_node/setter_node.gd.uid delete mode 100644 nodes/setter_node/setter_node.tscn delete mode 100644 nodes/wait_node/wait_node.gd delete mode 100644 nodes/wait_node/wait_node.gd.uid delete mode 100644 nodes/wait_node/wait_node.tscn create mode 100644 scenes/splash/dino/Render0001.png rename common/ui/fields/character_field/assets/portrait_placeholder.svg.import => scenes/splash/dino/Render0001.png.import (57%) create mode 100644 scenes/splash/dino/Render0002.png create mode 100644 scenes/splash/dino/Render0002.png.import create mode 100644 scenes/splash/dino/Render0003.png create mode 100644 scenes/splash/dino/Render0003.png.import create mode 100644 scenes/splash/dino/Render0004.png create mode 100644 scenes/splash/dino/Render0004.png.import create mode 100644 scenes/splash/dino/Render0005.png create mode 100644 scenes/splash/dino/Render0005.png.import create mode 100644 scenes/splash/dino/Render0006.png create mode 100644 scenes/splash/dino/Render0006.png.import create mode 100644 scenes/splash/dino/Render0007.png create mode 100644 scenes/splash/dino/Render0007.png.import create mode 100644 scenes/splash/dino/Render0008.png create mode 100644 scenes/splash/dino/Render0008.png.import create mode 100644 scenes/splash/dino/Render0009.png create mode 100644 scenes/splash/dino/Render0009.png.import create mode 100644 scenes/splash/dino/Render0010.png create mode 100644 scenes/splash/dino/Render0010.png.import create mode 100644 scenes/splash/dino/Render0011.png create mode 100644 scenes/splash/dino/Render0011.png.import create mode 100644 scenes/splash/dino/Render0012.png create mode 100644 scenes/splash/dino/Render0012.png.import create mode 100644 scenes/splash/dino/Render0013.png create mode 100644 scenes/splash/dino/Render0013.png.import create mode 100644 scenes/splash/dino/Render0014.png create mode 100644 scenes/splash/dino/Render0014.png.import create mode 100644 scenes/splash/dino/Render0015.png create mode 100644 scenes/splash/dino/Render0015.png.import create mode 100644 scenes/splash/dino/Render0016.png create mode 100644 scenes/splash/dino/Render0016.png.import create mode 100644 scenes/splash/dino/Render0017.png create mode 100644 scenes/splash/dino/Render0017.png.import create mode 100644 scenes/splash/dino/Render0018.png create mode 100644 scenes/splash/dino/Render0018.png.import create mode 100644 scenes/splash/dino/Render0019.png create mode 100644 scenes/splash/dino/Render0019.png.import create mode 100644 scenes/splash/dino/Render0020.png create mode 100644 scenes/splash/dino/Render0020.png.import create mode 100644 scenes/splash/dino/Render0021.png create mode 100644 scenes/splash/dino/Render0021.png.import create mode 100644 scenes/splash/dino/Render0022.png create mode 100644 scenes/splash/dino/Render0022.png.import create mode 100644 scenes/splash/dino/Render0023.png create mode 100644 scenes/splash/dino/Render0023.png.import create mode 100644 scenes/splash/dino/Render0024.png create mode 100644 scenes/splash/dino/Render0024.png.import create mode 100644 scenes/splash/dino/Render0025.png create mode 100644 scenes/splash/dino/Render0025.png.import create mode 100644 scenes/splash/dino/Render0026.png create mode 100644 scenes/splash/dino/Render0026.png.import create mode 100644 scenes/splash/dino/Render0027.png create mode 100644 scenes/splash/dino/Render0027.png.import create mode 100644 scenes/splash/dino/Render0028.png create mode 100644 scenes/splash/dino/Render0028.png.import create mode 100644 scenes/splash/dino/Render0029.png create mode 100644 scenes/splash/dino/Render0029.png.import create mode 100644 scenes/splash/dino/Render0030.png create mode 100644 scenes/splash/dino/Render0030.png.import create mode 100644 src/common/property.gd create mode 100644 src/common/property.gd.uid create mode 100644 ui/assets/icons/px_character.png create mode 100644 ui/assets/icons/px_character.png.import diff --git a/addons/gdUnit4/GdUnitRunner.cfg b/addons/gdUnit4/GdUnitRunner.cfg deleted file mode 100644 index b2caafaf..00000000 --- a/addons/gdUnit4/GdUnitRunner.cfg +++ /dev/null @@ -1,1060 +0,0 @@ -{ - "server_port": 31002, - "tests": [ - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_from_dict", - "fully_qualified_name": "unit.test_action_node.test_from_dict", - "guid": "8b3a8d57-f923462-2be70e5-92fbd40cd2", - "line_number": 17, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_action_node.gd", - "suite_name": "test_action_node", - "test_name": "test_from_dict" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_arguments_to_dict", - "fully_qualified_name": "unit.test_action_node.test_arguments_to_dict", - "guid": "4f7bdc24-4edf40f-e82ee0e-bdfda0cb23", - "line_number": 36, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_action_node.gd", - "suite_name": "test_action_node", - "test_name": "test_arguments_to_dict" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_add_option", - "fully_qualified_name": "unit.test_choice_node.test_add_option", - "guid": "d68db92d-45f14f0-8bde5f8-d5183d84f9", - "line_number": 28, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_choice_node.gd", - "suite_name": "test_choice_node", - "test_name": "test_add_option" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_to_fields", - "fully_qualified_name": "unit.test_choice_node.test_to_fields", - "guid": "091d9df9-b72849c-08391cd-1706aebafc", - "line_number": 50, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_choice_node.gd", - "suite_name": "test_choice_node", - "test_name": "test_to_fields" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_language_integration", - "fully_qualified_name": "unit.test_choice_node.test_language_integration", - "guid": "27af2362-abfe447-fa035ff-b9634a8fbe", - "line_number": 62, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_choice_node.gd", - "suite_name": "test_choice_node", - "test_name": "test_language_integration" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_restore_options", - "fully_qualified_name": "unit.test_choice_node.test_restore_options", - "guid": "a987c51b-b2c8494-58cfbcc-6d74538314", - "line_number": 102, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_choice_node.gd", - "suite_name": "test_choice_node", - "test_name": "test_restore_options" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_update_parent", - "fully_qualified_name": "unit.test_choice_node.test_update_parent", - "guid": "3fd45d76-5b7e4dd-b99ae16-474e96f72d", - "line_number": 130, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_choice_node.gd", - "suite_name": "test_choice_node", - "test_name": "test_update_parent" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_add_root", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_add_root", - "guid": "179cd145-74104e6-d901e79-2e1c32869d", - "line_number": 15, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_add_root" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_add_tab_first", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_add_tab_first", - "guid": "4d6e51e7-6735474-9ae787f-1865f58e1a", - "line_number": 29, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_add_tab_first" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_add_tab_with_previous", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_add_tab_with_previous", - "guid": "927774e3-b0aa4bd-ab9f4f3-191e66193d", - "line_number": 35, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_add_tab_with_previous" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_connect_side_panel", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_connect_side_panel", - "guid": "53de68af-9cea454-4928262-9633a9fac5", - "line_number": 45, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_connect_side_panel" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_commit_side_panel", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_commit_side_panel", - "guid": "e5780596-18e5455-5838115-1e2246c817", - "line_number": 56, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_commit_side_panel" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_get_current_graph_edit", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_get_current_graph_edit", - "guid": "3d78084f-fa7f486-793e9d5-5bf1708199", - "line_number": 62, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_get_current_graph_edit" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_is_file_opened_false", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_is_file_opened_false", - "guid": "a1a8e4ff-2fed4cc-dabf680-90889f556b", - "line_number": 73, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_is_file_opened_false" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_is_file_opened_true", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_is_file_opened_true", - "guid": "11c8d58a-18c64a5-f83dd0a-adc73cd6b3", - "line_number": 77, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_is_file_opened_true" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_new_graph_edit", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_new_graph_edit", - "guid": "9e338c89-299c4ce-2809695-51fc5e117d", - "line_number": 84, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_new_graph_edit" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_on_tab_close_pressed_saved", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_on_tab_close_pressed_saved", - "guid": "0fe0c990-427a4b0-f9e5dcc-3ff12bfdc9", - "line_number": 93, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_on_tab_close_pressed_saved" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_on_tab_close_pressed_unsaved", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_on_tab_close_pressed_unsaved", - "guid": "0b07bd64-6b0d4d4-385c6ee-153ba92998", - "line_number": 102, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_on_tab_close_pressed_unsaved" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_show_current_config", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_show_current_config", - "guid": "92b08ef9-ac704f8-491345e-1c6221a481", - "line_number": 117, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_show_current_config" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_update_save_state_saved", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_update_save_state_saved", - "guid": "e7224d3f-6af64eb-5a28751-f66bef818c", - "line_number": 126, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_update_save_state_saved" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_update_save_state_unsaved", - "fully_qualified_name": "unit.test_graph_edit_switcher.test_update_save_state_unsaved", - "guid": "638451a5-4f104ab-5ad66b0-74d936f397", - "line_number": 136, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_edit_switcher.gd", - "suite_name": "test_graph_edit_switcher", - "test_name": "test_update_save_state_unsaved" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_enable_picker_mode", - "fully_qualified_name": "unit.test_graph_node_picker.test_enable_picker_mode", - "guid": "47f84251-0be6447-2b42090-345eabfdb8", - "line_number": 4, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_node_picker.gd", - "suite_name": "test_graph_node_picker", - "test_name": "test_enable_picker_mode" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_enable_picker_mode_invalid_graph", - "fully_qualified_name": "unit.test_graph_node_picker.test_enable_picker_mode_invalid_graph", - "guid": "008f8541-9ff4415-9815729-0cf170ecd3", - "line_number": 27, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_node_picker.gd", - "suite_name": "test_graph_node_picker", - "test_name": "test_enable_picker_mode_invalid_graph" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_close", - "fully_qualified_name": "unit.test_graph_node_picker.test_close", - "guid": "cf67938e-d5cd4a3-b8694fd-2aa4d154f1", - "line_number": 37, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_graph_node_picker.gd", - "suite_name": "test_graph_node_picker", - "test_name": "test_close" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_delete_language", - "fully_qualified_name": "unit.test_language_switcher.test_delete_language", - "guid": "cb0d9463-7253478-db8f6f7-832e5adb39", - "line_number": 13, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_delete_language" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_delete_language_destroys_raw_data", - "fully_qualified_name": "unit.test_language_switcher.test_delete_language_destroys_raw_data", - "guid": "6d7cfb08-870e4aa-b8222aa-55377d0f6f", - "line_number": 24, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_delete_language_destroys_raw_data" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_delete_language_undo_restores_data", - "fully_qualified_name": "unit.test_language_switcher.test_delete_language_undo_restores_data", - "guid": "5609cea3-f7c148a-bbe5f1a-bc4a4d3d38", - "line_number": 37, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_delete_language_undo_restores_data" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_first_language_does_not_show_delete_button", - "fully_qualified_name": "unit.test_language_switcher.test_first_language_does_not_show_delete_button", - "guid": "eaf477a8-1762483-2931e83-12a56b0b70", - "line_number": 53, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_first_language_does_not_show_delete_button" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_language_get_value_renamed", - "fully_qualified_name": "unit.test_language_switcher.test_language_get_value_renamed", - "guid": "33d52919-c6b84ae-c959c33-c83aa10e33", - "line_number": 63, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_language_get_value_renamed" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_language_get_value_nonexist_after_switch", - "fully_qualified_name": "unit.test_language_switcher.test_language_get_value_nonexist_after_switch", - "guid": "7f24331f-17eb4bb-098e5b2-1b3c8e37ed", - "line_number": 72, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_language_get_value_nonexist_after_switch" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_language_set_value_locale_dictionary", - "fully_qualified_name": "unit.test_language_switcher.test_language_set_value_locale_dictionary", - "guid": "33600f8a-29d94c0-2b849ac-b7a0eaa988", - "line_number": 81, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_language_set_value_locale_dictionary" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_language_set_value_locale_dictionary_non_default", - "fully_qualified_name": "unit.test_language_switcher.test_language_set_value_locale_dictionary_non_default", - "guid": "14bc24e9-48b6468-5b306fe-2f10570ab9", - "line_number": 90, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_language_set_value_locale_dictionary_non_default" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_language_set_value_string", - "fully_qualified_name": "unit.test_language_switcher.test_language_set_value_string", - "guid": "4b8f1e19-91e8463-7880654-b5e8091b9e", - "line_number": 98, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_language_set_value_string" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_language_set_value_and_switch_locales", - "fully_qualified_name": "unit.test_language_switcher.test_language_set_value_and_switch_locales", - "guid": "931f554e-402b4bd-bb05c39-f39d9ca090", - "line_number": 104, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_language_set_value_and_switch_locales" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_load_languages", - "fully_qualified_name": "unit.test_language_switcher.test_load_languages", - "guid": "2f5c634f-483e43d-0a7ed37-4bd8e75b9a", - "line_number": 124, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_load_languages" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_load_languages_duplicate", - "fully_qualified_name": "unit.test_language_switcher.test_load_languages_duplicate", - "guid": "5b463b9b-9b3d4b9-592b40a-21442a6eb1", - "line_number": 132, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_language_switcher.gd", - "suite_name": "test_language_switcher", - "test_name": "test_load_languages_duplicate" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_to_dict", - "fully_qualified_name": "unit.test_option_node.test_to_dict", - "guid": "76edd481-52fc4ce-4ade2ab-83a0c7e2b5", - "line_number": 4, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_option_node.gd", - "suite_name": "test_option_node", - "test_name": "test_to_dict" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_from_dict_v2", - "fully_qualified_name": "unit.test_option_node.test_from_dict_v2", - "guid": "562978a7-dba945f-680a83f-6839cf6e2a", - "line_number": 20, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_option_node.gd", - "suite_name": "test_option_node", - "test_name": "test_from_dict_v2" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_from_dict_v3", - "fully_qualified_name": "unit.test_option_node.test_from_dict_v3", - "guid": "fb9dc20c-84f4432-5920b31-d07ed25118", - "line_number": 40, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_option_node.gd", - "suite_name": "test_option_node", - "test_name": "test_from_dict_v3" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 0, - "display_name": "test_absolute_to_relative_linux:0 ('/home/mrsharpener/Pen/w/b/20/ac.mp3', 'w/b/20/ac.mp3')", - "fully_qualified_name": "unit.test_path.test_absolute_to_relative_linux.test_absolute_to_relative_linux:0 ('/home/mrsharpener/Pen/w/b/20/ac.mp3', 'w/b/20/ac.mp3')", - "guid": "392e4080-e447468-c84de6b-0ec208648b", - "line_number": 29, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_absolute_to_relative_linux" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 1, - "display_name": "test_absolute_to_relative_linux:1 ('/home/mrsharpener/Pen/1.txt', '1.txt')", - "fully_qualified_name": "unit.test_path.test_absolute_to_relative_linux.test_absolute_to_relative_linux:1 ('/home/mrsharpener/Pen/1.txt', '1.txt')", - "guid": "a3b19b01-b2a74d3-0b2875a-92e4609a26", - "line_number": 29, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_absolute_to_relative_linux" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 2, - "display_name": "test_absolute_to_relative_linux:2 ('/home/missyeraser/K/y.o.u', '../../missyeraser/K/y.o.u')", - "fully_qualified_name": "unit.test_path.test_absolute_to_relative_linux.test_absolute_to_relative_linux:2 ('/home/missyeraser/K/y.o.u', '../../missyeraser/K/y.o.u')", - "guid": "f639d195-2a6349b-18caa33-11b71fa0a0", - "line_number": 29, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_absolute_to_relative_linux" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 3, - "display_name": "test_absolute_to_relative_linux:3 ('/opt/x/1/bro', '/opt/x/1/bro')", - "fully_qualified_name": "unit.test_path.test_absolute_to_relative_linux.test_absolute_to_relative_linux:3 ('/opt/x/1/bro', '/opt/x/1/bro')", - "guid": "82e7695e-f29745c-48cefb6-41c107abe8", - "line_number": 29, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_absolute_to_relative_linux" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 0, - "display_name": "test_absolute_to_relative_windows:0 ('C:\\\\Users\\\\MrSharpener\\\\Pen\\\\w\\\\b\\\\20\\\\ac.mp3', 'w/b/20/ac.mp3')", - "fully_qualified_name": "unit.test_path.test_absolute_to_relative_windows.test_absolute_to_relative_windows:0 ('C:\\\\Users\\\\MrSharpener\\\\Pen\\\\w\\\\b\\\\20\\\\ac.mp3', 'w/b/20/ac.mp3')", - "guid": "aff013d2-f15a419-5a7089b-d6faaefd1b", - "line_number": 36, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_absolute_to_relative_windows" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 1, - "display_name": "test_absolute_to_relative_windows:1 ('C:\\\\Users\\\\MrSharpener\\\\Pen\\\\1.txt', '1.txt')", - "fully_qualified_name": "unit.test_path.test_absolute_to_relative_windows.test_absolute_to_relative_windows:1 ('C:\\\\Users\\\\MrSharpener\\\\Pen\\\\1.txt', '1.txt')", - "guid": "56ff097e-4f3747a-4af3a0b-e94e09acf4", - "line_number": 36, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_absolute_to_relative_windows" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 2, - "display_name": "test_absolute_to_relative_windows:2 ('C:\\\\Users\\\\MissyEraser\\\\K\\\\y.o.u', '../../MissyEraser/K/y.o.u')", - "fully_qualified_name": "unit.test_path.test_absolute_to_relative_windows.test_absolute_to_relative_windows:2 ('C:\\\\Users\\\\MissyEraser\\\\K\\\\y.o.u', '../../MissyEraser/K/y.o.u')", - "guid": "af373b44-44f5418-7acb838-0e1952e4f4", - "line_number": 36, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_absolute_to_relative_windows" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 3, - "display_name": "test_absolute_to_relative_windows:3 ('E:\\\\10. __World\\\\g1d a', 'E:\\\\10. __World\\\\g1d a')", - "fully_qualified_name": "unit.test_path.test_absolute_to_relative_windows.test_absolute_to_relative_windows:3 ('E:\\\\10. __World\\\\g1d a', 'E:\\\\10. __World\\\\g1d a')", - "guid": "c513260a-bdd84c2-8847b21-000cfe33e4", - "line_number": 36, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_absolute_to_relative_windows" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 0, - "display_name": "test_relative_to_absolute_linux:0 ('w/b/20/ac.mp3', '/home/mrsharpener/Pen/w/b/20/ac.mp3')", - "fully_qualified_name": "unit.test_path.test_relative_to_absolute_linux.test_relative_to_absolute_linux:0 ('w/b/20/ac.mp3', '/home/mrsharpener/Pen/w/b/20/ac.mp3')", - "guid": "0af2839a-497b4bb-6b4d2d5-559d969dfd", - "line_number": 45, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_relative_to_absolute_linux" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 1, - "display_name": "test_relative_to_absolute_linux:1 ('1.txt', '/home/mrsharpener/Pen/1.txt')", - "fully_qualified_name": "unit.test_path.test_relative_to_absolute_linux.test_relative_to_absolute_linux:1 ('1.txt', '/home/mrsharpener/Pen/1.txt')", - "guid": "33322afb-3bee47b-aa39375-2655ff8c36", - "line_number": 45, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_relative_to_absolute_linux" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 2, - "display_name": "test_relative_to_absolute_linux:2 ('../../missyeraser/K/y.o.u', '/home/missyeraser/K/y.o.u')", - "fully_qualified_name": "unit.test_path.test_relative_to_absolute_linux.test_relative_to_absolute_linux:2 ('../../missyeraser/K/y.o.u', '/home/missyeraser/K/y.o.u')", - "guid": "64d6e013-8ae9425-693a675-ff724a8564", - "line_number": 45, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_relative_to_absolute_linux" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 3, - "display_name": "test_relative_to_absolute_linux:3 ('/opt/x/1/bro', '/opt/x/1/bro')", - "fully_qualified_name": "unit.test_path.test_relative_to_absolute_linux.test_relative_to_absolute_linux:3 ('/opt/x/1/bro', '/opt/x/1/bro')", - "guid": "c6976c5f-7a944a9-dbc0515-14eaba95de", - "line_number": 45, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_relative_to_absolute_linux" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 0, - "display_name": "test_relative_to_absolute_windows:0 ('w/b/20/ac.mp3', 'C:/Users/MrSharpener/Pen/w/b/20/ac.mp3')", - "fully_qualified_name": "unit.test_path.test_relative_to_absolute_windows.test_relative_to_absolute_windows:0 ('w/b/20/ac.mp3', 'C:/Users/MrSharpener/Pen/w/b/20/ac.mp3')", - "guid": "c51d8e5d-4436449-aa3130b-485161d13f", - "line_number": 54, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_relative_to_absolute_windows" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 1, - "display_name": "test_relative_to_absolute_windows:1 ('1.txt', 'C:/Users/MrSharpener/Pen/1.txt')", - "fully_qualified_name": "unit.test_path.test_relative_to_absolute_windows.test_relative_to_absolute_windows:1 ('1.txt', 'C:/Users/MrSharpener/Pen/1.txt')", - "guid": "29bc0f6c-3514484-08e1b53-7b289515a7", - "line_number": 54, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_relative_to_absolute_windows" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 2, - "display_name": "test_relative_to_absolute_windows:2 ('../../MissyEraser/K/y.o.u', 'C:/Users/MissyEraser/K/y.o.u')", - "fully_qualified_name": "unit.test_path.test_relative_to_absolute_windows.test_relative_to_absolute_windows:2 ('../../MissyEraser/K/y.o.u', 'C:/Users/MissyEraser/K/y.o.u')", - "guid": "13f4983f-5047424-ea8054c-b0dde53a81", - "line_number": 54, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_relative_to_absolute_windows" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": 3, - "display_name": "test_relative_to_absolute_windows:3 ('E:/10. __World/g1d a', 'E:/10. __World/g1d a')", - "fully_qualified_name": "unit.test_path.test_relative_to_absolute_windows.test_relative_to_absolute_windows:3 ('E:/10. __World/g1d a', 'E:/10. __World/g1d a')", - "guid": "5c2e662c-33ae461-180a014-56fb771303", - "line_number": 54, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_path.gd", - "suite_name": "test_path", - "test_name": "test_relative_to_absolute_windows" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_add_from_empty", - "fully_qualified_name": "unit.test_recent_file_container.test_add_from_empty", - "guid": "a2ff69d6-c4184b3-48b4ce9-0c48b5b0b3", - "line_number": 16, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_recent_file_container.gd", - "suite_name": "test_recent_file_container", - "test_name": "test_add_from_empty" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_add_override", - "fully_qualified_name": "unit.test_recent_file_container.test_add_override", - "guid": "e4eeea69-7e7043b-4a1a3bd-e1d9d2ceec", - "line_number": 22, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_recent_file_container.gd", - "suite_name": "test_recent_file_container", - "test_name": "test_add_override" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_create_file", - "fully_qualified_name": "unit.test_recent_file_container.test_create_file", - "guid": "66b76b2f-1514457-19863c7-07ce2f667c", - "line_number": 34, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_recent_file_container.gd", - "suite_name": "test_recent_file_container", - "test_name": "test_create_file" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_load_file_empty", - "fully_qualified_name": "unit.test_recent_file_container.test_load_file_empty", - "guid": "ff1a3d15-7aff44e-38fc6f9-a57397bdfb", - "line_number": 40, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_recent_file_container.gd", - "suite_name": "test_recent_file_container", - "test_name": "test_load_file_empty" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_load_file_exclude_not_exist", - "fully_qualified_name": "unit.test_recent_file_container.test_load_file_exclude_not_exist", - "guid": "ee1a90e2-075e4d4-c8f9e94-27ec9bb350", - "line_number": 45, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_recent_file_container.gd", - "suite_name": "test_recent_file_container", - "test_name": "test_load_file_exclude_not_exist" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_load_file_existing", - "fully_qualified_name": "unit.test_recent_file_container.test_load_file_existing", - "guid": "ed732e73-3f79405-2abdf63-9c5148005a", - "line_number": 57, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_recent_file_container.gd", - "suite_name": "test_recent_file_container", - "test_name": "test_load_file_existing" - }, - { - "@path": "res://addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd", - "@subpath": "", - "assembly_location": "", - "attribute_index": -1, - "display_name": "test_backwards_compatibility", - "fully_qualified_name": "unit.test_sentence_node.test_backwards_compatibility", - "guid": "2e03e022-65954f3-989932f-d198ecced7", - "line_number": 4, - "metadata": { - - }, - "require_godot_runtime": true, - "source_file": "res://unit/test_sentence_node.gd", - "suite_name": "test_sentence_node", - "test_name": "test_backwards_compatibility" - } - ], - "version": "5.0" -} \ No newline at end of file diff --git a/addons/gdUnit4/bin/GdUnitCmdTool.gd.uid b/addons/gdUnit4/bin/GdUnitCmdTool.gd.uid index 94633f65..8a081abb 100644 --- a/addons/gdUnit4/bin/GdUnitCmdTool.gd.uid +++ b/addons/gdUnit4/bin/GdUnitCmdTool.gd.uid @@ -1 +1 @@ -uid://p88p3dkvm32r +uid://bj5p2b3mmvpv2 diff --git a/addons/gdUnit4/bin/GdUnitCopyLog.gd.uid b/addons/gdUnit4/bin/GdUnitCopyLog.gd.uid index 33c3e3f6..b2d5e762 100644 --- a/addons/gdUnit4/bin/GdUnitCopyLog.gd.uid +++ b/addons/gdUnit4/bin/GdUnitCopyLog.gd.uid @@ -1 +1 @@ -uid://qnmevknqtkcr +uid://du3e4ltm5exyw diff --git a/addons/gdUnit4/plugin.cfg b/addons/gdUnit4/plugin.cfg index 45d0af55..4b3cf178 100644 --- a/addons/gdUnit4/plugin.cfg +++ b/addons/gdUnit4/plugin.cfg @@ -3,5 +3,5 @@ name="gdUnit4" description="Unit Testing Framework for Godot Scripts" author="Mike Schulze" -version="5.0.0" +version="5.0.5" script="plugin.gd" diff --git a/addons/gdUnit4/plugin.gd b/addons/gdUnit4/plugin.gd index cefdc412..1a52ef3c 100644 --- a/addons/gdUnit4/plugin.gd +++ b/addons/gdUnit4/plugin.gd @@ -30,8 +30,10 @@ func _enter_tree() -> void: var control := add_control_to_bottom_panel(_gd_console, "gdUnitConsole") @warning_ignore("unsafe_method_access") await _gd_console.setup_update_notification(control) - if GdUnit4CSharpApiLoader.is_dotnet_supported(): + if GdUnit4CSharpApiLoader.is_api_loaded(): prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version()) + else: + prints("No GdUnit4Net found.") # Connect to be notified for script changes to be able to discover new tests GdUnitTestDiscoverGuard.instance() @warning_ignore("return_value_discarded") diff --git a/addons/gdUnit4/plugin.gd.uid b/addons/gdUnit4/plugin.gd.uid index cf0dbb04..c83a8417 100644 --- a/addons/gdUnit4/plugin.gd.uid +++ b/addons/gdUnit4/plugin.gd.uid @@ -1 +1 @@ -uid://bjat85epf7ofm +uid://do7vudxbo4hhc diff --git a/addons/gdUnit4/src/Comparator.gd.uid b/addons/gdUnit4/src/Comparator.gd.uid index e941641d..66137723 100644 --- a/addons/gdUnit4/src/Comparator.gd.uid +++ b/addons/gdUnit4/src/Comparator.gd.uid @@ -1 +1 @@ -uid://cm41y6w7wthm4 +uid://fmchogf6bdf8 diff --git a/addons/gdUnit4/src/Fuzzers.gd.uid b/addons/gdUnit4/src/Fuzzers.gd.uid index c16cd4aa..f48b919c 100644 --- a/addons/gdUnit4/src/Fuzzers.gd.uid +++ b/addons/gdUnit4/src/Fuzzers.gd.uid @@ -1 +1 @@ -uid://p6pp3cntrnd +uid://dut2wofegoono diff --git a/addons/gdUnit4/src/GdUnitArrayAssert.gd.uid b/addons/gdUnit4/src/GdUnitArrayAssert.gd.uid index 532347e8..ab4f79db 100644 --- a/addons/gdUnit4/src/GdUnitArrayAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitArrayAssert.gd.uid @@ -1 +1 @@ -uid://cqcsax3mfwcio +uid://cffjqveit84v4 diff --git a/addons/gdUnit4/src/GdUnitAssert.gd.uid b/addons/gdUnit4/src/GdUnitAssert.gd.uid index 2d716408..ecde72d7 100644 --- a/addons/gdUnit4/src/GdUnitAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitAssert.gd.uid @@ -1 +1 @@ -uid://b2rci48lrkstq +uid://dichuec7mryel diff --git a/addons/gdUnit4/src/GdUnitAwaiter.gd.uid b/addons/gdUnit4/src/GdUnitAwaiter.gd.uid index 32ad46eb..42ce6ae7 100644 --- a/addons/gdUnit4/src/GdUnitAwaiter.gd.uid +++ b/addons/gdUnit4/src/GdUnitAwaiter.gd.uid @@ -1 +1 @@ -uid://dmw2wrfn6hq88 +uid://c0f57re4e7dk2 diff --git a/addons/gdUnit4/src/GdUnitBoolAssert.gd.uid b/addons/gdUnit4/src/GdUnitBoolAssert.gd.uid index e29a69d4..003f37ef 100644 --- a/addons/gdUnit4/src/GdUnitBoolAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitBoolAssert.gd.uid @@ -1 +1 @@ -uid://ba4be0xfbtnmf +uid://dquqkxy1o024t diff --git a/addons/gdUnit4/src/GdUnitConstants.gd.uid b/addons/gdUnit4/src/GdUnitConstants.gd.uid index 456b802b..60811fa4 100644 --- a/addons/gdUnit4/src/GdUnitConstants.gd.uid +++ b/addons/gdUnit4/src/GdUnitConstants.gd.uid @@ -1 +1 @@ -uid://dsfw4040i5wdo +uid://d066hnl6xhys diff --git a/addons/gdUnit4/src/GdUnitDictionaryAssert.gd.uid b/addons/gdUnit4/src/GdUnitDictionaryAssert.gd.uid index bfac4d80..742a07a0 100644 --- a/addons/gdUnit4/src/GdUnitDictionaryAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitDictionaryAssert.gd.uid @@ -1 +1 @@ -uid://difkcajov8fkg +uid://bq1gcsupvqo21 diff --git a/addons/gdUnit4/src/GdUnitFailureAssert.gd.uid b/addons/gdUnit4/src/GdUnitFailureAssert.gd.uid index 9d50f843..766bcf15 100644 --- a/addons/gdUnit4/src/GdUnitFailureAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitFailureAssert.gd.uid @@ -1 +1 @@ -uid://qtqlkea47sy2 +uid://c0ug13vv0w60v diff --git a/addons/gdUnit4/src/GdUnitFileAssert.gd.uid b/addons/gdUnit4/src/GdUnitFileAssert.gd.uid index 7ee6da68..5f5f9340 100644 --- a/addons/gdUnit4/src/GdUnitFileAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitFileAssert.gd.uid @@ -1 +1 @@ -uid://cpangqu3iagtj +uid://ccdvwlsp8gun0 diff --git a/addons/gdUnit4/src/GdUnitFloatAssert.gd.uid b/addons/gdUnit4/src/GdUnitFloatAssert.gd.uid index 9c3983d7..607523bf 100644 --- a/addons/gdUnit4/src/GdUnitFloatAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitFloatAssert.gd.uid @@ -1 +1 @@ -uid://cii6rn31jx3al +uid://bpmnnby4vuhk8 diff --git a/addons/gdUnit4/src/GdUnitFuncAssert.gd.uid b/addons/gdUnit4/src/GdUnitFuncAssert.gd.uid index 87e1e06b..6023a015 100644 --- a/addons/gdUnit4/src/GdUnitFuncAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitFuncAssert.gd.uid @@ -1 +1 @@ -uid://c5xibgs8ux5xy +uid://bkt4ihw5hsa5u diff --git a/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd.uid b/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd.uid index 7a59443c..b948b8d3 100644 --- a/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd.uid @@ -1 +1 @@ -uid://co2g8c7py1k3e +uid://dfqi0y7vrgj10 diff --git a/addons/gdUnit4/src/GdUnitIntAssert.gd.uid b/addons/gdUnit4/src/GdUnitIntAssert.gd.uid index 17e2b81e..ae2ed11d 100644 --- a/addons/gdUnit4/src/GdUnitIntAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitIntAssert.gd.uid @@ -1 +1 @@ -uid://bpfhi1eheni4h +uid://cue1n5kbfpf62 diff --git a/addons/gdUnit4/src/GdUnitObjectAssert.gd.uid b/addons/gdUnit4/src/GdUnitObjectAssert.gd.uid index e86bd11b..ac399915 100644 --- a/addons/gdUnit4/src/GdUnitObjectAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitObjectAssert.gd.uid @@ -1 +1 @@ -uid://d2d3bkxt1wcx7 +uid://c5wxapigqmk5q diff --git a/addons/gdUnit4/src/GdUnitResultAssert.gd.uid b/addons/gdUnit4/src/GdUnitResultAssert.gd.uid index eb8143b2..f690de84 100644 --- a/addons/gdUnit4/src/GdUnitResultAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitResultAssert.gd.uid @@ -1 +1 @@ -uid://4cgv3ss1mryp +uid://dnip2ya5y8ycw diff --git a/addons/gdUnit4/src/GdUnitSceneRunner.gd.uid b/addons/gdUnit4/src/GdUnitSceneRunner.gd.uid index 4457aaa1..513dc150 100644 --- a/addons/gdUnit4/src/GdUnitSceneRunner.gd.uid +++ b/addons/gdUnit4/src/GdUnitSceneRunner.gd.uid @@ -1 +1 @@ -uid://dhdbg6m7ygvou +uid://b3vx3wiljpn0f diff --git a/addons/gdUnit4/src/GdUnitSignalAssert.gd.uid b/addons/gdUnit4/src/GdUnitSignalAssert.gd.uid index f09743f6..7d5da055 100644 --- a/addons/gdUnit4/src/GdUnitSignalAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitSignalAssert.gd.uid @@ -1 +1 @@ -uid://nqq3ony261ch +uid://baq28tquvfpvx diff --git a/addons/gdUnit4/src/GdUnitStringAssert.gd.uid b/addons/gdUnit4/src/GdUnitStringAssert.gd.uid index b90e3a81..03331917 100644 --- a/addons/gdUnit4/src/GdUnitStringAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitStringAssert.gd.uid @@ -1 +1 @@ -uid://dcbgythmvh6g4 +uid://hcqrn03qu1e4 diff --git a/addons/gdUnit4/src/GdUnitTestSuite.gd.uid b/addons/gdUnit4/src/GdUnitTestSuite.gd.uid index faaa948b..a8db01ab 100644 --- a/addons/gdUnit4/src/GdUnitTestSuite.gd.uid +++ b/addons/gdUnit4/src/GdUnitTestSuite.gd.uid @@ -1 +1 @@ -uid://d27xn7ceg8ta1 +uid://c0yh28xahuiht diff --git a/addons/gdUnit4/src/GdUnitTuple.gd.uid b/addons/gdUnit4/src/GdUnitTuple.gd.uid index 9eb02219..4fcd08d7 100644 --- a/addons/gdUnit4/src/GdUnitTuple.gd.uid +++ b/addons/gdUnit4/src/GdUnitTuple.gd.uid @@ -1 +1 @@ -uid://4soxgauogw4g +uid://c16ujl1btfyyb diff --git a/addons/gdUnit4/src/GdUnitValueExtractor.gd.uid b/addons/gdUnit4/src/GdUnitValueExtractor.gd.uid index 9b4d3ee6..6e1921af 100644 --- a/addons/gdUnit4/src/GdUnitValueExtractor.gd.uid +++ b/addons/gdUnit4/src/GdUnitValueExtractor.gd.uid @@ -1 +1 @@ -uid://dus26afrslc7c +uid://4epm64nfbmhg diff --git a/addons/gdUnit4/src/GdUnitVectorAssert.gd.uid b/addons/gdUnit4/src/GdUnitVectorAssert.gd.uid index 7222e571..bb440ddd 100644 --- a/addons/gdUnit4/src/GdUnitVectorAssert.gd.uid +++ b/addons/gdUnit4/src/GdUnitVectorAssert.gd.uid @@ -1 +1 @@ -uid://61ct1w1pqjnk +uid://bkmgw6fy1aohx diff --git a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd.uid b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd.uid index 1a3409a7..995cf47d 100644 --- a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd.uid +++ b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd.uid @@ -1 +1 @@ -uid://d3a4ypfxdkmru +uid://caw2mwxk4y02a diff --git a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd.uid b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd.uid index 727b98e6..6aad89f3 100644 --- a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd.uid +++ b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd.uid @@ -1 +1 @@ -uid://bd0yn1rsh6307 +uid://ceuddy0hobjye diff --git a/addons/gdUnit4/src/asserts/GdAssertMessages.gd.uid b/addons/gdUnit4/src/asserts/GdAssertMessages.gd.uid index a22e8bef..2bb50832 100644 --- a/addons/gdUnit4/src/asserts/GdAssertMessages.gd.uid +++ b/addons/gdUnit4/src/asserts/GdAssertMessages.gd.uid @@ -1 +1 @@ -uid://cc3hdk43hh47 +uid://cce0evdm20w7c diff --git a/addons/gdUnit4/src/asserts/GdAssertReports.gd.uid b/addons/gdUnit4/src/asserts/GdAssertReports.gd.uid index 8660da2b..ce68dea3 100644 --- a/addons/gdUnit4/src/asserts/GdAssertReports.gd.uid +++ b/addons/gdUnit4/src/asserts/GdAssertReports.gd.uid @@ -1 +1 @@ -uid://6kr4lpvi1qwq +uid://cj58yddbde7p8 diff --git a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd.uid index 8fa7f5a1..4bf5edd5 100644 --- a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd.uid @@ -1 +1 @@ -uid://e04hjp5huh74 +uid://blnx4jsud6upv diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd.uid index 974376c9..80a27b41 100644 --- a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd.uid @@ -1 +1 @@ -uid://ciwobc7f6h46r +uid://b8p4grxo0smkx diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd.uid b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd.uid index 009718e3..39df948c 100644 --- a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd.uid @@ -1 +1 @@ -uid://mdemt0sdcelx +uid://bvacky1evj2jv diff --git a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd.uid index 7b1d3294..6b773f41 100644 --- a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd.uid @@ -1 +1 @@ -uid://cuu0go0gd1ul7 +uid://bmktyrxj0k8p2 diff --git a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd.uid index dd8adbfa..9c1a50e7 100644 --- a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd.uid @@ -1 +1 @@ -uid://bwrof6ds0dpxt +uid://dqw4e41njp2l8 diff --git a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd.uid index c0ba7bb3..a4866565 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd.uid @@ -1 +1 @@ -uid://4v5w8jahhm04 +uid://bkh5pmbre7kam diff --git a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd.uid index feea0235..c0e39fc3 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd.uid @@ -1 +1 @@ -uid://cmm7sbdnq6ynv +uid://b0p0dpxafixwg diff --git a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd.uid index a9047db4..f4eb1521 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd.uid @@ -1 +1 @@ -uid://ce0tctonwex1f +uid://cfmvglkttiixi diff --git a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd.uid index 05dc97c0..7e6295f9 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd.uid @@ -1 +1 @@ -uid://d0blnbdubapl2 +uid://dyj6mpqnx8fqh diff --git a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd.uid index 9481101a..7553986f 100644 --- a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd.uid @@ -1 +1 @@ -uid://csgwjvp1srw01 +uid://b28gwfs6x5d3c diff --git a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd.uid index ea042aa2..d361034e 100644 --- a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd.uid @@ -1 +1 @@ -uid://b4wyced4y202u +uid://co0vsefhtjvly diff --git a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd.uid index 2fc264a6..e12163f3 100644 --- a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd.uid @@ -1 +1 @@ -uid://boiiakwr5lbqu +uid://dmg376u3xqyi2 diff --git a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd.uid index af6b7313..b090c457 100644 --- a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd.uid @@ -1 +1 @@ -uid://dbe4cfms6j7tf +uid://bg88ekxp3v4hh diff --git a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd.uid index 13d71b9c..e3faa0e8 100644 --- a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd.uid @@ -1 +1 @@ -uid://dc0nxf3g1q6hg +uid://yqt80k0iet10 diff --git a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd.uid index 454ba1dd..9ee32442 100644 --- a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd.uid @@ -1 +1 @@ -uid://cgc45q2bbnw6h +uid://cnurg4dsh2j51 diff --git a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd.uid index 8d5800d4..6e929720 100644 --- a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd.uid +++ b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd.uid @@ -1 +1 @@ -uid://c8prlu08lq4pw +uid://cb76021730n1g diff --git a/addons/gdUnit4/src/asserts/ValueProvider.gd.uid b/addons/gdUnit4/src/asserts/ValueProvider.gd.uid index 44144cf3..053887b5 100644 --- a/addons/gdUnit4/src/asserts/ValueProvider.gd.uid +++ b/addons/gdUnit4/src/asserts/ValueProvider.gd.uid @@ -1 +1 @@ -uid://dmrnx0ievdw00 +uid://bwqax8vnkbk0t diff --git a/addons/gdUnit4/src/cmd/CmdArgumentParser.gd.uid b/addons/gdUnit4/src/cmd/CmdArgumentParser.gd.uid index d711c4d4..66421f50 100644 --- a/addons/gdUnit4/src/cmd/CmdArgumentParser.gd.uid +++ b/addons/gdUnit4/src/cmd/CmdArgumentParser.gd.uid @@ -1 +1 @@ -uid://dytw8hhfjgrfo +uid://b4lwihq3wr0tw diff --git a/addons/gdUnit4/src/cmd/CmdCommand.gd.uid b/addons/gdUnit4/src/cmd/CmdCommand.gd.uid index 401b3521..e49d6370 100644 --- a/addons/gdUnit4/src/cmd/CmdCommand.gd.uid +++ b/addons/gdUnit4/src/cmd/CmdCommand.gd.uid @@ -1 +1 @@ -uid://c3bmxrdyb2s7c +uid://o40bgvtisayg diff --git a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd.uid b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd.uid index e394020c..28b1f385 100644 --- a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd.uid +++ b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd.uid @@ -1 +1 @@ -uid://bv18vku5wy8dh +uid://dlkcvw2algkqo diff --git a/addons/gdUnit4/src/cmd/CmdOption.gd.uid b/addons/gdUnit4/src/cmd/CmdOption.gd.uid index a6e08ca7..8475e700 100644 --- a/addons/gdUnit4/src/cmd/CmdOption.gd.uid +++ b/addons/gdUnit4/src/cmd/CmdOption.gd.uid @@ -1 +1 @@ -uid://docx18wjoc50t +uid://dnagtjbx1bcqu diff --git a/addons/gdUnit4/src/cmd/CmdOptions.gd.uid b/addons/gdUnit4/src/cmd/CmdOptions.gd.uid index 5efaea1b..60be7c9d 100644 --- a/addons/gdUnit4/src/cmd/CmdOptions.gd.uid +++ b/addons/gdUnit4/src/cmd/CmdOptions.gd.uid @@ -1 +1 @@ -uid://d1rr3hrhxwpcx +uid://w2jlsroiofho diff --git a/addons/gdUnit4/src/core/GdArrayTools.gd.uid b/addons/gdUnit4/src/core/GdArrayTools.gd.uid index d31fe723..6ae859ac 100644 --- a/addons/gdUnit4/src/core/GdArrayTools.gd.uid +++ b/addons/gdUnit4/src/core/GdArrayTools.gd.uid @@ -1 +1 @@ -uid://dysbpliok4jy3 +uid://d30x6br7de16j diff --git a/addons/gdUnit4/src/core/GdDiffTool.gd.uid b/addons/gdUnit4/src/core/GdDiffTool.gd.uid index 39495dfb..4ad8196e 100644 --- a/addons/gdUnit4/src/core/GdDiffTool.gd.uid +++ b/addons/gdUnit4/src/core/GdDiffTool.gd.uid @@ -1 +1 @@ -uid://b15cajmjouv4w +uid://c0lieuv0eejrw diff --git a/addons/gdUnit4/src/core/GdObjects.gd.uid b/addons/gdUnit4/src/core/GdObjects.gd.uid index fb2475d9..aaa94f44 100644 --- a/addons/gdUnit4/src/core/GdObjects.gd.uid +++ b/addons/gdUnit4/src/core/GdObjects.gd.uid @@ -1 +1 @@ -uid://sdoq2leo0wh7 +uid://cbkqqtijh636g diff --git a/addons/gdUnit4/src/core/GdUnit4Version.gd.uid b/addons/gdUnit4/src/core/GdUnit4Version.gd.uid index 597caf56..641370b7 100644 --- a/addons/gdUnit4/src/core/GdUnit4Version.gd.uid +++ b/addons/gdUnit4/src/core/GdUnit4Version.gd.uid @@ -1 +1 @@ -uid://db2lxsj6mco0s +uid://dkxn7forlqv0d diff --git a/addons/gdUnit4/src/core/GdUnitFileAccess.gd.uid b/addons/gdUnit4/src/core/GdUnitFileAccess.gd.uid index 678e5c13..dd714bfd 100644 --- a/addons/gdUnit4/src/core/GdUnitFileAccess.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitFileAccess.gd.uid @@ -1 +1 @@ -uid://c3frtlg7eexve +uid://ctippg01yi8cg diff --git a/addons/gdUnit4/src/core/GdUnitProperty.gd b/addons/gdUnit4/src/core/GdUnitProperty.gd index 138dd9f7..48de036b 100644 --- a/addons/gdUnit4/src/core/GdUnitProperty.gd +++ b/addons/gdUnit4/src/core/GdUnitProperty.gd @@ -31,6 +31,9 @@ func value() -> Variant: return _value +func int_value() -> int: + return _value + func value_as_string() -> String: return _value diff --git a/addons/gdUnit4/src/core/GdUnitProperty.gd.uid b/addons/gdUnit4/src/core/GdUnitProperty.gd.uid index a1ea4b8c..703740de 100644 --- a/addons/gdUnit4/src/core/GdUnitProperty.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitProperty.gd.uid @@ -1 +1 @@ -uid://bvcue10yckc55 +uid://dhuger7fnp4o5 diff --git a/addons/gdUnit4/src/core/GdUnitResult.gd.uid b/addons/gdUnit4/src/core/GdUnitResult.gd.uid index 985e9878..1f77d27f 100644 --- a/addons/gdUnit4/src/core/GdUnitResult.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitResult.gd.uid @@ -1 +1 @@ -uid://diw4j3mxoow71 +uid://bbsofx7xfmyb5 diff --git a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd index 149893f2..9f36354d 100644 --- a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd +++ b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd @@ -70,7 +70,7 @@ func save_config(path: String = CONFIG_FILE) -> GdUnitResult: func load_config(path: String = CONFIG_FILE) -> GdUnitResult: if not FileAccess.file_exists(path): - return GdUnitResult.error("Can't find test runner configuration '%s'! Please select a test to run." % path) + return GdUnitResult.warn("Can't find test runner configuration '%s'! Please select a test to run." % path) var file := FileAccess.open(path, FileAccess.READ) if file == null: var error := FileAccess.get_open_error() diff --git a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd.uid b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd.uid index 3c0608d7..816ec711 100644 --- a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd.uid @@ -1 +1 @@ -uid://8t4ughvls7x4 +uid://chlwqcs7smmml diff --git a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd index c70be022..b8f1c82d 100644 --- a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd +++ b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd @@ -502,7 +502,7 @@ func invoke( arg9: Variant = NO_ARG) -> Variant: var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) if scene().has_method(name): - return scene().callv(name, args) + return await scene().callv(name, args) return "The method '%s' not exist checked loaded scene." % name @@ -569,7 +569,7 @@ func _handle_actions(event: InputEventAction) -> bool: return false __print(" process action %s (%s) <- %s" % [scene(), _scene_name(), event.as_text()]) if event.is_pressed(): - Input.action_press(event.action, InputMap.action_get_deadzone(event.action)) + Input.action_press(event.action, event.get_strength()) else: Input.action_release(event.action) return true diff --git a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd.uid b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd.uid index 66c5dd62..8ef4396e 100644 --- a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd.uid @@ -1 +1 @@ -uid://d28jctgpw3ia4 +uid://ddejpoiwafnsx diff --git a/addons/gdUnit4/src/core/GdUnitSettings.gd.uid b/addons/gdUnit4/src/core/GdUnitSettings.gd.uid index a937b3a4..7b8ab155 100644 --- a/addons/gdUnit4/src/core/GdUnitSettings.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitSettings.gd.uid @@ -1 +1 @@ -uid://b41o5s7w6ruau +uid://kb1fusmpl827 diff --git a/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd.uid b/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd.uid index 5411baae..25b42bb5 100644 --- a/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd.uid @@ -1 +1 @@ -uid://vdafterb2ngh +uid://vprbi1v5gyqb diff --git a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd.uid b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd.uid index ca0929b6..181baa96 100644 --- a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd.uid @@ -1 +1 @@ -uid://cpi5b0k7xnw4j +uid://bfxoxj4i1wdmf diff --git a/addons/gdUnit4/src/core/GdUnitSignals.gd.uid b/addons/gdUnit4/src/core/GdUnitSignals.gd.uid index 4a7fd80d..7d8a1529 100644 --- a/addons/gdUnit4/src/core/GdUnitSignals.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitSignals.gd.uid @@ -1 +1 @@ -uid://c3gfd1at2bm81 +uid://bh6i3ln6vlbrk diff --git a/addons/gdUnit4/src/core/GdUnitSingleton.gd.uid b/addons/gdUnit4/src/core/GdUnitSingleton.gd.uid index 07a94f76..5110f1f5 100644 --- a/addons/gdUnit4/src/core/GdUnitSingleton.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitSingleton.gd.uid @@ -1 +1 @@ -uid://bc5fybsywnuya +uid://coll4y58u7q5i diff --git a/addons/gdUnit4/src/core/GdUnitTestResourceLoader.gd b/addons/gdUnit4/src/core/GdUnitTestResourceLoader.gd index 43618525..33b80e28 100644 --- a/addons/gdUnit4/src/core/GdUnitTestResourceLoader.gd +++ b/addons/gdUnit4/src/core/GdUnitTestResourceLoader.gd @@ -40,7 +40,7 @@ static func load_test_suite_gd(resource_path: String) -> GdUnitTestSuite: static func load_test_suite_cs(resource_path: String) -> Node: - if not GdUnit4CSharpApiLoader.is_dotnet_supported(): + if not GdUnit4CSharpApiLoader.is_api_loaded(): return null var script :Script = ClassDB.instantiate("CSharpScript") script.source_code = GdUnitFileAccess.resource_as_string(resource_path) @@ -50,7 +50,7 @@ static func load_test_suite_cs(resource_path: String) -> Node: static func load_cs_script(resource_path: String, debug_write := false) -> Script: - if not GdUnit4CSharpApiLoader.is_dotnet_supported(): + if not GdUnit4CSharpApiLoader.is_api_loaded(): return null var script :Script = ClassDB.instantiate("CSharpScript") script.source_code = GdUnitFileAccess.resource_as_string(resource_path) diff --git a/addons/gdUnit4/src/core/GdUnitTestResourceLoader.gd.uid b/addons/gdUnit4/src/core/GdUnitTestResourceLoader.gd.uid index 31912a1e..169edf2e 100644 --- a/addons/gdUnit4/src/core/GdUnitTestResourceLoader.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitTestResourceLoader.gd.uid @@ -1 +1 @@ -uid://dtinkm1otfo2y +uid://btteb7dviq513 diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd.uid b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd.uid index a2b1341e..325afc1b 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd.uid @@ -1 +1 @@ -uid://dm1kreoovutly +uid://sutpkyk072xx diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd index b5d27349..654bdc88 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd @@ -224,7 +224,7 @@ static func is_test_suite(script: Script) -> bool: return true stack.push_back(base) elif script != null and script.get_class() == "CSharpScript": - return GdUnit4CSharpApiLoader.is_test_suite(script) + return true return false diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd.uid b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd.uid index f3d0fd6d..5a545073 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd.uid @@ -1 +1 @@ -uid://d05tmi5e02oqi +uid://b7rd1ly2ugi07 diff --git a/addons/gdUnit4/src/core/GdUnitTools.gd.uid b/addons/gdUnit4/src/core/GdUnitTools.gd.uid index 7a94c3ae..6549e888 100644 --- a/addons/gdUnit4/src/core/GdUnitTools.gd.uid +++ b/addons/gdUnit4/src/core/GdUnitTools.gd.uid @@ -1 +1 @@ -uid://bb5gyqgycvniq +uid://coarfyhpqcnc1 diff --git a/addons/gdUnit4/src/core/GodotVersionFixures.gd.uid b/addons/gdUnit4/src/core/GodotVersionFixures.gd.uid index 6c3d5a06..c8605b3d 100644 --- a/addons/gdUnit4/src/core/GodotVersionFixures.gd.uid +++ b/addons/gdUnit4/src/core/GodotVersionFixures.gd.uid @@ -1 +1 @@ -uid://5ihmoi7vjmd +uid://ra53xlpn6c44 diff --git a/addons/gdUnit4/src/core/LocalTime.gd.uid b/addons/gdUnit4/src/core/LocalTime.gd.uid index 9332291f..69637497 100644 --- a/addons/gdUnit4/src/core/LocalTime.gd.uid +++ b/addons/gdUnit4/src/core/LocalTime.gd.uid @@ -1 +1 @@ -uid://bkm1q7yvoxy7j +uid://bjf3dk5qw21wx diff --git a/addons/gdUnit4/src/core/_TestCase.gd.uid b/addons/gdUnit4/src/core/_TestCase.gd.uid index 15288372..b8cfa927 100644 --- a/addons/gdUnit4/src/core/_TestCase.gd.uid +++ b/addons/gdUnit4/src/core/_TestCase.gd.uid @@ -1 +1 @@ -uid://srhv4b4s7cl6 +uid://cbkonrpx4fo1w diff --git a/addons/gdUnit4/src/core/attributes/TestCaseAttribute.gd.uid b/addons/gdUnit4/src/core/attributes/TestCaseAttribute.gd.uid index 61fc47c9..7f9c7deb 100644 --- a/addons/gdUnit4/src/core/attributes/TestCaseAttribute.gd.uid +++ b/addons/gdUnit4/src/core/attributes/TestCaseAttribute.gd.uid @@ -1 +1 @@ -uid://dgxgnjr51711k +uid://doey67tyoy6xb diff --git a/addons/gdUnit4/src/core/command/GdUnitCommand.gd.uid b/addons/gdUnit4/src/core/command/GdUnitCommand.gd.uid index 41fca155..7c26cc9f 100644 --- a/addons/gdUnit4/src/core/command/GdUnitCommand.gd.uid +++ b/addons/gdUnit4/src/core/command/GdUnitCommand.gd.uid @@ -1 +1 @@ -uid://b5x0oc5ngf8lj +uid://de56aharilx1p diff --git a/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd.uid b/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd.uid index 8c138b6d..43b91fc5 100644 --- a/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd.uid +++ b/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd.uid @@ -1 +1 @@ -uid://c0sifu2vquucy +uid://b5tm2bvrkhtbd diff --git a/addons/gdUnit4/src/core/command/GdUnitShortcut.gd.uid b/addons/gdUnit4/src/core/command/GdUnitShortcut.gd.uid index af3594b3..5c9b76eb 100644 --- a/addons/gdUnit4/src/core/command/GdUnitShortcut.gd.uid +++ b/addons/gdUnit4/src/core/command/GdUnitShortcut.gd.uid @@ -1 +1 @@ -uid://do4egyy4scgfs +uid://dch3r18obmu2h diff --git a/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd.uid b/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd.uid index b0fbd4ee..24372c27 100644 --- a/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd.uid +++ b/addons/gdUnit4/src/core/command/GdUnitShortcutAction.gd.uid @@ -1 +1 @@ -uid://bt74gepjg7ekn +uid://buyllgjg2vyux diff --git a/addons/gdUnit4/src/core/discovery/GdUnitGUID.gd.uid b/addons/gdUnit4/src/core/discovery/GdUnitGUID.gd.uid index 713df158..2eba042f 100644 --- a/addons/gdUnit4/src/core/discovery/GdUnitGUID.gd.uid +++ b/addons/gdUnit4/src/core/discovery/GdUnitGUID.gd.uid @@ -1 +1 @@ -uid://d0151xs4n3iyb +uid://dwssr1bh4i1aw diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd b/addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd index d833e3bb..766f9eab 100644 --- a/addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd @@ -9,6 +9,9 @@ extends RefCounted ## A unique identifier for the test case. Used to track and reference specific test instances. var guid := GdUnitGUID.new() +## The resource path to the test suite +var suite_resource_path: String + ## The name of the test method/function. Should start with "test_" prefix. var test_name: String @@ -52,6 +55,7 @@ var metadata: Dictionary = {} static func from_dict(dict: Dictionary) -> GdUnitTestCase: var test := GdUnitTestCase.new() test.guid = GdUnitGUID.new(str(dict["guid"])) + test.suite_resource_path = dict["suite_resource_path"] if dict.has("suite_resource_path") else dict["source_file"] test.suite_name = dict["managed_type"] test.test_name = dict["test_name"] test.display_name = dict["simple_name"] @@ -67,6 +71,7 @@ static func from_dict(dict: Dictionary) -> GdUnitTestCase: static func to_dict(test: GdUnitTestCase) -> Dictionary: return { "guid": test.guid._guid, + "suite_resource_path": test.suite_resource_path, "managed_type": test.suite_name, "test_name" : test.test_name, "simple_name" : test.display_name, @@ -79,7 +84,7 @@ static func to_dict(test: GdUnitTestCase) -> Dictionary: } -static func from(_source_file: String, _line_number: int, _test_name: String, _attribute_index := -1, _test_parameters := "") -> GdUnitTestCase: +static func from(_suite_resource_path: String, _source_file: String, _line_number: int, _test_name: String, _attribute_index := -1, _test_parameters := "") -> GdUnitTestCase: if(_source_file == null or _source_file.is_empty()): prints(_test_name) @@ -87,13 +92,14 @@ static func from(_source_file: String, _line_number: int, _test_name: String, _a assert(_source_file != null and not _source_file.is_empty(), "Precondition: The parameter 'source_file' is not set") var test := GdUnitTestCase.new() + test.suite_resource_path = _suite_resource_path test.test_name = _test_name test.source_file = _source_file test.line_number = _line_number test.attribute_index = _attribute_index test._build_suite_name() test._build_display_name(_test_parameters) - test._build_fully_qualified_name() + test._build_fully_qualified_name(_suite_resource_path) return test @@ -109,8 +115,8 @@ func _build_display_name(_test_parameters: String) -> void: display_name = "%s:%d (%s)" % [test_name, attribute_index, _test_parameters.trim_prefix("[").trim_suffix("]").replace('"', "'")] -func _build_fully_qualified_name() -> void: - var name_space := source_file.trim_prefix("res://").trim_suffix(".gd").trim_suffix(".cs").replace("/", ".") +func _build_fully_qualified_name(_resource_path: String) -> void: + var name_space := _resource_path.trim_prefix("res://").trim_suffix(".gd").trim_suffix(".cs").replace("/", ".") if attribute_index == -1: fully_qualified_name = "%s.%s" % [name_space, test_name] diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd.uid b/addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd.uid index cee0b72d..da1e037b 100644 --- a/addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd.uid +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestCase.gd.uid @@ -1 +1 @@ -uid://xluthcvn10cq +uid://di77insyh6j75 diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd index f4103787..4f9111d0 100644 --- a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd @@ -151,6 +151,10 @@ func find_test_by_id(id: GdUnitGUID) -> GdUnitTestCase: ## [param script] The test script to analyze[br] ## [param discover_sink] Optional callback for test discovery events func discover(script: Script, discover_sink: Callable = default_discover_sink) -> void: + # Verify the script has no errors before run test discovery + var result := script.reload(true) + if result != OK: + return if _is_debug: _discovered_changes["changed_tests"] = Array([], TYPE_OBJECT, "RefCounted", GdUnitTestCase) diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd.uid b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd.uid index 43ec72d5..46e1b8dd 100644 --- a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd.uid +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd.uid @@ -1 +1 @@ -uid://bjxpngc0i437j +uid://c4o1eyxqtr85k diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverSink.gd.uid b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverSink.gd.uid index 06658467..475228ea 100644 --- a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverSink.gd.uid +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverSink.gd.uid @@ -1 +1 @@ -uid://di3w8b6us6l0p +uid://6lq18dxktabs diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd index f7a672b6..996dd95c 100644 --- a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd @@ -11,6 +11,10 @@ static func run() -> Array[GdUnitTestCase]: var t:= Thread.new() @warning_ignore("return_value_discarded") t.start(func () -> Array[GdUnitTestCase]: + # Loading previous test session + var runner_config := GdUnitRunnerConfig.new() + runner_config.load_config() + var recovered_tests := runner_config.test_cases() var test_suite_directories :PackedStringArray = GdUnitCommandHandler.scan_all_test_directories(GdUnitSettings.test_root_folder()) var scanner := GdUnitTestSuiteScanner.new() @@ -24,11 +28,15 @@ static func run() -> Array[GdUnitTestCase]: await (Engine.get_main_loop() as SceneTree).process_frame for test_suites_script in collected_test_suites: discover_tests(test_suites_script, func(test_case: GdUnitTestCase) -> void: + # Sync test uid from last test session + recover_test_guid(test_case, recovered_tests) collected_tests.append(test_case) GdUnitTestDiscoverSink.discover(test_case) ) console_log_discover_results(collected_tests) + if !recovered_tests.is_empty(): + console_log("Recovery last test session successfully, %d tests restored." % recovered_tests.size(), true) return collected_tests ) # wait unblocked to the tread is finished @@ -40,6 +48,46 @@ static func run() -> Array[GdUnitTestCase]: return test_to_execute +## Restores the last test run session by loading the test run config file and rediscover the tests +static func restore_last_session() -> void: + if GdUnitSettings.is_test_discover_enabled(): + return + + var runner_config := GdUnitRunnerConfig.new() + var result := runner_config.load_config() + # Report possible config loading errors + if result.is_error(): + console_log("Recovery of the last test session failed: %s" % result.error_message(), true) + # If no config file found, skip test recovery + if result.is_warn(): + return + + # If no tests recorded, skip test recovery + var test_cases := runner_config.test_cases() + if test_cases.size() == 0: + return + + # We run the test session restoring in an extra thread so that the main thread is not blocked + var t:= Thread.new() + t.start(func () -> void: + # Do sync the main thread before emit the discovered test suites to the inspector + await (Engine.get_main_loop() as SceneTree).process_frame + console_log("Recovery last test session ..", true) + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverStart.new()) + for test_case in test_cases: + GdUnitTestDiscoverSink.discover(test_case) + GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverEnd.new(0, 0)) + console_log("Recovery last test session successfully, %d tests restored." % test_cases.size(), true) + ) + t.wait_to_finish() + + +static func recover_test_guid(current: GdUnitTestCase, recovered_tests: Array[GdUnitTestCase]) -> void: + for recovered_test in recovered_tests: + if recovered_test.fully_qualified_name == current.fully_qualified_name: + current.guid = recovered_test.guid + + static func console_log_discover_results(tests: Array[GdUnitTestCase]) -> void: var grouped_by_suites := GdArrayTools.group_by(tests, func(test: GdUnitTestCase) -> String: return test.source_file @@ -51,9 +99,10 @@ static func console_log_discover_results(tests: Array[GdUnitTestCase]) -> void: console_log("") -static func console_log(message: String) -> void: +static func console_log(message: String, on_console := false) -> void: prints(message) - #GdUnitSignals.instance().gdunit_message.emit(message) + if on_console: + GdUnitSignals.instance().gdunit_message.emit(message) static func filter_tests(method: Dictionary) -> bool: @@ -81,7 +130,7 @@ static func discover_tests(source_script: Script, discover_sink := default_disco for test_case in resolver.resolve_test_cases(source_script as GDScript): discover_sink.call(test_case) elif source_script.get_class() == "CSharpScript": - if not GdUnit4CSharpApiLoader.is_dotnet_supported(): + if not GdUnit4CSharpApiLoader.is_api_loaded(): return for test_case in GdUnit4CSharpApiLoader.discover_tests(source_script): discover_sink.call(test_case) diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd.uid b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd.uid index 981c3f9b..60028632 100644 --- a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd.uid +++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd.uid @@ -1 +1 @@ -uid://dkh40yvtnqoxw +uid://qubknuaxgms1 diff --git a/addons/gdUnit4/src/core/event/GdUnitEvent.gd.uid b/addons/gdUnit4/src/core/event/GdUnitEvent.gd.uid index 549a202f..e0f4f841 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEvent.gd.uid +++ b/addons/gdUnit4/src/core/event/GdUnitEvent.gd.uid @@ -1 +1 @@ -uid://0bl5sfv34mjl +uid://mi4dinjy4nb8 diff --git a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd.uid b/addons/gdUnit4/src/core/event/GdUnitEventInit.gd.uid index 919c791c..e0fb0946 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd.uid +++ b/addons/gdUnit4/src/core/event/GdUnitEventInit.gd.uid @@ -1 +1 @@ -uid://bc6lj1xsoonhx +uid://durnex2gkjsea diff --git a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd.uid b/addons/gdUnit4/src/core/event/GdUnitEventStop.gd.uid index db9b4a23..d31c5831 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd.uid +++ b/addons/gdUnit4/src/core/event/GdUnitEventStop.gd.uid @@ -1 +1 @@ -uid://bsj6l1y8mebur +uid://bontcauq3i2y3 diff --git a/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverEnd.gd.uid b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverEnd.gd.uid index 141a50b2..79bb2b1b 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverEnd.gd.uid +++ b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverEnd.gd.uid @@ -1 +1 @@ -uid://bpepiow1roltr +uid://i7i8rjh3m30m diff --git a/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverStart.gd.uid b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverStart.gd.uid index 0f87c131..da5e9a03 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverStart.gd.uid +++ b/addons/gdUnit4/src/core/event/GdUnitEventTestDiscoverStart.gd.uid @@ -1 +1 @@ -uid://cvawxva8c1imy +uid://coy02lpj7lgra diff --git a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd.uid b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd.uid index 20171dfc..eb4e8cef 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd.uid +++ b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd.uid @@ -1 +1 @@ -uid://d3efv548pfjn3 +uid://cybn5vv8g6ij4 diff --git a/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd.uid b/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd.uid index 272d4ebb..4f6c1f83 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd.uid +++ b/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd.uid @@ -1 +1 @@ -uid://befegid5bg08s +uid://bbiwtyyvfpv74 diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd.uid b/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd.uid index 009974b7..e8942d0f 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd.uid +++ b/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd.uid @@ -1 +1 @@ -uid://r6m4c3af0jvf +uid://546waj3gx7jv diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd index 1e1c08f9..b97f6fcd 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd +++ b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd @@ -23,9 +23,9 @@ func execute(test_suite :GdUnitTestSuite) -> void: func run_and_wait(tests: Array[GdUnitTestCase]) -> void: - # first we group all tests by his parent suite + # first we group all tests by resource path var grouped_by_suites := GdArrayTools.group_by(tests, func(test: GdUnitTestCase) -> String: - return test.source_file + return test.suite_resource_path ) var scanner := GdUnitTestSuiteScanner.new() for suite_path: String in grouped_by_suites.keys(): diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd.uid b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd.uid index a893e9b8..dc1bfb07 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd.uid +++ b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd.uid @@ -1 +1 @@ -uid://byu4stkw4742q +uid://cpopn8eaay2cn diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd.uid index 11b9385b..9ba20046 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd.uid @@ -1 +1 @@ -uid://s5oftq7357lm +uid://cvta7ko1501fd diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd.uid index 9e433536..0783bdc4 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd.uid @@ -1 +1 @@ -uid://xpwskm1g6xa7 +uid://ddgonfohcnc71 diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd.uid index d6cbf4bb..ee180cf6 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd.uid @@ -1 +1 @@ -uid://d3ih04g8adile +uid://bvc28jl2py5uv diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd.uid index 5d7ec4bd..c9a2c8be 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd.uid @@ -1 +1 @@ -uid://bk7tpgycds4bi +uid://c65ts615srhu4 diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd.uid index a1153692..5942fb12 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd.uid @@ -1 +1 @@ -uid://dqowxiqnu8fdt +uid://3o45uqg6xq32 diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd.uid index a65d54cb..35d6581d 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd.uid @@ -1 +1 @@ -uid://c4bbcbwcgesbb +uid://d1uxwd33mjg3t diff --git a/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd.uid index 5b7733a3..b46b611a 100644 --- a/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd.uid @@ -1 +1 @@ -uid://c8homl26jnl3 +uid://dddrb70m1i27q diff --git a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd.uid index 35baec26..847a4336 100644 --- a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd.uid @@ -1 +1 @@ -uid://dwnhibi8y605u +uid://bw02toxonv7i5 diff --git a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd.uid index 1f5ec810..996e12ac 100644 --- a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd.uid @@ -1 +1 @@ -uid://bwl53ncmxcqnf +uid://cw0d366wx8i3n diff --git a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd.uid index 8aab3be1..dbe711b0 100644 --- a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd.uid @@ -1 +1 @@ -uid://dudj2wcynwlla +uid://ijkm5vlud50 diff --git a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleTestStage.gd.uid b/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleTestStage.gd.uid index c826c879..4cd87db7 100644 --- a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleTestStage.gd.uid +++ b/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleTestStage.gd.uid @@ -1 +1 @@ -uid://5mf7vxiq00dn +uid://h1vt5ehmfpc4 diff --git a/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd.uid b/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd.uid index 54e24d96..def2ea99 100644 --- a/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd.uid +++ b/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd.uid @@ -1 +1 @@ -uid://cd7lic1hrb5vf +uid://tfw5f2av7ke7 diff --git a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd b/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd index 202926ea..de8c29a6 100644 --- a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd +++ b/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd @@ -255,6 +255,9 @@ static func decode(value: Variant) -> String: @warning_ignore("unsafe_cast") if GdArrayTools.is_type_array(type) and (value as Array).is_empty(): return "" + # For Variant types we need to determine the original type + if type == GdObjects.TYPE_VARIANT: + type = typeof(value) var decoder := _get_value_decoder(type) if decoder == null: push_error("No value decoder registered for type '%d'! Please open a Bug issue at 'https://github.com/MikeSchulze/gdUnit4/issues/new/choose'." % type) @@ -267,6 +270,9 @@ static func decode(value: Variant) -> String: static func decode_typed(type: int, value: Variant) -> String: if value == null: return "null" + # For Variant types we need to determine the original type + if type == GdObjects.TYPE_VARIANT: + type = typeof(value) var decoder := _get_value_decoder(type) if decoder == null: push_error("No value decoder registered for type '%d'! Please open a Bug issue at 'https://github.com/MikeSchulze/gdUnit4/issues/new/choose'." % type) diff --git a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd.uid b/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd.uid index 7a16a92d..ed3167f3 100644 --- a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd.uid +++ b/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd.uid @@ -1 +1 @@ -uid://bvcgb8ww4rpy1 +uid://ovt8lkvlngkq diff --git a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd index 087cf408..1cc27dbf 100644 --- a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd +++ b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd @@ -57,7 +57,7 @@ func set_value(value: String) -> void: if _type == TYPE_NIL or _type == GdObjects.TYPE_VARIANT: _type = _extract_value_type(value) - if _type == GdObjects.TYPE_VARIANT: + if _type == GdObjects.TYPE_VARIANT and _default_value == null: _default_value = value if _default_value == null: match _type: @@ -131,7 +131,7 @@ func _to_string() -> String: s += ": " + GdObjects.type_as_string(_type) if _type_hint != TYPE_NIL: s += "[%s]" % GdObjects.type_as_string(_type_hint) - if typeof(_default_value) != TYPE_STRING: + if has_default(): s += "=" + value_as_string() return s diff --git a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd.uid b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd.uid index e8efb74a..30d9ff2c 100644 --- a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd.uid +++ b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd.uid @@ -1 +1 @@ -uid://c5iskcxtyerbi +uid://dnmyxq3ftdweh diff --git a/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd.uid b/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd.uid index 7edf93d8..ef68bd89 100644 --- a/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd.uid +++ b/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd.uid @@ -1 +1 @@ -uid://bbbvxjnaas2n8 +uid://cwmh0qkwy4lsv diff --git a/addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd b/addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd index bb3a11ac..9c45e625 100644 --- a/addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd +++ b/addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd @@ -26,7 +26,7 @@ func _init(fd: GdFunctionDescriptor) -> void: func resolve_test_cases(script: GDScript) -> Array[GdUnitTestCase]: if not is_parameterized(): - return [GdUnitTestCase.from(_fd.source_path(), _fd.line_number(), _fd.name())] + return [GdUnitTestCase.from(script.resource_path, _fd.source_path(), _fd.line_number(), _fd.name())] return extract_test_cases_by_reflection(script) @@ -94,7 +94,7 @@ func extract_test_cases_by_reflection(script: GDScript) -> Array[GdUnitTestCase] # if no parameter set detected we need to resolve it by using reflection if parameter_sets.size() == 0: _is_static = false - return _extract_test_cases_by_reflection(source) + return _extract_test_cases_by_reflection(source, script) else: var test_cases: Array[GdUnitTestCase] = [] var property_names := _extract_property_names(source) @@ -102,7 +102,7 @@ func extract_test_cases_by_reflection(script: GDScript) -> Array[GdUnitTestCase] var parameter_set := parameter_sets[parameter_set_index] _static_sets_by_index[parameter_set_index] = _is_static_parameter_set(parameter_set, property_names) @warning_ignore("return_value_discarded") - test_cases.append(GdUnitTestCase.from(_fd.source_path(), _fd.line_number(), _fd.name(), parameter_set_index, parameter_set)) + test_cases.append(GdUnitTestCase.from(script.resource_path, _fd.source_path(), _fd.line_number(), _fd.name(), parameter_set_index, parameter_set)) parameter_set_index += 1 return test_cases @@ -122,13 +122,13 @@ func _is_static_parameter_set(parameters :String, property_names :PackedStringAr return true -func _extract_test_cases_by_reflection(source: Node) -> Array[GdUnitTestCase]: +func _extract_test_cases_by_reflection(source: Node, script: GDScript) -> Array[GdUnitTestCase]: var parameter_sets := load_parameter_sets(source) var test_cases: Array[GdUnitTestCase] = [] for index in parameter_sets.size(): var parameter_set := str(parameter_sets[index]) @warning_ignore("return_value_discarded") - test_cases.append(GdUnitTestCase.from(_fd.source_path(), _fd.line_number(), _fd.name(), index, parameter_set)) + test_cases.append(GdUnitTestCase.from(script.resource_path, _fd.source_path(), _fd.line_number(), _fd.name(), index, parameter_set)) return test_cases diff --git a/addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd.uid b/addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd.uid index 183d61bd..ae174881 100644 --- a/addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd.uid +++ b/addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd.uid @@ -1 +1 @@ -uid://bk704y2xw4nyd +uid://cr7giifccl6bx diff --git a/addons/gdUnit4/src/core/parse/GdScriptParser.gd b/addons/gdUnit4/src/core/parse/GdScriptParser.gd index 83789361..7f8c7031 100644 --- a/addons/gdUnit4/src/core/parse/GdScriptParser.gd +++ b/addons/gdUnit4/src/core/parse/GdScriptParser.gd @@ -74,6 +74,7 @@ var _regex_clazz_name := GdUnitTools.to_regex("(class) ([a-zA-Z0-9_]+) (extends[ var _regex_strip_comments := GdUnitTools.to_regex("^([^#\"']|'[^']*'|\"[^\"]*\")*\\K#.*") var _scanned_inner_classes := PackedStringArray() var _script_constants := {} +var _is_awaiting := GdUnitTools.to_regex("\\bawait\\s+(?![^\"]*\"[^\"]*$)(?!.*#.*await)") static func to_unix_format(input :String) -> String: @@ -650,14 +651,17 @@ func _enrich_function_descriptor(script: GDScript, fds: Array[GdFunctionDescript func is_func_coroutine(rows :PackedStringArray, index :int) -> bool: var is_coroutine := false for rowIndex in range(index+1, rows.size()): - var input := rows[rowIndex] - is_coroutine = input.contains("await") - if is_coroutine: - return true + var input := rows[rowIndex].strip_edges() + # skip empty lines + if input.is_empty(): + continue var token := next_token(input, 0) # scan until next function if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION: break + + if _is_awaiting.search(input): + return true return is_coroutine diff --git a/addons/gdUnit4/src/core/parse/GdScriptParser.gd.uid b/addons/gdUnit4/src/core/parse/GdScriptParser.gd.uid index 4961e914..5580f706 100644 --- a/addons/gdUnit4/src/core/parse/GdScriptParser.gd.uid +++ b/addons/gdUnit4/src/core/parse/GdScriptParser.gd.uid @@ -1 +1 @@ -uid://b78j43ljfuk77 +uid://dkoi16up7my6t diff --git a/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd.uid b/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd.uid index a45cbb43..a9351687 100644 --- a/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd.uid +++ b/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd.uid @@ -1 +1 @@ -uid://b1yoqtvbi13bp +uid://db0x6bk6p662j diff --git a/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd.uid b/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd.uid index aad421fd..a338cc7d 100644 --- a/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd.uid +++ b/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd.uid @@ -1 +1 @@ -uid://cxrcrn0fwe7u6 +uid://dqtbrkqywyioi diff --git a/addons/gdUnit4/src/core/report/GdUnitReport.gd.uid b/addons/gdUnit4/src/core/report/GdUnitReport.gd.uid index 97b6e99d..3440b552 100644 --- a/addons/gdUnit4/src/core/report/GdUnitReport.gd.uid +++ b/addons/gdUnit4/src/core/report/GdUnitReport.gd.uid @@ -1 +1 @@ -uid://bvq0loao62flo +uid://2uqm256ou7l4 diff --git a/addons/gdUnit4/src/core/runners/GdUnitBaseTestRunner.gd.uid b/addons/gdUnit4/src/core/runners/GdUnitBaseTestRunner.gd.uid index 43b27cef..cc91c2a9 100644 --- a/addons/gdUnit4/src/core/runners/GdUnitBaseTestRunner.gd.uid +++ b/addons/gdUnit4/src/core/runners/GdUnitBaseTestRunner.gd.uid @@ -1 +1 @@ -uid://d067xlq7k011y +uid://diaekfrhgif7n diff --git a/addons/gdUnit4/src/core/runners/GdUnitTestCIRunner.gd.uid b/addons/gdUnit4/src/core/runners/GdUnitTestCIRunner.gd.uid index 6bd3a81e..5859a2d3 100644 --- a/addons/gdUnit4/src/core/runners/GdUnitTestCIRunner.gd.uid +++ b/addons/gdUnit4/src/core/runners/GdUnitTestCIRunner.gd.uid @@ -1 +1 @@ -uid://bj5xoyqhuebbg +uid://bbmp0coocwb6p diff --git a/addons/gdUnit4/src/core/runners/GdUnitTestRunner.gd.uid b/addons/gdUnit4/src/core/runners/GdUnitTestRunner.gd.uid index aff91a66..22b2fcb0 100644 --- a/addons/gdUnit4/src/core/runners/GdUnitTestRunner.gd.uid +++ b/addons/gdUnit4/src/core/runners/GdUnitTestRunner.gd.uid @@ -1 +1 @@ -uid://ck1aurdyu3fdc +uid://dfybw0apor6w6 diff --git a/addons/gdUnit4/src/core/runners/GdUnitTestRunner.tscn b/addons/gdUnit4/src/core/runners/GdUnitTestRunner.tscn index 1da430e4..afffc171 100644 --- a/addons/gdUnit4/src/core/runners/GdUnitTestRunner.tscn +++ b/addons/gdUnit4/src/core/runners/GdUnitTestRunner.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://belidlfknh74r"] -[ext_resource type="Script" uid="uid://cewyamo5wr2xw" path="res://addons/gdUnit4/src/core/runners/GdUnitTestRunner.gd" id="1"] -[ext_resource type="Script" uid="uid://cp5knenan84na" path="res://addons/gdUnit4/src/network/GdUnitTcpClient.gd" id="2"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/core/runners/GdUnitTestRunner.gd" id="1"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/network/GdUnitTcpClient.gd" id="2"] [node name="Control" type="Node"] script = ExtResource("1") diff --git a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd.uid b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd.uid index 8a49357c..8bdf1f42 100644 --- a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd.uid +++ b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteDefaultTemplate.gd.uid @@ -1 +1 @@ -uid://byso8v2terhtt +uid://0mughm34cuxb diff --git a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd.uid b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd.uid index 30c8f638..04df5344 100644 --- a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd.uid +++ b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd.uid @@ -1 +1 @@ -uid://c14u8ppcco1at +uid://u7tl5obo6ss7 diff --git a/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd.uid b/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd.uid index 28f25fcc..66e8f059 100644 --- a/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd.uid +++ b/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd.uid @@ -1 +1 @@ -uid://12h8llwtn2r5 +uid://bllwdumm80bl1 diff --git a/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd.uid b/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd.uid index 58f9b6cf..e5586a7c 100644 --- a/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd.uid +++ b/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd.uid @@ -1 +1 @@ -uid://cwxjlrkeyjqso +uid://b8nbf5wokdtiy diff --git a/addons/gdUnit4/src/core/writers/GdUnitCSIMessageWriter.gd.uid b/addons/gdUnit4/src/core/writers/GdUnitCSIMessageWriter.gd.uid index d3fbc18d..3ecb2f58 100644 --- a/addons/gdUnit4/src/core/writers/GdUnitCSIMessageWriter.gd.uid +++ b/addons/gdUnit4/src/core/writers/GdUnitCSIMessageWriter.gd.uid @@ -1 +1 @@ -uid://dd0l0xtkj75j +uid://ofhpc2ovxde4 diff --git a/addons/gdUnit4/src/core/writers/GdUnitMessageWriter.gd.uid b/addons/gdUnit4/src/core/writers/GdUnitMessageWriter.gd.uid index e5af79ed..880d1ed8 100644 --- a/addons/gdUnit4/src/core/writers/GdUnitMessageWriter.gd.uid +++ b/addons/gdUnit4/src/core/writers/GdUnitMessageWriter.gd.uid @@ -1 +1 @@ -uid://b68mtydadk8xp +uid://ccsi7syb3wtgy diff --git a/addons/gdUnit4/src/core/writers/GdUnitRichTextMessageWriter.gd.uid b/addons/gdUnit4/src/core/writers/GdUnitRichTextMessageWriter.gd.uid index 02a36e64..dc9ce006 100644 --- a/addons/gdUnit4/src/core/writers/GdUnitRichTextMessageWriter.gd.uid +++ b/addons/gdUnit4/src/core/writers/GdUnitRichTextMessageWriter.gd.uid @@ -1 +1 @@ -uid://53rfaenrwp4p +uid://bfxdwhov4a0iv diff --git a/addons/gdUnit4/src/dotnet/GdUnit4CSharpApi.cs b/addons/gdUnit4/src/dotnet/GdUnit4CSharpApi.cs index af8320f4..d62d3da3 100644 --- a/addons/gdUnit4/src/dotnet/GdUnit4CSharpApi.cs +++ b/addons/gdUnit4/src/dotnet/GdUnit4CSharpApi.cs @@ -1,204 +1,176 @@ namespace gdUnit4.addons.gdUnit4.src.dotnet; +#if GDUNIT4NET_API_V5 using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; +using GdUnit4; using GdUnit4.Api; -using GdUnit4.Core.Discovery; using Godot; using Godot.Collections; // GdUnit4 GDScript - C# API wrapper // ReSharper disable once CheckNamespace +public partial class GdUnit4CSharpApi : GdUnit4NetApiGodotBridge +{ + [Signal] + public delegate void ExecutionCompletedEventHandler(); + + private CancellationTokenSource? executionCts; + + public override void _Notification(int what) + { + if (what != NotificationPredelete) + return; + executionCts?.Dispose(); + executionCts = null; + } + + public static bool IsApiLoaded() + => true; + + public static Array DiscoverTests(CSharpScript sourceScript) + { + try + { + // Get the list of test case descriptors from the API + var testCaseDescriptors = DiscoverTestsFromScript(sourceScript); + // Convert each TestCaseDescriptor to a Dictionary + return testCaseDescriptors + .Select(descriptor => new Dictionary + { + ["guid"] = descriptor.Id.ToString(), + ["managed_type"] = descriptor.ManagedType, + ["test_name"] = descriptor.ManagedMethod, + ["source_file"] = sourceScript.ResourcePath, + ["line_number"] = descriptor.LineNumber, + ["attribute_index"] = descriptor.AttributeIndex, + ["require_godot_runtime"] = descriptor.RequireRunningGodotEngine, + ["code_file_path"] = descriptor.CodeFilePath ?? "", + ["simple_name"] = descriptor.SimpleName, + ["fully_qualified_name"] = descriptor.FullyQualifiedName, + ["assembly_location"] = descriptor.AssemblyPath + }) + .Aggregate(new Array(), (array, dict) => + { + array.Add(dict); + return array; + }); + } + catch (Exception e) + { + GD.PrintErr($"Error discovering tests: {e.Message}\n{e.StackTrace}"); + return new Array(); + } + } + + public void ExecuteAsync(Array tests, Callable listener) + { + try + { + // Cancel any ongoing execution + executionCts?.Cancel(); + executionCts?.Dispose(); + + // Create new cancellation token source + executionCts = new CancellationTokenSource(); + + var testSuiteNodes = new List { BuildTestSuiteNodeFrom(tests) }; + ExecuteAsync(testSuiteNodes, listener, executionCts.Token) + .GetAwaiter() + .OnCompleted(() => EmitSignal(SignalName.ExecutionCompleted)); + } + catch (Exception e) + { + GD.PrintErr($"Error executing tests: {e.Message}\n{e.StackTrace}"); + Task.Run(() => { }).GetAwaiter().OnCompleted(() => EmitSignal(SignalName.ExecutionCompleted)); + } + } + + public void CancelExecution() + { + try + { + executionCts?.Cancel(); + } + catch (Exception e) + { + GD.PrintErr($"Error cancelling execution: {e.Message}"); + } + } + + // Convert a set of Tests stored as Dictionaries to TestSuiteNode + // all tests are assigned to a single test suit + internal static TestSuiteNode BuildTestSuiteNodeFrom(Array tests) + { + if (tests.Count == 0) + throw new InvalidOperationException("Cant build 'TestSuiteNode' from an empty test set."); + + // Create a suite ID + var suiteId = Guid.NewGuid(); + var firstTest = tests[0]; + var managedType = firstTest["managed_type"].AsString(); + var assemblyLocation = firstTest["assembly_location"].AsString(); + var sourceFile = firstTest["source_file"].AsString(); + + // Create TestCaseNodes for each test in the suite + var testCaseNodes = tests + .Select(test => new TestCaseNode + { + Id = Guid.Parse(test["guid"].AsString()), + ParentId = suiteId, + ManagedMethod = test["test_name"].AsString(), + LineNumber = test["line_number"].AsInt32(), + AttributeIndex = test["attribute_index"].AsInt32(), + RequireRunningGodotEngine = test["require_godot_runtime"].AsBool() + } + ) + .ToList(); + + return new TestSuiteNode + { + Id = suiteId, + ParentId = Guid.Empty, + ManagedType = managedType, + AssemblyPath = assemblyLocation, + SourceFile = sourceFile, + Tests = testCaseNodes + }; + } +} +#else +using Godot; +using Godot.Collections; + public partial class GdUnit4CSharpApi : RefCounted { - [Signal] - public delegate void ExecutionCompletedEventHandler(); - - private static readonly object LockObject = new(); - - private static Type? apiType; - private static Assembly? gdUnit4Api; - private CancellationTokenSource? executionCts; - - public override void _Notification(int what) - { - if (what != NotificationPredelete) - return; - executionCts?.Dispose(); - executionCts = null; - } - - private static Assembly GetApi() - { - if (gdUnit4Api != null) - return gdUnit4Api; - lock (LockObject) - return gdUnit4Api ??= Assembly.Load("gdUnit4Api"); - } - - private static Type? GetApiType() - { - if (apiType != null) - return apiType; - apiType = GetApi().GetType("GdUnit4.GdUnit4NetApiGodotBridge"); - return apiType; - } - - private static Version? GdUnit4NetVersion() - { - try - { - return GetApi().GetName().Version; - } - catch (Exception) - { - return null; - } - } - - private static T? InvokeApiMethod(string methodName, params object[] args) - { - var method = GetApiType()?.GetMethod(methodName) ?? - throw new MethodAccessException($"Can't invoke method {methodName}"); - return (T?)method.Invoke(null, args); - } - - public static bool FindGdUnit4NetAssembly() - { - try - { - return GetApi().GetType("GdUnit4.GdUnit4NetApiGodotBridge") != null; - } - catch (Exception) - { - return false; - } - } - - public static string Version() - => GdUnit4NetVersion()?.ToString() - ?? "Unknown"; - - public static bool IsTestSuite(CSharpScript script) - => InvokeApiMethod("IsTestSuite", script); - - public static Array DiscoverTests(CSharpScript sourceScript) - { - try - { - // Get the list of test case descriptors from the API - var testCaseDescriptors = InvokeApiMethod>("DiscoverTestsFromScript", sourceScript)!; - // Convert each TestCaseDescriptor to a Dictionary - return testCaseDescriptors - .Select(descriptor => new Dictionary - { - ["guid"] = descriptor.Id.ToString(), - ["managed_type"] = descriptor.ManagedType, - ["test_name"] = descriptor.ManagedMethod, - ["source_file"] = sourceScript.ResourcePath, - ["line_number"] = descriptor.LineNumber, - ["attribute_index"] = descriptor.AttributeIndex, - ["require_godot_runtime"] = descriptor.RequireRunningGodotEngine, - ["code_file_path"] = descriptor.CodeFilePath ?? "", - ["simple_name"] = descriptor.SimpleName, - ["fully_qualified_name"] = descriptor.FullyQualifiedName, - ["assembly_location"] = descriptor.AssemblyPath - }) - .Aggregate(new Array(), (array, dict) => - { - array.Add(dict); - return array; - }); - } - catch (Exception e) - { - GD.PrintErr($"Error discovering tests: {e.Message}\n{e.StackTrace}"); - return new Array(); - } - } - - public void ExecuteAsync(Array tests, Callable listener) - { - try - { - // Cancel any ongoing execution - executionCts?.Cancel(); - executionCts?.Dispose(); - - // Create new cancellation token source - executionCts = new CancellationTokenSource(); - - var testSuiteNodes = new List { BuildTestSuiteNodeFrom(tests) }; - InvokeApiMethod("ExecuteAsync", testSuiteNodes, listener, executionCts.Token)? - .GetAwaiter() - .OnCompleted(() => EmitSignal(SignalName.ExecutionCompleted)); - } - catch (Exception e) - { - GD.PrintErr($"Error executing tests: {e.Message}\n{e.StackTrace}"); - Task.Run(() => { }).GetAwaiter().OnCompleted(() => EmitSignal(SignalName.ExecutionCompleted)); - } - } - - public void CancelExecution() - { - try - { - executionCts?.Cancel(); - } - catch (Exception e) - { - GD.PrintErr($"Error cancelling execution: {e.Message}"); - } - } - - public static Dictionary CreateTestSuite(string sourcePath, int lineNumber, string testSuitePath) - => InvokeApiMethod("CreateTestSuite", sourcePath, lineNumber, testSuitePath)!; - - - // Convert a set of Tests stored as Dictionaries to TestSuiteNode - // all tests are assigned to a single test suit - internal static TestSuiteNode BuildTestSuiteNodeFrom(Array tests) - { - if (tests.Count == 0) - throw new InvalidOperationException("Cant build 'TestSuiteNode' from an empty test set."); - - // Create a suite ID - var suiteId = Guid.NewGuid(); - var firstTest = tests[0]; - var managedType = firstTest["managed_type"].AsString(); - var assemblyLocation = firstTest["assembly_location"].AsString(); - var sourceFile = firstTest["source_file"].AsString(); - - // Create TestCaseNodes for each test in the suite - var testCaseNodes = tests - .Select(test => new TestCaseNode - { - Id = Guid.Parse(test["guid"].AsString()), - ParentId = suiteId, - ManagedMethod = test["test_name"].AsString(), - LineNumber = test["line_number"].AsInt32(), - AttributeIndex = test["attribute_index"].AsInt32(), - RequireRunningGodotEngine = test["require_godot_runtime"].AsBool() - } - ) - .ToList(); - - return new TestSuiteNode - { - Id = suiteId, - ParentId = Guid.Empty, - ManagedType = managedType, - AssemblyPath = assemblyLocation, - SourceFile = sourceFile, - Tests = testCaseNodes - }; - } + [Signal] + public delegate void ExecutionCompletedEventHandler(); + + public static bool IsApiLoaded() + { + GD.PushWarning("No `gdunit4.api` dependency found, check your project dependencies."); + return false; + } + + + public static string Version() + => "Unknown"; + + public static Array DiscoverTests(CSharpScript sourceScript) => new(); + + public void ExecuteAsync(Array tests, Callable listener) + { + } + + public static bool IsTestSuite(CSharpScript script) + => false; + + public static Dictionary CreateTestSuite(string sourcePath, int lineNumber, string testSuitePath) + => new(); } +#endif diff --git a/addons/gdUnit4/src/dotnet/GdUnit4CSharpApiLoader.gd b/addons/gdUnit4/src/dotnet/GdUnit4CSharpApiLoader.gd index 398afbe9..199570c6 100644 --- a/addons/gdUnit4/src/dotnet/GdUnit4CSharpApiLoader.gd +++ b/addons/gdUnit4/src/dotnet/GdUnit4CSharpApiLoader.gd @@ -32,7 +32,7 @@ static var _test_event_listener := TestEventListener.new() ## Returns an instance of the GdUnit4CSharpApi wrapper.[br] ## @return Script: The loaded C# wrapper or null if .NET is not supported static func instance() -> Script: - if not GdUnit4CSharpApiLoader.is_dotnet_supported(): + if not GdUnit4CSharpApiLoader.is_api_loaded(): return null return _gdUnit4NetWrapper @@ -41,7 +41,7 @@ static func instance() -> Script: ## Returns or creates a single instance of the API [br] ## This improves performance by reusing the same object static func api_instance() -> RefCounted: - if _api_instance == null and is_dotnet_supported(): + if _api_instance == null and is_api_loaded(): @warning_ignore("unsafe_method_access") _api_instance = instance().new() return _api_instance @@ -52,14 +52,8 @@ static func is_engine_version_supported(engine_version: int = Engine.get_version ## Checks if the .NET environment is properly configured and available.[br] -## This performs multiple checks:[br] -## 1. Verifies if the wrapper is already loaded[br] -## 2. Confirms Godot has C# support[br] -## 3. Validates the project's C# configuration[br] -## 4. Attempts to load the wrapper and find the GdUnit4 assembly[br] -## ## @return bool: True if .NET is fully supported and the assembly is found -static func is_dotnet_supported() -> bool: +static func is_api_loaded() -> bool: # If the wrapper is already loaded we don't need to check again if _gdUnit4NetWrapper != null: return true @@ -74,13 +68,14 @@ static func is_dotnet_supported() -> bool: # Finally load the wrapper and check if the GdUnit4 assembly can be found _gdUnit4NetWrapper = load("res://addons/gdUnit4/src/dotnet/GdUnit4CSharpApi.cs") - return _gdUnit4NetWrapper != null and _gdUnit4NetWrapper.call("FindGdUnit4NetAssembly") + @warning_ignore("unsafe_method_access") + return _gdUnit4NetWrapper.IsApiLoaded() ## Returns the version of the GdUnit4 .NET assembly.[br] ## @return String: The version string or "unknown" if .NET is not supported static func version() -> String: - if not GdUnit4CSharpApiLoader.is_dotnet_supported(): + if not GdUnit4CSharpApiLoader.is_api_loaded(): return "unknown" @warning_ignore("unsafe_method_access") return instance().Version() @@ -105,7 +100,7 @@ static func execute(tests: Array[GdUnitTestCase]) -> void: static func create_test_suite(source_path: String, line_number: int, test_suite_path: String) -> GdUnitResult: - if not GdUnit4CSharpApiLoader.is_dotnet_supported(): + if not GdUnit4CSharpApiLoader.is_api_loaded(): return GdUnitResult.error("Can't create test suite. No .NET support found.") @warning_ignore("unsafe_method_access") var result: Dictionary = instance().CreateTestSuite(source_path, line_number, test_suite_path) @@ -114,11 +109,6 @@ static func create_test_suite(source_path: String, line_number: int, test_suite_ return GdUnitResult.success(result) -static func is_test_suite(script: Script) -> bool: - @warning_ignore("unsafe_method_access") - return instance().IsTestSuite(script) - - static func is_csharp_file(resource_path: String) -> bool: var ext := resource_path.get_extension() - return ext == "cs" and GdUnit4CSharpApiLoader.is_dotnet_supported() + return ext == "cs" and GdUnit4CSharpApiLoader.is_api_loaded() diff --git a/addons/gdUnit4/src/dotnet/GdUnit4CSharpApiLoader.gd.uid b/addons/gdUnit4/src/dotnet/GdUnit4CSharpApiLoader.gd.uid index 0d1c56ab..d46ee2c1 100644 --- a/addons/gdUnit4/src/dotnet/GdUnit4CSharpApiLoader.gd.uid +++ b/addons/gdUnit4/src/dotnet/GdUnit4CSharpApiLoader.gd.uid @@ -1 +1 @@ -uid://em6wu2kmyv05 +uid://clbrjt518h411 diff --git a/addons/gdUnit4/src/doubler/CallableDoubler.gd.uid b/addons/gdUnit4/src/doubler/CallableDoubler.gd.uid index 9c937dca..44ccbcc6 100644 --- a/addons/gdUnit4/src/doubler/CallableDoubler.gd.uid +++ b/addons/gdUnit4/src/doubler/CallableDoubler.gd.uid @@ -1 +1 @@ -uid://c44li8m22q2g +uid://bekuscrnacsu8 diff --git a/addons/gdUnit4/src/doubler/GdFunctionDoubler.gd b/addons/gdUnit4/src/doubler/GdFunctionDoubler.gd index 892e65c8..090b71df 100644 --- a/addons/gdUnit4/src/doubler/GdFunctionDoubler.gd +++ b/addons/gdUnit4/src/doubler/GdFunctionDoubler.gd @@ -200,8 +200,12 @@ static func typeless_args(descriptor: GdFunctionDescriptor) -> String: var collect := PackedStringArray() for arg in descriptor.args(): if arg.has_default(): - @warning_ignore("return_value_discarded") - collect.push_back(arg.name() + "_" + "=" + arg.value_as_string()) + # For Variant types we need to enforce the type in the signature + if arg.type() == GdObjects.TYPE_VARIANT: + collect.push_back("%s_:%s=%s" % [arg.name(), GdObjects.type_as_string(arg.type()), arg.value_as_string()]) + else: + @warning_ignore("return_value_discarded") + collect.push_back("%s_=%s" % [arg.name(), arg.value_as_string()]) else: @warning_ignore("return_value_discarded") collect.push_back(arg.name() + "_") diff --git a/addons/gdUnit4/src/doubler/GdFunctionDoubler.gd.uid b/addons/gdUnit4/src/doubler/GdFunctionDoubler.gd.uid index e0c3e24e..158d6f6a 100644 --- a/addons/gdUnit4/src/doubler/GdFunctionDoubler.gd.uid +++ b/addons/gdUnit4/src/doubler/GdFunctionDoubler.gd.uid @@ -1 +1 @@ -uid://c7snbahh8hwm3 +uid://hdyhj2fbcegc diff --git a/addons/gdUnit4/src/doubler/GdUnitClassDoubler.gd.uid b/addons/gdUnit4/src/doubler/GdUnitClassDoubler.gd.uid index c85f07a2..7b2ae3e0 100644 --- a/addons/gdUnit4/src/doubler/GdUnitClassDoubler.gd.uid +++ b/addons/gdUnit4/src/doubler/GdUnitClassDoubler.gd.uid @@ -1 +1 @@ -uid://by7gx6owx6r0u +uid://dliu4g7unhwj5 diff --git a/addons/gdUnit4/src/doubler/GdUnitObjectInteractions.gd.uid b/addons/gdUnit4/src/doubler/GdUnitObjectInteractions.gd.uid index 742a23df..8b646db9 100644 --- a/addons/gdUnit4/src/doubler/GdUnitObjectInteractions.gd.uid +++ b/addons/gdUnit4/src/doubler/GdUnitObjectInteractions.gd.uid @@ -1 +1 @@ -uid://bx7ng06gj6xt2 +uid://ct4khumjijdfm diff --git a/addons/gdUnit4/src/doubler/GdUnitObjectInteractionsVerifier.gd.uid b/addons/gdUnit4/src/doubler/GdUnitObjectInteractionsVerifier.gd.uid index 3db11b0b..80928c20 100644 --- a/addons/gdUnit4/src/doubler/GdUnitObjectInteractionsVerifier.gd.uid +++ b/addons/gdUnit4/src/doubler/GdUnitObjectInteractionsVerifier.gd.uid @@ -1 +1 @@ -uid://7ni6p5fmj303 +uid://peakocc268o5 diff --git a/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd.uid b/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd.uid index 2503e518..66656317 100644 --- a/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd.uid +++ b/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd.uid @@ -1 +1 @@ -uid://bnykspgpnndrk +uid://drpfbnkhhiue diff --git a/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd.uid b/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd.uid index 9b5ac982..ba66d779 100644 --- a/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd.uid +++ b/addons/gdUnit4/src/fuzzers/FloatFuzzer.gd.uid @@ -1 +1 @@ -uid://d05keunlyb13g +uid://b7y4t0ub35p13 diff --git a/addons/gdUnit4/src/fuzzers/Fuzzer.gd.uid b/addons/gdUnit4/src/fuzzers/Fuzzer.gd.uid index 1d1bc1b1..e72dd2c9 100644 --- a/addons/gdUnit4/src/fuzzers/Fuzzer.gd.uid +++ b/addons/gdUnit4/src/fuzzers/Fuzzer.gd.uid @@ -1 +1 @@ -uid://dn4s1yxhf58rv +uid://e337t4bar8cp diff --git a/addons/gdUnit4/src/fuzzers/IntFuzzer.gd.uid b/addons/gdUnit4/src/fuzzers/IntFuzzer.gd.uid index 73bd8501..cf79c329 100644 --- a/addons/gdUnit4/src/fuzzers/IntFuzzer.gd.uid +++ b/addons/gdUnit4/src/fuzzers/IntFuzzer.gd.uid @@ -1 +1 @@ -uid://bvyh3bnhntg8f +uid://vtek3ibtchre diff --git a/addons/gdUnit4/src/fuzzers/StringFuzzer.gd.uid b/addons/gdUnit4/src/fuzzers/StringFuzzer.gd.uid index d45396a5..398c6405 100644 --- a/addons/gdUnit4/src/fuzzers/StringFuzzer.gd.uid +++ b/addons/gdUnit4/src/fuzzers/StringFuzzer.gd.uid @@ -1 +1 @@ -uid://cmghw5dtcdrxl +uid://dq400wh5q0ilk diff --git a/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd.uid b/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd.uid index 36643e8c..42a623da 100644 --- a/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd.uid +++ b/addons/gdUnit4/src/fuzzers/Vector2Fuzzer.gd.uid @@ -1 +1 @@ -uid://c0nafcj411138 +uid://bg3nrrvm61ptw diff --git a/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd.uid b/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd.uid index cb55b26b..1975a70e 100644 --- a/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd.uid +++ b/addons/gdUnit4/src/fuzzers/Vector3Fuzzer.gd.uid @@ -1 +1 @@ -uid://bdw37s7i5ddgy +uid://b8dicnlqjqvv1 diff --git a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd.uid b/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd.uid index 418aba02..6bd02e24 100644 --- a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd.uid +++ b/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd.uid @@ -1 +1 @@ -uid://8wtpiv1qvpg6 +uid://clgomxdt2lcgv diff --git a/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd.uid b/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd.uid index 54dcb2d0..c2eee052 100644 --- a/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd.uid +++ b/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd.uid @@ -1 +1 @@ -uid://bpvbfh6rscg6b +uid://bg18q33ici5x3 diff --git a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd.uid b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd.uid index c75956c7..98124612 100644 --- a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd.uid +++ b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd.uid @@ -1 +1 @@ -uid://berqup1jifqvo +uid://dvmgtradtmap1 diff --git a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd.uid b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd.uid index 12a810d8..0918179f 100644 --- a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd.uid +++ b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd.uid @@ -1 +1 @@ -uid://bxfddo1mxd7yk +uid://bf7cly10vkif1 diff --git a/addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd.uid b/addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd.uid index 4066bdc8..3c9dbb33 100644 --- a/addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd.uid +++ b/addons/gdUnit4/src/matchers/EqualsArgumentMatcher.gd.uid @@ -1 +1 @@ -uid://dptx538y0iv5 +uid://b5g3fu6ia18v3 diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd.uid b/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd.uid index da99d3a3..f12b045b 100644 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd.uid +++ b/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd.uid @@ -1 +1 @@ -uid://lcfje3tsypup +uid://ca01bqnc2dfi5 diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd.uid b/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd.uid index f0d7dc53..db251d07 100644 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd.uid +++ b/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd.uid @@ -1 +1 @@ -uid://c8voejjm5titg +uid://k5if1ve7sf4f diff --git a/addons/gdUnit4/src/mocking/GdUnitMock.gd.uid b/addons/gdUnit4/src/mocking/GdUnitMock.gd.uid index 9c3e5478..1676f1ee 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMock.gd.uid +++ b/addons/gdUnit4/src/mocking/GdUnitMock.gd.uid @@ -1 +1 @@ -uid://dgc1qam4o7i0i +uid://snhg1g74hjp4 diff --git a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd.uid b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd.uid index f40ca653..2ba58f24 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd.uid +++ b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd.uid @@ -1 +1 @@ -uid://dq56630tdb00j +uid://c0shsnsrusbyd diff --git a/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd.uid b/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd.uid index 7e82b1c1..7e3da141 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd.uid +++ b/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd.uid @@ -1 +1 @@ -uid://jm8onp8m4k25 +uid://dbftl6vpbaswy diff --git a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd.uid b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd.uid index 75e24f16..7d27ffc4 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd.uid +++ b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd.uid @@ -1 +1 @@ -uid://derlwqu7gm3qj +uid://bqq7tk4hvva8f diff --git a/addons/gdUnit4/src/monitor/ErrorLogEntry.gd.uid b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd.uid index 1a235d0d..ca7a1423 100644 --- a/addons/gdUnit4/src/monitor/ErrorLogEntry.gd.uid +++ b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd.uid @@ -1 +1 @@ -uid://dkiel0qlk1gk2 +uid://daopfdh4fej8n diff --git a/addons/gdUnit4/src/monitor/GdUnitMonitor.gd.uid b/addons/gdUnit4/src/monitor/GdUnitMonitor.gd.uid index a5a4a1fa..44588a08 100644 --- a/addons/gdUnit4/src/monitor/GdUnitMonitor.gd.uid +++ b/addons/gdUnit4/src/monitor/GdUnitMonitor.gd.uid @@ -1 +1 @@ -uid://cgjdtqp2vmlvn +uid://dw7kiptih6weg diff --git a/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd.uid b/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd.uid index 18e6fab6..79a1439e 100644 --- a/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd.uid +++ b/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd.uid @@ -1 +1 @@ -uid://c05r34gpp50to +uid://dhlaa1gqwg0rm diff --git a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd index 2ea2234e..b0737051 100644 --- a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd +++ b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd @@ -52,6 +52,20 @@ func erase_log_entry(entry: ErrorLogEntry) -> void: _entries.erase(entry) +func collect_full_logs() -> PackedStringArray: + await (Engine.get_main_loop() as SceneTree).process_frame + await (Engine.get_main_loop() as SceneTree).physics_frame + + var file := FileAccess.open(_godot_log_file, FileAccess.READ) + file.seek(_eof) + var records := PackedStringArray() + while not file.eof_reached(): + @warning_ignore("return_value_discarded") + records.append(file.get_line()) + + return records + + func _collect_log_entries(force_collect_reports: bool) -> Array[ErrorLogEntry]: var file := FileAccess.open(_godot_log_file, FileAccess.READ) file.seek(_eof) diff --git a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd.uid b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd.uid index 99621185..b31d376f 100644 --- a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd.uid +++ b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd.uid @@ -1 +1 @@ -uid://1i2srtcp8q5 +uid://dcuvgoaxonxbc diff --git a/addons/gdUnit4/src/network/GdUnitServer.gd.uid b/addons/gdUnit4/src/network/GdUnitServer.gd.uid index 49b1cb8e..fe80d01f 100644 --- a/addons/gdUnit4/src/network/GdUnitServer.gd.uid +++ b/addons/gdUnit4/src/network/GdUnitServer.gd.uid @@ -1 +1 @@ -uid://d2b4atsva3n6r +uid://cdmq5d5672rdx diff --git a/addons/gdUnit4/src/network/GdUnitServer.tscn b/addons/gdUnit4/src/network/GdUnitServer.tscn index 99620f1e..4dbe8c49 100644 --- a/addons/gdUnit4/src/network/GdUnitServer.tscn +++ b/addons/gdUnit4/src/network/GdUnitServer.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://cn5mp3tmi2gb1"] -[ext_resource type="Script" uid="uid://cw5a5npml5wef" path="res://addons/gdUnit4/src/network/GdUnitServer.gd" id="1"] -[ext_resource type="Script" uid="uid://c7ncb187ucolp" path="res://addons/gdUnit4/src/network/GdUnitTcpServer.gd" id="2"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/network/GdUnitServer.gd" id="1"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/network/GdUnitTcpServer.gd" id="2"] [node name="Control" type="Node"] script = ExtResource("1") diff --git a/addons/gdUnit4/src/network/GdUnitServerConstants.gd.uid b/addons/gdUnit4/src/network/GdUnitServerConstants.gd.uid index a358c1eb..1f7d5624 100644 --- a/addons/gdUnit4/src/network/GdUnitServerConstants.gd.uid +++ b/addons/gdUnit4/src/network/GdUnitServerConstants.gd.uid @@ -1 +1 @@ -uid://jjy8r2hwxssd +uid://cit33bc2qquij diff --git a/addons/gdUnit4/src/network/GdUnitTask.gd.uid b/addons/gdUnit4/src/network/GdUnitTask.gd.uid index b9e21fa8..3ea3fe16 100644 --- a/addons/gdUnit4/src/network/GdUnitTask.gd.uid +++ b/addons/gdUnit4/src/network/GdUnitTask.gd.uid @@ -1 +1 @@ -uid://xe5jb81h1hfm +uid://ccwk7neqtr2sn diff --git a/addons/gdUnit4/src/network/GdUnitTcpClient.gd.uid b/addons/gdUnit4/src/network/GdUnitTcpClient.gd.uid index 6b9a73fe..118d5f3d 100644 --- a/addons/gdUnit4/src/network/GdUnitTcpClient.gd.uid +++ b/addons/gdUnit4/src/network/GdUnitTcpClient.gd.uid @@ -1 +1 @@ -uid://cdqknt40422ej +uid://c38gh6mfpnt83 diff --git a/addons/gdUnit4/src/network/GdUnitTcpNode.gd.uid b/addons/gdUnit4/src/network/GdUnitTcpNode.gd.uid index 272805ba..17912849 100644 --- a/addons/gdUnit4/src/network/GdUnitTcpNode.gd.uid +++ b/addons/gdUnit4/src/network/GdUnitTcpNode.gd.uid @@ -1 +1 @@ -uid://cbkf0u7jrre8m +uid://cjtx84t86jf1g diff --git a/addons/gdUnit4/src/network/GdUnitTcpServer.gd.uid b/addons/gdUnit4/src/network/GdUnitTcpServer.gd.uid index e3d48020..8cee9f96 100644 --- a/addons/gdUnit4/src/network/GdUnitTcpServer.gd.uid +++ b/addons/gdUnit4/src/network/GdUnitTcpServer.gd.uid @@ -1 +1 @@ -uid://b5rg08xa3h6x3 +uid://cauxpc1sb4ptk diff --git a/addons/gdUnit4/src/network/rpc/RPC.gd.uid b/addons/gdUnit4/src/network/rpc/RPC.gd.uid index 7fc771c3..17163402 100644 --- a/addons/gdUnit4/src/network/rpc/RPC.gd.uid +++ b/addons/gdUnit4/src/network/rpc/RPC.gd.uid @@ -1 +1 @@ -uid://b78q0d0k2xhls +uid://cedvqoiapts6d diff --git a/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd.uid b/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd.uid index 74ee5a9c..fb232793 100644 --- a/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd.uid +++ b/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd.uid @@ -1 +1 @@ -uid://dkro11eh12cxm +uid://bwo4g7r6dlqm diff --git a/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd.uid b/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd.uid index e096f60e..0bbae8a2 100644 --- a/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd.uid +++ b/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd.uid @@ -1 +1 @@ -uid://chdib4feehlsb +uid://bt5xyiti7u805 diff --git a/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd.uid b/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd.uid index 23b84604..a7efa394 100644 --- a/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd.uid +++ b/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd.uid @@ -1 +1 @@ -uid://bd5vwo3ck8v4c +uid://cijkq1m75qmmv diff --git a/addons/gdUnit4/src/network/rpc/RPCMessage.gd.uid b/addons/gdUnit4/src/network/rpc/RPCMessage.gd.uid index 8f3132ec..61f5bd4f 100644 --- a/addons/gdUnit4/src/network/rpc/RPCMessage.gd.uid +++ b/addons/gdUnit4/src/network/rpc/RPCMessage.gd.uid @@ -1 +1 @@ -uid://dengpjk16on7n +uid://binsc1d6mcj5i diff --git a/addons/gdUnit4/src/reporters/GdUnitConsoleTestReporter.gd.uid b/addons/gdUnit4/src/reporters/GdUnitConsoleTestReporter.gd.uid index 15b89628..de31a85a 100644 --- a/addons/gdUnit4/src/reporters/GdUnitConsoleTestReporter.gd.uid +++ b/addons/gdUnit4/src/reporters/GdUnitConsoleTestReporter.gd.uid @@ -1 +1 @@ -uid://duffxt287c2xv +uid://yf1lwslmsr1p diff --git a/addons/gdUnit4/src/reporters/GdUnitHtmlTestReporter.gd.uid b/addons/gdUnit4/src/reporters/GdUnitHtmlTestReporter.gd.uid index 1e086a98..f27ff085 100644 --- a/addons/gdUnit4/src/reporters/GdUnitHtmlTestReporter.gd.uid +++ b/addons/gdUnit4/src/reporters/GdUnitHtmlTestReporter.gd.uid @@ -1 +1 @@ -uid://ckpa3m7t165g2 +uid://b8r8buvfmxkc1 diff --git a/addons/gdUnit4/src/reporters/GdUnitTestReporter.gd.uid b/addons/gdUnit4/src/reporters/GdUnitTestReporter.gd.uid index df62c4d9..7478658c 100644 --- a/addons/gdUnit4/src/reporters/GdUnitTestReporter.gd.uid +++ b/addons/gdUnit4/src/reporters/GdUnitTestReporter.gd.uid @@ -1 +1 @@ -uid://cqyx7s50kvgit +uid://bgihlovlje028 diff --git a/addons/gdUnit4/src/reporters/JUnitXmlReport.gd.uid b/addons/gdUnit4/src/reporters/JUnitXmlReport.gd.uid index 08b83e47..b6b68cac 100644 --- a/addons/gdUnit4/src/reporters/JUnitXmlReport.gd.uid +++ b/addons/gdUnit4/src/reporters/JUnitXmlReport.gd.uid @@ -1 +1 @@ -uid://ckt5bbakcyu1m +uid://fyna6rb2ko53 diff --git a/addons/gdUnit4/src/reporters/XmlElement.gd.uid b/addons/gdUnit4/src/reporters/XmlElement.gd.uid index 670bd0ad..1a0d1894 100644 --- a/addons/gdUnit4/src/reporters/XmlElement.gd.uid +++ b/addons/gdUnit4/src/reporters/XmlElement.gd.uid @@ -1 +1 @@ -uid://cq27dcqhpeglp +uid://dr0dtmu570ddy diff --git a/addons/gdUnit4/src/reporters/html/GdUnitByPathReport.gd.uid b/addons/gdUnit4/src/reporters/html/GdUnitByPathReport.gd.uid index c80e9b0f..5d41d20e 100644 --- a/addons/gdUnit4/src/reporters/html/GdUnitByPathReport.gd.uid +++ b/addons/gdUnit4/src/reporters/html/GdUnitByPathReport.gd.uid @@ -1 +1 @@ -uid://yo22m618tcs3 +uid://bxtk8ptaee4sy diff --git a/addons/gdUnit4/src/reporters/html/GdUnitHtmlPatterns.gd.uid b/addons/gdUnit4/src/reporters/html/GdUnitHtmlPatterns.gd.uid index e3e8a692..054298b6 100644 --- a/addons/gdUnit4/src/reporters/html/GdUnitHtmlPatterns.gd.uid +++ b/addons/gdUnit4/src/reporters/html/GdUnitHtmlPatterns.gd.uid @@ -1 +1 @@ -uid://bd6iibim1mjbg +uid://c7sqdlct08bsw diff --git a/addons/gdUnit4/src/reporters/html/GdUnitHtmlReport.gd.uid b/addons/gdUnit4/src/reporters/html/GdUnitHtmlReport.gd.uid index f01d91b3..e835ca32 100644 --- a/addons/gdUnit4/src/reporters/html/GdUnitHtmlReport.gd.uid +++ b/addons/gdUnit4/src/reporters/html/GdUnitHtmlReport.gd.uid @@ -1 +1 @@ -uid://dlwag4dpcgbo8 +uid://dc5qieesw2531 diff --git a/addons/gdUnit4/src/reporters/html/GdUnitReportSummary.gd.uid b/addons/gdUnit4/src/reporters/html/GdUnitReportSummary.gd.uid index 1be2ad00..389904a5 100644 --- a/addons/gdUnit4/src/reporters/html/GdUnitReportSummary.gd.uid +++ b/addons/gdUnit4/src/reporters/html/GdUnitReportSummary.gd.uid @@ -1 +1 @@ -uid://bmdjjeedfuqnf +uid://dy757y4ltjjil diff --git a/addons/gdUnit4/src/reporters/html/GdUnitTestCaseReport.gd.uid b/addons/gdUnit4/src/reporters/html/GdUnitTestCaseReport.gd.uid index 8dab443f..1cf32fac 100644 --- a/addons/gdUnit4/src/reporters/html/GdUnitTestCaseReport.gd.uid +++ b/addons/gdUnit4/src/reporters/html/GdUnitTestCaseReport.gd.uid @@ -1 +1 @@ -uid://coymnbc7qpl3o +uid://6amw43v1lk5r diff --git a/addons/gdUnit4/src/reporters/html/GdUnitTestSuiteReport.gd.uid b/addons/gdUnit4/src/reporters/html/GdUnitTestSuiteReport.gd.uid index 0974ce65..6d3c1c09 100644 --- a/addons/gdUnit4/src/reporters/html/GdUnitTestSuiteReport.gd.uid +++ b/addons/gdUnit4/src/reporters/html/GdUnitTestSuiteReport.gd.uid @@ -1 +1 @@ -uid://cvl6b5i5uhnci +uid://dn118sseqeyl2 diff --git a/addons/gdUnit4/src/reporters/html/template/.gdignore b/addons/gdUnit4/src/reporters/html/template/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd.uid b/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd.uid index d8975049..200d36b4 100644 --- a/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd.uid +++ b/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd.uid @@ -1 +1 @@ -uid://dw53ujxolkr5m +uid://ppqfk5innaal diff --git a/addons/gdUnit4/src/spy/GdUnitSpyFunctionDoubler.gd.uid b/addons/gdUnit4/src/spy/GdUnitSpyFunctionDoubler.gd.uid index d7803326..714bc89d 100644 --- a/addons/gdUnit4/src/spy/GdUnitSpyFunctionDoubler.gd.uid +++ b/addons/gdUnit4/src/spy/GdUnitSpyFunctionDoubler.gd.uid @@ -1 +1 @@ -uid://drnkfb6u0ggpu +uid://w22nuqoq7i4c diff --git a/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd.uid b/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd.uid index 2ac39b47..4b9e7fa0 100644 --- a/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd.uid +++ b/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd.uid @@ -1 +1 @@ -uid://cytpqfrmm0pt3 +uid://dq2ct33cofsyl diff --git a/addons/gdUnit4/src/ui/GdUnitConsole.gd.uid b/addons/gdUnit4/src/ui/GdUnitConsole.gd.uid index 3d53d9db..1aa118ca 100644 --- a/addons/gdUnit4/src/ui/GdUnitConsole.gd.uid +++ b/addons/gdUnit4/src/ui/GdUnitConsole.gd.uid @@ -1 +1 @@ -uid://bc2216ij7fvk1 +uid://2i7agu7i0w68 diff --git a/addons/gdUnit4/src/ui/GdUnitConsole.tscn b/addons/gdUnit4/src/ui/GdUnitConsole.tscn index 924859eb..c3c7e29f 100644 --- a/addons/gdUnit4/src/ui/GdUnitConsole.tscn +++ b/addons/gdUnit4/src/ui/GdUnitConsole.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://dm0wvfyeew7vd"] -[ext_resource type="Script" uid="uid://b0ab52wtehlss" path="res://addons/gdUnit4/src/ui/GdUnitConsole.gd" id="1"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/GdUnitConsole.gd" id="1"] [node name="Control" type="Control"] use_parent_material = true diff --git a/addons/gdUnit4/src/ui/GdUnitFonts.gd.uid b/addons/gdUnit4/src/ui/GdUnitFonts.gd.uid index b5e252dc..939406ef 100644 --- a/addons/gdUnit4/src/ui/GdUnitFonts.gd.uid +++ b/addons/gdUnit4/src/ui/GdUnitFonts.gd.uid @@ -1 +1 @@ -uid://dgf16dlm5ms6k +uid://cao8sc3cwog8q diff --git a/addons/gdUnit4/src/ui/GdUnitInspector.gd.uid b/addons/gdUnit4/src/ui/GdUnitInspector.gd.uid index 9bc99846..c907808b 100644 --- a/addons/gdUnit4/src/ui/GdUnitInspector.gd.uid +++ b/addons/gdUnit4/src/ui/GdUnitInspector.gd.uid @@ -1 +1 @@ -uid://u4ddpnglc1kf +uid://vothtltt41qt diff --git a/addons/gdUnit4/src/ui/GdUnitInspector.tscn b/addons/gdUnit4/src/ui/GdUnitInspector.tscn index 9b5a55fa..54ee9193 100644 --- a/addons/gdUnit4/src/ui/GdUnitInspector.tscn +++ b/addons/gdUnit4/src/ui/GdUnitInspector.tscn @@ -1,12 +1,12 @@ [gd_scene load_steps=8 format=3 uid="uid://mpo5o6d4uybu"] -[ext_resource type="PackedScene" uid="uid://dx7xy4dgi3wwb" path="res://addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn" id="1"] -[ext_resource type="PackedScene" uid="uid://dva3tonxsxrlk" path="res://addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn" id="2"] -[ext_resource type="PackedScene" uid="uid://c22l4odk7qesc" path="res://addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn" id="3"] -[ext_resource type="PackedScene" uid="uid://djp8ait0bxpsc" path="res://addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn" id="4"] -[ext_resource type="Script" uid="uid://crbw6kef7nktv" path="res://addons/gdUnit4/src/ui/GdUnitInspector.gd" id="5"] -[ext_resource type="PackedScene" uid="uid://bqfpidewtpeg0" path="res://addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn" id="7"] -[ext_resource type="PackedScene" uid="uid://cn5mp3tmi2gb1" path="res://addons/gdUnit4/src/network/GdUnitServer.tscn" id="7_721no"] +[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 diff --git a/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd.uid b/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd.uid index 9e51e2d6..105cd856 100644 --- a/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd.uid +++ b/addons/gdUnit4/src/ui/GdUnitInspectorTreeConstants.gd.uid @@ -1 +1 @@ -uid://s3jesnpl5w4 +uid://djkcwschqiupb diff --git a/addons/gdUnit4/src/ui/GdUnitUiTools.gd.uid b/addons/gdUnit4/src/ui/GdUnitUiTools.gd.uid index 68288f5f..5578a48b 100644 --- a/addons/gdUnit4/src/ui/GdUnitUiTools.gd.uid +++ b/addons/gdUnit4/src/ui/GdUnitUiTools.gd.uid @@ -1 +1 @@ -uid://3w6n0vrjwmo8 +uid://htntluoxnikn diff --git a/addons/gdUnit4/src/ui/ScriptEditorControls.gd b/addons/gdUnit4/src/ui/ScriptEditorControls.gd index 5e07fe15..6f3590b2 100644 --- a/addons/gdUnit4/src/ui/ScriptEditorControls.gd +++ b/addons/gdUnit4/src/ui/ScriptEditorControls.gd @@ -54,9 +54,9 @@ static func save_an_open_script(script_path: String, close:=false) -> bool: # select the script in the editor EditorInterface.edit_script(open_script, 0); # save and close - editor_popup.id_pressed.emit(FILE_SAVE) + editor_popup.id_pressed.emit(Field.FILE_SAVE) if close: - editor_popup.id_pressed.emit(FILE_CLOSE) + editor_popup.id_pressed.emit(Field.FILE_CLOSE) return true return false @@ -64,7 +64,7 @@ static func save_an_open_script(script_path: String, close:=false) -> bool: # Saves all opened script static func save_all_open_script() -> void: if Engine.is_editor_hint(): - _menu_popup().id_pressed.emit(FILE_SAVE_ALL) + _menu_popup().id_pressed.emit(Field.FILE_SAVE_ALL) static func close_open_editor_scripts() -> void: diff --git a/addons/gdUnit4/src/ui/ScriptEditorControls.gd.uid b/addons/gdUnit4/src/ui/ScriptEditorControls.gd.uid index e1923cbc..a249f619 100644 --- a/addons/gdUnit4/src/ui/ScriptEditorControls.gd.uid +++ b/addons/gdUnit4/src/ui/ScriptEditorControls.gd.uid @@ -1 +1 @@ -uid://li3m7sxmxbog +uid://cy3jltba1mub4 diff --git a/addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd.uid b/addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd.uid index 67d7dd23..af64f29e 100644 --- a/addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd.uid +++ b/addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd.uid @@ -1 +1 @@ -uid://7fd7477usa3w +uid://bh6aoa1oac8pp diff --git a/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd.uid b/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd.uid index 832ee47e..5a6c0bc5 100644 --- a/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd.uid +++ b/addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd.uid @@ -1 +1 @@ -uid://bsdgpryghi78m +uid://3p1xmguf0a4b diff --git a/addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd.uid b/addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd.uid index 7ba54248..d8224efa 100644 --- a/addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd.uid +++ b/addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd.uid @@ -1 +1 @@ -uid://dix5adosojsc +uid://bo1xnst1353h5 diff --git a/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd.uid b/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd.uid index 908d8e01..c26b6d4a 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd.uid +++ b/addons/gdUnit4/src/ui/parts/InspectorMonitor.gd.uid @@ -1 +1 @@ -uid://crw1egthht6dn +uid://dji3ntx24jxpc diff --git a/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn b/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn index d627bf28..0262b8cd 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn +++ b/addons/gdUnit4/src/ui/parts/InspectorMonitor.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=6 format=3 uid="uid://djp8ait0bxpsc"] -[ext_resource type="Script" uid="uid://dwc45di05dw2s" path="res://addons/gdUnit4/src/ui/parts/InspectorMonitor.gd" id="3"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorMonitor.gd" id="3"] [sub_resource type="Image" id="Image_sx31i"] data = { diff --git a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd.uid b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd.uid index ed4e4d1d..2f120448 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd.uid +++ b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd.uid @@ -1 +1 @@ -uid://b5sffcmr4tagj +uid://b45snkt7caquf diff --git a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn index c21b3504..1824230a 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn +++ b/addons/gdUnit4/src/ui/parts/InspectorProgressBar.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://dva3tonxsxrlk"] -[ext_resource type="Script" uid="uid://clrglsyemovs2" path="res://addons/gdUnit4/src/ui/parts/InspectorProgressBar.gd" id="1"] +[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) diff --git a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd index ee6b3d9b..f2623aeb 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd +++ b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd @@ -18,8 +18,8 @@ signal tree_view_mode_changed(flat :bool) @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: Button = %btn_tree_mode -@onready var _button_sort_mode: Button = %btn_tree_sort +@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 diff --git a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd.uid b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd.uid index 5580160e..6bd8c139 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd.uid +++ b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd.uid @@ -1 +1 @@ -uid://dvrxigojhylsy +uid://o7pbyqyihbvb diff --git a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn index 182338b8..c6848272 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn +++ b/addons/gdUnit4/src/ui/parts/InspectorStatusBar.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=32 format=3 uid="uid://c22l4odk7qesc"] -[ext_resource type="Script" uid="uid://kv33is2tf6np" path="res://addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd" id="3"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorStatusBar.gd" id="3"] [sub_resource type="Image" id="Image_mb3ih"] data = { diff --git a/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd b/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd index 3407048d..e4d99e44 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd +++ b/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd @@ -5,6 +5,8 @@ signal run_overall_pressed(debug: bool) signal run_pressed(debug: bool) signal stop_pressed() +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 @@ -14,7 +16,6 @@ signal stop_pressed() @onready var _button_stop: Button = %stop - const SETTINGS_SHORTCUT_MAPPING := { GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST: GdUnitShortcut.ShortCut.RERUN_TESTS, GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG: GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG, @@ -23,11 +24,16 @@ const SETTINGS_SHORTCUT_MAPPING := { } -@warning_ignore("return_value_discarded") func _ready() -> void: + var inspector :InspectorTreeMainPanel = 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) + run_pressed.connect(inspector._on_run_pressed) + GdUnit4Version.init_version_label(_version_label) var command_handler := GdUnitCommandHandler.instance() - run_pressed.connect(command_handler._on_run_pressed) run_overall_pressed.connect(command_handler._on_run_overall_pressed) stop_pressed.connect(command_handler._on_stop_pressed) command_handler.gdunit_runner_start.connect(_on_gdunit_runner_start) @@ -37,7 +43,6 @@ func _ready() -> void: init_shortcuts(command_handler) - func init_buttons() -> void: _button_run_overall.icon = GdUnitUiTools.get_run_overall_icon() _button_run_overall.visible = GdUnitSettings.is_inspector_toolbar_button_show() @@ -46,6 +51,9 @@ func init_buttons() -> void: _button_stop.icon = GdUnitUiTools.get_icon("Stop") _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_shortcuts(command_handler: GdUnitCommandHandler) -> void: @@ -58,6 +66,12 @@ func init_shortcuts(command_handler: GdUnitCommandHandler) -> void: GdUnitSignals.instance().gdunit_settings_changed.connect(_on_settings_changed.bind(command_handler)) +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_runoverall_pressed(debug:=false) -> void: run_overall_pressed.emit(debug) @@ -79,8 +93,6 @@ func _on_gdunit_runner_start() -> void: func _on_gdunit_runner_stop(_client_id: int) -> void: _button_run_overall.disabled = false - _button_run.disabled = false - _button_run_debug.disabled = false _button_stop.disabled = true diff --git a/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd.uid b/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd.uid index 2addee17..951f7463 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd.uid +++ b/addons/gdUnit4/src/ui/parts/InspectorToolBar.gd.uid @@ -1 +1 @@ -uid://d311g1tcmdbtv +uid://jwd8m4833smh diff --git a/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn b/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn index 210fa9e3..5d75da02 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn +++ b/addons/gdUnit4/src/ui/parts/InspectorToolBar.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=22 format=3 uid="uid://dx7xy4dgi3wwb"] -[ext_resource type="Script" uid="uid://25styuldjg2p" path="res://addons/gdUnit4/src/ui/parts/InspectorToolBar.gd" id="3"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorToolBar.gd" id="3"] [sub_resource type="Image" id="Image_c7rhl"] data = { diff --git a/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd index ac383025..fce16f98 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd +++ b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd @@ -3,6 +3,8 @@ 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) + const CONTEXT_MENU_RUN_ID = 0 const CONTEXT_MENU_DEBUG_ID = 1 @@ -61,8 +63,10 @@ const STATE = GdUnitInspectorTreeConstants.STATE var _tree_root: TreeItem +var _current_selected_item: TreeItem = null var _item_hash := Dictionary() var _current_tree_view_mode := GdUnitSettings.get_inspector_tree_view_mode() +var _run_test_recovery := true func _build_cache_key(resource_path: String, test_name: String) -> Array: @@ -195,6 +199,11 @@ func is_test_id(item: TreeItem, id: GdUnitGUID) -> bool: 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: _context_menu.set_item_icon(CONTEXT_MENU_RUN_ID, GdUnitUiTools.get_icon("Play")) @@ -215,6 +224,8 @@ func _ready() -> void: var command_handler := GdUnitCommandHandler.instance() command_handler.gdunit_runner_start.connect(_on_gdunit_runner_start) command_handler.gdunit_runner_stop.connect(_on_gdunit_runner_stop) + if _run_test_recovery: + GdUnitTestDiscoverer.restore_last_session() # we need current to manually redraw bacause of the animation bug @@ -226,6 +237,7 @@ func _process(_delta: float) -> void: func init_tree() -> void: cleanup_tree() + _tree.deselect_all() _tree.set_hide_root(true) _tree.ensure_cursor_is_visible() _tree.set_allow_reselect(true) @@ -255,6 +267,7 @@ func cleanup_tree() -> void: return _free_recursive() _tree.clear() + _current_selected_item = null func _free_recursive(items:=_tree_root.get_children()) -> void: @@ -263,12 +276,20 @@ func _free_recursive(items:=_tree_root.get_children()) -> void: item.call_deferred("free") -func sort_tree_items(parent :TreeItem) -> void: +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 GdUnitSettings.get_inspector_tree_sort_mode(): + match sort_mode: GdUnitInspectorTreeConstants.SORT_MODE.UNSORTED: items.sort_custom(sort_items_by_original_index) @@ -281,32 +302,42 @@ func sort_tree_items(parent :TreeItem) -> void: GdUnitInspectorTreeConstants.SORT_MODE.EXECUTION_TIME: items.sort_custom(sort_items_by_execution_time) + # readding sorted childs for item in items: - parent.remove_child(item) parent.add_child(item) if item.get_child_count() > 0: - sort_tree_items(item) + _sort_tree_items(item, sort_mode) parent.visible = true - _tree.queue_redraw() -func sort_items_by_name(a: TreeItem, b: TreeItem, ascending: bool) -> bool: +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) - # Compare types first - if type_a != type_b: - return type_a == GdUnitType.FOLDER - 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 if ascending else name_a.naturalnocasecmp_to(name_b) > 0 + # 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) -func sort_items_by_execution_time(a: TreeItem, b: TreeItem) -> bool: + 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) - # Compare types first - if type_a != type_b: - return type_a == GdUnitType.FOLDER + + # 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 @@ -317,13 +348,20 @@ func sort_items_by_execution_time(a: TreeItem, b: TreeItem) -> bool: return execution_time_a > execution_time_b -func sort_items_by_original_index(a: TreeItem, b: TreeItem) -> bool: +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) - if type_a != type_b: - return type_a == GdUnitType.FOLDER + + # 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 @@ -530,11 +568,12 @@ func set_state_running(item: TreeItem) -> void: if parent != _tree_root: set_state_running(parent) # force scrolling to current test case - @warning_ignore("return_value_discarded") - select_item(item) + _tree.scroll_to_item(item, true) func set_state_succeded(item: TreeItem) -> void: + 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) @@ -728,7 +767,7 @@ func show_failed_report(selected_item: TreeItem) -> void: func update_test_suite(event: GdUnitEvent) -> void: var item := _find_tree_item_by_path(extract_resource_path(event), event.suite_name()) if not item: - push_error("[GdUnitInspector:731] Internal Error: Can't find tree item for\n %s" % event) + push_error("[InspectorTreeMainPanel.gd:753] Internal Error: Can't find tree item for\n %s" % event) return if event.type() == GdUnitEvent.TESTSUITE_BEFORE: set_state_running(item) @@ -742,7 +781,7 @@ func update_test_suite(event: GdUnitEvent) -> void: 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()]) + #push_error("Internal Error: Can't find test id %s" % [event.guid()]) return if event.type() == GdUnitEvent.TESTCASE_BEFORE: set_state_running(item) @@ -766,10 +805,10 @@ func create_item(parent: TreeItem, test: GdUnitTestCase, item_name: String, type 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.source_file, test.line_number, test.test_name)) + 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.source_file, test.line_number, test.suite_name)) + item.set_meta(META_TEST_CASE, GdUnitTestCase.from(test.suite_resource_path, test.source_file, test.line_number, test.suite_name)) # We need to add the suite item to the item cache by path because the guid is not provided add_tree_item_to_cache(test.source_file, item_name, item) @@ -1077,7 +1116,7 @@ func _dump_tree_as_json(dump_name: String) -> void: func _to_json(parent :TreeItem) -> Dictionary: var item_as_dict := GdObjects.obj2dict(parent) - item_as_dict["TreeItem"]["childs"] = parent.get_children().map(func(item: TreeItem) -> Dictionary: + item_as_dict["TreeItem"]["childrens"] = parent.get_children().map(func(item: TreeItem) -> Dictionary: return _to_json(item)) return item_as_dict @@ -1124,6 +1163,8 @@ func _on_Tree_item_selected() -> void: if not _context_menu.is_item_disabled(CONTEXT_MENU_RUN_ID): var selected_item: TreeItem = _tree.get_selected() show_failed_report(selected_item) + _current_selected_item = _tree.get_selected() + tree_item_selected.emit(_current_selected_item) # Opens the test suite @@ -1153,7 +1194,7 @@ func _on_Tree_item_activated() -> void: # external signal receiver ################################################################################ func _on_gdunit_runner_start() -> void: - reset_tree_state(_tree_root) + reset_tree_state(_current_selected_item) _context_menu.set_item_disabled(CONTEXT_MENU_RUN_ID, true) _context_menu.set_item_disabled(CONTEXT_MENU_DEBUG_ID, true) clear_reports() @@ -1179,6 +1220,7 @@ func _on_gdunit_event(event: GdUnitEvent) -> void: 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") @@ -1188,6 +1230,7 @@ func _on_gdunit_event(event: GdUnitEvent) -> void: GdUnitEvent.STOP: sort_tree_items(_tree_root) + select_item(_current_selected_item) #_dump_tree_as_json("tree_example") GdUnitEvent.TESTCASE_BEFORE: @@ -1219,7 +1262,7 @@ 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()]) + #_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 index 2da8e436..356d0f16 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd.uid +++ b/addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd.uid @@ -1 +1 @@ -uid://2io2vddkuoqf +uid://b1wycqsf5vf04 diff --git a/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn b/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn index 7d7af9b4..a4247706 100644 --- a/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn +++ b/addons/gdUnit4/src/ui/parts/InspectorTreePanel.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=27 format=3 uid="uid://bqfpidewtpeg0"] -[ext_resource type="Script" uid="uid://dalle60ox3sat" path="res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd" id="1"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd" id="1"] [sub_resource type="Image" id="Image_466oo"] data = { diff --git a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd.uid b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd.uid index 8b2dd786..b123c8f2 100644 --- a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd.uid +++ b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd.uid @@ -1 +1 @@ -uid://djjujnonaiy34 +uid://c3unwpwya10b5 diff --git a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn index 40bdb502..7e62c438 100644 --- a/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn +++ b/addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://pmnkxrhglak5"] -[ext_resource type="Script" uid="uid://birwkunpkmqc4" path="res://addons/gdUnit4/src/ui/settings/GdUnitInputCapture.gd" id="1_gki1u"] +[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) diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd index 4a9a9465..14a8bd5b 100644 --- a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd +++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd @@ -10,15 +10,15 @@ const GdUnitUpdateClient = preload ("res://addons/gdUnit4/src/update/GdUnitUpdat @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: Node = $property_template -@onready var _properties_common: Node = % "common-content" -@onready var _properties_ui: Node = % "ui-content" -@onready var _properties_shortcuts: Node = % "shortcut-content" -@onready var _properties_report: Node = % "report-content" +@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: = %Update +@onready var _update_tab: Control = %Update var _font_size: float @@ -41,7 +41,7 @@ func _sort_by_key(left: GdUnitProperty, right: GdUnitProperty) -> bool: return left.name() < right.name() -func setup_properties(properties_parent: Node, property_category: String) -> void: +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) @@ -66,8 +66,9 @@ func setup_properties(properties_parent: Node, property_category: String) -> voi grid.columns = 4 grid.theme = theme_ - var sub_category: Node = _properties_template.get_child(3).duplicate() - sub_category.get_child(0).text = current_category.capitalize() + 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) @@ -91,7 +92,7 @@ func setup_properties(properties_parent: Node, property_category: String) -> voi @warning_ignore("return_value_discarded") reset_btn.pressed.connect(_on_btn_property_reset_pressed.bind(property, input, reset_btn)) # property help text - var info: Node = _properties_template.get_child(2).duplicate() + var info: Label = _properties_template.get_child(2).duplicate() info.text = property.help() info_labels.append(info) grid.add_child(info) @@ -106,7 +107,6 @@ func setup_properties(properties_parent: Node, property_category: String) -> voi properties_parent.custom_minimum_size.x = min_size_overall -@warning_ignore("return_value_discarded") func _create_input_element(property: GdUnitProperty, reset_btn: Button) -> Node: if property.is_selectable_value(): var options := OptionButton.new() @@ -114,7 +114,7 @@ func _create_input_element(property: GdUnitProperty, reset_btn: Button) -> Node: for value in property.value_set(): options.add_item(value) options.item_selected.connect(_on_option_selected.bind(property, reset_btn)) - options.select(property.value()) + options.select(property.int_value()) return options if property.type() == TYPE_BOOL: var check_btn := CheckButton.new() @@ -131,7 +131,8 @@ func _create_input_element(property: GdUnitProperty, reset_btn: Button) -> Node: return input if property.type() == TYPE_PACKED_INT32_ARRAY: var key_input_button := Button.new() - key_input_button.text = to_shortcut(property.value()) + 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() @@ -150,7 +151,6 @@ func to_shortcut(keys: PackedInt32Array) -> String: return input_event.as_text() -@warning_ignore("return_value_discarded") func to_keys(input_event: InputEventKey) -> PackedInt32Array: var keys := PackedInt32Array() if input_event.ctrl_pressed: @@ -184,8 +184,8 @@ func _install_examples() -> void: 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.code() != 200: - push_warning("Examples cannot be retrieved from GitHub! \n Error code: %d : %s" % [response.code(), response.response()]) + 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() @@ -199,20 +199,28 @@ func _install_examples() -> void: stop_progress() return update_progress("Refresh project") - await rescan(true) + await rescan() + await reimport("res://gdUnit4-examples/") + update_progress("Examples successfully installed") await get_tree().create_timer(3).timeout stop_progress() -func rescan(update_scripts:=false) -> void: - await get_tree().idle_frame +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 - if update_scripts: - EditorInterface.get_resource_filesystem().update_script_classes() + + +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: @@ -252,17 +260,19 @@ func _on_btn_close_pressed() -> void: func _on_btn_property_reset_pressed(property: GdUnitProperty, input: Node, reset_btn: Button) -> void: if input is CheckButton: - input.button_pressed = property.default() + var is_default_pressed: bool = property.default() + (input as CheckButton).button_pressed = is_default_pressed elif input is LineEdit: - input.text = str(property.default()) + (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.select(0) + (input as OptionButton).select(0) _on_option_selected(0, property, reset_btn) elif input is Button: - input.text = to_shortcut(property.default()) - _on_property_text_changed(property.default(), property, reset_btn) + 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: @@ -271,7 +281,7 @@ func _on_property_text_changed(new_value: Variant, property: GdUnitProperty, res var error: Variant = GdUnitSettings.update_property(property) if error: var label: Label = _property_error.get_child(0) as Label - label.set_text(error) + label.set_text(str(error)) var control := gui_get_focus_owner() _property_error.show() if control != null: diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd.uid b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd.uid index 11e3f9e3..49da37f8 100644 --- a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd.uid +++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd.uid @@ -1 +1 @@ -uid://bbmloatmyvou5 +uid://bf71hux0t1te6 diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn index 96f1c0b0..ba732ad1 100644 --- a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn +++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.tscn @@ -1,11 +1,11 @@ [gd_scene load_steps=8 format=3 uid="uid://dwgat6j2u77g4"] -[ext_resource type="Script" uid="uid://dgoxpc1orxs08" path="res://addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd" id="2"] -[ext_resource type="Texture2D" uid="uid://bkh022wwqq7s3" path="res://addons/gdUnit4/src/ui/settings/logo.png" id="3_isfyl"] -[ext_resource type="PackedScene" uid="uid://dte0m2endcgtu" path="res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn" id="4"] -[ext_resource type="PackedScene" uid="uid://0xyeci1tqebj" path="res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn" id="5_n1jtv"] -[ext_resource type="PackedScene" uid="uid://pmnkxrhglak5" path="res://addons/gdUnit4/src/ui/settings/GdUnitInputCapture.tscn" id="5_xu3j8"] -[ext_resource type="Script" uid="uid://ogva4spkrtij" path="res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd" id="8_2ggr0"] +[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/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 diff --git a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd.uid b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd.uid index 94cc662d..7b1034e6 100644 --- a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd.uid +++ b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd.uid @@ -1 +1 @@ -uid://b8ypatydap4v6 +uid://bcnywkpktlnqy diff --git a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn index f0667923..5ccc4430 100644 --- a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn +++ b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://dte0m2endcgtu"] -[ext_resource type="Script" uid="uid://d6ayx5x8dpnw" path="res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd" id="1"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd" id="1"] [node name="TestSuiteTemplate" type="MarginContainer"] anchors_preset = 15 diff --git a/addons/gdUnit4/src/update/GdMarkDownReader.gd b/addons/gdUnit4/src/update/GdMarkDownReader.gd index ce6885de..dab19e40 100644 --- a/addons/gdUnit4/src/update/GdMarkDownReader.gd +++ b/addons/gdUnit4/src/update/GdMarkDownReader.gd @@ -215,7 +215,7 @@ func process_tables(input: String) -> String: return "\n".join(bbcode) -class Table: +class GdUnitMDReaderTable: var _columns: int var _rows: Array[Row] = [] @@ -292,7 +292,7 @@ class Table: func parse_table(lines: Array) -> PackedStringArray: var line: String = lines[0] - var table := Table.new(line.count("|") + 1) + var table := GdUnitMDReaderTable.new(line.count("|") + 1) while not lines.is_empty(): line = lines.pop_front() if not table.parse_row(line): diff --git a/addons/gdUnit4/src/update/GdMarkDownReader.gd.uid b/addons/gdUnit4/src/update/GdMarkDownReader.gd.uid index f8964a21..0d04a4ef 100644 --- a/addons/gdUnit4/src/update/GdMarkDownReader.gd.uid +++ b/addons/gdUnit4/src/update/GdMarkDownReader.gd.uid @@ -1 +1 @@ -uid://c0pbiv6wn5iiu +uid://dj3esct7kbp36 diff --git a/addons/gdUnit4/src/update/GdUnitPatch.gd.uid b/addons/gdUnit4/src/update/GdUnitPatch.gd.uid index 76bf4f50..111ab472 100644 --- a/addons/gdUnit4/src/update/GdUnitPatch.gd.uid +++ b/addons/gdUnit4/src/update/GdUnitPatch.gd.uid @@ -1 +1 @@ -uid://yfl8yfxw73e1 +uid://bxiyaaxrv5obm diff --git a/addons/gdUnit4/src/update/GdUnitPatcher.gd b/addons/gdUnit4/src/update/GdUnitPatcher.gd index 1adae828..73d25c92 100644 --- a/addons/gdUnit4/src/update/GdUnitPatcher.gd +++ b/addons/gdUnit4/src/update/GdUnitPatcher.gd @@ -22,6 +22,7 @@ func _scan(scan_path :String, current :GdUnit4Version) -> void: func patch_count() -> int: var count := 0 for key :String in _patches.keys(): + @warning_ignore("unsafe_method_access") count += _patches[key].size() return count @@ -29,7 +30,7 @@ func patch_count() -> int: func execute() -> void: for key :String in _patches.keys(): for path :String in _patches[key]: - var patch :GdUnitPatch = load(key + "/" + path).new() + var patch :GdUnitPatch = (load(key + "/" + path) as GDScript).new() if patch: prints("execute patch", patch.version(), patch.get_script().resource_path) if not patch.execute(): diff --git a/addons/gdUnit4/src/update/GdUnitPatcher.gd.uid b/addons/gdUnit4/src/update/GdUnitPatcher.gd.uid index c3dd5eac..80781f6c 100644 --- a/addons/gdUnit4/src/update/GdUnitPatcher.gd.uid +++ b/addons/gdUnit4/src/update/GdUnitPatcher.gd.uid @@ -1 +1 @@ -uid://cdcl6oy7t8r2a +uid://cy5bcbbj383x1 diff --git a/addons/gdUnit4/src/update/GdUnitUpdate.gd b/addons/gdUnit4/src/update/GdUnitUpdate.gd index 1b492fe8..ef878151 100644 --- a/addons/gdUnit4/src/update/GdUnitUpdate.gd +++ b/addons/gdUnit4/src/update/GdUnitUpdate.gd @@ -18,7 +18,7 @@ var _download_url :String func _ready() -> void: - init_progress(5) + init_progress(6) func _process(_delta :float) -> void: @@ -56,6 +56,8 @@ func message_h4(message: String, color: Color, show_spinner := true) -> void: if show_spinner: _progress_content.add_image(_spinner_img) _progress_content.append_text(" [font_size=16]%s[/font_size]" % _colored(message, color)) + if _debug_mode: + prints(message) @warning_ignore("return_value_discarded") @@ -91,6 +93,9 @@ func run_update() -> void: else: copy_directory(tmp_path, "res://") + await update_progress("Patch invalid UID's") + await patch_uids() + await update_progress("New GdUnit version successfully installed, Restarting Godot please wait.") await get_tree().create_timer(3).timeout enable_gdUnit() @@ -99,6 +104,60 @@ func run_update() -> void: restart_godot() +func patch_uids(path := "res://addons/gdUnit4/src/") -> void: + var to_reimport: PackedStringArray + for file in DirAccess.get_files_at(path): + var file_path := path.path_join(file) + var ext := file.get_extension() + + if ext == "tscn" or ext == "scn" or ext == "tres" or ext == "res": + message_h4("Patch GdUnit4 scene: '%s'" % file, Color.WEB_GREEN) + remove_uids_from_file(file_path) + elif FileAccess.file_exists(file_path + ".import"): + to_reimport.append(file_path) + + if not to_reimport.is_empty(): + message_h4("Reimport resources '%s'" % ", ".join(to_reimport), Color.WEB_GREEN) + if Engine.is_editor_hint(): + EditorInterface.get_resource_filesystem().reimport_files(to_reimport) + + for dir in DirAccess.get_directories_at(path): + if not dir.begins_with("."): + patch_uids(path.path_join(dir)) + await get_tree().process_frame + + +func remove_uids_from_file(file_path: String) -> bool: + var file := FileAccess.open(file_path, FileAccess.READ) + if file == null: + print("Failed to open file: ", file_path) + return false + + var original_content := file.get_as_text() + file.close() + + # Remove UIDs using regex + var regex := RegEx.new() + regex.compile("(\\[ext_resource[^\\]]*?)\\s+uid=\"uid://[^\"]*\"") + + var modified_content := regex.sub(original_content, "$1", true) + + # Check if any changes were made + if original_content != modified_content: + prints("Patched invalid uid's out in '%s'" % file_path) + # Write the modified content back + file = FileAccess.open(file_path, FileAccess.WRITE) + if file == null: + print("Failed to write to file: ", file_path) + return false + + file.store_string(modified_content) + file.close() + return true + + return false + + func restart_godot() -> void: prints("Force restart Godot") EditorInterface.restart_editor(true) diff --git a/addons/gdUnit4/src/update/GdUnitUpdate.gd.uid b/addons/gdUnit4/src/update/GdUnitUpdate.gd.uid index ebd02b68..6167660c 100644 --- a/addons/gdUnit4/src/update/GdUnitUpdate.gd.uid +++ b/addons/gdUnit4/src/update/GdUnitUpdate.gd.uid @@ -1 +1 @@ -uid://yq8po6gm4kqs +uid://b4820gymnwsmc diff --git a/addons/gdUnit4/src/update/GdUnitUpdate.tscn b/addons/gdUnit4/src/update/GdUnitUpdate.tscn index 3654f7d1..20d60b76 100644 --- a/addons/gdUnit4/src/update/GdUnitUpdate.tscn +++ b/addons/gdUnit4/src/update/GdUnitUpdate.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=6 format=3 uid="uid://2eahgaw88y6q"] -[ext_resource type="Script" uid="uid://cyyxey236wyyr" path="res://addons/gdUnit4/src/update/GdUnitUpdate.gd" id="1"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdate.gd" id="1"] [sub_resource type="Gradient" id="Gradient_wilsr"] colors = PackedColorArray(0.151276, 0.151276, 0.151276, 1, 1, 1, 1, 1) diff --git a/addons/gdUnit4/src/update/GdUnitUpdateClient.gd.uid b/addons/gdUnit4/src/update/GdUnitUpdateClient.gd.uid index 498979e8..5cf7da1e 100644 --- a/addons/gdUnit4/src/update/GdUnitUpdateClient.gd.uid +++ b/addons/gdUnit4/src/update/GdUnitUpdateClient.gd.uid @@ -1 +1 @@ -uid://2lrcgo0tmnlo +uid://dhi05b5jk225i diff --git a/addons/gdUnit4/src/update/GdUnitUpdateNotify.gd.uid b/addons/gdUnit4/src/update/GdUnitUpdateNotify.gd.uid index 4b7e5900..d401f804 100644 --- a/addons/gdUnit4/src/update/GdUnitUpdateNotify.gd.uid +++ b/addons/gdUnit4/src/update/GdUnitUpdateNotify.gd.uid @@ -1 +1 @@ -uid://cje1afd65l33e +uid://w4jy3sjetpin diff --git a/addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn b/addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn index 0878ad47..46119f14 100644 --- a/addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn +++ b/addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=4 format=3 uid="uid://0xyeci1tqebj"] -[ext_resource type="Script" uid="uid://touotnvkoyo" path="res://addons/gdUnit4/src/update/GdUnitUpdateNotify.gd" id="1_112wo"] -[ext_resource type="Script" uid="uid://ogva4spkrtij" path="res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd" id="2_18asx"] -[ext_resource type="PackedScene" uid="uid://2eahgaw88y6q" path="res://addons/gdUnit4/src/update/GdUnitUpdate.tscn" id="3_x87h6"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdateNotify.gd" id="1_112wo"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/update/GdUnitUpdateClient.gd" id="2_18asx"] +[ext_resource type="PackedScene" path="res://addons/gdUnit4/src/update/GdUnitUpdate.tscn" id="3_x87h6"] [node name="Control" type="MarginContainer"] anchors_preset = 15 diff --git a/autoloads/constants.gd b/autoloads/constants.gd index 3ce56ef1..eb926f14 100644 --- a/autoloads/constants.gd +++ b/autoloads/constants.gd @@ -2,24 +2,24 @@ extends Node ## Dictionary of Monologue node types and their corresponding scenes. const NODE_SCENES = { - "Root": preload("res://nodes/root_node/root_node.tscn"), - "Audio": preload("res://nodes/audio_node/audio_node.tscn"), - "Action": preload("res://nodes/action_node/action_node.tscn"), - "Background": preload("res://nodes/background_node/background_node.tscn"), - "Bridge": preload("res://nodes/bridge_in_node/bridge_in_node.tscn"), - "BridgeIn": preload("res://nodes/bridge_in_node/bridge_in_node.tscn"), - "BridgeOut": preload("res://nodes/bridge_out_node/bridge_out_node.tscn"), - "Choice": preload("res://nodes/choice_node/choice_node.tscn"), - "Character": preload("res://nodes/character_node/character_node.tscn"), - "Comment": preload("res://nodes/comment_node/comment_node.tscn"), - "Condition": preload("res://nodes/condition_node/condition_node.tscn"), - "Random": preload("res://nodes/random_node/random_node.tscn"), - "EndPath": preload("res://nodes/end_path_node/end_path_node.tscn"), - "Event": preload("res://nodes/event_node/event_node.tscn"), - "Sentence": preload("res://nodes/sentence_node/sentence_node.tscn"), - "Setter": preload("res://nodes/setter_node/setter_node.tscn"), - "Wait": preload("res://nodes/wait_node/wait_node.tscn"), - "Reroute": preload("res://nodes/reroute_node/reroute_node.tscn") +#"Root": preload("res://nodes/root_node/root_node.tscn"), +#"Audio": preload("res://nodes/audio_node/audio_node.tscn"), +#"Action": preload("res://nodes/action_node/action_node.tscn"), +#"Background": preload("res://nodes/background_node/background_node.tscn"), +#"Bridge": preload("res://nodes/bridge_in_node/bridge_in_node.tscn"), +#"BridgeIn": preload("res://nodes/bridge_in_node/bridge_in_node.tscn"), +#"BridgeOut": preload("res://nodes/bridge_out_node/bridge_out_node.tscn"), +#"Choice": preload("res://nodes/choice_node/choice_node.tscn"), +#"Character": preload("res://nodes/character_node/character_node.tscn"), +#"Comment": preload("res://nodes/comment_node/comment_node.tscn"), +#"Condition": preload("res://nodes/condition_node/condition_node.tscn"), +#"Random": preload("res://nodes/random_node/random_node.tscn"), +#"EndPath": preload("res://nodes/end_path_node/end_path_node.tscn"), +#"Event": preload("res://nodes/event_node/event_node.tscn"), +#"Sentence": preload("res://nodes/sentence_node/sentence_node.tscn"), +#"Setter": preload("res://nodes/setter_node/setter_node.tscn"), +#"Wait": preload("res://nodes/wait_node/wait_node.tscn"), +#"Reroute": preload("res://nodes/reroute_node/reroute_node.tscn") } const HISTORY_PATH = "user://history.save" const PREFERENCES_PATH = "user://preferences.cfg" diff --git a/autoloads/field.gd b/autoloads/field.gd new file mode 100644 index 00000000..61510e14 --- /dev/null +++ b/autoloads/field.gd @@ -0,0 +1 @@ +extends Node diff --git a/autoloads/field.gd.uid b/autoloads/field.gd.uid new file mode 100644 index 00000000..40758538 --- /dev/null +++ b/autoloads/field.gd.uid @@ -0,0 +1 @@ +uid://dx8kqo6gi8c4r diff --git a/autoloads/storyline_manager.gd b/autoloads/storyline_manager.gd new file mode 100644 index 00000000..3d03044e --- /dev/null +++ b/autoloads/storyline_manager.gd @@ -0,0 +1,59 @@ +extends Node + +var _documents: Dictionary = {} +var _active_document_id: String +var _observers: Array = [] + + +func create_storyline(fname: String) -> StorylineDocument: + var doc: StorylineDocument = StorylineDocument.new(fname) + _documents.set(doc.id, doc) + set_active_storyline(doc.id) + notify_change() + return doc + + +func close_storyline(id: String) -> void: + if _documents.get(id).is_dirty: + # Save changes ? + return + _documents.erase(id) + + if _active_document_id == id: + var remaining = _documents.keys() + _active_document_id = remaining[0] + + notify_change() + + +func set_active_storyline(id: String) -> void: + _active_document_id = id + + +func get_storyline(id: String) -> StorylineDocument: + return _documents.get(id, null) + + +func get_storyline_ids() -> Array: + return _documents.keys() + + +func get_active_storyline() -> StorylineDocument: + return _documents.get(_active_document_id) + + +func add_observer(object: Object) -> void: + if object in _observers: + push_warning("Observer is already registered.") + return + + _observers.append(object) + + +func notify_change() -> void: + for observer: Object in _observers: + if not observer.has_method("on_storyline_change"): + push_warning("Object doesn't have method 'on_storyline_change'.") + return + + observer.call("on_storyline_change") diff --git a/autoloads/storyline_manager.gd.uid b/autoloads/storyline_manager.gd.uid new file mode 100644 index 00000000..ed977378 --- /dev/null +++ b/autoloads/storyline_manager.gd.uid @@ -0,0 +1 @@ +uid://dabxmafwjrhkv diff --git a/autoloads/undo_redo_service.gd b/autoloads/undo_redo_service.gd new file mode 100644 index 00000000..1db57981 --- /dev/null +++ b/autoloads/undo_redo_service.gd @@ -0,0 +1,36 @@ +# UndoRedoService +extends Node + +const MAX_STEPS: int = 100 + +var contexts: Dictionary = {} +var active_context_id: String = "" + + +func create_context(context_id: String) -> HistoryHandler: + if contexts.has(context_id): + return contexts[context_id] + + var undo_redo: HistoryHandler = HistoryHandler.new() + undo_redo.max_steps = MAX_STEPS + contexts[context_id] = undo_redo + return undo_redo + + +func get_context(context_id: String) -> HistoryHandler: + return contexts.get(context_id, null) + + +func destroy_context(context_id: String) -> void: + if not contexts.has(context_id): + return + + var undo_redo: HistoryHandler = contexts[context_id] + undo_redo.clear_history() + contexts.erase(undo_redo) + + +func get_active_context() -> HistoryHandler: + if active_context_id.is_empty(): + return null + return get_context(active_context_id) diff --git a/autoloads/undo_redo_service.gd.uid b/autoloads/undo_redo_service.gd.uid new file mode 100644 index 00000000..a7d4ddb5 --- /dev/null +++ b/autoloads/undo_redo_service.gd.uid @@ -0,0 +1 @@ +uid://cl3x7l44eayh3 diff --git a/common/graph_node_row.gd b/common/graph_node_row.gd new file mode 100644 index 00000000..50c3c723 --- /dev/null +++ b/common/graph_node_row.gd @@ -0,0 +1,15 @@ +class_name GraphNodeRow + +var _content: String = "" +var _enable_left_port: bool = false +var _enable_right_port: bool = false + + +func _init(content: String, enable_left_port: bool = false, enable_right_port: bool = true) -> void: + _content = content + _enable_left_port = enable_left_port + _enable_right_port = enable_right_port + + +func get_content() -> String: + return _content diff --git a/common/graph_node_row.gd.uid b/common/graph_node_row.gd.uid new file mode 100644 index 00000000..32da1f7f --- /dev/null +++ b/common/graph_node_row.gd.uid @@ -0,0 +1 @@ +uid://bu0v85m5t527i diff --git a/common/inspectable_object.gd.gd b/common/inspectable_object.gd.gd new file mode 100644 index 00000000..28e28cfd --- /dev/null +++ b/common/inspectable_object.gd.gd @@ -0,0 +1,76 @@ +@abstract +class_name InspectableObject extends RefCounted + +var _properties: Dictionary[String, Property] = {} +var _observers: Array[Object] = [] + + +func _init() -> void: + define_property("id", IDGen.generate(), "text", {}) + + +func define_property( + pname: String, default_value: Variant, type: String, options: Dictionary = {} +) -> void: + var property: Property = Property.new(pname, default_value, type, options) + _properties.set(pname, property) + + property.add_observer( + func(opname: String, old_value: Variant, new_value: Variant) -> void: + _notify_change(opname, old_value, new_value) + _on_property_changed(opname, old_value, new_value) + ) + + +func add_observer(object: Object) -> void: + if object in _observers: + push_warning("Observer is already registered.") + return + + _observers.append(object) + + +func _notify_change(pname: String, old_value: Variant, new_value: Variant) -> void: + for observer: Object in _observers: + if not observer.has_method("on_property_changed"): + push_warning("Object doesn't have method 'on_property_changed'.") + return + + observer.call("on_property_changed", pname, old_value, new_value) + + +func get_properties() -> Array[Property]: + var properties: Array[Property] = [] + for pname in _properties.keys(): + properties.append(_properties[pname]) + + return properties + + +func get_property(pname: String) -> Property: + return _properties.get(pname) + + +func get_property_value(pname: String) -> Variant: + return get_property(pname).get_value() + + +func set_property_value(pname: String, pvalue: Variant) -> void: + if not _properties.has(pname): + return + + var property: Property = get_property(pname) + property.set_value(pvalue) + + +func _to_dict() -> Dictionary: + var dict: Dictionary = {"$type": get_type()} + for property: Property in get_properties(): + dict[property.name] = property.value + + return dict + + +@abstract func get_type() -> String +@abstract func initialize_properties() -> void +@abstract func _on_property_changed(pname: String, old_value: Variant, new_value: Variant) -> void diff --git a/common/inspectable_object.gd.gd.uid b/common/inspectable_object.gd.gd.uid new file mode 100644 index 00000000..d5c2a3e0 --- /dev/null +++ b/common/inspectable_object.gd.gd.uid @@ -0,0 +1 @@ +uid://c7ehitto0jxu3 diff --git a/common/inspectable_storyline_object.gd b/common/inspectable_storyline_object.gd new file mode 100644 index 00000000..b04196cf --- /dev/null +++ b/common/inspectable_storyline_object.gd @@ -0,0 +1,10 @@ +@abstract +class_name InspectableStorylineObject extends InspectableObject + + +func _init() -> void: + define_property("position", Vector2.ZERO, "vector2", {}) + super._init() + + +@abstract func build_graph_preview() -> Array[Control] diff --git a/common/inspectable_storyline_object.gd.uid b/common/inspectable_storyline_object.gd.uid new file mode 100644 index 00000000..6015b309 --- /dev/null +++ b/common/inspectable_storyline_object.gd.uid @@ -0,0 +1 @@ +uid://c4pgnm3s0fk7m diff --git a/common/layouts/character_edit/character_edit.gd b/common/layouts/character_edit/character_edit.gd index 3567a091..c6b574be 100644 --- a/common/layouts/character_edit/character_edit.gd +++ b/common/layouts/character_edit/character_edit.gd @@ -1,8 +1,8 @@ class_name CharacterEdit extends CharacterEditSection -var nicknames := Property.new(MonologueGraphNode.LINE) -var display_name := Property.new(MonologueGraphNode.LINE) -var description := Property.new(MonologueGraphNode.TEXT) +var nicknames := OldProperty.new(Field.LINE) +var display_name := OldProperty.new(Field.LINE) +var description := OldProperty.new(Field.TEXT) func _ready() -> void: diff --git a/common/layouts/character_edit/portrait_list_section.gd b/common/layouts/character_edit/portrait_list_section.gd index 130beee7..7bf54b70 100644 --- a/common/layouts/character_edit/portrait_list_section.gd +++ b/common/layouts/character_edit/portrait_list_section.gd @@ -4,8 +4,8 @@ signal portrait_selected const DEFAULT_PORTRAIT_NAME = "new portrait %s" -var portraits := Property.new(MonologueGraphNode.LIST, {}, []) -var default_portrait := Property.new(MonologueGraphNode.LINE, {}, "") +var portraits := OldProperty.new(Field.LIST, {}, []) +var default_portrait := OldProperty.new(Field.LINE, {}, "") var selected: int = -1 var references: Array[AbstractPortraitOption] = [] diff --git a/common/layouts/character_edit/portrait_settings_section.gd b/common/layouts/character_edit/portrait_settings_section.gd index 075278cb..8f17bea8 100644 --- a/common/layouts/character_edit/portrait_settings_section.gd +++ b/common/layouts/character_edit/portrait_settings_section.gd @@ -3,13 +3,11 @@ class_name PortraitSettingsSection extends PortraitEditSection @warning_ignore("unused_signal") signal changed -var portrait_type := Property.new(MonologueGraphNode.DROPDOWN, {}, "Image", "PortraitType") -var image_path := Property.new( - MonologueGraphNode.FILE, {"filters": FilePicker.IMAGE}, "", "ImagePath" -) -var offset := Property.new(MonologueGraphNode.VECTOR, {}, [0, 0], "Offset") -var mirror := Property.new(MonologueGraphNode.TOGGLE, {}, false, "Mirror") -var one_shot := Property.new(MonologueGraphNode.TOGGLE, {}, false, "OneShot") +var portrait_type := OldProperty.new(Field.DROPDOWN, {}, "Image", "PortraitType") +var image_path := OldProperty.new(Field.FILE, {"filters": FilePicker.IMAGE}, "", "ImagePath") +var offset := OldProperty.new(Field.VECTOR, {}, [0, 0], "Offset") +var mirror := OldProperty.new(Field.TOGGLE, {}, false, "Mirror") +var one_shot := OldProperty.new(Field.TOGGLE, {}, false, "OneShot") @onready var preview_section := %PreviewSection @onready var timeline_section: TimelineSection = %TimelineSection diff --git a/common/layouts/character_edit/timeline_section.gd b/common/layouts/character_edit/timeline_section.gd index bde6dede..cc097ad9 100644 --- a/common/layouts/character_edit/timeline_section.gd +++ b/common/layouts/character_edit/timeline_section.gd @@ -3,7 +3,7 @@ class_name TimelineSection extends PortraitEditSection @warning_ignore("unused_signal") signal changed -var animation := Property.new(MonologueGraphNode.TIMELINE, {}, {}) +var animation := OldProperty.new(Field.TIMELINE, {}, {}) var id: String var base_path: String: diff --git a/common/layouts/dimmer/dimmer.gd b/common/layouts/editor/editor_dimmer.gd similarity index 100% rename from common/layouts/dimmer/dimmer.gd rename to common/layouts/editor/editor_dimmer.gd diff --git a/common/layouts/dimmer/dimmer.gd.uid b/common/layouts/editor/editor_dimmer.gd.uid similarity index 100% rename from common/layouts/dimmer/dimmer.gd.uid rename to common/layouts/editor/editor_dimmer.gd.uid diff --git a/common/layouts/editor/list_section/list_section.gd b/common/layouts/editor/editor_list_section.gd similarity index 80% rename from common/layouts/editor/list_section/list_section.gd rename to common/layouts/editor/editor_list_section.gd index a801c1d5..3d06e794 100644 --- a/common/layouts/editor/list_section/list_section.gd +++ b/common/layouts/editor/editor_list_section.gd @@ -19,9 +19,9 @@ func clear() -> void: func load_items(property: Property) -> void: clear() - property.setters["is_section"] = true - var field := property.show(vbox) - add_func = field._on_add_button_pressed + #property.setters["is_section"] = true + #var field := property.show(vbox) + #add_func = field._on_add_button_pressed func _on_add_button_pressed() -> void: diff --git a/common/layouts/editor/list_section/list_section.gd.uid b/common/layouts/editor/editor_list_section.gd.uid similarity index 100% rename from common/layouts/editor/list_section/list_section.gd.uid rename to common/layouts/editor/editor_list_section.gd.uid diff --git a/common/layouts/editor/list_section/list_section.tscn b/common/layouts/editor/editor_list_section.tscn similarity index 90% rename from common/layouts/editor/list_section/list_section.tscn rename to common/layouts/editor/editor_list_section.tscn index f48a6937..36284efc 100644 --- a/common/layouts/editor/list_section/list_section.tscn +++ b/common/layouts/editor/editor_list_section.tscn @@ -1,12 +1,12 @@ [gd_scene load_steps=3 format=3 uid="uid://mfdu320oy6ex"] -[ext_resource type="Script" uid="uid://yglbu25x1rsy" path="res://common/layouts/editor/list_section/list_section.gd" id="1_coa1v"] +[ext_resource type="Script" uid="uid://yglbu25x1rsy" path="res://common/layouts/editor/editor_list_section.gd" id="1_coa1v"] [ext_resource type="Texture2D" uid="uid://hlck6y4i3l5q" path="res://ui/assets/icons/plus.svg" id="3_j4mj2"] [node name="ListSection" type="PanelContainer"] custom_minimum_size = Vector2(250, 0) -offset_right = 8.0 -offset_bottom = 8.0 +offset_right = 250.0 +offset_bottom = 47.0 script = ExtResource("1_coa1v") [node name="VBox" type="VBoxContainer" parent="."] diff --git a/common/layouts/editor/inspector/inspector_category_container.gd b/common/layouts/editor/inspector/inspector_category_container.gd new file mode 100644 index 00000000..5adf85b8 --- /dev/null +++ b/common/layouts/editor/inspector/inspector_category_container.gd @@ -0,0 +1,11 @@ +extends FoldableContainer + +@onready var _vbox: VBoxContainer = %VBox + + +func add_control( + node: Node, + force_readable_name: bool = false, + internal: InternalMode = InternalMode.INTERNAL_MODE_DISABLED +) -> void: + _vbox.add_child(node, force_readable_name, internal) diff --git a/common/layouts/editor/inspector/inspector_category_container.gd.uid b/common/layouts/editor/inspector/inspector_category_container.gd.uid new file mode 100644 index 00000000..df6a747c --- /dev/null +++ b/common/layouts/editor/inspector/inspector_category_container.gd.uid @@ -0,0 +1 @@ +uid://djpi62hkqu8r6 diff --git a/common/layouts/editor/inspector/inspector_category_container.tscn b/common/layouts/editor/inspector/inspector_category_container.tscn new file mode 100644 index 00000000..8ced7dcc --- /dev/null +++ b/common/layouts/editor/inspector/inspector_category_container.tscn @@ -0,0 +1,13 @@ +[gd_scene load_steps=2 format=3 uid="uid://bvf68w7xrfrom"] + +[ext_resource type="Script" uid="uid://djpi62hkqu8r6" path="res://common/layouts/editor/inspector/inspector_category_container.gd" id="1_d6x5l"] + +[node name="FoldableContainer" type="FoldableContainer"] +offset_right = 24.0 +offset_bottom = 32.0 +title_alignment = 1 +script = ExtResource("1_d6x5l") + +[node name="VBox" type="VBoxContainer" parent="."] +unique_name_in_owner = true +layout_mode = 2 diff --git a/common/layouts/editor/inspector/inspector_panel.gd b/common/layouts/editor/inspector/inspector_panel.gd new file mode 100644 index 00000000..9e4695fe --- /dev/null +++ b/common/layouts/editor/inspector/inspector_panel.gd @@ -0,0 +1,90 @@ +class_name InspectorPanel extends PanelContainer + +@onready var property_container: VBoxContainer = %Fields +@onready var inspector_category_container: PackedScene = preload("uid://bvf68w7xrfrom") + +var current_object: InspectableObject + + +func inspect(object: InspectableObject) -> void: + current_object = object + rebuild() + object.add_observer(self) + + +func rebuild() -> void: + for prop: Control in property_container.get_children(): + prop.queue_free() + + if !current_object: + var label: Label = Label.new() + label.text = "No node selected" + property_container.add_child(label) + + var properties: Array[Property] = current_object.get_properties() + var categories: Dictionary = _group_by_category(properties) + for category_name: String in categories.keys(): + var props = categories[category_name] + _create_category_section(category_name, props) + + +func _group_by_category(properties: Array) -> Dictionary: + var groups: Dictionary = {} + for prop in properties: + var category = "General" + if prop.options.has("category"): + category = prop.options.category + + if not groups.has(category): + groups[category] = [] + groups[category].append(prop) + return groups + + +func _create_category_section(category_name: String, properties: Array) -> void: + var container: Control = inspector_category_container.instantiate() + container.title = category_name + property_container.add_child(container) + + for property: Property in properties: + var property_editor: Control = _create_property_editor(property) + container.add_control(property_editor) + + +func _create_property_editor(property: Property) -> Control: + if _is_list_property(property): + pass # TODO + # return _create_list_property_editor(property) + + var p_container: PanelContainer = PanelContainer.new() + var p_vbox: VBoxContainer = VBoxContainer.new() + var p_label: Label = Label.new() + p_label.text = property.get_display_name() + p_vbox.add_child(p_label) + p_container.add_child(p_vbox) + + # TODO: Input field + return p_container + + +#func _create_list_property_editor(property: Property) -> Control: +#pass +# +# +#func _create_list_item_editor(list_property, item, index: int) -> Control: +#pass +# +# +#func _create_nested_property_editor(property: Property, parent: Control) -> Control: +#pass + + +func _is_list_property(property: Property) -> bool: + return property.type == "list" + + +func on_property_changed(node: InspectableNode, property_name: String) -> void: + if _is_list_property( + node.get_properties().filter(func(p: Property): return p.name == property_name)[0] + ): + rebuild() diff --git a/common/layouts/inspector/inspector_panel.gd.uid b/common/layouts/editor/inspector/inspector_panel.gd.uid similarity index 100% rename from common/layouts/inspector/inspector_panel.gd.uid rename to common/layouts/editor/inspector/inspector_panel.gd.uid diff --git a/common/layouts/inspector/inspector_panel.tscn b/common/layouts/editor/inspector/inspector_panel.tscn similarity index 96% rename from common/layouts/inspector/inspector_panel.tscn rename to common/layouts/editor/inspector/inspector_panel.tscn index e4579b5b..a6e4982d 100644 --- a/common/layouts/inspector/inspector_panel.tscn +++ b/common/layouts/editor/inspector/inspector_panel.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://dgvhvxdrd58qp"] -[ext_resource type="Script" uid="uid://dtf4ge38njewp" path="res://common/layouts/inspector/inspector_panel.gd" id="1_haagr"] +[ext_resource type="Script" uid="uid://dtf4ge38njewp" path="res://common/layouts/editor/inspector/inspector_panel.gd" id="1_haagr"] [ext_resource type="Texture2D" uid="uid://b272tbdmvxj20" path="res://ui/assets/icons/play.svg" id="2_34x8o"] [node name="InspectorPanel" type="PanelContainer"] diff --git a/common/layouts/graph_edit/custom_graph_edit.gd b/common/layouts/graph_edit/custom_graph_edit.gd index f8065c01..a1374cd7 100644 --- a/common/layouts/graph_edit/custom_graph_edit.gd +++ b/common/layouts/graph_edit/custom_graph_edit.gd @@ -5,25 +5,30 @@ var close_button_scene = preload("res://common/ui/buttons/close_button.tscn") var base_options = {} var data: Dictionary var file_path: String -var undo_redo := HistoryHandler.new() -var version = undo_redo.get_version() +var undo_redo: HistoryHandler: + get = get_undo_redo +var context_id: String = IDGen.generate() +var version: int var languages = [] -var active_graphnode: MonologueGraphNode # for tab-switching purpose +#var active_graphnode: MonologueGraphNode # for tab-switching purpose var connecting_mode: bool var current_language_index: int var moving_mode: bool var recorded_positions: Dictionary = {} # for undo/redo positoning purpose -var selected_nodes: Array[MonologueGraphNode] = [] # for group delete +#var selected_nodes: Array[MonologueGraphNode] = [] # for group delete var mouse_hovering: bool = false func _ready() -> void: - var auto_arrange_button = get_menu_hbox().get_children().back() - auto_arrange_button.connect("pressed", _on_auto_arrange_nodes) + UndoRedoService.create_context(context_id) + version = undo_redo.get_version() + + #var auto_arrange_button = get_menu_hbox().get_children().back() + #auto_arrange_button.connect("pressed", _on_auto_arrange_nodes) - center_offset.call_deferred() + #center_offset.call_deferred() # Hide scroll bar for child in get_children(true): @@ -37,25 +42,29 @@ func _ready() -> void: for sb_name in ["grabber", "scroll"]: subchild.add_theme_stylebox_override(sb_name, StyleBoxEmpty.new()) -func _on_add_btn() -> void: - GlobalSignal.emit("select_new_node") +func get_undo_redo() -> HistoryHandler: + return UndoRedoService.get_context(context_id) -func center_offset(): - var base_offset = Vector2.ZERO - var root_node: RootNode = get_root_node() - if root_node: - base_offset = root_node.position_offset + (root_node.size / 2) * zoom - scroll_offset = -size / 2 + base_offset +func _on_add_btn() -> void: + GlobalSignal.emit("select_new_node") -func _input(event: InputEvent) -> void: - moving_mode = ( - Input.is_action_pressed("Select") - and event is InputEventMouseMotion - and not selected_nodes.is_empty() - ) +#func center_offset(): +#var base_offset = Vector2.ZERO +#var root_node: RootNode = get_root_node() +#if root_node: +#base_offset = root_node.position_offset + (root_node.size / 2) * zoom +# +#scroll_offset = -size / 2 + base_offset + +#func _input(event: InputEvent) -> void: +#moving_mode = ( +#Input.is_action_pressed("Select") +#and event is InputEventMouseMotion +#and not selected_nodes.is_empty() +#) func _gui_input(_event: InputEvent) -> void: @@ -81,41 +90,16 @@ func _gui_input(_event: InputEvent) -> void: DisplayServer.cursor_set_custom_image(Cursor.arrow) -## Adds a node of the given type to this graph. -func add_node( - node_type, record: bool = true, picker: GraphNodePicker = null -) -> Array[MonologueGraphNode]: - # if adding from picker, track existing to_nodes of the picker_from_node - var picker_to_names = [] - if picker: - for picker_to_node in get_all_connections_from_slot(picker.from_node, picker.from_port): - picker_to_names.append(picker_to_node.name) - - var node_scene = Constants.NODE_SCENES.get(node_type) - var new_node = node_scene.instantiate() - - # created_nodes include auxilliary nodes from new_node, such as BridgeOut - var created_nodes = new_node.add_to(self) - - # if enabled, track the addition of created_nodes into the graph history - if record: - var addition = AddNodeHistory.new(self, created_nodes) - if not picker_to_names.is_empty(): - addition.picker_from_node = picker.from_node - addition.picker_from_port = picker.from_port - addition.picker_to_names = picker_to_names - - undo_redo.create_action("Add new %s" % [new_node.node_type]) - undo_redo.add_prepared_history(addition) - undo_redo.commit_action(false) +### Adds a node of the given type to this graph. +#func add_node( +#node_type, record: bool = true, picker: GraphNodePicker = null +#) -> Array[MonologueGraphNode]: +#return [] - return created_nodes - - -func clear(): - for node in get_nodes(): - node.queue_free() - clear_connections() +#func clear(): +#for node in get_nodes(): +#node.queue_free() +#clear_connections() ## Disconnect all outbound connections of the given graphnode and port. @@ -127,26 +111,27 @@ func disconnect_outbound_from_node(from_node: StringName, from_port: int) -> voi disconnect_node(from_node, from_port, to_node, to_port) -## Deletes the given graphnode and return its dictionary data. -func free_graphnode(node: MonologueGraphNode) -> Dictionary: - var inbound_connections = get_all_inbound_connections(node.name) - var outbound_connections = get_all_outbound_connections(node.name) - for c in inbound_connections + outbound_connections: - disconnect_node(c.get("from_node"), c.get("from_port"), c.get("to_node"), c.get("to_port")) - - var node_data = node._to_dict() - if "options" in node: - # tag options into the node_data - node_data.merge({"Options": node.options.value}) - if active_graphnode == node: - active_graphnode = null - - selected_nodes.erase(node) - recorded_positions.erase(node) - node.queue_free() - # if side panel is showing this node, close it since it's gone - GlobalSignal.emit("close_panel", [node]) - return node_data +# +### Deletes the given graphnode and return its dictionary data. +#func free_graphnode(node: MonologueGraphNode) -> Dictionary: +#var inbound_connections = get_all_inbound_connections(node.name) +#var outbound_connections = get_all_outbound_connections(node.name) +#for c in inbound_connections + outbound_connections: +#disconnect_node(c.get("from_node"), c.get("from_port"), c.get("to_node"), c.get("to_port")) +# +#var node_data = node._to_dict() +#if "options" in node: +## tag options into the node_data +#node_data.merge({"Options": node.options.value}) +#if active_graphnode == node: +#active_graphnode = null +# +#selected_nodes.erase(node) +#recorded_positions.erase(node) +#node.queue_free() +## if side panel is showing this node, close it since it's gone +#GlobalSignal.emit("close_panel", [node]) +#return node_data ## Find all other connections that connect to the given graphnode. @@ -177,115 +162,109 @@ func get_all_connections_from_slot(from_node: StringName, from_port: int) -> Arr return node_connections -func get_free_bridge_number(_n = 1, lp_max = 50) -> int: - for node in get_nodes(): - if ( - (node.node_type == "NodeBridgeOut" or node.node_type == "NodeBridgeIn") - and node.number_selector.value == _n - ): - if lp_max <= 0: - return _n - - return get_free_bridge_number(_n + 1, lp_max - 1) - return _n - - -func get_linked_bridge_node(target_number) -> MonologueGraphNode: - for node in get_nodes(): - if node.node_type == "NodeBridgeOut" and node.number_selector.value == target_number: - return node - return null - - -func get_root_node() -> RootNode: - for node in get_nodes(): - if node is RootNode: - return node - return null - - -## Find a graph node by ID. Includes OptionNodes. -func get_node_by_id(id: String) -> MonologueGraphNode: - if not id.is_empty(): - for node in get_nodes(): - if node.id.value == id: - return node - elif node is ChoiceNode: - var option = node.get_option_by_id(id) - if option: - return option - return null +#func get_free_bridge_number(_n = 1, lp_max = 50) -> int: +#for node in get_nodes(): +#if ( +#(node.node_type == "NodeBridgeOut" or node.node_type == "NodeBridgeIn") +#and node.number_selector.value == _n +#): +#if lp_max <= 0: +#return _n +# +#return get_free_bridge_number(_n + 1, lp_max - 1) +#return _n + +#func get_linked_bridge_node(target_number) -> MonologueGraphNode: +#for node in get_nodes(): +#if node.node_type == "NodeBridgeOut" and node.number_selector.value == target_number: +#return node +#return null + +#func get_root_node() -> RootNode: +#for node in get_nodes(): +#if node is RootNode: +#return node +#return null + +### Find a graph node by ID. Includes OptionNodes. +#func get_node_by_id(id: String) -> MonologueGraphNode: +#if not id.is_empty(): +#for node in get_nodes(): +#if node.id.value == id: +#return node +# +#return null func is_unsaved() -> bool: return version != undo_redo.get_version() -## Connect picker_from_node to [param node] if needed, reposition nodes. -func pick_and_center( - nodes: Array[MonologueGraphNode], picker: GraphNodePicker -) -> PackedStringArray: - var to_names = [] - var offset = (scroll_offset + size/2) / zoom # center of graph - - if picker.from_node and picker.from_port != -1: - if nodes[0].get_input_port_count() > 0: - var from_node = picker.from_node - var from_port = picker.from_port - disconnect_outbound_from_node(from_node, from_port) - propagate_connection(from_node, from_port, nodes[0].name, 0) - if picker.graph_release: - offset = (picker.release + scroll_offset) / zoom - - picker.flush() - - for node in nodes: - node.position_offset = offset - - post_node_offset.call_deferred(nodes) - return to_names - - -func post_node_offset(nodes: Array[MonologueGraphNode]) -> void: - for node in nodes: - node.position_offset -= node.size/2 - - if not nodes[0].is_slot_enabled_left(0): - return - - var first_port_pos = nodes[0].get_input_port_position(0) - for node in nodes: - node.position_offset -= first_port_pos - node.position_offset = round(node.position_offset / snapping_distance) * snapping_distance - - -## Connects/disconnects and updates a given connection's NextID if possible. -## If [param next] is true, establish connection and propagate NextIDs. -## If it is false, destroy connection and clear all linked NextIDs. -func propagate_connection(from_node, from_port, to_node, to_port, next = true) -> void: - if next: - connect_node(from_node, from_port, to_node, to_port) - - else: - disconnect_node(from_node, from_port, to_node, to_port) - - var graph_node = get_node_or_null(NodePath(from_node)) - if graph_node and graph_node.has_method("update_next_id"): - if next: - var next_node = get_node_or_null(NodePath(to_node)) - graph_node.update_next_id(from_port, next_node) - else: - graph_node.update_next_id(from_port, null) - - -func trigger_delete(): - if not active_graphnode and selected_nodes: - var root_filter = func(n): return n is not RootNode - var selected_copy = selected_nodes.duplicate().filter(root_filter) - var delete_history = DeleteNodeHistory.new(self, selected_copy) - undo_redo.create_action("Delete %s" % str(selected_copy)) - undo_redo.add_prepared_history(delete_history) - undo_redo.commit_action() +### Connect picker_from_node to [param node] if needed, reposition nodes. +#func pick_and_center( +#nodes: Array[MonologueGraphNode], picker: GraphNodePicker +#) -> PackedStringArray: +#var to_names = [] +#var offset = (scroll_offset + size / 2) / zoom # center of graph +# +#if picker.from_node and picker.from_port != -1: +#if nodes[0].get_input_port_count() > 0: +#var from_node = picker.from_node +#var from_port = picker.from_port +#disconnect_outbound_from_node(from_node, from_port) +#propagate_connection(from_node, from_port, nodes[0].name, 0) +#if picker.graph_release: +#offset = (picker.release + scroll_offset) / zoom +# +#picker.flush() +# +#for node in nodes: +#node.position_offset = offset +# +#post_node_offset.call_deferred(nodes) +#return to_names +# +# +#func post_node_offset(nodes: Array[MonologueGraphNode]) -> void: +#for node in nodes: +#node.position_offset -= node.size / 2 +# +#if not nodes[0].is_slot_enabled_left(0): +#return +# +#var first_port_pos = nodes[0].get_input_port_position(0) +#for node in nodes: +#node.position_offset -= first_port_pos +#node.position_offset = round(node.position_offset / snapping_distance) * snapping_distance +# +# +### Connects/disconnects and updates a given connection's NextID if possible. +### If [param next] is true, establish connection and propagate NextIDs. +### If it is false, destroy connection and clear all linked NextIDs. +#func propagate_connection(from_node, from_port, to_node, to_port, next = true) -> void: +#if next: +#connect_node(from_node, from_port, to_node, to_port) +# +#else: +#disconnect_node(from_node, from_port, to_node, to_port) +# +#var graph_node = get_node_or_null(NodePath(from_node)) +#if graph_node and graph_node.has_method("update_next_id"): +#if next: +#var next_node = get_node_or_null(NodePath(to_node)) +#graph_node.update_next_id(from_port, next_node) +#else: +#graph_node.update_next_id(from_port, null) +# +# +#func trigger_delete(): +#if not active_graphnode and selected_nodes: +#var root_filter = func(n): return n is not RootNode +#var selected_copy = selected_nodes.duplicate().filter(root_filter) +#var delete_history = DeleteNodeHistory.new(self, selected_copy) +#undo_redo.create_action("Delete %s" % str(selected_copy)) +#undo_redo.add_prepared_history(delete_history) +#undo_redo.commit_action() ## Checks and ensure graph is ready before triggering undo. @@ -300,51 +279,30 @@ func trigger_redo() -> void: undo_redo.redo() -func update_node_positions() -> void: - var affected_nodes = selected_nodes if selected_nodes else get_nodes() - for node in affected_nodes: - recorded_positions[node] = node.position_offset +#func update_node_positions() -> void: +#var affected_nodes = selected_nodes if selected_nodes else get_nodes() +#for node in affected_nodes: +#recorded_positions[node] = node.position_offset func update_version() -> void: version = undo_redo.get_version() -func _on_auto_arrange_nodes() -> void: - var affected = selected_nodes if selected_nodes else get_nodes() - var changed = affected.filter(func(n): return n.position_offset != recorded_positions[n]) - if changed and affected.size() > 1: - undo_redo.create_action("Auto arrange nodes") - for node in changed: - undo_redo.add_do_property(node, "position_offset", node.position_offset) - undo_redo.add_undo_property(node, "position_offset", recorded_positions[node]) - undo_redo.commit_action(false) - update_node_positions() +#func _on_auto_arrange_nodes() -> void: +#var affected = selected_nodes if selected_nodes else get_nodes() +#var changed = affected.filter(func(n): return n.position_offset != recorded_positions[n]) +#if changed and affected.size() > 1: +#undo_redo.create_action("Auto arrange nodes") +#for node in changed: +#undo_redo.add_do_property(node, "position_offset", node.position_offset) +#undo_redo.add_undo_property(node, "position_offset", recorded_positions[node]) +#undo_redo.commit_action(false) +#update_node_positions() -func _on_child_entered_tree(node: Node) -> void: - if node is MonologueGraphNode: - if node is RootNode: - return - - if not node.show_close_button: - return - - var node_header = node.get_children(true)[0] - var close_button: TextureButton = close_button_scene.instantiate() - - var close_callback = func(): - var delete_history = DeleteNodeHistory.new(self, [node]) - var message = "Delete %s (id: %s)" - undo_redo.create_action(message % [node.node_type, node.id.value]) - undo_redo.add_prepared_history(delete_history) - undo_redo.commit_action(false) - selected_nodes.erase(node) - recorded_positions.erase(node) - free_graphnode(node) - - close_button.connect("pressed", close_callback) - node_header.add_child(close_button) +func _on_child_entered_tree(_node: Node) -> void: + pass func _on_connection_drag_started(_from_node, _from_port, _is_output) -> void: @@ -355,24 +313,24 @@ func _on_connection_drag_ended() -> void: connecting_mode = false -func _on_connection_request(from_node, from_port, to_node, to_port) -> void: - # so check to make sure there are no other connections before connecting - if get_all_connections_from_slot(from_node, from_port).size() <= 0: - var arguments = [from_node, from_port, to_node, to_port] - var message = "Connect %s port %d to %s port %d" - undo_redo.create_action(message % arguments) - undo_redo.add_do_method(propagate_connection.bindv(arguments)) - undo_redo.add_undo_method(propagate_connection.bindv(arguments + [false])) - undo_redo.commit_action() - - -func _on_disconnection_request(from_node, from_port, to_node, to_port) -> void: - var arguments = [from_node, from_port, to_node, to_port] - var message = "Disconnect %s from %s port %d" - undo_redo.create_action(message % [to_node, from_node, from_port]) - undo_redo.add_do_method(propagate_connection.bindv(arguments + [false])) - undo_redo.add_undo_method(propagate_connection.bindv(arguments)) - undo_redo.commit_action() +#func _on_connection_request(from_node, from_port, to_node, to_port) -> void: +## so check to make sure there are no other connections before connecting +#if get_all_connections_from_slot(from_node, from_port).size() <= 0: +#var arguments = [from_node, from_port, to_node, to_port] +#var message = "Connect %s port %d to %s port %d" +#undo_redo.create_action(message % arguments) +#undo_redo.add_do_method(propagate_connection.bindv(arguments)) +#undo_redo.add_undo_method(propagate_connection.bindv(arguments + [false])) +#undo_redo.commit_action() +# +# +#func _on_disconnection_request(from_node, from_port, to_node, to_port) -> void: +#var arguments = [from_node, from_port, to_node, to_port] +#var message = "Disconnect %s from %s port %d" +#undo_redo.create_action(message % [to_node, from_node, from_port]) +#undo_redo.add_do_method(propagate_connection.bindv(arguments + [false])) +#undo_redo.add_undo_method(propagate_connection.bindv(arguments)) +#undo_redo.commit_action() func _on_connection_to_empty(node: String, port: int, release: Vector2) -> void: @@ -391,23 +349,21 @@ func _on_gui_input(event: InputEvent) -> void: GlobalSignal.emit("show_languages", [false]) -func _on_node_selected(node) -> void: - if node is MonologueGraphNode: - selected_nodes.append(node) - - -func _on_node_deselected(node) -> void: - recorded_positions[node] = node.position_offset - selected_nodes.erase(node) - active_graphnode = null # when a deselection happens, clear active node - - -func get_nodes() -> Array[MonologueGraphNode]: - var list: Array[MonologueGraphNode] = [] - for node in get_children(): - if node is MonologueGraphNode: - list.append(node) - return list +#func _on_node_selected(node) -> void: +#if node is MonologueGraphNode: +#selected_nodes.append(node) +# +# +#func _on_node_deselected(node) -> void: +#recorded_positions[node] = node.position_offset +#selected_nodes.erase(node) +#active_graphnode = null # when a deselection happens, clear active node + +#func get_nodes() -> Array[MonologueGraphNode]: +#var list: Array[MonologueGraphNode] = [] +#for node in get_children(): +#list.append(node) +#return list func _on_mouse_entered() -> void: diff --git a/common/layouts/graph_edit/monologue_graph_edit.gd b/common/layouts/graph_edit/monologue_graph_edit.gd index 4463c2b7..c74f3090 100644 --- a/common/layouts/graph_edit/monologue_graph_edit.gd +++ b/common/layouts/graph_edit/monologue_graph_edit.gd @@ -1,70 +1,64 @@ class_name MonologueGraphEdit extends CustomGraphEdit -var characters := Property.new(MonologueGraphNode.LIST, {}, []) -var variables := Property.new(MonologueGraphNode.LIST, {}, []) -var _character_references = [] -var _variable_references = [] +signal node_view_selected(node: InspectableNode) + +var characters := Property.new("characters", {}, "character", {}) +var variables := Property.new("variables", {}, "variable", {}) + +var storyline_id: String func _ready() -> void: super._ready() - characters.setters["add_callback"] = add_character - characters.setters["get_callback"] = get_characters - characters.connect("preview", load_character) - variables.setters["add_callback"] = add_variable - variables.setters["get_callback"] = get_variables - variables.connect("preview", load_variables) +func refresh() -> void: + var storyline: StorylineDocument = StorylineManager.get_storyline(storyline_id) + for child: GraphElement in get_all_graph_nodes(): + child.queue_free() + for node: InspectableNode in storyline.nodes: + add_graph_node_view(node) -func add_character(dict: Dictionary = {}) -> MonologueCharacter: - var character = MonologueCharacter.new(self) - if dict: - character._from_dict(dict) - character.idx.value = _character_references.size() - character.character.setters["character_index"] = character.idx.value - _character_references.append(character) - return character +func add_graph_node_view(node: InspectableNode) -> void: + var new_node: GraphNode = GraphNode.new() + new_node.title = node.get_title() + build_graph_node_view_content(new_node, node) + new_node.node_selected.connect(_on_node_view_selected.bind(node)) + add_child(new_node) -func add_variable(dict: Dictionary = {}) -> MonologueVariable: - var variable = MonologueVariable.new(self) - if dict: - variable._from_dict(dict) - variable.index = _variable_references.size() - _variable_references.append(variable) - return variable +func build_graph_node_view_content(graph_node: GraphNode, node: InspectableNode) -> void: + var rows: Array = node.get_rows() -func get_characters() -> Array: - return _character_references - - -func get_variables() -> Array: - return _variable_references + for row: GraphNodeRow in rows: + var hbox: HBoxContainer = HBoxContainer.new() + var label: Label = Label.new() + var idx: int = rows.find(row) + label.text = row.get_content() + hbox.add_child(label) -## Perform initial loading of speakers and set indexes correctly. -func load_character(new_character_list: Array): - _character_references.clear() - var ascending = func(a, b): return a.get("EditorIndex") < b.get("EditorIndex") - new_character_list.sort_custom(ascending) - for speaker_data in new_character_list: - add_character(speaker_data) + graph_node.add_child(hbox) + graph_node.set_slot( + idx, + row._enable_left_port, + 0, + Color.WHITE, + row._enable_right_port, + 0, + Color.WHITE, + null, + null, + true + ) - if _character_references.is_empty(): - var narrator = add_character() - narrator.character.value["Name"] = "_NARRATOR" - narrator.protected.value = true - new_character_list.append(narrator._to_dict()) - characters.value = new_character_list +func _on_node_view_selected(node: InspectableNode) -> void: + node_view_selected.emit(node) -func load_variables(new_variable_list: Array): - _variable_references.clear() - for variable in new_variable_list: - add_variable(variable) - variables.value = new_variable_list +func get_all_graph_nodes() -> Array: + return get_children().filter(func(child) -> bool: return child is GraphNode) diff --git a/common/layouts/inspector/inspector_panel.gd b/common/layouts/inspector/inspector_panel.gd deleted file mode 100644 index 6777b685..00000000 --- a/common/layouts/inspector/inspector_panel.gd +++ /dev/null @@ -1,213 +0,0 @@ -## Side panel which displays graph node details. This panel should not contain -## references to MonologueEditor or GraphEditSwitcher. -class_name InspectorPanel extends PanelContainer - -@onready var fields_container = %Fields -@onready var topbox = %TopBox -@onready var ribbon_scene = preload("res://common/ui/ribbon/ribbon.tscn") -@onready var collapsible_field = preload("res://common/ui/fields/collapsible_field/collapsible_field.tscn") - -var collapsibles: Dictionary[String, CollapsibleField] -var id_field_container: Control -var selected_node: MonologueGraphNode - - -func _ready(): - GlobalSignal.add_listener("close_panel", _on_close_button_pressed) - - -func clear(): - for field in fields_container.get_children(): - field.free() - if is_instance_valid(id_field_container): - id_field_container.queue_free() - collapsibles.clear() - - -func on_graph_node_deselected(_node: MonologueGraphNode) -> void: - pass - - -func on_graph_node_selected(node: MonologueGraphNode, bypass: bool = false) -> void: - if not node: - return - - if not bypass: - var graph_edit = node.get_parent() - await get_tree().process_frame - if ( - is_instance_valid(node) - and not graph_edit.moving_mode - and graph_edit.selected_nodes.size() == 1 - ): - graph_edit.active_graphnode = node - else: - graph_edit.active_graphnode = null - return - - if node is BackgroundNode: - return on_new_graph_node_selected(node, bypass) - - # hack to preserve focus if the side panel contains the same node paths - var focus_owner = get_viewport().gui_get_focus_owner() - var refocus_path: NodePath = "" - var refocus_line: int = -1 - var refocus_column: int = -1 - if focus_owner: - refocus_path = get_path_to(focus_owner) - if focus_owner is TextEdit: - refocus_line = focus_owner.get_caret_line() - refocus_column = focus_owner.get_caret_column() - elif focus_owner is LineEdit: - refocus_column = focus_owner.get_caret_column() - var uncollapse_paths: Array[NodePath] = [] - if node == selected_node: - for collapsible: CollapsibleField in collapsibles.values(): - if collapsible.is_open(): - uncollapse_paths.append(get_path_to(collapsible)) - - clear() - selected_node = node - node._update() - - if not node.is_editable(): - return - - var items = node._get_field_groups() - var already_invoke := [] - var property_names = node.get_property_names() - - for item in items: - _load_groups(item, node, already_invoke) - - for property_name in property_names: - if property_name in already_invoke: - continue - - if property_name == "id": - var property = node.get(property_name) - var field = property.show(topbox, 0, false) - field.set_label_visible(false) - id_field_container = property.field_container - else: - var field = node.get(property_name).show(fields_container) - field.set_label_text(property_name.capitalize()) - - show() - restore_collapsible_state(uncollapse_paths) - # if focus was preserved, restore it - restore_focus(refocus_path, refocus_line, refocus_column) - - -func on_new_graph_node_selected(node: MonologueGraphNode, bypass: bool = false) -> void: - var properties := node._get_inspector_property_list() - print(properties) - # Create a field in the inspector - # Link the field to the property and if any changes to the property is made, update the value of the property - # + Undo/Redo - - -func _load_groups(item, graph_node: MonologueGraphNode, already_invoke) -> void: - if item is String: - var property = graph_node.get(item) - var field = property.show(fields_container) - - if property.custom_label != null: - field.set_label_text(property.custom_label) - else: - field.set_label_text(item.capitalize()) - - already_invoke.append(item) - else: - for group in item: - _recursive_build_collapsible_field( - fields_container, item, group, graph_node, already_invoke - ) - - -func _recursive_build_collapsible_field( - parent: Control, - item: Dictionary, - group: String, - graph_node: MonologueGraphNode, - already_invoke: Array -) -> CollapsibleField: - var fields = item[group] - var field_obj: CollapsibleField = collapsible_field.instantiate() - var field_margin = MarginContainer.new() - field_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL - field_margin.add_theme_constant_override("margin_right", 0) - field_margin.add_theme_constant_override("margin_bottom", 0) - field_margin.add_child(field_obj) - if parent is CollapsibleField: - parent.add_item(field_margin) - else: - parent.add_child(field_margin) - field_obj.set_title(group) - - for field_name in fields: - if field_name is Dictionary: - for sub_group in field_name: - _recursive_build_collapsible_field( - field_obj, field_name, sub_group, graph_node, already_invoke - ) - continue - - var property: Property = graph_node.get(field_name) - var field = property.show(fields_container) - var field_container = property.field_container - - if property.custom_label != null: - field.set_label_text(property.custom_label) - else: - field.set_label_text(field_name.capitalize()) - - fields_container.remove_child(field_container) - field_obj.add_item(field_container) - already_invoke.append(field_name) - - field.collapsible_field = field_obj - if property.uncollapse: - field_obj.open() - property.uncollapse = false - return field_obj - - -## If the side panel for the node is visible, release the focus so that -## text controls trigger the focus_exited() signal to update. -func refocus(node: MonologueGraphNode) -> void: - if visible and selected_node == node: - var focus_owner = get_viewport().gui_get_focus_owner() - if focus_owner: - focus_owner.release_focus() - focus_owner.grab_focus() - - -## If any collapsible fields were opened before the side panel was refreshed, -## this method will reopen them via their node paths. -func restore_collapsible_state(uncollapse_paths: Array[NodePath]) -> void: - for path in uncollapse_paths: - var field = get_node_or_null(path) - if is_instance_valid(field) and field is CollapsibleField: - field.open() - - -## Hacky improvement for #52 to maintain focus on side panel refresh. -func restore_focus(node_path: NodePath, line: int, column: int) -> void: - if node_path: - var node = get_node_or_null(node_path) - if is_instance_valid(node) and node is Control: - node.grab_focus() - if line >= 0: - node.set_caret_line(line) - if column >= 0: - node.set_caret_column(column) - - -func _on_rfh_button_pressed() -> void: - GlobalSignal.emit("test_trigger", [selected_node.id.value]) - - -func _on_close_button_pressed(node: MonologueGraphNode = null) -> void: - if not node or selected_node == node: - selected_node.get_parent().set_selected(null) diff --git a/common/layouts/inspector/monologue_inspector.gd b/common/layouts/inspector/monologue_inspector.gd deleted file mode 100644 index b3b767f2..00000000 --- a/common/layouts/inspector/monologue_inspector.gd +++ /dev/null @@ -1,44 +0,0 @@ -class_name MonologueInspector extends Node - -var _properties: Array = [] -var _node: Node - - -func bind_node(node: Node) -> void: - _node = node - - -## Create a property -## -## -## -func add_property(property_name: String, property_data: Dictionary) -> void: - if has_property(property_name): - push_error("Property %s already exist." % property_name) - return - - property_data.merge({"name": property_name}) - _properties.append(property_data) - - -func has_property(property_name: String) -> bool: - var candidates: Array = _properties.filter(func(p): return p.get("name") == property_name) - return candidates.size() <= 0 - - -func get_property(property_name: String) -> Variant: - var candidates: Array = _properties.filter(func(p): return p.get("name") == property_name) - if candidates.size() <= 0: - return null - - return candidates.get(0) - - -func update_property(property_name: String, property_data: Dictionary) -> void: - if not has_property(property_name): - push_warning("Cannot find property %s." % property_name) - return - - -func _get_inspector_property_list() -> Array: - return _properties diff --git a/common/layouts/inspector/monologue_inspector.gd.uid b/common/layouts/inspector/monologue_inspector.gd.uid deleted file mode 100644 index 1a7f83cf..00000000 --- a/common/layouts/inspector/monologue_inspector.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cfys1iyoy6jjw diff --git a/common/monologue_graph_node.gd b/common/monologue_graph_node.gd index 02a52208..218cae6d 100644 --- a/common/monologue_graph_node.gd +++ b/common/monologue_graph_node.gd @@ -1,240 +1,16 @@ -## Abstract graph node class for Monologue dialogue nodes. This should not -## be used on its own, it should be overridden to replace [member node_type]. -class_name MonologueGraphNode extends GraphNode +@abstract +class_name InspectableNode extends InspectableObject -static var subclasses = [] -@export_group("Appearance") -@export var titlebar_color: Color = Color("ff0000") -@export var show_close_button: bool = true -@export var show_titlebar: bool = true +func _init() -> void: + define_property("position", Vector2.ZERO, "vector2", {}) + super._init() -# field UI scene definitions that a graph node can have -const CHECKBOX = preload("res://common/ui/fields/check_box/monologue_check_box.tscn") -const DROPDOWN = preload("res://common/ui/fields/dropdown/monologue_dropdown.tscn") -const FILE = preload("res://common/ui/fields/file_picker/monologue_file_picker.tscn") -const LINE = preload("res://common/ui/fields/line_edit/monologue_line_edit.tscn") -const LIST = preload("res://common/ui/fields/list/monologue_list.tscn") -const SLIDER = preload("res://common/ui/fields/slider/monologue_slider.tscn") -const SPINBOX = preload("res://common/ui/fields/spin_box/monologue_spin_box.tscn") -const TIMELINE = preload("res://common/ui/fields/timeline/monologue_timeline.tscn") -const TEXT = preload("res://common/ui/fields/text/monologue_text.tscn") -const TOGGLE = preload("res://common/ui/fields/toggle/monologue_toggle.tscn") -const VECTOR = preload("res://common/ui/fields/vector/monologue_vector.tscn") -const LEFT_SLOT = preload("res://ui/assets/icons/slot.svg") -const RIGHT_SLOT = preload("res://ui/assets/icons/slot.svg") +@abstract func get_type() -> String -var id := Property.new(LINE, {}, IDGen.generate()) -var editor_position := Property.new(VECTOR, {}, [0.0, 0.0]) -var node_type: String = "NodeUnknown" +@abstract func get_title() -> String +@abstract func get_color() -> Color +@abstract func get_icon() -> Texture2D - -func _ready() -> void: - set_anchors_preset(Control.PRESET_TOP_LEFT) - if show_titlebar: - _set_titlebar_color(titlebar_color) - - title = node_type - id.setters["copyable"] = true - id.setters["validator"] = _validate_id - id.callers["set_label_visible"] = [false] - editor_position.display.connect(_on_editor_position_change) - editor_position.visible = false - for property_name in get_property_names(): - get(property_name).connect("change", change.bind(property_name)) - get(property_name).connect("display", display) - - _update_slot_icons() - _harmonize_size.call_deferred() - - dragged.connect(_on_dragged) - - -func _on_dragged(_from: Vector2 = Vector2.ZERO, _to: Vector2 = Vector2.ZERO) -> void: - var new_editor_position: Array = [position_offset.x, position_offset.y] - editor_position.save_value(new_editor_position) - - -func _on_editor_position_change() -> void: - await get_tree().process_frame - position_offset.x = editor_position.value[0] - position_offset.y = editor_position.value[1] - - -func _update_slot_icons() -> void: - for slot_idx in get_child_count(): - if is_slot_enabled_left(slot_idx): - set_slot_custom_icon_left(slot_idx, LEFT_SLOT) - if is_slot_enabled_right(slot_idx): - set_slot_custom_icon_right(slot_idx, RIGHT_SLOT) - - -func _harmonize_size() -> void: - var min_size: Vector2 = get_combined_minimum_size() - size.x = ceil(min_size.x / 30) * 30 - size.y = min_size.y - - -func _set_titlebar_color(val: Color): - var is_dark = val.get_luminance() < 0.5 - var stylebox: StyleBoxFlat = get_theme_stylebox("titlebar", "GraphNode").duplicate() - stylebox.bg_color = val - stylebox.corner_radius_top_left = 5 - stylebox.corner_radius_top_right = 5 - - stylebox.border_color = Color("4d4d4d") - stylebox.set_border_width_all(1) - stylebox.border_width_bottom = 0 - - var stylebox_selected = get_theme_stylebox("titlebar_selected", "GraphNode").duplicate() - stylebox_selected.bg_color = val - - remove_theme_stylebox_override("titlebar") - remove_theme_stylebox_override("titlebar_selected") - add_theme_stylebox_override("titlebar", stylebox) - add_theme_stylebox_override("titlebar_selected", stylebox_selected) - - var titlebar: HBoxContainer = get_titlebar_hbox() - var title_label: Label = titlebar.get_children().filter(func(c) -> bool: return c is Label)[0] - title_label.add_theme_color_override("font_color", Color.WHITE if is_dark else Color.BLACK) - - -func add_to(graph: MonologueGraphEdit) -> Array[MonologueGraphNode]: - graph.add_child(self, true) - var all_ids := graph.get_nodes().map(func(n) -> String: return n.id.value) - id.setters["value"] = IDGen.generate(10, all_ids) - return [self] - - -## Commits a given property's value into undo/redo history. -## Order of parameters is important due to how bind() works. -func change(old_value: Variant, new_value: Variant, property: String) -> void: - var changes: Array[PropertyChange] = [] - changes.append(PropertyChange.new(property, old_value, new_value)) - - var graph = get_graph_edit() - var undo_redo = graph.undo_redo - undo_redo.create_action("%s: %s => %s" % [property, old_value, new_value]) - var history = PropertyHistory.new(graph, graph.get_path_to(self), changes) - undo_redo.add_prepared_history(history) - undo_redo.commit_action() - - -func display() -> void: - get_graph_edit().set_selected(self) - - -func get_graph_edit() -> MonologueGraphEdit: - return get_parent() - - -func get_property_names() -> PackedStringArray: - var names = PackedStringArray() - for property in get_property_list(): - if Constants.PROPERTY_CLASSES.has(property.class_name): - names.append(property.name) - return names - - -func is_editable() -> bool: - var ignorable := ["id"] - - for property in get_property_names(): - if property in ignorable: - continue - return true - return false - - -## Reload the preview text of the graph node, if any. -func reload_preview() -> void: - pass - - -func _from_dict(dict: Dictionary) -> void: - var editor_pos = dict.get("EditorPosition") - if editor_pos is Dictionary: - editor_pos = [editor_pos.get("x", 0), editor_pos.get("y", 0)] - dict["EditorPosition"] = editor_pos - - for key in dict.keys(): - var property = get(key.to_snake_case()) - if property is Property: - property.value = dict.get(key) - var private_property = get("_" + key.to_snake_case()) - if private_property is Property: - private_property.value = dict.get(key) - - _load_position(dict) - _update() # refresh node UI after loading properties - - -func _load_connections(data: Dictionary, key: String = "NextID") -> void: - var next_id = data.get(key) - if next_id is String: - var next_node = get_graph_edit().get_node_by_id(next_id) - if next_node: - get_graph_edit().connect_node(name, 0, next_node.name, 0) - - -func _load_position(data: Dictionary) -> void: - var editor_pos = data.get("EditorPosition") - if editor_pos and editor_pos is Dictionary: # Backward compatibility - position_offset.x = editor_pos.get("x", randi_range(-400, 400)) - position_offset.y = editor_pos.get("y", randi_range(-200, 200)) - elif editor_position and editor_pos is Array: - position_offset.x = editor_pos[0] - position_offset.y = editor_pos[1] - - -func _to_dict() -> Dictionary: - var base_dict = {"$type": node_type, "ID": id.value, "EditorPosition": editor_position.value} - _to_next(base_dict) - _to_fields(base_dict) - return base_dict - - -func _to_fields(dict: Dictionary) -> void: - for property_name in get_property_names(): - var property = get(property_name) - var is_raw = property is Localizable - if property.visible: - var value = property.to_raw_value() if is_raw else property.value - dict[Util.to_key_name(property_name)] = value - - -func _to_next(dict: Dictionary, key: String = "NextID") -> void: - var next_id_node = get_graph_edit().get_all_connections_from_slot(name, 0) - dict[key] = next_id_node[0].id.value if next_id_node else -1 - - -func _update() -> void: - size.y = 0 - _harmonize_size() - - -func _validate_id(text: String) -> bool: - return get_graph_edit().get_node_by_id(text) == null - - -func _get_field_groups() -> Array: - return [] - - -func _get_inspector_property_list() -> Array: - return ( - [ - {"property": "id", "type": LINE}, - {"property": "editor_position", "type": VECTOR}, - ] - + get_inspector_property_list() - ) - - -func get_inspector_property_list() -> Array: - return [] - - -func _setup_inspector(inspector: MonologueInspector) -> void: - pass +@abstract func get_rows() -> Array[GraphNodeRow] diff --git a/common/monologue_indexer.gd b/common/monologue_indexer.gd new file mode 100644 index 00000000..0d715198 --- /dev/null +++ b/common/monologue_indexer.gd @@ -0,0 +1,8 @@ +@abstract class_name MonologueIndexer + +enum ObjectType { NODE, FIELD } + +@abstract func get_scene() -> PackedScene + +# {"name": "", "type": ""} +@abstract func get_metadata() -> Dictionary diff --git a/common/monologue_indexer.gd.uid b/common/monologue_indexer.gd.uid new file mode 100644 index 00000000..40702262 --- /dev/null +++ b/common/monologue_indexer.gd.uid @@ -0,0 +1 @@ +uid://dc5qyu0n5oaqb diff --git a/common/storyline.gd b/common/storyline.gd new file mode 100644 index 00000000..5b6362b0 --- /dev/null +++ b/common/storyline.gd @@ -0,0 +1,27 @@ +class_name StorylineDocument extends RefCounted + +var id: String = IDGen.generate() +var name: String = "" +var nodes: Array[InspectableNode] = [] +var is_dirty: bool = false +var file_path: String = "" +var history: UndoRedo = UndoRedo.new() + + +func _init(sname: String, sfile_path: String = "") -> void: + name = sname + file_path = sfile_path + + var root_node: RootNode = RootNode.new() + nodes.append(root_node) + + +func add_node(node: InspectableNode) -> void: + node.add_observer(self) + + +# Called by InspectableNode +func on_property_changed(pname: String, _old_value: Variant, _new_value: Variant) -> void: + print(pname) + + is_dirty = true diff --git a/common/storyline.gd.uid b/common/storyline.gd.uid new file mode 100644 index 00000000..33257a8d --- /dev/null +++ b/common/storyline.gd.uid @@ -0,0 +1 @@ +uid://cv5yppp8eqjja diff --git a/common/ui/buttons/close_button.tscn b/common/ui/buttons/close_button.tscn index cfbcc8d2..83938832 100644 --- a/common/ui/buttons/close_button.tscn +++ b/common/ui/buttons/close_button.tscn @@ -1,11 +1,15 @@ -[gd_scene load_steps=2 format=3 uid="uid://dspmmme0jspdx"] +[gd_scene load_steps=3 format=3 uid="uid://dspmmme0jspdx"] -[ext_resource type="Texture2D" uid="uid://c7vdr4e0mxst6" path="res://ui/assets/icons/close_icon.png" id="1_jkx7d"] +[ext_resource type="Texture2D" uid="uid://kk5em170gn2b" path="res://ui/assets/icons/ui_close.svg" id="1_ai8vd"] -[node name="CloseButton" type="TextureButton"] -offset_right = 16.0 -offset_bottom = 16.0 -size_flags_vertical = 3 -mouse_default_cursor_shape = 2 -texture_normal = ExtResource("1_jkx7d") -stretch_mode = 3 +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_tlp5i"] + +[node name="Button" type="Button"] +anchors_preset = -1 +anchor_right = 0.017 +anchor_bottom = 0.03 +offset_right = 0.199999 +theme_override_constants/icon_max_width = 0 +theme_override_styles/normal = SubResource("StyleBoxEmpty_tlp5i") +icon = ExtResource("1_ai8vd") +flat = true diff --git a/common/ui/editor_properties/editor_property.gd b/common/ui/editor_properties/editor_property.gd new file mode 100644 index 00000000..b96f9361 --- /dev/null +++ b/common/ui/editor_properties/editor_property.gd @@ -0,0 +1,21 @@ +class_name EditorPropertyManager extends Node + +@onready var editor_property_label: PackedScene = preload("uid://x0daq5tsejey") + + +func create_editor_property(property: Property) -> HBoxContainer: + var editor_property_scene: PackedScene = FieldBucket.get_field(property.type) + if not editor_property_scene: + push_error("Can't find property scene for property with type '%s'." % property.type) + return + + var ep_field: Control = editor_property_scene.instantiate() + + var ep_label: Label = editor_property_label.instantiate() + ep_label.text = property.get_display_name() + + var ep_container: HBoxContainer = HBoxContainer.new() + ep_container.add_child(ep_label) + ep_container.add_child(ep_field) + + return ep_container diff --git a/common/ui/editor_properties/editor_property.gd.uid b/common/ui/editor_properties/editor_property.gd.uid new file mode 100644 index 00000000..f06c51f6 --- /dev/null +++ b/common/ui/editor_properties/editor_property.gd.uid @@ -0,0 +1 @@ +uid://8dvqxo4uw7cd diff --git a/common/ui/editor_properties/field_bridge.gd b/common/ui/editor_properties/field_bridge.gd new file mode 100644 index 00000000..61510e14 --- /dev/null +++ b/common/ui/editor_properties/field_bridge.gd @@ -0,0 +1 @@ +extends Node diff --git a/common/ui/editor_properties/field_bridge.gd.uid b/common/ui/editor_properties/field_bridge.gd.uid new file mode 100644 index 00000000..85df71f3 --- /dev/null +++ b/common/ui/editor_properties/field_bridge.gd.uid @@ -0,0 +1 @@ +uid://7su6leuvyoaa diff --git a/common/ui/editor_properties/field_bucket.gd b/common/ui/editor_properties/field_bucket.gd new file mode 100644 index 00000000..f0aae1f6 --- /dev/null +++ b/common/ui/editor_properties/field_bucket.gd @@ -0,0 +1,40 @@ +# Autoload +extends Node + +const DEFAULT_FIELDS_LOCATION: String = "res://common/ui/editor_properties/" + +var _bucket: Dictionary = {} + + +func _ready() -> void: + _search_fields() + + +func _search_fields() -> void: + var directories: Array = DirAccess.get_directories_at(DEFAULT_FIELDS_LOCATION) + for dir: String in directories: + var dir_path: String = DEFAULT_FIELDS_LOCATION.path_join(dir) + var files: Array = DirAccess.get_files_at(dir_path) + for file: String in files: + if not file == "index.gd": + continue + + var script_path: String = dir_path.path_join(file) + var script: MonologueIndexer = load(script_path).new() + + var fdata: Dictionary = script.call("get_metadata") + var fname: String = fdata.get("name") + + _bucket.set(fname, script) + + +func get_field_indexer(field_name: String) -> PackedScene: + return _bucket.get(field_name) + + +func get_scene(field_name: String) -> PackedScene: + return get_field_indexer(field_name).call("get_scene") + + +func get_metadata(field_name: String) -> PackedScene: + return get_field_indexer(field_name).call("get_metadata") diff --git a/common/ui/editor_properties/field_bucket.gd.uid b/common/ui/editor_properties/field_bucket.gd.uid new file mode 100644 index 00000000..070f8643 --- /dev/null +++ b/common/ui/editor_properties/field_bucket.gd.uid @@ -0,0 +1 @@ +uid://cmte2g7n7ogyd diff --git a/common/ui/editor_properties/text/index.gd b/common/ui/editor_properties/text/index.gd new file mode 100644 index 00000000..ace9652f --- /dev/null +++ b/common/ui/editor_properties/text/index.gd @@ -0,0 +1,9 @@ +extends MonologueIndexer + + +func get_scene() -> PackedScene: + return preload("uid://be0xxn5gocqjo") + + +func get_metadata() -> Dictionary: + return {"name": "text", "type": ObjectType.FIELD} diff --git a/common/ui/editor_properties/text/index.gd.uid b/common/ui/editor_properties/text/index.gd.uid new file mode 100644 index 00000000..c82c935b --- /dev/null +++ b/common/ui/editor_properties/text/index.gd.uid @@ -0,0 +1 @@ +uid://86csxw8qmvb4 diff --git a/common/ui/editor_properties/text/text_field.gd b/common/ui/editor_properties/text/text_field.gd new file mode 100644 index 00000000..61510e14 --- /dev/null +++ b/common/ui/editor_properties/text/text_field.gd @@ -0,0 +1 @@ +extends Node diff --git a/common/ui/editor_properties/text/text_field.gd.uid b/common/ui/editor_properties/text/text_field.gd.uid new file mode 100644 index 00000000..789d3ca6 --- /dev/null +++ b/common/ui/editor_properties/text/text_field.gd.uid @@ -0,0 +1 @@ +uid://ntsxdpbgyehu diff --git a/common/ui/editor_properties/text/text_field.tscn b/common/ui/editor_properties/text/text_field.tscn new file mode 100644 index 00000000..def3c19d --- /dev/null +++ b/common/ui/editor_properties/text/text_field.tscn @@ -0,0 +1,5 @@ +[gd_scene format=3 uid="uid://be0xxn5gocqjo"] + +[node name="LineEdit" type="LineEdit"] +offset_right = 76.5625 +offset_bottom = 29.0 diff --git a/common/ui/fields/character_field/assets/portrait_placeholder.svg b/common/ui/fields/character_field/assets/portrait_placeholder.svg deleted file mode 100644 index 35f2b975..00000000 --- a/common/ui/fields/character_field/assets/portrait_placeholder.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/common/ui/fields/character_field/monologue_character_field.gd b/common/ui/fields/character_field/monologue_character_field.gd deleted file mode 100644 index 19fe4f9a..00000000 --- a/common/ui/fields/character_field/monologue_character_field.gd +++ /dev/null @@ -1,29 +0,0 @@ -class_name MonologueCharacterField extends MonologueField - -@onready var name_edit := %NameEdit -@onready var delete_button := $HBoxContainer/VBoxContainer/VBoxContainer/HBoxContainer/DeleteButton - -var character_index: int = -1 -var default_portrait: String = "" -var graph_edit: MonologueGraphEdit - - -func propagate(value: Variant) -> void: - super.propagate(value) - name_edit.text = value.get("Name", "") - - -func _on_name_edit_focus_exited() -> void: - _on_name_edit_text_submitted(name_edit.text) - - -func _on_name_edit_text_submitted(new_text: String) -> void: - field_updated.emit({"Name" = new_text}) - - -func _on_edit_button_pressed() -> void: - GlobalSignal.emit("open_character_edit", [graph_edit, character_index]) - - -func use_custom_field_label() -> bool: - return true diff --git a/common/ui/fields/character_field/monologue_character_field.gd.uid b/common/ui/fields/character_field/monologue_character_field.gd.uid deleted file mode 100644 index a8c217d4..00000000 --- a/common/ui/fields/character_field/monologue_character_field.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cmvo3t5bngju6 diff --git a/common/ui/fields/character_field/monologue_character_field.tscn b/common/ui/fields/character_field/monologue_character_field.tscn deleted file mode 100644 index 00ae4823..00000000 --- a/common/ui/fields/character_field/monologue_character_field.tscn +++ /dev/null @@ -1,77 +0,0 @@ -[gd_scene load_steps=7 format=3 uid="uid://rmul5j0wm0l8"] - -[ext_resource type="PackedScene" uid="uid://dfwf55ovgwir3" path="res://common/ui/buttons/delete_button.tscn" id="1_eutnh"] -[ext_resource type="Script" uid="uid://cmvo3t5bngju6" path="res://common/ui/fields/character_field/monologue_character_field.gd" id="1_no1me"] -[ext_resource type="Texture2D" uid="uid://ddah0eo1qhki4" path="res://common/ui/fields/character_field/assets/portrait_placeholder.svg" id="2_jkh4y"] -[ext_resource type="Shader" uid="uid://bsso8dloc4bce" path="res://logic/shaders/texture_rect_clip.tres" id="2_nva25"] -[ext_resource type="PackedScene" uid="uid://x0daq5tsejey" path="res://common/ui/fields/field_label.tscn" id="4_uw7v4"] - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_q7tom"] -shader = ExtResource("2_nva25") -shader_parameter/corner_scale = 0.1 - -[node name="VBoxContainer" type="VBoxContainer"] -offset_right = 40.0 -offset_bottom = 40.0 -size_flags_horizontal = 3 -theme_override_constants/separation = 0 -script = ExtResource("1_no1me") - -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="CenterContainer" type="CenterContainer" parent="HBoxContainer"] -layout_mode = 2 - -[node name="TextureRect" type="TextureRect" parent="HBoxContainer/CenterContainer"] -material = SubResource("ShaderMaterial_q7tom") -clip_contents = true -custom_minimum_size = Vector2(65, 0) -layout_mode = 2 -texture = ExtResource("2_jkh4y") -expand_mode = 4 -stretch_mode = 6 - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="NameContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="FieldLabel" parent="HBoxContainer/VBoxContainer/NameContainer" instance=ExtResource("4_uw7v4")] -custom_minimum_size = Vector2(50, 31) -layout_mode = 2 -text = "Name" - -[node name="InnerVBox" type="VBoxContainer" parent="HBoxContainer/VBoxContainer/NameContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="NameEdit" type="LineEdit" parent="HBoxContainer/VBoxContainer/NameContainer/InnerVBox"] -unique_name_in_owner = true -layout_mode = 2 -placeholder_text = "Character name" - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 -alignment = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer/VBoxContainer"] -layout_mode = 2 -alignment = 1 - -[node name="EditButton" type="Button" parent="HBoxContainer/VBoxContainer/VBoxContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -text = "Edit" - -[node name="DeleteButton" parent="HBoxContainer/VBoxContainer/VBoxContainer/HBoxContainer" instance=ExtResource("1_eutnh")] -layout_mode = 2 -size_flags_vertical = 4 - -[connection signal="focus_exited" from="HBoxContainer/VBoxContainer/NameContainer/InnerVBox/NameEdit" to="." method="_on_name_edit_focus_exited"] -[connection signal="text_submitted" from="HBoxContainer/VBoxContainer/NameContainer/InnerVBox/NameEdit" to="." method="_on_name_edit_text_submitted"] -[connection signal="pressed" from="HBoxContainer/VBoxContainer/VBoxContainer/HBoxContainer/EditButton" to="." method="_on_edit_button_pressed"] diff --git a/common/ui/fields/check_box/monologue_check_box.gd b/common/ui/fields/check_box/monologue_check_box.gd deleted file mode 100644 index cf9c9305..00000000 --- a/common/ui/fields/check_box/monologue_check_box.gd +++ /dev/null @@ -1,11 +0,0 @@ -class_name MonologueCheckBox extends MonologueField - -@onready var check_box = $VBox/CheckBox - -func propagate(value: Variant) -> void: - super.propagate(value) - check_box.set_pressed_no_signal(value if (value is bool) else false) - - -func _on_check_box_toggled(toggled_on: bool) -> void: - field_updated.emit(toggled_on) diff --git a/common/ui/fields/check_box/monologue_check_box.gd.uid b/common/ui/fields/check_box/monologue_check_box.gd.uid deleted file mode 100644 index 688b968a..00000000 --- a/common/ui/fields/check_box/monologue_check_box.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://m2a68q1fce52 diff --git a/common/ui/fields/check_box/monologue_check_box.tscn b/common/ui/fields/check_box/monologue_check_box.tscn deleted file mode 100644 index 2246bc36..00000000 --- a/common/ui/fields/check_box/monologue_check_box.tscn +++ /dev/null @@ -1,20 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://71sq1ohwv8cn"] - -[ext_resource type="Script" uid="uid://m2a68q1fce52" path="res://common/ui/fields/check_box/monologue_check_box.gd" id="1_awprp"] - -[node name="MonologueCheckBox" type="HBoxContainer"] -custom_minimum_size = Vector2(0, 32) -offset_right = 15.0 -offset_bottom = 32.0 -theme_type_variation = &"HBoxContainer_Small" -script = ExtResource("1_awprp") - -[node name="VBox" type="VBoxContainer" parent="."] -layout_mode = 2 -alignment = 1 - -[node name="CheckBox" type="CheckBox" parent="VBox"] -layout_mode = 2 -expand_icon = true - -[connection signal="toggled" from="VBox/CheckBox" to="." method="_on_check_box_toggled"] diff --git a/common/ui/fields/collapsible_field/collapsible_field.gd b/common/ui/fields/collapsible_field/collapsible_field.gd deleted file mode 100644 index d6463a53..00000000 --- a/common/ui/fields/collapsible_field/collapsible_field.gd +++ /dev/null @@ -1,98 +0,0 @@ -class_name CollapsibleField extends VBoxContainer - -signal add_pressed - -@export var show_add_button: bool = false -@export var separate_items: bool = false -@export var expand: bool = false - -@onready var button := $Button -@onready var collapsible_container := $CollapsibleContainer -@onready var vbox := %FieldContainer -@onready var add_button := %AddButton - -@onready var icon_close := preload("res://ui/assets/icons/arrow_right.svg") -@onready var icon_open := preload("res://ui/assets/icons/arrow_down.svg") - - -func _ready() -> void: - button.icon = icon_close - add_button.visible = show_add_button - close() - _update() - - -func add_item(item: Control, force_readable_name: bool = false) -> void: - var existing_children = vbox.get_children().filter(_is_not_being_deleted) - if separate_items and existing_children.size() > 0: - var separator := HSeparator.new() - separator.theme_type_variation = "HDottedSeparator" - vbox.add_child(separator, true) - - item.visibility_changed.connect(_update) - - vbox.add_child(item, force_readable_name) - _update() - - -func _update(): - var can_see: bool = show_add_button - %AddButtonContainer.visible = can_see - - for child in vbox.get_children(): - if not child.visible: - continue - can_see = true - - if visible != can_see: - visible = can_see - - if expand: - size_flags_vertical = SIZE_EXPAND_FILL - vbox.size_flags_vertical = SIZE_EXPAND_FILL - - -func set_title(text: String) -> void: - button.text = text - - -func get_items() -> Array[Node]: - return vbox.get_children().filter(func(c): return c is not HSeparator) - - -func is_open() -> bool: - return collapsible_container.visible - - -func clear() -> void: - for child in vbox.get_children(): - child.queue_free() - - _update() - - -func _on_button_pressed() -> void: - if is_open(): - close() - else: - open() - - -func open() -> void: - button.icon = icon_open - collapsible_container.show() - button.release_focus() - - -func close() -> void: - button.icon = icon_close - collapsible_container.hide() - button.release_focus() - - -func _on_add_button_pressed() -> void: - add_pressed.emit() - - -func _is_not_being_deleted(node: Node) -> bool: - return not node.is_queued_for_deletion() diff --git a/common/ui/fields/collapsible_field/collapsible_field.gd.uid b/common/ui/fields/collapsible_field/collapsible_field.gd.uid deleted file mode 100644 index 7f7e7dd8..00000000 --- a/common/ui/fields/collapsible_field/collapsible_field.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bgy7bshgkocth diff --git a/common/ui/fields/collapsible_field/collapsible_field.tscn b/common/ui/fields/collapsible_field/collapsible_field.tscn deleted file mode 100644 index e577ef69..00000000 --- a/common/ui/fields/collapsible_field/collapsible_field.tscn +++ /dev/null @@ -1,55 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://hvv74un17dp0"] - -[ext_resource type="Script" uid="uid://bgy7bshgkocth" path="res://common/ui/fields/collapsible_field/collapsible_field.gd" id="1_tmui1"] -[ext_resource type="Texture2D" uid="uid://cb6n6enqfvclw" path="res://ui/assets/icons/arrow_right.svg" id="2_357t1"] - -[node name="CollapsibleField" type="VBoxContainer"] -offset_right = 72.0 -offset_bottom = 89.0 -size_flags_horizontal = 3 -theme_override_constants/separation = 0 -script = ExtResource("1_tmui1") - -[node name="Button" type="Button" parent="."] -layout_mode = 2 -icon = ExtResource("2_357t1") -alignment = 0 -icon_alignment = 2 - -[node name="CollapsibleContainer" type="MarginContainer" parent="."] -layout_mode = 2 -size_flags_vertical = 3 -theme_type_variation = &"MarginContainer_Medium" -theme_override_constants/margin_left = 40 -theme_override_constants/margin_top = 0 -theme_override_constants/margin_right = 0 - -[node name="PanelContainer" type="PanelContainer" parent="CollapsibleContainer"] -layout_mode = 2 -theme_type_variation = &"CollapsibleFieldPanel" - -[node name="VBox" type="VBoxContainer" parent="CollapsibleContainer/PanelContainer"] -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="FieldContainer" type="VBoxContainer" parent="CollapsibleContainer/PanelContainer/VBox"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_type_variation = &"FieldContainer" - -[node name="AddButtonContainer" type="MarginContainer" parent="CollapsibleContainer/PanelContainer/VBox"] -unique_name_in_owner = true -layout_mode = 2 - -[node name="AddButton" type="Button" parent="CollapsibleContainer/PanelContainer/VBox/AddButtonContainer"] -unique_name_in_owner = true -visible = false -layout_mode = 2 -mouse_default_cursor_shape = 2 -theme_type_variation = &"Button_Outline" -text = "+" - -[connection signal="pressed" from="Button" to="." method="_on_button_pressed"] -[connection signal="pressed" from="CollapsibleContainer/PanelContainer/VBox/AddButtonContainer/AddButton" to="." method="_on_add_button_pressed"] diff --git a/common/ui/fields/dropdown/monolgue_dropdown.gd b/common/ui/fields/dropdown/monolgue_dropdown.gd deleted file mode 100644 index 8a606cb2..00000000 --- a/common/ui/fields/dropdown/monolgue_dropdown.gd +++ /dev/null @@ -1,104 +0,0 @@ -class_name MonologueDropdown extends MonologueField - -@export var store_index: bool -## Usefull when items are set after the value is set. -@export var late_items: bool - -@onready var option_button: OptionButton = $OptionButton - -var backup_value: Variant - - -func _ready() -> void: - option_button.get_popup().transparent_bg = true - - -func disable_items(index_list: PackedInt32Array): - for index in range(1, option_button.item_count): - var is_disabled = index_list.has(index) - option_button.set_item_disabled(index, is_disabled) - validate() - - -func get_items() -> Array[Dictionary]: - var result: Array[Dictionary] = [] - for idx in range(option_button.item_count): - result.append( - { - "id": option_button.get_item_id(idx), - "text": option_button.get_item_text(idx), - "metadata": option_button.get_item_metadata(idx) - } - ) - return result - - -func get_item_idx_from_text(text: String) -> int: - var items = get_items() - for item in items: - if item.text == text: - return items.find(item) - return -1 - - -func propagate(value: Variant) -> void: - super.propagate(value) - backup_value = value - var index = get_item_idx_from_text(value) if value is String else value - if index < 0 or index >= option_button.item_count: # avoid falsy check - option_button.selected = 0 - else: - option_button.selected = index - validate() - - -func set_icons(index_to_texture: Dictionary): - for index in index_to_texture.keys(): - option_button.set_item_icon(index, index_to_texture.get(index)) - - -# `key_text` can contain "/" to navigate inside `data`. -func set_items( - data: Array, - key_text: String = "text", - key_id: String = "EditorIndex", - key_meta: String = "metadata" -) -> void: - option_button.clear() - for idx in range(data.size()): - var item_id = data[idx].get(key_id, -1) - if item_id is String: - item_id = -1 - var item_name = data[idx] - var item_name_path = key_text.split("/") - - for path in item_name_path: - item_name = item_name.get(path) - if item_name == null: - item_name = "undefined" - break - - option_button.add_item(item_name, item_id) - option_button.set_item_metadata(idx, data[idx].get(key_meta, "")) - - if late_items: - propagate(backup_value) - - validate() - - -func validate(): - var is_out = option_button.selected >= option_button.item_count - var is_negative = option_button.selected < 0 - if is_negative or is_out or option_button.is_item_disabled(option_button.selected): - option_button.theme_type_variation = "OptionButton_Error" - else: - option_button.theme_type_variation = "" - - -func _on_item_selected(index: int) -> void: - var value = index - if not store_index: - value = option_button.get_item_text(index) - validate() - field_updated.emit(value) diff --git a/common/ui/fields/dropdown/monolgue_dropdown.gd.uid b/common/ui/fields/dropdown/monolgue_dropdown.gd.uid deleted file mode 100644 index e71d25b9..00000000 --- a/common/ui/fields/dropdown/monolgue_dropdown.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ci7ipnx47b6fx diff --git a/common/ui/fields/dropdown/monologue_dropdown.tscn b/common/ui/fields/dropdown/monologue_dropdown.tscn deleted file mode 100644 index 944d58a6..00000000 --- a/common/ui/fields/dropdown/monologue_dropdown.tscn +++ /dev/null @@ -1,16 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://csunin0yg3ay0"] - -[ext_resource type="Script" uid="uid://ci7ipnx47b6fx" path="res://common/ui/fields/dropdown/monolgue_dropdown.gd" id="1_jtdjo"] - -[node name="MonologueDropdown" type="HBoxContainer"] -offset_right = 7.0 -offset_bottom = 29.0 -script = ExtResource("1_jtdjo") - -[node name="OptionButton" type="OptionButton" parent="."] -layout_mode = 2 -size_flags_horizontal = 3 -clip_text = true -allow_reselect = true - -[connection signal="item_selected" from="OptionButton" to="." method="_on_item_selected"] diff --git a/common/ui/fields/field_label.tscn b/common/ui/fields/field_label.tscn deleted file mode 100644 index 31924d94..00000000 --- a/common/ui/fields/field_label.tscn +++ /dev/null @@ -1,7 +0,0 @@ -[gd_scene format=3 uid="uid://x0daq5tsejey"] - -[node name="FieldLabel" type="Label"] -custom_minimum_size = Vector2(100, 31) -size_flags_vertical = 0 -text = "Value" -vertical_alignment = 1 diff --git a/common/ui/fields/file_picker/file_picker.gd b/common/ui/fields/file_picker/file_picker.gd deleted file mode 100644 index 7a249e95..00000000 --- a/common/ui/fields/file_picker/file_picker.gd +++ /dev/null @@ -1,73 +0,0 @@ -class_name FilePicker extends MonologueField - -const AUDIO = ["*.mp3,*.ogg,*.wav;Sound Files"] -const IMAGE = ["*.bmp,*.jpg,*.jpeg,*.png,*.svg,*.webp;Image Files"] - -@export var base_path: String -@export var filters: PackedStringArray - -@onready var line_edit: LineEdit = $HBox/LineEdit -@onready var picker_button: Button = $HBox/FilePickerButton -@onready var warn_label: Label = $WarnLabel - - -func propagate(value: Variant) -> void: - super.propagate(value) - line_edit.text = value - validate(value) - - -func validate(path: String) -> bool: - warn_label.hide() - var is_valid = true - path = path.lstrip(" ") - path = path.rstrip(" ") - if path and filters: - var absolute_path = Path.relative_to_absolute(path, base_path) - if not FileAccess.file_exists(absolute_path): - warn_label.show() - warn_label.text = "File path not found!" - is_valid = false - else: - var correct_suffix: bool = false - var file_name: String = absolute_path.get_file() - for filter in filters: - var targets = _split_match(filter) - for target in targets: - if file_name.match(target): - correct_suffix = true - break - if not correct_suffix: - warn_label.show() - var formats = Array(filters).map(_split_match) - var text = ", ".join(formats.map(func(f): return ", ".join(f))) - warn_label.text = "File must match: %s" % text - is_valid = false - return is_valid - - -func _on_file_selected(path: String): - line_edit.text = Path.absolute_to_relative(path, base_path) - _on_focus_exited() - - -func _on_focus_exited() -> void: - _on_text_submitted(line_edit.text) - - -func _on_picker_button_pressed(): - GlobalSignal.emit("open_file_request", [_on_file_selected, filters, base_path.get_base_dir()]) - - -func _on_text_changed(new_text: String) -> void: - validate(new_text) - field_changed.emit(new_text) - - -func _on_text_submitted(file_path: String) -> void: - validate(file_path) - field_updated.emit(file_path) - - -func _split_match(filter: String) -> Array: - return filter.split(";")[0].split(",") diff --git a/common/ui/fields/file_picker/file_picker.gd.uid b/common/ui/fields/file_picker/file_picker.gd.uid deleted file mode 100644 index c3318124..00000000 --- a/common/ui/fields/file_picker/file_picker.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bqlktpo5qx1ps diff --git a/common/ui/fields/file_picker/monologue_file_picker.tscn b/common/ui/fields/file_picker/monologue_file_picker.tscn deleted file mode 100644 index 502e53eb..00000000 --- a/common/ui/fields/file_picker/monologue_file_picker.tscn +++ /dev/null @@ -1,41 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://o5dt5106rohh"] - -[ext_resource type="Script" uid="uid://bqlktpo5qx1ps" path="res://common/ui/fields/file_picker/file_picker.gd" id="1_siiu8"] -[ext_resource type="Texture2D" uid="uid://t1i3wy037vsu" path="res://ui/assets/icons/folder_icon.png" id="2_plad0"] - -[sub_resource type="LabelSettings" id="LabelSettings_51u22"] -font_color = Color(0.768627, 0.180392, 0.25098, 1) - -[node name="FilePicker" type="VBoxContainer"] -anchors_preset = -1 -anchor_right = 0.276 -anchor_bottom = 0.111 -offset_right = 0.599976 -offset_bottom = 0.199989 -size_flags_horizontal = 3 -script = ExtResource("1_siiu8") - -[node name="HBox" type="HBoxContainer" parent="."] -layout_mode = 2 -theme_override_constants/separation = 0 -alignment = 2 - -[node name="LineEdit" type="LineEdit" parent="HBox"] -layout_mode = 2 -size_flags_horizontal = 3 -structured_text_bidi_override = 2 - -[node name="FilePickerButton" type="Button" parent="HBox"] -custom_minimum_size = Vector2(33, 25) -layout_mode = 2 -theme_type_variation = &"FlatButton" -icon = ExtResource("2_plad0") -icon_alignment = 1 -expand_icon = true - -[node name="WarnLabel" type="Label" parent="."] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 -text = "File path not found!" -label_settings = SubResource("LabelSettings_51u22") -autowrap_mode = 2 diff --git a/common/ui/fields/line_edit/monologue_line_edit.gd b/common/ui/fields/line_edit/monologue_line_edit.gd deleted file mode 100644 index 6814ba96..00000000 --- a/common/ui/fields/line_edit/monologue_line_edit.gd +++ /dev/null @@ -1,54 +0,0 @@ -class_name MonologueLine extends MonologueField - -@export var copyable: bool -@export var font_size: int = 16 -@export var is_sublabel: bool -@export var sublabel_prefix: String = "↳ " -@export var note_text: String - -var ribbon_scene = preload("res://common/ui/ribbon/ribbon.tscn") -var revert_text: String -var validator: Callable = func(_text): return true - -@onready var copy_button = $HBox/InnerVBox/LineEdit/HBoxContainer/CopyButton -@onready var line_edit = $HBox/InnerVBox/LineEdit -@onready var warning = $HBox/InnerVBox/WarnLabel -@onready var note = $NoteLabel - - -# Called when the node enters the scene tree for the first time. -func _ready() -> void: - copy_button.visible = copyable - line_edit.add_theme_font_size_override("font_size", font_size) - warning.add_theme_font_size_override("font_size", font_size) - warning.hide() - note.visible = !note_text.is_empty() - note.text = note_text - - -func propagate(value: Variant) -> void: - super.propagate(value) - line_edit.text = str(value) - revert_text = line_edit.text - - -func _on_copy_button_pressed() -> void: - DisplayServer.clipboard_set(line_edit.text) - var ribbon = ribbon_scene.instantiate() - ribbon.position = get_viewport().get_mouse_position() - get_window().add_child(ribbon) - - -func _on_focus_exited() -> void: - _on_text_submitted(line_edit.text) - - -func _on_text_changed(new_text: String) -> void: - field_changed.emit(new_text) - - -func _on_text_submitted(new_text: String) -> void: - if validator.call(new_text): - field_updated.emit(new_text) - else: - line_edit.text = revert_text diff --git a/common/ui/fields/line_edit/monologue_line_edit.gd.uid b/common/ui/fields/line_edit/monologue_line_edit.gd.uid deleted file mode 100644 index 27ab279b..00000000 --- a/common/ui/fields/line_edit/monologue_line_edit.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cdx8c5vk7f3jb diff --git a/common/ui/fields/line_edit/monologue_line_edit.tscn b/common/ui/fields/line_edit/monologue_line_edit.tscn deleted file mode 100644 index 78236a17..00000000 --- a/common/ui/fields/line_edit/monologue_line_edit.tscn +++ /dev/null @@ -1,59 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://bw7thqdhujl41"] - -[ext_resource type="Script" uid="uid://cdx8c5vk7f3jb" path="res://common/ui/fields/line_edit/monologue_line_edit.gd" id="1_toqtt"] -[ext_resource type="Texture2D" uid="uid://dm2u0xqmmcorj" path="res://ui/assets/icons/copy.png" id="2_lbcco"] - -[node name="MonologueLineEdit" type="VBoxContainer"] -offset_right = 40.0 -offset_bottom = 40.0 -size_flags_horizontal = 3 -script = ExtResource("1_toqtt") -sublabel_prefix = "┗ " - -[node name="HBox" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="InnerVBox" type="VBoxContainer" parent="HBox"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="LineEdit" type="LineEdit" parent="HBox/InnerVBox"] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="HBox/InnerVBox/LineEdit"] -layout_mode = 1 -anchors_preset = 11 -anchor_left = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -30.0 -grow_horizontal = 0 -grow_vertical = 2 -alignment = 2 - -[node name="CopyButton" type="Button" parent="HBox/InnerVBox/LineEdit/HBoxContainer"] -custom_minimum_size = Vector2(33, 25) -layout_mode = 2 -icon = ExtResource("2_lbcco") -flat = true -icon_alignment = 1 -expand_icon = true - -[node name="WarnLabel" type="Label" parent="HBox/InnerVBox"] -layout_mode = 2 -theme_type_variation = &"WarnLabel" -text = "Warning" - -[node name="NoteLabel" type="Label" parent="."] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 -theme_type_variation = &"NoteLabel" -theme_override_font_sizes/font_size = 12 -text = "Note: Description" -autowrap_mode = 3 - -[connection signal="focus_exited" from="HBox/InnerVBox/LineEdit" to="." method="_on_focus_exited"] -[connection signal="text_changed" from="HBox/InnerVBox/LineEdit" to="." method="_on_text_changed"] -[connection signal="text_submitted" from="HBox/InnerVBox/LineEdit" to="." method="_on_text_submitted"] -[connection signal="pressed" from="HBox/InnerVBox/LineEdit/HBoxContainer/CopyButton" to="." method="_on_copy_button_pressed"] diff --git a/common/ui/fields/list/monologue_list.gd b/common/ui/fields/list/monologue_list.gd deleted file mode 100644 index 6b8cf482..00000000 --- a/common/ui/fields/list/monologue_list.gd +++ /dev/null @@ -1,147 +0,0 @@ -## A list field that represents a [MonologueGraphEdit] data item. -class_name MonologueList extends MonologueField - -@onready var button := $CollapsibleField/Button -@onready var collapsible_container := $CollapsibleField/CollapsibleContainer -@onready var vbox := $CollapsibleField/CollapsibleContainer/PanelContainer/VBox -@onready var field_container := $CollapsibleField/CollapsibleContainer/PanelContainer/VBox/FieldContainer - -var delete_scene = preload("res://common/ui/buttons/delete_button.tscn") - -var add_callback: Callable = Constants.empty_callback -var delete_callback: Callable = func(list): return list -var get_callback: Callable = Constants.empty_callback -var data_list: Array = [] -var flat: bool = false -var expand: bool = false -var is_section: bool = false - - -func _ready() -> void: - collapsible_field = $CollapsibleField - collapsible_field.add_pressed.connect(_on_add_button_pressed) - collapsible_field.expand = expand - post_ready.call_deferred() - - if is_section: - collapsible_field.show_add_button = false - collapsible_field.expand = true - button.hide() - collapsible_container.add_theme_constant_override("margin_left", 0) - field_container.add_theme_constant_override("margin_left", 0) - - if flat: - collapsible_field.separate_items = false - button.hide() - collapsible_container.add_theme_constant_override("margin_left", 0) - field_container.add_theme_constant_override("margin_left", 0) - collapsible_field._update() - - -func post_ready() -> void: - if get_parent().get_child_count() <= 1: - collapsible_field.open() - - -## Add a new option node into the list and show its fields in the vbox. -func append_list_item(item) -> void: - var panel := create_flat_item_container() if flat else create_item_container() - var field_box = create_item_vbox(panel) - collapsible_field.add_item(panel, true) - for property_name in item.get_property_names(): - var property = item.get(property_name) - if not property.visible: - continue - var field = item.get(property_name).show(field_box, false) - field.set_label_text(Util.to_key_name(property_name, " ")) - var identifier = item.id.value if "id" in item else item.name.value - - if "custom_delete_button" in item and item.custom_delete_button: - item.custom_delete_button.connect("pressed", _on_delete_button_pressed.bind(identifier)) - return - create_delete_button(field_box, identifier) - - -func clear_list(): - collapsible_field.clear() - data_list = [] - - -func create_item_container() -> PanelContainer: - var item_container = PanelContainer.new() - item_container.theme_type_variation = "ItemContainer" - return item_container - - -func create_flat_item_container() -> PanelContainer: - var item_container = PanelContainer.new() - item_container.theme_type_variation = "ItemContainerFlat" - return item_container - - -func create_item_vbox(panel: PanelContainer) -> VBoxContainer: - var item_vbox = VBoxContainer.new() - panel.add_child(item_vbox, true) - return item_vbox - - -func create_delete_button(field_box: VBoxContainer, id: Variant) -> void: - var delete_button = delete_scene.instantiate() - delete_button.connect("pressed", _on_delete_button_pressed.bind(id)) - - var first_hbox = _find_first_hbox(field_box) - if first_hbox: - first_hbox.add_child(delete_button, true) - else: - delete_button.queue_free() - - -func set_label_text(text: String) -> void: - collapsible_field.set_title(text) - - -func set_label_visible(_can_see: bool) -> void: - pass - - -func use_custom_field_label() -> bool: - return true - - -func propagate(data: Variant) -> void: - super.propagate(data) - clear_list() - data_list = get_callback.call() - for reference in data_list: - append_list_item(reference) - data_list = data_list.map(func(r): return r._to_dict()) - - -func _find_first_hbox(control: Control) -> HBoxContainer: - for child in control.get_children(): - if child is HBoxContainer and child.visible: - return child - else: - var recursive = _find_first_hbox(child) - if recursive: - return recursive - return null - - -func _on_add_button_pressed() -> void: - # the add_callback creates the actual instance in its source node - data_list = get_callback.call() - data_list = data_list.map(func(r): return r._to_dict()) - var new_item = add_callback.call() - data_list.append(new_item._to_dict.call()) - append_list_item(new_item) - field_updated.emit(data_list) - - -func _on_delete_button_pressed(id: Variant) -> void: - for reference in data_list: - if reference.get("ID") == id or reference.get("Name") == id: - data_list.erase(reference) - break - var modified_list = delete_callback.call(data_list) - field_updated.emit(modified_list) diff --git a/common/ui/fields/list/monologue_list.gd.uid b/common/ui/fields/list/monologue_list.gd.uid deleted file mode 100644 index 988801fc..00000000 --- a/common/ui/fields/list/monologue_list.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://u77m3oxit3r8 diff --git a/common/ui/fields/list/monologue_list.tscn b/common/ui/fields/list/monologue_list.tscn deleted file mode 100644 index 4fb5a8c0..00000000 --- a/common/ui/fields/list/monologue_list.tscn +++ /dev/null @@ -1,19 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://ddc27sbeek1p"] - -[ext_resource type="Script" uid="uid://u77m3oxit3r8" path="res://common/ui/fields/list/monologue_list.gd" id="1_8kx3n"] -[ext_resource type="PackedScene" uid="uid://hvv74un17dp0" path="res://common/ui/fields/collapsible_field/collapsible_field.tscn" id="2_jih45"] - -[node name="MonologueList" type="VBoxContainer"] -anchors_preset = -1 -anchor_right = 0.125 -anchor_bottom = 0.062 -offset_bottom = 0.479996 -theme_override_constants/separation = 5 -script = ExtResource("1_8kx3n") - -[node name="CollapsibleField" parent="." instance=ExtResource("2_jih45")] -layout_mode = 2 -show_add_button = true -separate_items = true - -[editable path="CollapsibleField"] diff --git a/common/ui/fields/localizable.gd b/common/ui/fields/localizable.gd deleted file mode 100644 index f692da31..00000000 --- a/common/ui/fields/localizable.gd +++ /dev/null @@ -1,65 +0,0 @@ -## Properties which can have language switching. -class_name Localizable extends Property - -## Stores the value in a locale dictionary. -var raw_data: Dictionary = {} -## The value to be initialized on a new locale. Empty string by default. -var initialized_value: Variant - - -func _init(ui_scene: PackedScene, ui_setters: Dictionary = {}, default: Variant = "") -> void: - super(ui_scene, ui_setters, default) - initialized_value = default - GlobalSignal.add_listener("language_deleted", store_data) - - -## Gets the current language from the language switcher. -func get_locale() -> LanguageOption: - if GlobalVariables.language_switcher: - return GlobalVariables.language_switcher.get_current_language() - else: - return null - - -func get_value() -> Variant: - if GlobalVariables.language_switcher: - return raw_data.get(get_locale().name, initialized_value) - return super.get_value() - - -func set_value(new_value: Variant) -> void: - if GlobalVariables.language_switcher: - var langs = GlobalVariables.language_switcher.get_languages().keys() - if new_value is Dictionary and Util.is_any_inside(new_value.keys(), langs): - raw_data = _from_raw_value(new_value) - else: - raw_data[get_locale().name] = new_value - super.set_value(new_value) - - -func store_data(node_name: String, restoration: Dictionary, _choices: Dictionary) -> void: - if raw_data.has(node_name): - restoration[self] = raw_data.duplicate(true) - - -## Export property value as localized dictionary. -func to_raw_value() -> Variant: - if GlobalVariables.language_switcher: - var new_dict = {} - for key in raw_data: - var option = GlobalVariables.language_switcher.get_by_node_name(key) - if option: - new_dict[str(option)] = raw_data.get(key, initialized_value) - return new_dict - return value - - -## Private method to load property value is a localized dictionary. -func _from_raw_value(string_dict: Dictionary) -> Dictionary: - var language_dict = GlobalVariables.language_switcher.get_languages() - var new_dict = {} - for key in string_dict: - var language_node_name = language_dict.get(key) - if language_node_name: - new_dict[language_node_name] = string_dict.get(key) - return new_dict diff --git a/common/ui/fields/localizable.gd.uid b/common/ui/fields/localizable.gd.uid deleted file mode 100644 index 22b5f7d7..00000000 --- a/common/ui/fields/localizable.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bntwx0k2d3hqy diff --git a/common/ui/fields/monologue_argument.gd b/common/ui/fields/monologue_argument.gd deleted file mode 100644 index 6d2ca256..00000000 --- a/common/ui/fields/monologue_argument.gd +++ /dev/null @@ -1,89 +0,0 @@ -## Similar to MonologueVariable but allows reference to existing variables. -class_name MonologueArgument extends MonologueVariable - -var last_boolean: bool -var last_number: float -var last_string: String - - -func _init(node: MonologueGraphNode): - super(node.get_parent()) - type.callers["set_items"][0].append({"id": 3, "text": "Variable"}) - #type.callers["set_icons"][0][3] = load("res://ui/assets/icons/bool_icon.png") - value.connect("preview", record_morph) - - -func change(_old_value: Variant, new_value: Variant, property: String) -> void: - var old_list = graph.arguments.value.duplicate(true) - var new_list = graph.arguments.value.duplicate(true) - new_list[index][property.capitalize()] = new_value - - # bound_node can be deleted, so we need to use PropertyChange here - graph.undo_redo.create_action("Arguments => %s" % new_value) - var pc = PropertyChange.new("arguments", old_list, new_list) - var ph = PropertyHistory.new(graph, graph.get_path(), [pc]) - graph.undo_redo.add_prepared_history(ph) - graph.undo_redo.commit_action() - - -## Creates a representation of the argument in an HBoxContainer. -func create_representation(parent: Control) -> HBoxContainer: - var representation = HBoxContainer.new() - parent.add_child(representation) - - var name_label = Label.new() - var name_text = name.value if name.value else "argument" - name_label.text = " #%d: %s" % [representation.get_index(), name_text] - representation.add_child(name_label) - - var type_label = Label.new() - type_label.text = "[%s]" % type.value if type.value else "type" - representation.add_child(type_label) - - var value_label = Label.new() - value_label.theme_type_variation = "NodeValue" - value_label.text = ( - str(value.value) if value.value is not String or value.value != "" else "value" - ) - representation.add_child(value_label) - - return representation - - -## Record the argument value so field morphing will populate correct value type. -func record_morph(new_value: Variant) -> void: - match typeof(new_value): - TYPE_BOOL: - last_boolean = new_value - TYPE_INT, TYPE_FLOAT: - last_number = new_value - TYPE_STRING: - last_string = new_value - - -## Reset the value if the argument value is not matching the type. -func reset_value(): - match type.value: - "Boolean": - if value.value is not bool: - value.value = last_boolean - "Integer": - if value.value is not float and value.value is not int: - value.value = last_number - _: - if value.value is not String: - value.value = last_string - - -func _type_morph(selected_type: String = type.value): - if selected_type == "Variable": - value.callers["set_items"] = [graph.variables, "Name", "ID", "Type"] - if graph.variables and value.value is not String: - value.value = graph.variables.value[0].get("Name") - value.morph(MonologueGraphNode.DROPDOWN) - else: - value.callers.erase("set_items") - super._type_morph(selected_type) - - reset_value() - value.propagate(value.value, false) diff --git a/common/ui/fields/monologue_argument.gd.uid b/common/ui/fields/monologue_argument.gd.uid deleted file mode 100644 index ea371999..00000000 --- a/common/ui/fields/monologue_argument.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://4omqhch6lxme diff --git a/common/ui/fields/monologue_field.gd b/common/ui/fields/monologue_field.gd deleted file mode 100644 index a6b8a127..00000000 --- a/common/ui/fields/monologue_field.gd +++ /dev/null @@ -1,40 +0,0 @@ -## UI control which allow the user to edit a graph node [MonologueProperty]. -class_name MonologueField extends Control - -## Emitted when the field's value is changed but not yet committed. -@warning_ignore("unused_signal") -signal field_changed(value: Variant) - -## Emitted when the field's value is updated/comitted by user input. -@warning_ignore("unused_signal") -signal field_updated(value: Variant) - -var collapsible_field: CollapsibleField: - set = set_collapsible_field -var field_label: Label - - -## Set the collapsible control that this MonologueField belongs to. -func set_collapsible_field(collapsible: CollapsibleField): - collapsible_field = collapsible - - -## Called by node panel to set field label text, if applicable. -func set_label_text(text: String) -> void: - if not field_label: return - field_label.text = text - - -## Set the field's label visibility. -func set_label_visible(can_see: bool) -> void: - field_label.visible = can_see - - -## Meant to propagate the value set in [MonologueProperty] to this Field. -## This method does not emit [signal field_updated]. -func propagate(_value: Variant) -> void: - if collapsible_field: - collapsible_field.open() - -func use_custom_field_label() -> bool: - return false diff --git a/common/ui/fields/monologue_field.gd.uid b/common/ui/fields/monologue_field.gd.uid deleted file mode 100644 index 020c039d..00000000 --- a/common/ui/fields/monologue_field.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bkhv4rjlyh64j diff --git a/common/ui/fields/monologue_variable.gd b/common/ui/fields/monologue_variable.gd deleted file mode 100644 index 3179bac6..00000000 --- a/common/ui/fields/monologue_variable.gd +++ /dev/null @@ -1,70 +0,0 @@ -class_name MonologueVariable extends RefCounted - -var name := Property.new(MonologueGraphNode.LINE) -var type := Property.new(MonologueGraphNode.DROPDOWN, {}, "Boolean") -var value := Property.new(MonologueGraphNode.TOGGLE, {}, false) - -var index: int = -1 -var graph: MonologueGraphEdit - - -func _init(graph_edit: MonologueGraphEdit) -> void: - graph = graph_edit - - type.callers["set_items"] = [ - [ - {"id": 0, "text": "Boolean"}, - {"id": 1, "text": "Integer"}, - {"id": 2, "text": "String"}, - ] - ] - type.callers["set_icons"] = [ - { - 0: load("res://ui/assets/icons/bool_icon.png"), - 1: load("res://ui/assets/icons/int_icon.png"), - 2: load("res://ui/assets/icons/str_icon.png"), - } - ] - - type.connect("shown", _type_morph) - type.connect("change", change.bind("type")) - name.connect("change", change.bind("name")) - value.connect("change", change.bind("value")) - - -func change(_old_value: Variant, new_value: Variant, property: String) -> void: - var old_list = graph.variables.value.duplicate(true) - var new_list = graph.variables.value.duplicate(true) - new_list[index][property.capitalize()] = new_value - - graph.undo_redo.create_action("Variables => %s" % new_value) - graph.undo_redo.add_do_property(graph.variables, "value", new_list) - graph.undo_redo.add_do_method(graph.variables.propagate.bind(new_list)) - graph.undo_redo.add_undo_property(graph.variables, "value", old_list) - graph.undo_redo.add_undo_method(graph.variables.propagate.bind(old_list)) - graph.undo_redo.commit_action() - - -func get_property_names() -> PackedStringArray: - return ["name", "type", "value"] - - -func _from_dict(dict: Dictionary) -> void: - _type_morph() - name.value = dict.get("Name") - type.value = dict.get("Type") - value.value = dict.get("Value") - - -func _to_dict() -> Dictionary: - return {"Name": name.value, "Type": type.value, "Value": value.value} - - -func _type_morph(selected_type: String = type.value): - match selected_type: - "Boolean": - value.morph(MonologueGraphNode.TOGGLE) - "Integer": - value.morph(MonologueGraphNode.SPINBOX) - "String": - value.morph(MonologueGraphNode.LINE) diff --git a/common/ui/fields/monologue_variable.gd.uid b/common/ui/fields/monologue_variable.gd.uid deleted file mode 100644 index 47857bfa..00000000 --- a/common/ui/fields/monologue_variable.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bo7lnuuv6ra7k diff --git a/common/ui/fields/portrait_option/abstract_portrait.gd b/common/ui/fields/portrait_option/abstract_portrait.gd deleted file mode 100644 index 6041f04a..00000000 --- a/common/ui/fields/portrait_option/abstract_portrait.gd +++ /dev/null @@ -1,70 +0,0 @@ -class_name AbstractPortraitOption extends RefCounted - -const PORTRAIT_FIELD := preload("res://common/ui/fields/portrait_option/portrait_option.tscn") - -var portrait_name := Property.new(MonologueGraphNode.LINE, {}, "") -var portrait := Property.new(PORTRAIT_FIELD, {}, {}) -var id := Property.new(MonologueGraphNode.LINE, {}, IDGen.generate(5)) -var idx := Property.new(MonologueGraphNode.SPINBOX, {}, 0) - -var custom_delete_button: Button = Button.new() - -var graph: MonologueGraphEdit -var root: PortraitListSection - - -func _init(node: PortraitListSection): - root = node - portrait.connect("change", update_portrait) - portrait_name.connect("change", update_portrait_name) - portrait.setters["graph_edit"] = graph - portrait_name.visible = false - - -func update_portrait(old_value: Variant, new_value: Variant): - var old_list = root.portraits.value.duplicate(true) - var new_list = root.portraits.value.duplicate(true) - - graph.undo_redo.create_action("Portrait %s => %s" % [str(old_value), str(new_value)]) - graph.undo_redo.add_do_property(root.portraits, "value", new_list) - graph.undo_redo.add_do_method(root.portraits.propagate.bind(new_list)) - graph.undo_redo.add_undo_property(root.portraits, "value", old_list) - graph.undo_redo.add_undo_method(root.portraits.propagate.bind(old_list)) - graph.undo_redo.commit_action() - - -func update_portrait_name(old_value: Variant, new_value: Variant): - var old_list = root.portraits.value.duplicate(true) - var new_list = root.portraits.value.duplicate(true) - - old_list[idx.value]["Name"] = old_value - new_list[idx.value]["Name"] = new_value - - graph.undo_redo.create_action("Portrait Name %s => %s" % [str(old_value), str(new_value)]) - graph.undo_redo.add_do_property(root.portraits, "value", new_list) - graph.undo_redo.add_do_method(root.portraits.propagate.bind(new_list)) - graph.undo_redo.add_undo_property(root.portraits, "value", old_list) - graph.undo_redo.add_undo_method(root.portraits.propagate.bind(old_list)) - graph.undo_redo.commit_action() - - -func get_property_names() -> PackedStringArray: - return ["portrait"] - - -func _from_dict(dict: Dictionary) -> void: - if dict.get("ID") is String: - id.value = dict.get("ID", IDGen.generate(5)) - portrait_name.value = dict.get("Name", "") - portrait.value = dict.get("Portrait", {}) - idx.value = dict.get("EditorIndex") - portrait.setters["portrait_index"] = idx.value - - -func _to_dict() -> Dictionary: - return { - "ID": id.value, - "Name": portrait_name.value, - "Portrait": portrait.value, - "EditorIndex": idx.value, - } diff --git a/common/ui/fields/portrait_option/abstract_portrait.gd.uid b/common/ui/fields/portrait_option/abstract_portrait.gd.uid deleted file mode 100644 index 95793685..00000000 --- a/common/ui/fields/portrait_option/abstract_portrait.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cqf51jo7a4llv diff --git a/common/ui/fields/portrait_option/portrait_option.gd b/common/ui/fields/portrait_option/portrait_option.gd deleted file mode 100644 index 7ad050f7..00000000 --- a/common/ui/fields/portrait_option/portrait_option.gd +++ /dev/null @@ -1,102 +0,0 @@ -class_name PortraitOption extends MonologueField - -signal pressed(this_option: PortraitOption) -signal set_to_default(this_option: PortraitOption) -signal name_submitted(this_option: PortraitOption) - -@onready var line_edit: LineEdit = %LineEdit -@onready var btn_star := $MarginContainer/HBoxContainer/HBoxContainer/btnStar -@onready var button := %Button - -@onready var active_stylebox: StyleBoxFlat = StyleBoxFlat.new() -@onready var star_icon := preload("res://ui/assets/icons/star.svg") -@onready var star_full_icon := preload("res://ui/assets/icons/star_full.svg") - -@export var is_default: bool = false - -var is_active: bool = false -var line_edit_unfocus_stylebox := StyleBoxEmpty.new() -var custom_delete_button: Button = Button.new() - - -func _ready() -> void: - line_edit_unfocus() - - active_stylebox.bg_color = Color("d55160") - active_stylebox.set_corner_radius_all(5) - - -func propagate(value: Variant) -> void: - super.propagate(value) - - -func set_option_name(new_name: String) -> void: - line_edit.text = new_name - - -func line_edit_unfocus() -> void: - line_edit.editable = false - line_edit.selecting_enabled = false - line_edit.flat = true - line_edit.mouse_filter = Control.MOUSE_FILTER_PASS - - button.show() - add_theme_stylebox_override("focus", line_edit_unfocus_stylebox) - name_submitted.emit(self) - - -func _on_btn_edit_pressed() -> void: - line_edit.editable = true - line_edit.selecting_enabled = true - line_edit.flat = false - line_edit.mouse_filter = Control.MOUSE_FILTER_STOP - line_edit.grab_focus() - - button.hide() - - -func _on_line_edit_focus_exited() -> void: - line_edit_unfocus() - - -func _on_btn_star_pressed() -> void: - set_default() - - -func set_default() -> void: - is_default = true - btn_star.texture_normal = star_full_icon - set_to_default.emit(self) - - -func release_default() -> void: - is_default = false - btn_star.texture_normal = star_icon - - -func set_active() -> void: - is_active = true - add_theme_stylebox_override("panel", active_stylebox) - - -func release_active() -> void: - is_active = false - remove_theme_stylebox_override("panel") - line_edit_unfocus() - - -func _on_button_pressed() -> void: - pressed.emit(self) - - -func _on_button_gui_input(event: InputEvent) -> void: - if is_active and event is InputEventMouseButton and event.is_pressed() and event.double_click: - _on_btn_edit_pressed() - - -func _on_line_edit_text_submitted(_new_text: String) -> void: - line_edit_unfocus() - - -func use_custom_field_label() -> bool: - return true diff --git a/common/ui/fields/portrait_option/portrait_option.gd.uid b/common/ui/fields/portrait_option/portrait_option.gd.uid deleted file mode 100644 index c2e25a2b..00000000 --- a/common/ui/fields/portrait_option/portrait_option.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://8i3yw0dywx4c diff --git a/common/ui/fields/portrait_option/portrait_option.tscn b/common/ui/fields/portrait_option/portrait_option.tscn deleted file mode 100644 index 7483cd82..00000000 --- a/common/ui/fields/portrait_option/portrait_option.tscn +++ /dev/null @@ -1,80 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://c3jo73wxyv6ux"] - -[ext_resource type="Script" uid="uid://8i3yw0dywx4c" path="res://common/ui/fields/portrait_option/portrait_option.gd" id="1_0c6eg"] -[ext_resource type="Texture2D" uid="uid://bogfuvhttgn1v" path="res://ui/assets/icons/star.svg" id="3_p62s6"] - -[node name="PortraitOption" type="Panel"] -custom_minimum_size = Vector2(0, 40) -anchors_preset = -1 -anchor_right = 0.096 -anchor_bottom = 0.05 -offset_right = -0.399994 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -script = ExtResource("1_0c6eg") - -[node name="MarginContainer" type="MarginContainer" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"] -layout_mode = 2 - -[node name="Control" type="Control" parent="MarginContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/HBoxContainer/Control"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="LineEdit" type="LineEdit" parent="MarginContainer/HBoxContainer/Control/HBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 4 -theme_type_variation = &"LineEditPortraitOption" -text = "default" -flat = true -caret_blink = true -text_direction = 1 - -[node name="Button" type="Button" parent="MarginContainer/HBoxContainer/Control"] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = -1 -anchor_left = -0.047 -anchor_top = -0.167 -anchor_right = 1.081 -anchor_bottom = 1.167 -offset_left = 0.0420003 -offset_top = 0.00800037 -offset_right = 0.033989 -offset_bottom = -0.00800133 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 1 -flat = true - -[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/HBoxContainer"] -layout_mode = 2 - -[node name="btnStar" type="TextureButton" parent="MarginContainer/HBoxContainer/HBoxContainer"] -layout_mode = 2 -texture_normal = ExtResource("3_p62s6") -stretch_mode = 5 - -[connection signal="focus_exited" from="MarginContainer/HBoxContainer/Control/HBoxContainer/LineEdit" to="." method="_on_line_edit_focus_exited"] -[connection signal="text_submitted" from="MarginContainer/HBoxContainer/Control/HBoxContainer/LineEdit" to="." method="_on_line_edit_text_submitted"] -[connection signal="gui_input" from="MarginContainer/HBoxContainer/Control/Button" to="." method="_on_button_gui_input"] -[connection signal="pressed" from="MarginContainer/HBoxContainer/Control/Button" to="." method="_on_button_pressed"] -[connection signal="pressed" from="MarginContainer/HBoxContainer/HBoxContainer/btnStar" to="." method="_on_btn_star_pressed"] diff --git a/common/ui/fields/property.gd b/common/ui/fields/property.gd deleted file mode 100644 index de3195e7..00000000 --- a/common/ui/fields/property.gd +++ /dev/null @@ -1,157 +0,0 @@ -## Represents a graph node property and its UI controls in Monologue. -class_name Property extends RefCounted - -## Emitted when property change is to be commited to undo/redo history. -signal change(old_value: Variant, new_value: Variant) -## Emitted if the graph node of this property should be displayed in panel. -signal display -## Emitted when the field's value is being changed and is requesting a preview. -signal preview(value: Variant) -## Emitted on show() and only if the field is visible to the user. -signal shown - -## Dictionary of field method names to argument list values. -var callers: Dictionary = {} -## Reference to UI instance. -var field: Control -## Reference to the field container. -var field_container: Control -## Scene used to instantiate the field's UI control. -var scene: PackedScene -## Dictionary of field property names to set values. -var setters: Dictionary -## Dictionary of callables to connect to field signals. -var connecters: Dictionary -## Temporary boolean to uncollapse the field when first shown if set to true. -var uncollapse: bool -## Initial value of the property. -var default_value: Variant -## Actual value of the property. -var value: Variant: - set = set_value, - get = get_value -## Toggles visibility of the field instance. -var visible: bool: - set = set_visible -## Overwrites the displayed property label -var custom_label: Variant - - -var field_label := preload("res://common/ui/fields/field_label.tscn") - - -func _init( - ui_scene: PackedScene, - ui_setters: Dictionary = {}, - default: Variant = "", - ui_custom_label: Variant = null -) -> void: - scene = ui_scene - setters = ui_setters - value = default - default_value = default - custom_label = ui_custom_label - visible = true - - -func get_value() -> Variant: - return value - - -## Invokes a given method with the given arguments on the field if present. -func invoke(method_name: String, argument_list: Array) -> Variant: - if is_instance_valid(field): - return field.callv(method_name, argument_list) - return null - - -## Change the property's UI scene and replace the active field instance. -func morph(new_scene: PackedScene) -> void: - scene = new_scene - if is_instance_valid(field): - var panel = field.get_parent() - var child_index = field.get_index() - field_container.queue_free() - show(panel, child_index) - - -func propagate(new_value: Variant, can_display: bool = true) -> void: - preview.emit(new_value) - if is_instance_valid(field): - field.propagate(new_value) - elif can_display or not visible: - uncollapse = true - display.emit() - - -## Trigger a property value change only when valeus are different. -func save_value(new_value: Variant) -> void: - if new_value is Dictionary: - new_value = value.merged(new_value, true) - - if not Util.is_equal(value, new_value): - change.emit(value, new_value) - - -## Setter for value which does not trigger change signals. -func set_value(new_value: Variant) -> void: - value = new_value - - -func set_visible(can_see: bool) -> void: - visible = can_see - _check_visibility() - - -func show(panel: Control, child_index: int = -1, auto_margin: bool = true) -> MonologueField: - field = scene.instantiate() - - field_container = MarginContainer.new() - field_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL - field_container.size_flags_vertical = field.size_flags_vertical - field_container.add_theme_constant_override("margin_right", 0) - field_container.add_theme_constant_override("margin_bottom", 0) - if field is CollapsibleField or field is MonologueList or not auto_margin: - field_container.add_theme_constant_override("margin_left", 0) - field_container.add_theme_constant_override("margin_top", 0) - - var field_box := HBoxContainer.new() - field_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL - field.size_flags_horizontal = Control.SIZE_EXPAND_FILL - field_box.add_theme_constant_override("separation", 0) - if field is MonologueField and not field.use_custom_field_label(): - var label := field_label.instantiate() - field.field_label = label - field_box.add_child(label) - - for property in setters.keys(): - field.set(property, setters.get(property)) - - field_box.add_child(field) - field_container.add_child(field_box) - panel.add_child(field_container) - _check_visibility() - - if child_index >= 0: - panel.move_child(field_container, child_index) - - for method in callers.keys(): - field.callv(method, callers.get(method, [])) - - for callable in connecters.keys(): - var signal_name: String = connecters.get(callable, "") - if field.has_signal(signal_name): - field.connect(connecters.get(callable), callable) - - field.propagate(value) - field.connect("field_changed", preview.emit) - field.connect("field_updated", save_value) - _check_visibility() - if visible: - shown.emit() - return field - - -func _check_visibility(): - if is_instance_valid(field): - field_container.visible = visible diff --git a/common/ui/fields/property.gd.uid b/common/ui/fields/property.gd.uid deleted file mode 100644 index 44756639..00000000 --- a/common/ui/fields/property.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://chah3f6jf1tls diff --git a/common/ui/fields/property_list.gd b/common/ui/fields/property_list.gd deleted file mode 100644 index 9ff5796e..00000000 --- a/common/ui/fields/property_list.gd +++ /dev/null @@ -1,28 +0,0 @@ -## Represents a graph node property list and its UI controls in Monologue. -class_name PropertyList extends Property - -## Scene used to instantiate the item field's UI control. -var item_scene: PackedScene - - -func _init( - ui_item_scene: PackedScene, - ui_setters: Dictionary = {}, - default: Variant = "", - ui_custom_label: Variant = null -) -> void: - item_scene = ui_item_scene - scene = MonologueGraphNode.LIST - setters = ui_setters - value = default - default_value = default - custom_label = ui_custom_label - visible = true - - -func add_item() -> Control: - return Control.new() - - -func get_items() -> Array: - return [] diff --git a/common/ui/fields/property_list.gd.uid b/common/ui/fields/property_list.gd.uid deleted file mode 100644 index 2a62388c..00000000 --- a/common/ui/fields/property_list.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://f2xkawigtf41 diff --git a/common/ui/fields/slider/monologue_slider.gd b/common/ui/fields/slider/monologue_slider.gd deleted file mode 100644 index 8a91584f..00000000 --- a/common/ui/fields/slider/monologue_slider.gd +++ /dev/null @@ -1,54 +0,0 @@ -class_name MonologueSlider extends MonologueField - -@export var default: float -@export var minimum: float -@export var maximum: float -@export var step: float -@export var suffix: String - -@onready var spin_box = $SpinBox -@onready var reset_button = $ResetButton -@onready var slider = $HSlider - -var skip_spin_box_update: bool = false - - -func _ready(): - slider.min_value = minimum - slider.max_value = maximum - slider.step = step - - spin_box.min_value = minimum - spin_box.max_value = maximum - spin_box.step = step - spin_box.suffix = suffix - - -func propagate(value: Variant) -> void: - super.propagate(value) - slider.value = value if (value is float or value is int) else default - - -func _on_drag_ended(value_changed: bool) -> void: - if value_changed: - field_updated.emit(slider.value) - - -func _on_reset() -> void: - if slider.value != default: - slider.value = default - field_updated.emit(default) - - -func _on_value_changed(value: float) -> void: - skip_spin_box_update = true - spin_box.value = value - - -func _on_spin_box_value_changed(value: float) -> void: - if skip_spin_box_update: - skip_spin_box_update = false - return - - slider.value = value - field_updated.emit(slider.value) diff --git a/common/ui/fields/slider/monologue_slider.gd.uid b/common/ui/fields/slider/monologue_slider.gd.uid deleted file mode 100644 index 773fc609..00000000 --- a/common/ui/fields/slider/monologue_slider.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bfviu4cmsm4jo diff --git a/common/ui/fields/slider/monologue_slider.tscn b/common/ui/fields/slider/monologue_slider.tscn deleted file mode 100644 index b0609525..00000000 --- a/common/ui/fields/slider/monologue_slider.tscn +++ /dev/null @@ -1,65 +0,0 @@ -[gd_scene load_steps=11 format=3 uid="uid://cndkr1vq6ab1o"] - -[ext_resource type="Script" uid="uid://bfviu4cmsm4jo" path="res://common/ui/fields/slider/monologue_slider.gd" id="1_waj5i"] - -[sub_resource type="Texture2DRD" id="Texture2DRD_ipp53"] - -[sub_resource type="Texture2DRD" id="Texture2DRD_8e8dw"] - -[sub_resource type="Texture2DRD" id="Texture2DRD_cqv1s"] - -[sub_resource type="Texture2DRD" id="Texture2DRD_w48go"] - -[sub_resource type="Texture2DRD" id="Texture2DRD_peeyv"] - -[sub_resource type="Texture2DRD" id="Texture2DRD_delru"] - -[sub_resource type="Texture2DRD" id="Texture2DRD_8xcfa"] - -[sub_resource type="Texture2DRD" id="Texture2DRD_catv5"] - -[sub_resource type="Texture2DRD" id="Texture2DRD_d3004"] - -[node name="MonologueSlider" type="HBoxContainer"] -offset_right = 291.0 -offset_bottom = 29.0 -script = ExtResource("1_waj5i") - -[node name="HSlider" type="HSlider" parent="."] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 4 -min_value = -100.0 - -[node name="SpinBox" type="SpinBox" parent="."] -custom_minimum_size = Vector2(60, 0) -layout_mode = 2 -theme_override_constants/set_min_buttons_width_from_icons = 0 -theme_override_constants/buttons_width = 0 -theme_override_constants/field_and_buttons_separation = 0 -theme_override_constants/buttons_vertical_separation = 0 -theme_override_icons/up_disabled = SubResource("Texture2DRD_ipp53") -theme_override_icons/down = SubResource("Texture2DRD_8e8dw") -theme_override_icons/down_hover = SubResource("Texture2DRD_cqv1s") -theme_override_icons/down_pressed = SubResource("Texture2DRD_w48go") -theme_override_icons/down_disabled = SubResource("Texture2DRD_peeyv") -theme_override_icons/up_pressed = SubResource("Texture2DRD_delru") -theme_override_icons/up_hover = SubResource("Texture2DRD_8xcfa") -theme_override_icons/up = SubResource("Texture2DRD_catv5") -theme_override_icons/updown = SubResource("Texture2DRD_d3004") -alignment = 1 -update_on_text_changed = true -select_all_on_focus = true - -[node name="ResetButton" type="Button" parent="."] -visible = false -layout_mode = 2 -size_flags_vertical = 4 -theme_type_variation = &"Button_Outline" -text = "reset" - -[connection signal="drag_ended" from="HSlider" to="." method="_on_drag_ended"] -[connection signal="value_changed" from="HSlider" to="." method="_on_value_changed"] -[connection signal="value_changed" from="SpinBox" to="." method="_on_spin_box_value_changed"] -[connection signal="pressed" from="ResetButton" to="." method="_on_reset"] diff --git a/common/ui/fields/spin_box/monologue_spin_box.gd b/common/ui/fields/spin_box/monologue_spin_box.gd deleted file mode 100644 index c0543a9f..00000000 --- a/common/ui/fields/spin_box/monologue_spin_box.gd +++ /dev/null @@ -1,26 +0,0 @@ -class_name MonologueSpinBox extends MonologueField - -@export var as_integer: bool = true -@export var minimum: float = -9999999999 -@export var maximum: float = 9999999999 -@export var step: float = 1 -@export var suffix: String - -@onready var spin_box = $CustomSpinBox - - -func _ready(): - spin_box.min_value = minimum - spin_box.max_value = maximum - spin_box.step = step - spin_box.suffix = suffix - spin_box._update_settings() - - -func propagate(value: Variant) -> void: - super.propagate(value) - spin_box.value = value if (value is float or value is int) else 0 - - -func _on_custom_spin_box_value_changed(value: Variant) -> void: - field_updated.emit(value) diff --git a/common/ui/fields/spin_box/monologue_spin_box.gd.uid b/common/ui/fields/spin_box/monologue_spin_box.gd.uid deleted file mode 100644 index 5502bee1..00000000 --- a/common/ui/fields/spin_box/monologue_spin_box.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://catei6iaaw77f diff --git a/common/ui/fields/spin_box/monologue_spin_box.tscn b/common/ui/fields/spin_box/monologue_spin_box.tscn deleted file mode 100644 index 252b5995..00000000 --- a/common/ui/fields/spin_box/monologue_spin_box.tscn +++ /dev/null @@ -1,12 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://c0d8fac8so0p0"] - -[ext_resource type="Script" uid="uid://catei6iaaw77f" path="res://common/ui/fields/spin_box/monologue_spin_box.gd" id="1_wmtop"] -[ext_resource type="PackedScene" uid="uid://wiapsnoaoc44" path="res://common/ui/custom_spinbox/custom_spinbox.tscn" id="3_a42se"] - -[node name="MonologueSpinBox" type="HBoxContainer"] -script = ExtResource("1_wmtop") - -[node name="CustomSpinBox" parent="." instance=ExtResource("3_a42se")] -layout_mode = 2 - -[connection signal="value_changed" from="CustomSpinBox" to="." method="_on_custom_spin_box_value_changed"] diff --git a/common/ui/fields/text/monologue_text.gd b/common/ui/fields/text/monologue_text.gd deleted file mode 100644 index 2889ba5d..00000000 --- a/common/ui/fields/text/monologue_text.gd +++ /dev/null @@ -1,28 +0,0 @@ -class_name MonologueText extends MonologueField - -@export var minimum_size := Vector2(200, 200) - -@onready var text_edit = $TextEdit -@onready var expand_container = $TextEdit/Button -@onready var expand_button = $TextEdit/Button - - -func _ready(): - text_edit.custom_minimum_size = minimum_size - - -func propagate(value: Variant) -> void: - super.propagate(value) - text_edit.text = str(value) - - -func _on_focus_exited() -> void: - field_updated.emit(text_edit.text) - - -func _on_text_changed() -> void: - field_changed.emit(text_edit.text) - - -func _on_button_pressed() -> void: - GlobalSignal.emit("expand_text_edit", [text_edit]) diff --git a/common/ui/fields/text/monologue_text.gd.uid b/common/ui/fields/text/monologue_text.gd.uid deleted file mode 100644 index fd975a2c..00000000 --- a/common/ui/fields/text/monologue_text.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://laakj3xm565t diff --git a/common/ui/fields/text/monologue_text.tscn b/common/ui/fields/text/monologue_text.tscn deleted file mode 100644 index 4b56de7d..00000000 --- a/common/ui/fields/text/monologue_text.tscn +++ /dev/null @@ -1,54 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://durq2yowmkr60"] - -[ext_resource type="Script" uid="uid://laakj3xm565t" path="res://common/ui/fields/text/monologue_text.gd" id="1_m7tlj"] -[ext_resource type="Texture2D" uid="uid://bu603ytypk2jb" path="res://ui/assets/icons/expand.svg" id="2_mkdom"] - -[node name="MonologueText" type="HBoxContainer"] -size_flags_horizontal = 3 -script = ExtResource("1_m7tlj") - -[node name="TextEdit" type="TextEdit" parent="."] -custom_minimum_size = Vector2(200, 200) -layout_mode = 2 -size_flags_horizontal = 3 -wrap_mode = 1 -caret_blink = true - -[node name="Button" type="Button" parent="TextEdit"] -custom_minimum_size = Vector2(22, 22) -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -22.0 -offset_top = 2.0 -offset_right = -2.0 -offset_bottom = 22.0 -grow_horizontal = 0 - -[node name="CenterContainer" type="CenterContainer" parent="TextEdit/Button"] -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -7.5 -offset_top = -7.5 -offset_right = 7.5 -offset_bottom = 7.5 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 - -[node name="TextureRect" type="TextureRect" parent="TextEdit/Button/CenterContainer"] -custom_minimum_size = Vector2(14, 14) -layout_mode = 2 -mouse_filter = 2 -texture = ExtResource("2_mkdom") -expand_mode = 4 -stretch_mode = 5 - -[connection signal="focus_exited" from="TextEdit" to="." method="_on_focus_exited"] -[connection signal="text_changed" from="TextEdit" to="." method="_on_text_changed"] -[connection signal="pressed" from="TextEdit/Button" to="." method="_on_button_pressed"] diff --git a/common/ui/fields/timeline/monologue_timeline.gd b/common/ui/fields/timeline/monologue_timeline.gd deleted file mode 100644 index f4b575d5..00000000 --- a/common/ui/fields/timeline/monologue_timeline.gd +++ /dev/null @@ -1,331 +0,0 @@ -class_name MonologueAnimationTimeline extends MonologueField - -const IMAGE = ["*.bmp,*.jpg,*.jpeg,*.png,*.svg,*.webp;Image Files"] -const DEFAULT_LAYER_NAME: String = "Layer %s" - -var filters: Array = ["*.bmp", "*.jpg", "*.jpeg", "*.png", "*.svg", "*.webp"] - -@onready var layer_vbox := %LayerVBox -@onready var layer_timeline_vbox := %LayerTimelineVBox -@onready var cell_number_hbox := %CellNumberHBox -@onready var fps_spinbox := %FpsSpinBox -@onready var layer_container := %LayerContainer -@onready var import_frame_button := %ImportFrameButton - -@onready var layer := preload("res://common/ui/fields/timeline/timeline_layer.tscn") -@onready var layer_timeline := preload("res://common/ui/fields/timeline/timeline_cell_layer.tscn") -@onready var cell_number := preload("res://common/ui/fields/timeline/timeline_cell_number.tscn") -@onready var placement_indicator := preload("res://common/ui/horizontal_placement_indicator.tscn") - -var cell_count: int = 1 -var base_path: String -var selected_cell_idx: int = -1 -var selected_cell_layer_idx: int = -1 -var current_indicator: Control -var selected_layer: Layer -var preview_section - -var fps: float = 12.0 - - -func _process(_delta: float) -> void: - if current_indicator == null: - return - var indicator_dist: float = current_indicator.global_position.y - get_global_mouse_position().y - var layer_height: float = get_layer_height() - var layer_dist: float = ( - get_global_mouse_position().y - (selected_layer.global_position.y + layer_height / 2.0) - ) - var indicator_index: int = current_indicator.get_index() - - current_indicator.show() - if indicator_dist >= layer_height / 2.0: - layer_vbox.move_child(current_indicator, indicator_index - 1) - elif indicator_dist <= -layer_height / 2.0: - layer_vbox.move_child(current_indicator, indicator_index + 1) - elif abs(layer_dist) < layer_height: - current_indicator.hide() - - -func _clear() -> void: - cell_count = 1 - for child in layer_vbox.get_children(): - child.queue_free() - for child in layer_timeline_vbox.get_children(): - child.queue_free() - - -func propagate(value: Variant) -> void: - super.propagate(value) - _from_dict(value) - - -func use_custom_field_label() -> bool: - return true - - -func _from_dict(dict: Dictionary) -> void: - _clear() - cell_count = dict.get("FrameCount", 1) - fps_spinbox.value = dict.get("Fps", 12) - selected_cell_idx = -1 - selected_cell_layer_idx = -1 - - var default_layer_data := [ - {"LayerName": DEFAULT_LAYER_NAME % 1, "Frames": {0: {"ImagePath": "", "Exposure": 1}}} - ] - - for layer_data in dict.get("Layers", default_layer_data): - var new_layer: Layer = add_timeline() - new_layer.timeline_label.text = layer_data.get("LayerName", "Layer") - layer_timeline_vbox.get_children().back()._from_dict(layer_data) - - _update_cell_number() - _update_preview.call_deferred() - - -func _to_dict() -> Dictionary: - var dict: Dictionary = {"Fps": fps, "FrameCount": cell_count, "Layers": []} - var layers: Array = get_all_layers() - - for l: Layer in layers: - var layer_idx: int = layers.find(l) - var l_timeline: LayerTimeline = layer_timeline_vbox.get_child(layer_idx) - dict["Layers"].append({"LayerName": l.timeline_label.text, "Frames": l_timeline._to_dict()}) - return dict - - -func get_all_layers() -> Array: - var layers: Array = [] - for child in layer_vbox.get_children(): - if child is not Layer or child.is_queued_for_deletion(): - continue - layers.append(child) - - return layers - - -func get_cell_width() -> int: - return layer_container.cell_width - - -func get_layer_height() -> int: - return get_all_layers()[0].size.y - - -func add_cell() -> void: - cell_count += 1 - _update_cell_number() - - -func add_timeline() -> Layer: - var new_layer: Layer = layer.instantiate() - var new_layer_timeline: LayerTimeline = layer_timeline.instantiate() - new_layer_timeline.timeline = self - - layer_vbox.add_child(new_layer) - layer_timeline_vbox.add_child(new_layer_timeline) - - new_layer.timeline_label.text = DEFAULT_LAYER_NAME % layer_vbox.get_child_count() - new_layer.hover_button.connect("button_down", _on_layer_button_down.bind(new_layer)) - new_layer.hover_button.connect("button_up", _on_layer_button_up.bind(new_layer)) - new_layer.delete_button_pressed.connect(_on_layer_delete_button_pressed.bind(new_layer)) - new_layer_timeline.connect("timeline_updated", _on_timeline_updated.bind(new_layer_timeline)) - - _update_preview() - - return new_layer - - -func _update_cell_number() -> void: - for cell in cell_number_hbox.get_children(): - cell.queue_free() - for i in range(cell_count): - var new_cell := cell_number.instantiate() - new_cell.cell_number = i + 1 - new_cell.custom_minimum_size.x = get_cell_width() - cell_number_hbox.add_child(new_cell) - - -func _update_preview() -> void: - if layer_timeline_vbox == null: - return - var sprites: Array = [] - var layers: Array[Node] = layer_timeline_vbox.get_children() - layers.reverse() - for child_timeline: LayerTimeline in layers: - sprites.append(child_timeline._to_sprite_frames()) - preview_section.update_animation(sprites) - - -func _on_timeline_updated(_layer_timeline: LayerTimeline) -> void: - _update_field.call_deferred() - - -func _update_field() -> void: - _update_preview() - field_updated.emit(_to_dict()) - - -func _on_btn_add_cell_pressed() -> void: - add_cell() - _update_field.call_deferred() - - -func _on_btn_add_layer_pressed() -> void: - add_timeline() - _update_field.call_deferred() - - -func _on_import_frame_button_pressed() -> void: - if selected_cell_idx <= -1 and selected_cell_layer_idx <= -1: - return - GlobalSignal.emit("open_files_request", [_on_files_selected, IMAGE, base_path.get_base_dir()]) - - -func get_selected_cell() -> Variant: - if selected_cell_idx <= -1 and selected_cell_layer_idx <= -1: - return null - - var s_layer_timeline: LayerTimeline = ( - layer_timeline_vbox.get_children()[selected_cell_layer_idx] - ) - return s_layer_timeline.get_all_cells()[selected_cell_idx] - - -func _on_files_selected(paths: Array) -> void: - if selected_cell_idx <= -1 and selected_cell_layer_idx <= -1: - return - - var first_path: String = paths.pop_front() - get_selected_cell().image_path = Path.absolute_to_relative(first_path, base_path) - get_selected_cell()._update() - - var selected_cell_layer: LayerTimeline = layer_timeline_vbox.get_child(selected_cell_layer_idx) - var first_frame_duration: int = int(selected_cell_layer.get_frame_duration(selected_cell_idx)) - var idx: int = 0 - for path in paths: - idx += 1 - var cell: TimelineCell = selected_cell_layer.add_cell( - Path.absolute_to_relative(path, base_path) - ) - selected_cell_layer.hbox.move_child(cell, selected_cell_idx + idx * first_frame_duration) - - for i in range(first_frame_duration - 1): - var exp_cell: TimelineCell = selected_cell_layer.add_cell() - exp_cell.is_exposure = true - selected_cell_layer.hbox.move_child( - exp_cell, selected_cell_idx + idx * first_frame_duration + i + 1 - ) - exp_cell._update() - - _update_field.call_deferred() - - -func cell_selected(s_cell: TimelineCell, s_timeline: LayerTimeline) -> void: - var cell_idx: int = s_timeline.get_all_cells().find(s_cell) - var timeline_idx: int = layer_timeline_vbox.get_children().find(s_timeline) - selected_cell_idx = cell_idx - selected_cell_layer_idx = timeline_idx - sub_select(cell_idx, timeline_idx) - if not s_cell.is_exposure: - import_frame_button.disabled = false - - -func cell_deselected() -> void: - var disable_func: Callable = func() -> void: - if import_frame_button.has_focus(): - return - import_frame_button.disabled = true - selected_cell_idx = -1 - selected_cell_layer_idx = -1 - sub_select(-1, -1) - disable_func.call_deferred() - - -func sub_select(col_idx: int, row_idx: int) -> void: - var deselect: bool = col_idx <= -1 and row_idx <= -1 - for cell in cell_number_hbox.get_children(): - cell.reset_style() - var timeline_idx: int = 0 - for t: LayerTimeline in layer_timeline_vbox.get_children(): - var cell_idx: int = 0 - for cell in t.get_all_cells(): - if cell_idx == col_idx and not deselect: - cell.sub_select() - if row_idx != timeline_idx: - cell.lose_focus() - else: - cell.reset_style() - cell.lose_focus() - cell_idx += 1 - timeline_idx += 1 - if not deselect: - cell_number_hbox.get_child(col_idx).sub_select() - - -func _on_layer_scroll_container_gui_input(_event: InputEvent) -> void: - %LayerTimelineScrollContainer.scroll_vertical = %LayerScrollContainer.scroll_vertical - - -func _on_layer_timeline_scroll_container_gui_input(_event: InputEvent) -> void: - %LayerScrollContainer.scroll_vertical = %LayerTimelineScrollContainer.scroll_vertical - - -func _on_layer_button_down(target_layer: Layer) -> void: - var layer_idx: int = get_all_layers().find(target_layer) - current_indicator = placement_indicator.instantiate() - layer_vbox.add_child(current_indicator) - layer_vbox.move_child(current_indicator, layer_idx + 1) - selected_layer = target_layer - - -func _on_layer_button_up(target_layer: Layer) -> void: - selected_layer = null - if not current_indicator.visible: - current_indicator.free() - current_indicator = null - return - - var new_placement_idx: int = current_indicator.get_index() - var layer_idx: int = get_all_layers().find(target_layer) - var t_layer_timeline: LayerTimeline = layer_timeline_vbox.get_child(layer_idx) - layer_vbox.move_child(target_layer, new_placement_idx) - current_indicator.free() - current_indicator = null - layer_timeline_vbox.move_child(t_layer_timeline, get_all_layers().find(target_layer)) - _update_field.call_deferred() - - -func _on_layer_delete_button_pressed(target_layer: Layer) -> void: - var layer_idx: int = get_all_layers().find(target_layer) - var t_layer_timeline: LayerTimeline = layer_timeline_vbox.get_child(layer_idx - 1) - t_layer_timeline.queue_free() - target_layer.queue_free() - _update_field.call_deferred() - - -func _on_fps_spin_box_value_changed(value: float) -> void: - if value != fps: - fps = value - _update_field.call_deferred() - - -func _on_play_backwards_button_pressed() -> void: - preview_section.play_backwards() - - -func _on_skip_backward_button_pressed() -> void: - pass # Replace with function body. - - -func _on_stop_button_pressed() -> void: - preview_section.stop() - - -func _on_skip_forward_button_pressed() -> void: - pass # Replace with function body. - - -func _on_play_button_pressed() -> void: - preview_section.play() diff --git a/common/ui/fields/timeline/monologue_timeline.gd.uid b/common/ui/fields/timeline/monologue_timeline.gd.uid deleted file mode 100644 index 96e5a4b6..00000000 --- a/common/ui/fields/timeline/monologue_timeline.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bqqjre8f0yujm diff --git a/common/ui/fields/timeline/monologue_timeline.tscn b/common/ui/fields/timeline/monologue_timeline.tscn deleted file mode 100644 index 75b01fb9..00000000 --- a/common/ui/fields/timeline/monologue_timeline.tscn +++ /dev/null @@ -1,366 +0,0 @@ -[gd_scene load_steps=33 format=3 uid="uid://bqbr75kwcpu3i"] - -[ext_resource type="Script" uid="uid://bqqjre8f0yujm" path="res://common/ui/fields/timeline/monologue_timeline.gd" id="1_scldq"] -[ext_resource type="Texture2D" uid="uid://xiahr3jughjg" path="res://ui/assets/icons/plus_min.svg" id="2_qevyo"] -[ext_resource type="Texture2D" uid="uid://fcpb4fd7uma3" path="res://ui/assets/icons/trash_min.svg" id="3_i3rsl"] -[ext_resource type="PackedScene" uid="uid://wiapsnoaoc44" path="res://common/ui/custom_spinbox/custom_spinbox.tscn" id="4_i3rsl"] -[ext_resource type="Texture2D" uid="uid://08g8utdd2kai" path="res://ui/assets/icons/media_play_backward.svg" id="4_wgfvs"] -[ext_resource type="Texture2D" uid="uid://bowdhs0emx5i2" path="res://ui/assets/icons/media_skip_backward.svg" id="5_aaeml"] -[ext_resource type="Texture2D" uid="uid://ctm64x2sdw2y2" path="res://ui/assets/icons/media_stop.svg" id="6_kgr41"] -[ext_resource type="Texture2D" uid="uid://8cxo7ofybuix" path="res://ui/assets/icons/media_skip_forward.svg" id="7_gcvrj"] -[ext_resource type="Texture2D" uid="uid://bficqn5yhfmah" path="res://ui/assets/icons/media_play.svg" id="8_ga8ls"] -[ext_resource type="Texture2D" uid="uid://c07ekrem76mk3" path="res://ui/assets/icons/improt_cell.svg" id="9_vagt2"] -[ext_resource type="Script" uid="uid://csoamuetoupw0" path="res://common/ui/fields/timeline/timeline_layer_cell_container.gd" id="10_6qrpt"] -[ext_resource type="Texture2D" uid="uid://hlck6y4i3l5q" path="res://ui/assets/icons/plus.svg" id="11_u65pl"] -[ext_resource type="PackedScene" uid="uid://c3kq4oc8yxco7" path="res://common/ui/fields/timeline/timeline_cell_number.tscn" id="12_4mkmo"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wlqpu"] -draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.835294, 0.317647, 0.376471, 1) -corner_radius_top_left = 2 -corner_radius_top_right = 2 -corner_radius_bottom_right = 2 -corner_radius_bottom_left = 2 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v62g1"] -draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.84, 0.3192, 0.37996, 0.498039) -corner_radius_top_left = 2 -corner_radius_top_right = 2 -corner_radius_bottom_right = 2 -corner_radius_bottom_left = 2 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8ap2m"] -draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.835294, 0.317647, 0.376471, 1) -corner_radius_top_left = 2 -corner_radius_top_right = 2 -corner_radius_bottom_right = 2 -corner_radius_bottom_left = 2 - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_4f5yu"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xe0nk"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_arqpt"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7j2ls"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_argys"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_aaeml"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kgr41"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_gcvrj"] - -[sub_resource type="LabelSettings" id="LabelSettings_86o0e"] -line_spacing = 0.0 -font_size = 14 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_vknlb"] -bg_color = Color(0.117647, 0.117647, 0.129412, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xe0nk"] -bg_color = Color(0.117647, 0.117647, 0.129412, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_arqpt"] -bg_color = Color(0.117647, 0.117647, 0.129412, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7j2ls"] -bg_color = Color(0.117647, 0.117647, 0.129412, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qevyo"] -draw_center = false -corner_radius_top_left = 4 -corner_radius_top_right = 4 - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_omduo"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_t1qxj"] - -[node name="Timeline" type="PanelContainer"] -offset_right = 528.0 -offset_bottom = 83.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_type_variation = &"CollapsibleFieldPanel" -script = ExtResource("1_scldq") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 -theme_override_constants/separation = 5 - -[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer"] -custom_minimum_size = Vector2(0, 26) -layout_mode = 2 - -[node name="btnAddLayer" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"] -visible = false -custom_minimum_size = Vector2(26, 26) -layout_mode = 2 -theme_override_styles/focus = SubResource("StyleBoxFlat_wlqpu") -theme_override_styles/hover = SubResource("StyleBoxFlat_v62g1") -theme_override_styles/pressed = SubResource("StyleBoxFlat_8ap2m") -theme_override_styles/normal = SubResource("StyleBoxEmpty_4f5yu") -icon = ExtResource("2_qevyo") -expand_icon = true - -[node name="btnDelete" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"] -visible = false -custom_minimum_size = Vector2(26, 26) -layout_mode = 2 -theme_override_styles/focus = SubResource("StyleBoxFlat_wlqpu") -theme_override_styles/hover = SubResource("StyleBoxFlat_v62g1") -theme_override_styles/pressed = SubResource("StyleBoxFlat_8ap2m") -theme_override_styles/normal = SubResource("StyleBoxEmpty_4f5yu") -icon = ExtResource("3_i3rsl") -expand_icon = true - -[node name="VSeparator" type="VSeparator" parent="VBoxContainer/PanelContainer/HBoxContainer"] -visible = false -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="FpsSpinBox" parent="VBoxContainer/PanelContainer/HBoxContainer/HBoxContainer" instance=ExtResource("4_i3rsl")] -unique_name_in_owner = true -layout_mode = 2 -suffix = "fps" - -[node name="PlayBackwardsButton" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"] -custom_minimum_size = Vector2(26, 26) -layout_mode = 2 -theme_override_styles/disabled = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover = SubResource("StyleBoxEmpty_arqpt") -theme_override_styles/pressed = SubResource("StyleBoxEmpty_7j2ls") -theme_override_styles/normal = SubResource("StyleBoxEmpty_argys") -icon = ExtResource("4_wgfvs") -flat = true -icon_alignment = 1 -expand_icon = true - -[node name="SkipBackwardButton" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"] -visible = false -custom_minimum_size = Vector2(26, 26) -layout_mode = 2 -theme_override_styles/disabled = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover = SubResource("StyleBoxEmpty_arqpt") -theme_override_styles/pressed = SubResource("StyleBoxEmpty_7j2ls") -theme_override_styles/normal = SubResource("StyleBoxEmpty_argys") -icon = ExtResource("5_aaeml") -flat = true -icon_alignment = 1 -expand_icon = true - -[node name="StopButton" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"] -custom_minimum_size = Vector2(26, 26) -layout_mode = 2 -theme_override_styles/disabled = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover = SubResource("StyleBoxEmpty_arqpt") -theme_override_styles/pressed = SubResource("StyleBoxEmpty_7j2ls") -theme_override_styles/normal = SubResource("StyleBoxEmpty_argys") -icon = ExtResource("6_kgr41") -flat = true -icon_alignment = 1 -expand_icon = true - -[node name="SkipForwardButton" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"] -visible = false -custom_minimum_size = Vector2(26, 26) -layout_mode = 2 -theme_override_styles/disabled = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover = SubResource("StyleBoxEmpty_arqpt") -theme_override_styles/pressed = SubResource("StyleBoxEmpty_7j2ls") -theme_override_styles/normal = SubResource("StyleBoxEmpty_argys") -icon = ExtResource("7_gcvrj") -flat = true -icon_alignment = 1 -expand_icon = true - -[node name="PlayButton" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"] -custom_minimum_size = Vector2(26, 26) -layout_mode = 2 -theme_override_styles/disabled = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover = SubResource("StyleBoxEmpty_arqpt") -theme_override_styles/pressed = SubResource("StyleBoxEmpty_7j2ls") -theme_override_styles/normal = SubResource("StyleBoxEmpty_argys") -icon = ExtResource("8_ga8ls") -flat = true -icon_alignment = 1 -expand_icon = true - -[node name="VSeparator2" type="VSeparator" parent="VBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 - -[node name="ImportFrameButton" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"] -unique_name_in_owner = true -custom_minimum_size = Vector2(26, 26) -layout_mode = 2 -theme_override_styles/disabled = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_xe0nk") -theme_override_styles/hover = SubResource("StyleBoxEmpty_arqpt") -theme_override_styles/pressed = SubResource("StyleBoxEmpty_7j2ls") -theme_override_styles/normal = SubResource("StyleBoxEmpty_argys") -disabled = true -icon = ExtResource("9_vagt2") -flat = true -icon_alignment = 1 -expand_icon = true - -[node name="LayerContainer" type="HSplitContainer" parent="VBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_vertical = 3 -theme_override_constants/separation = 5 -script = ExtResource("10_6qrpt") - -[node name="LayerContainer" type="PanelContainer" parent="VBoxContainer/LayerContainer"] -custom_minimum_size = Vector2(150, 0) -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxEmpty_aaeml") - -[node name="MainLayerVBox" type="VBoxContainer" parent="VBoxContainer/LayerContainer/LayerContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox"] -custom_minimum_size = Vector2(0, 35) -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxEmpty_kgr41") - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox/PanelContainer"] -layout_mode = 2 -size_flags_vertical = 3 -theme_override_constants/separation = 0 - -[node name="PanelContainer4" type="PanelContainer" parent="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox/PanelContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_styles/panel = SubResource("StyleBoxEmpty_gcvrj") - -[node name="Label" type="Label" parent="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox/PanelContainer/HBoxContainer/PanelContainer4"] -layout_mode = 2 -label_settings = SubResource("LabelSettings_86o0e") - -[node name="LayerScrollContainer" type="ScrollContainer" parent="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_vertical = 3 -vertical_scroll_mode = 3 - -[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox/LayerScrollContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_constants/separation = 0 - -[node name="LayerVBox" type="VBoxContainer" parent="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox/LayerScrollContainer/VBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="btnAddLayer" type="Button" parent="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox/LayerScrollContainer/VBoxContainer"] -custom_minimum_size = Vector2(24, 24) -layout_mode = 2 -theme_override_constants/icon_max_width = 10 -theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_vknlb") -theme_override_styles/hover = SubResource("StyleBoxFlat_xe0nk") -theme_override_styles/pressed = SubResource("StyleBoxFlat_arqpt") -theme_override_styles/normal = SubResource("StyleBoxFlat_7j2ls") -icon = ExtResource("11_u65pl") -icon_alignment = 1 - -[node name="LayerTimelineContainer" type="ScrollContainer" parent="VBoxContainer/LayerContainer"] -layout_mode = 2 -vertical_scroll_mode = 0 - -[node name="LayerTimelineVBox" type="VBoxContainer" parent="VBoxContainer/LayerContainer/LayerTimelineContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="FrameCountTimeline" type="PanelContainer" parent="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox"] -clip_contents = true -custom_minimum_size = Vector2(0, 25) -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_qevyo") - -[node name="HBox" type="HBoxContainer" parent="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox/FrameCountTimeline"] -layout_mode = 2 -size_flags_vertical = 3 -theme_override_constants/separation = 0 - -[node name="CellNumberHBox" type="HBoxContainer" parent="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox/FrameCountTimeline/HBox"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="CellNumber" parent="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox/FrameCountTimeline/HBox/CellNumberHBox" instance=ExtResource("12_4mkmo")] -custom_minimum_size = Vector2(27, 35) -layout_mode = 2 - -[node name="PanelContainer6" type="PanelContainer" parent="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox/FrameCountTimeline/HBox"] -custom_minimum_size = Vector2(27, 26) -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxEmpty_omduo") - -[node name="LayerTimelineScrollContainer" type="ScrollContainer" parent="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -follow_focus = true -horizontal_scroll_mode = 0 - -[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox/LayerTimelineScrollContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_constants/separation = 0 - -[node name="LayerTimelineVBox" type="VBoxContainer" parent="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox/LayerTimelineScrollContainer/VBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_constants/separation = 0 - -[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox/LayerTimelineScrollContainer/VBoxContainer"] -custom_minimum_size = Vector2(0, 24) -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxEmpty_t1qxj") - -[connection signal="value_changed" from="VBoxContainer/PanelContainer/HBoxContainer/HBoxContainer/FpsSpinBox" to="." method="_on_fps_spin_box_value_changed"] -[connection signal="pressed" from="VBoxContainer/PanelContainer/HBoxContainer/PlayBackwardsButton" to="." method="_on_play_backwards_button_pressed"] -[connection signal="pressed" from="VBoxContainer/PanelContainer/HBoxContainer/SkipBackwardButton" to="." method="_on_skip_backward_button_pressed"] -[connection signal="pressed" from="VBoxContainer/PanelContainer/HBoxContainer/StopButton" to="." method="_on_stop_button_pressed"] -[connection signal="pressed" from="VBoxContainer/PanelContainer/HBoxContainer/SkipForwardButton" to="." method="_on_skip_forward_button_pressed"] -[connection signal="pressed" from="VBoxContainer/PanelContainer/HBoxContainer/PlayButton" to="." method="_on_play_button_pressed"] -[connection signal="pressed" from="VBoxContainer/PanelContainer/HBoxContainer/ImportFrameButton" to="." method="_on_import_frame_button_pressed"] -[connection signal="mouse_entered" from="VBoxContainer/LayerContainer" to="VBoxContainer/LayerContainer" method="_on_mouse_entered"] -[connection signal="mouse_exited" from="VBoxContainer/LayerContainer" to="VBoxContainer/LayerContainer" method="_on_mouse_exited"] -[connection signal="gui_input" from="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox/LayerScrollContainer" to="." method="_on_layer_scroll_container_gui_input"] -[connection signal="pressed" from="VBoxContainer/LayerContainer/LayerContainer/MainLayerVBox/LayerScrollContainer/VBoxContainer/btnAddLayer" to="." method="_on_btn_add_layer_pressed"] -[connection signal="gui_input" from="VBoxContainer/LayerContainer/LayerTimelineContainer/LayerTimelineVBox/LayerTimelineScrollContainer" to="." method="_on_layer_timeline_scroll_container_gui_input"] diff --git a/common/ui/fields/timeline/timeline_cell.gd b/common/ui/fields/timeline/timeline_cell.gd deleted file mode 100644 index ef0a95ea..00000000 --- a/common/ui/fields/timeline/timeline_cell.gd +++ /dev/null @@ -1,105 +0,0 @@ -class_name TimelineCell extends PanelContainer - -signal button_down -signal button_up -signal button_focus_exited - -@onready var single_cell_texture := preload("res://ui/assets/icons/cell_single.svg") -@onready var empty_cell_texture := preload("res://ui/assets/icons/cell_empty.svg") -@onready var left_cell_texture := preload("res://ui/assets/icons/cell_left.svg") -@onready var right_cell_texture := preload("res://ui/assets/icons/cell_right.svg") -@onready var middle_cell_texture := preload("res://ui/assets/icons/cell_middle.svg") - -@onready var button := %Button -@onready var line_indicator := %LineIndicator -@onready var texture_rect := $TextureContainer/TextureRect -@onready var texture_container := $TextureContainer -@onready var hflow := %HFlow - -var image_path: String: - set = _set_image_path -var is_exposure: bool = false # If is the same frame as the previous one -var timeline: LayerTimeline - - -func _set_image_path(value: String) -> void: - image_path = value - _update() - - -func _ready() -> void: - GlobalSignal.add_listener("timeline_zoom_in", _on_timeline_zoom) - GlobalSignal.add_listener("timeline_zoom_out", _on_timeline_zoom) - _update() - - -func _update() -> void: - if not is_exposure and not image_path.is_empty(): - var root_dir = timeline.timeline.base_path.get_base_dir() + Path.get_separator() - texture_rect.texture = ImageLoader.load_thumbnail( - Path.relative_to_absolute(image_path, root_dir) - ) - - texture_container.visible = !is_exposure - line_indicator.visible = is_exposure - - -func lose_focus() -> void: - button.button_pressed = false - - -func get_base_sb() -> StyleBoxFlat: - # TODO: Use theme variation instead - var sb: StyleBox = StyleBoxFlat.new() - sb.bg_color = Color("d651613f") - sb.border_color = Color("1e1e21") - sb.border_width_right = 1 - return sb - - -func sub_select() -> void: - add_theme_stylebox_override("panel", get_base_sb()) - - -func reset_style() -> void: - var sb: StyleBox = get_base_sb() - sb.draw_center = false - add_theme_stylebox_override("panel", sb) - - -func _on_timeline_zoom(cell_width: int) -> void: - custom_minimum_size.x = cell_width - - -func _on_button_button_down() -> void: - button_down.emit() - - -func _on_button_button_up() -> void: - button_up.emit() - - -func _on_button_toggled(toggled_on: bool) -> void: - if toggled_on == false: - button_focus_exited.emit() - - -func _on_inc_exposure_button_pressed() -> void: - timeline.add_exposure(self) - - -func _on_mouse_entered() -> void: - if timeline.current_indicator == null: - hflow.show() - - -func _on_mouse_exited() -> void: - hflow.hide() - - -func _on_dec_exposure_button_pressed() -> void: - timeline.remove_cell(self) - - -func _on_button_focus_exited() -> void: - button_focus_exited.emit() diff --git a/common/ui/fields/timeline/timeline_cell.gd.uid b/common/ui/fields/timeline/timeline_cell.gd.uid deleted file mode 100644 index cb1930dd..00000000 --- a/common/ui/fields/timeline/timeline_cell.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bstbijin8qrmw diff --git a/common/ui/fields/timeline/timeline_cell.tscn b/common/ui/fields/timeline/timeline_cell.tscn deleted file mode 100644 index 1ec2c598..00000000 --- a/common/ui/fields/timeline/timeline_cell.tscn +++ /dev/null @@ -1,152 +0,0 @@ -[gd_scene load_steps=11 format=3 uid="uid://cdpox07h3wlf3"] - -[ext_resource type="Script" uid="uid://bstbijin8qrmw" path="res://common/ui/fields/timeline/timeline_cell.gd" id="1_w2yvg"] -[ext_resource type="Script" uid="uid://dqhwi718vnyxk" path="res://common/layouts/character_edit/alpha_bg.gd" id="2_04ohn"] -[ext_resource type="Texture2D" uid="uid://2avo8aox8ls2" path="res://ui/assets/icons/minus_min.svg" id="3_tep1x"] -[ext_resource type="Texture2D" uid="uid://xiahr3jughjg" path="res://ui/assets/icons/plus_min.svg" id="4_ob7f1"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y3h2k"] -draw_center = false -border_width_right = 1 -border_width_bottom = 1 -border_color = Color(0.117647, 0.117647, 0.129412, 1) - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xo2vn"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1614f"] -draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.835294, 0.317647, 0.376471, 1) -corner_radius_top_left = 2 -corner_radius_top_right = 2 -corner_radius_bottom_right = 2 -corner_radius_bottom_left = 2 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y37jy"] -draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.84, 0.3192, 0.37996, 0.498039) -corner_radius_top_left = 2 -corner_radius_top_right = 2 -corner_radius_bottom_right = 2 -corner_radius_bottom_left = 2 - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_5et5j"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7wlkg"] -bg_color = Color(0.117647, 0.117647, 0.129412, 1) - -[node name="Cell" type="PanelContainer"] -custom_minimum_size = Vector2(27, 52) -offset_right = 27.0 -offset_bottom = 52.0 -theme_override_styles/panel = SubResource("StyleBoxFlat_y3h2k") -script = ExtResource("1_w2yvg") - -[node name="VBox" type="VBoxContainer" parent="."] -layout_mode = 2 -alignment = 1 - -[node name="LineIndicator" type="ColorRect" parent="VBox"] -unique_name_in_owner = true -visible = false -custom_minimum_size = Vector2(0, 2) -layout_mode = 2 - -[node name="TextureContainer" type="PanelContainer" parent="."] -clip_contents = true -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxEmpty_xo2vn") - -[node name="AlphaBG" type="ColorRect" parent="TextureContainer"] -layout_mode = 2 -script = ExtResource("2_04ohn") - -[node name="TextureRect" type="TextureRect" parent="TextureContainer"] -custom_minimum_size = Vector2(26, 26) -layout_mode = 2 -expand_mode = 1 -stretch_mode = 5 - -[node name="IncExposureContainer" type="Control" parent="."] -layout_mode = 2 -mouse_filter = 2 - -[node name="Button" type="Button" parent="IncExposureContainer"] -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 -mouse_filter = 1 -theme_override_styles/focus = SubResource("StyleBoxFlat_1614f") -theme_override_styles/hover = SubResource("StyleBoxFlat_y37jy") -theme_override_styles/pressed = SubResource("StyleBoxFlat_1614f") -theme_override_styles/normal = SubResource("StyleBoxEmpty_5et5j") -keep_pressed_outside = true - -[node name="HFlow" type="HFlowContainer" parent="IncExposureContainer"] -unique_name_in_owner = true -visible = false -layout_mode = 1 -anchors_preset = 14 -anchor_top = 0.5 -anchor_right = 1.0 -anchor_bottom = 0.5 -offset_top = -12.0 -offset_bottom = 12.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 -alignment = 1 -last_wrap_alignment = 2 - -[node name="DecHBox" type="HBoxContainer" parent="IncExposureContainer/HFlow"] -layout_mode = 2 -size_flags_horizontal = 3 -mouse_filter = 2 - -[node name="DecExposureButton" type="Button" parent="IncExposureContainer/HFlow/DecHBox"] -custom_minimum_size = Vector2(24, 24) -layout_mode = 2 -mouse_filter = 1 -theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_7wlkg") -theme_override_styles/hover = SubResource("StyleBoxFlat_7wlkg") -theme_override_styles/pressed = SubResource("StyleBoxFlat_7wlkg") -theme_override_styles/normal = SubResource("StyleBoxFlat_7wlkg") -icon = ExtResource("3_tep1x") -expand_icon = true - -[node name="IncHBox" type="HBoxContainer" parent="IncExposureContainer/HFlow"] -layout_mode = 2 -size_flags_horizontal = 3 -mouse_filter = 2 -alignment = 2 - -[node name="IncExposureButton" type="Button" parent="IncExposureContainer/HFlow/IncHBox"] -custom_minimum_size = Vector2(24, 24) -layout_mode = 2 -mouse_filter = 1 -theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_7wlkg") -theme_override_styles/hover = SubResource("StyleBoxFlat_7wlkg") -theme_override_styles/pressed = SubResource("StyleBoxFlat_7wlkg") -theme_override_styles/normal = SubResource("StyleBoxFlat_7wlkg") -icon = ExtResource("4_ob7f1") -expand_icon = true - -[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"] -[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"] -[connection signal="button_down" from="IncExposureContainer/Button" to="." method="_on_button_button_down"] -[connection signal="button_up" from="IncExposureContainer/Button" to="." method="_on_button_button_up"] -[connection signal="focus_exited" from="IncExposureContainer/Button" to="." method="_on_button_focus_exited"] -[connection signal="toggled" from="IncExposureContainer/Button" to="." method="_on_button_toggled"] -[connection signal="pressed" from="IncExposureContainer/HFlow/DecHBox/DecExposureButton" to="." method="_on_dec_exposure_button_pressed"] -[connection signal="pressed" from="IncExposureContainer/HFlow/IncHBox/IncExposureButton" to="." method="_on_inc_exposure_button_pressed"] diff --git a/common/ui/fields/timeline/timeline_cell_layer.gd b/common/ui/fields/timeline/timeline_cell_layer.gd deleted file mode 100644 index eb117611..00000000 --- a/common/ui/fields/timeline/timeline_cell_layer.gd +++ /dev/null @@ -1,230 +0,0 @@ -class_name LayerTimeline extends PanelContainer - -signal timeline_updated - -@onready var hbox := %HBox - -var timeline: MonologueAnimationTimeline -var timeline_cell := preload("res://common/ui/fields/timeline/timeline_cell.tscn") -var placement_indicator := preload("res://common/ui/vertical_placement_indicator.tscn") - -var current_indicator: Control -var selected_cell: TimelineCell - - -func _ready() -> void: - add_cell() - - -func add_cell(image_path = "") -> TimelineCell: - var cells := get_all_cells() - var is_exposure: bool = false if image_path != null else cells.size() > 0 - - var new_cell := timeline_cell.instantiate() - new_cell.timeline = self - hbox.add_child(new_cell) - new_cell.is_exposure = is_exposure - new_cell.custom_minimum_size.x = timeline.get_cell_width() - new_cell.image_path = image_path - new_cell._update() - new_cell.connect("button_down", _on_cell_button_down.bind(new_cell)) - new_cell.connect("button_up", _on_cell_button_up.bind(new_cell)) - new_cell.connect("button_focus_exited", _on_cell_focus_exited) - - if get_all_cells().size() > timeline.cell_count: - timeline.add_cell() - - return new_cell - - -func _on_cell_button_down(cell: TimelineCell) -> void: - current_indicator = placement_indicator.instantiate() - hbox.add_child(current_indicator) - hbox.move_child(current_indicator, cell.get_index() + 1) - current_indicator.show() - selected_cell = cell - - -func _on_cell_button_up(cell: TimelineCell) -> void: - selected_cell = null - if not current_indicator.visible: - timeline.cell_selected(cell, self) - current_indicator.queue_free() - current_indicator = null - return - - var indicator_idx = current_indicator.get_index() - hbox.move_child(cell, indicator_idx) - - current_indicator.queue_free() - current_indicator = null - timeline.selected_cell_idx = get_all_cells().find(cell) - timeline.selected_cell_layer_idx = timeline.layer_timeline_vbox.get_children().find(self) - - var first_cell: TimelineCell = get_all_cells()[0] - if first_cell.is_exposure: - first_cell.is_exposure = false - first_cell._update() - - timeline_updated.emit() - - for child in get_all_cells(): - if child == cell: - continue - - child.lose_focus() - - -func _on_cell_focus_exited() -> void: - get_all_cells()[timeline.selected_cell_idx].reset_style() - timeline.cell_deselected() - - -func _process(_delta: float) -> void: - if current_indicator == null: - return - - var indicator_dist: float = current_indicator.global_position.x - get_global_mouse_position().x - var cell_width: float = timeline.get_cell_width() - var cell_dist: float = ( - get_global_mouse_position().x - (selected_cell.global_position.x + cell_width / 2.0) - ) - var indicator_index: int = current_indicator.get_index() - current_indicator.show() - if indicator_dist >= cell_width / 2.0: - hbox.move_child(current_indicator, indicator_index - 1) - elif indicator_dist <= -cell_width / 2.0: - hbox.move_child(current_indicator, indicator_index + 1) - elif abs(cell_dist) < cell_width: - current_indicator.hide() - - -func remove_cell(cell: TimelineCell) -> void: - var cells: Array = get_all_cells() - var index: int = cells.find(cell) - - if not cell.is_exposure and have_exposure_after(cell): - var c_after: TimelineCell = cells[index + 1] - c_after.is_exposure = false - c_after.image_path = cell.image_path - c_after._update() - - hbox.remove_child(cell) - cell.queue_free() - - timeline_updated.emit() - - -func fill() -> void: - _clear() - for _i in range(timeline.cell_count): - add_cell() - - -func _clear() -> void: - for cell in get_all_cells(): - cell.queue_free() - - -func _from_dict(dict: Dictionary) -> void: - _clear() - var frames: Dictionary = dict.get("Frames") - for frame_idx in frames.keys(): - for i in range(frames[frame_idx].get("Exposure", 1)): - var frame_data: Dictionary = frames[frame_idx] - var cell := add_cell() - cell.is_exposure = i > 0 - if i <= 0: - cell.image_path = frame_data.get("ImagePath", "") - cell._update() - - -func _to_dict() -> Dictionary: - var dict: Dictionary = {} - var cells: Array = get_all_cells() - for cell: TimelineCell in cells: - if cell.is_exposure: - continue - - var cell_idx: int = cells.find(cell) - dict[cell_idx] = {"ImagePath": cell.image_path, "Exposure": get_frame_duration(cell_idx)} - - return dict - - -func get_all_cells() -> Array: - var cells: Array = [] - for child in hbox.get_children(): - if child is not TimelineCell or child.is_queued_for_deletion(): - continue - cells.append(child) - - return cells - - -func _to_sprite_frames() -> SpriteFrames: - var sprite_frames := SpriteFrames.new() - sprite_frames.set_animation_speed("default", timeline.fps) - - var cells: Array = get_all_cells() - for i in range(timeline.cell_count): - var texture: Texture2D - var frame_duration: float = 1.0 - - if cells.size() > i: - var cell: TimelineCell = cells[i - 1] - if cell.is_exposure: - continue - - var idx = cells.find(cell) - frame_duration = get_frame_duration(idx) - - var root_dir = timeline.base_path.get_base_dir() + Path.get_separator() - var frame_path: String = Path.relative_to_absolute(cell.image_path, root_dir) - if FileAccess.file_exists(frame_path): - texture = ImageLoader.load_image(frame_path) - else: - texture = Texture2D.new() - - sprite_frames.add_frame("default", texture, frame_duration, i) - - return sprite_frames - - -func get_frame_duration(frame_idx: int) -> float: - var duration: float = 1.0 - - var cells: Array = get_all_cells().slice(frame_idx + 1) - for cell: TimelineCell in cells: - if cell.is_exposure: - duration += 1.0 - continue - break - - return duration - - -func add_exposure(of_cell: TimelineCell): - var index: int = get_all_cells().find(of_cell) - - var cell: TimelineCell = add_cell() - cell.is_exposure = true - hbox.move_child(cell, index + 1) - cell._on_mouse_exited() - timeline_updated.emit() - - -func _on_button_pressed() -> void: - var cell: TimelineCell = add_cell() - cell.is_exposure = false - cell._update() - timeline_updated.emit() - - -func have_exposure_after(cell: TimelineCell) -> bool: - var cells := get_all_cells() - var index: int = cells.find(cell) - if index == cells.size() - 1: - return false - - return cells[index + 1].is_exposure diff --git a/common/ui/fields/timeline/timeline_cell_layer.gd.uid b/common/ui/fields/timeline/timeline_cell_layer.gd.uid deleted file mode 100644 index 52d99c5c..00000000 --- a/common/ui/fields/timeline/timeline_cell_layer.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dt0lru5kmkesc diff --git a/common/ui/fields/timeline/timeline_cell_layer.tscn b/common/ui/fields/timeline/timeline_cell_layer.tscn deleted file mode 100644 index 022b82ef..00000000 --- a/common/ui/fields/timeline/timeline_cell_layer.tscn +++ /dev/null @@ -1,44 +0,0 @@ -[gd_scene load_steps=7 format=3 uid="uid://hc8mgc7ndi5d"] - -[ext_resource type="Script" uid="uid://dt0lru5kmkesc" path="res://common/ui/fields/timeline/timeline_cell_layer.gd" id="1_uew4g"] -[ext_resource type="Texture2D" uid="uid://hlck6y4i3l5q" path="res://ui/assets/icons/plus.svg" id="2_68mbi"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0ynx3"] -bg_color = Color(0.117647, 0.117647, 0.129412, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jdrfe"] -bg_color = Color(0.117647, 0.117647, 0.129412, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wroo4"] -bg_color = Color(0.117647, 0.117647, 0.129412, 1) - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_abmyl"] -bg_color = Color(0.117647, 0.117647, 0.129412, 1) - -[node name="LayerTimeline" type="PanelContainer"] -custom_minimum_size = Vector2(0, 52) -size_flags_horizontal = 3 -theme_type_variation = &"ItemContainerFlat" -script = ExtResource("1_uew4g") - -[node name="BaseContainer" type="HBoxContainer" parent="."] -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="HBox" type="HBoxContainer" parent="BaseContainer"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/separation = 0 - -[node name="Button" type="Button" parent="BaseContainer"] -custom_minimum_size = Vector2(24, 0) -layout_mode = 2 -theme_override_constants/icon_max_width = 10 -theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_0ynx3") -theme_override_styles/hover = SubResource("StyleBoxFlat_jdrfe") -theme_override_styles/pressed = SubResource("StyleBoxFlat_wroo4") -theme_override_styles/normal = SubResource("StyleBoxFlat_abmyl") -icon = ExtResource("2_68mbi") -icon_alignment = 1 - -[connection signal="pressed" from="BaseContainer/Button" to="." method="_on_button_pressed"] diff --git a/common/ui/fields/timeline/timeline_cell_number.tscn b/common/ui/fields/timeline/timeline_cell_number.tscn deleted file mode 100644 index 40e27468..00000000 --- a/common/ui/fields/timeline/timeline_cell_number.tscn +++ /dev/null @@ -1,100 +0,0 @@ -[gd_scene load_steps=6 format=3 uid="uid://c3kq4oc8yxco7"] - -[sub_resource type="GDScript" id="GDScript_aiax2"] -script/source = "extends PanelContainer - -signal selected(cell_number: int) - -@onready var label: Label = $Label - -@export var cell_number: int = 1 - - -func _ready() -> void: - label.text = str(cell_number) - GlobalSignal.add_listener(\"timeline_zoom_in\", _on_timeline_zoom) - GlobalSignal.add_listener(\"timeline_zoom_out\", _on_timeline_zoom) - - -func get_base_sb() -> StyleBoxFlat: - # TODO: Use theme variation instead - var sb: StyleBox = StyleBoxFlat.new() - sb.bg_color = Color(\"d651613f\") - sb.border_color = Color(\"1e1e21\") - sb.border_width_right = 1 - return sb - - -func sub_select() -> void: - add_theme_stylebox_override(\"panel\", get_base_sb()) - -func reset_style() -> void: - var sb: StyleBox = get_base_sb() - sb.draw_center = false - add_theme_stylebox_override(\"panel\", sb) - - -func _on_button_pressed() -> void: - selected.emit(cell_number) - - -func _on_timeline_zoom(cell_width: int) -> void: - custom_minimum_size.x = cell_width - - -func _on_minimum_size_changed() -> void: - custom_minimum_size.x = max(custom_minimum_size.x, 27.0) -" - -[sub_resource type="LabelSettings" id="LabelSettings_jbxed"] -line_spacing = 0.0 -font_size = 14 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fi15l"] -draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.835294, 0.317647, 0.376471, 1) -corner_radius_top_left = 2 -corner_radius_top_right = 2 -corner_radius_bottom_right = 2 -corner_radius_bottom_left = 2 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_vog8c"] -draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.84, 0.3192, 0.37996, 0.498039) -corner_radius_top_left = 2 -corner_radius_top_right = 2 -corner_radius_bottom_right = 2 -corner_radius_bottom_left = 2 - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_x14x4"] - -[node name="CellNumber" type="PanelContainer"] -custom_minimum_size = Vector2(27, 26) -theme_type_variation = &"TimelineCellNumber" -script = SubResource("GDScript_aiax2") - -[node name="Label" type="Label" parent="."] -layout_mode = 2 -text = "1" -label_settings = SubResource("LabelSettings_jbxed") -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="Button" type="Button" parent="."] -visible = false -layout_mode = 2 -theme_override_styles/focus = SubResource("StyleBoxFlat_fi15l") -theme_override_styles/hover = SubResource("StyleBoxFlat_vog8c") -theme_override_styles/pressed = SubResource("StyleBoxFlat_fi15l") -theme_override_styles/normal = SubResource("StyleBoxEmpty_x14x4") - -[connection signal="minimum_size_changed" from="." to="." method="_on_minimum_size_changed"] -[connection signal="pressed" from="Button" to="." method="_on_button_pressed"] diff --git a/common/ui/fields/timeline/timeline_layer.gd b/common/ui/fields/timeline/timeline_layer.gd deleted file mode 100644 index 72a293ce..00000000 --- a/common/ui/fields/timeline/timeline_layer.gd +++ /dev/null @@ -1,10 +0,0 @@ -class_name Layer extends PanelContainer - -signal delete_button_pressed - -@onready var timeline_label := %Label -@onready var hover_button := %HoverButton - - -func _on_delete_button_pressed() -> void: - delete_button_pressed.emit() diff --git a/common/ui/fields/timeline/timeline_layer.gd.uid b/common/ui/fields/timeline/timeline_layer.gd.uid deleted file mode 100644 index 8d065b20..00000000 --- a/common/ui/fields/timeline/timeline_layer.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bodradtjajfd7 diff --git a/common/ui/fields/timeline/timeline_layer.tscn b/common/ui/fields/timeline/timeline_layer.tscn deleted file mode 100644 index 6db97d30..00000000 --- a/common/ui/fields/timeline/timeline_layer.tscn +++ /dev/null @@ -1,88 +0,0 @@ -[gd_scene load_steps=7 format=3 uid="uid://d2ekvpgbp07fe"] - -[ext_resource type="Script" uid="uid://bodradtjajfd7" path="res://common/ui/fields/timeline/timeline_layer.gd" id="1_teuw3"] -[ext_resource type="PackedScene" uid="uid://dfwf55ovgwir3" path="res://common/ui/buttons/delete_button.tscn" id="2_ta0kq"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_cpp16"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6c6x0"] -draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.84, 0.3192, 0.37996, 0.498039) -corner_radius_top_left = 4 -corner_radius_top_right = 4 -corner_radius_bottom_right = 4 -corner_radius_bottom_left = 4 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j41u5"] -draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.835294, 0.317647, 0.376471, 1) -corner_radius_top_left = 2 -corner_radius_top_right = 2 -corner_radius_bottom_right = 2 -corner_radius_bottom_left = 2 - -[sub_resource type="LabelSettings" id="LabelSettings_86o0e"] -line_spacing = 0.0 -font_size = 14 - -[node name="Layer" type="PanelContainer"] -custom_minimum_size = Vector2(0, 52) -theme_type_variation = &"TimelineLayerPanel" -script = ExtResource("1_teuw3") - -[node name="Control" type="Control" parent="."] -layout_mode = 2 -mouse_filter = 2 - -[node name="HoverButton" type="Button" parent="Control"] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -8.0 -offset_top = -8.0 -offset_right = 8.0 -offset_bottom = 8.0 -grow_horizontal = 2 -grow_vertical = 2 -theme_override_styles/focus = SubResource("StyleBoxEmpty_cpp16") -theme_override_styles/hover = SubResource("StyleBoxFlat_6c6x0") -theme_override_styles/pressed = SubResource("StyleBoxFlat_j41u5") -theme_override_styles/normal = SubResource("StyleBoxEmpty_cpp16") -keep_pressed_outside = true - -[node name="HBoxContainer" type="HBoxContainer" parent="."] -layout_mode = 2 -size_flags_vertical = 3 -mouse_filter = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -mouse_filter = 2 - -[node name="Label" type="Label" parent="HBoxContainer/HBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -text = "Layer 1" -label_settings = SubResource("LabelSettings_86o0e") - -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] -layout_mode = 2 -mouse_filter = 2 -alignment = 1 - -[node name="DeleteButton" parent="HBoxContainer/VBoxContainer" instance=ExtResource("2_ta0kq")] -layout_mode = 2 - -[connection signal="pressed" from="HBoxContainer/VBoxContainer/DeleteButton" to="." method="_on_delete_button_pressed"] diff --git a/common/ui/fields/timeline/timeline_layer_cell_container.gd b/common/ui/fields/timeline/timeline_layer_cell_container.gd deleted file mode 100644 index 217915b6..00000000 --- a/common/ui/fields/timeline/timeline_layer_cell_container.gd +++ /dev/null @@ -1,38 +0,0 @@ -extends HSplitContainer - -const MAX_CELL_WIDTH: int = 150 -const MIN_CELL_WIDTH: int = 26 - -@onready var layer_timeline_scroll_container := %LayerTimelineScrollContainer - -var mouse_hover: bool = false -var cell_width: int = 75 - - -func _input(_event: InputEvent) -> void: - # Disable scroll container if Ctrl is pressed - if Input.is_action_just_pressed("Ctrl"): - layer_timeline_scroll_container.mouse_filter = MOUSE_FILTER_IGNORE - elif Input.is_action_just_released("Ctrl"): - layer_timeline_scroll_container.mouse_filter = MOUSE_FILTER_PASS - - -func _gui_input(event: InputEvent) -> void: - if event is InputEventMouseButton and event.is_pressed() and Input.is_action_pressed("Ctrl"): - var t: float = remap(cell_width, MIN_CELL_WIDTH, MAX_CELL_WIDTH, 0.0, 1.0) - # zoom in - if event.button_index == MOUSE_BUTTON_WHEEL_UP: - cell_width = min(cell_width + lerp(2.0, 12.0, pow(t, 2)), MAX_CELL_WIDTH) - GlobalSignal.emit("timeline_zoom_in", [cell_width]) - # zoom out - if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: - cell_width = max(cell_width - lerp(2.0, 12.0, pow(t, 2)), MIN_CELL_WIDTH) - GlobalSignal.emit("timeline_zoom_out", [cell_width]) - - -func _on_mouse_entered() -> void: - mouse_hover = true - - -func _on_mouse_exited() -> void: - mouse_hover = false diff --git a/common/ui/fields/timeline/timeline_layer_cell_container.gd.uid b/common/ui/fields/timeline/timeline_layer_cell_container.gd.uid deleted file mode 100644 index bb8612df..00000000 --- a/common/ui/fields/timeline/timeline_layer_cell_container.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://csoamuetoupw0 diff --git a/common/ui/fields/toggle/monologue_toggle.gd b/common/ui/fields/toggle/monologue_toggle.gd deleted file mode 100644 index b9000bc0..00000000 --- a/common/ui/fields/toggle/monologue_toggle.gd +++ /dev/null @@ -1,12 +0,0 @@ -class_name MonologueToggle extends MonologueField - -@onready var check_button = %CheckButton - - -func propagate(value: Variant) -> void: - super.propagate(value) - check_button.set_pressed_no_signal(value if value is bool else false) - - -func _on_check_button_toggled(toggled_on: bool) -> void: - field_updated.emit(toggled_on) diff --git a/common/ui/fields/toggle/monologue_toggle.gd.uid b/common/ui/fields/toggle/monologue_toggle.gd.uid deleted file mode 100644 index 1bd63073..00000000 --- a/common/ui/fields/toggle/monologue_toggle.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ctumcy1k8si7o diff --git a/common/ui/fields/toggle/monologue_toggle.tscn b/common/ui/fields/toggle/monologue_toggle.tscn deleted file mode 100644 index 8e5f30e7..00000000 --- a/common/ui/fields/toggle/monologue_toggle.tscn +++ /dev/null @@ -1,20 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://dh7yuosc0hhpp"] - -[ext_resource type="Script" uid="uid://ctumcy1k8si7o" path="res://common/ui/fields/toggle/monologue_toggle.gd" id="1_0u5l0"] - -[node name="MonologueCheckButton" type="HBoxContainer"] -offset_right = 43.0 -offset_bottom = 29.0 -script = ExtResource("1_0u5l0") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -custom_minimum_size = Vector2(9.385, 0) -layout_mode = 2 -alignment = 1 - -[node name="CheckButton" type="CheckButton" parent="VBoxContainer"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_constants/icon_max_width = 0 - -[connection signal="toggled" from="VBoxContainer/CheckButton" to="." method="_on_check_button_toggled"] diff --git a/common/ui/fields/vector/monologue_vector.gd b/common/ui/fields/vector/monologue_vector.gd deleted file mode 100644 index 1f863382..00000000 --- a/common/ui/fields/vector/monologue_vector.gd +++ /dev/null @@ -1,33 +0,0 @@ -class_name MonologueVector extends MonologueField - -@export var minimum: float = -9999999999 -@export var maximum: float = 9999999999 -@export var step: float = 1 - -@onready var x_spin_box := %XSpinBox -@onready var y_spin_box := %YSpinBox - -var ribbon_scene = preload("res://common/ui/ribbon/ribbon.tscn") - - -func _ready() -> void: - x_spin_box.min_value = minimum - y_spin_box.min_value = minimum - x_spin_box.max_value = maximum - y_spin_box.max_value = maximum - x_spin_box.step = step - y_spin_box.step = step - - -func propagate(value: Variant) -> void: - super.propagate(value) - x_spin_box.value = value[0] if (value[0] is float or value[0] is int) else 0 - y_spin_box.value = value[1] if (value[1] is float or value[1] is int) else 0 - - -func _on_focus_exited() -> void: - _on_spin_box_value_changed() - - -func _on_spin_box_value_changed(_value: float = 0.0) -> void: - field_updated.emit([x_spin_box.value, y_spin_box.value]) diff --git a/common/ui/fields/vector/monologue_vector.gd.uid b/common/ui/fields/vector/monologue_vector.gd.uid deleted file mode 100644 index 8d95749d..00000000 --- a/common/ui/fields/vector/monologue_vector.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://37empx7gweoa diff --git a/common/ui/fields/vector/monologue_vector.tscn b/common/ui/fields/vector/monologue_vector.tscn deleted file mode 100644 index cac4ffe1..00000000 --- a/common/ui/fields/vector/monologue_vector.tscn +++ /dev/null @@ -1,39 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://5ciquxsl5tw5"] - -[ext_resource type="Script" uid="uid://37empx7gweoa" path="res://common/ui/fields/vector/monologue_vector.gd" id="1_qpl0j"] -[ext_resource type="PackedScene" uid="uid://wiapsnoaoc44" path="res://common/ui/custom_spinbox/custom_spinbox.tscn" id="2_034ks"] - -[node name="MonologueVector" type="HBoxContainer"] -offset_right = 282.0 -offset_bottom = 29.0 -script = ExtResource("1_qpl0j") - -[node name="HBoxContainer2" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer2"] -layout_mode = 2 -text = "x" - -[node name="XSpinBox" parent="HBoxContainer2" instance=ExtResource("2_034ks")] -unique_name_in_owner = true -layout_mode = 2 -suffix = "px" - -[node name="VSeparator" type="VSeparator" parent="."] -layout_mode = 2 - -[node name="HBoxContainer3" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="Label" type="Label" parent="HBoxContainer3"] -layout_mode = 2 -text = "y" - -[node name="YSpinBox" parent="HBoxContainer3" instance=ExtResource("2_034ks")] -unique_name_in_owner = true -layout_mode = 2 -suffix = "px" - -[connection signal="value_changed" from="HBoxContainer2/XSpinBox" to="." method="_on_spin_box_value_changed"] -[connection signal="value_changed" from="HBoxContainer3/YSpinBox" to="." method="_on_spin_box_value_changed"] diff --git a/icon.png b/icon.png index d4d10e603c9ef7889707f3443c9a65826f08f22d..09575b08b8485bc459f98bc97162c379e063b613 100644 GIT binary patch literal 5340967 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

seiPpuqYWCP5Cn^I2nOzvw>^wGsQol3DZ8 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 z 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

Q7eIQwD!8!j4YQ>h*tr8YQ?rzk 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%0DAj+?RNT49i|b|T1#UuUgxg%v$&y^ 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