2 class_name DialogicSidebar extends Control
4 ## Script that handles the editor sidebar.
6 signal content_item_activated(item_name)
7 signal show_sidebar(show: bool)
10 @onready var editors_manager = get_parent().get_parent()
11 @onready var resource_tree: Tree = %ResourceTree
13 var current_resource_list: Array = []
21 var group_mode: GroupMode = GroupMode.TYPE
24 func _ready() -> void:
25 if owner != null and owner.get_parent() is SubViewport:
27 if editors_manager is SubViewportContainer:
31 editors_manager.resource_opened.connect(_on_editors_resource_opened)
32 editors_manager.editor_changed.connect(_on_editors_editor_changed)
34 resource_tree.item_activated.connect(_on_resources_tree_item_activated)
35 resource_tree.item_mouse_selected.connect(_on_resources_tree_item_clicked)
36 resource_tree.item_collapsed.connect(_on_resources_tree_item_collapsed)
38 %ContentList.item_selected.connect(
39 func(idx: int): content_item_activated.emit(%ContentList.get_item_text(idx))
42 %OpenButton.pressed.connect(_show_sidebar)
43 %CloseButton.pressed.connect(_hide_sidebar)
45 var editor_scale := DialogicUtil.get_editor_scale()
48 %Logo.texture = load("res://addons/dialogic/Editor/Images/dialogic-logo.svg")
49 %Logo.custom_minimum_size.y = 30 * editor_scale
50 %Search.right_icon = get_theme_icon("Search", "EditorIcons")
51 %Options.icon = get_theme_icon("GuiTabMenuHl", "EditorIcons")
52 %OptionsPanel.add_theme_stylebox_override("panel", get_theme_stylebox("PanelForeground", "EditorStyles"))
55 %ContentList.add_theme_color_override(
56 "font_hovered_color", get_theme_color("warning_color", "Editor")
58 %ContentList.add_theme_color_override(
59 "font_selected_color", get_theme_color("property_color_z", "Editor")
63 %RightClickMenu.clear()
64 %RightClickMenu.add_icon_item(get_theme_icon("Remove", "EditorIcons"), "Remove From List", 1)
65 %RightClickMenu.add_separator()
66 %RightClickMenu.add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), "Copy Identifier", 4)
67 %RightClickMenu.add_separator()
68 %RightClickMenu.add_icon_item(
69 get_theme_icon("Filesystem", "EditorIcons"), "Show in FileSystem", 2
71 %RightClickMenu.add_icon_item(
72 get_theme_icon("ExternalLink", "EditorIcons"), "Open in External Program", 3
76 %GroupingOptions.set_item_icon(0, get_theme_icon("AnimationTrackGroup", "EditorIcons"))
77 %GroupingOptions.set_item_icon(1, get_theme_icon("Folder", "EditorIcons"))
78 %GroupingOptions.set_item_icon(2, get_theme_icon("FolderBrowse", "EditorIcons"))
79 %GroupingOptions.set_item_icon(3, get_theme_icon("AnimationTrackList", "EditorIcons"))
80 %GroupingOptions.item_selected.connect(_on_grouping_changed)
82 await get_tree().process_frame
83 if DialogicUtil.get_editor_setting("sidebar_collapsed", false):
86 %MainVSplit.split_offset = DialogicUtil.get_editor_setting("sidebar_v_split", 0)
87 group_mode = DialogicUtil.get_editor_setting("sidebar_group_mode", 0)
88 %GroupingOptions.select(%GroupingOptions.get_item_index(group_mode))
90 %FolderColors.button_pressed = DialogicUtil.get_editor_setting("sidebar_use_folder_colors", true)
91 %TrimFolderPaths.button_pressed = DialogicUtil.get_editor_setting("sidebar_trim_folder_paths", true)
93 update_resource_list()
96 func set_unsaved_indicator(saved: bool = true) -> void:
97 if saved and %CurrentResource.text.ends_with("(*)"):
98 %CurrentResource.text = %CurrentResource.text.trim_suffix("(*)")
99 if not saved and not %CurrentResource.text.ends_with("(*)"):
100 %CurrentResource.text = %CurrentResource.text + "(*)"
103 func _on_logo_gui_input(event: InputEvent) -> void:
104 if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
105 editors_manager.open_editor(editors_manager.editors["HomePage"].node)
108 #region SHOW/HIDE SIDEBAR
109 ################################################################################
111 func _show_sidebar() -> void:
114 DialogicUtil.set_editor_setting("sidebar_collapsed", false)
115 show_sidebar.emit(true)
118 func _hide_sidebar() -> void:
121 DialogicUtil.set_editor_setting("sidebar_collapsed", true)
122 show_sidebar.emit(false)
127 ################################################################################
129 ################################################################################
132 func _on_editors_resource_opened(_resource: Resource) -> void:
133 update_resource_list()
136 func _on_editors_editor_changed(_previous: DialogicEditor, current: DialogicEditor) -> void:
137 %ContentListSection.visible = current.current_resource is DialogicTimeline
138 update_resource_list()
141 ## Cleans resources that have been deleted from the resource list
142 func clean_resource_list(resources_list: Array = []) -> PackedStringArray:
143 return PackedStringArray(resources_list.filter(func(x): return ResourceLoader.exists(x)))
146 #region BULDING/FILTERING THE RESOURCE LIST
148 func update_resource_list(resources_list: PackedStringArray = []) -> void:
149 var filter: String = %Search.text
150 var current_file := ""
151 if editors_manager.current_editor and editors_manager.current_editor.current_resource:
152 current_file = editors_manager.current_editor.current_resource.resource_path
154 var character_directory: Dictionary = DialogicResourceUtil.get_character_directory()
155 var timeline_directory: Dictionary = DialogicResourceUtil.get_timeline_directory()
156 if resources_list.is_empty():
157 resources_list = DialogicUtil.get_editor_setting("last_resources", [])
158 if not current_file in resources_list:
159 resources_list.append(current_file)
161 resources_list = clean_resource_list(resources_list)
163 %CurrentResource.text = "No Resource"
164 %CurrentResource.add_theme_color_override(
165 "font_uneditable_color", get_theme_color("disabled_font_color", "Editor")
168 resource_tree.clear()
170 var character_items: Array = get_directory_items.call(character_directory, filter, load("res://addons/dialogic/Editor/Images/Resources/character.svg"), resources_list)
171 var timeline_items: Array = get_directory_items.call(timeline_directory, filter, get_theme_icon("TripleBar", "EditorIcons"), resources_list)
172 var all_items := character_items + timeline_items
175 var root: TreeItem = resource_tree.create_item()
179 all_items.sort_custom(_sort_by_item_text)
180 for item in all_items:
181 add_item(item, root, current_file)
185 character_items.sort_custom(_sort_by_item_text)
186 timeline_items.sort_custom(_sort_by_item_text)
187 if character_items.size() > 0:
188 var character_tree := add_folder_item("Characters", root)
189 for item in character_items:
190 add_item(item, character_tree, current_file)
192 if timeline_items.size() > 0:
193 var timeline_tree := add_folder_item("Timelines", root)
194 for item in timeline_items:
195 add_item(item, timeline_tree, current_file)
200 for item in all_items:
201 var dir := item.get_parent_directory() as String
202 if not dirs.has(dir):
204 dirs[dir].append(item)
207 var dir_item := add_folder_item(dir, root)
209 for item in dirs[dir]:
210 add_item(item, dir_item, current_file)
214 # Collect all different directories that contain resources
216 for item in all_items:
217 var path := (item.metadata.get_base_dir() as String).trim_prefix("res://")
218 if not dirs.has(path):
220 dirs[path].append(item)
222 # Sort them into ones with the same folder name
225 var sliced: String = dir.get_slice("/", dir.get_slice_count("/")-1)
226 if not sliced in dir_names:
227 dir_names[sliced] = {"folders":[dir]}
229 dir_names[sliced].folders.append(dir)
231 # Create a dictionary mapping a unique name to each directory
232 # If two have been found to have the same folder name, the parent directory is added
233 var unique_folder_names := {}
234 for dir_name in dir_names:
235 if dir_names[dir_name].folders.size() > 1:
236 for i in dir_names[dir_name].folders:
238 unique_folder_names[i.get_slice("/", i.get_slice_count("/")-2)+"/"+i.get_slice("/", i.get_slice_count("/")-1)] = i
240 unique_folder_names[i] = i
242 unique_folder_names[dir_name] = dir_names[dir_name].folders[0]
244 # Sort the folder names by their folder name (not by the full path)
245 var sorted_dir_keys := unique_folder_names.keys()
246 sorted_dir_keys.sort_custom(
248 return x.get_slice("/", x.get_slice_count("/")-1) < y.get_slice("/", y.get_slice_count("/")-1)
250 var folder_colors: Dictionary = ProjectSettings.get_setting("file_customization/folder_colors", {})
252 for dir in sorted_dir_keys:
253 var display_name: String = dir
254 if not %TrimFolderPaths.button_pressed:
255 display_name = unique_folder_names[dir]
256 var dir_path: String = unique_folder_names[dir]
257 var dir_color_path := ""
258 var dir_color := Color.BLACK
259 if %FolderColors.button_pressed:
260 for path in folder_colors:
261 if String("res://"+dir_path+"/").begins_with(path) and len(path) > len(dir_color_path):
262 dir_color_path = path
263 dir_color = folder_colors[path]
265 var dir_item := add_folder_item(display_name, root, dir_color, dir_path)
267 for item in dirs[dir_path]:
268 add_item(item, dir_item, current_file)
271 if %CurrentResource.text != "No Resource":
272 %CurrentResource.add_theme_color_override(
273 "font_uneditable_color", get_theme_color("font_color", "Editor")
276 DialogicUtil.set_editor_setting("last_resources", resources_list)
279 func add_item(item:ResourceListItem, parent:TreeItem, current_file := "") -> TreeItem:
280 var tree_item := resource_tree.create_item(parent)
281 tree_item.set_text(0, item.text)
282 tree_item.set_icon(0, item.icon)
283 tree_item.set_metadata(0, item.metadata)
284 tree_item.set_tooltip_text(0, item.tooltip)
286 if item.metadata == current_file:
287 %CurrentResource.text = item.metadata.get_file()
288 resource_tree.set_selected(tree_item, 0)
290 var bg_color := parent.get_custom_bg_color(0)
291 if bg_color != get_theme_color("base_color", "Editor"):
293 tree_item.set_custom_bg_color(0, bg_color)
298 func add_folder_item(label: String, parent:TreeItem, color:= Color.BLACK, tooltip:="") -> TreeItem:
299 var folder_item := resource_tree.create_item(parent)
300 folder_item.set_text(0, label)
301 folder_item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
302 folder_item.set_tooltip_text(0, tooltip)
303 if color == Color.BLACK:
304 folder_item.set_custom_bg_color(0, get_theme_color("base_color", "Editor"))
307 folder_item.set_custom_bg_color(0, color)
309 if label in DialogicUtil.get_editor_setting("resource_list_collapsed_info", []):
310 folder_item.collapsed = true
315 func get_directory_items(directory:Dictionary, filter:String, icon:Texture2D, resources_list:Array) -> Array:
317 for item_name in directory:
318 if (directory[item_name] in resources_list) and (filter.is_empty() or filter.to_lower() in item_name.to_lower()):
319 var item := ResourceListItem.new()
320 item.text = item_name
322 item.metadata = directory[item_name]
323 item.tooltip = directory[item_name]
328 class ResourceListItem:
337 func _to_string() -> String:
338 return JSON.stringify(
342 "icon": icon.resource_path,
343 "metadata": metadata,
345 "parent_dir": get_parent_directory()
351 func get_parent_directory() -> String:
352 return (metadata.get_base_dir() as String).split("/")[-1]
355 func _sort_by_item_text(a: ResourceListItem, b: ResourceListItem) -> bool:
356 return a.text < b.text
361 #region INTERACTING WITH RESOURCES
364 func _on_resources_tree_item_activated() -> void:
365 if resource_tree.get_selected() == null:
367 var item := resource_tree.get_selected()
368 if item.get_metadata(0) == null:
370 edit_resource(item.get_metadata(0))
373 func _on_resources_tree_item_clicked(_pos: Vector2, mouse_button_index: int) -> void:
374 match mouse_button_index:
376 var selected_item := resource_tree.get_selected()
377 if selected_item == null:
379 if selected_item.get_metadata(0) == null:
381 var resource_item := load(selected_item.get_metadata(0))
382 call_deferred("edit_resource", resource_item)
385 remove_item_from_list(resource_tree.get_selected())
388 if resource_tree.get_selected().get_metadata(0):
389 %RightClickMenu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2()))
390 %RightClickMenu.set_meta("item_clicked", resource_tree.get_selected())
393 func _on_resources_tree_item_collapsed(item:TreeItem) -> void:
394 var collapsed_info := DialogicUtil.get_editor_setting("resource_list_collapsed_info", [])
395 if item.get_text(0) in collapsed_info:
396 if not item.collapsed:
397 collapsed_info.erase(item.get_text(0))
400 collapsed_info.append(item.get_text(0))
401 DialogicUtil.set_editor_setting("resource_list_collapsed_info", collapsed_info)
404 func edit_resource(resource_item: Variant) -> void:
405 if resource_item is Resource:
406 editors_manager.edit_resource(resource_item)
408 editors_manager.edit_resource(load(resource_item))
411 func remove_item_from_list(item: TreeItem) -> void:
413 for entry in DialogicUtil.get_editor_setting("last_resources", []):
414 if entry != item.get_metadata(0):
415 new_list.append(entry)
416 DialogicUtil.set_editor_setting("last_resources", new_list)
417 update_resource_list(new_list)
420 func _on_right_click_menu_id_pressed(id: int) -> void:
422 1: # REMOVE ITEM FROM LIST
423 remove_item_from_list(%RightClickMenu.get_meta("item_clicked"))
424 2: # OPEN IN FILESYSTEM
425 EditorInterface.get_file_system_dock().navigate_to_path(
426 %RightClickMenu.get_meta("item_clicked").get_metadata(0)
428 3: # OPEN IN EXTERNAL EDITOR
430 ProjectSettings.globalize_path(
431 %RightClickMenu.get_meta("item_clicked").get_metadata(0)
435 DisplayServer.clipboard_set(
436 DialogicResourceUtil.get_unique_identifier(
437 %RightClickMenu.get_meta("item_clicked").get_metadata(0)
445 func _on_search_text_changed(_new_text: String) -> void:
446 update_resource_list()
447 for item in resource_tree.get_root().get_children():
448 if item.get_children().size() > 0:
449 resource_tree.set_selected(item.get_child(0), 0)
453 func _on_search_text_submitted(_new_text: String) -> void:
454 if resource_tree.get_selected() == null:
456 var item := resource_tree.get_selected()
457 if item.get_metadata(0) == null:
459 edit_resource(item.get_metadata(0))
467 func update_content_list(list: PackedStringArray) -> void:
468 var prev_selected := ""
469 if %ContentList.is_anything_selected():
470 prev_selected = %ContentList.get_item_text(%ContentList.get_selected_items()[0])
472 %ContentList.add_item("~ Top")
476 %ContentList.add_item(i)
477 if i == prev_selected:
478 %ContentList.select(%ContentList.item_count - 1)
482 var current_resource: Resource = editors_manager.get_current_editor().current_resource
484 var timeline_directory := DialogicResourceUtil.get_timeline_directory()
485 var label_directory := DialogicResourceUtil.get_label_cache()
486 if current_resource != null:
487 for i in timeline_directory:
488 if timeline_directory[i] == current_resource.resource_path:
489 label_directory[i] = list
491 # also always store the current timelines labels for easy access
492 label_directory[""] = list
494 DialogicResourceUtil.set_label_cache(label_directory)
499 #region RESOURCE LIST OPTIONS
501 func _on_options_pressed() -> void:
502 %OptionsPopup.popup_on_parent(Rect2(%Options.global_position+%Options.size*Vector2(0,1), Vector2()))
505 func _on_grouping_changed(idx: int) -> void:
506 var id: int = %GroupingOptions.get_item_id(idx)
507 if (GroupMode as Dictionary).values().has(id):
508 group_mode = (id as GroupMode)
509 DialogicUtil.set_editor_setting("sidebar_group_mode", id)
510 update_resource_list()
512 %FolderColors.disabled = group_mode != GroupMode.PATH
513 %TrimFolderPaths.disabled = group_mode != GroupMode.PATH
516 func _on_folder_colors_toggled(toggled_on: bool) -> void:
517 DialogicUtil.set_editor_setting("sidebar_use_folder_colors", toggled_on)
518 update_resource_list()
521 func _on_trim_folder_paths_toggled(toggled_on: bool) -> void:
522 DialogicUtil.set_editor_setting("sidebar_trim_folder_paths", toggled_on)
523 update_resource_list()
528 func _on_main_v_split_dragged(offset: int) -> void:
529 DialogicUtil.set_editor_setting("sidebar_v_split", offset)