Files
pomchronicles/addons/dialogic/Editor/Common/sidebar.gd

530 lines
17 KiB
GDScript

@tool
class_name DialogicSidebar extends Control
## Script that handles the editor sidebar.
signal content_item_activated(item_name)
signal show_sidebar(show: bool)
# References
@onready var editors_manager = get_parent().get_parent()
@onready var resource_tree: Tree = %ResourceTree
var current_resource_list: Array = []
enum GroupMode {
NONE,
TYPE,
FOLDER,
PATH,
}
var group_mode: GroupMode = GroupMode.TYPE
func _ready() -> void:
if owner != null and owner.get_parent() is SubViewport:
return
if editors_manager is SubViewportContainer:
return
## CONNECTIONS
editors_manager.resource_opened.connect(_on_editors_resource_opened)
editors_manager.editor_changed.connect(_on_editors_editor_changed)
resource_tree.item_activated.connect(_on_resources_tree_item_activated)
resource_tree.item_mouse_selected.connect(_on_resources_tree_item_clicked)
resource_tree.item_collapsed.connect(_on_resources_tree_item_collapsed)
%ContentList.item_selected.connect(
func(idx: int): content_item_activated.emit(%ContentList.get_item_text(idx))
)
%OpenButton.pressed.connect(_show_sidebar)
%CloseButton.pressed.connect(_hide_sidebar)
var editor_scale := DialogicUtil.get_editor_scale()
## ICONS
%Logo.texture = load("res://addons/dialogic/Editor/Images/dialogic-logo.svg")
%Logo.custom_minimum_size.y = 30 * editor_scale
%Search.right_icon = get_theme_icon("Search", "EditorIcons")
%Options.icon = get_theme_icon("GuiTabMenuHl", "EditorIcons")
%OptionsPanel.add_theme_stylebox_override("panel", get_theme_stylebox("PanelForeground", "EditorStyles"))
%OptionsPopup.hide()
%ContentList.add_theme_color_override(
"font_hovered_color", get_theme_color("warning_color", "Editor")
)
%ContentList.add_theme_color_override(
"font_selected_color", get_theme_color("property_color_z", "Editor")
)
## RIGHT CLICK MENU
%RightClickMenu.clear()
%RightClickMenu.add_icon_item(get_theme_icon("Remove", "EditorIcons"), "Remove From List", 1)
%RightClickMenu.add_separator()
%RightClickMenu.add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), "Copy Identifier", 4)
%RightClickMenu.add_separator()
%RightClickMenu.add_icon_item(
get_theme_icon("Filesystem", "EditorIcons"), "Show in FileSystem", 2
)
%RightClickMenu.add_icon_item(
get_theme_icon("ExternalLink", "EditorIcons"), "Open in External Program", 3
)
## SORT MENU
%GroupingOptions.set_item_icon(0, get_theme_icon("AnimationTrackGroup", "EditorIcons"))
%GroupingOptions.set_item_icon(1, get_theme_icon("Folder", "EditorIcons"))
%GroupingOptions.set_item_icon(2, get_theme_icon("FolderBrowse", "EditorIcons"))
%GroupingOptions.set_item_icon(3, get_theme_icon("AnimationTrackList", "EditorIcons"))
%GroupingOptions.item_selected.connect(_on_grouping_changed)
await get_tree().process_frame
if DialogicUtil.get_editor_setting("sidebar_collapsed", false):
_hide_sidebar()
%MainVSplit.split_offset = DialogicUtil.get_editor_setting("sidebar_v_split", 0)
group_mode = DialogicUtil.get_editor_setting("sidebar_group_mode", 0)
%GroupingOptions.select(%GroupingOptions.get_item_index(group_mode))
%FolderColors.button_pressed = DialogicUtil.get_editor_setting("sidebar_use_folder_colors", true)
%TrimFolderPaths.button_pressed = DialogicUtil.get_editor_setting("sidebar_trim_folder_paths", true)
update_resource_list()
func set_unsaved_indicator(saved: bool = true) -> void:
if saved and %CurrentResource.text.ends_with("(*)"):
%CurrentResource.text = %CurrentResource.text.trim_suffix("(*)")
if not saved and not %CurrentResource.text.ends_with("(*)"):
%CurrentResource.text = %CurrentResource.text + "(*)"
func _on_logo_gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
editors_manager.open_editor(editors_manager.editors["HomePage"].node)
#region SHOW/HIDE SIDEBAR
################################################################################
func _show_sidebar() -> void:
%VBoxPrimary.show()
%VBoxHidden.hide()
DialogicUtil.set_editor_setting("sidebar_collapsed", false)
show_sidebar.emit(true)
func _hide_sidebar() -> void:
%VBoxPrimary.hide()
%VBoxHidden.show()
DialogicUtil.set_editor_setting("sidebar_collapsed", true)
show_sidebar.emit(false)
#endregion
################################################################################
## RESOURCE LIST
################################################################################
func _on_editors_resource_opened(_resource: Resource) -> void:
update_resource_list()
func _on_editors_editor_changed(_previous: DialogicEditor, current: DialogicEditor) -> void:
%ContentListSection.visible = current.current_resource is DialogicTimeline
update_resource_list()
## Cleans resources that have been deleted from the resource list
func clean_resource_list(resources_list: Array = []) -> PackedStringArray:
return PackedStringArray(resources_list.filter(func(x): return ResourceLoader.exists(x)))
#region BULDING/FILTERING THE RESOURCE LIST
func update_resource_list(resources_list: PackedStringArray = []) -> void:
var filter: String = %Search.text
var current_file := ""
if editors_manager.current_editor and editors_manager.current_editor.current_resource:
current_file = editors_manager.current_editor.current_resource.resource_path
var character_directory: Dictionary = DialogicResourceUtil.get_character_directory()
var timeline_directory: Dictionary = DialogicResourceUtil.get_timeline_directory()
if resources_list.is_empty():
resources_list = DialogicUtil.get_editor_setting("last_resources", [])
if not current_file in resources_list:
resources_list.append(current_file)
resources_list = clean_resource_list(resources_list)
%CurrentResource.text = "No Resource"
%CurrentResource.add_theme_color_override(
"font_uneditable_color", get_theme_color("disabled_font_color", "Editor")
)
resource_tree.clear()
var character_items: Array = get_directory_items.call(character_directory, filter, load("res://addons/dialogic/Editor/Images/Resources/character.svg"), resources_list)
var timeline_items: Array = get_directory_items.call(timeline_directory, filter, get_theme_icon("TripleBar", "EditorIcons"), resources_list)
var all_items := character_items + timeline_items
# BUILD TREE
var root: TreeItem = resource_tree.create_item()
match group_mode:
GroupMode.NONE:
all_items.sort_custom(_sort_by_item_text)
for item in all_items:
add_item(item, root, current_file)
GroupMode.TYPE:
character_items.sort_custom(_sort_by_item_text)
timeline_items.sort_custom(_sort_by_item_text)
if character_items.size() > 0:
var character_tree := add_folder_item("Characters", root)
for item in character_items:
add_item(item, character_tree, current_file)
if timeline_items.size() > 0:
var timeline_tree := add_folder_item("Timelines", root)
for item in timeline_items:
add_item(item, timeline_tree, current_file)
GroupMode.FOLDER:
var dirs := {}
for item in all_items:
var dir := item.get_parent_directory() as String
if not dirs.has(dir):
dirs[dir] = []
dirs[dir].append(item)
for dir in dirs:
var dir_item := add_folder_item(dir, root)
for item in dirs[dir]:
add_item(item, dir_item, current_file)
GroupMode.PATH:
# Collect all different directories that contain resources
var dirs := {}
for item in all_items:
var path := (item.metadata.get_base_dir() as String).trim_prefix("res://")
if not dirs.has(path):
dirs[path] = []
dirs[path].append(item)
# Sort them into ones with the same folder name
var dir_names := {}
for dir in dirs:
var sliced: String = dir.get_slice("/", dir.get_slice_count("/")-1)
if not sliced in dir_names:
dir_names[sliced] = {"folders":[dir]}
else:
dir_names[sliced].folders.append(dir)
# Create a dictionary mapping a unique name to each directory
# If two have been found to have the same folder name, the parent directory is added
var unique_folder_names := {}
for dir_name in dir_names:
if dir_names[dir_name].folders.size() > 1:
for i in dir_names[dir_name].folders:
if "/" in i:
unique_folder_names[i.get_slice("/", i.get_slice_count("/")-2)+"/"+i.get_slice("/", i.get_slice_count("/")-1)] = i
else:
unique_folder_names[i] = i
else:
unique_folder_names[dir_name] = dir_names[dir_name].folders[0]
# Sort the folder names by their folder name (not by the full path)
var sorted_dir_keys := unique_folder_names.keys()
sorted_dir_keys.sort_custom(
func(x, y):
return x.get_slice("/", x.get_slice_count("/")-1) < y.get_slice("/", y.get_slice_count("/")-1)
)
var folder_colors: Dictionary = ProjectSettings.get_setting("file_customization/folder_colors", {})
for dir in sorted_dir_keys:
var display_name: String = dir
if not %TrimFolderPaths.button_pressed:
display_name = unique_folder_names[dir]
var dir_path: String = unique_folder_names[dir]
var dir_color_path := ""
var dir_color := Color.BLACK
if %FolderColors.button_pressed:
for path in folder_colors:
if String("res://"+dir_path+"/").begins_with(path) and len(path) > len(dir_color_path):
dir_color_path = path
dir_color = folder_colors[path]
var dir_item := add_folder_item(display_name, root, dir_color, dir_path)
for item in dirs[dir_path]:
add_item(item, dir_item, current_file)
if %CurrentResource.text != "No Resource":
%CurrentResource.add_theme_color_override(
"font_uneditable_color", get_theme_color("font_color", "Editor")
)
DialogicUtil.set_editor_setting("last_resources", resources_list)
func add_item(item:ResourceListItem, parent:TreeItem, current_file := "") -> TreeItem:
var tree_item := resource_tree.create_item(parent)
tree_item.set_text(0, item.text)
tree_item.set_icon(0, item.icon)
tree_item.set_metadata(0, item.metadata)
tree_item.set_tooltip_text(0, item.tooltip)
if item.metadata == current_file:
%CurrentResource.text = item.metadata.get_file()
resource_tree.set_selected(tree_item, 0)
var bg_color := parent.get_custom_bg_color(0)
if bg_color != get_theme_color("base_color", "Editor"):
bg_color.a = 0.1
tree_item.set_custom_bg_color(0, bg_color)
return tree_item
func add_folder_item(label: String, parent:TreeItem, color:= Color.BLACK, tooltip:="") -> TreeItem:
var folder_item := resource_tree.create_item(parent)
folder_item.set_text(0, label)
folder_item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
folder_item.set_tooltip_text(0, tooltip)
if color == Color.BLACK:
folder_item.set_custom_bg_color(0, get_theme_color("base_color", "Editor"))
else:
color.a = 0.2
folder_item.set_custom_bg_color(0, color)
if label in DialogicUtil.get_editor_setting("resource_list_collapsed_info", []):
folder_item.collapsed = true
return folder_item
func get_directory_items(directory:Dictionary, filter:String, icon:Texture2D, resources_list:Array) -> Array:
var items := []
for item_name in directory:
if (directory[item_name] in resources_list) and (filter.is_empty() or filter.to_lower() in item_name.to_lower()):
var item := ResourceListItem.new()
item.text = item_name
item.icon = icon
item.metadata = directory[item_name]
item.tooltip = directory[item_name]
items.append(item)
return items
class ResourceListItem:
extends Object
var text: String
var index: int = -1
var icon: Texture
var metadata: String
var tooltip: String
func _to_string() -> String:
return JSON.stringify(
{
"text": text,
"index": index,
"icon": icon.resource_path,
"metadata": metadata,
"tooltip": tooltip,
"parent_dir": get_parent_directory()
},
"\t",
false
)
func get_parent_directory() -> String:
return (metadata.get_base_dir() as String).split("/")[-1]
func _sort_by_item_text(a: ResourceListItem, b: ResourceListItem) -> bool:
return a.text < b.text
#endregion
#region INTERACTING WITH RESOURCES
func _on_resources_tree_item_activated() -> void:
if resource_tree.get_selected() == null:
return
var item := resource_tree.get_selected()
if item.get_metadata(0) == null:
return
edit_resource(item.get_metadata(0))
func _on_resources_tree_item_clicked(_pos: Vector2, mouse_button_index: int) -> void:
match mouse_button_index:
MOUSE_BUTTON_LEFT:
var selected_item := resource_tree.get_selected()
if selected_item == null:
return
if selected_item.get_metadata(0) == null:
return
var resource_item := load(selected_item.get_metadata(0))
call_deferred("edit_resource", resource_item)
MOUSE_BUTTON_MIDDLE:
remove_item_from_list(resource_tree.get_selected())
MOUSE_BUTTON_RIGHT:
if resource_tree.get_selected().get_metadata(0):
%RightClickMenu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2()))
%RightClickMenu.set_meta("item_clicked", resource_tree.get_selected())
func _on_resources_tree_item_collapsed(item:TreeItem) -> void:
var collapsed_info := DialogicUtil.get_editor_setting("resource_list_collapsed_info", [])
if item.get_text(0) in collapsed_info:
if not item.collapsed:
collapsed_info.erase(item.get_text(0))
else:
if item.collapsed:
collapsed_info.append(item.get_text(0))
DialogicUtil.set_editor_setting("resource_list_collapsed_info", collapsed_info)
func edit_resource(resource_item: Variant) -> void:
if resource_item is Resource:
editors_manager.edit_resource(resource_item)
else:
editors_manager.edit_resource(load(resource_item))
func remove_item_from_list(item: TreeItem) -> void:
var new_list := []
for entry in DialogicUtil.get_editor_setting("last_resources", []):
if entry != item.get_metadata(0):
new_list.append(entry)
DialogicUtil.set_editor_setting("last_resources", new_list)
update_resource_list(new_list)
func _on_right_click_menu_id_pressed(id: int) -> void:
match id:
1: # REMOVE ITEM FROM LIST
remove_item_from_list(%RightClickMenu.get_meta("item_clicked"))
2: # OPEN IN FILESYSTEM
EditorInterface.get_file_system_dock().navigate_to_path(
%RightClickMenu.get_meta("item_clicked").get_metadata(0)
)
3: # OPEN IN EXTERNAL EDITOR
OS.shell_open(
ProjectSettings.globalize_path(
%RightClickMenu.get_meta("item_clicked").get_metadata(0)
)
)
4: # COPY IDENTIFIER
DisplayServer.clipboard_set(
DialogicResourceUtil.get_unique_identifier(
%RightClickMenu.get_meta("item_clicked").get_metadata(0)
)
)
#endregion
#region FILTERING
func _on_search_text_changed(_new_text: String) -> void:
update_resource_list()
for item in resource_tree.get_root().get_children():
if item.get_children().size() > 0:
resource_tree.set_selected(item.get_child(0), 0)
break
func _on_search_text_submitted(_new_text: String) -> void:
if resource_tree.get_selected() == null:
return
var item := resource_tree.get_selected()
if item.get_metadata(0) == null:
return
edit_resource(item.get_metadata(0))
%Search.clear()
#endregion
#region CONTENT LIST
func update_content_list(list: PackedStringArray) -> void:
var prev_selected := ""
if %ContentList.is_anything_selected():
prev_selected = %ContentList.get_item_text(%ContentList.get_selected_items()[0])
%ContentList.clear()
%ContentList.add_item("~ Top")
for i in list:
if i.is_empty():
continue
%ContentList.add_item(i)
if i == prev_selected:
%ContentList.select(%ContentList.item_count - 1)
if list.is_empty():
return
var current_resource: Resource = editors_manager.get_current_editor().current_resource
var timeline_directory := DialogicResourceUtil.get_timeline_directory()
var label_directory := DialogicResourceUtil.get_label_cache()
if current_resource != null:
for i in timeline_directory:
if timeline_directory[i] == current_resource.resource_path:
label_directory[i] = list
# also always store the current timelines labels for easy access
label_directory[""] = list
DialogicResourceUtil.set_label_cache(label_directory)
#endregion
#region RESOURCE LIST OPTIONS
func _on_options_pressed() -> void:
%OptionsPopup.popup_on_parent(Rect2(%Options.global_position+%Options.size*Vector2(0,1), Vector2()))
func _on_grouping_changed(idx: int) -> void:
var id: int = %GroupingOptions.get_item_id(idx)
if (GroupMode as Dictionary).values().has(id):
group_mode = (id as GroupMode)
DialogicUtil.set_editor_setting("sidebar_group_mode", id)
update_resource_list()
%FolderColors.disabled = group_mode != GroupMode.PATH
%TrimFolderPaths.disabled = group_mode != GroupMode.PATH
func _on_folder_colors_toggled(toggled_on: bool) -> void:
DialogicUtil.set_editor_setting("sidebar_use_folder_colors", toggled_on)
update_resource_list()
func _on_trim_folder_paths_toggled(toggled_on: bool) -> void:
DialogicUtil.set_editor_setting("sidebar_trim_folder_paths", toggled_on)
update_resource_list()
#endregion
func _on_main_v_split_dragged(offset: int) -> void:
DialogicUtil.set_editor_setting("sidebar_v_split", offset)