diff --git a/class_overview.gd b/class_overview.gd new file mode 100644 index 0000000..c2db801 --- /dev/null +++ b/class_overview.gd @@ -0,0 +1,6 @@ +extends PanelContainer + +func _ready(): + pass + + diff --git a/class_overview.tscn b/class_overview.tscn new file mode 100644 index 0000000..1ee7d0c --- /dev/null +++ b/class_overview.tscn @@ -0,0 +1,143 @@ +[gd_scene load_steps=4 format=1] + +[ext_resource path="res://addons/DataEditor/class_overview.gd" type="Script" id=1] +[ext_resource path="res://addons/DataEditor/style/light_gray_panel.tres" type="StyleBox" id=2] +[ext_resource path="res://addons/DataEditor/fonts/droid_serif_bold.tres" type="DynamicFont" id=3] + +[node name="ClassOverview" type="PanelContainer"] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 0.0 +margin/bottom = 0.0 +script/script = ExtResource( 1 ) + +[node name="Panel" type="Panel" parent="."] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 7.0 +margin/top = 7.0 +margin/right = 1273.0 +margin/bottom = 593.0 +custom_styles/panel = ExtResource( 2 ) + +[node name="Body" type="VBoxContainer" parent="Panel"] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 10.0 +margin/top = 10.0 +margin/right = 10.0 +margin/bottom = 10.0 +alignment = 0 + +[node name="ClassPropertiesLabel" type="Label" parent="Panel/Body"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1246.0 +margin/bottom = 2.0 +custom_fonts/font = ExtResource( 3 ) +text = "Static Class Properties" +uppercase = true +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="HSeparator" type="HSeparator" parent="Panel/Body"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 6.0 +margin/right = 1246.0 +margin/bottom = 9.0 + +[node name="Control" type="Control" parent="Panel/Body"] + +rect/min_size = Vector2( 0, 15 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 13.0 +margin/right = 1246.0 +margin/bottom = 28.0 + +[node name="Scroll" type="ScrollContainer" parent="Panel/Body"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 32.0 +margin/right = 1246.0 +margin/bottom = 566.0 +scroll/horizontal = true +scroll/vertical = true + +[node name="Statics" type="VBoxContainer" parent="Panel/Body/Scroll"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1246.0 +margin/bottom = 38.0 +alignment = 0 + +[node name="NoStaticsPropertiesLabel" type="Label" parent="Panel/Body/Scroll/Statics"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1246.0 +margin/bottom = 14.0 +text = "There are no static properties for this class. Click below to add one." +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="AddStaticPropertyButton" type="Button" parent="Panel/Body/Scroll/Statics"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 18.0 +margin/right = 1246.0 +margin/bottom = 38.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Add static property" +flat = false + + diff --git a/class_properties.gd b/class_properties.gd new file mode 100644 index 0000000..828ef47 --- /dev/null +++ b/class_properties.gd @@ -0,0 +1,50 @@ +tool +extends Panel + +var property_item_class = preload("property_item.tscn") + +onready var class_properties_box = get_node("Body/Scroll/ClassProperties") +onready var no_class_properties_label = get_node("Body/Scroll/ClassProperties/NoClassPropertiesLabel") + +var item = null + +signal on_item_changed(item) + + +func build_properties(item): + self.item = item + for node in class_properties_box.get_children(): + if node.has_meta("property"): + class_properties_box.remove_child(node) + + + var number_of_properties = 0 + for property in item.get_property_list(): + if property["usage"] == (PROPERTY_USAGE_SCRIPT_VARIABLE + PROPERTY_USAGE_STORAGE + PROPERTY_USAGE_EDITOR + PROPERTY_USAGE_NETWORK): + no_class_properties_label.hide() + number_of_properties += 1 + var property_item = property_item_class.instance() + var property_name = property["name"] + var value = item.get(property_name) + property_item.initialize(property_name, property["type"], value, property["hint"], property["hint_string"]) + property_item.connect("property_item_load_button_down", self, "_property_item_requests_file_dialog", []) + var changed_values = [] + property_item.connect("on_property_value_changed", self, "item_changed", changed_values) + property_item.set_meta("property", true) + class_properties_box.add_child(property_item) + pass + if number_of_properties == 0: + no_class_properties_label.show() + + +func item_changed(property, value): + if item: + item.set(property, value) + emit_signal("on_item_changed", item) + + + + + + + diff --git a/class_properties.tscn b/class_properties.tscn new file mode 100644 index 0000000..fbb9bed --- /dev/null +++ b/class_properties.tscn @@ -0,0 +1,116 @@ +[gd_scene load_steps=4 format=1] + +[ext_resource path="res://addons/DataEditor/style/light_gray_panel.tres" type="StyleBox" id=1] +[ext_resource path="res://addons/DataEditor/class_properties.gd" type="Script" id=2] +[ext_resource path="res://addons/DataEditor/fonts/droid_serif_bold.tres" type="DynamicFont" id=3] + +[node name="ClassProperties" type="Panel"] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 0.0 +margin/bottom = 0.0 +custom_styles/panel = ExtResource( 1 ) +script/script = ExtResource( 2 ) + +[node name="Body" type="VBoxContainer" parent="."] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 10.0 +margin/top = 10.0 +margin/right = 10.0 +margin/bottom = 10.0 +alignment = 0 + +[node name="TitleLabel" type="Label" parent="Body"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1260.0 +margin/bottom = 2.0 +custom_fonts/font = ExtResource( 3 ) +text = "Class Properties" +uppercase = true +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="HSeparator" type="HSeparator" parent="Body"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 6.0 +margin/right = 1260.0 +margin/bottom = 9.0 + +[node name="Spacer" type="Control" parent="Body"] + +rect/min_size = Vector2( 0, 15 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 13.0 +margin/right = 1260.0 +margin/bottom = 28.0 + +[node name="Scroll" type="ScrollContainer" parent="Body"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 32.0 +margin/right = 1260.0 +margin/bottom = 580.0 +scroll/horizontal = true +scroll/vertical = true + +[node name="ClassProperties" type="VBoxContainer" parent="Body/Scroll"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 454.0 +margin/bottom = 14.0 +alignment = 0 + +[node name="NoClassPropertiesLabel" type="Label" parent="Body/Scroll/ClassProperties"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 454.0 +margin/bottom = 14.0 +text = "There are currently no class properties. Edit the Class to add new ones." +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + + diff --git a/custom_properties.gd b/custom_properties.gd new file mode 100644 index 0000000..420ee61 --- /dev/null +++ b/custom_properties.gd @@ -0,0 +1,77 @@ +tool +extends Panel + +var item = null +var property_item_class = preload("property_item.tscn") +var remove_icon = preload("icons/icon_remove.png") + + +onready var custom_properties_box = get_node("Body/Scroll/CustomProperties") +onready var no_custom_properties_label = get_node("Body/Scroll/CustomProperties/NoCustomPropertiesLabel") + +onready var item_manager = null # Item Manager, used to load, modify and save items + +signal on_item_changed(item) +signal new_custom_property_created +signal custom_property_add_requested +signal custom_property_delete_requested(custom_property_id) + +#TODO: Somehow the properties are initialized twice + +func _ready(): + pass + self.item_manager = Globals.get("item_manager") + +func build_properties(item): + self.item = item + var properties = item._custom_properties + for node in custom_properties_box.get_children(): + if node.has_meta("property"): + custom_properties_box.remove_child(node) + + var number_of_properties = 0 + var property_names = properties.keys() + property_names.sort() + for property_name in property_names: + no_custom_properties_label.hide() + number_of_properties += 1 + + var container = MarginContainer.new() + var property_item = property_item_class.instance() + var type = properties[property_name][0] + + # If there already is a value, read it, otherwise set it to null + var value = null + if properties[property_name].size() == 2: + value = properties[property_name][1] + + property_item.initialize(property_name, type, value, 0, "", true) + property_item.connect("custom_property_delete_requested", self, "emit_signal", ["custom_property_delete_requested", property_name, ]) + property_item.connect("property_item_load_button_down", self, "_property_item_requests_file_dialog", []) + var changed_values = [] + property_item.connect("on_property_value_changed", self, "item_changed", changed_values) + + container.set_meta("property", true) + container.add_child(property_item) + custom_properties_box.add_child(container) + + pass + if number_of_properties == 0: + no_custom_properties_label.show() + + +# Fires signal when the item's custom properties is to be updated, delegates to data_editor_gui. +func item_changed(property, value): + if item: + item._custom_properties[property][1] = value + emit_signal("on_item_changed", item) + + +# Delegates the deletion +func delete_custom_property(property_name): + emit_signal("custom_property_delete_requested", property_name) + + +# Fires signal when the item's custom properties is to be updated, delegates to data_editor_gui.func _on_NewCustomPropertyButton_button_down(): +func _on_NewCustomPropertyButton_button_down(): + emit_signal("custom_property_add_requested") \ No newline at end of file diff --git a/custom_properties.tscn b/custom_properties.tscn new file mode 100644 index 0000000..67d2a49 --- /dev/null +++ b/custom_properties.tscn @@ -0,0 +1,134 @@ +[gd_scene load_steps=4 format=1] + +[ext_resource path="res://addons/DataEditor/style/light_gray_panel.tres" type="StyleBox" id=1] +[ext_resource path="res://addons/DataEditor/custom_properties.gd" type="Script" id=2] +[ext_resource path="res://addons/DataEditor/fonts/droid_serif_bold.tres" type="DynamicFont" id=3] + +[node name="Panel" type="Panel"] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 0.0 +margin/bottom = 0.0 +custom_styles/panel = ExtResource( 1 ) +script/script = ExtResource( 2 ) + +[node name="Body" type="VBoxContainer" parent="."] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 10.0 +margin/top = 10.0 +margin/right = 10.0 +margin/bottom = 10.0 +alignment = 0 + +[node name="TitleLabel" type="Label" parent="Body"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1260.0 +margin/bottom = 2.0 +custom_fonts/font = ExtResource( 3 ) +text = "Custom Properties" +uppercase = true +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="HSeparator" type="HSeparator" parent="Body"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 6.0 +margin/right = 1260.0 +margin/bottom = 9.0 + +[node name="Spacer" type="Control" parent="Body"] + +rect/min_size = Vector2( 0, 15 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 13.0 +margin/right = 1260.0 +margin/bottom = 28.0 + +[node name="Scroll" type="ScrollContainer" parent="Body"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 32.0 +margin/right = 1260.0 +margin/bottom = 556.0 +scroll/horizontal = true +scroll/vertical = true + +[node name="CustomProperties" type="VBoxContainer" parent="Body/Scroll"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 441.0 +margin/bottom = 524.0 +alignment = 0 + +[node name="NoCustomPropertiesLabel" type="Label" parent="Body/Scroll/CustomProperties"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 441.0 +margin/bottom = 14.0 +text = "There are no custom properties for this item. Click below to add one." +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="NewCustomPropertyButton" type="Button" parent="Body"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 560.0 +margin/right = 1260.0 +margin/bottom = 580.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Add new custom property" +flat = false + +[connection signal="button_down" from="Body/NewCustomPropertyButton" to="." method="_on_NewCustomPropertyButton_button_down"] + + diff --git a/data.gd b/data.gd new file mode 100644 index 0000000..ea41eac --- /dev/null +++ b/data.gd @@ -0,0 +1,110 @@ +extends Node + +var item_manager = null +var items = {} +var values = {} + +signal any_value_changed(item, property, value) + + +var signals_any_value_changed = [] +var signals_item_class_any_value_changed = [] +var signals_item_any_values_changed = [] +var signals_item_value_changed = [] + +func _init(): + self.item_manager = preload("item_manager.gd").new() + self.items = item_manager.items + +# TODO: Allow eager loading + + + +func get_item(item_class, id): + return item_manager.get_item(item_class, id) + +func get_items(item_class): + return item_manager.get_items(item_class) + +# if items[item_class].has(id): +# return items[item_class][id] +# else: +# _load_item(item_class, id) +# return items[item_class][id] + +func _load_item(item_class, id): + items[item_class][id] = item_manager.load_item(item_class, id) +# values[item_class][id] = {} + +func load_values_of_all_items(): + pass + +func load_item_value(item, property): + return get_progress(item._class, item._id, property) + + +func get_progress(item_class, id, property): + if items[item_class].has(id) and items[item_class][id].has(property): + return items[item_class][id][property] + + +func set_progress(item_class, id, property, value): + var item = item_manager.get_item(item_class, id) + var has_value = item.get(property) + if item and has_value: + item.set(property, value) + emit_signal("any_value_changed", item, property, value) + + var signal_name = "" + signal_name = item_class + # Class signal + if has_user_signal(signal_name): + emit_signal(signal_name, item, property, value) + + # Item signal + signal_name = item_class + "|" + id + if has_user_signal(signal_name): + emit_signal(signal_name, item, property, value) + + # Property signal + signal_name = item_class + "|" + id + "|" + property + if has_user_signal(signal_name): + emit_signal(signal_name, item, property, value) + + return true + else: + return false + +func observe_all_changes(observer, method, binds=[], flags = 0): + self.connect("any_value_changed", observer, method, binds, flags) + +func observe_class(observer, item_class, method, binds=[], flags = 0): + self.add_user_signal(item_class) # TODO: Args + self.connect(item_class, observer, method, binds, flags) + +func observe_item(observer, item, method, binds=[], flags = 0): + var signal_name = item._class + "|" + item._id + if not has_user_signal(signal_name): + self.add_user_signal(signal_name) # TODO: Args + self.connect(signal_name, observer, method, binds, flags) + +func observe_item_property(observer, item, property, method, binds=[], flags = 0): + var signal_name = item._class + "|" + item._id + "|" + property + if not has_user_signal(signal_name): + self.add_user_signal(signal_name) # TODO: Args + self.connect(signal_name, observer, method, binds, flags) + +func stop_observing_all_changes(observer): + pass +# observer.disconnect( + +#TODO: func block_signals() + + +func set_item_progress(item, property, value): + set_progress(item._class, item._id, property, value) + +func get_progress_by_item(item): + + + \ No newline at end of file diff --git a/data_editor.gd b/data_editor.gd new file mode 100644 index 0000000..dba5289 --- /dev/null +++ b/data_editor.gd @@ -0,0 +1,97 @@ +tool +extends EditorPlugin + +var data_editor_class = preload("data_editor_gui.tscn") +var data_class = preload("data.gd") +var gui = null + +var all_items = {} + +signal data_item_class_opened(item_class) + +func _enter_tree(): + OS.set_low_processor_usage_mode(true) + check_for_data_singleton() + check_plugin_settings() + gui = data_editor_class.instance() + get_editor_viewport().add_child(gui) + gui.set_area_as_parent_rect() + gui.hide() + + +# Remove control and data singleton +func _exit_tree(): + OS.set_low_processor_usage_mode(false) + get_editor_viewport().remove_child(gui) + gui.free() + var config = ConfigFile.new() + var status = config.load("res://engine.cfg") + if status == OK: + if config.has_section_key("autoload", "data"): + config.set_value("autoload", "data", null) + config.save("res://engine.cfg") + # Check if the Classes and Data folders exist + Globals.clear("item_manager") + +func _ready(): + gui.connect("class_edit_requested", self, "edit_class", []) + Globals.set("debug_is_editor", true) + +# Opens the selected class in the Script Editor +func edit_class(item_class): + edit_resource(item_class) + + +func check_for_data_singleton(): + var config = ConfigFile.new() + var status = config.load("res://engine.cfg") + + + if status == OK: + if not config.has_section_key("autoload", "data"): + config.set_value("autoload", "data", "*res://addons/DataEditor/data.gd") + config.save("res://engine.cfg") + #var directory = Directory.new() + #directory.copy("res://engine.cfg", "res://engine.cfg_BACKUP") + +# Load the plugin settings and adds default if they do not exist. +# TODO: Obtain defaults from dialog +func check_plugin_settings(): + var config = ConfigFile.new() + var status = config.load("res://addons/DataEditor/plugin.cfg") + if status == OK: + if not config.has_section_key("custom", "class_directory"): + config.set_value("custom", "class_directory", "res://classes") + # TODO: Create folders + if not config.has_section_key("custom", "extension"): + config.set_value("custom", "extension", "json") + if not config.has_section_key("custom", "output_directory"): + config.set_value("custom", "output_directory", "res://data") + # TODO: Create folders + if not config.has_section_key("custom", "password"): + config.set_value("custom", "password", "") + if not config.has_section_key("custom", "sanitize_ids"): + config.set_value("custom", "sanitize_ids", true) + if not config.has_section_key("custom", "serializer"): + config.set_value("custom", "serializer", "json") + config.save("res://addons/DataEditor/plugin.cfg") + + +# Virtual: Name of the tool button on top +func get_name(): + return "Data" + +# Virtual: Makes sure that the control owns the main screen +func has_main_screen(): + return true + +# Virtual: +func make_visible(visible): + if(visible): + gui.reload() + gui.show() + + else: + gui.hide() + + diff --git a/data_editor_gui.gd b/data_editor_gui.gd new file mode 100644 index 0000000..17df28b --- /dev/null +++ b/data_editor_gui.gd @@ -0,0 +1,431 @@ +tool +extends Control + +var selected_item = null +var selected_id = null +var selected_class = null + + +onready var item_tree = get_node("VBox/Body/ItemTree") +onready var id_label = get_node("VBox/Body/Content/VBox/Container/ItemIdLabel") + +onready var instance_details = get_node("VBox/Body/Content/VBox/InstanceDetails/") +onready var class_properties = get_node("VBox/Body/Content/VBox/InstanceDetails/HBox/ClassProperties") +onready var custom_properties = get_node("VBox/Body/Content/VBox/InstanceDetails/HBox/CustomProperties") +onready var class_overview = get_node("VBox/Body/Content/VBox/ClassOverview") +onready var no_classes = get_node("VBox/Body/Content/VBox/NoClasses") + +#onready var last_modified_date = get_node("VBox/Body/Content/VBox/Container/GridContainer/LastModifiedDate") +#onready var created_date = get_node("VBox/Body/Content/VBox/Container/GridContainer/CreatedDate") + + +onready var new_custom_property_dialog = get_node("NewCustomPropertyDialog") +onready var new_custom_property_name = get_node("NewCustomPropertyDialog/LineEdit") +onready var new_custom_property_type_options = get_node("NewCustomPropertyDialog/TypeOptions") + +onready var add_button = get_node("VBox/Head/Add") +onready var delete_button = get_node("VBox/Head/Delete") +onready var duplicate_button = get_node("VBox/Head/Duplicate") +onready var change_display_name_button = get_node("VBox/Body/Content/VBox/Container/HBox/DisplayName") +onready var rename_button = get_node("VBox/Head/Rename") +onready var save_button = get_node("VBox/Head/Save") +onready var save_all_button = get_node("VBox/Head/SaveAll") + +onready var copy_id_button = get_node("VBox/Body/Content/VBox/Container/HBox/CopyId") +onready var edit_class_button = get_node("VBox/Body/Content/VBox/Container/HBox/EditClass") + +# Dialogs +onready var input_dialog = get_node("InputDialog") + +onready var new_item_class_dialog = get_node("NewClassDialog") +onready var new_item_class_name = get_node("NewClassDialog/ClassName") +onready var new_item_class_icon = get_node("NewClassDialog/ClassIconPath") +onready var new_item_class_icon_dialog = get_node("NewClassDialog/ClassIconFileDialog") + + +#onready var delete_item_dialog = get_node("DeleteItemDialog") + +#onready var rename_item_dialog = get_node("RenameItemDialog") +#onready var rename_item_id = get_node("RenameItemDialog/Id") + +#onready var duplicate_item_dialog = get_node("DuplicateItemDialog") +#onready var duplicate_item_id = get_node("DuplicateItemDialog/Id") + +#onready var delete_class_dialog = get_node("DeleteClassDialog") + +#onready var display_name_dialog = get_node("DisplayNameDialog") +#onready var display_name_dialog_name = get_node("DisplayNameDialog/Name") + +onready var warn_dialog = get_node("WarnDialog") +onready var options_screen = get_node("OptionsDialog") + +var item_tree_class = preload("item_tree.tscn") +#var active_element = null +var item_manager = null + +signal class_edit_requested(script) +signal input_dialog_confirmed(text1, text2) + +# First initialize the item manager which is used for loading, saving and configs +func _init(): + item_manager = preload("item_manager.gd").new() + #item_manager.set_name("ItemManager") + #Globals.set("item_manager", item_manager) +# self.add_child(item_manager) +# item_manager.raise() + + + + +func _ready(): + #add_button.set_shortcut(create_shortcut(KEY_MASK_CMD|KEY_N)) + #delete_button.set_shortcut(create_shortcut(KEY_DELETE)) + #save_button.set_shortcut(create_shortcut(KEY_MASK_SHIFT|KEY_MASK_CMD|KEY_S)) + #save_all_button.set_shortcut(create_shortcut(KEY_MASK_SHIFT|KEY_MASK_CMD|KEY_MASK_ALT|KEY_S)) + + Globals.set("debug_is_editor", false) + # Tree signals + item_tree.connect("on_new_item_pressed", self, "handle_actions", ["add"]) + item_tree.connect("on_rename_pressed", self, "handle_actions", ["rename"]) + item_tree.connect("on_delete_pressed", self, "handle_actions", ["delete"]) + item_tree.connect("on_duplicate_pressed", self, "handle_actions", ["duplicate"]) + item_tree.connect("on_item_selected", self, "change_item_context", []) + item_tree.connect("on_open", self, "open_item", []) + + custom_properties.connect("custom_property_add_requested", self, "handle_actions", ["add_custom_property"]) + custom_properties.connect("new_custom_property_created", self, "handle_actions", ["add_custom_property"]) + custom_properties.connect("custom_property_delete_requested", self, "delete_custom_property", []) + class_properties.connect("on_item_changed", self, "toggle_item_dirty_state", []) + + options_screen.connect("extension_changed", item_manager, "rename_extension_of_all_items", []) + options_screen.connect("encryption_changed", item_manager, "delete_and_resave", []) + + item_manager.connect("class_insertion_failed", self, "show_warning", []) + item_manager.connect("item_insertion_failed", self, "show_warning", []) + item_manager.connect("custom_property_insertion_failed", self, "show_warning", []) + item_manager.connect("item_duplication_failed", self, "show_warning", []) + + # Add types to the custom property type dropdown + var type_names = item_manager.type_names.keys() + type_names.sort() + new_custom_property_type_options.clear() + var index = 0 + for type in type_names: + new_custom_property_type_options.add_item(type) + new_custom_property_type_options.set_item_metadata(index, item_manager.type_names[type]) + index += 1 + + # No classes available + var has_no_classes = item_manager.classes.size() == 0 + if has_no_classes: + change_display_name_button.set_disabled(has_no_classes) + duplicate_button.set_disabled(true) + save_button.set_disabled(true) + save_all_button.set_disabled(true) + save_all_button.set_disabled(true) + rename_button.set_disabled(true) + add_button.set_disabled(true) + delete_button.set_disabled(true) + copy_id_button.set_disabled(true) + edit_class_button.set_disabled(true) + no_classes.show() + id_label.set_text("No Classes") + instance_details.hide() + class_overview.hide() + else: + # Select the first item in the tree when loading the GUI + change_item_context(selected_item, selected_class) + +# TODO: Other OS... +func open_item(): + var item_path = item_manager.get_full_path(selected_item) + print(item_path) + OS.execute("explorer", [item_path], false) + +func change_item_context(selected_item, selected_class): + + if selected_class: + self.selected_class = selected_class + + + + # TODO: Move to method, clean up + var has_no_classes = item_manager.classes.size() == 0 + if has_no_classes: + change_display_name_button.set_disabled(has_no_classes) + duplicate_button.set_disabled(true) + save_button.set_disabled(true) + save_all_button.set_disabled(true) + save_all_button.set_disabled(true) + rename_button.set_disabled(true) + add_button.set_disabled(true) + delete_button.set_disabled(true) + copy_id_button.set_disabled(true) + edit_class_button.set_disabled(true) + no_classes.show() + instance_details.hide() + class_overview.hide() + id_label.set_text("No Classes") + + + # An item was selected + if selected_item: + + # Context was lost, e.g. because of changes to the classes. Get a new copy from the item_manager + if selected_item and not selected_item.get("_id"): + self.item_manager.load_manager() + self.item_tree.load_tree(true) + selected_item = item_tree.select_first_element() + + + change_display_name_button.set_disabled(false) + duplicate_button.set_disabled(false) + save_button.set_disabled(false) + save_all_button.set_disabled(false) + rename_button.set_disabled(false) + add_button.set_disabled(false) + delete_button.set_disabled(false) + copy_id_button.set_disabled(false) + edit_class_button.set_disabled(false) + + self.selected_item = selected_item + self.selected_id = selected_item._id + class_overview.hide() + no_classes.hide() + instance_details.show() + if selected_item._display_name == selected_id: + id_label.set_text(selected_id) + else: + id_label.set_text(selected_item._display_name + " (" + selected_id + ")") + + class_properties.build_properties(selected_item) + custom_properties.build_properties(selected_item) + # A class was selected + elif selected_class: + change_display_name_button.set_disabled(true) + duplicate_button.set_disabled(true) + save_button.set_disabled(true) + save_all_button.set_disabled(false) + rename_button.set_disabled(false) + add_button.set_disabled(false) + delete_button.set_disabled(false) + copy_id_button.set_disabled(false) + edit_class_button.set_disabled(false) + + self.selected_item = null + self.selected_id = null + id_label.set_text(selected_class.capitalize() + " " + (selected_class)) + class_overview.show() + instance_details.hide() + no_classes.hide() + + + +func _on_ItemTree_on_new_item_created(new_item): + selected_item = new_item + + +func create_shortcut(keys): + var short_cut = ShortCut.new() + var input_event = InputEvent() + input_event.type = InputEvent.KEY + input_event.ID = keys + short_cut.set_shortcut(input_event) + +func warn_about_reload(): + if item_manager.has_unsaved_items(): + input_dialog.popup(self, "reload_confirmed", tr("Confirm reload"), tr("Some changes have not been saved. \nThey will be discarded if you proceed. Are you sure you want to perform this action?")) + +func reload(): + item_manager.load_manager() + item_tree.load_tree(true) + if item_manager.get_item(selected_class, selected_id): + item_tree.select_item(item_manager.get_item(selected_class, selected_id)) + + + #var last_selected_class = selected_class + #var last_selected_id = selected_id + #selected_item = item_manager.get_item(last_selected_class, last_selected_id) + #if selected_item: + #selected_id = selected_item._id + #change_item_context(selected_item, selected_class) + + +func toggle_item_dirty_state(item): + item._dirty = true + item_tree.set_tree_item_label_text(item) + +#func show_delete_custom_property_dialog(property_name): +# input_dialog.popup(self, "delete_custom_property", tr("Delete Custom Property"), tr("Are you sure you want to delete this property?")) + +# Validation takes place in the item manager +func _on_NewCustomPropertyDialog_confirmed(): + var custom_property_id = new_custom_property_name.get_text().strip_edges() + var custom_property_type = new_custom_property_type_options.get_selected_metadata() + var success = item_manager.add_custom_property(selected_item, custom_property_id, custom_property_type) + if success: + item_tree.set_tree_item_label_text(selected_item) + toggle_item_dirty_state(selected_item) + custom_properties.build_properties(selected_item) + + + +# TODO: Show confirmation dialog +func delete_custom_property(property_name): + #input_dialog.popup(self, "_on_delete__confirmed", tr("New Item"), tr("Please enter an ID and optionally a display name the new item"), tr("ID"), "", tr("Display Name (optional)"), "") + item_manager.delete_custom_property(selected_item, property_name) + toggle_item_dirty_state(selected_item) + custom_properties.build_properties(selected_item) + + +# TODO: New Class Dialog is still a mess +func _on_AddClassButton_button_down(): + new_item_class_name.set_text("") + new_item_class_icon.set_text("") + new_item_class_icon_dialog.set_current_path("") + new_item_class_dialog.popup_centered() + new_item_class_name.grab_focus() + + +func _on_NewClassDialog_confirmed(): + var name = new_item_class_name.get_text().to_lower() + var icon_path = new_item_class_icon.get_text() + item_manager.create_class(name, icon_path) + item_tree.load_tree() + item_tree.select_class(name) + reload() + edit_class() + + +# New Class Dialog +func _on_NewClassIconSearchButton_button_down(): + new_item_class_icon_dialog.popup_centered() + +func _on_NewClassIconFileDialog_file_selected(path): + new_item_class_icon.set_text(path) + + +func handle_actions(action, argument = ""): + if action == "add": + input_dialog.popup(self, "_on_add_item_confirmed", tr("New Item"), tr("Please enter an ID for and optionally a display name the new item"), tr("ID"), "", tr("Display Name (optional)"), "") + elif action == "rename": + if selected_item: + input_dialog.popup(self, "_on_rename_item_confirmed", tr("Rename Item"), tr("Please enter a new ID for this item."), "ID", selected_id) + else: + input_dialog.popup(self, "_on_rename_class_confirmed", tr("Rename Class"), tr("Please enter a new name for this class. All pending changes will be discarded!"), "ID", selected_class) + elif action == "duplicate": + var new_display_name = "" + if selected_item._dirty: + input_dialog.popup(self, "item_duplication_failed", tr("Item duplication failed"), tr("Before duplicating this item, please first save it.")) + return + if selected_id != selected_item._display_name: + new_display_name = selected_item._display_name + input_dialog.popup(self, "_on_duplicate_confirmed", tr("Duplicate Item"), tr("Please enter a new ID for this item"), "ID", selected_id, tr("Display Name (optional)"), new_display_name) + reload() + elif action == "save": + item_manager.save_item(selected_item) + item_tree.load_tree() + #reload() + elif action == "save_all": + item_manager.save_all_items() + item_tree.load_tree() + #reload() + elif action == "reload": + item_manager.load_manager() + reload() + elif action == "new_class": + _on_AddClassButton_button_down() # TODO: Incorporate into dialog handling + elif action == "delete": + if selected_item: + input_dialog.popup(self, "_on_delete_item_confirmed", tr("Delete Item"), tr("Are you sure you want to delete this item?")) + else: + input_dialog.popup(self, "_on_delete_class_confirmed", tr("Delete Class"), tr("Are you sure you want to delete class along with all items?")) + elif action == "options": + options_screen.popup_centered() + elif action == "edit_class": + edit_class() + elif action == "copy_id": + copy_id() + elif action == "change_display_name": + input_dialog.popup(self, "change_display_name", tr("Change Display Name"), tr("Please enter a display name for this item."), "Display Name", selected_item._display_name) + elif action == "add_custom_property": + new_custom_property_name.set_text("") + new_custom_property_dialog.popup_centered() + new_custom_property_name.grab_focus() + +######################################################################### +# Handlers # +######################################################################### + +func _on_add_item_confirmed(id, display_name): + var new_item = item_manager.create_and_add_new_item(selected_class, id, display_name) + if new_item: + item_tree.add_leaf(new_item, true) + + +func _on_rename_item_confirmed(id): + item_manager.rename_item(selected_item, id) + reload() + + +func _on_rename_class_confirmed(name): + item_manager.rename_class(selected_class, name) + reload() + + +func _on_duplicate_confirmed(id, display_name): + var duplicated_item = item_manager.duplicate_item(selected_item, id, display_name) + item_tree.add_leaf(duplicated_item, true) + + +func _on_delete_item_confirmed(): + item_manager.delete_item(selected_item) + reload() + + +func _on_delete_class_confirmed(): + item_manager.delete_class(selected_class) + if item_manager.classes.size() > 0: + item_tree.select_class(item_manager.class_names[0]) + else: + change_item_context(null, null) + reload() + +######################################################################### +# Buttons on the right # +######################################################################### +func change_display_name(new_name): + selected_item._display_name = new_name + toggle_item_dirty_state(selected_item) + change_item_context(selected_item, selected_class) + +func copy_id(): + if selected_item: + OS.set_clipboard(selected_id) + else: + OS.set_clipboard(selected_class) + +func edit_class(): + var script = null + if selected_item: + script = selected_item.get_script() + else: + script = item_manager.classes[selected_class].new("dummy").get_script() # If no item is selected, create a dummy item + emit_signal("class_edit_requested", script) + +##################################################### +# OTHERS +##################################################### + +func show_warning(title, text): + warn_dialog.set_title(title) + warn_dialog.set_text(text) + warn_dialog.popup_centered() + + +func log_text(text): + var file = File.new() + file.open("res://test.log", File.READ_WRITE) + var old_text = file.get_as_text() + var date = str(OS.get_datetime()["hour"]) + ":" + str(OS.get_datetime()["minute"]) + ":" + str(OS.get_datetime()["second"]) + "\t" + file.store_line(old_text + date + text) diff --git a/data_editor_gui.tscn b/data_editor_gui.tscn new file mode 100644 index 0000000..6236838 --- /dev/null +++ b/data_editor_gui.tscn @@ -0,0 +1,840 @@ +[gd_scene load_steps=22 format=1] + +[ext_resource path="res://addons/DataEditor/data_editor_gui.gd" type="Script" id=1] +[ext_resource path="res://addons/DataEditor/icons/icon_add.png" type="Texture" id=2] +[ext_resource path="res://addons/DataEditor/icons/icon_save.png" type="Texture" id=3] +[ext_resource path="res://addons/DataEditor/icons/icon_rename.png" type="Texture" id=4] +[ext_resource path="res://addons/DataEditor/icons/icon_duplicate.png" type="Texture" id=5] +[ext_resource path="res://addons/DataEditor/icons/icon_remove.png" type="Texture" id=6] +[ext_resource path="res://addons/DataEditor/icons/icon_reload_small.png" type="Texture" id=7] +[ext_resource path="res://addons/DataEditor/icons/icon_script.png" type="Texture" id=8] +[ext_resource path="res://addons/DataEditor/icons/icon_options.png" type="Texture" id=9] +[ext_resource path="res://addons/DataEditor/item_tree.tscn" type="PackedScene" id=10] +[ext_resource path="res://addons/DataEditor/icons/icon_display-name.png" type="Texture" id=11] +[ext_resource path="res://addons/DataEditor/icons/icon_copy.png" type="Texture" id=12] +[ext_resource path="res://addons/DataEditor/icons/icon_edit.png" type="Texture" id=13] +[ext_resource path="res://addons/DataEditor/fonts/droid_sans_title.tres" type="DynamicFont" id=14] +[ext_resource path="res://addons/DataEditor/class_properties.tscn" type="PackedScene" id=15] +[ext_resource path="res://addons/DataEditor/custom_properties.tscn" type="PackedScene" id=16] +[ext_resource path="res://addons/DataEditor/class_overview.tscn" type="PackedScene" id=17] +[ext_resource path="res://addons/DataEditor/no_classes.tscn" type="PackedScene" id=18] +[ext_resource path="res://addons/DataEditor/warn.gd" type="Script" id=19] +[ext_resource path="res://addons/DataEditor/options.tscn" type="PackedScene" id=20] +[ext_resource path="res://addons/DataEditor/input_dialog.tscn" type="PackedScene" id=21] + +[node name="DataEditor" type="Control"] + +anchor/right = 1 +anchor/bottom = 1 +rect/min_size = Vector2( 800, 0 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 0.0 +margin/bottom = 0.0 +script/script = ExtResource( 1 ) + +[node name="VBox" type="VBoxContainer" parent="."] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 0.0 +margin/bottom = 0.0 +alignment = 0 + +[node name="Head" type="HBoxContainer" parent="VBox"] + +rect/min_size = Vector2( 0, 22 ) +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1280.0 +margin/bottom = 28.0 +alignment = 0 + +[node name="Add" type="ToolButton" parent="VBox/Head"] + +rect/min_size = Vector2( 28, 28 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 56.0 +margin/bottom = 28.0 +disabled = true +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Add" +icon = ExtResource( 2 ) +flat = true +align = 0 + +[node name="VSeparator5" type="VSeparator" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 60.0 +margin/top = 0.0 +margin/right = 63.0 +margin/bottom = 28.0 + +[node name="Save" type="ToolButton" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 67.0 +margin/top = 0.0 +margin/right = 127.0 +margin/bottom = 28.0 +disabled = true +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Save" +icon = ExtResource( 3 ) +flat = false + +[node name="SaveAll" type="ToolButton" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 131.0 +margin/top = 0.0 +margin/right = 212.0 +margin/bottom = 28.0 +disabled = true +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Save All" +icon = ExtResource( 3 ) +flat = false + +[node name="VSeparator" type="VSeparator" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 216.0 +margin/top = 0.0 +margin/right = 219.0 +margin/bottom = 28.0 + +[node name="Rename" type="ToolButton" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 223.0 +margin/top = 0.0 +margin/right = 306.0 +margin/bottom = 28.0 +disabled = true +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Rename" +icon = ExtResource( 4 ) +flat = false + +[node name="Duplicate" type="ToolButton" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 310.0 +margin/top = 0.0 +margin/right = 402.0 +margin/bottom = 28.0 +disabled = true +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Duplicate" +icon = ExtResource( 5 ) +flat = false + +[node name="Delete" type="ToolButton" parent="VBox/Head"] + +rect/min_size = Vector2( 28, 28 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 406.0 +margin/top = 0.0 +margin/right = 480.0 +margin/bottom = 28.0 +disabled = true +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Delete" +icon = ExtResource( 6 ) +flat = true + +[node name="VSeparator3" type="VSeparator" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 484.0 +margin/top = 0.0 +margin/right = 487.0 +margin/bottom = 28.0 + +[node name="Refresh2" type="ToolButton" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 491.0 +margin/top = 0.0 +margin/right = 563.0 +margin/bottom = 28.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Reload" +icon = ExtResource( 7 ) +flat = false + +[node name="VSeparator2" type="VSeparator" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 567.0 +margin/top = 0.0 +margin/right = 570.0 +margin/bottom = 28.0 + +[node name="NewClass" type="ToolButton" parent="VBox/Head"] + +rect/min_size = Vector2( 28, 28 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 574.0 +margin/top = 0.0 +margin/right = 670.0 +margin/bottom = 28.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "New Class" +icon = ExtResource( 8 ) +flat = true +align = 0 + +[node name="VSeparator4" type="VSeparator" parent="VBox/Head"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 674.0 +margin/top = 0.0 +margin/right = 677.0 +margin/bottom = 28.0 + +[node name="Options" type="ToolButton" parent="VBox/Head"] + +rect/min_size = Vector2( 28, 28 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 681.0 +margin/top = 0.0 +margin/right = 763.0 +margin/bottom = 28.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Options" +icon = ExtResource( 9 ) +flat = true +align = 0 + +[node name="Search" type="LineEdit" parent="VBox/Head"] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 377.0 +margin/top = 0.0 +margin/right = 435.0 +margin/bottom = 28.0 +placeholder/text = "Search" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="HSeparator" type="HSeparator" parent="VBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 32.0 +margin/right = 1280.0 +margin/bottom = 35.0 + +[node name="Body" type="HSplitContainer" parent="VBox"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 39.0 +margin/right = 1280.0 +margin/bottom = 600.0 +split/offset = 0 +split/collapsed = false +split/dragger_visibility = 0 + +[node name="ItemTree" parent="VBox/Body" instance=ExtResource( 10 )] + +anchor/right = 0 +anchor/bottom = 0 +rect/min_size = Vector2( 165, 0 ) +size_flags/horizontal = 2 +size_flags/stretch_ratio = 0.0 +margin/right = 165.0 +margin/bottom = 561.0 + +[node name="Content" type="Panel" parent="VBox/Body"] + +visibility/self_opacity = 0.0 +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 177.0 +margin/top = 0.0 +margin/right = 1280.0 +margin/bottom = 561.0 + +[node name="VBox" type="VBoxContainer" parent="VBox/Body/Content"] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +size_flags/stretch_ratio = 0.0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 0.0 +margin/bottom = 0.0 +alignment = 0 + +[node name="Container" type="MarginContainer" parent="VBox/Body/Content/VBox"] + +rect/min_size = Vector2( 0, 25 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1103.0 +margin/bottom = 25.0 + +[node name="HBox" type="HBoxContainer" parent="VBox/Body/Content/VBox/Container"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 8.0 +margin/top = 0.0 +margin/right = 1103.0 +margin/bottom = 25.0 +custom_constants/separation = 15 +alignment = 2 + +[node name="DisplayName" type="ToolButton" parent="VBox/Body/Content/VBox/Container/HBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 0 +size_flags/vertical = 2 +margin/left = 722.0 +margin/top = 0.0 +margin/right = 892.0 +margin/bottom = 25.0 +disabled = true +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Change Display Name" +icon = ExtResource( 11 ) +flat = true +align = 0 + +[node name="CopyId" type="ToolButton" parent="VBox/Body/Content/VBox/Container/HBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 0 +size_flags/vertical = 2 +margin/left = 907.0 +margin/top = 0.0 +margin/right = 988.0 +margin/bottom = 25.0 +disabled = true +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Copy ID" +icon = ExtResource( 12 ) +flat = true +align = 0 + +[node name="EditClass" type="ToolButton" parent="VBox/Body/Content/VBox/Container/HBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 0 +size_flags/vertical = 2 +margin/left = 1003.0 +margin/top = 0.0 +margin/right = 1095.0 +margin/bottom = 25.0 +disabled = true +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Edit Class" +icon = ExtResource( 13 ) +flat = true +align = 0 + +[node name="ItemIdLabel" type="Label" parent="VBox/Body/Content/VBox/Container"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 8.0 +margin/top = 0.0 +margin/right = 1103.0 +margin/bottom = 25.0 +custom_fonts/font = ExtResource( 14 ) +custom_colors/font_color_shadow = Color( 0.300781, 0.300781, 0.300781, 1 ) +custom_constants/shadow_offset_x = 1 +custom_constants/shadow_offset_y = 1 +text = "No Classes" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="GridContainer" type="GridContainer" parent="VBox/Body/Content/VBox/Container"] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 0 +size_flags/vertical = 2 +margin/left = 555.0 +margin/top = 0.0 +margin/right = 555.0 +margin/bottom = 25.0 +custom_constants/hseparation = 10 +columns = 2 + +[node name="Created" type="Label" parent="VBox/Body/Content/VBox/Container/GridContainer"] + +visibility/opacity = 0.25 +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 91.0 +margin/bottom = 14.0 +text = "Created:" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="CreatedDate" type="Label" parent="VBox/Body/Content/VBox/Container/GridContainer"] + +visibility/opacity = 0.25 +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 101.0 +margin/top = 0.0 +margin/right = 217.0 +margin/bottom = 14.0 +text = "01.01.01 01:01:01" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="LastModifiedLabel" type="Label" parent="VBox/Body/Content/VBox/Container/GridContainer"] + +visibility/opacity = 0.25 +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 18.0 +margin/right = 91.0 +margin/bottom = 32.0 +text = "Last modified:" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="LastModifiedDate" type="Label" parent="VBox/Body/Content/VBox/Container/GridContainer"] + +visibility/opacity = 0.25 +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 101.0 +margin/top = 18.0 +margin/right = 217.0 +margin/bottom = 32.0 +text = "01.01.01 01:01:01" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBox/Body/Content/VBox/Container"] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 8.0 +margin/top = 0.0 +margin/right = 1103.0 +margin/bottom = 25.0 +alignment = 0 + +[node name="HSeparator" type="HSeparator" parent="VBox/Body/Content/VBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 29.0 +margin/right = 1103.0 +margin/bottom = 32.0 + +[node name="Spacer" type="Control" parent="VBox/Body/Content/VBox"] + +rect/min_size = Vector2( 0, 10 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 36.0 +margin/right = 1103.0 +margin/bottom = 46.0 + +[node name="InstanceDetails" type="PanelContainer" parent="VBox/Body/Content/VBox"] + +editor/display_folded = true +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 50.0 +margin/right = 1103.0 +margin/bottom = 303.0 + +[node name="HBox" type="HBoxContainer" parent="VBox/Body/Content/VBox/InstanceDetails"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 7.0 +margin/top = 7.0 +margin/right = 1096.0 +margin/bottom = 246.0 +alignment = 0 + +[node name="ClassProperties" parent="VBox/Body/Content/VBox/InstanceDetails/HBox" instance=ExtResource( 15 )] + +anchor/right = 0 +anchor/bottom = 0 +margin/right = 542.0 +margin/bottom = 239.0 + +[node name="CustomProperties" parent="VBox/Body/Content/VBox/InstanceDetails/HBox" instance=ExtResource( 16 )] + +anchor/right = 0 +anchor/bottom = 0 +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 546.0 +margin/right = 1089.0 +margin/bottom = 239.0 + +[node name="ClassOverview" parent="VBox/Body/Content/VBox" instance=ExtResource( 17 )] + +visibility/visible = false +anchor/right = 0 +anchor/bottom = 0 +margin/top = 50.0 +margin/right = 1103.0 +margin/bottom = 561.0 + +[node name="NoClasses" parent="VBox/Body/Content/VBox" instance=ExtResource( 18 )] + +anchor/right = 0 +anchor/bottom = 0 +margin/left = 0.0 +margin/top = 50.0 +margin/right = 1103.0 +margin/bottom = 561.0 + +[node name="WarnDialog" type="AcceptDialog" parent="."] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 350.0 +margin/bottom = 110.0 +popup/exclusive = true +window/title = "" +dialog/hide_on_ok = true +script/script = ExtResource( 19 ) + +[node name="NewClassDialog" type="ConfirmationDialog" parent="."] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 519.0 +margin/top = 193.0 +margin/right = 990.0 +margin/bottom = 316.0 +popup/exclusive = true +window/title = "New Class" +dialog/text = "Enter the name and the path to an optional icon" +dialog/hide_on_ok = true + +[node name="ClassName" type="LineEdit" parent="NewClassDialog"] + +focus_neighbour/bottom = NodePath("../ClassIconPath") +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 8.0 +margin/top = 27.0 +margin/right = 466.0 +margin/bottom = 51.0 +placeholder/text = "Name" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="ClassIconPath" type="LineEdit" parent="NewClassDialog"] + +focus_neighbour/top = NodePath("../ClassName") +focus_neighbour/right = NodePath("../NewClassIconSearchButton") +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 8.0 +margin/top = 60.0 +margin/right = 393.0 +margin/bottom = 84.0 +placeholder/text = "Icon Path (optional)" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="ClassIconFileDialog" type="FileDialog" parent="NewClassDialog"] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 393.0 +margin/bottom = 404.0 +popup/exclusive = true +window/title = "Open a File" +dialog/hide_on_ok = true +mode = 0 +access = 2 +filters = StringArray( "*.png" ) +show_hidden_files = false + +[node name="NewClassIconSearchButton" type="Button" parent="NewClassDialog"] + +focus_neighbour/left = NodePath("../ClassIconPath") +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 399.0 +margin/top = 61.0 +margin/right = 466.0 +margin/bottom = 83.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Search" +flat = false + +[node name="OptionsDialog" parent="." instance=ExtResource( 20 )] + +visibility/visible = false +margin/right = 550.0 +margin/bottom = 310.0 + +[node name="InputDialog" parent="." instance=ExtResource( 21 )] + +visibility/visible = false + +[node name="NewCustomPropertyDialog" type="ConfirmationDialog" parent="."] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 117.0 +margin/top = 117.0 +margin/right = 489.0 +margin/bottom = 243.0 +popup/exclusive = false +window/title = "New custom property" +dialog/text = "Enter the name and type of the property" +dialog/hide_on_ok = true + +[node name="LineEdit" type="LineEdit" parent="NewCustomPropertyDialog"] + +focus_neighbour/bottom = NodePath("../TypeOptions") +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 7.0 +margin/top = 29.0 +margin/right = 363.0 +margin/bottom = 53.0 +placeholder/text = "Name" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="TypeOptions" type="OptionButton" parent="NewCustomPropertyDialog"] + +focus_neighbour/top = NodePath("../LineEdit") +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 7.0 +margin/top = 64.0 +margin/right = 363.0 +margin/bottom = 84.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "BOOL" +flat = false +align = 0 +selected = 0 +items = [ "BOOL", null, false, -1, 1, "COLOR", null, false, -1, 14, "IMAGE", null, false, -1, 15, "INT", null, false, -1, 2, "NODE_PATH", null, false, -1, 16, "OBJECT", null, false, -1, 18, "PLANE", null, false, -1, 9, "QUAT", null, false, -1, 10, "REAL", null, false, -1, 3, "RECT2", null, false, -1, 6, "STRING", null, false, -1, 4, "TRANSFORM", null, false, -1, 13, "VECTOR2", null, false, -1, 5, "VECTOR3", null, false, -1, 7 ] + +[connection signal="button_down" from="VBox/Head/Add" to="." method="handle_actions" binds= [ "add" ]] + +[connection signal="button_down" from="VBox/Head/Save" to="." method="handle_actions" binds= [ "save" ]] + +[connection signal="button_down" from="VBox/Head/SaveAll" to="." method="handle_actions" binds= [ "save_all" ]] + +[connection signal="button_down" from="VBox/Head/Rename" to="." method="handle_actions" binds= [ "rename" ]] + +[connection signal="button_down" from="VBox/Head/Duplicate" to="." method="handle_actions" binds= [ "duplicate" ]] + +[connection signal="button_down" from="VBox/Head/Delete" to="." method="handle_actions" binds= [ "delete" ]] + +[connection signal="button_down" from="VBox/Head/Refresh2" to="." method="handle_actions" binds= [ "reload" ]] + +[connection signal="button_down" from="VBox/Head/NewClass" to="." method="handle_actions" binds= [ "new_class" ]] + +[connection signal="button_down" from="VBox/Head/Options" to="." method="handle_actions" binds= [ "options" ]] + +[connection signal="button_down" from="VBox/Body/Content/VBox/Container/HBox/DisplayName" to="." method="handle_actions" binds= [ "change_display_name" ]] + +[connection signal="button_down" from="VBox/Body/Content/VBox/Container/HBox/CopyId" to="." method="handle_actions" binds= [ "copy_id" ]] + +[connection signal="button_down" from="VBox/Body/Content/VBox/Container/HBox/EditClass" to="." method="handle_actions" binds= [ "edit_class" ]] + +[connection signal="confirmed" from="NewClassDialog" to="." method="_on_NewClassDialog_confirmed"] + +[connection signal="file_selected" from="NewClassDialog/ClassIconFileDialog" to="." method="_on_NewClassIconFileDialog_file_selected"] + +[connection signal="button_down" from="NewClassDialog/NewClassIconSearchButton" to="." method="_on_NewClassIconSearchButton_button_down"] + +[connection signal="confirmed" from="NewCustomPropertyDialog" to="." method="_on_NewCustomPropertyDialog_confirmed"] + + diff --git a/data_item.gd b/data_item.gd new file mode 100644 index 0000000..ca64e86 --- /dev/null +++ b/data_item.gd @@ -0,0 +1,58 @@ +extends Node + +var _items_path = "" + +# Class information +# No setter, that's why there is a comma +var _class setget ,get_class +var _class_name setget ,get_class_name + +var _dirty = false # TODO: Exclude +var _persistent = false # TODO: Exclude + + +var _id = "" +var _display_name setget set_display_name,get_display_name +var _created = 0 +var _last_modified = 0 + + +# Instance-level custom properties, consists of arrays containing name, type, (hint and hint_text), default value +var _custom_properties = {} + +func _ready(): + var config = ConfigFile.new() + config.load("res://addons/DataEditor/plugin.cfg") + _items_path = config.has_section_key("plugin", "output_directory") + +func get_class(): + return self.get_script().get_path().get_file().basename() + +func get_class_name(): + return self.get_class().capitalize() + + +func get_display_name(): + if _display_name == null or _display_name == "": + return self._id + else: + return _display_name + +func set_display_name(name): + _display_name = name + + +func sanitize_value(property, type, value): + if property["type"] == TYPE_COLOR: + value = value.to_html() + elif property["type"] == TYPE_STRING: + value = value.json_escape() + return value + + + +func _init(id): + self._id = id + +func _notification(what): + print(what) \ No newline at end of file diff --git a/fonts/DroidSans.ttf b/fonts/DroidSans.ttf new file mode 100644 index 0000000..efd1f8b Binary files /dev/null and b/fonts/DroidSans.ttf differ diff --git a/fonts/DroidSerif-Bold.ttf b/fonts/DroidSerif-Bold.ttf new file mode 100644 index 0000000..108ed39 Binary files /dev/null and b/fonts/DroidSerif-Bold.ttf differ diff --git a/fonts/DroidSerif-BoldItalic.ttf b/fonts/DroidSerif-BoldItalic.ttf new file mode 100644 index 0000000..045e8a6 Binary files /dev/null and b/fonts/DroidSerif-BoldItalic.ttf differ diff --git a/fonts/DroidSerif-Italic.ttf b/fonts/DroidSerif-Italic.ttf new file mode 100644 index 0000000..689a1ba Binary files /dev/null and b/fonts/DroidSerif-Italic.ttf differ diff --git a/fonts/droid_sans_title.tres b/fonts/droid_sans_title.tres new file mode 100644 index 0000000..922bedb --- /dev/null +++ b/fonts/droid_sans_title.tres @@ -0,0 +1,11 @@ +[gd_resource type="DynamicFont" load_steps=2 format=1] + +[ext_resource path="res://addons/DataEditor/fonts/DroidSans.ttf" type="DynamicFontData" id=1] + +[resource] + +font/size = 22 +font/use_mipmaps = false +font/use_filter = false +font/font = ExtResource( 1 ) + diff --git a/fonts/droid_serif_bold.tres b/fonts/droid_serif_bold.tres new file mode 100644 index 0000000..05a5aa0 --- /dev/null +++ b/fonts/droid_serif_bold.tres @@ -0,0 +1,11 @@ +[gd_resource type="DynamicFont" load_steps=2 format=1] + +[ext_resource path="res://addons/DataEditor/fonts/DroidSerif-Bold.ttf" type="DynamicFontData" id=1] + +[resource] + +font/size = 16 +font/use_mipmaps = false +font/use_filter = false +font/font = ExtResource( 1 ) + diff --git a/fonts/droid_serif_bolditalic.tres b/fonts/droid_serif_bolditalic.tres new file mode 100644 index 0000000..b6017cb --- /dev/null +++ b/fonts/droid_serif_bolditalic.tres @@ -0,0 +1,11 @@ +[gd_resource type="DynamicFont" load_steps=2 format=1] + +[ext_resource path="res://addons/DataEditor/fonts/DroidSerif-Italic.ttf" type="DynamicFontData" id=1] + +[resource] + +font/size = 16 +font/use_mipmaps = false +font/use_filter = false +font/font = ExtResource( 1 ) + diff --git a/fonts/droid_serif_italic.tres b/fonts/droid_serif_italic.tres new file mode 100644 index 0000000..b6017cb --- /dev/null +++ b/fonts/droid_serif_italic.tres @@ -0,0 +1,11 @@ +[gd_resource type="DynamicFont" load_steps=2 format=1] + +[ext_resource path="res://addons/DataEditor/fonts/DroidSerif-Italic.ttf" type="DynamicFontData" id=1] + +[resource] + +font/size = 16 +font/use_mipmaps = false +font/use_filter = false +font/font = ExtResource( 1 ) + diff --git a/icons/empty.png b/icons/empty.png new file mode 100644 index 0000000..2504386 Binary files /dev/null and b/icons/empty.png differ diff --git a/icons/gom.png b/icons/gom.png new file mode 100644 index 0000000..ca3781a Binary files /dev/null and b/icons/gom.png differ diff --git a/icons/icon_add.png b/icons/icon_add.png new file mode 100644 index 0000000..297be14 Binary files /dev/null and b/icons/icon_add.png differ diff --git a/icons/icon_copy.png b/icons/icon_copy.png new file mode 100644 index 0000000..d777f13 Binary files /dev/null and b/icons/icon_copy.png differ diff --git a/icons/icon_display-name.png b/icons/icon_display-name.png new file mode 100644 index 0000000..7c44933 Binary files /dev/null and b/icons/icon_display-name.png differ diff --git a/icons/icon_duplicate.png b/icons/icon_duplicate.png new file mode 100644 index 0000000..320b365 Binary files /dev/null and b/icons/icon_duplicate.png differ diff --git a/icons/icon_edit.png b/icons/icon_edit.png new file mode 100644 index 0000000..c114d2f Binary files /dev/null and b/icons/icon_edit.png differ diff --git a/icons/icon_empty.png b/icons/icon_empty.png new file mode 100644 index 0000000..da163ba Binary files /dev/null and b/icons/icon_empty.png differ diff --git a/icons/icon_load.png b/icons/icon_load.png new file mode 100644 index 0000000..cc05e98 Binary files /dev/null and b/icons/icon_load.png differ diff --git a/icons/icon_move_down.png b/icons/icon_move_down.png new file mode 100644 index 0000000..3934310 Binary files /dev/null and b/icons/icon_move_down.png differ diff --git a/icons/icon_move_up.png b/icons/icon_move_up.png new file mode 100644 index 0000000..684013d Binary files /dev/null and b/icons/icon_move_up.png differ diff --git a/icons/icon_multi_line.png b/icons/icon_multi_line.png new file mode 100644 index 0000000..95a029c Binary files /dev/null and b/icons/icon_multi_line.png differ diff --git a/icons/icon_options.png b/icons/icon_options.png new file mode 100644 index 0000000..4db4c53 Binary files /dev/null and b/icons/icon_options.png differ diff --git a/icons/icon_reload_small.png b/icons/icon_reload_small.png new file mode 100644 index 0000000..1397ac6 Binary files /dev/null and b/icons/icon_reload_small.png differ diff --git a/icons/icon_remove.png b/icons/icon_remove.png new file mode 100644 index 0000000..289db46 Binary files /dev/null and b/icons/icon_remove.png differ diff --git a/icons/icon_rename.png b/icons/icon_rename.png new file mode 100644 index 0000000..2df503f Binary files /dev/null and b/icons/icon_rename.png differ diff --git a/icons/icon_save.png b/icons/icon_save.png new file mode 100644 index 0000000..8695b78 Binary files /dev/null and b/icons/icon_save.png differ diff --git a/icons/icon_script.png b/icons/icon_script.png new file mode 100644 index 0000000..5aa673f Binary files /dev/null and b/icons/icon_script.png differ diff --git a/input_dialog.gd b/input_dialog.gd new file mode 100644 index 0000000..29b5247 --- /dev/null +++ b/input_dialog.gd @@ -0,0 +1,57 @@ +tool +extends ConfirmationDialog + +#var text = "" +#var title = "" +var placeholder_1 = "" +var placeholder_2 = "" +var caller = null +var callback_method = "" + +onready var line_edit_1 = get_node("VBox/LineEdit1") +onready var line_edit_2 = get_node("VBox/LineEdit2") + + +#func _init(text, placerholder_1 = "", placeholder_2 = ""): + +func popup(caller, callback_method, title, text, placeholder_1 = "", default_text_1 = "", placeholder_2 = "", default_text_2 = ""): + self.caller = caller + self.callback_method = callback_method + self.placeholder_1 = placeholder_1 + self.placeholder_2 = placeholder_2 + + caller.connect("input_dialog_confirmed", caller, callback_method, []) + set_text(text) + set_title(title) + if placeholder_1 == "": + line_edit_1.hide() + else: + line_edit_1.show() + line_edit_1.set_placeholder(placeholder_1) + + + if placeholder_2 == "": + line_edit_2.hide() + else: + line_edit_2.show() + line_edit_2.set_placeholder(placeholder_2) + + line_edit_1.set_text(default_text_1) + line_edit_2.set_text(default_text_2) + self.popup_centered() + + if not line_edit_1.is_hidden(): + line_edit_1.grab_focus() + +func _on_ConfirmationDialog_confirmed(): + var text1 = line_edit_1.get_text().strip_edges() + var text2 = line_edit_2.get_text().strip_edges() + if placeholder_2: + caller.emit_signal("input_dialog_confirmed", text1, text2) + elif placeholder_1: + caller.emit_signal("input_dialog_confirmed", text1) + else: + caller.emit_signal("input_dialog_confirmed") + + + caller.disconnect("input_dialog_confirmed", caller, callback_method) \ No newline at end of file diff --git a/input_dialog.tscn b/input_dialog.tscn new file mode 100644 index 0000000..ee668fa --- /dev/null +++ b/input_dialog.tscn @@ -0,0 +1,71 @@ +[gd_scene load_steps=2 format=1] + +[ext_resource path="res://addons/DataEditor/input_dialog.gd" type="Script" id=1] + +[node name="ConfirmationDialog" type="ConfirmationDialog"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 440.0 +margin/bottom = 150.0 +popup/exclusive = true +window/title = "Please Confirm..." +dialog/text = "Text" +dialog/hide_on_ok = true +script/script = ExtResource( 1 ) + +[node name="VBox" type="VBoxContainer" parent="."] + +anchor/top = 1 +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 8.0 +margin/top = 103.0 +margin/right = 8.0 +margin/bottom = 45.0 +custom_constants/separation = 10 +alignment = 0 + +[node name="LineEdit1" type="LineEdit" parent="VBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 424.0 +margin/bottom = 24.0 +placeholder/text = "Hint 1" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="LineEdit2" type="LineEdit" parent="VBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 34.0 +margin/right = 424.0 +margin/bottom = 58.0 +placeholder/text = "Hint 2" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[connection signal="confirmed" from="." to="." method="_on_ConfirmationDialog_confirmed"] + + diff --git a/item_manager.gd b/item_manager.gd new file mode 100644 index 0000000..89bca57 --- /dev/null +++ b/item_manager.gd @@ -0,0 +1,580 @@ +extends Node + +# Holds a dictionary of dictionaries with all items (class and then items) +var items = {} + +var class_names = [] +var classes = {} + +var config_class_directory = "" +var config_output_directory = "" +var config_sanitize_ids = "" +var config_encrypt = "" +var config_password = "" +var config_extension = "" +var config_serializer = "" + +signal item_duplication_failed(title, reason) +signal item_insertion_failed(title, reason) +signal class_insertion_failed(title, reason) +signal custom_property_insertion_failed(title, reason) + +var property_blacklist = ["_dirty"] + +var default_type_values = { + str(TYPE_STRING): "", # OK for simple + str(TYPE_BOOL): false, # OK + str(TYPE_COLOR): Color(0,0,0), + str(TYPE_OBJECT): "res://", + str(TYPE_IMAGE): "res://", + str(TYPE_INT): 0, + str(TYPE_NODE_PATH): @"", + str(TYPE_REAL): 0.0, + str(TYPE_RECT2): Rect2(0,0,32,32), + str(TYPE_VECTOR2): Vector2(0,0), + str(TYPE_VECTOR3): Vector3(0,0,0), + str(TYPE_PLANE): Plane(0,0,0,0), + str(TYPE_QUAT): Quat(0,0,0,0), + str(TYPE_TRANSFORM): Transform(Vector3(0,0,0),Vector3(0,0,0),Vector3(0,0,0),Vector3(0,0,0)) + } + +var type_names = {"STRING":TYPE_STRING, "BOOL":TYPE_BOOL, "COLOR":TYPE_COLOR, "OBJECT":TYPE_OBJECT, "IMAGE":TYPE_IMAGE, "INT":TYPE_INT, "NODE_PATH":TYPE_NODE_PATH, "REAL":TYPE_REAL, "RECT2":TYPE_RECT2, "VECTOR2":TYPE_VECTOR2, "VECTOR3":TYPE_VECTOR3, "PLANE":TYPE_PLANE, "QUAT":TYPE_QUAT, "TRANSFORM":TYPE_TRANSFORM } + + +func _init(): + load_manager() + +func load_manager(): + initialize_variables() + load_config() + load_class_names() + load_classes() + set_up_item_folders() + load_items() + Globals.set("item_manager", self) + +func initialize_variables(): + items = {} + class_names = [] + classes = {} + + config_class_directory = "" + config_output_directory = "" + config_sanitize_ids = "" + config_encrypt = "" + config_password = "" + config_extension = "" + config_serializer = "" + +func load_config(): + var config = ConfigFile.new() + config.load("res://addons/DataEditor/plugin.cfg") + self.config_class_directory = config.get_value("custom", "class_directory") + self.config_output_directory = config.get_value("custom", "output_directory") + self.config_sanitize_ids = config.get_value("custom", "sanitize_ids") + self.config_encrypt = config.get_value("custom", "encrypt") + self.config_password = config.get_value("custom", "password") + self.config_serializer = config.get_value("custom", "serializer") + self.config_extension = config.get_value("custom", "extension") + + +func load_class_names(): + class_names.clear() + var directory = Directory.new() + if directory.open(config_class_directory) == OK: + directory.list_dir_begin() + var file_name = directory.get_next() + while (file_name != ""): + if file_name.extension() == "gd" and not directory.current_is_dir() and file_name != "data_item.gd" : + class_names.append(file_name.replace(".gd", "")) + file_name = directory.get_next() + class_names.sort() + + +func load_classes(): + classes = {} + for item_class in class_names: + classes[item_class] = load(config_class_directory + "/" + item_class + ".gd") + #classes[item_class].reload(true) + pass + + +# Creates the directories for the items if they do not yet exist +func set_up_item_folders(): + var directory = Directory.new() + for item_class in classes: + var path = config_output_directory + "/" + item_class + if not directory.dir_exists(path): + directory.make_dir_recursive(path) + +func get_item_path(item): + return config_output_directory + "/" + item._class + "/" + item._id + "." + config_extension + +func get_full_path(item): + return config_output_directory.replace("res://", "") + "/" + item._class + "/" + item._id + "." + config_extension + +func load_items(): + items.clear() + var directory = Directory.new() + for item_class in class_names: + items[item_class] = {} + directory.open(config_output_directory + "/" + item_class ) + directory.list_dir_begin() + var file_name = directory.get_next() + while (file_name != ""): + if file_name.extension() == config_extension and not directory.current_is_dir() : + var id = file_name.basename() + if config_serializer == "json": + items[item_class][id] = load_json_item(item_class, file_name) + elif config_serializer == "binary": + items[item_class][id] = load_binary_item(item_class, file_name) + else: + pass + file_name = directory.get_next() + pass + pass + +# +func load_binary_item(item_class, file_name): + var file = File.new() + var id = file_name.basename() + var status = 0 + if not config_encrypt: + file.open(config_output_directory + "/" + item_class + "/" + file_name, File.READ) + else: + file.open_encrypted_with_pass(config_output_directory + "/" + item_class + "/" + file_name, File.READ, config_password) + + var item = classes[item_class].new(id) + if status == OK: + # Load all the variables + while file.get_pos() < file.get_len(): + var property_name = str(file.get_var()) + + var value = file.get_var() + item.set(property_name, value) + pass + # And now iterate over the rest of the variables and check if they have not yet been initialized + + item._dirty = false + item._persistent = true + else: + pass # TODO: Handle + file.close() + return item + + +func load_json_item(item_class, file_name): + var file = File.new() + var id = file_name.basename() + var status = file.open(config_output_directory + "/" + item_class + "/" + file_name, File.READ) + var item = classes[item_class].new(id) + if status == OK: + var text = file.get_as_text() + var dict = {} + dict.parse_json(text) + + for property_name in dict: + + + if property_name == "_custom_properties": + var value = dict["_custom_properties"] + item._custom_properties = {} + for custom_property in value: + item._custom_properties[custom_property] = [] + var cp_value = value[custom_property][0] + var cp_type = value[custom_property][1] + cp_value = parse_value(cp_type, cp_value) + item._custom_properties[custom_property].append(cp_type) + item._custom_properties[custom_property].append(cp_value) + else: + var value = dict[property_name][0] + var type = dict[property_name][1] + value = parse_value(type, value) + item.set(property_name, value) + pass + item._dirty = false + item._persistent = true + else: + pass # TODO: Handle + file.close() + return item + +# Handles some special cases of JSON deserialization, e.g. Color +func parse_value(type, value): + if type == TYPE_COLOR: + value = Color(value) + elif type == TYPE_PLANE: + var split = value.replace("(", "").replace(")", "").split(",") + value = Plane(split[0], split[1], split[2], split[3]) + elif type == TYPE_QUAT: + var split = value.replace("(", "").replace(")", "").split(",") + value = Quat(split[0], split[1], split[2], split[3]) + elif type == TYPE_RECT2: + var split = value.replace("(", "").replace(")", "").split(",") + value = Rect2(split[0], split[1], split[2], split[3]) + elif type == TYPE_TRANSFORM: + var split = value.replace("(", "").replace(")", "").split(",") + value = Transform(Vector3(split[0], split[1], split[2]), Vector3(split[3], split[4], split[5]), Vector3(split[6], split[7], split[8]), Vector3(split[9], split[10], split[11])) + return value + + + + +func save_item(item): + if item: + item._last_modified= OS.get_unix_time() + if config_serializer == "json": + save_json_item(item) + elif config_serializer == "binary": + save_binary_item(item) + else: + pass + + +func save_binary_item(item): + var file = File.new() + var status = 0 + if not config_encrypt: + status = file.open(get_item_path(item), File.WRITE) + else: + status = file.open_encrypted_with_pass(get_item_path(item), File.WRITE, config_password) + if status == OK: + for property in item.get_property_list(): + # Serialize each property, even those starting with an underscore because they might be informative to external editors + var property_name = property["name"] + var property_usage = property["usage"] + if property_usage >= PROPERTY_USAGE_SCRIPT_VARIABLE: + file.store_var(property_name) + file.store_var(item.get(property_name)) + pass + item._persistent = true + item._dirty = false + else: + pass #TODO: Handle + file.close() + +func save_json_item(item): + var file = File.new() + var status = 0 + status = file.open(get_item_path(item), File.WRITE) + var dict = {} + if status == OK: + for property in item.get_property_list(): + # Serialize each property + var property_name = property["name"].json_escape() + var property_usage = property["usage"] + if property_usage >= PROPERTY_USAGE_SCRIPT_VARIABLE and not property_name in property_blacklist: + var type = typeof(item.get(property_name)) + var value = item.get(property_name) + + # Custom properties are handled separately since they are stored as arrays + if property_name == "_custom_properties": + dict["_custom_properties"] = {} + for custom_property in value: + var type = value[custom_property][0] + var sanitized_value = sanitize_variant(value[custom_property][1], type) + dict["_custom_properties"][custom_property] = [sanitized_value, type] + pass + # Normal properties are simply stored as type-value pairs in an array + else: + value = sanitize_variant(value, type) + dict[property_name] = [value, type] + pass + item._persistent = true + item._dirty = false + else: + #TODO: Handle + pass + file.store_string(dict.to_json()) + file.close() + +func sanitize_variant(value, type): + if type == TYPE_COLOR: + value = value.to_html() + elif type == TYPE_STRING: + value = value.json_escape() + return value + + + +func save_all_items(): + for item_class in items: + for id in items[item_class]: + save_item(items[item_class][id]) + pass + pass + + +func delete_item(item): + var path = get_item_path(item) + var directory = Directory.new() + # TODO: Check why items[item._class].erase(item) doesn't work + var items_of_class = items[item._class] + #items_of_class.erase(item.id) + #items[item._class] = items_of_class + var status = directory.remove(path) + load_manager() + + + +func get_item(item_class, id): + if items.has(item_class) and items[item_class].has(id): + return items[item_class][id] + else: + return null + +func get_items(item_class): + if items.has(item_class): + return items[item_class] + else: + return null + + +func create_and_add_new_item(item_class, id, display_name): + id = sanitize_string(id) + id = rename_id_if_exists(item_class, id) + if id == "" or id == null: + emit_signal("item_insertion_failed", "Item insertion failed", "The item must haven an ID.") + return null + if items[item_class].has(id): + emit_signal("item_insertion_failed", "Item insertion failed", "The item could not be created.") + return null + var new_item = classes[item_class].new(id) + if display_name: + new_item._display_name = display_name + items[item_class][id] = new_item + new_item._created = OS.get_unix_time() + return new_item + +func duplicate_item(item, id, display_name): + id = sanitize_string(id) + id = rename_id_if_exists(item._class, id) + if id == "" or id == null: + emit_signal("item_duplication_failed", "Item duplication failed", "The item must haven an ID.") + return null + if items[item._class].has(id): + emit_signal("item_duplication_failed", "Item duplication failed", "The item could not be duplicated.") + return null + + var new_item = classes[item._class].new(id) + # Copy all properties + + for property in new_item.get_property_list(): + if property["usage"] >= PROPERTY_USAGE_SCRIPT_VARIABLE: + new_item.set(property["name"], item.get(property["name"])) + new_item._id = id + if display_name: + new_item._display_name = display_name + else: + new_item._display_name = new_item._id + + new_item._dirty = true + new_item._persistent = false + items[new_item._class][new_item._id] = new_item + return new_item + +# Rename the item, delete the old entrym overwrite the id and save anew +# TODO: Consider rename, could it still be referenced/locked somewhere? +func rename_item(item, new_id): + new_id = sanitize_string(new_id) + if is_id_available(new_id): + var directory = Directory.new() + directory.remove(get_item_path(item)) + if item._id == item._display_name: + item._display_name = new_id + save_item(item) + load_manager() + else: + pass # TODO: Issue warning + + +func is_id_available(id): + return true + +# Adds a custom property to an item. +# Returns true if it succeeded, false if it failed +func add_custom_property(item, name, type): + name = sanitize_string(name.strip_edges()) + + if item.get(name): + emit_signal("custom_property_insertion_failed", "Custom Property Insertion Failed", "There already is a property with that name.") + return false + if item._custom_properties.has(name): + emit_signal("custom_property_insertion_failed", "Custom Property Insertion Failed", "There already is a custom property with that name.") + return false + elif name == '': + emit_signal("custom_property_insertion_failed", "Custom Property Insertion Failed", "The custom property name cannot be empty.") + return false + else: + item._custom_properties[str(name)] = [type, default_type_values[str(type)]] + item._dirty = true + return true + +func delete_custom_property(item, property_name): + item._custom_properties.erase(property_name) + +func delete_class(item_class): + # Delete items + var directory = Directory.new() + var path = config_output_directory + "/" + item_class + var status = directory.open(path) + if status == OK: + directory.list_dir_begin() + var file_name = directory.get_next() + while (file_name != ""): + if not directory.current_is_dir(): + directory.remove(path + "/" + file_name) + file_name = directory.get_next() + pass + directory.remove(path) + classes.erase(item_class) + class_names.erase(item_class) + items.erase(item_class) + + directory.remove(config_class_directory + "/" + item_class + ".gd") + directory.remove(config_class_directory + "/" + item_class + ".png") + +func create_class(name, icon_path): + # Check if the classes folder already exists. If not, create it- + var directory = Directory.new() + if not directory.dir_exists(config_class_directory): + directory.make_dir(config_class_directory) + + name = sanitize_string(name) + if name == "": + emit_signal("class_insertion_failed", tr("Invalid name"), tr("The class name cannot be empty.")) + return + elif class_names.has(name): + emit_signal("class_insertion_failed", tr("Invalid name"), tr("The class name already exists.")) + return + + # Handle icons + var icon_file = File.new() + if icon_path == "" or not icon_file.file_exists(icon_path): + icon_path = "res://addons/DataEditor/icons/icon_empty.png" + + var icon_resource = load(icon_path) + var icon_data = icon_resource.get_data() + if icon_data.get_width() <= 22 and icon_data.get_height() <= 22: + var directory = Directory.new() + var error = directory.copy(icon_path, config_class_directory + "/" + name + ".png") + if error != OK: + emit_signal("class_insertion_failed", tr("Could not copy icon"), tr("There was a problem while copying the icon. Was it already opened by another program?") + "\nError code: " + str(error)) + return + else: + emit_signal("class_insertion_failed", tr("Invalid icon size"), tr("Icon must be smaller than 22x22 pixels.")) + return + + # Create class + var class_source = "" + class_source += "extends \"res://addons/DataEditor/data_item.gd\"\n\n" + class_source += "export(String) var your_string_property = \"\"\n" + class_source += "export(bool) var your_boolean_property = true\n" + class_source += "export(Color) var your_color_property = Color(1,0,1)\n" + class_source += "\n\n\n" + class_source += "func _init(id).(id):\n" + class_source += "\tpass\n" + + var script_file = File.new() + var directory = Directory.new() + if not directory.dir_exists(config_class_directory): + directory.make_dir(config_class_directory) + + script_file.open(config_class_directory + "/" + name + ".gd", File.WRITE) + script_file.store_string(class_source) + script_file.close() + load_manager() + + +func sanitize_string(string): + if config_sanitize_ids: + return string.replace(" ", "_").replace("\\", "_").replace("/", "_").replace(":", "_").replace("*", "_").replace("?", "_").replace("\"", "_").replace("<", "_").replace(">", "_").replace("|", "_").to_lower() + else: + return string + +func rename_id_if_exists(item_class, id): + if not items[item_class].has(id): + return id + else: + var regex = RegEx.new() + regex.compile("(\\D*)(\\d*)") + var has_valid_name = false + var number = 0 + var current_name = id + while(true): + + regex.find(current_name) + var id_without_number = regex.get_capture(1) + var number_at_end_string = regex.get_capture(2) +# var id_without_number = regex.search(current_name).get_string(1) +# var number_at_end_string = regex.search(current_name).get_string(2) + var number_at_end = int(number_at_end_string) + number = number + number_at_end + 1 + var new_id = id_without_number + str(number) + if not items[item_class].has(new_id): + return new_id + + +func rename_class(item_class, new_item_class): + new_item_class = sanitize_string(new_item_class) + var directory = Directory.new() + if new_item_class == "": + emit_signal("class_insertion_failed", tr("Invalid name"), tr("The class name cannot be empty.")) + return + elif class_names.has(new_item_class): + emit_signal("class_insertion_failed", tr("Invalid name"), tr("The class name already exists.")) + return + + directory.rename(config_class_directory + item_class + ".gd", config_class_directory + new_item_class + ".gd") + directory.rename(config_class_directory + item_class + ".png", config_class_directory + new_item_class + ".png") + directory.rename(config_output_directory + "/" + item_class, config_output_directory + "/" + new_item_class) + load_manager() + + +func rename_extension_of_all_items(new_extension, serializer): + var directory = Directory.new() + for item_class in class_names: + for id in items[item_class]: + var item = items[item_class][id] + var original_item_path = get_item_path(item) + var new_item_path = original_item_path.replace("." + config_extension, "." + new_extension) + if serializer == config_serializer: + directory.rename(original_item_path, new_item_path) + directory.remove(original_item_path) + load_config() +# load_manager() + save_all_items() + else: + directory.remove(original_item_path) + load_config() + save_all_items() +# load_manager() + pass + + +func delete_and_resave(is_encrypted, password): + var directory = Directory.new() + for item_class in class_names: + for id in items[item_class]: + var item = items[item_class][id] + var item_path = get_item_path(item) + directory.remove(item_path) + pass + pass + load_config() + save_all_items() + +func has_unsaved_items(): + for item_class in items: + for id in items[item_class]: + var item = items[item_class][id] + if item._dirty: + return true + pass + pass + return false + +# TODO: Lazy loading +# TODO: Proper path handling +# TODO: Arrays +# TODO: Proper renaming \ No newline at end of file diff --git a/item_tree.gd b/item_tree.gd new file mode 100644 index 0000000..a01d542 --- /dev/null +++ b/item_tree.gd @@ -0,0 +1,285 @@ +# Item Tree +tool +extends Control + +var dummy_root = null + + +onready var tree = get_node("Panel/VBox/Tree") # The item tree +onready var class_context_menu = get_node("ClassContextMenu") # Context menu when right clicking on a class +onready var instance_context_menu = get_node("InstanceContextMenu") # Context menu when right clicking on an instance +onready var filter_control = get_node("Panel/VBox/Margin/HBoxContainer/Filter") + +var item_manager = null # Item Manager, used to load, modify and save items + +var tree_roots = {} # Holds a copy of all tree roots, i.e. classes, accessible by class name +var tree_elements = {} # Reference to all tree elements, accessible by ["_roots"][item_class] (for the roots) and [item_class][id] for the items + +signal on_delete_pressed # Emitted when item is deleted +signal on_rename_pressed # Emitted when item is renamed +signal on_duplicate_pressed # Emitted when item is duplicated +signal on_new_item_pressed # Emitted when new item is created +signal on_open # Emitted when item is opened in OS +signal on_item_selected(item, is_leaf) # Emitted when a tree item is selected, carries the item and wheter or not it is a leaf element + +var last_selected = null # Reference to last selected item +var last_selected_id = "" # Name/id of last selected item +var last_selected_class = "" # Name/id of last selected class + +var plugin_config + +var filter = "" + +# Load the tree when ready +func _ready(): +# test = EditorPlugin.new() + tree.set_hide_root(true) + tree.set_select_mode(Tree.SELECT_SINGLE) + tree.set_allow_rmb_select(true) + load_tree() + + +func load_tree(is_reload = false): + plugin_config = ConfigFile.new() + plugin_config.load("res://addons/DataEditor/plugin.cfg") + + + self.item_manager = Globals.get("item_manager") + tree_elements = {} + tree_roots = {} + #filter_control.set_text("") + #filter = "" + last_selected_id = "" + last_selected_class = "" + last_selected = null + var tree_element_to_be_selected = null + + + # Store the class and name of the last selected item, in case the tree is reloaded + last_selected = tree.get_selected() + if last_selected: + if last_selected.has_meta("item"): + if last_selected.get_meta("item").get("_id"): + self.last_selected_id = last_selected.get_meta("item")._id + else: + self.last_selected_id = null + self.last_selected_class = last_selected.get_meta("class") + #print(last_selected_id) + #print(last_selected_class) + + tree.clear() + + + # Create the roots for each item class, e.g. actors, monsters, crystals... + dummy_root = tree.create_item() + tree_elements["_roots"] = {} + var classes = item_manager.class_names + for item_class in classes: + var root = create_item_root(item_class) + tree_elements["_roots"][item_class] = root + pass + + # Populate the list with items + classes.sort() + for item_class in classes: + tree_elements[item_class] = {} + + var ids = item_manager.items[item_class].keys() + ids.sort() + for id in ids: + var item = item_manager.get_item(item_class, id) + if filter == "" or id.find(filter) != -1: + var tree_item = add_leaf(item, false) + pass + pass + + if last_selected_id: + tree_element_to_be_selected = get_tree_item(last_selected_class, last_selected_id) + elif last_selected_class: + tree_element_to_be_selected = get_tree_item(last_selected_class) + elif tree.get_root().get_children(): + tree_element_to_be_selected = tree.get_root().get_children() + else: + tree_element_to_be_selected = null # No elements to be selected + + last_selected + + # Handle filter + if tree_element_to_be_selected and not filter_control.has_focus(): + tree.grab_focus() + tree_element_to_be_selected.select(0) + +func create_item_root(item_class): + var tree_item = tree.create_item(dummy_root) + tree_item.set_selectable(0, true) + tree_item.set_icon(0, load(plugin_config.get_value("custom", "class_directory") + "/" + item_class + ".png")) + tree_item.set_text(0, item_class.capitalize()) + tree_item.set_meta("class", item_class) + tree_roots[item_class] = tree_item + return tree_item + +func select_first_element(): + var first_element = tree.get_root().get_children() + if first_element: + first_element.select(0) + first_element = first_element.get_children() + last_selected = first_element.get_meta("item") + last_selected_id = last_selected._id + last_selected_class = last_selected._class + return last_selected + + + +# Creates a new tree item, optionally with an existing item +func add_leaf(item, update_order): + var id = item._id + var item_class = item._class + var tree_item = tree.create_item(tree_roots[item_class]) + set_tree_item_label_text(item, tree_item) + tree_item.set_selectable(0, true) + tree_item.set_meta("class", item_class) + tree_item.set_meta("item", item) + + tree_elements[item_class][id] = tree_item + + # Don't order the tree in the beginning as the ids will already be sorted + if update_order: + # Move the items which come before in the alphabet to the top + var elements_name_array = tree_elements[item_class].keys() + elements_name_array.sort() + tree_item.move_to_top() + var to_be_reordered = [] + for element_name in elements_name_array: + if str(id) > str(element_name) or str(id) == str(element_name): + to_be_reordered.append(element_name) + else: + break + pass + to_be_reordered.invert() + for element_name in to_be_reordered: + tree_elements[item_class][element_name].move_to_top() + pass + tree_item.select(0) + return tree_item + + +func set_tree_item_label_text(item, tree_item = null): + if tree_item == null: + tree_item = get_tree_item(item._class, item._id) + + if item._dirty or not item._persistent: + tree_item.set_text(0, " " + item._display_name + " (*)") + tree_item.set_custom_color(0, Color(1, 0.5, 0.5)) + else: + tree_item.set_text(0, " " + item._display_name) + tree_item.set_custom_color(0, Color(0.7, 0.7, 0.7)) + +func get_selected_item_root(): + var selected = tree.get_selected() + if selected.has_meta("item"): + return selected.get_parent() + else: + return selected + + +# A tree element in the tree was selected (either class or item) +func _on_Tree_cell_selected(): + emit_signal("on_item_selected", get_selected_item(), get_selected_class()) + +func get_selected_item(): + var selected = tree.get_selected() + if selected.has_meta("item"): + return selected.get_meta("item") + else: + return null + + +func get_selected_class(): + return tree.get_selected().get_meta("class") + + +func get_tree_item(item_class, id = ""): + if id != "": + if tree_elements.has(item_class) and tree_elements[item_class].has(id): + return tree_elements[item_class][id] + else: + return null + else: + if tree_elements["_roots"].has(item_class): + return tree_elements["_roots"][item_class] + else: + return null + +# Sets the selection to a specific tree item +func select_item(item): + if tree_elements.has(item._class) and tree_elements[item._class].has(item._id): + tree_elements[item._class][item._id].select(0) + +# Sets the selection to a specific class +func select_class(item_class): + if tree_elements["_roots"].has(item_class): + tree_elements["_roots"][item_class].select(0) + + +func _on_Filter_text_changed( text ): + self.filter = text + load_tree() + + + +# Right click context menu on leafs +# 0 = Add | 1 = Rename | 2 = Delete | 3 = Duplicate | 4 = Open +func _on_InstanceContextMenu_item_pressed(index): + if index == 0: + emit_signal("on_new_item_pressed") + elif index == 1: + emit_signal("on_rename_pressed") + elif index == 2: + emit_signal("on_delete_pressed") + elif index == 3: + emit_signal("on_duplicate_pressed") + elif index == 4: + emit_signal("on_open") + +########################################################### +# CLASS CONTEXT MENU # +########################################################### +func _on_ClassContextMenu_about_to_show(): + class_context_menu.set_item_text(0, tr("Add") + " " + get_selected_class().capitalize()) + +func _on_InstanceContextMenu_about_to_show(): + pass + +# Right click context menu on class branch +# 0 = Add | 1 = Delete Class +func _on_ClassContextMenu_item_pressed(index): + if index == 0: + emit_signal("on_new_item_pressed") + elif index == 1: + emit_signal("on_delete_pressed") + elif index == 2: + emit_signal("on_rename_pressed") + + + +func _on_Tree_item_rmb_selected(pos): + var is_leaf = get_selected_item() != null + if is_leaf: + instance_context_menu.set_pos(get_global_mouse_pos()) + instance_context_menu.popup() + else: + class_context_menu.set_pos(get_global_mouse_pos()) + class_context_menu.popup() + + +func _on_DeleteItemDialog_confirmed(): + var selected_item = get_selected_item() + if selected_item != null: + item_manager.delete_item(selected_item) + load_tree() + + + + + +#TODO: Proper memory management? diff --git a/item_tree.tscn b/item_tree.tscn new file mode 100644 index 0000000..e5a83cd --- /dev/null +++ b/item_tree.tscn @@ -0,0 +1,196 @@ +[gd_scene load_steps=8 format=1] + +[ext_resource path="res://addons/DataEditor/item_tree.gd" type="Script" id=1] +[ext_resource path="res://addons/DataEditor/icons/icon_add.png" type="Texture" id=2] +[ext_resource path="res://addons/DataEditor/icons/icon_remove.png" type="Texture" id=3] +[ext_resource path="res://addons/DataEditor/icons/icon_rename.png" type="Texture" id=4] +[ext_resource path="res://addons/DataEditor/icons/icon_duplicate.png" type="Texture" id=5] +[ext_resource path="res://addons/DataEditor/icons/icon_load.png" type="Texture" id=6] + +[sub_resource type="StyleBoxFlat" id=1] + +content_margin/left = -1.0 +content_margin/right = -1.0 +content_margin/top = -1.0 +content_margin/bottom = -1.0 +bg_color = Color( 0.172549, 0.164706, 0.196078, 1 ) +light_color = Color( 0.113725, 0.113725, 0.12549, 1 ) +dark_color = Color( 0.113725, 0.113725, 0.12549, 1 ) +border_size = 2 +border_blend = true +draw_bg = true + +[node name="Tree" type="Control"] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 0.0 +margin/bottom = 0.0 +script/script = ExtResource( 1 ) + +[node name="Panel" type="PanelContainer" parent="."] + +anchor/right = 1 +anchor/bottom = 1 +rect/min_size = Vector2( 150, 0 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 50.0 +margin/right = 0.0 +margin/bottom = 0.0 + +[node name="VBox" type="VBoxContainer" parent="Panel"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 7.0 +margin/top = 7.0 +margin/right = 1273.0 +margin/bottom = 543.0 +alignment = 0 + +[node name="Margin" type="MarginContainer" parent="Panel/VBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1266.0 +margin/bottom = 24.0 + +[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBox/Margin"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 8.0 +margin/top = 0.0 +margin/right = 1266.0 +margin/bottom = 24.0 +alignment = 0 + +[node name="AddButton" type="ToolButton" parent="Panel/VBox/Margin/HBoxContainer"] + +visibility/visible = false +rect/min_size = Vector2( 28, 28 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 28.0 +margin/bottom = 28.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +icon = ExtResource( 2 ) +flat = true + +[node name="AddButton2" type="ToolButton" parent="Panel/VBox/Margin/HBoxContainer"] + +visibility/visible = false +rect/min_size = Vector2( 28, 28 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 32.0 +margin/top = 0.0 +margin/right = 60.0 +margin/bottom = 28.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +icon = ExtResource( 3 ) +flat = true + +[node name="Filter" type="LineEdit" parent="Panel/VBox/Margin/HBoxContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1258.0 +margin/bottom = 24.0 +placeholder/text = "Search" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="Tree" type="Tree" parent="Panel/VBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 28.0 +margin/right = 1266.0 +margin/bottom = 536.0 +custom_styles/bg = SubResource( 1 ) +custom_colors/guide_color = Color( 0.398438, 0.398438, 0.398438, 1 ) +custom_constants/hseparation = 4 + +[node name="ClassContextMenu" type="PopupMenu" parent="."] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 20.0 +margin/bottom = 20.0 +popup/exclusive = false +items = [ "Add Item", ExtResource( 2 ), false, false, false, 0, 0, null, "", false, "Delete Class", ExtResource( 3 ), false, false, false, 1, 0, null, "", false, "Rename Class", ExtResource( 4 ), false, false, false, -1, 0, null, "", false ] + +[node name="InstanceContextMenu" type="PopupMenu" parent="."] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 20.0 +margin/bottom = 20.0 +popup/exclusive = false +items = [ "Add", ExtResource( 2 ), false, false, false, 0, 0, null, "", false, "Rename", ExtResource( 4 ), false, false, false, 1, 0, null, "", false, "Delete", ExtResource( 3 ), false, false, false, 2, 0, null, "", false, "Duplicate", ExtResource( 5 ), false, false, false, 3, 0, null, "", false, "Open File", ExtResource( 6 ), false, false, true, 4, 0, null, "", false ] + +[connection signal="button_down" from="Panel/VBox/Margin/HBoxContainer/AddButton" to="." method="_on_AddButton_button_down"] + +[connection signal="text_changed" from="Panel/VBox/Margin/HBoxContainer/Filter" to="." method="_on_Filter_text_changed"] + +[connection signal="cell_selected" from="Panel/VBox/Tree" to="." method="_on_Tree_cell_selected"] + +[connection signal="item_rmb_selected" from="Panel/VBox/Tree" to="." method="_on_Tree_item_rmb_selected"] + +[connection signal="about_to_show" from="ClassContextMenu" to="." method="_on_ClassContextMenu_about_to_show"] + +[connection signal="item_pressed" from="ClassContextMenu" to="." method="_on_ClassContextMenu_item_pressed"] + +[connection signal="about_to_show" from="InstanceContextMenu" to="." method="_on_InstanceContextMenu_about_to_show"] + +[connection signal="item_pressed" from="InstanceContextMenu" to="." method="_on_InstanceContextMenu_item_pressed"] + + diff --git a/no_classes.tscn b/no_classes.tscn new file mode 100644 index 0000000..2583d50 --- /dev/null +++ b/no_classes.tscn @@ -0,0 +1,115 @@ +[gd_scene load_steps=3 format=1] + +[ext_resource path="res://addons/DataEditor/style/light_gray_panel.tres" type="StyleBox" id=1] +[ext_resource path="res://addons/DataEditor/fonts/droid_serif_bold.tres" type="DynamicFont" id=2] + +[node name="Control" type="Panel"] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 2.0 +margin/top = -1.0 +margin/right = -2.0 +margin/bottom = 1.0 +custom_styles/panel = ExtResource( 1 ) + +[node name="Body" type="VBoxContainer" parent="."] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 10.0 +margin/top = 10.0 +margin/right = 10.0 +margin/bottom = 10.0 +alignment = 0 + +[node name="TitleLabel" type="Label" parent="Body"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 1260.0 +margin/bottom = 2.0 +custom_fonts/font = ExtResource( 2 ) +text = "No Classes" +uppercase = true +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="HSeparator" type="HSeparator" parent="Body"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 6.0 +margin/right = 1260.0 +margin/bottom = 9.0 + +[node name="Spacer" type="Control" parent="Body"] + +rect/min_size = Vector2( 0, 15 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 13.0 +margin/right = 1260.0 +margin/bottom = 28.0 + +[node name="Scroll" type="ScrollContainer" parent="Body"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 3 +margin/left = 0.0 +margin/top = 32.0 +margin/right = 1260.0 +margin/bottom = 580.0 +scroll/horizontal = true +scroll/vertical = true + +[node name="ClassProperties" type="VBoxContainer" parent="Body/Scroll"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 699.0 +margin/bottom = 31.0 +alignment = 0 + +[node name="NoClassPropertiesLabel" type="Label" parent="Body/Scroll/ClassProperties"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 699.0 +margin/bottom = 31.0 +text = "Currently, there are no classes. Click on \"Add Class\" to add a new class and make the necessary adjustments. +Once you feel ready, press \"Refresh\" and start adding instances/items." +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + + diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..e60a1f9 --- /dev/null +++ b/notes.txt @@ -0,0 +1,21 @@ +Add "Notes" to each element +Add Display Name for each +Add Refresh +Check how to deal with reloading the tree +Add Sub categories +Translate +Add "state" handling +Add UndoRedo + +Move short callbacks from self to the object and call directly (e.g. popup) + +Use EditorFileSystem class to check for changes (external ones) + +print_progress + + +Handle when switching from encrypted to non-encrypted and vice versa + +# Progress? +get_status(story_item) +set_status(story_item, value) \ No newline at end of file diff --git a/options.gd b/options.gd new file mode 100644 index 0000000..31df0ad --- /dev/null +++ b/options.gd @@ -0,0 +1,190 @@ +tool +extends AcceptDialog + +var config = null + +onready var serializer_label = get_node("Panel/GridContainer/SerializerLabel") +onready var serializer_option = get_node("Panel/GridContainer/SerializerOption") +onready var extension_label = get_node("Panel/GridContainer/ExtensionLabel") +onready var extension_line_edit = get_node("Panel/GridContainer/ExtensionLineEdit") +onready var encrypt_label = get_node("Panel/GridContainer/EncryptLabel") +onready var encrypt_check_box = get_node("Panel/GridContainer/EncryptCheckBox") +onready var password_label = get_node("Panel/GridContainer/PasswordLabel") +onready var password_line_edit = get_node("Panel/GridContainer/PasswordLineEdit") +onready var output_directory_label = get_node("Panel/GridContainer/OutputDirectoryLabel") +onready var output_directory_line_edit = get_node("Panel/GridContainer/OutputDirectoryHBox/OutputDirectoryLineEdit") +onready var class_directory_label = get_node("Panel/GridContainer/OutputDirectoryLabel") +onready var class_directory_line_edit = get_node("Panel/GridContainer/ClassDirectoryHBox/ClassDirectoryLineEdit") +onready var sanitize_ids_label = get_node("Panel/GridContainer/SanitizeIdsLabel") +onready var sanitize_ids_check_box = get_node("Panel/GridContainer/SanitizeIdsCheckBox") + +onready var warn_dialog = get_node("WarnDialog") + +var extension = "" +var serializer = "" +var encrypt = false +var password = "" +var class_directory = "" +var output_directory = "" +var sanitize_ids = true + +signal extension_changed(new_extension, serializer) +signal encryption_changed(is_encrypted, password) + +func _ready(): + self.set_title(tr("Options")) + self.add_cancel(tr("Cancel")) # TODO: Does this keep on adding cancels? + serializer_label.set_text(tr("Serializer")) + extension_label.set_text(tr("File Extension")) + encrypt_label.set_text(tr("Encrypt Files")) + class_directory_label.set_text(tr("Class Directory")) + output_directory_label.set_text(tr("Output Directory")) + sanitize_ids_label.set_text(tr("Sanitize IDs")) + + config = ConfigFile.new() + config.load("res://addons/DataEditor/plugin.cfg") + serializer = config.get_value("custom", "serializer") + extension = config.get_value("custom", "extension") + + class_directory = config.get_value("custom", "class_directory") + sanitize_ids = config.get_value("custom", "sanitize_ids") + encrypt = config.get_value("custom", "encrypt") + password = config.get_value("custom", "password") + output_directory = config.get_value("custom", "output_directory") + sanitize_ids = config.get_value("custom", "sanitize_ids") + + serializer_option.clear() + serializer_option.add_item("json", 0) + serializer_option.add_item("binary", 1) + if serializer == "json": + serializer_option.select(0) + elif serializer == "binary": + serializer_option.select(1) + else: + serializer_option.select(0) + serializer = "json" + + extension_line_edit.set_text(extension) + + if serializer == "binary": + encrypt_check_box.set_disabled(false) + password_line_edit.set_editable(true) + else: + encrypt = false + password = "" + encrypt_check_box.set_disabled(true) + password_line_edit.set_editable(false) + + encrypt_check_box.set_pressed(encrypt) + encrypt_check_box.set_text(str(encrypt)) + + password_line_edit.set_text(str(password)) + + class_directory_line_edit.set_text(str(class_directory)) + output_directory_line_edit.set_text(str(output_directory)) + + sanitize_ids_check_box.set_pressed(sanitize_ids) + sanitize_ids_check_box.set_text(str(sanitize_ids)) + +func _on_SerializerOption_item_selected(index): + if index == 0: + extension_line_edit.set_text("json") + encrypt = false + password = "" + encrypt_check_box.set_disabled(true) + password_line_edit.set_editable(false) + if index == 1: + extension_line_edit.set_text("gob") + encrypt_check_box.set_disabled(false) + password_line_edit.set_editable(true) + + + +func _on_Options_confirmed(): + extract_values() + extension = extension.strip_edges() + if extension.begins_with("."): + extension = extension.replace(".", "") + + # TODO: Validate + var error_message = "" +# if self.serializer != "binary" or self.serializer != "json": +# error_message = tr("Please choose either 'json' or 'binary' as serializer.\n") + if self.extension == "": + error_message = tr("Please choose a valid file extension, e.g. 'gob' or 'json'.") + if self.class_directory == "" or not self.class_directory.begins_with("res://"): + error_message = tr("The class directory must be a resource path, e.g. 'res://classes'.") + if self.output_directory == "" or not self.output_directory.begins_with("res://"): + error_message = tr("The output directory must be a resource path, e.g. 'res://data'.") + + var extension_changed = false + var encryption_changed = false + if extension != config.get_value("custom", "extension") or serializer != config.get_value("custom", "serializer"): + extension_changed = true + + if encrypt != config.get_value("custom", "encrypt") or password != config.get_value("custom", "password"): + encryption_changed = true + + if error_message == "": + config.set_value("custom", "extension", extension) + config.set_value("custom", "serializer", serializer) + config.set_value("custom", "encrypt", encrypt) + config.set_value("custom", "password", password) + config.set_value("custom", "class_directory", output_directory) + config.set_value("custom", "output_directory", output_directory) + config.set_value("custom", "sanitize_ids", sanitize_ids) + config.save("res://addons/DataEditor/plugin.cfg") + hide() + else: + warn_dialog.set_text(error_message) + warn_dialog.popup_centered() + + if extension_changed: + emit_signal("extension_changed", extension, serializer) + + if encryption_changed: + emit_signal("encryption_changed", encrypt, password) +# TODO: Add a tip to NOT FORGET THE PASSWORD + +func extract_values(): + serializer = serializer_option.get_item_text(serializer_option.get_selected()) + extension = extension_line_edit.get_text() + encrypt = encrypt_check_box.is_pressed() + password = password_line_edit.get_text() + output_directory = output_directory_line_edit.get_text() + sanitize_ids = sanitize_ids_check_box.is_pressed() + +func _on_ClassDirectoryButton_button_down(): + var dialog = EditorFileDialog.new() + dialog.set_mode(EditorFileDialog.MODE_OPEN_DIR) + dialog.connect("dir_selected", self, "set_class_directory", []) + if not self.find_node("EditorFileDialog"): + add_child(dialog) + else: + get_node("EditorFileDialog").popup_centered() + dialog.popup_centered_ratio() + + +func _on_OutputDirectoryButton_button_down(): + var dialog = EditorFileDialog.new() + dialog.set_mode(EditorFileDialog.MODE_OPEN_DIR) + dialog.connect("dir_selected", self, "set_output_directory", []) + if not self.find_node("EditorFileDialog"): + add_child(dialog) + else: + get_node("EditorFileDialog").popup_centered() + dialog.popup_centered_ratio() + +func set_class_directory(selected_directory): + class_directory = selected_directory + class_directory_line_edit.set_text(selected_directory) + +func set_output_directory(selected_directory): + output_directory = selected_directory + output_directory_line_edit.set_text(selected_directory) + +func _on_EncryptCheckBox_button_down(): + encrypt_check_box.set_text(str(!encrypt_check_box.is_pressed())) + +func _on_SanitizeIdsCheckBox_button_down(): + sanitize_ids_check_box.set_text(str(!sanitize_ids_check_box.is_pressed())) diff --git a/options.tscn b/options.tscn new file mode 100644 index 0000000..10856a1 --- /dev/null +++ b/options.tscn @@ -0,0 +1,359 @@ +[gd_scene load_steps=4 format=1] + +[ext_resource path="res://addons/DataEditor/options.gd" type="Script" id=1] +[ext_resource path="res://addons/DataEditor/style/light_gray_panel.tres" type="StyleBox" id=2] +[ext_resource path="res://addons/DataEditor/icons/icon_load.png" type="Texture" id=3] + +[node name="Options" type="AcceptDialog"] + +rect/min_size = Vector2( 400, 300 ) +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 400.0 +margin/bottom = 300.0 +popup/exclusive = true +window/title = "Options" +dialog/hide_on_ok = false +script/script = ExtResource( 1 ) + +[node name="Panel" type="Panel" parent="."] + +anchor/right = 1 +anchor/bottom = 1 +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 8.0 +margin/top = 8.0 +margin/right = 8.0 +margin/bottom = 40.0 +custom_styles/panel = ExtResource( 2 ) + +[node name="GridContainer" type="GridContainer" parent="Panel"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 7.0 +margin/top = 7.0 +margin/right = 377.0 +margin/bottom = 255.0 +custom_constants/vseparation = 10 +custom_constants/hseparation = 5 +columns = 2 + +[node name="SerializerLabel" type="Label" parent="Panel/GridContainer"] + +rect/min_size = Vector2( 150, 0 ) +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 3.0 +margin/right = 150.0 +margin/bottom = 17.0 +text = "Serializer" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="SerializerOption" type="OptionButton" parent="Panel/GridContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 155.0 +margin/top = 0.0 +margin/right = 370.0 +margin/bottom = 20.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "json" +flat = false +align = 0 +selected = 0 +items = [ "json", null, false, 0, null, "binary", null, false, 1, null ] + +[node name="ExtensionLabel" type="Label" parent="Panel/GridContainer"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 35.0 +margin/right = 150.0 +margin/bottom = 49.0 +text = "File Extension" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="ExtensionLineEdit" type="LineEdit" parent="Panel/GridContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 155.0 +margin/top = 30.0 +margin/right = 370.0 +margin/bottom = 54.0 +text = "json" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="EncryptLabel" type="Label" parent="Panel/GridContainer"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 68.0 +margin/right = 150.0 +margin/bottom = 82.0 +text = "Encrypt Files" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="EncryptCheckBox" type="CheckBox" parent="Panel/GridContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 155.0 +margin/top = 64.0 +margin/right = 370.0 +margin/bottom = 87.0 +disabled = true +toggle_mode = true +enabled_focus_mode = 2 +shortcut = null +text = "False" +flat = false +align = 0 + +[node name="PasswordLabel" type="Label" parent="Panel/GridContainer"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 102.0 +margin/right = 150.0 +margin/bottom = 116.0 +text = "Password" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="PasswordLineEdit" type="LineEdit" parent="Panel/GridContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 155.0 +margin/top = 97.0 +margin/right = 370.0 +margin/bottom = 121.0 +placeholder/alpha = 0.6 +editable = false +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="ClassDirectoryLabel" type="Label" parent="Panel/GridContainer"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 136.0 +margin/right = 150.0 +margin/bottom = 150.0 +text = "Class Directory" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="ClassDirectoryHBox" type="HBoxContainer" parent="Panel/GridContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 155.0 +margin/top = 131.0 +margin/right = 370.0 +margin/bottom = 155.0 +alignment = 0 + +[node name="ClassDirectoryLineEdit" type="LineEdit" parent="Panel/GridContainer/ClassDirectoryHBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 183.0 +margin/bottom = 24.0 +text = "res://classes" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="ClassDirectoryButton" type="Button" parent="Panel/GridContainer/ClassDirectoryHBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 187.0 +margin/top = 0.0 +margin/right = 215.0 +margin/bottom = 24.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +icon = ExtResource( 3 ) +flat = false + +[node name="OutputDirectoryLabel" type="Label" parent="Panel/GridContainer"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 170.0 +margin/right = 150.0 +margin/bottom = 184.0 +text = "Output Directory" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="OutputDirectoryHBox" type="HBoxContainer" parent="Panel/GridContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 155.0 +margin/top = 165.0 +margin/right = 370.0 +margin/bottom = 189.0 +alignment = 0 + +[node name="OutputDirectoryLineEdit" type="LineEdit" parent="Panel/GridContainer/OutputDirectoryHBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 183.0 +margin/bottom = 24.0 +text = "res://data" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="OutputDirectoryButton" type="Button" parent="Panel/GridContainer/OutputDirectoryHBox"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 187.0 +margin/top = 0.0 +margin/right = 215.0 +margin/bottom = 24.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +icon = ExtResource( 3 ) +flat = false + +[node name="SanitizeIdsLabel" type="Label" parent="Panel/GridContainer"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 0.0 +margin/top = 203.0 +margin/right = 150.0 +margin/bottom = 217.0 +text = "Sanitize IDs" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="SanitizeIdsCheckBox" type="CheckBox" parent="Panel/GridContainer"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 155.0 +margin/top = 199.0 +margin/right = 370.0 +margin/bottom = 222.0 +toggle_mode = true +is_pressed = true +enabled_focus_mode = 2 +shortcut = null +text = "True" +flat = false +align = 0 + +[node name="WarnDialog" type="AcceptDialog" parent="."] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 260.0 +margin/bottom = 110.0 +popup/exclusive = false +window/title = "Alert!" +dialog/hide_on_ok = true + +[connection signal="confirmed" from="." to="." method="_on_Options_confirmed"] + +[connection signal="item_selected" from="Panel/GridContainer/SerializerOption" to="." method="_on_SerializerOption_item_selected"] + +[connection signal="button_down" from="Panel/GridContainer/EncryptCheckBox" to="." method="_on_EncryptCheckBox_button_down"] + +[connection signal="button_down" from="Panel/GridContainer/ClassDirectoryHBox/ClassDirectoryButton" to="." method="_on_ClassDirectoryButton_button_down"] + +[connection signal="button_down" from="Panel/GridContainer/OutputDirectoryHBox/OutputDirectoryButton" to="." method="_on_OutputDirectoryButton_button_down"] + +[connection signal="button_down" from="Panel/GridContainer/SanitizeIdsCheckBox" to="." method="_on_SanitizeIdsCheckBox_button_down"] + + diff --git a/plugin.cfg b/plugin.cfg new file mode 100644 index 0000000..5140e1a --- /dev/null +++ b/plugin.cfg @@ -0,0 +1,17 @@ +[custom] + +class_directory="res://classes" +encrypt=false +extension="json" +output_directory="res://data" +password="" +sanitize_ids=true +serializer="json" + +[plugin] + +author="Matthias Stoeckli" +description="An editor to write and use a data from Godot" +name="DataEditor" +script="data_editor.gd" +version="0.1" diff --git a/property_item.gd b/property_item.gd new file mode 100644 index 0000000..f375a7f --- /dev/null +++ b/property_item.gd @@ -0,0 +1,479 @@ +tool +extends Panel +# This class represents an item control used to change the data values, e.g. the text boxes. + + +var load_icon = preload("icons/icon_load.png") +var multi_line_icon = preload("icons/icon_multi_line.png") + +var property_name = "" +var type = TYPE_NIL +var hint = 0 +var hint_array = [] +var hint_text = "" +var number_of_hints = 0 +var value = null # The value of the property + +var has_delete_button = false + + +var control = null +var popup = null +var menu = null +var value_editor = [] + + +var dialog = null +var object_type_line_edit = null + + +signal on_property_value_changed(property, value) +signal property_item_load_button_down(property_item) +signal custom_property_delete_requested(property_name) + +# has_delete is used for custom properties +func initialize(property_name, type, value = null, hint = 0, hint_text = "", has_delete = false): + self.property_name = property_name + self.type = type + self.value = value + self.hint = hint + self.hint_text = hint_text + self.has_delete_button = has_delete + + + +func _ready(): + # Label describing property + var property_label = Label.new() + property_label.set_text(property_name.capitalize()) + + # Split property hints + self.hint_array = hint_text.split(",") + self.number_of_hints = hint_array.size() + + ################################################## + # For each type, one control is defined + ################################################## + if type == TYPE_BOOL: + create_bool() + elif type == TYPE_INT or type == TYPE_REAL: + create_number() + elif type == TYPE_STRING: + create_string() + elif type == TYPE_COLOR: + create_color() + elif type == TYPE_NODE_PATH: + create_node_path() + elif type == TYPE_VECTOR2: + control = create_custom_editor_button(value); + create_custom_editor(2, 2, 10, ["x", "y"]) + elif type == TYPE_VECTOR3: + control = create_custom_editor_button(value); + create_custom_editor(3, 3, 10, ["x", "y", "z"]) + elif type == TYPE_RECT2: + control = create_custom_editor_button(value); + create_custom_editor(4, 4, 10, ["x", "y", "w", "h"]) + elif type == TYPE_PLANE: + control = create_custom_editor_button(value); + create_custom_editor(4, 4, 10, ["x", "y", "z", "d"]) + elif type == TYPE_QUAT: + control = create_custom_editor_button(value); + create_custom_editor(4, 4, 10, ["x", "y", "z", "w"]) + elif type == TYPE_TRANSFORM: + control = create_custom_editor_button(value); + create_custom_editor(12, 4, 16, ["xx", "xy", "xz", "xo", "yx", "yy", "yz", "yo", "zx", "zy", "zz", "zo"]) + elif type == TYPE_OBJECT or type == TYPE_IMAGE: + create_object_or_image() + else: + control = get_not_yet_supported() + + control.set_h_size_flags(Control.SIZE_EXPAND_FILL) + control.set_margin(MARGIN_LEFT, 200) + control.set_custom_minimum_size(Vector2(get_parent().get_parent().get_parent().get_size().x - 270, 0)) + add_child(property_label) + add_child(control) + + if has_delete_button: + var delete_button = ToolButton.new() + delete_button.set_button_icon(preload("res://addons/DataEditor/icons/icon_remove.png")) + delete_button.set_h_size_flags(Control.SIZE_EXPAND) + delete_button.set_margin(MARGIN_LEFT, get_parent().get_parent().get_parent().get_size().x - 34 ) + delete_button.set_custom_minimum_size(Vector2(28, 24)) + delete_button.connect("button_down", self, "route_delete_request") +# delete_button.connect("button_down", self, "emit_signal", ["custom_property_delete_requested") + add_child(delete_button) + +func route_delete_request(): + emit_signal("custom_property_delete_requested") + +func create_custom_editor_button(value): + var button = Button.new() + button.set_text(str(value)) + button.connect("button_down", self, "open_custom_editor") + return button + +func open_custom_editor(): + menu.set_pos(get_global_mouse_pos()) + menu.get_children()[1].grab_focus() + menu.popup() + + +################################################## +# All types +################################################## + +func create_bool(): + control = CheckBox.new() + control.set_text(str(value)) + control.set_pressed(value) + control.connect("toggled", self, "set_checkbox_label", []) + control.connect("toggled", self, "property_value_changed", []) + +func create_number(): + if hint == PROPERTY_HINT_RANGE: + var control_min = -16777216 + var control_max = 16777216 + + var control_step = 0 + if type == TYPE_INT: + control_step = 1 + else: + control_step = 0.00001 + + if number_of_hints >= 1: + if not hint_array[0].empty(): + control_min = int(hint_array[0]) + + if number_of_hints >= 2: + if not hint_array[1].empty(): + control_max = int(hint_array[1]) + + if number_of_hints >= 3: + if not hint_array[2].empty(): + control_step = float(hint_array[2]) + + # TODO: This does not seem to be exposed in GDScript yet? + if number_of_hints >= 4 and hint_array[3] == "slider": + control = HSlider.new() + control.set_min(control_min) + control.set_max(control_max) + control.set_step(control_step) + control.set_value(value); + control.connect("value_changed", self, "property_value_changed", []) + #controlset_size(Size2(110,30)*EDSCALE); + else: + control = SpinBox.new() + control.set_min(control_min) + control.set_max(control_max) + control.set_step(control_step) + control.set_value(value) + control.connect("value_changed", self, "property_value_changed", []) + #set_size(Size2(70,35)*EDSCALE); + elif hint == PROPERTY_HINT_ENUM: + control = MenuButton.new() + for i in range(0, hint_array.size()): + control.get_popup().add_item(hint_array[i]) + control.set_flat(false) +# control.set_pos(get_pos()) + control.set_text(control.get_popup().get_item_text(value)) + control.get_popup().connect("item_pressed", self, "int_enum_property_value_changed", []) + + elif hint == PROPERTY_HINT_EXP_EASING: + control = get_not_yet_supported() + elif hint == PROPERTY_HINT_FLAGS: + control = get_not_yet_supported() + else: + control = SpinBox.new() + control.set_value(value); + if type == TYPE_REAL: + control.set_min(-16777216) + control.set_max(16777216) + control.set_step(0.00001) + else: + control.set_max(2147483647) + control.set_min(-2147483647) + control.set_step(1) + control.connect("value_changed", self, "property_value_changed", []) + #control = create_custom_editor_button(value); + #create_custom_editor(1, 1, 50, ["value"]) + #custom_editor_value_applied() + +func create_string(): + if hint == PROPERTY_HINT_ENUM: + control = MenuButton.new() + for i in range(0, hint_array.size()): + control.get_popup().add_item(hint_array[i]) + control.set_flat(false) + control.set_text(value) + control.get_popup().connect("item_pressed", self, "string_enum_property_value_changed", []) + elif hint == PROPERTY_HINT_MULTILINE_TEXT: + # RABRABRAB + control = HBoxContainer.new() + var line_edit = LineEdit.new() + line_edit.set_h_size_flags(SIZE_EXPAND_FILL) + var more_button = ToolButton.new() + more_button.set_button_icon(multi_line_icon) + line_edit.set_text(str(value)) + control.add_child(line_edit) + control.add_child(more_button) + + popup = Popup.new() + popup.set_size(Vector2(600, 400)) + var text_edit = TextEdit.new() + text_edit.set_anchor_and_margin(MARGIN_LEFT, ANCHOR_BEGIN, 0) + text_edit.set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 0) + text_edit.set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, 0) + text_edit.set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, 0) + add_child(popup) + popup.add_child(text_edit) + text_edit.set_text(str(value)) + + line_edit.connect("text_changed", self, "property_value_changed", []) + more_button.connect("button_down", popup, "popup_centered_minsize", [Vector2(800, 600)]) + popup.connect("popup_hide", self, "text_edit_popup_closed", []) + else: + control = LineEdit.new() + control.set_text(str(value)) + control.connect("text_changed", self, "property_value_changed", []) + +func create_color(): + control = ColorPickerButton.new() + # If, for some reason, the color is still (de)serialized wrongly, split the string + if typeof(value) == TYPE_STRING and value.find(","): + var split_color = value.split(",") + value = Color(split_color[0], split_color[1], split_color[2], split_color[3]) + control.set_color(value) + control.connect("color_changed", self, "property_value_changed", []) + +func create_node_path(): + control = LineEdit.new() + control.set_text(value) + control.connect("text_changed", self, "property_value_changed", []) + + +# Adapted Port of property editor +func create_custom_editor(amount, columns, label_w, strings, read_only = false): + self.value_editor = [] + self.menu = PopupMenu.new() + menu.connect("popup_hide", self, "custom_editor_value_applied") + var w = 80 + var h = 20 + var m = 10 + var MAX_VALUE_EDITORS = 12 + var value_label = [] + for i in range(0, amount): + var line_edit = LineEdit.new() + line_edit.set_text(str(get_custom_editor_value(i))) + value_editor.append(line_edit) + menu.add_child(value_editor[i]) + value_label.append(Label.new()) + menu.add_child(value_label[i]) + pass + + var rows=((amount-1)/columns)+1 + menu.set_size(Vector2( m*(1+columns)+(w+label_w)*columns, m*(1+rows)+h*rows ) ); + for i in range(0, amount): + var c = i % columns; + var r = i / columns; + value_editor[i].show() + value_label[i].show() + if i < strings.size(): + value_label[i].set_text(strings[i]) + else: + value_label[i].set_text("") + + value_editor[i].set_pos( Vector2( m+label_w+c*(w+m+label_w), m+r*(h+m) )) + value_editor[i].set_size( Vector2( w, h ) ) + value_label[i].set_pos( Vector2( m+c*(w+m+label_w), m+r*(h+m) ) ) + value_editor[i].set_editable(!read_only) + + pass + + add_child(menu) + + +func custom_editor_value_applied(): + # TODO: Validate + var va = [] + for line in value_editor: + var v = float(line.get_text()) + va.append(v) + + var value = null + if type == TYPE_VECTOR2: + value = Vector2(va[0], va[1]) + if type == TYPE_VECTOR3: + value = Vector3(va[0], va[1], va[2]) + if type == TYPE_RECT2: + value = Rect2(va[0], va[1], va[2], va[3]) + if type == TYPE_PLANE: + value = Plane(va[0], va[1], va[2], va[3]) + if type == TYPE_QUAT: + value = Quat(va[0], va[1], va[2], va[3]) + if type == TYPE_TRANSFORM: + value = Transform(Vector3(va[0], va[1], va[2]), Vector3(va[4], va[5], va[6]), Vector3(va[8], va[9], va[10]), Vector3(va[3], va[7], va[11])) + + if value != self.value: + self.value = value + emit_signal("on_property_value_changed", property_name, value) + control.set_text(str(value)) + +func get_custom_editor_value(index): + if type == TYPE_VECTOR2: + if index == 0: return value.x + else: return value.y + elif type == TYPE_VECTOR3: + if index == 0: return value.x + elif index == 1: return value.y + else: return value.z + elif type == TYPE_RECT2: + if index == 0: return value.pos.x + elif index == 1: return value.pos.y + elif index == 2: return value.size.x + else: return value.size.y + elif type == TYPE_QUAT: + if index == 0: return value.x + elif index == 1: return value.y + elif index == 2: return value.z + else: return value.w + elif type == TYPE_PLANE: + if index == 0: return value.x + elif index == 1: return value.y + elif index == 2: return value.z + else: return value.d + elif type == TYPE_TRANSFORM: + if index == 0: return value.basis.x.x + elif index == 1: return value.basis.x.y + elif index == 2: return value.basis.x.z + elif index == 3: return value.origin.x + elif index == 4: return value.basis.y.x + elif index == 5: return value.basis.y.y + elif index == 6: return value.basis.y.z + elif index == 7: return value.origin.y + elif index == 8: return value.basis.z.x + elif index == 9: return value.basis.z.y + elif index == 10: return value.basis.z.z + else: return value.origin.z + +func create_object_or_image(): + control = HBoxContainer.new() + object_type_line_edit = LineEdit.new() + object_type_line_edit.set_text(str(value)) + object_type_line_edit.set_h_size_flags(SIZE_EXPAND_FILL) + object_type_line_edit.connect("text_changed", self, "property_value_changed", []) + if hint_text == "Texture" or type == TYPE_IMAGE: + var f = File.new() + if value != null and f.file_exists(value): + var texture = load(value) + var texture_frame = TextureFrame.new() + texture_frame.set_expand(true) + texture_frame.set_custom_minimum_size(Vector2(get_parent_area_size().y, get_parent_area_size().y)) + texture_frame.set_texture(texture) + var texture_popup = Popup.new() + var texture_frame_full = TextureFrame.new() + texture_frame_full.set_texture(texture) + texture_popup.add_child(texture_frame_full) + texture_popup.set_size(texture.get_size()) +# texture_frame.set_process_input(true) +# texture_frame.connect("input_event", self, "open_image", []) + control.add_child(texture_frame) +# control.add_child(texture_popup) + + control.add_child(object_type_line_edit) + + + var load_button = ToolButton.new() + load_button.set_button_icon(load_icon) + control.add_child(load_button) + + + if Globals.get("debug_is_editor"): + dialog = EditorFileDialog.new() + dialog.set_access(EditorFileDialog.ACCESS_RESOURCES) + dialog.set_mode(EditorFileDialog.MODE_OPEN_FILE) + load_button.connect("button_down", dialog, "popup_centered_ratio") + + var filter = "" + var resource_type = "" + var extension_array = [] + + if hint == PROPERTY_HINT_RESOURCE_TYPE: + resource_type = hint_text + extension_array = ResourceLoader.get_recognized_extensions_for_type(resource_type) + else: + extension_array = hint_array + + for extension in extension_array: +# if filter.begins_with("."): +# filter = "*" + extension +# elif filter.begins_with("*"): +# filter = "*." + extension +# filter = filter + " ; " + extension.to_upper() + extension.replace("*", "").replace(".", "") + filter = "*." + extension + " ; " + extension.to_upper() + dialog.add_filter(filter) + pass + dialog.connect("file_selected", self, "fill_resource_name", []) + add_child(dialog) + + + #.add_icon_item(get_icon("Load","EditorIcons"), "Load") + + + +func get_not_yet_supported(): + var control = Label.new() + control.set_text(str("This type is not yet supported.")) + return control + +# TODO: That's all a bit too confusing... +func fill_resource_name(resource_path): + object_type_line_edit.set_text(resource_path) + property_value_changed(resource_path) + if hint_text == "Texture": + var texture = load(resource_path) + if texture: + control.get_child(0).set_texture(texture) + +# Sets the label of the checkboxe's text to the value +func set_checkbox_label(value): + control.set_text(str(value)) + +func property_value_changed(value): + if type == TYPE_INT: + value = int(value) + if type == TYPE_REAL: + value = float(value) + if type == TYPE_COLOR: + value = Color(value) + if self.value != value: + self.value = value + emit_signal("on_property_value_changed", property_name, value) + + +func text_edit_popup_closed(): + var text_edit = popup.get_child(0) + var value = text_edit.get_text() + if value and self.value != value: + self.value = value + emit_signal("on_property_value_changed", property_name, value) + control.get_child(0).set_text(value) + + + +func int_enum_property_value_changed(value): + control.set_text(control.get_popup().get_item_text(value)) + if self.value != value: + self.value = value + emit_signal("on_property_value_changed", property_name, value) + +# Simply changes the text of the calling menu to the selected value if it's an enum +func string_enum_property_value_changed(value): + control.set_text(control.get_popup().get_item_text(value)) + if str(self.value) != str(value): + self.value = value + emit_signal("on_property_value_changed", property_name, control.get_popup().get_item_text(value)) + + +func open_image(texture): + pass \ No newline at end of file diff --git a/property_item.tscn b/property_item.tscn new file mode 100644 index 0000000..607ecef --- /dev/null +++ b/property_item.tscn @@ -0,0 +1,55 @@ +[gd_scene load_steps=3 format=1] + +[ext_resource path="res://addons/DataEditor/property_item.gd" type="Script" id=1] +[ext_resource path="res://addons/DataEditor/icons/icon_remove.png" type="Texture" id=2] + +[node name="Panel" type="Panel"] + +anchor/right = 1 +anchor/bottom = 1 +rect/min_size = Vector2( 0, 26 ) +focus/ignore_mouse = false +focus/stop_mouse = false +size_flags/horizontal = 3 +size_flags/vertical = 2 +margin/left = 1.0 +margin/top = 0.0 +margin/right = -1.0 +margin/bottom = 574.0 +script/script = ExtResource( 1 ) + +[node name="DeleteButton" type="ToolButton" parent="."] + +visibility/visible = false +anchor/left = 1 +anchor/top = 3 +anchor/right = 1 +anchor/bottom = 3 +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 1280.0 +margin/top = 13.0 +margin/right = 1252.0 +margin/bottom = -11.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +icon = ExtResource( 2 ) +flat = true + +[node name="PopupMenu" type="PopupMenu" parent="."] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 20.0 +margin/bottom = 20.0 +popup/exclusive = false +items = [ ] + + diff --git a/style/light_gray_panel.tres b/style/light_gray_panel.tres new file mode 100644 index 0000000..8d41d13 --- /dev/null +++ b/style/light_gray_panel.tres @@ -0,0 +1,15 @@ +[gd_resource type="StyleBoxFlat" format=1] + +[resource] + +content_margin/left = -1.0 +content_margin/right = -1.0 +content_margin/top = -1.0 +content_margin/bottom = -1.0 +bg_color = Color( 0.172549, 0.164706, 0.196078, 1 ) +light_color = Color( 0.8, 0.8, 0.8, 1 ) +dark_color = Color( 0.8, 0.8, 0.8, 1 ) +border_size = 0 +border_blend = true +draw_bg = true + diff --git a/warn.gd b/warn.gd new file mode 100644 index 0000000..c27eab7 --- /dev/null +++ b/warn.gd @@ -0,0 +1,9 @@ +tool +extends AcceptDialog + +func warn(title, text): + set_title(title) + set_text(text) + popup_centered() + + # Probably this should be placed at get_base_control()