From 163753949e31ec71ea5aeda902e69947369955af Mon Sep 17 00:00:00 2001 From: Pablo Andres Fuente Date: Sun, 22 Sep 2024 20:47:20 -0300 Subject: [PATCH] Add filter & sort to editor file dialog Closes https://github.com/godotengine/godot-proposals/issues/2721 On `EditorFileDialog`: * Add filter box that only shows folders and files in current directory that match * Add sort button to sort files and directories * Add a shortcut for CTRL+F for selecting the filter box Also moved common code between `EditorFileDialog` and `FileSystemDock` to it's own file. Co-authored-by: fox <12120644+foxydevloper@users.noreply.github.com> --- editor/file_info.cpp | 61 +++++++++++++ editor/file_info.h | 74 ++++++++++++++++ editor/filesystem_dock.cpp | 143 +++++++++++------------------- editor/filesystem_dock.h | 32 +------ editor/gui/editor_file_dialog.cpp | 138 ++++++++++++++++++++++------ editor/gui/editor_file_dialog.h | 16 +++- 6 files changed, 313 insertions(+), 151 deletions(-) create mode 100644 editor/file_info.cpp create mode 100644 editor/file_info.h diff --git a/editor/file_info.cpp b/editor/file_info.cpp new file mode 100644 index 00000000000..04d82547ab1 --- /dev/null +++ b/editor/file_info.cpp @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* file_info.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "editor/file_info.h" + +void sort_file_info_list(List &r_file_list, FileSortOption p_file_sort_option) { + // Sort the file list if needed. + switch (p_file_sort_option) { + case FileSortOption::FILE_SORT_TYPE: + r_file_list.sort_custom(); + break; + case FileSortOption::FILE_SORT_TYPE_REVERSE: + r_file_list.sort_custom(); + r_file_list.reverse(); + break; + case FileSortOption::FILE_SORT_MODIFIED_TIME: + r_file_list.sort_custom(); + break; + case FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE: + r_file_list.sort_custom(); + r_file_list.reverse(); + break; + case FileSortOption::FILE_SORT_NAME_REVERSE: + r_file_list.sort(); + r_file_list.reverse(); + break; + case FileSortOption::FILE_SORT_NAME: + r_file_list.sort(); + break; + default: + ERR_FAIL_MSG("Invalid file sort option."); + break; + } +} diff --git a/editor/file_info.h b/editor/file_info.h new file mode 100644 index 00000000000..2ce521992c5 --- /dev/null +++ b/editor/file_info.h @@ -0,0 +1,74 @@ +/**************************************************************************/ +/* file_info.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef FILE_INFO_H +#define FILE_INFO_H + +#include "core/variant/variant.h" + +enum class FileSortOption { + FILE_SORT_NAME = 0, + FILE_SORT_NAME_REVERSE = 1, + FILE_SORT_TYPE = 2, + FILE_SORT_TYPE_REVERSE = 3, + FILE_SORT_MODIFIED_TIME = 4, + FILE_SORT_MODIFIED_TIME_REVERSE = 5, + FILE_SORT_MAX = 6, +}; + +struct FileInfo { + String name; + String path; + String icon_path; + StringName type; + Vector sources; + bool import_broken = false; + uint64_t modified_time = 0; + + bool operator<(const FileInfo &p_fi) const { + return FileNoCaseComparator()(name, p_fi.name); + } +}; + +struct FileInfoTypeComparator { + bool operator()(const FileInfo &p_a, const FileInfo &p_b) const { + return FileNoCaseComparator()(p_a.name.get_extension() + p_a.type + p_a.name.get_basename(), p_b.name.get_extension() + p_b.type + p_b.name.get_basename()); + } +}; + +struct FileInfoModifiedTimeComparator { + bool operator()(const FileInfo &p_a, const FileInfo &p_b) const { + return p_a.modified_time > p_b.modified_time; + } +}; + +void sort_file_info_list(List &r_file_list, FileSortOption p_file_sort_option); + +#endif // FILE_INFO_H diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index dd2eec6893d..f8cfb8f9e1b 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -263,7 +263,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } // Create items for all subdirectories. - bool reversed = file_sort == FILE_SORT_NAME_REVERSE; + bool reversed = file_sort == FileSortOption::FILE_SORT_NAME_REVERSE; for (int i = reversed ? p_dir->get_subdir_count() - 1 : 0; reversed ? i >= 0 : i < p_dir->get_subdir_count(); reversed ? i-- : i++) { @@ -294,28 +294,28 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } } - FileInfo fi; - fi.name = p_dir->get_file(i); - fi.type = p_dir->get_file_type(i); - fi.icon_path = p_dir->get_file_icon_path(i); - fi.import_broken = !p_dir->get_file_import_is_valid(i); - fi.modified_time = p_dir->get_file_modified_time(i); + FileInfo file_info; + file_info.name = p_dir->get_file(i); + file_info.type = p_dir->get_file_type(i); + file_info.icon_path = p_dir->get_file_icon_path(i); + file_info.import_broken = !p_dir->get_file_import_is_valid(i); + file_info.modified_time = p_dir->get_file_modified_time(i); - file_list.push_back(fi); + file_list.push_back(file_info); } // Sort the file list if needed. - _sort_file_info_list(file_list); + sort_file_info_list(file_list, file_sort); // Build the tree. const int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)); - for (const FileInfo &fi : file_list) { + for (const FileInfo &file_info : file_list) { TreeItem *file_item = tree->create_item(subdirectory_item); - const String file_metadata = lpath.path_join(fi.name); - file_item->set_text(0, fi.name); + const String file_metadata = lpath.path_join(file_info.name); + file_item->set_text(0, file_info.name); file_item->set_structured_text_bidi_override(0, TextServer::STRUCTURED_TEXT_FILE); - file_item->set_icon(0, _get_tree_item_icon(!fi.import_broken, fi.type, fi.icon_path)); + file_item->set_icon(0, _get_tree_item_icon(!file_info.import_broken, file_info.type, file_info.icon_path)); if (da->is_link(file_metadata)) { file_item->set_icon_overlay(0, get_editor_theme_icon(SNAME("LinkOverlay"))); file_item->set_tooltip_text(0, vformat(TTR("Link to: %s"), da->read_link(file_metadata))); @@ -860,19 +860,19 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List * String file = p_path->get_file(i); if (_matches_all_search_tokens(file)) { - FileInfo fi; - fi.name = file; - fi.type = p_path->get_file_type(i); - fi.path = p_path->get_file_path(i); - fi.import_broken = !p_path->get_file_import_is_valid(i); - fi.modified_time = p_path->get_file_modified_time(i); + FileInfo file_info; + file_info.name = file; + file_info.type = p_path->get_file_type(i); + file_info.path = p_path->get_file_path(i); + file_info.import_broken = !p_path->get_file_import_is_valid(i); + file_info.modified_time = p_path->get_file_modified_time(i); - if (_is_file_type_disabled_by_feature_profile(fi.type)) { + if (_is_file_type_disabled_by_feature_profile(file_info.type)) { // This type is disabled, will not appear here. continue; } - matches->push_back(fi); + matches->push_back(file_info); if (matches->size() > p_max_items) { return; } @@ -880,45 +880,6 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List * } } -struct FileSystemDock::FileInfoTypeComparator { - bool operator()(const FileInfo &p_a, const FileInfo &p_b) const { - return FileNoCaseComparator()(p_a.name.get_extension() + p_a.type + p_a.name.get_basename(), p_b.name.get_extension() + p_b.type + p_b.name.get_basename()); - } -}; - -struct FileSystemDock::FileInfoModifiedTimeComparator { - bool operator()(const FileInfo &p_a, const FileInfo &p_b) const { - return p_a.modified_time > p_b.modified_time; - } -}; - -void FileSystemDock::_sort_file_info_list(List &r_file_list) { - // Sort the file list if needed. - switch (file_sort) { - case FILE_SORT_TYPE: - r_file_list.sort_custom(); - break; - case FILE_SORT_TYPE_REVERSE: - r_file_list.sort_custom(); - r_file_list.reverse(); - break; - case FILE_SORT_MODIFIED_TIME: - r_file_list.sort_custom(); - break; - case FILE_SORT_MODIFIED_TIME_REVERSE: - r_file_list.sort_custom(); - r_file_list.reverse(); - break; - case FILE_SORT_NAME_REVERSE: - r_file_list.sort(); - r_file_list.reverse(); - break; - default: // FILE_SORT_NAME - r_file_list.sort(); - break; - } -} - void FileSystemDock::_update_file_list(bool p_keep_selection) { // Register the previously current and selected items. HashSet previous_selection; @@ -1005,22 +966,22 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { int index; EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->find_file(favorite, &index); - FileInfo fi; - fi.name = favorite.get_file(); - fi.path = favorite; + FileInfo file_info; + file_info.name = favorite.get_file(); + file_info.path = favorite; if (efd) { - fi.type = efd->get_file_type(index); - fi.icon_path = efd->get_file_icon_path(index); - fi.import_broken = !efd->get_file_import_is_valid(index); - fi.modified_time = efd->get_file_modified_time(index); + file_info.type = efd->get_file_type(index); + file_info.icon_path = efd->get_file_icon_path(index); + file_info.import_broken = !efd->get_file_import_is_valid(index); + file_info.modified_time = efd->get_file_modified_time(index); } else { - fi.type = ""; - fi.import_broken = true; - fi.modified_time = 0; + file_info.type = ""; + file_info.import_broken = true; + file_info.modified_time = 0; } - if (searched_tokens.is_empty() || _matches_all_search_tokens(fi.name)) { - file_list.push_back(fi); + if (searched_tokens.is_empty() || _matches_all_search_tokens(file_info.name)) { + file_list.push_back(file_info); } } } @@ -1077,7 +1038,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * ITEM_COLOR_SCALE); } - bool reversed = file_sort == FILE_SORT_NAME_REVERSE; + bool reversed = file_sort == FileSortOption::FILE_SORT_NAME_REVERSE; for (int i = reversed ? efd->get_subdir_count() - 1 : 0; reversed ? i >= 0 : i < efd->get_subdir_count(); reversed ? i-- : i++) { @@ -1099,21 +1060,21 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { // Display the folder content. for (int i = 0; i < efd->get_file_count(); i++) { - FileInfo fi; - fi.name = efd->get_file(i); - fi.path = directory.path_join(fi.name); - fi.type = efd->get_file_type(i); - fi.icon_path = efd->get_file_icon_path(i); - fi.import_broken = !efd->get_file_import_is_valid(i); - fi.modified_time = efd->get_file_modified_time(i); + FileInfo file_info; + file_info.name = efd->get_file(i); + file_info.path = directory.path_join(file_info.name); + file_info.type = efd->get_file_type(i); + file_info.icon_path = efd->get_file_icon_path(i); + file_info.import_broken = !efd->get_file_import_is_valid(i); + file_info.modified_time = efd->get_file_modified_time(i); - file_list.push_back(fi); + file_list.push_back(file_info); } } } // Sort the file list if needed. - _sort_file_info_list(file_list); + sort_file_info_list(file_list, file_sort); // Fills the ItemList control node from the FileInfos. String main_scene = GLOBAL_GET("application/run/main_scene"); @@ -3935,7 +3896,7 @@ void FileSystemDock::_project_settings_changed() { } void FileSystemDock::set_file_sort(FileSortOption p_file_sort) { - for (int i = 0; i != FILE_SORT_MAX; i++) { + for (int i = 0; i != (int)FileSortOption::FILE_SORT_MAX; i++) { tree_button_sort->get_popup()->set_item_checked(i, (i == (int)p_file_sort)); file_list_button_sort->get_popup()->set_item_checked(i, (i == (int)p_file_sort)); } @@ -3965,13 +3926,13 @@ MenuButton *FileSystemDock::_create_file_menu_button() { PopupMenu *p = button->get_popup(); p->connect(SceneStringName(id_pressed), callable_mp(this, &FileSystemDock::_file_sort_popup)); - p->add_radio_check_item(TTR("Sort by Name (Ascending)"), FILE_SORT_NAME); - p->add_radio_check_item(TTR("Sort by Name (Descending)"), FILE_SORT_NAME_REVERSE); - p->add_radio_check_item(TTR("Sort by Type (Ascending)"), FILE_SORT_TYPE); - p->add_radio_check_item(TTR("Sort by Type (Descending)"), FILE_SORT_TYPE_REVERSE); - p->add_radio_check_item(TTR("Sort by Last Modified"), FILE_SORT_MODIFIED_TIME); - p->add_radio_check_item(TTR("Sort by First Modified"), FILE_SORT_MODIFIED_TIME_REVERSE); - p->set_item_checked(file_sort, true); + p->add_radio_check_item(TTR("Sort by Name (Ascending)"), (int)FileSortOption::FILE_SORT_NAME); + p->add_radio_check_item(TTR("Sort by Name (Descending)"), (int)FileSortOption::FILE_SORT_NAME_REVERSE); + p->add_radio_check_item(TTR("Sort by Type (Ascending)"), (int)FileSortOption::FILE_SORT_TYPE); + p->add_radio_check_item(TTR("Sort by Type (Descending)"), (int)FileSortOption::FILE_SORT_TYPE_REVERSE); + p->add_radio_check_item(TTR("Sort by Last Modified"), (int)FileSortOption::FILE_SORT_MODIFIED_TIME); + p->add_radio_check_item(TTR("Sort by First Modified"), (int)FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE); + p->set_item_checked((int)file_sort, true); return button; } @@ -4041,7 +4002,7 @@ void FileSystemDock::save_layout_to_config(Ref p_layout, const Strin p_layout->set_value(p_section, "dock_filesystem_h_split_offset", get_h_split_offset()); p_layout->set_value(p_section, "dock_filesystem_v_split_offset", get_v_split_offset()); p_layout->set_value(p_section, "dock_filesystem_display_mode", get_display_mode()); - p_layout->set_value(p_section, "dock_filesystem_file_sort", get_file_sort()); + p_layout->set_value(p_section, "dock_filesystem_file_sort", (int)get_file_sort()); p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", get_file_list_display_mode()); PackedStringArray selected_files = get_selected_paths(); p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 108b646029a..ee68f44d4cd 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -33,6 +33,7 @@ #include "editor/dependency_editor.h" #include "editor/editor_file_system.h" +#include "editor/file_info.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/script_create_dialog.h" #include "scene/gui/box_container.h" @@ -93,16 +94,6 @@ public: DISPLAY_MODE_HSPLIT, }; - enum FileSortOption { - FILE_SORT_NAME = 0, - FILE_SORT_NAME_REVERSE, - FILE_SORT_TYPE, - FILE_SORT_TYPE_REVERSE, - FILE_SORT_MODIFIED_TIME, - FILE_SORT_MODIFIED_TIME_REVERSE, - FILE_SORT_MAX, - }; - enum Overwrite { OVERWRITE_UNDECIDED, OVERWRITE_REPLACE, @@ -146,7 +137,7 @@ private: HashMap folder_colors; Dictionary assigned_folder_colors; - FileSortOption file_sort = FILE_SORT_NAME; + FileSortOption file_sort = FileSortOption::FILE_SORT_NAME; VBoxContainer *scanning_vb = nullptr; ProgressBar *scanning_progress = nullptr; @@ -336,25 +327,6 @@ private: void _tree_empty_click(const Vector2 &p_pos, MouseButton p_button); void _tree_empty_selected(); - struct FileInfo { - String name; - String path; - String icon_path; - StringName type; - Vector sources; - bool import_broken = false; - uint64_t modified_time = 0; - - bool operator<(const FileInfo &fi) const { - return FileNoCaseComparator()(name, fi.name); - } - }; - - struct FileInfoTypeComparator; - struct FileInfoModifiedTimeComparator; - - void _sort_file_info_list(List &r_file_list); - void _search(EditorFileSystemDirectory *p_path, List *matches, int p_max_items); void _set_current_path_line_edit_text(const String &p_path); diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 18f1f6da0c5..a63c3f7848e 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -31,7 +31,6 @@ #include "editor_file_dialog.h" #include "core/config/project_settings.h" -#include "core/io/file_access.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "editor/dependency_editor.h" @@ -183,6 +182,9 @@ void EditorFileDialog::_update_theme_item_cache() { theme_cache.favorites_down = get_editor_theme_icon(SNAME("MoveDown")); theme_cache.create_folder = get_editor_theme_icon(SNAME("FolderCreate")); + theme_cache.filter_box = get_editor_theme_icon(SNAME("Search")); + theme_cache.file_sort_button = get_editor_theme_icon(SNAME("Sort")); + theme_cache.folder = get_editor_theme_icon(SNAME("Folder")); theme_cache.folder_icon_color = get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")); @@ -326,6 +328,10 @@ void EditorFileDialog::shortcut_input(const Ref &p_event) { dir->select_all(); handled = true; } + if (ED_IS_SHORTCUT("file_dialog/focus_filter", p_event)) { + _focus_filter_box(); + handled = true; + } if (ED_IS_SHORTCUT("file_dialog/move_favorite_up", p_event)) { _favorite_move_up(); handled = true; @@ -369,6 +375,8 @@ void EditorFileDialog::update_dir() { } dir->set_text(dir_access->get_current_dir(false)); + filter_box->clear(); + // Disable "Open" button only when selecting file(s) mode. get_ok_button()->set_disabled(_is_open_should_be_disabled()); switch (mode) { @@ -422,7 +430,13 @@ void EditorFileDialog::_post_popup() { item_list->grab_focus(); } - if (mode == FILE_MODE_OPEN_DIR) { + bool is_open_directory_mode = mode == FILE_MODE_OPEN_DIR; + PopupMenu *p = file_sort_button->get_popup(); + p->set_item_disabled(2, is_open_directory_mode); + p->set_item_disabled(3, is_open_directory_mode); + p->set_item_disabled(4, is_open_directory_mode); + p->set_item_disabled(5, is_open_directory_mode); + if (is_open_directory_mode) { file_box->set_visible(false); } else { file_box->set_visible(true); @@ -956,7 +970,7 @@ void EditorFileDialog::update_file_list() { dir_access->list_dir_begin(); - List files; + List file_infos; List dirs; String item = dir_access->get_next(); @@ -967,27 +981,44 @@ void EditorFileDialog::update_file_list() { continue; } - if (show_hidden_files) { - if (!dir_access->current_is_dir()) { - files.push_back(item); - } else { - dirs.push_back(item); - } - } else if (!dir_access->current_is_hidden()) { - String full_path = cdir == "res://" ? item : dir_access->get_current_dir() + "/" + item; - if (dir_access->current_is_dir()) { - if (Engine::get_singleton()->is_project_manager_hint() || !EditorFileSystem::_should_skip_directory(full_path)) { - dirs.push_back(item); + bool matches_search = true; + if (search_string.length() > 0) { + matches_search = item.find(search_string) != -1; + } + + FileInfo file_info; + file_info.name = item; + file_info.path = cdir.path_join(file_info.name); + file_info.type = item.get_extension(); + file_info.modified_time = FileAccess::get_modified_time(file_info.path); + + if (matches_search) { + if (show_hidden_files) { + if (!dir_access->current_is_dir()) { + file_infos.push_back(file_info); + } else { + dirs.push_back(file_info.name); + } + } else if (!dir_access->current_is_hidden()) { + String full_path = cdir == "res://" ? file_info.name : dir_access->get_current_dir() + "/" + file_info.name; + if (dir_access->current_is_dir()) { + if (Engine::get_singleton()->is_project_manager_hint() || !EditorFileSystem::_should_skip_directory(full_path)) { + dirs.push_back(file_info.name); + } + } else { + file_infos.push_back(file_info); } - } else { - files.push_back(item); } } item = dir_access->get_next(); } dirs.sort_custom(); - files.sort_custom(); + bool reverse_directories = file_sort == FileSortOption::FILE_SORT_NAME_REVERSE; + if (reverse_directories) { + dirs.reverse(); + } + sort_file_info_list(file_infos, file_sort); while (!dirs.is_empty()) { const String &dir_name = dirs.front()->get(); @@ -1037,25 +1068,26 @@ void EditorFileDialog::update_file_list() { } } - while (!files.is_empty()) { + while (!file_infos.is_empty()) { bool match = patterns.is_empty(); + FileInfo file_info = file_infos.front()->get(); for (const String &E : patterns) { - if (files.front()->get().matchn(E)) { + if (file_info.name.matchn(E)) { match = true; break; } } if (match) { - item_list->add_item(files.front()->get()); + item_list->add_item(file_info.name); if (get_icon_func) { - Ref icon = get_icon_func(cdir.path_join(files.front()->get())); + Ref icon = get_icon_func(file_info.path); if (display_mode == DISPLAY_THUMBNAILS) { Ref thumbnail; if (get_thumbnail_func) { - thumbnail = get_thumbnail_func(cdir.path_join(files.front()->get())); + thumbnail = get_thumbnail_func(file_info.path); } if (thumbnail.is_null()) { thumbnail = file_thumbnail; @@ -1069,22 +1101,21 @@ void EditorFileDialog::update_file_list() { } Dictionary d; - d["name"] = files.front()->get(); + d["name"] = file_info.name; d["dir"] = false; - String fullpath = cdir.path_join(files.front()->get()); - d["path"] = fullpath; + d["path"] = file_info.path; item_list->set_item_metadata(-1, d); if (display_mode == DISPLAY_THUMBNAILS && previews_enabled) { - EditorResourcePreview::get_singleton()->queue_resource_preview(fullpath, this, "_thumbnail_result", fullpath); + EditorResourcePreview::get_singleton()->queue_resource_preview(file_info.path, this, "_thumbnail_result", file_info.path); } - if (file->get_text() == files.front()->get()) { + if (file->get_text() == file_info.name) { item_list->set_current(item_list->get_item_count() - 1); } } - files.pop_front(); + file_infos.pop_front(); } if (favorites->get_current() >= 0) { @@ -1353,6 +1384,24 @@ void EditorFileDialog::_make_dir() { makedirname->grab_focus(); } +void EditorFileDialog::_focus_filter_box() { + filter_box->grab_focus(); + filter_box->select_all(); +} + +void EditorFileDialog::_filter_changed(const String &p_text) { + search_string = p_text; + invalidate(); +} + +void EditorFileDialog::_file_sort_popup(int p_id) { + for (int i = 0; i != static_cast(FileSortOption::FILE_SORT_MAX); i++) { + file_sort_button->get_popup()->set_item_checked(i, (i == p_id)); + } + file_sort = static_cast(p_id); + invalidate(); +} + void EditorFileDialog::_delete_items() { // Collect the selected folders and files to delete and check them in the deletion dependency dialog. Vector folders; @@ -1444,6 +1493,10 @@ void EditorFileDialog::_update_icons() { show_hidden->set_icon(theme_cache.toggle_hidden); makedir->set_icon(theme_cache.create_folder); + filter_box->set_right_icon(theme_cache.filter_box); + file_sort_button->set_icon(theme_cache.file_sort_button); + filter_box->set_clear_button_enabled(true); + fav_up->set_icon(theme_cache.favorites_up); fav_down->set_icon(theme_cache.favorites_down); } @@ -2093,6 +2146,7 @@ EditorFileDialog::EditorFileDialog() { // Allow both Cmd + L and Cmd + Shift + G to match Safari's and Finder's shortcuts respectively. ED_SHORTCUT_OVERRIDE_ARRAY("file_dialog/focus_path", "macos", { int32_t(KeyModifierMask::META | Key::L), int32_t(KeyModifierMask::META | KeyModifierMask::SHIFT | Key::G) }); + ED_SHORTCUT("file_dialog/focus_filter", TTR("Focus Filter"), KeyModifierMask::CMD_OR_CTRL | Key::F); ED_SHORTCUT("file_dialog/move_favorite_up", TTR("Move Favorite Up"), KeyModifierMask::CMD_OR_CTRL | Key::UP); ED_SHORTCUT("file_dialog/move_favorite_down", TTR("Move Favorite Down"), KeyModifierMask::CMD_OR_CTRL | Key::DOWN); @@ -2256,11 +2310,37 @@ EditorFileDialog::EditorFileDialog() { VBoxContainer *list_vb = memnew(VBoxContainer); list_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + HBoxContainer *lower_hb = memnew(HBoxContainer); + lower_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + l = memnew(Label(TTR("Directories & Files:"))); l->set_theme_type_variation("HeaderSmall"); - list_vb->add_child(l); + lower_hb->add_child(l); + + list_vb->add_child(lower_hb); preview_hb->add_child(list_vb); + filter_box = memnew(LineEdit); + filter_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); + filter_box->set_placeholder(TTR("Filter")); + filter_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorFileDialog::_filter_changed)); + lower_hb->add_child(filter_box); + + file_sort_button = memnew(MenuButton); + file_sort_button->set_flat(true); + file_sort_button->set_tooltip_text(TTR("Sort files")); + + PopupMenu *p = file_sort_button->get_popup(); + p->connect(SceneStringName(id_pressed), callable_mp(this, &EditorFileDialog::_file_sort_popup)); + p->add_radio_check_item(TTR("Sort by Name (Ascending)"), static_cast(FileSortOption::FILE_SORT_NAME)); + p->add_radio_check_item(TTR("Sort by Name (Descending)"), static_cast(FileSortOption::FILE_SORT_NAME_REVERSE)); + p->add_radio_check_item(TTR("Sort by Type (Ascending)"), static_cast(FileSortOption::FILE_SORT_TYPE)); + p->add_radio_check_item(TTR("Sort by Type (Descending)"), static_cast(FileSortOption::FILE_SORT_TYPE_REVERSE)); + p->add_radio_check_item(TTR("Sort by Last Modified"), static_cast(FileSortOption::FILE_SORT_MODIFIED_TIME)); + p->add_radio_check_item(TTR("Sort by First Modified"), static_cast(FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE)); + p->set_item_checked(0, true); + lower_hb->add_child(file_sort_button); + // Item (files and folders) list with context menu. item_list = memnew(ItemList); diff --git a/editor/gui/editor_file_dialog.h b/editor/gui/editor_file_dialog.h index 6272e27f826..1922155133b 100644 --- a/editor/gui/editor_file_dialog.h +++ b/editor/gui/editor_file_dialog.h @@ -32,13 +32,15 @@ #define EDITOR_FILE_DIALOG_H #include "core/io/dir_access.h" +#include "editor/file_info.h" #include "scene/gui/dialogs.h" #include "scene/property_list_helper.h" -class GridContainer; class DependencyRemoveDialog; +class GridContainer; class HSplitContainer; class ItemList; +class MenuButton; class OptionButton; class PopupMenu; class TextureRect; @@ -127,6 +129,11 @@ private: Button *favorite = nullptr; Button *show_hidden = nullptr; + String search_string; + LineEdit *filter_box = nullptr; + FileSortOption file_sort = FileSortOption::FILE_SORT_NAME; + MenuButton *file_sort_button = nullptr; + Button *fav_up = nullptr; Button *fav_down = nullptr; @@ -165,6 +172,9 @@ private: Ref favorites_up; Ref favorites_down; + Ref filter_box; + Ref file_sort_button; + Ref folder; Color folder_icon_color; @@ -227,6 +237,10 @@ private: void _make_dir(); void _make_dir_confirm(); + void _focus_filter_box(); + void _filter_changed(const String &p_text); + void _file_sort_popup(int p_id); + void _delete_items(); void _delete_files_global();