Skip to content

Commit 1e50e05

Browse files
committed
Merge pull request #105723 from KoBeWi/sortatron
Add file sort to FileDialog
2 parents b639c0c + 33dcd7a commit 1e50e05

File tree

5 files changed

+203
-45
lines changed

5 files changed

+203
-45
lines changed

doc/classes/FileDialog.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@
255255
<theme_item name="reload" data_type="icon" type="Texture2D">
256256
Custom icon for the reload button.
257257
</theme_item>
258+
<theme_item name="sort" data_type="icon" type="Texture2D">
259+
Custom icon for the sorting options menu.
260+
</theme_item>
258261
<theme_item name="toggle_filename_filter" data_type="icon" type="Texture2D">
259262
Custom icon for the toggle button for the filter for file names.
260263
</theme_item>

scene/gui/file_dialog.cpp

Lines changed: 138 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
#include "scene/gui/item_list.h"
4141
#include "scene/gui/label.h"
4242
#include "scene/gui/line_edit.h"
43+
#include "scene/gui/menu_button.h"
4344
#include "scene/gui/option_button.h"
45+
#include "scene/gui/separator.h"
4446
#include "scene/theme/theme_db.h"
4547

4648
void FileDialog::popup_file_dialog() {
@@ -267,6 +269,7 @@ void FileDialog::_notification(int p_what) {
267269
_setup_button(show_hidden, theme_cache.toggle_hidden);
268270
_setup_button(make_dir_button, theme_cache.create_folder);
269271
_setup_button(show_filename_filter_button, theme_cache.toggle_filename_filter);
272+
_setup_button(file_sort_button, theme_cache.sort);
270273
invalidate();
271274
} break;
272275

@@ -779,17 +782,14 @@ void FileDialog::update_file_list() {
779782
item = dir_access->get_next();
780783
}
781784

782-
dirs.sort_custom<FileNoCaseComparator>();
783-
files.sort_custom<FileNoCaseComparator>();
784-
785785
String filename_filter_lower = file_name_filter.to_lower();
786786

787787
List<String> patterns;
788-
// build filter
788+
// Build filter.
789789
if (filter->get_selected() == filter->get_item_count() - 1) {
790-
// match all
790+
// Match all.
791791
} else if (filters.size() > 1 && filter->get_selected() == 0) {
792-
// match all filters
792+
// Match all filters.
793793
for (int i = 0; i < filters.size(); i++) {
794794
String f = filters[i].get_slicec(';', 0);
795795
for (int j = 0; j < f.get_slice_count(","); j++) {
@@ -810,6 +810,10 @@ void FileDialog::update_file_list() {
810810
}
811811
}
812812

813+
LocalVector<DirInfo> filtered_dirs;
814+
filtered_dirs.reserve(dirs.size());
815+
const String base_dir = dir_access->get_current_dir();
816+
813817
for (const String &dir_name : dirs) {
814818
bool bundle = dir_access->is_bundle(dir_name);
815819
bool found = true;
@@ -825,18 +829,39 @@ void FileDialog::update_file_list() {
825829
}
826830

827831
if (found && (filename_filter_lower.is_empty() || dir_name.to_lower().contains(filename_filter_lower))) {
828-
file_list->add_item(dir_name, theme_cache.folder);
829-
file_list->set_item_icon_modulate(-1, theme_cache.folder_icon_color);
830-
831-
Dictionary d;
832-
d["name"] = dir_name;
833-
d["dir"] = !bundle;
834-
d["bundle"] = bundle;
835-
file_list->set_item_metadata(-1, d);
832+
DirInfo di;
833+
di.name = dir_name;
834+
di.bundle = bundle;
835+
if (file_sort == FileSortOption::MODIFIED_TIME || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
836+
di.modified_time = FileAccess::get_modified_time(base_dir.path_join(dir_name));
837+
}
838+
filtered_dirs.push_back(di);
836839
}
837840
}
838841

839-
String base_dir = dir_access->get_current_dir();
842+
if (file_sort == FileSortOption::MODIFIED_TIME || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
843+
filtered_dirs.sort_custom<DirInfo::TimeComparator>();
844+
} else {
845+
filtered_dirs.sort_custom<DirInfo::NameComparator>();
846+
}
847+
848+
if (file_sort == FileSortOption::NAME_REVERSE || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
849+
filtered_dirs.reverse();
850+
}
851+
852+
for (const DirInfo &info : filtered_dirs) {
853+
file_list->add_item(info.name, theme_cache.folder);
854+
file_list->set_item_icon_modulate(-1, theme_cache.folder_icon_color);
855+
856+
Dictionary d;
857+
d["name"] = info.name;
858+
d["dir"] = !info.bundle;
859+
d["bundle"] = info.bundle;
860+
file_list->set_item_metadata(-1, d);
861+
}
862+
863+
LocalVector<FileInfo> filtered_files;
864+
filtered_files.reserve(files.size());
840865

841866
for (const String &filename : files) {
842867
bool match = patterns.is_empty();
@@ -851,22 +876,57 @@ void FileDialog::update_file_list() {
851876
}
852877

853878
if (match && (filename_filter_lower.is_empty() || filename.to_lower().contains(filename_filter_lower))) {
854-
const Ref<Texture2D> icon = get_icon_func ? get_icon_func(base_dir.path_join(filename)) : theme_cache.file;
855-
file_list->add_item(filename, icon);
856-
file_list->set_item_icon_modulate(-1, theme_cache.file_icon_color);
857-
858-
if (mode == FILE_MODE_OPEN_DIR) {
859-
file_list->set_item_disabled(-1, true);
860-
}
861-
Dictionary d;
862-
d["name"] = filename;
863-
d["dir"] = false;
864-
d["bundle"] = false;
865-
file_list->set_item_metadata(-1, d);
866-
867-
if (filename_edit->get_text() == filename || match_str == filename) {
868-
file_list->select(file_list->get_item_count() - 1);
879+
FileInfo fi;
880+
fi.name = filename;
881+
fi.match_string = match_str;
882+
883+
// Only assign sorting fields when needed.
884+
if (file_sort == FileSortOption::TYPE || file_sort == FileSortOption::TYPE_REVERSE) {
885+
fi.type_sort = filename.get_extension() + filename.get_basename();
886+
} else if (file_sort == FileSortOption::MODIFIED_TIME || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
887+
fi.modified_time = FileAccess::get_modified_time(base_dir.path_join(filename));
869888
}
889+
filtered_files.push_back(fi);
890+
}
891+
}
892+
893+
switch (file_sort) {
894+
case FileSortOption::NAME:
895+
case FileSortOption::NAME_REVERSE:
896+
filtered_files.sort_custom<FileInfo::NameComparator>();
897+
break;
898+
case FileSortOption::TYPE:
899+
case FileSortOption::TYPE_REVERSE:
900+
filtered_files.sort_custom<FileInfo::TypeComparator>();
901+
break;
902+
case FileSortOption::MODIFIED_TIME:
903+
case FileSortOption::MODIFIED_TIME_REVERSE:
904+
filtered_files.sort_custom<FileInfo::TimeComparator>();
905+
break;
906+
default:
907+
ERR_PRINT(vformat("Invalid FileDialog sort option: %d", int(file_sort)));
908+
}
909+
910+
if (file_sort == FileSortOption::NAME_REVERSE || file_sort == FileSortOption::TYPE_REVERSE || file_sort == FileSortOption::MODIFIED_TIME_REVERSE) {
911+
filtered_files.reverse();
912+
}
913+
914+
for (const FileInfo &info : filtered_files) {
915+
const Ref<Texture2D> icon = get_icon_func ? get_icon_func(base_dir.path_join(info.name)) : theme_cache.file;
916+
file_list->add_item(info.name, icon);
917+
file_list->set_item_icon_modulate(-1, theme_cache.file_icon_color);
918+
919+
if (mode == FILE_MODE_OPEN_DIR) {
920+
file_list->set_item_disabled(-1, true);
921+
}
922+
Dictionary d;
923+
d["name"] = info.name;
924+
d["dir"] = false;
925+
d["bundle"] = false;
926+
file_list->set_item_metadata(-1, d);
927+
928+
if (filename_edit->get_text() == info.name || info.match_string == info.name) {
929+
file_list->select(file_list->get_item_count() - 1);
870930
}
871931
}
872932

@@ -1314,6 +1374,14 @@ void FileDialog::_update_drives(bool p_select) {
13141374
}
13151375
}
13161376

1377+
void FileDialog::_sort_option_selected(int p_option) {
1378+
for (int i = 0; i < int(FileSortOption::MAX); i++) {
1379+
file_sort_button->get_popup()->set_item_checked(i, (i == p_option));
1380+
}
1381+
file_sort = FileSortOption(p_option);
1382+
invalidate();
1383+
}
1384+
13171385
TypedArray<Dictionary> FileDialog::_get_options() const {
13181386
TypedArray<Dictionary> out;
13191387
for (const FileDialog::Option &opt : options) {
@@ -1563,6 +1631,7 @@ void FileDialog::_bind_methods() {
15631631
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_filename_filter);
15641632
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, file);
15651633
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, create_folder);
1634+
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, sort);
15661635

15671636
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, folder_icon_color);
15681637
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, file_icon_color);
@@ -1706,36 +1775,61 @@ FileDialog::FileDialog() {
17061775
top_toolbar->add_child(refresh_button);
17071776
refresh_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::update_file_list));
17081777

1778+
top_toolbar->add_child(memnew(VSeparator));
1779+
1780+
make_dir_button = memnew(Button);
1781+
make_dir_button->set_theme_type_variation(SceneStringName(FlatButton));
1782+
make_dir_button->set_accessibility_name(ETR("Create New Folder"));
1783+
make_dir_button->set_tooltip_text(ETR("Create a new folder."));
1784+
top_toolbar->add_child(make_dir_button);
1785+
make_dir_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir));
1786+
1787+
HBoxContainer *lower_toolbar = memnew(HBoxContainer);
1788+
main_vbox->add_child(lower_toolbar);
1789+
1790+
{
1791+
Label *label = memnew(Label(ETR("Directories & Files:")));
1792+
label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1793+
label->set_theme_type_variation("HeaderSmall");
1794+
lower_toolbar->add_child(label);
1795+
}
1796+
17091797
show_hidden = memnew(Button);
17101798
show_hidden->set_theme_type_variation(SceneStringName(FlatButton));
17111799
show_hidden->set_toggle_mode(true);
17121800
show_hidden->set_pressed(is_showing_hidden_files());
17131801
show_hidden->set_accessibility_name(ETR("Show Hidden Files"));
17141802
show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files."));
1715-
top_toolbar->add_child(show_hidden);
1803+
lower_toolbar->add_child(show_hidden);
17161804
show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files));
17171805

1806+
lower_toolbar->add_child(memnew(VSeparator));
1807+
17181808
show_filename_filter_button = memnew(Button);
17191809
show_filename_filter_button->set_theme_type_variation(SceneStringName(FlatButton));
17201810
show_filename_filter_button->set_toggle_mode(true);
17211811
show_filename_filter_button->set_pressed(false);
17221812
show_filename_filter_button->set_accessibility_name(ETR("Filter File Names"));
17231813
show_filename_filter_button->set_tooltip_text(ETR("Toggle the visibility of the filter for file names."));
1724-
top_toolbar->add_child(show_filename_filter_button);
1814+
lower_toolbar->add_child(show_filename_filter_button);
17251815
show_filename_filter_button->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_filename_filter));
17261816

1727-
make_dir_button = memnew(Button);
1728-
make_dir_button->set_theme_type_variation(SceneStringName(FlatButton));
1729-
make_dir_button->set_accessibility_name(ETR("Create New Folder"));
1730-
make_dir_button->set_tooltip_text(ETR("Create a new folder."));
1731-
top_toolbar->add_child(make_dir_button);
1732-
make_dir_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir));
1733-
1734-
{
1735-
Label *label = memnew(Label(ETR("Directories & Files:")));
1736-
label->set_theme_type_variation("HeaderSmall");
1737-
main_vbox->add_child(label);
1738-
}
1817+
file_sort_button = memnew(MenuButton);
1818+
file_sort_button->set_flat(false);
1819+
file_sort_button->set_theme_type_variation("FlatMenuButton");
1820+
file_sort_button->set_tooltip_text(ETR("Sort files"));
1821+
file_sort_button->set_accessibility_name(ETR("Sort Files"));
1822+
1823+
PopupMenu *sort_menu = file_sort_button->get_popup();
1824+
sort_menu->add_radio_check_item(ETR("Sort by Name (Ascending)"), int(FileSortOption::NAME));
1825+
sort_menu->add_radio_check_item(ETR("Sort by Name (Descending)"), int(FileSortOption::NAME_REVERSE));
1826+
sort_menu->add_radio_check_item(ETR("Sort by Type (Ascending)"), int(FileSortOption::TYPE));
1827+
sort_menu->add_radio_check_item(ETR("Sort by Type (Descending)"), int(FileSortOption::TYPE_REVERSE));
1828+
sort_menu->add_radio_check_item(ETR("Sort by Modified Time (Newest First)"), int(FileSortOption::MODIFIED_TIME));
1829+
sort_menu->add_radio_check_item(ETR("Sort by Modified Time (Oldest First)"), int(FileSortOption::MODIFIED_TIME_REVERSE));
1830+
sort_menu->set_item_checked(0, true);
1831+
lower_toolbar->add_child(file_sort_button);
1832+
sort_menu->connect(SceneStringName(id_pressed), callable_mp(this, &FileDialog::_sort_option_selected));
17391833

17401834
file_list = memnew(ItemList);
17411835
file_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);

scene/gui/file_dialog.h

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class GridContainer;
3939
class HBoxContainer;
4040
class ItemList;
4141
class LineEdit;
42+
class MenuButton;
4243
class OptionButton;
4344
class PopupMenu;
4445
class VBoxContainer;
@@ -52,6 +53,59 @@ class FileDialog : public ConfirmationDialog {
5253
int default_idx = 0;
5354
};
5455

56+
struct DirInfo {
57+
String name;
58+
uint64_t modified_time = 0;
59+
bool bundle = false;
60+
61+
struct NameComparator {
62+
bool operator()(const DirInfo &p_a, const DirInfo &p_b) const {
63+
return FileNoCaseComparator()(p_a.name, p_b.name);
64+
}
65+
};
66+
67+
struct TimeComparator {
68+
bool operator()(const DirInfo &p_a, const DirInfo &p_b) const {
69+
return p_a.modified_time > p_b.modified_time;
70+
}
71+
};
72+
};
73+
74+
struct FileInfo {
75+
String name;
76+
String match_string;
77+
String type_sort;
78+
uint64_t modified_time = 0;
79+
80+
struct NameComparator {
81+
bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
82+
return FileNoCaseComparator()(p_a.name, p_b.name);
83+
}
84+
};
85+
86+
struct TypeComparator {
87+
bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
88+
return FileNoCaseComparator()(p_a.type_sort, p_b.type_sort);
89+
}
90+
};
91+
92+
struct TimeComparator {
93+
bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
94+
return p_a.modified_time > p_b.modified_time;
95+
}
96+
};
97+
};
98+
99+
enum class FileSortOption {
100+
NAME,
101+
NAME_REVERSE,
102+
TYPE,
103+
TYPE_REVERSE,
104+
MODIFIED_TIME,
105+
MODIFIED_TIME_REVERSE,
106+
MAX
107+
};
108+
55109
public:
56110
enum Access {
57111
ACCESS_RESOURCES,
@@ -90,6 +144,7 @@ class FileDialog : public ConfirmationDialog {
90144

91145
Access access = ACCESS_RESOURCES;
92146
FileMode mode = FILE_MODE_SAVE_FILE;
147+
FileSortOption file_sort = FileSortOption::NAME;
93148
Ref<DirAccess> dir_access;
94149

95150
Vector<String> filters;
@@ -124,9 +179,10 @@ class FileDialog : public ConfirmationDialog {
124179
HBoxContainer *shortcuts_container = nullptr;
125180

126181
Button *refresh_button = nullptr;
182+
Button *make_dir_button = nullptr;
127183
Button *show_hidden = nullptr;
128184
Button *show_filename_filter_button = nullptr;
129-
Button *make_dir_button = nullptr;
185+
MenuButton *file_sort_button = nullptr;
130186

131187
ItemList *file_list = nullptr;
132188
Label *message = nullptr;
@@ -158,6 +214,7 @@ class FileDialog : public ConfirmationDialog {
158214
Ref<Texture2D> folder;
159215
Ref<Texture2D> file;
160216
Ref<Texture2D> create_folder;
217+
Ref<Texture2D> sort;
161218

162219
Color folder_icon_color;
163220
Color file_icon_color;
@@ -206,6 +263,7 @@ class FileDialog : public ConfirmationDialog {
206263

207264
void _change_dir(const String &p_new_dir);
208265
void _update_drives(bool p_select = true);
266+
void _sort_option_selected(int p_option);
209267

210268
void _invalidate();
211269
void _setup_button(Button *p_button, const Ref<Texture2D> &p_icon);

scene/theme/default_theme.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
695695
theme->set_icon("folder", "FileDialog", icons["folder"]);
696696
theme->set_icon("file", "FileDialog", icons["file"]);
697697
theme->set_icon("create_folder", "FileDialog", icons["folder_create"]);
698+
theme->set_icon("sort", "FileDialog", icons["sort"]);
699+
698700
theme->set_color("folder_icon_color", "FileDialog", Color(1, 1, 1));
699701
theme->set_color("file_icon_color", "FileDialog", Color(1, 1, 1));
700702
theme->set_color("file_disabled_color", "FileDialog", Color(1, 1, 1, 0.25));

scene/theme/icons/sort.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)