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
4648void 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+
13171385TypedArray<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);
0 commit comments