@tool extends Control ## Node that manages editors, the toolbar and the sidebar. signal resource_opened(resource) signal editor_changed(previous, current) ### References @onready var hsplit = $HSplit @onready var sidebar = $HSplit/Sidebar @onready var editors_holder = $HSplit/VBox/Editors @onready var toolbar = $HSplit/VBox/Toolbar @onready var tabbar = $HSplit/VBox/Toolbar/EditorTabBar var reference_manager: Node: get: return get_node("../../ReferenceManager") ## Information on supported resource extensions and registered editors var current_editor: DialogicEditor = null var previous_editor: DialogicEditor = null var editors := {} var supported_file_extensions := [] var used_resources_cache : Array = [] ################################################################################ ## REGISTERING EDITORS ################################################################################ ## Asks all childs of the editor holder to register func _ready() -> void: if owner.get_parent() is SubViewport: return tabbar.clear_tabs() # Load base editors _add_editor("res://addons/dialogic/Editor/HomePage/home_page.tscn") _add_editor("res://addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn") _add_editor("res://addons/dialogic/Editor/CharacterEditor/character_editor.tscn") # Load custom editors for indexer in DialogicUtil.get_indexers(): for editor_path in indexer._get_editors(): _add_editor(editor_path) _add_editor("res://addons/dialogic/Editor/Settings/settings_editor.tscn") tabbar.tab_clicked.connect(_on_editors_tab_changed) # Needs to be done here to make sure this node is ready when doing the register calls for editor in editors_holder.get_children(): editor.editors_manager = self editor._register() DialogicResourceUtil.update() await get_parent().get_parent().ready await get_tree().process_frame load_saved_state() used_resources_cache = DialogicUtil.get_editor_setting('last_resources', []) sidebar.update_resource_list(used_resources_cache) find_parent('EditorView').plugin_reference.get_editor_interface().get_file_system_dock().files_moved.connect(_on_file_moved) find_parent('EditorView').plugin_reference.get_editor_interface().get_file_system_dock().file_removed.connect(_on_file_removed) hsplit.set("theme_override_constants/separation", get_theme_constant("base_margin", "Editor") * DialogicUtil.get_editor_scale()) func _add_editor(path:String) -> void: var editor: DialogicEditor = load(path).instantiate() editors_holder.add_child(editor) editor.hide() tabbar.add_tab(editor._get_title(), editor._get_icon()) ## Call to register an editor/tab that edits a resource with a custom ending. func register_resource_editor(resource_extension:String, editor:DialogicEditor) -> void: editors[editor.name] = {'node':editor, 'buttons':[], 'extension': resource_extension} supported_file_extensions.append(resource_extension) editor.resource_saved.connect(_on_resource_saved.bind(editor)) editor.resource_unsaved.connect(_on_resource_unsaved.bind(editor)) ## Call to register an editor/tab that doesn't edit a resource func register_simple_editor(editor:DialogicEditor) -> void: editors[editor.name] = {'node': editor, 'buttons':[]} ## Call to add an icon button. These buttons are always visible. func add_icon_button(icon:Texture, tooltip:String, editor:DialogicEditor=null) -> Node: var button: Button = toolbar.add_icon_button(icon, tooltip) if editor != null: editors[editor.name]['buttons'].append(button) return button ## Call to add a custom action button. Only visible if editor is visible. func add_custom_button(label:String, icon:Texture, editor:DialogicEditor) -> Node: var button: Button = toolbar.add_custom_button(label, icon) editors[editor.name]['buttons'].append(button) return button func can_edit_resource(resource:Resource) -> bool: return resource.resource_path.get_extension() in supported_file_extensions ################################################################################ ## OPENING/CLOSING ################################################################################ func _on_editors_tab_changed(tab:int) -> void: open_editor(editors_holder.get_child(tab)) func edit_resource(resource:Resource, save_previous:bool = true, silent:= false) -> void: if not resource: # The resource doesn't exists, show an error print('[Dialogic] The resource you are trying to edit doesn\'t exists any more.') return if current_editor and save_previous: current_editor._save() if !resource.resource_path in used_resources_cache: used_resources_cache.append(resource.resource_path) sidebar.update_resource_list(used_resources_cache) ## Open the correct editor var extension: String = resource.resource_path.get_extension() for editor in editors.values(): if editor.get('extension', '') == extension: editor['node']._open_resource(resource) if !silent: open_editor(editor['node'], false) if !silent: resource_opened.emit(resource) ## Only works if there was a different editor opened previously func toggle_editor(editor) -> void: if editor.visible: open_editor(previous_editor, true) else: open_editor(editor, true) ## Shows the given editor func open_editor(editor:DialogicEditor, save_previous: bool = true, extra_info:Variant = null) -> void: if current_editor and save_previous: current_editor._save() if current_editor: current_editor._close() current_editor.hide() if current_editor != previous_editor: previous_editor = current_editor editor._open(extra_info) editor.opened.emit() current_editor = editor editor.show() tabbar.current_tab = editor.get_index() if editor.current_resource: var text:String = editor.current_resource.resource_path.get_file() if editor.current_resource_state == DialogicEditor.ResourceStates.UNSAVED: text += "(*)" ## This makes custom button editor-specific ## I think it's better without. save_current_state() editor_changed.emit(previous_editor, current_editor) ## Rarely used to completely clear an editor. func clear_editor(editor:DialogicEditor, save:bool = false) -> void: if save: editor._save() editor._clear() ## Shows a file selector. Calls [accept_callable] once accepted func show_add_resource_dialog(accept_callable:Callable, filter:String = "*", title = "New resource", default_name = "new_character", mode = EditorFileDialog.FILE_MODE_SAVE_FILE) -> void: find_parent('EditorView').godot_file_dialog( _on_add_resource_dialog_accepted.bind(accept_callable), filter, mode, title, default_name, true, "Do not use \"'()!;:/\\*# in character or timeline names!" ) func _on_add_resource_dialog_accepted(path:String, callable:Callable) -> void: var file_name :String= path.get_file().trim_suffix('.'+path.get_extension()) for i in ['#','&','+',';','(',')','!','*','*','"',"'",'%', '$', ':','.',',']: file_name = file_name.replace(i, '') callable.call(path.trim_suffix(path.get_file()).path_join(file_name)+'.'+path.get_extension()) ## Called by the plugin.gd script on CTRL+S or Debug Game start func save_current_resource() -> void: current_editor._save() ## Change the resource state func _on_resource_saved(editor:DialogicEditor): sidebar.set_unsaved_indicator(true) ## Change the resource state func _on_resource_unsaved(editor:DialogicEditor): sidebar.set_unsaved_indicator(false) ## Tries opening the last resource func load_saved_state() -> void: var current_resources: Dictionary = DialogicUtil.get_editor_setting('current_resources', {}) for editor in current_resources.keys(): editors[editor]['node']._open_resource(load(current_resources[editor])) var current_editor: String = DialogicUtil.get_editor_setting('current_editor', 'HomePage') open_editor(editors[current_editor]['node']) func save_current_state() -> void: DialogicUtil.set_editor_setting('current_editor', current_editor.name) var current_resources: Dictionary = {} for editor in editors.values(): if editor['node'].current_resource != null: current_resources[editor['node'].name] = editor['node'].current_resource.resource_path DialogicUtil.set_editor_setting('current_resources', current_resources) func _on_file_moved(old_name:String, new_name:String) -> void: if !old_name.get_extension() in supported_file_extensions: return used_resources_cache = DialogicUtil.get_editor_setting('last_resources', []) if old_name in used_resources_cache: used_resources_cache.insert(used_resources_cache.find(old_name), new_name) used_resources_cache.erase(old_name) sidebar.update_resource_list(used_resources_cache) for editor in editors: if editors[editor].node.current_resource != null and editors[editor].node.current_resource.resource_path == old_name: editors[editor].node.current_resource.take_over_path(new_name) edit_resource(load(new_name), true, true) save_current_state() func _on_file_removed(file_name:String) -> void: var current_resources: Dictionary = DialogicUtil.get_editor_setting('current_resources', {}) for editor_name in current_resources: if current_resources[editor_name] == file_name: clear_editor(editors[editor_name].node, false) sidebar.update_resource_list() save_current_state() ################################################################################ ## HELPERS ################################################################################ func get_current_editor() -> DialogicEditor: return current_editor func _exit_tree(): DialogicUtil.set_editor_setting('last_resources', used_resources_cache)