]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Editor/Common/sidebar.gd
Updated export config options
[wolf-seeking-sheep.git] / addons / dialogic / Editor / Common / sidebar.gd
1 @tool
2 class_name DialogicSidebar extends Control
3
4 ## Script that handles the editor sidebar.
5
6 signal content_item_activated(item_name)
7 signal show_sidebar(show: bool)
8
9 # References
10 @onready var editors_manager = get_parent().get_parent()
11 @onready var resource_tree: Tree = %ResourceTree
12
13 var current_resource_list: Array = []
14
15 enum GroupMode {
16         NONE,
17         TYPE,
18         FOLDER,
19         PATH,
20 }
21 var group_mode: GroupMode = GroupMode.TYPE
22
23
24 func _ready() -> void:
25         if owner != null and owner.get_parent() is SubViewport:
26                 return
27         if editors_manager is SubViewportContainer:
28                 return
29
30         ## CONNECTIONS
31         editors_manager.resource_opened.connect(_on_editors_resource_opened)
32         editors_manager.editor_changed.connect(_on_editors_editor_changed)
33
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)
37
38         %ContentList.item_selected.connect(
39                 func(idx: int): content_item_activated.emit(%ContentList.get_item_text(idx))
40         )
41
42         %OpenButton.pressed.connect(_show_sidebar)
43         %CloseButton.pressed.connect(_hide_sidebar)
44
45         var editor_scale := DialogicUtil.get_editor_scale()
46
47         ## ICONS
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"))
53         %OptionsPopup.hide()
54
55         %ContentList.add_theme_color_override(
56                 "font_hovered_color", get_theme_color("warning_color", "Editor")
57         )
58         %ContentList.add_theme_color_override(
59                 "font_selected_color", get_theme_color("property_color_z", "Editor")
60         )
61
62         ## RIGHT CLICK MENU
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
70         )
71         %RightClickMenu.add_icon_item(
72                 get_theme_icon("ExternalLink", "EditorIcons"), "Open in External Program", 3
73         )
74
75         ## SORT MENU
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)
81
82         await get_tree().process_frame
83         if DialogicUtil.get_editor_setting("sidebar_collapsed", false):
84                 _hide_sidebar()
85
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))
89
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)
92
93         update_resource_list()
94
95
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 + "(*)"
101
102
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)
106
107
108 #region SHOW/HIDE SIDEBAR
109 ################################################################################
110
111 func _show_sidebar() -> void:
112         %VBoxPrimary.show()
113         %VBoxHidden.hide()
114         DialogicUtil.set_editor_setting("sidebar_collapsed", false)
115         show_sidebar.emit(true)
116
117
118 func _hide_sidebar() -> void:
119         %VBoxPrimary.hide()
120         %VBoxHidden.show()
121         DialogicUtil.set_editor_setting("sidebar_collapsed", true)
122         show_sidebar.emit(false)
123
124 #endregion
125
126
127 ################################################################################
128 ##                                              RESOURCE LIST
129 ################################################################################
130
131
132 func _on_editors_resource_opened(_resource: Resource) -> void:
133         update_resource_list()
134
135
136 func _on_editors_editor_changed(_previous: DialogicEditor, current: DialogicEditor) -> void:
137         %ContentListSection.visible = current.current_resource is DialogicTimeline
138         update_resource_list()
139
140
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)))
144
145
146 #region BULDING/FILTERING THE RESOURCE LIST
147
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
153
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)
160
161         resources_list = clean_resource_list(resources_list)
162
163         %CurrentResource.text = "No Resource"
164         %CurrentResource.add_theme_color_override(
165                 "font_uneditable_color", get_theme_color("disabled_font_color", "Editor")
166         )
167
168         resource_tree.clear()
169
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
173
174         # BUILD TREE
175         var root: TreeItem = resource_tree.create_item()
176
177         match group_mode:
178                 GroupMode.NONE:
179                         all_items.sort_custom(_sort_by_item_text)
180                         for item in all_items:
181                                 add_item(item, root, current_file)
182
183
184                 GroupMode.TYPE:
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)
191
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)
196
197
198                 GroupMode.FOLDER:
199                         var dirs := {}
200                         for item in all_items:
201                                 var dir := item.get_parent_directory() as String
202                                 if not dirs.has(dir):
203                                         dirs[dir] = []
204                                 dirs[dir].append(item)
205
206                         for dir in dirs:
207                                 var dir_item := add_folder_item(dir, root)
208
209                                 for item in dirs[dir]:
210                                         add_item(item, dir_item, current_file)
211
212
213                 GroupMode.PATH:
214                         # Collect all different directories that contain resources
215                         var dirs := {}
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):
219                                         dirs[path] = []
220                                 dirs[path].append(item)
221
222                         # Sort them into ones with the same folder name
223                         var dir_names := {}
224                         for dir in dirs:
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]}
228                                 else:
229                                         dir_names[sliced].folders.append(dir)
230
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:
237                                                 if "/" in i:
238                                                         unique_folder_names[i.get_slice("/", i.get_slice_count("/")-2)+"/"+i.get_slice("/", i.get_slice_count("/")-1)] = i
239                                                 else:
240                                                         unique_folder_names[i] = i
241                                 else:
242                                         unique_folder_names[dir_name] = dir_names[dir_name].folders[0]
243
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(
247                                 func(x, y):
248                                         return x.get_slice("/", x.get_slice_count("/")-1) < y.get_slice("/", y.get_slice_count("/")-1)
249                                         )
250                         var folder_colors: Dictionary = ProjectSettings.get_setting("file_customization/folder_colors", {})
251
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]
264
265                                 var dir_item := add_folder_item(display_name, root, dir_color, dir_path)
266
267                                 for item in dirs[dir_path]:
268                                         add_item(item, dir_item, current_file)
269
270
271         if %CurrentResource.text != "No Resource":
272                 %CurrentResource.add_theme_color_override(
273                         "font_uneditable_color", get_theme_color("font_color", "Editor")
274                 )
275
276         DialogicUtil.set_editor_setting("last_resources", resources_list)
277
278
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)
285
286         if item.metadata == current_file:
287                 %CurrentResource.text = item.metadata.get_file()
288                 resource_tree.set_selected(tree_item, 0)
289
290         var bg_color := parent.get_custom_bg_color(0)
291         if bg_color != get_theme_color("base_color", "Editor"):
292                 bg_color.a = 0.1
293                 tree_item.set_custom_bg_color(0, bg_color)
294
295         return tree_item
296
297
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"))
305         else:
306                 color.a = 0.2
307                 folder_item.set_custom_bg_color(0, color)
308
309         if label in DialogicUtil.get_editor_setting("resource_list_collapsed_info", []):
310                 folder_item.collapsed = true
311
312         return folder_item
313
314
315 func get_directory_items(directory:Dictionary, filter:String, icon:Texture2D, resources_list:Array) -> Array:
316         var items := []
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
321                         item.icon = icon
322                         item.metadata = directory[item_name]
323                         item.tooltip = directory[item_name]
324                         items.append(item)
325         return items
326
327
328 class ResourceListItem:
329         extends Object
330
331         var text: String
332         var index: int = -1
333         var icon: Texture
334         var metadata: String
335         var tooltip: String
336
337         func _to_string() -> String:
338                 return JSON.stringify(
339                         {
340                                 "text": text,
341                                 "index": index,
342                                 "icon": icon.resource_path,
343                                 "metadata": metadata,
344                                 "tooltip": tooltip,
345                                 "parent_dir": get_parent_directory()
346                         },
347                         "\t",
348                         false
349                 )
350
351         func get_parent_directory() -> String:
352                 return (metadata.get_base_dir() as String).split("/")[-1]
353
354
355 func _sort_by_item_text(a: ResourceListItem, b: ResourceListItem) -> bool:
356         return a.text < b.text
357
358 #endregion
359
360
361 #region INTERACTING WITH RESOURCES
362
363
364 func _on_resources_tree_item_activated() -> void:
365         if resource_tree.get_selected() == null:
366                 return
367         var item := resource_tree.get_selected()
368         if item.get_metadata(0) == null:
369                 return
370         edit_resource(item.get_metadata(0))
371
372
373 func _on_resources_tree_item_clicked(_pos: Vector2, mouse_button_index: int) -> void:
374         match mouse_button_index:
375                 MOUSE_BUTTON_LEFT:
376                         var selected_item := resource_tree.get_selected()
377                         if selected_item == null:
378                                 return
379                         if selected_item.get_metadata(0) == null:
380                                 return
381                         var resource_item := load(selected_item.get_metadata(0))
382                         call_deferred("edit_resource", resource_item)
383
384                 MOUSE_BUTTON_MIDDLE:
385                         remove_item_from_list(resource_tree.get_selected())
386
387                 MOUSE_BUTTON_RIGHT:
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())
391
392
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))
398         else:
399                 if item.collapsed:
400                         collapsed_info.append(item.get_text(0))
401         DialogicUtil.set_editor_setting("resource_list_collapsed_info", collapsed_info)
402
403
404 func edit_resource(resource_item: Variant) -> void:
405         if resource_item is Resource:
406                 editors_manager.edit_resource(resource_item)
407         else:
408                 editors_manager.edit_resource(load(resource_item))
409
410
411 func remove_item_from_list(item: TreeItem) -> void:
412         var new_list := []
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)
418
419
420 func _on_right_click_menu_id_pressed(id: int) -> void:
421         match id:
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)
427                         )
428                 3:  # OPEN IN EXTERNAL EDITOR
429                         OS.shell_open(
430                                 ProjectSettings.globalize_path(
431                                         %RightClickMenu.get_meta("item_clicked").get_metadata(0)
432                                 )
433                         )
434                 4:  # COPY IDENTIFIER
435                         DisplayServer.clipboard_set(
436                                 DialogicResourceUtil.get_unique_identifier(
437                                         %RightClickMenu.get_meta("item_clicked").get_metadata(0)
438                                 )
439                         )
440 #endregion
441
442
443 #region FILTERING
444
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)
450                         break
451
452
453 func _on_search_text_submitted(_new_text: String) -> void:
454         if resource_tree.get_selected() == null:
455                 return
456         var item := resource_tree.get_selected()
457         if item.get_metadata(0) == null:
458                 return
459         edit_resource(item.get_metadata(0))
460         %Search.clear()
461
462 #endregion
463
464
465 #region CONTENT LIST
466
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])
471         %ContentList.clear()
472         %ContentList.add_item("~ Top")
473         for i in list:
474                 if i.is_empty():
475                         continue
476                 %ContentList.add_item(i)
477                 if i == prev_selected:
478                         %ContentList.select(%ContentList.item_count - 1)
479         if list.is_empty():
480                 return
481
482         var current_resource: Resource = editors_manager.get_current_editor().current_resource
483
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
490
491         # also always store the current timelines labels for easy access
492         label_directory[""] = list
493
494         DialogicResourceUtil.set_label_cache(label_directory)
495
496 #endregion
497
498
499 #region RESOURCE LIST OPTIONS
500
501 func _on_options_pressed() -> void:
502         %OptionsPopup.popup_on_parent(Rect2(%Options.global_position+%Options.size*Vector2(0,1), Vector2()))
503
504
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()
511
512         %FolderColors.disabled = group_mode != GroupMode.PATH
513         %TrimFolderPaths.disabled = group_mode != GroupMode.PATH
514
515
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()
519
520
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()
524
525 #endregion
526
527
528 func _on_main_v_split_dragged(offset: int) -> void:
529         DialogicUtil.set_editor_setting("sidebar_v_split", offset)