Merge pull request #97344 from pafuent/editor_file_dialog_filter_sort

Add filter & sort to editor file dialog
This commit is contained in:
Rémi Verschelde 2024-09-26 18:45:24 +02:00
commit 344ce2b047
No known key found for this signature in database
GPG Key ID: C3336907360768E1
6 changed files with 313 additions and 151 deletions

61
editor/file_info.cpp Normal file
View File

@ -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<FileInfo> &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<FileInfoTypeComparator>();
break;
case FileSortOption::FILE_SORT_TYPE_REVERSE:
r_file_list.sort_custom<FileInfoTypeComparator>();
r_file_list.reverse();
break;
case FileSortOption::FILE_SORT_MODIFIED_TIME:
r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
break;
case FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE:
r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
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;
}
}

74
editor/file_info.h Normal file
View File

@ -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<String> 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<FileInfo> &r_file_list, FileSortOption p_file_sort_option);
#endif // FILE_INFO_H

View File

@ -263,7 +263,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
} }
// Create items for all subdirectories. // 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; for (int i = reversed ? p_dir->get_subdir_count() - 1 : 0;
reversed ? i >= 0 : i < p_dir->get_subdir_count(); reversed ? i >= 0 : i < p_dir->get_subdir_count();
reversed ? i-- : i++) { reversed ? i-- : i++) {
@ -294,28 +294,28 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
} }
} }
FileInfo fi; FileInfo file_info;
fi.name = p_dir->get_file(i); file_info.name = p_dir->get_file(i);
fi.type = p_dir->get_file_type(i); file_info.type = p_dir->get_file_type(i);
fi.icon_path = p_dir->get_file_icon_path(i); file_info.icon_path = p_dir->get_file_icon_path(i);
fi.import_broken = !p_dir->get_file_import_is_valid(i); file_info.import_broken = !p_dir->get_file_import_is_valid(i);
fi.modified_time = p_dir->get_file_modified_time(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 the file list if needed.
_sort_file_info_list(file_list); sort_file_info_list(file_list, file_sort);
// Build the tree. // Build the tree.
const int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)); 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); TreeItem *file_item = tree->create_item(subdirectory_item);
const String file_metadata = lpath.path_join(fi.name); const String file_metadata = lpath.path_join(file_info.name);
file_item->set_text(0, fi.name); file_item->set_text(0, file_info.name);
file_item->set_structured_text_bidi_override(0, TextServer::STRUCTURED_TEXT_FILE); 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)) { if (da->is_link(file_metadata)) {
file_item->set_icon_overlay(0, get_editor_theme_icon(SNAME("LinkOverlay"))); 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))); 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<FileInfo> *
String file = p_path->get_file(i); String file = p_path->get_file(i);
if (_matches_all_search_tokens(file)) { if (_matches_all_search_tokens(file)) {
FileInfo fi; FileInfo file_info;
fi.name = file; file_info.name = file;
fi.type = p_path->get_file_type(i); file_info.type = p_path->get_file_type(i);
fi.path = p_path->get_file_path(i); file_info.path = p_path->get_file_path(i);
fi.import_broken = !p_path->get_file_import_is_valid(i); file_info.import_broken = !p_path->get_file_import_is_valid(i);
fi.modified_time = p_path->get_file_modified_time(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. // This type is disabled, will not appear here.
continue; continue;
} }
matches->push_back(fi); matches->push_back(file_info);
if (matches->size() > p_max_items) { if (matches->size() > p_max_items) {
return; return;
} }
@ -880,45 +880,6 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> *
} }
} }
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<FileSystemDock::FileInfo> &r_file_list) {
// Sort the file list if needed.
switch (file_sort) {
case FILE_SORT_TYPE:
r_file_list.sort_custom<FileInfoTypeComparator>();
break;
case FILE_SORT_TYPE_REVERSE:
r_file_list.sort_custom<FileInfoTypeComparator>();
r_file_list.reverse();
break;
case FILE_SORT_MODIFIED_TIME:
r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
break;
case FILE_SORT_MODIFIED_TIME_REVERSE:
r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
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) { void FileSystemDock::_update_file_list(bool p_keep_selection) {
// Register the previously current and selected items. // Register the previously current and selected items.
HashSet<String> previous_selection; HashSet<String> previous_selection;
@ -1005,22 +966,22 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
int index; int index;
EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->find_file(favorite, &index); EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->find_file(favorite, &index);
FileInfo fi; FileInfo file_info;
fi.name = favorite.get_file(); file_info.name = favorite.get_file();
fi.path = favorite; file_info.path = favorite;
if (efd) { if (efd) {
fi.type = efd->get_file_type(index); file_info.type = efd->get_file_type(index);
fi.icon_path = efd->get_file_icon_path(index); file_info.icon_path = efd->get_file_icon_path(index);
fi.import_broken = !efd->get_file_import_is_valid(index); file_info.import_broken = !efd->get_file_import_is_valid(index);
fi.modified_time = efd->get_file_modified_time(index); file_info.modified_time = efd->get_file_modified_time(index);
} else { } else {
fi.type = ""; file_info.type = "";
fi.import_broken = true; file_info.import_broken = true;
fi.modified_time = 0; file_info.modified_time = 0;
} }
if (searched_tokens.is_empty() || _matches_all_search_tokens(fi.name)) { if (searched_tokens.is_empty() || _matches_all_search_tokens(file_info.name)) {
file_list.push_back(fi); 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); 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; for (int i = reversed ? efd->get_subdir_count() - 1 : 0;
reversed ? i >= 0 : i < efd->get_subdir_count(); reversed ? i >= 0 : i < efd->get_subdir_count();
reversed ? i-- : i++) { reversed ? i-- : i++) {
@ -1099,21 +1060,21 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
// Display the folder content. // Display the folder content.
for (int i = 0; i < efd->get_file_count(); i++) { for (int i = 0; i < efd->get_file_count(); i++) {
FileInfo fi; FileInfo file_info;
fi.name = efd->get_file(i); file_info.name = efd->get_file(i);
fi.path = directory.path_join(fi.name); file_info.path = directory.path_join(file_info.name);
fi.type = efd->get_file_type(i); file_info.type = efd->get_file_type(i);
fi.icon_path = efd->get_file_icon_path(i); file_info.icon_path = efd->get_file_icon_path(i);
fi.import_broken = !efd->get_file_import_is_valid(i); file_info.import_broken = !efd->get_file_import_is_valid(i);
fi.modified_time = efd->get_file_modified_time(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 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. // Fills the ItemList control node from the FileInfos.
String main_scene = GLOBAL_GET("application/run/main_scene"); 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) { 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)); 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)); 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(); PopupMenu *p = button->get_popup();
p->connect(SceneStringName(id_pressed), callable_mp(this, &FileSystemDock::_file_sort_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 (Ascending)"), (int)FileSortOption::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 Name (Descending)"), (int)FileSortOption::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 (Ascending)"), (int)FileSortOption::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 Type (Descending)"), (int)FileSortOption::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 Last Modified"), (int)FileSortOption::FILE_SORT_MODIFIED_TIME);
p->add_radio_check_item(TTR("Sort by First Modified"), FILE_SORT_MODIFIED_TIME_REVERSE); p->add_radio_check_item(TTR("Sort by First Modified"), (int)FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE);
p->set_item_checked(file_sort, true); p->set_item_checked((int)file_sort, true);
return button; return button;
} }
@ -4041,7 +4002,7 @@ void FileSystemDock::save_layout_to_config(Ref<ConfigFile> 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_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_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_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()); p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", get_file_list_display_mode());
PackedStringArray selected_files = get_selected_paths(); PackedStringArray selected_files = get_selected_paths();
p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files); p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files);

View File

@ -33,6 +33,7 @@
#include "editor/dependency_editor.h" #include "editor/dependency_editor.h"
#include "editor/editor_file_system.h" #include "editor/editor_file_system.h"
#include "editor/file_info.h"
#include "editor/plugins/script_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h"
#include "editor/script_create_dialog.h" #include "editor/script_create_dialog.h"
#include "scene/gui/box_container.h" #include "scene/gui/box_container.h"
@ -93,16 +94,6 @@ public:
DISPLAY_MODE_HSPLIT, 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 { enum Overwrite {
OVERWRITE_UNDECIDED, OVERWRITE_UNDECIDED,
OVERWRITE_REPLACE, OVERWRITE_REPLACE,
@ -146,7 +137,7 @@ private:
HashMap<String, Color> folder_colors; HashMap<String, Color> folder_colors;
Dictionary assigned_folder_colors; Dictionary assigned_folder_colors;
FileSortOption file_sort = FILE_SORT_NAME; FileSortOption file_sort = FileSortOption::FILE_SORT_NAME;
VBoxContainer *scanning_vb = nullptr; VBoxContainer *scanning_vb = nullptr;
ProgressBar *scanning_progress = nullptr; ProgressBar *scanning_progress = nullptr;
@ -336,25 +327,6 @@ private:
void _tree_empty_click(const Vector2 &p_pos, MouseButton p_button); void _tree_empty_click(const Vector2 &p_pos, MouseButton p_button);
void _tree_empty_selected(); void _tree_empty_selected();
struct FileInfo {
String name;
String path;
String icon_path;
StringName type;
Vector<String> 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<FileSystemDock::FileInfo> &r_file_list);
void _search(EditorFileSystemDirectory *p_path, List<FileInfo> *matches, int p_max_items); void _search(EditorFileSystemDirectory *p_path, List<FileInfo> *matches, int p_max_items);
void _set_current_path_line_edit_text(const String &p_path); void _set_current_path_line_edit_text(const String &p_path);

View File

@ -31,7 +31,6 @@
#include "editor_file_dialog.h" #include "editor_file_dialog.h"
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include "core/io/file_access.h"
#include "core/os/keyboard.h" #include "core/os/keyboard.h"
#include "core/os/os.h" #include "core/os/os.h"
#include "editor/dependency_editor.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.favorites_down = get_editor_theme_icon(SNAME("MoveDown"));
theme_cache.create_folder = get_editor_theme_icon(SNAME("FolderCreate")); 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 = get_editor_theme_icon(SNAME("Folder"));
theme_cache.folder_icon_color = get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")); theme_cache.folder_icon_color = get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"));
@ -326,6 +328,10 @@ void EditorFileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
dir->select_all(); dir->select_all();
handled = true; 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)) { if (ED_IS_SHORTCUT("file_dialog/move_favorite_up", p_event)) {
_favorite_move_up(); _favorite_move_up();
handled = true; handled = true;
@ -369,6 +375,8 @@ void EditorFileDialog::update_dir() {
} }
dir->set_text(dir_access->get_current_dir(false)); dir->set_text(dir_access->get_current_dir(false));
filter_box->clear();
// Disable "Open" button only when selecting file(s) mode. // Disable "Open" button only when selecting file(s) mode.
get_ok_button()->set_disabled(_is_open_should_be_disabled()); get_ok_button()->set_disabled(_is_open_should_be_disabled());
switch (mode) { switch (mode) {
@ -422,7 +430,13 @@ void EditorFileDialog::_post_popup() {
item_list->grab_focus(); 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); file_box->set_visible(false);
} else { } else {
file_box->set_visible(true); file_box->set_visible(true);
@ -956,7 +970,7 @@ void EditorFileDialog::update_file_list() {
dir_access->list_dir_begin(); dir_access->list_dir_begin();
List<String> files; List<FileInfo> file_infos;
List<String> dirs; List<String> dirs;
String item = dir_access->get_next(); String item = dir_access->get_next();
@ -967,27 +981,44 @@ void EditorFileDialog::update_file_list() {
continue; continue;
} }
if (show_hidden_files) { bool matches_search = true;
if (!dir_access->current_is_dir()) { if (search_string.length() > 0) {
files.push_back(item); matches_search = item.find(search_string) != -1;
} else { }
dirs.push_back(item);
} FileInfo file_info;
} else if (!dir_access->current_is_hidden()) { file_info.name = item;
String full_path = cdir == "res://" ? item : dir_access->get_current_dir() + "/" + item; file_info.path = cdir.path_join(file_info.name);
if (dir_access->current_is_dir()) { file_info.type = item.get_extension();
if (Engine::get_singleton()->is_project_manager_hint() || !EditorFileSystem::_should_skip_directory(full_path)) { file_info.modified_time = FileAccess::get_modified_time(file_info.path);
dirs.push_back(item);
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(); item = dir_access->get_next();
} }
dirs.sort_custom<FileNoCaseComparator>(); dirs.sort_custom<FileNoCaseComparator>();
files.sort_custom<FileNoCaseComparator>(); 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()) { while (!dirs.is_empty()) {
const String &dir_name = dirs.front()->get(); 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(); bool match = patterns.is_empty();
FileInfo file_info = file_infos.front()->get();
for (const String &E : patterns) { for (const String &E : patterns) {
if (files.front()->get().matchn(E)) { if (file_info.name.matchn(E)) {
match = true; match = true;
break; break;
} }
} }
if (match) { if (match) {
item_list->add_item(files.front()->get()); item_list->add_item(file_info.name);
if (get_icon_func) { if (get_icon_func) {
Ref<Texture2D> icon = get_icon_func(cdir.path_join(files.front()->get())); Ref<Texture2D> icon = get_icon_func(file_info.path);
if (display_mode == DISPLAY_THUMBNAILS) { if (display_mode == DISPLAY_THUMBNAILS) {
Ref<Texture2D> thumbnail; Ref<Texture2D> thumbnail;
if (get_thumbnail_func) { 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()) { if (thumbnail.is_null()) {
thumbnail = file_thumbnail; thumbnail = file_thumbnail;
@ -1069,22 +1101,21 @@ void EditorFileDialog::update_file_list() {
} }
Dictionary d; Dictionary d;
d["name"] = files.front()->get(); d["name"] = file_info.name;
d["dir"] = false; d["dir"] = false;
String fullpath = cdir.path_join(files.front()->get()); d["path"] = file_info.path;
d["path"] = fullpath;
item_list->set_item_metadata(-1, d); item_list->set_item_metadata(-1, d);
if (display_mode == DISPLAY_THUMBNAILS && previews_enabled) { 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); item_list->set_current(item_list->get_item_count() - 1);
} }
} }
files.pop_front(); file_infos.pop_front();
} }
if (favorites->get_current() >= 0) { if (favorites->get_current() >= 0) {
@ -1353,6 +1384,24 @@ void EditorFileDialog::_make_dir() {
makedirname->grab_focus(); 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<int>(FileSortOption::FILE_SORT_MAX); i++) {
file_sort_button->get_popup()->set_item_checked(i, (i == p_id));
}
file_sort = static_cast<FileSortOption>(p_id);
invalidate();
}
void EditorFileDialog::_delete_items() { void EditorFileDialog::_delete_items() {
// Collect the selected folders and files to delete and check them in the deletion dependency dialog. // Collect the selected folders and files to delete and check them in the deletion dependency dialog.
Vector<String> folders; Vector<String> folders;
@ -1444,6 +1493,10 @@ void EditorFileDialog::_update_icons() {
show_hidden->set_icon(theme_cache.toggle_hidden); show_hidden->set_icon(theme_cache.toggle_hidden);
makedir->set_icon(theme_cache.create_folder); 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_up->set_icon(theme_cache.favorites_up);
fav_down->set_icon(theme_cache.favorites_down); 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. // 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", ED_SHORTCUT_OVERRIDE_ARRAY("file_dialog/focus_path", "macos",
{ int32_t(KeyModifierMask::META | Key::L), int32_t(KeyModifierMask::META | KeyModifierMask::SHIFT | Key::G) }); { 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_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); 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); VBoxContainer *list_vb = memnew(VBoxContainer);
list_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); 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 = memnew(Label(TTR("Directories & Files:")));
l->set_theme_type_variation("HeaderSmall"); 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); 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<int>(FileSortOption::FILE_SORT_NAME));
p->add_radio_check_item(TTR("Sort by Name (Descending)"), static_cast<int>(FileSortOption::FILE_SORT_NAME_REVERSE));
p->add_radio_check_item(TTR("Sort by Type (Ascending)"), static_cast<int>(FileSortOption::FILE_SORT_TYPE));
p->add_radio_check_item(TTR("Sort by Type (Descending)"), static_cast<int>(FileSortOption::FILE_SORT_TYPE_REVERSE));
p->add_radio_check_item(TTR("Sort by Last Modified"), static_cast<int>(FileSortOption::FILE_SORT_MODIFIED_TIME));
p->add_radio_check_item(TTR("Sort by First Modified"), static_cast<int>(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 (files and folders) list with context menu.
item_list = memnew(ItemList); item_list = memnew(ItemList);

View File

@ -32,13 +32,15 @@
#define EDITOR_FILE_DIALOG_H #define EDITOR_FILE_DIALOG_H
#include "core/io/dir_access.h" #include "core/io/dir_access.h"
#include "editor/file_info.h"
#include "scene/gui/dialogs.h" #include "scene/gui/dialogs.h"
#include "scene/property_list_helper.h" #include "scene/property_list_helper.h"
class GridContainer;
class DependencyRemoveDialog; class DependencyRemoveDialog;
class GridContainer;
class HSplitContainer; class HSplitContainer;
class ItemList; class ItemList;
class MenuButton;
class OptionButton; class OptionButton;
class PopupMenu; class PopupMenu;
class TextureRect; class TextureRect;
@ -127,6 +129,11 @@ private:
Button *favorite = nullptr; Button *favorite = nullptr;
Button *show_hidden = 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_up = nullptr;
Button *fav_down = nullptr; Button *fav_down = nullptr;
@ -165,6 +172,9 @@ private:
Ref<Texture2D> favorites_up; Ref<Texture2D> favorites_up;
Ref<Texture2D> favorites_down; Ref<Texture2D> favorites_down;
Ref<Texture2D> filter_box;
Ref<Texture2D> file_sort_button;
Ref<Texture2D> folder; Ref<Texture2D> folder;
Color folder_icon_color; Color folder_icon_color;
@ -227,6 +237,10 @@ private:
void _make_dir(); void _make_dir();
void _make_dir_confirm(); 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_items();
void _delete_files_global(); void _delete_files_global();