feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -132,6 +132,13 @@ void ProjectDialog::_validate_path() {
ERR_FAIL_COND_MSG(ret != UNZ_OK, "Failed to get current file info.");
String name = String::utf8(fname);
// Skip the __MACOSX directory created by macOS's built-in file zipper.
if (name.begins_with("__MACOSX")) {
ret = unzGoToNextFile(pkg);
continue;
}
if (name.get_file() == "project.godot") {
break; // ret == UNZ_OK.
}
@ -162,7 +169,7 @@ void ProjectDialog::_validate_path() {
}
}
if (target_path.is_empty() || target_path.is_relative_path()) {
if (target_path.is_relative_path()) {
_set_message(TTR("The path specified is invalid."), MESSAGE_ERROR, target_path_input_type);
return;
}
@ -352,7 +359,7 @@ void ProjectDialog::_install_path_changed() {
void ProjectDialog::_browse_project_path() {
String path = project_path->get_text();
if (path.is_empty()) {
if (path.is_relative_path()) {
path = EDITOR_GET("filesystem/directories/default_project_path");
}
if (mode == MODE_IMPORT && install_path->is_visible_in_tree()) {
@ -382,12 +389,16 @@ void ProjectDialog::_browse_project_path() {
void ProjectDialog::_browse_install_path() {
ERR_FAIL_COND_MSG(mode != MODE_IMPORT, "Install path is only used for MODE_IMPORT.");
String path = install_path->get_text();
if (path.is_relative_path() || !DirAccess::dir_exists_absolute(path)) {
path = EDITOR_GET("filesystem/directories/default_project_path");
}
if (create_dir->is_pressed()) {
// Select parent directory of install path.
fdialog_install->set_current_dir(install_path->get_text().get_base_dir());
fdialog_install->set_current_dir(path.get_base_dir());
} else {
// Select install path.
fdialog_install->set_current_dir(install_path->get_text());
fdialog_install->set_current_dir(path);
}
fdialog_install->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
@ -438,6 +449,8 @@ void ProjectDialog::_renderer_selected() {
String renderer_type = renderer_button_group->get_pressed_button()->get_meta(SNAME("rendering_method"));
bool rd_error = false;
if (renderer_type == "forward_plus") {
renderer_info->set_text(
String::utf8("") + TTR("Supports desktop platforms only.") +
@ -445,6 +458,7 @@ void ProjectDialog::_renderer_selected() {
String::utf8("\n") + TTR("Can scale to large complex scenes.") +
String::utf8("\n") + TTR("Uses RenderingDevice backend.") +
String::utf8("\n") + TTR("Slower rendering of simple scenes."));
rd_error = !rendering_device_supported;
} else if (renderer_type == "mobile") {
renderer_info->set_text(
String::utf8("") + TTR("Supports desktop + mobile platforms.") +
@ -452,16 +466,24 @@ void ProjectDialog::_renderer_selected() {
String::utf8("\n") + TTR("Less scalable for complex scenes.") +
String::utf8("\n") + TTR("Uses RenderingDevice backend.") +
String::utf8("\n") + TTR("Fast rendering of simple scenes."));
rd_error = !rendering_device_supported;
} else if (renderer_type == "gl_compatibility") {
renderer_info->set_text(
String::utf8("") + TTR("Supports desktop, mobile + web platforms.") +
String::utf8("\n") + TTR("Least advanced 3D graphics (currently work-in-progress).") +
String::utf8("\n") + TTR("Least advanced 3D graphics.") +
String::utf8("\n") + TTR("Intended for low-end/older devices.") +
String::utf8("\n") + TTR("Uses OpenGL 3 backend (OpenGL 3.3/ES 3.0/WebGL2).") +
String::utf8("\n") + TTR("Fastest rendering of simple scenes."));
} else {
WARN_PRINT("Unknown renderer type. Please report this as a bug on GitHub.");
}
rd_not_supported->set_visible(rd_error);
get_ok_button()->set_disabled(rd_error);
if (rd_error) {
// Needs to be set here since theme colors aren't available at startup.
rd_not_supported->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
}
}
void ProjectDialog::_nonempty_confirmation_ok_pressed() {
@ -473,12 +495,14 @@ void ProjectDialog::ok_pressed() {
// Before we create a project, check that the target folder is empty.
// If not, we need to ask the user if they're sure they want to do this.
if (!is_folder_empty) {
ConfirmationDialog *cd = memnew(ConfirmationDialog);
cd->set_title(TTR("Warning: This folder is not empty"));
cd->set_text(TTR("You are about to create a Godot project in a non-empty folder.\nThe entire contents of this folder will be imported as project resources!\n\nAre you sure you wish to continue?"));
cd->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectDialog::_nonempty_confirmation_ok_pressed));
get_parent()->add_child(cd);
cd->popup_centered();
if (!nonempty_confirmation) {
nonempty_confirmation = memnew(ConfirmationDialog);
nonempty_confirmation->set_title(TTR("Warning: This folder is not empty"));
nonempty_confirmation->set_text(TTR("You are about to create a Godot project in a non-empty folder.\nThe entire contents of this folder will be imported as project resources!\n\nAre you sure you wish to continue?"));
nonempty_confirmation->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectDialog::_nonempty_confirmation_ok_pressed));
add_child(nonempty_confirmation);
}
nonempty_confirmation->popup_centered();
return;
}
@ -537,6 +561,21 @@ void ProjectDialog::ok_pressed() {
fa_icon->store_string(get_default_project_icon());
EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(vcs_metadata_selection->get_selected()), path);
// Ensures external editors and IDEs use UTF-8 encoding.
const String editor_config_path = path.path_join(".editorconfig");
Ref<FileAccess> f = FileAccess::open(editor_config_path, FileAccess::WRITE);
if (f.is_null()) {
// .editorconfig isn't so critical.
ERR_PRINT("Couldn't create .editorconfig in project path.");
} else {
f->store_line("root = true");
f->store_line("");
f->store_line("[*]");
f->store_line("charset = utf-8");
f->close();
FileAccess::set_hidden_attribute(editor_config_path, true);
}
}
// Two cases for importing a ZIP.
@ -572,6 +611,13 @@ void ProjectDialog::ok_pressed() {
ERR_FAIL_COND_MSG(ret != UNZ_OK, "Failed to get current file info.");
String name = String::utf8(fname);
// Skip the __MACOSX directory created by macOS's built-in file zipper.
if (name.begins_with("__MACOSX")) {
ret = unzGoToNextFile(pkg);
continue;
}
if (name.get_file() == "project.godot") {
zip_root = name.get_base_dir();
break;
@ -604,7 +650,15 @@ void ProjectDialog::ok_pressed() {
ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
ERR_FAIL_COND_MSG(ret != UNZ_OK, "Failed to get current file info.");
String rel_path = String::utf8(fname).trim_prefix(zip_root);
String name = String::utf8(fname);
// Skip the __MACOSX directory created by macOS's built-in file zipper.
if (name.begins_with("__MACOSX")) {
ret = unzGoToNextFile(pkg);
continue;
}
String rel_path = name.trim_prefix(zip_root);
if (rel_path.is_empty()) { // Root.
} else if (rel_path.ends_with("/")) { // Directory.
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@ -671,7 +725,7 @@ void ProjectDialog::ok_pressed() {
hide();
if (mode == MODE_NEW || mode == MODE_IMPORT || mode == MODE_INSTALL) {
emit_signal(SNAME("project_created"), path);
emit_signal(SNAME("project_created"), path, edit_check_box->is_pressed());
} else if (mode == MODE_RENAME) {
emit_signal(SNAME("projects_updated"));
}
@ -713,6 +767,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
create_dir->hide();
project_status_rect->hide();
project_browse->hide();
edit_check_box->hide();
name_container->show();
install_path_container->hide();
@ -728,6 +783,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
project_path->set_editable(true);
String fav_dir = EDITOR_GET("filesystem/directories/default_project_path");
fav_dir = fav_dir.simplify_path();
if (!fav_dir.is_empty()) {
project_path->set_text(fav_dir);
install_path->set_text(fav_dir);
@ -742,10 +798,11 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
create_dir->show();
project_status_rect->show();
project_browse->show();
edit_check_box->show();
if (mode == MODE_IMPORT) {
set_title(TTR("Import Existing Project"));
set_ok_button_text(TTR("Import & Edit"));
set_ok_button_text(TTR("Import"));
name_container->hide();
install_path_container->hide();
@ -755,7 +812,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
// Project path dialog is also opened; no need to change focus.
} else if (mode == MODE_NEW) {
set_title(TTR("Create New Project"));
set_ok_button_text(TTR("Create & Edit"));
set_ok_button_text(TTR("Create"));
name_container->show();
install_path_container->hide();
@ -766,7 +823,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
callable_mp(project_name, &LineEdit::select_all).call_deferred();
} else if (mode == MODE_INSTALL) {
set_title(TTR("Install Project:") + " " + zip_title);
set_ok_button_text(TTR("Install & Edit"));
set_ok_button_text(TTR("Install"));
project_name->set_text(zip_title);
@ -795,9 +852,9 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
void ProjectDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
create_dir->set_icon(get_editor_theme_icon(SNAME("FolderCreate")));
project_browse->set_icon(get_editor_theme_icon(SNAME("FolderBrowse")));
install_browse->set_icon(get_editor_theme_icon(SNAME("FolderBrowse")));
create_dir->set_button_icon(get_editor_theme_icon(SNAME("FolderCreate")));
project_browse->set_button_icon(get_editor_theme_icon(SNAME("FolderBrowse")));
install_browse->set_button_icon(get_editor_theme_icon(SNAME("FolderBrowse")));
} break;
case NOTIFICATION_READY: {
fdialog_project = memnew(EditorFileDialog);
@ -846,7 +903,7 @@ ProjectDialog::ProjectDialog() {
create_dir->set_text(TTR("Create Folder"));
create_dir->set_pressed(true);
pphb_label->add_child(create_dir);
create_dir->connect("toggled", callable_mp(this, &ProjectDialog::_create_dir_toggled));
create_dir->connect(SceneStringName(toggled), callable_mp(this, &ProjectDialog::_create_dir_toggled));
HBoxContainer *pphb = memnew(HBoxContainer);
project_path_container->add_child(pphb);
@ -916,10 +973,16 @@ ProjectDialog::ProjectDialog() {
default_renderer_type = EditorSettings::get_singleton()->get_setting("project_manager/default_renderer");
}
rendering_device_supported = DisplayServer::can_create_rendering_device();
if (!rendering_device_supported) {
default_renderer_type = "gl_compatibility";
}
Button *rs_button = memnew(CheckBox);
rs_button->set_button_group(renderer_button_group);
rs_button->set_text(TTR("Forward+"));
#if defined(WEB_ENABLED)
#ifndef RD_ENABLED
rs_button->set_disabled(true);
#endif
rs_button->set_meta(SNAME("rendering_method"), "forward_plus");
@ -931,7 +994,7 @@ ProjectDialog::ProjectDialog() {
rs_button = memnew(CheckBox);
rs_button->set_button_group(renderer_button_group);
rs_button->set_text(TTR("Mobile"));
#if defined(WEB_ENABLED)
#ifndef RD_ENABLED
rs_button->set_disabled(true);
#endif
rs_button->set_meta(SNAME("rendering_method"), "mobile");
@ -963,6 +1026,15 @@ ProjectDialog::ProjectDialog() {
renderer_info = memnew(Label);
renderer_info->set_modulate(Color(1, 1, 1, 0.7));
rvb->add_child(renderer_info);
rd_not_supported = memnew(Label);
rd_not_supported->set_text(vformat(TTR("RenderingDevice-based methods not available on this GPU:\n%s\nPlease use the Compatibility renderer."), RenderingServer::get_singleton()->get_video_adapter_name()));
rd_not_supported->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
rd_not_supported->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
rd_not_supported->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
rd_not_supported->set_visible(false);
renderer_container->add_child(rd_not_supported);
_renderer_selected();
l = memnew(Label);
@ -993,9 +1065,25 @@ ProjectDialog::ProjectDialog() {
fdialog_install->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
add_child(fdialog_install);
Control *spacer2 = memnew(Control);
spacer2->set_v_size_flags(Control::SIZE_EXPAND_FILL);
vb->add_child(spacer2);
edit_check_box = memnew(CheckBox);
edit_check_box->set_text(TTR("Edit Now"));
edit_check_box->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
edit_check_box->set_pressed(true);
vb->add_child(edit_check_box);
project_name->connect(SceneStringName(text_changed), callable_mp(this, &ProjectDialog::_project_name_changed).unbind(1));
project_name->connect(SceneStringName(text_submitted), callable_mp(this, &ProjectDialog::ok_pressed).unbind(1));
project_path->connect(SceneStringName(text_changed), callable_mp(this, &ProjectDialog::_project_path_changed).unbind(1));
project_path->connect(SceneStringName(text_submitted), callable_mp(this, &ProjectDialog::ok_pressed).unbind(1));
install_path->connect(SceneStringName(text_changed), callable_mp(this, &ProjectDialog::_install_path_changed).unbind(1));
install_path->connect(SceneStringName(text_submitted), callable_mp(this, &ProjectDialog::ok_pressed).unbind(1));
fdialog_install->connect("dir_selected", callable_mp(this, &ProjectDialog::_install_path_selected));
fdialog_install->connect("file_selected", callable_mp(this, &ProjectDialog::_install_path_selected));

View file

@ -34,6 +34,7 @@
#include "scene/gui/dialogs.h"
class Button;
class CheckBox;
class CheckButton;
class EditorFileDialog;
class LineEdit;
@ -65,6 +66,7 @@ private:
Mode mode = MODE_NEW;
bool is_folder_empty = true;
ConfirmationDialog *nonempty_confirmation = nullptr;
CheckButton *create_dir = nullptr;
Button *project_browse = nullptr;
@ -77,6 +79,8 @@ private:
Label *renderer_info = nullptr;
HBoxContainer *default_files_container = nullptr;
Ref<ButtonGroup> renderer_button_group;
bool rendering_device_supported = false;
Label *rd_not_supported = nullptr;
Label *msg = nullptr;
LineEdit *project_name = nullptr;
@ -87,6 +91,8 @@ private:
OptionButton *vcs_metadata_selection = nullptr;
CheckBox *edit_check_box = nullptr;
EditorFileDialog *fdialog_project = nullptr;
EditorFileDialog *fdialog_install = nullptr;
AcceptDialog *dialog_error = nullptr;

View file

@ -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"
@ -67,9 +69,9 @@ void ProjectListItemControl::_notification(int p_what) {
favorite_button->set_texture_normal(get_editor_theme_icon(SNAME("Favorites")));
if (project_is_missing) {
explore_button->set_icon(get_editor_theme_icon(SNAME("FileBroken")));
explore_button->set_button_icon(get_editor_theme_icon(SNAME("FileBroken")));
} else {
explore_button->set_icon(get_editor_theme_icon(SNAME("Load")));
explore_button->set_button_icon(get_editor_theme_icon(SNAME("Load")));
}
} break;
@ -88,7 +90,7 @@ void ProjectListItemControl::_notification(int p_what) {
draw_style_box(get_theme_stylebox(SNAME("selected"), SNAME("Tree")), Rect2(Point2(), get_size()));
}
if (is_hovering) {
draw_style_box(get_theme_stylebox(SNAME("hover"), SNAME("Tree")), Rect2(Point2(), get_size()));
draw_style_box(get_theme_stylebox(SNAME("hovered"), SNAME("Tree")), Rect2(Point2(), get_size()));
}
draw_line(Point2(0, get_size().y + 1), Point2(get_size().x, get_size().y + 1), get_theme_color(SNAME("guide_color"), SNAME("Tree")));
@ -196,12 +198,12 @@ void ProjectListItemControl::set_is_missing(bool p_missing) {
if (project_is_missing) {
project_icon->set_modulate(Color(1, 1, 1, 0.5));
explore_button->set_icon(get_editor_theme_icon(SNAME("FileBroken")));
explore_button->set_button_icon(get_editor_theme_icon(SNAME("FileBroken")));
explore_button->set_tooltip_text(TTR("Error: Project is missing on the filesystem."));
} else {
project_icon->set_modulate(Color(1, 1, 1, 1.0));
explore_button->set_icon(get_editor_theme_icon(SNAME("Load")));
explore_button->set_button_icon(get_editor_theme_icon(SNAME("Load")));
#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
explore_button->set_tooltip_text(TTR("Show in File Manager"));
#else
@ -350,7 +352,7 @@ const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
// Helpers.
bool ProjectList::project_feature_looks_like_version(const String &p_feature) {
return p_feature.contains(".") && p_feature.substr(0, 3).is_numeric();
return p_feature.contains_char('.') && p_feature.substr(0, 3).is_numeric();
}
// Notifications.
@ -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<ScanData *>(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() {
@ -417,14 +468,16 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
String conf = p_path.path_join("project.godot");
bool grayed = false;
bool missing = false;
bool recovery_mode = false;
Ref<ConfigFile> cf = memnew(ConfigFile);
Error cf_err = cf->load(conf);
int config_version = 0;
String cf_project_name;
String project_name = TTR("Unnamed Project");
if (cf_err == OK) {
String cf_project_name = cf->get_value("application", "config/name", "");
cf_project_name = cf->get_value("application", "config/name", "");
if (!cf_project_name.is_empty()) {
project_name = cf_project_name.xml_unescape();
}
@ -438,9 +491,23 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
const String description = cf->get_value("application", "config/description", "");
const PackedStringArray tags = cf->get_value("application", "config/tags", PackedStringArray());
const String icon = cf->get_value("application", "config/icon", "");
const String main_scene = cf->get_value("application", "run/main_scene", "");
String icon = cf->get_value("application", "config/icon", "");
if (icon.begins_with("uid://")) {
Error err;
Ref<FileAccess> file = FileAccess::open(p_path.path_join(".godot/uid_cache.bin"), FileAccess::READ, &err);
if (err == OK) {
icon = ResourceUID::get_path_from_cache(file, icon);
if (icon.is_empty()) {
WARN_PRINT(vformat("Could not load icon from UID for project at path \"%s\". Make sure UID cache exists.", p_path));
}
} else {
// Cache does not exist yet, so ignore and fallback to default icon.
icon = "";
}
}
PackedStringArray project_features = cf->get_value("application", "config/features", PackedStringArray());
PackedStringArray unsupported_features = ProjectSettings::get_unsupported_features(project_features);
@ -486,7 +553,29 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
ProjectManager::get_singleton()->add_new_tag(tag);
}
return Item(project_name, description, project_version, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version);
// We can't use OS::get_user_dir() because it attempts to load paths from the current loaded project through ProjectSettings,
// while here we're parsing project files externally. Therefore, we have to replicate its behavior.
String user_dir;
if (!cf_project_name.is_empty()) {
String appname = OS::get_singleton()->get_safe_dir_name(cf_project_name);
bool use_custom_dir = cf->get_value("application", "config/use_custom_user_dir", false);
if (use_custom_dir) {
String custom_dir = OS::get_singleton()->get_safe_dir_name(cf->get_value("application", "config/custom_user_dir_name", ""), true);
if (custom_dir.is_empty()) {
custom_dir = appname;
}
user_dir = custom_dir;
} else {
user_dir = OS::get_singleton()->get_godot_dir_name().path_join("app_userdata").path_join(appname);
}
} else {
user_dir = OS::get_singleton()->get_godot_dir_name().path_join("app_userdata").path_join("[unnamed project]");
}
String recovery_mode_lock_file = OS::get_singleton()->get_user_data_dir(user_dir).path_join(".recovery_mode_lock");
recovery_mode = FileAccess::exists(recovery_mode_lock_file);
return Item(project_name, description, project_version, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, recovery_mode, config_version);
}
void ProjectList::_update_icons_async() {
@ -581,7 +670,7 @@ void ProjectList::sort_projects() {
bool item_visible = true;
if (!_search_term.is_empty()) {
String search_path;
if (search_term.contains("/")) {
if (search_term.contains_char('/')) {
// Search path will match the whole path
search_path = item.path;
} else {
@ -624,25 +713,39 @@ void ProjectList::find_projects(const String &p_path) {
}
void ProjectList::find_projects_multiple(const PackedStringArray &p_paths) {
List<String> 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);
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));
}
for (const String &E : projects) {
add_project(E, false);
}
scan_data = memnew(ScanData);
scan_data->paths_to_scan = p_paths;
scan_data->scan_in_progress.set();
save_config();
scan_data->thread = memnew(Thread);
scan_data->thread->start(_scan_thread, scan_data);
if (ProjectManager::get_singleton()->is_initialized()) {
update_project_list();
if (scan_progress) {
scan_progress->reset_size();
scan_progress->popup_centered();
}
set_process(true);
}
void ProjectList::load_project_list() {
@ -656,7 +759,11 @@ void ProjectList::load_project_list() {
}
}
void ProjectList::_scan_folder_recursive(const String &p_path, List<String> *r_projects) {
void ProjectList::_scan_folder_recursive(const String &p_path, List<String> *r_projects, const SafeFlag &p_scan_active) {
if (!p_scan_active.is_set()) {
return;
}
Ref<DirAccess> 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 +771,12 @@ void ProjectList::_scan_folder_recursive(const String &p_path, List<String> *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());
}
@ -760,7 +871,7 @@ void ProjectList::_create_project_item_control(int p_index) {
hb->set_tags(item.tags, this);
hb->set_unsupported_features(item.unsupported_features.duplicate());
hb->set_project_version(item.project_version);
hb->set_last_edited_info(Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true));
hb->set_last_edited_info(!item.missing ? Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true) : TTR("Missing Date"));
hb->set_is_favorite(item.favorite);
hb->set_is_missing(item.missing);

View file

@ -35,6 +35,7 @@
#include "scene/gui/box_container.h"
#include "scene/gui/scroll_container.h"
class AcceptDialog;
class Button;
class Label;
class ProjectList;
@ -115,6 +116,7 @@ public:
bool favorite = false;
bool grayed = false;
bool missing = false;
bool recovery_mode = false;
int version = 0;
ProjectListItemControl *control = nullptr;
@ -133,6 +135,7 @@ public:
bool p_favorite,
bool p_grayed,
bool p_missing,
bool p_recovery_mode,
int p_version) {
project_name = p_name;
description = p_description;
@ -146,6 +149,7 @@ public:
favorite = p_favorite;
grayed = p_grayed;
missing = p_missing;
recovery_mode = p_recovery_mode;
version = p_version;
control = nullptr;
@ -176,6 +180,20 @@ private:
VBoxContainer *project_list_vbox = nullptr;
// Projects scan.
struct ScanData {
Thread *thread = nullptr;
PackedStringArray paths_to_scan;
List<String> 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 +204,7 @@ private:
// Project list updates.
void _scan_folder_recursive(const String &p_path, List<String> *r_projects);
static void _scan_folder_recursive(const String &p_path, List<String> *r_projects, const SafeFlag &p_scan_active);
// Project list items.

View file

@ -36,7 +36,7 @@
void ProjectTag::_notification(int p_what) {
if (display_close && p_what == NOTIFICATION_THEME_CHANGED) {
button->set_icon(get_theme_icon(SNAME("close"), SNAME("TabBar")));
button->set_button_icon(get_theme_icon(SNAME("close"), SNAME("TabBar")));
}
}
@ -70,5 +70,5 @@ ProjectTag::ProjectTag(const String &p_text, bool p_display_close) {
button->set_text(p_text.capitalize());
button->set_focus_mode(FOCUS_NONE);
button->set_icon_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
button->set_theme_type_variation(SNAME("ProjectTag"));
button->set_theme_type_variation(SNAME("ProjectTagButton"));
}

View file

@ -31,14 +31,13 @@
#include "quick_settings_dialog.h"
#include "core/config/project_settings.h"
#include "core/string/translation.h"
#include "core/string/translation_server.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/label.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
@ -98,6 +97,7 @@ void QuickSettingsDialog::_update_current_values() {
if (current_theme == theme_value) {
theme_option_button->set_text(current_theme);
theme_option_button->select(i);
theme_option_button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
custom_theme_label->set_visible(current_theme == "Custom");
}