HighNoonRoulette/addons/script-ide/quickopen/quick_open_panel.gd
2025-08-28 01:55:38 +02:00

234 lines
6.4 KiB
GDScript

## Quick open panel to quickly access all resources that are in the project.
## Initially shows all resources, but can be changed to more specific resources
## or filtered down with text.
@tool
extends PopupPanel
const ADDONS: StringName = &"res://addons"
const SEPARATOR: StringName = &" - "
const STRUCTURE_START: StringName = &"("
const STRUCTURE_END: StringName = &")"
#region UI
@onready var filter_bar: TabBar = %FilterBar
@onready var search_option_btn: OptionButton = %SearchOptionBtn
@onready var filter_txt: LineEdit = %FilterTxt
@onready var files_list: ItemList = %FilesList
#endregion
var plugin: EditorPlugin
var scenes: Array[FileData]
var scripts: Array[FileData]
var resources: Array[FileData]
var others: Array[FileData]
# For performance and memory considerations, we add all files into one reusable array.
var all_files: Array[FileData]
var is_rebuild_cache: bool = true
#region Plugin and Shortcut processing
func _ready() -> void:
files_list.item_selected.connect(open_file)
search_option_btn.item_selected.connect(rebuild_cache_and_ui.unbind(1))
filter_txt.text_changed.connect(fill_files_list.unbind(1))
filter_bar.tab_changed.connect(change_fill_files_list.unbind(1))
about_to_popup.connect(on_show)
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
file_system.filesystem_changed.connect(schedule_rebuild)
if (plugin != null):
filter_txt.gui_input.connect(plugin.navigate_on_list.bind(files_list, open_file))
func _shortcut_input(event: InputEvent) -> void:
if (!event.is_pressed() || event.is_echo()):
return
if (plugin.tab_cycle_forward_shc.matches_event(event)):
get_viewport().set_input_as_handled()
var new_tab: int = filter_bar.current_tab + 1
if (new_tab == filter_bar.get_tab_count()):
new_tab = 0
filter_bar.current_tab = new_tab
elif (plugin.tab_cycle_backward_shc.matches_event(event)):
get_viewport().set_input_as_handled()
var new_tab: int = filter_bar.current_tab - 1
if (new_tab == -1):
new_tab = filter_bar.get_tab_count() - 1
filter_bar.current_tab = new_tab
#endregion
func open_file(index: int):
hide()
var file: String = files_list.get_item_metadata(index)
if (ResourceLoader.exists(file)):
var res: Resource = load(file)
EditorInterface.edit_resource(res)
if (res is PackedScene):
EditorInterface.open_scene_from_path(file)
func schedule_rebuild():
is_rebuild_cache = true
func on_show():
if (search_option_btn.selected != 0):
search_option_btn.selected = 0
is_rebuild_cache = true
var rebuild_ui: bool = false
var all_tab_not_pressed: bool = filter_bar.current_tab != 0
rebuild_ui = is_rebuild_cache || all_tab_not_pressed
if (is_rebuild_cache):
rebuild_cache()
if (rebuild_ui):
if (all_tab_not_pressed):
# Triggers the ui update.
filter_bar.current_tab = 0
else:
fill_files_list()
filter_txt.select_all()
focus_and_select_first()
func rebuild_cache():
is_rebuild_cache = false
all_files.clear()
scenes.clear()
scripts.clear()
resources.clear()
others.clear()
build_file_cache()
func rebuild_cache_and_ui():
rebuild_cache()
fill_files_list()
focus_and_select_first()
func focus_and_select_first():
filter_txt.grab_focus()
if (files_list.item_count > 0):
files_list.select(0)
func build_file_cache():
var dir: EditorFileSystemDirectory = EditorInterface.get_resource_filesystem().get_filesystem()
build_file_cache_dir(dir)
all_files.append_array(scenes)
all_files.append_array(scripts)
all_files.append_array(resources)
all_files.append_array(others)
func build_file_cache_dir(dir: EditorFileSystemDirectory):
for index: int in dir.get_subdir_count():
build_file_cache_dir(dir.get_subdir(index))
for index: int in dir.get_file_count():
var file: String = dir.get_file_path(index)
if (search_option_btn.get_selected_id() == 0 && file.begins_with(ADDONS)):
continue
var last_delimiter: int = file.rfind(&"/")
var file_name: String = file.substr(last_delimiter + 1)
var file_structure: String = &""
if (file_name.length() + 6 != file.length()):
file_structure = SEPARATOR + STRUCTURE_START + file.substr(6, last_delimiter - 6) + STRUCTURE_END
var file_data: FileData = FileData.new()
file_data.file = file
file_data.file_name = file_name
file_data.file_name_structure = file_name + file_structure
file_data.file_type = dir.get_file_type(index)
# Needed, as otherwise we have no icon.
if (file_data.file_type == &"Resource"):
file_data.file_type = &"Object"
match (file.get_extension()):
&"tscn": scenes.append(file_data)
&"gd": scripts.append(file_data)
&"tres": resources.append(file_data)
&"gdshader": resources.append(file_data)
_: others.append(file_data)
func change_fill_files_list():
fill_files_list()
focus_and_select_first()
func fill_files_list():
files_list.clear()
if (filter_bar.current_tab == 0):
fill_files_list_with(all_files)
elif (filter_bar.current_tab == 1):
fill_files_list_with(scenes)
elif (filter_bar.current_tab == 2):
fill_files_list_with(scripts)
elif (filter_bar.current_tab == 3):
fill_files_list_with(resources)
elif (filter_bar.current_tab == 4):
fill_files_list_with(others)
func fill_files_list_with(files: Array[FileData]):
var filter_text: String = filter_txt.text
files.sort_custom(sort_by_filter)
for file_data: FileData in files:
var file: String = file_data.file
if (filter_text.is_empty() || filter_text.is_subsequence_ofn(file)):
var icon: Texture2D = EditorInterface.get_base_control().get_theme_icon(file_data.file_type, &"EditorIcons")
files_list.add_item(file_data.file_name_structure, icon)
files_list.set_item_metadata(files_list.item_count - 1, file)
files_list.set_item_tooltip(files_list.item_count - 1, file)
func sort_by_filter(file_data1: FileData, file_data2: FileData) -> bool:
var filter_text: String = filter_txt.text
var name1: String = file_data1.file_name
var name2: String = file_data2.file_name
for index: int in filter_text.length():
var a_oob: bool = index >= name1.length()
var b_oob: bool = index >= name2.length()
if (a_oob):
if (b_oob):
return false;
return true
if (b_oob):
return false
var char: String = filter_text[index]
var a_match: bool = char == name1[index]
var b_match: bool = char == name2[index]
if (a_match && !b_match):
return true
if (b_match && !a_match):
return false
return name1 < name2
class FileData:
var file: String
var file_name: String
var file_name_structure: String
var file_type: StringName