|
@@ -43,6 +43,7 @@
|
|
|
#include "scene/gui/menu_button.h"
|
|
|
#include "scene/gui/option_button.h"
|
|
|
#include "scene/gui/separator.h"
|
|
|
+#include "scene/gui/split_container.h"
|
|
|
#include "scene/theme/theme_db.h"
|
|
|
|
|
|
void FileDialog::popup_file_dialog() {
|
|
@@ -249,11 +250,13 @@ void FileDialog::_notification(int p_what) {
|
|
|
} break;
|
|
|
|
|
|
case NOTIFICATION_VISIBILITY_CHANGED: {
|
|
|
- if (!is_visible()) {
|
|
|
+ if (is_visible()) {
|
|
|
+ _update_favorite_list();
|
|
|
+ _update_recent_list();
|
|
|
+ invalidate(); // Put it here to preview in the editor.
|
|
|
+ } else {
|
|
|
set_process_shortcut_input(false);
|
|
|
}
|
|
|
-
|
|
|
- invalidate(); // Put it here to preview in the editor.
|
|
|
} break;
|
|
|
|
|
|
case NOTIFICATION_THEME_CHANGED: {
|
|
@@ -266,10 +269,13 @@ void FileDialog::_notification(int p_what) {
|
|
|
}
|
|
|
_setup_button(dir_up, theme_cache.parent_folder);
|
|
|
_setup_button(refresh_button, theme_cache.reload);
|
|
|
+ _setup_button(favorite_button, theme_cache.favorite);
|
|
|
_setup_button(show_hidden, theme_cache.toggle_hidden);
|
|
|
_setup_button(make_dir_button, theme_cache.create_folder);
|
|
|
_setup_button(show_filename_filter_button, theme_cache.toggle_filename_filter);
|
|
|
_setup_button(file_sort_button, theme_cache.sort);
|
|
|
+ _setup_button(fav_up_button, theme_cache.favorite_up);
|
|
|
+ _setup_button(fav_down_button, theme_cache.favorite_down);
|
|
|
invalidate();
|
|
|
} break;
|
|
|
|
|
@@ -400,6 +406,8 @@ void FileDialog::_file_submitted(const String &p_file) {
|
|
|
}
|
|
|
|
|
|
void FileDialog::_save_confirm_pressed() {
|
|
|
+ _save_to_recent();
|
|
|
+
|
|
|
String f = dir_access->get_current_dir().path_join(filename_edit->get_text());
|
|
|
emit_signal(SNAME("file_selected"), f);
|
|
|
hide();
|
|
@@ -443,6 +451,7 @@ void FileDialog::_action_pressed() {
|
|
|
if (mode == FILE_MODE_OPEN_FILES) {
|
|
|
const Vector<String> files = get_selected_files();
|
|
|
if (!files.is_empty()) {
|
|
|
+ _save_to_recent();
|
|
|
emit_signal(SNAME("files_selected"), files);
|
|
|
hide();
|
|
|
}
|
|
@@ -453,6 +462,7 @@ void FileDialog::_action_pressed() {
|
|
|
String f = file_text.is_absolute_path() ? file_text : dir_access->get_current_dir().path_join(file_text);
|
|
|
|
|
|
if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && (dir_access->file_exists(f) || dir_access->is_bundle(f))) {
|
|
|
+ _save_to_recent();
|
|
|
emit_signal(SNAME("file_selected"), f);
|
|
|
hide();
|
|
|
} else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) {
|
|
@@ -467,6 +477,7 @@ void FileDialog::_action_pressed() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ _save_to_recent();
|
|
|
emit_signal(SNAME("dir_selected"), path);
|
|
|
hide();
|
|
|
}
|
|
@@ -528,6 +539,7 @@ void FileDialog::_action_pressed() {
|
|
|
confirm_save->set_text(vformat(atr(ETR("File \"%s\" already exists.\nDo you want to overwrite it?")), f));
|
|
|
confirm_save->popup_centered(Size2(250, 80));
|
|
|
} else {
|
|
|
+ _save_to_recent();
|
|
|
emit_signal(SNAME("file_selected"), f);
|
|
|
hide();
|
|
|
}
|
|
@@ -937,6 +949,21 @@ void FileDialog::update_file_list() {
|
|
|
_file_list_select_first();
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ favorite_list->deselect_all();
|
|
|
+ favorite_button->set_pressed(false);
|
|
|
+
|
|
|
+ const int fav_count = favorite_list->get_item_count();
|
|
|
+ for (int i = 0; i < fav_count; i++) {
|
|
|
+ const String fav_dir = favorite_list->get_item_metadata(i);
|
|
|
+ if (fav_dir != base_dir && fav_dir != base_dir + "/") {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ favorite_list->select(i);
|
|
|
+ favorite_button->set_pressed(true);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ _update_fav_buttons();
|
|
|
}
|
|
|
|
|
|
void FileDialog::_filter_selected(int) {
|
|
@@ -1274,6 +1301,8 @@ void FileDialog::set_access(Access p_access) {
|
|
|
invalidate();
|
|
|
update_filters();
|
|
|
update_dir();
|
|
|
+ _update_favorite_list();
|
|
|
+ _update_recent_list();
|
|
|
}
|
|
|
|
|
|
void FileDialog::invalidate() {
|
|
@@ -1382,6 +1411,223 @@ void FileDialog::_sort_option_selected(int p_option) {
|
|
|
invalidate();
|
|
|
}
|
|
|
|
|
|
+void FileDialog::_favorite_selected(int p_item) {
|
|
|
+ ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_item, global_favorites.size());
|
|
|
+ _change_dir(favorite_list->get_item_metadata(p_item));
|
|
|
+ _push_history();
|
|
|
+}
|
|
|
+
|
|
|
+void FileDialog::_favorite_pressed() {
|
|
|
+ String directory = get_current_dir();
|
|
|
+ if (!directory.ends_with("/")) {
|
|
|
+ directory += "/";
|
|
|
+ }
|
|
|
+
|
|
|
+ bool found = false;
|
|
|
+ for (const String &name : global_favorites) {
|
|
|
+ if (!_path_matches_access(name)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name == directory) {
|
|
|
+ found = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (found) {
|
|
|
+ global_favorites.erase(directory);
|
|
|
+ } else {
|
|
|
+ global_favorites.push_back(directory);
|
|
|
+ }
|
|
|
+ _update_favorite_list();
|
|
|
+}
|
|
|
+
|
|
|
+void FileDialog::_favorite_move_up() {
|
|
|
+ int current = favorite_list->get_current();
|
|
|
+ if (current <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ int a_idx = global_favorites.find(favorite_list->get_item_metadata(current - 1));
|
|
|
+ int b_idx = global_favorites.find(favorite_list->get_item_metadata(current));
|
|
|
+
|
|
|
+ if (a_idx == -1 || b_idx == -1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ SWAP(global_favorites[a_idx], global_favorites[b_idx]);
|
|
|
+ _update_favorite_list();
|
|
|
+}
|
|
|
+
|
|
|
+void FileDialog::_favorite_move_down() {
|
|
|
+ int current = favorite_list->get_current();
|
|
|
+ if (current == -1 || current >= favorite_list->get_item_count() - 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ int a_idx = global_favorites.find(favorite_list->get_item_metadata(current));
|
|
|
+ int b_idx = global_favorites.find(favorite_list->get_item_metadata(current + 1));
|
|
|
+
|
|
|
+ if (a_idx == -1 || b_idx == -1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ SWAP(global_favorites[a_idx], global_favorites[b_idx]);
|
|
|
+ _update_favorite_list();
|
|
|
+}
|
|
|
+
|
|
|
+void FileDialog::_update_favorite_list() {
|
|
|
+ const String current = get_current_dir();
|
|
|
+
|
|
|
+ favorite_list->clear();
|
|
|
+ favorite_button->set_pressed(false);
|
|
|
+
|
|
|
+ Vector<String> favorited_paths;
|
|
|
+ Vector<String> favorited_names;
|
|
|
+
|
|
|
+ int current_favorite = -1;
|
|
|
+ for (uint32_t i = 0; i < global_favorites.size(); i++) {
|
|
|
+ String name = global_favorites[i];
|
|
|
+ if (!_path_matches_access(name)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!name.ends_with("/") || !name.begins_with(root_prefix)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!dir_access->dir_exists(name)) {
|
|
|
+ // Remove invalid directory from the list of favorited directories.
|
|
|
+ global_favorites.remove_at(i);
|
|
|
+ i--;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (name == current) {
|
|
|
+ current_favorite = favorited_names.size();
|
|
|
+ }
|
|
|
+ favorited_paths.append(name);
|
|
|
+
|
|
|
+ // Compute favorite display text.
|
|
|
+ if (name == "res://" || name == "user://") {
|
|
|
+ name = "/";
|
|
|
+ } else {
|
|
|
+ if (current_favorite == -1 && name == current + "/") {
|
|
|
+ current_favorite = favorited_names.size();
|
|
|
+ }
|
|
|
+ name = name.trim_suffix("/");
|
|
|
+ name = name.get_file();
|
|
|
+ }
|
|
|
+ favorited_names.append(name);
|
|
|
+ }
|
|
|
+
|
|
|
+ // EditorNode::disambiguate_filenames(favorited_paths, favorited_names); // TODO Needs a non-editor method.
|
|
|
+
|
|
|
+ const int favorites_size = favorited_paths.size();
|
|
|
+ for (int i = 0; i < favorites_size; i++) {
|
|
|
+ favorite_list->add_item(favorited_names[i], theme_cache.folder);
|
|
|
+ favorite_list->set_item_tooltip(-1, favorited_paths[i]);
|
|
|
+ favorite_list->set_item_metadata(-1, favorited_paths[i]);
|
|
|
+
|
|
|
+ if (i == current_favorite) {
|
|
|
+ favorite_button->set_pressed(true);
|
|
|
+ favorite_list->set_current(favorite_list->get_item_count() - 1);
|
|
|
+ recent_list->deselect_all();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _update_fav_buttons();
|
|
|
+}
|
|
|
+
|
|
|
+void FileDialog::_update_fav_buttons() {
|
|
|
+ const int current = favorite_list->get_current();
|
|
|
+ fav_up_button->set_disabled(current < 1);
|
|
|
+ fav_down_button->set_disabled(current == -1 || current >= favorite_list->get_item_count() - 1);
|
|
|
+}
|
|
|
+
|
|
|
+void FileDialog::_recent_selected(int p_item) {
|
|
|
+ ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_item, global_recents.size());
|
|
|
+ _change_dir(recent_list->get_item_metadata(p_item));
|
|
|
+ _push_history();
|
|
|
+}
|
|
|
+
|
|
|
+void FileDialog::_save_to_recent() {
|
|
|
+ String directory = get_current_dir();
|
|
|
+ if (!directory.ends_with("/")) {
|
|
|
+ directory += "/";
|
|
|
+ }
|
|
|
+
|
|
|
+ int count = 0;
|
|
|
+ for (uint32_t i = 0; i < global_recents.size(); i++) {
|
|
|
+ const String &dir = global_recents[i];
|
|
|
+ if (!_path_matches_access(dir)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dir == directory || count > MAX_RECENTS) {
|
|
|
+ global_recents.remove_at(i);
|
|
|
+ i--;
|
|
|
+ } else {
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ global_recents.insert(0, directory);
|
|
|
+
|
|
|
+ _update_recent_list();
|
|
|
+}
|
|
|
+
|
|
|
+void FileDialog::_update_recent_list() {
|
|
|
+ recent_list->clear();
|
|
|
+
|
|
|
+ Vector<String> recent_dir_paths;
|
|
|
+ Vector<String> recent_dir_names;
|
|
|
+
|
|
|
+ for (uint32_t i = 0; i < global_recents.size(); i++) {
|
|
|
+ String name = global_recents[i];
|
|
|
+ if (!_path_matches_access(name)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!name.begins_with(root_prefix)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!dir_access->dir_exists(name)) {
|
|
|
+ // Remove invalid directory from the list of recent directories.
|
|
|
+ global_recents.remove_at(i);
|
|
|
+ i--;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ recent_dir_paths.append(name);
|
|
|
+
|
|
|
+ // Compute recent directory display text.
|
|
|
+ if (name == "res://" || name == "user://") {
|
|
|
+ name = "/";
|
|
|
+ } else {
|
|
|
+ name = name.trim_suffix("/").get_file();
|
|
|
+ }
|
|
|
+ recent_dir_names.append(name);
|
|
|
+ }
|
|
|
+
|
|
|
+ // EditorNode::disambiguate_filenames(recent_dir_paths, recent_dir_names); // TODO Needs a non-editor method.
|
|
|
+
|
|
|
+ const int recent_size = recent_dir_paths.size();
|
|
|
+ for (int i = 0; i < recent_size; i++) {
|
|
|
+ recent_list->add_item(recent_dir_names[i], theme_cache.folder);
|
|
|
+ recent_list->set_item_tooltip(-1, recent_dir_paths[i]);
|
|
|
+ recent_list->set_item_metadata(-1, recent_dir_paths[i]);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+bool FileDialog::_path_matches_access(const String &p_path) const {
|
|
|
+ bool is_res = p_path.begins_with("res://");
|
|
|
+ bool is_user = p_path.begins_with("user://");
|
|
|
+ if (access == ACCESS_RESOURCES) {
|
|
|
+ return is_res;
|
|
|
+ } else if (access == ACCESS_USERDATA) {
|
|
|
+ return is_user;
|
|
|
+ }
|
|
|
+ return !is_res && !is_user;
|
|
|
+}
|
|
|
+
|
|
|
TypedArray<Dictionary> FileDialog::_get_options() const {
|
|
|
TypedArray<Dictionary> out;
|
|
|
for (const FileDialog::Option &opt : options) {
|
|
@@ -1626,12 +1872,15 @@ void FileDialog::_bind_methods() {
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, forward_folder);
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, back_folder);
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, reload);
|
|
|
+ BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, favorite);
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_hidden);
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, folder);
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_filename_filter);
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, file);
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, create_folder);
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, sort);
|
|
|
+ BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, favorite_up);
|
|
|
+ BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, favorite_down);
|
|
|
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, folder_icon_color);
|
|
|
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, FileDialog, file_icon_color);
|
|
@@ -1775,6 +2024,13 @@ FileDialog::FileDialog() {
|
|
|
top_toolbar->add_child(refresh_button);
|
|
|
refresh_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::update_file_list));
|
|
|
|
|
|
+ favorite_button = memnew(Button);
|
|
|
+ favorite_button->set_theme_type_variation(SceneStringName(FlatButton));
|
|
|
+ favorite_button->set_accessibility_name(TTRC("(Un)favorite Folder"));
|
|
|
+ favorite_button->set_tooltip_text(TTRC("(Un)favorite current folder."));
|
|
|
+ top_toolbar->add_child(favorite_button);
|
|
|
+ favorite_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_favorite_pressed));
|
|
|
+
|
|
|
top_toolbar->add_child(memnew(VSeparator));
|
|
|
|
|
|
make_dir_button = memnew(Button);
|
|
@@ -1784,8 +2040,66 @@ FileDialog::FileDialog() {
|
|
|
top_toolbar->add_child(make_dir_button);
|
|
|
make_dir_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir));
|
|
|
|
|
|
+ HSplitContainer *main_split = memnew(HSplitContainer);
|
|
|
+ main_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
+ main_vbox->add_child(main_split);
|
|
|
+
|
|
|
+ {
|
|
|
+ VSplitContainer *fav_split = memnew(VSplitContainer);
|
|
|
+ main_split->add_child(fav_split);
|
|
|
+
|
|
|
+ VBoxContainer *fav_vbox = memnew(VBoxContainer);
|
|
|
+ fav_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
+ fav_split->add_child(fav_vbox);
|
|
|
+
|
|
|
+ HBoxContainer *fav_hbox = memnew(HBoxContainer);
|
|
|
+ fav_vbox->add_child(fav_hbox);
|
|
|
+
|
|
|
+ {
|
|
|
+ Label *label = memnew(Label(ETR("Favorites:")));
|
|
|
+ label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
+ fav_hbox->add_child(label);
|
|
|
+ }
|
|
|
+
|
|
|
+ fav_up_button = memnew(Button);
|
|
|
+ fav_up_button->set_theme_type_variation(SceneStringName(FlatButton));
|
|
|
+ fav_hbox->add_child(fav_up_button);
|
|
|
+ fav_up_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_favorite_move_up));
|
|
|
+
|
|
|
+ fav_down_button = memnew(Button);
|
|
|
+ fav_down_button->set_theme_type_variation(SceneStringName(FlatButton));
|
|
|
+ fav_hbox->add_child(fav_down_button);
|
|
|
+ fav_down_button->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_favorite_move_down));
|
|
|
+
|
|
|
+ favorite_list = memnew(ItemList);
|
|
|
+ favorite_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
+ favorite_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
|
|
+ favorite_list->set_accessibility_name(ETR("Favorites"));
|
|
|
+ fav_vbox->add_child(favorite_list);
|
|
|
+ favorite_list->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_favorite_selected));
|
|
|
+
|
|
|
+ VBoxContainer *recent_vbox = memnew(VBoxContainer);
|
|
|
+ recent_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
+ fav_split->add_child(recent_vbox);
|
|
|
+
|
|
|
+ {
|
|
|
+ Label *label = memnew(Label(ETR("Recent:")));
|
|
|
+ recent_vbox->add_child(label);
|
|
|
+ }
|
|
|
+
|
|
|
+ recent_list = memnew(ItemList);
|
|
|
+ recent_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
+ recent_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
|
|
+ recent_list->set_accessibility_name(ETR("Recent"));
|
|
|
+ recent_vbox->add_child(recent_list);
|
|
|
+ recent_list->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_recent_selected));
|
|
|
+ }
|
|
|
+
|
|
|
+ VBoxContainer *file_vbox = memnew(VBoxContainer);
|
|
|
+ main_split->add_child(file_vbox);
|
|
|
+
|
|
|
HBoxContainer *lower_toolbar = memnew(HBoxContainer);
|
|
|
- main_vbox->add_child(lower_toolbar);
|
|
|
+ file_vbox->add_child(lower_toolbar);
|
|
|
|
|
|
{
|
|
|
Label *label = memnew(Label(ETR("Directories & Files:")));
|
|
@@ -1836,7 +2150,7 @@ FileDialog::FileDialog() {
|
|
|
file_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
|
file_list->set_accessibility_name(ETR("Directories and Files"));
|
|
|
file_list->set_allow_rmb_select(true);
|
|
|
- main_vbox->add_child(file_list);
|
|
|
+ file_vbox->add_child(file_list);
|
|
|
file_list->connect("multi_selected", callable_mp(this, &FileDialog::_file_list_multi_selected));
|
|
|
file_list->connect("item_selected", callable_mp(this, &FileDialog::_file_list_selected));
|
|
|
file_list->connect("item_activated", callable_mp(this, &FileDialog::_file_list_item_activated));
|
|
@@ -1853,7 +2167,7 @@ FileDialog::FileDialog() {
|
|
|
|
|
|
filename_filter_box = memnew(HBoxContainer);
|
|
|
filename_filter_box->set_visible(false);
|
|
|
- main_vbox->add_child(filename_filter_box);
|
|
|
+ file_vbox->add_child(filename_filter_box);
|
|
|
|
|
|
{
|
|
|
Label *label = memnew(Label(ETR("Filter:")));
|
|
@@ -1871,7 +2185,7 @@ FileDialog::FileDialog() {
|
|
|
filename_filter->connect(SceneStringName(text_submitted), callable_mp(this, &FileDialog::_filename_filter_selected).unbind(1));
|
|
|
|
|
|
file_box = memnew(HBoxContainer);
|
|
|
- main_vbox->add_child(file_box);
|
|
|
+ file_vbox->add_child(file_box);
|
|
|
|
|
|
{
|
|
|
Label *label = memnew(Label(ETR("File:")));
|