diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index 428063e6dbed..f530c107273b 100644 --- a/editor/project_manager/project_list.cpp +++ b/editor/project_manager/project_list.cpp @@ -41,8 +41,10 @@ #include "editor/project_manager/project_tag.h" #include "editor/themes/editor_scale.h" #include "scene/gui/button.h" +#include "scene/gui/dialogs.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/progress_bar.h" #include "scene/gui/texture_button.h" #include "scene/gui/texture_rect.h" #include "scene/resources/image_texture.h" @@ -358,7 +360,7 @@ bool ProjectList::project_feature_looks_like_version(const String &p_feature) { void ProjectList::_notification(int p_what) { switch (p_what) { case NOTIFICATION_PROCESS: { - // Load icons as a coroutine to speed up launch when you have hundreds of projects + // Load icons as a coroutine to speed up launch when you have hundreds of projects. if (_icon_load_index < _projects.size()) { Item &item = _projects.write[_icon_load_index]; if (item.control->should_load_project_icon()) { @@ -366,13 +368,62 @@ void ProjectList::_notification(int p_what) { } _icon_load_index++; + // Scan directories in thread to avoid blocking the window. + } else if (scan_data && scan_data->scan_in_progress.is_set()) { + // Wait for the thread. } else { set_process(false); + if (scan_data) { + _scan_finished(); + } } } break; } } +// Projects scan. + +void ProjectList::_scan_thread(void *p_scan_data) { + ScanData *scan_data = static_cast(p_scan_data); + + for (const String &base_path : scan_data->paths_to_scan) { + print_verbose(vformat("Scanning for projects in \"%s\".", base_path)); + _scan_folder_recursive(base_path, &scan_data->found_projects, scan_data->scan_in_progress); + + if (!scan_data->scan_in_progress.is_set()) { + print_verbose("Scan aborted."); + break; + } + } + print_verbose(vformat("Found %d project(s).", scan_data->found_projects.size())); + scan_data->scan_in_progress.clear(); +} + +void ProjectList::_scan_finished() { + if (scan_data->scan_in_progress.is_set()) { + // Abort scanning. + scan_data->scan_in_progress.clear(); + } + + scan_data->thread->wait_to_finish(); + memdelete(scan_data->thread); + if (scan_progress) { + scan_progress->hide(); + } + + for (const String &E : scan_data->found_projects) { + add_project(E, false); + } + memdelete(scan_data); + scan_data = nullptr; + + save_config(); + + if (ProjectManager::get_singleton()->is_initialized()) { + update_project_list(); + } +} + // Initialization & loading. void ProjectList::_migrate_config() { @@ -624,25 +675,39 @@ void ProjectList::find_projects(const String &p_path) { } void ProjectList::find_projects_multiple(const PackedStringArray &p_paths) { - List projects; + if (!scan_progress && is_inside_tree()) { + scan_progress = memnew(AcceptDialog); + scan_progress->set_title(TTR("Scanning")); + scan_progress->set_ok_button_text(TTR("Cancel")); - for (int i = 0; i < p_paths.size(); i++) { - const String &base_path = p_paths.get(i); - print_verbose(vformat("Scanning for projects in \"%s\".", base_path)); + VBoxContainer *vb = memnew(VBoxContainer); + scan_progress->add_child(vb); - _scan_folder_recursive(base_path, &projects); - print_verbose(vformat("Found %d project(s).", projects.size())); - } + Label *label = memnew(Label); + label->set_text(TTR("Scanning for projects...")); + vb->add_child(label); - for (const String &E : projects) { - add_project(E, false); + ProgressBar *progress = memnew(ProgressBar); + progress->set_indeterminate(true); + vb->add_child(progress); + + add_child(scan_progress); + scan_progress->connect(SceneStringName(confirmed), callable_mp(this, &ProjectList::_scan_finished)); + scan_progress->connect("canceled", callable_mp(this, &ProjectList::_scan_finished)); } - save_config(); + scan_data = memnew(ScanData); + scan_data->paths_to_scan = p_paths; + scan_data->scan_in_progress.set(); - if (ProjectManager::get_singleton()->is_initialized()) { - update_project_list(); + scan_data->thread = memnew(Thread); + scan_data->thread->start(_scan_thread, scan_data); + + if (scan_progress) { + scan_progress->reset_size(); + scan_progress->popup_centered(); } + set_process(true); } void ProjectList::load_project_list() { @@ -656,7 +721,11 @@ void ProjectList::load_project_list() { } } -void ProjectList::_scan_folder_recursive(const String &p_path, List *r_projects) { +void ProjectList::_scan_folder_recursive(const String &p_path, List *r_projects, const SafeFlag &p_scan_active) { + if (!p_scan_active.is_set()) { + return; + } + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); Error error = da->change_dir(p_path); ERR_FAIL_COND_MSG(error != OK, vformat("Failed to open the path \"%s\" for scanning (code %d).", p_path, error)); @@ -664,8 +733,12 @@ void ProjectList::_scan_folder_recursive(const String &p_path, List *r_p da->list_dir_begin(); String n = da->get_next(); while (!n.is_empty()) { + if (!p_scan_active.is_set()) { + return; + } + if (da->current_is_dir() && n[0] != '.') { - _scan_folder_recursive(da->get_current_dir().path_join(n), r_projects); + _scan_folder_recursive(da->get_current_dir().path_join(n), r_projects, p_scan_active); } else if (n == "project.godot") { r_projects->push_back(da->get_current_dir()); } diff --git a/editor/project_manager/project_list.h b/editor/project_manager/project_list.h index 6e0f5830ac31..7a77974d4aba 100644 --- a/editor/project_manager/project_list.h +++ b/editor/project_manager/project_list.h @@ -35,6 +35,7 @@ #include "scene/gui/box_container.h" #include "scene/gui/scroll_container.h" +class AcceptDialog; class Button; class Label; class ProjectList; @@ -176,6 +177,20 @@ class ProjectList : public ScrollContainer { VBoxContainer *project_list_vbox = nullptr; + // Projects scan. + + struct ScanData { + Thread *thread = nullptr; + PackedStringArray paths_to_scan; + List found_projects; + SafeFlag scan_in_progress; + }; + ScanData *scan_data = nullptr; + AcceptDialog *scan_progress = nullptr; + + static void _scan_thread(void *p_scan_data); + void _scan_finished(); + // Initialization & loading. void _migrate_config(); @@ -186,7 +201,7 @@ class ProjectList : public ScrollContainer { // Project list updates. - void _scan_folder_recursive(const String &p_path, List *r_projects); + static void _scan_folder_recursive(const String &p_path, List *r_projects, const SafeFlag &p_scan_active); // Project list items. diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 90ce01e38377..baf0c394ac93 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -61,7 +61,7 @@ void ProgressBar::_notification(int p_what) { Size2 size = get_size(); real_t fill_size = MIN(size.width, size.height) * 2; - if (Engine::get_singleton()->is_editor_hint() && !editor_preview_indeterminate) { + if (is_part_of_edited_scene() && !editor_preview_indeterminate) { // Center the filled bar when we're not previewing the animation. _inderminate_fill_progress = (MAX(size.width, size.height) / 2) + (fill_size / 2); } @@ -217,7 +217,7 @@ void ProgressBar::set_indeterminate(bool p_indeterminate) { indeterminate = p_indeterminate; _inderminate_fill_progress = 0; - bool should_process = !Engine::get_singleton()->is_editor_hint() || editor_preview_indeterminate; + bool should_process = !is_part_of_edited_scene() || editor_preview_indeterminate; set_process_internal(indeterminate && should_process); notify_property_list_changed(); @@ -235,7 +235,7 @@ void ProgressBar::set_editor_preview_indeterminate(bool p_preview_indeterminate) } editor_preview_indeterminate = p_preview_indeterminate; - if (Engine::get_singleton()->is_editor_hint()) { + if (is_part_of_edited_scene()) { _inderminate_fill_progress = 0; set_process_internal(indeterminate && editor_preview_indeterminate); queue_redraw();