Initial commit to new repo

This commit is contained in:
Matthias Stöckli 2017-08-03 11:35:44 +02:00
parent 7c48575ac3
commit c9434c9992
51 changed files with 4555 additions and 0 deletions

6
class_overview.gd Normal file
View File

@ -0,0 +1,6 @@
extends PanelContainer
func _ready():
pass

143
class_overview.tscn Normal file
View File

@ -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

50
class_properties.gd Normal file
View File

@ -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)

116
class_properties.tscn Normal file
View File

@ -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

77
custom_properties.gd Normal file
View File

@ -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")

134
custom_properties.tscn Normal file
View File

@ -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"]

110
data.gd Normal file
View File

@ -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):

97
data_editor.gd Normal file
View File

@ -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()

431
data_editor_gui.gd Normal file
View File

@ -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)

840
data_editor_gui.tscn Normal file
View File

@ -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"]

58
data_item.gd Normal file
View File

@ -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)

BIN
fonts/DroidSans.ttf Normal file

Binary file not shown.

BIN
fonts/DroidSerif-Bold.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
fonts/DroidSerif-Italic.ttf Normal file

Binary file not shown.

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

BIN
icons/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

BIN
icons/gom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/icon_add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

BIN
icons/icon_copy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

BIN
icons/icon_display-name.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

BIN
icons/icon_duplicate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

BIN
icons/icon_edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

BIN
icons/icon_empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

BIN
icons/icon_load.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

BIN
icons/icon_move_down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

BIN
icons/icon_move_up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

BIN
icons/icon_multi_line.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

BIN
icons/icon_options.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

BIN
icons/icon_reload_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

BIN
icons/icon_remove.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

BIN
icons/icon_rename.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

BIN
icons/icon_save.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

BIN
icons/icon_script.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

57
input_dialog.gd Normal file
View File

@ -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)

71
input_dialog.tscn Normal file
View File

@ -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"]

580
item_manager.gd Normal file
View File

@ -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

285
item_tree.gd Normal file
View File

@ -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?

196
item_tree.tscn Normal file
View File

@ -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"]

115
no_classes.tscn Normal file
View File

@ -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

21
notes.txt Normal file
View File

@ -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)

190
options.gd Normal file
View File

@ -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()))

359
options.tscn Normal file
View File

@ -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"]

17
plugin.cfg Normal file
View File

@ -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"

479
property_item.gd Normal file
View File

@ -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

55
property_item.tscn Normal file
View File

@ -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 = [ ]

View File

@ -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

9
warn.gd Normal file
View File

@ -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()