diff options
Diffstat (limited to 'editor/project_manager.cpp')
-rw-r--r-- | editor/project_manager.cpp | 341 |
1 files changed, 212 insertions, 129 deletions
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 81554c9550..134f238bb6 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -41,6 +41,7 @@ #include "core/string/translation.h" #include "core/version.h" #include "core/version_hash.gen.h" +#include "editor/editor_vcs_interface.h" #include "editor_scale.h" #include "editor_settings.h" #include "editor_themes.h" @@ -52,6 +53,7 @@ #include "scene/gui/texture_rect.h" #include "scene/main/window.h" #include "servers/display_server.h" +#include "servers/navigation_server_3d.h" static inline String get_project_key_from_path(const String &dir) { return dir.replace("/", "::"); @@ -66,19 +68,19 @@ public: MODE_NEW, MODE_IMPORT, MODE_INSTALL, - MODE_RENAME + MODE_RENAME, }; private: enum MessageType { MESSAGE_ERROR, MESSAGE_WARNING, - MESSAGE_SUCCESS + MESSAGE_SUCCESS, }; enum InputType { PROJECT_PATH, - INSTALL_PATH + INSTALL_PATH, }; Mode mode; @@ -89,6 +91,7 @@ private: Container *path_container; Container *install_path_container; Container *rasterizer_container; + HBoxContainer *default_files_container; Ref<ButtonGroup> rasterizer_button_group; Label *msg; LineEdit *project_path; @@ -98,6 +101,7 @@ private: TextureRect *install_status_rect; FileDialog *fdialog; FileDialog *fdialog_install; + OptionButton *vcs_metadata_selection; String zip_path; String zip_title; AcceptDialog *dialog_error; @@ -157,7 +161,7 @@ private: } } - if (valid_path == "") { + if (valid_path.is_empty()) { set_message(TTR("The path specified doesn't exist."), MESSAGE_ERROR); memdelete(d); get_ok_button()->set_disabled(true); @@ -171,7 +175,7 @@ private: valid_install_path = install_path->get_text().strip_edges(); } - if (valid_install_path == "") { + if (valid_install_path.is_empty()) { set_message(TTR("The path specified doesn't exist."), MESSAGE_ERROR, INSTALL_PATH); memdelete(d); get_ok_button()->set_disabled(true); @@ -180,7 +184,7 @@ private: } if (mode == MODE_IMPORT || mode == MODE_RENAME) { - if (valid_path != "" && !d->file_exists("project.godot")) { + if (!valid_path.is_empty() && !d->file_exists("project.godot")) { if (valid_path.ends_with(".zip")) { FileAccess *src_f = nullptr; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); @@ -221,7 +225,7 @@ private: d->list_dir_begin(); is_folder_empty = true; String n = d->get_next(); - while (n != String()) { + while (!n.is_empty()) { if (!n.begins_with(".")) { // Allow `.`, `..` (reserved current/parent folder names) // and hidden files/folders to be present. @@ -261,7 +265,7 @@ private: d->list_dir_begin(); is_folder_empty = true; String n = d->get_next(); - while (n != String()) { + while (!n.is_empty()) { if (!n.begins_with(".")) { // Allow `.`, `..` (reserved current/parent folder names) // and hidden files/folders to be present. @@ -291,16 +295,16 @@ private: void _path_text_changed(const String &p_path) { String sp = _test_path(); - if (sp != "") { + if (!sp.is_empty()) { // If the project name is empty or default, infer the project name from the selected folder name - if (project_name->get_text().strip_edges() == "" || project_name->get_text().strip_edges() == TTR("New Game Project")) { + if (project_name->get_text().strip_edges().is_empty() || project_name->get_text().strip_edges() == TTR("New Game Project")) { sp = sp.replace("\\", "/"); int lidx = sp.rfind("/"); if (lidx != -1) { sp = sp.substr(lidx + 1, sp.length()).capitalize(); } - if (sp == "" && mode == MODE_IMPORT) { + if (sp.is_empty() && mode == MODE_IMPORT) { sp = TTR("Imported Project"); } @@ -309,7 +313,7 @@ private: } } - if (created_folder_path != "" && created_folder_path != p_path) { + if (!created_folder_path.is_empty() && created_folder_path != p_path) { _remove_created_folder(); } } @@ -378,7 +382,7 @@ private: void _create_folder() { const String project_name_no_edges = project_name->get_text().strip_edges(); - if (project_name_no_edges == "" || created_folder_path != "" || project_name_no_edges.ends_with(".")) { + if (project_name_no_edges.is_empty() || !created_folder_path.is_empty() || project_name_no_edges.ends_with(".")) { set_message(TTR("Invalid project name."), MESSAGE_WARNING); return; } @@ -413,7 +417,7 @@ private: _test_path(); - if (p_text.strip_edges() == "") { + if (p_text.strip_edges().is_empty()) { set_message(TTR("It would be a good idea to name your project."), MESSAGE_ERROR); } } @@ -428,7 +432,7 @@ private: if (mode == MODE_RENAME) { String dir2 = _test_path(); - if (dir2 == "") { + if (dir2.is_empty()) { set_message(TTR("Invalid project path (changed anything?)."), MESSAGE_ERROR); return; } @@ -473,32 +477,29 @@ private: cd->grab_focus(); return; } + PackedStringArray project_features = ProjectSettings::get_required_features(); ProjectSettings::CustomMap initial_settings; - initial_settings["rendering/vulkan/rendering/back_end"] = rasterizer_button_group->get_pressed_button()->get_meta(SNAME("driver_name")); + // Be sure to change this code if/when renderers are changed. + int renderer_type = rasterizer_button_group->get_pressed_button()->get_meta(SNAME("driver_name")); + initial_settings["rendering/vulkan/rendering/back_end"] = renderer_type; + if (renderer_type == 0) { + project_features.push_back("Vulkan Clustered"); + } else if (renderer_type == 1) { + project_features.push_back("Vulkan Mobile"); + } else { + WARN_PRINT("Unknown renderer type. Please report this as a bug on GitHub."); + } + project_features.sort(); + initial_settings["application/config/features"] = project_features; initial_settings["application/config/name"] = project_name->get_text().strip_edges(); initial_settings["application/config/icon"] = "res://icon.png"; - initial_settings["rendering/environment/defaults/default_environment"] = "res://default_env.tres"; if (ProjectSettings::get_singleton()->save_custom(dir.plus_file("project.godot"), initial_settings, Vector<String>(), false) != OK) { set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR); } else { ResourceSaver::save(dir.plus_file("icon.png"), create_unscaled_default_project_icon()); - - FileAccess *f = FileAccess::open(dir.plus_file("default_env.tres"), FileAccess::WRITE); - if (!f) { - set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR); - } else { - f->store_line("[gd_resource type=\"Environment\" load_steps=2 format=3]"); - f->store_line(""); - f->store_line("[sub_resource type=\"Sky\" id=\"1\"]"); - f->store_line(""); - f->store_line("[resource]"); - f->store_line("background_mode = 2"); - f->store_line("sky = SubResource( \"1\" )"); - memdelete(f); - } + EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(vcs_metadata_selection->get_selected()), dir); } - } else if (mode == MODE_INSTALL) { if (project_path->get_text().ends_with(".zip")) { dir = install_path->get_text(); @@ -545,7 +546,7 @@ private: String path = fname; - if (path == String() || path == zip_root || !zip_root.is_subsequence_of(path)) { + if (path.is_empty() || path == zip_root || !zip_root.is_subsequence_of(path)) { // } else if (path.ends_with("/")) { // a dir @@ -616,7 +617,7 @@ private: } void _remove_created_folder() { - if (created_folder_path != "") { + if (!created_folder_path.is_empty()) { DirAccess *d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); d->remove(created_folder_path); memdelete(d); @@ -693,6 +694,7 @@ public: install_path_container->hide(); install_status_rect->hide(); rasterizer_container->hide(); + default_files_container->hide(); get_ok_button()->set_disabled(false); ProjectSettings *current = memnew(ProjectSettings); @@ -715,7 +717,7 @@ public: } else { fav_dir = EditorSettings::get_singleton()->get("filesystem/directories/default_project_path"); - if (fav_dir != "") { + if (!fav_dir.is_empty()) { project_path->set_text(fav_dir); fdialog->set_current_dir(fav_dir); } else { @@ -744,6 +746,7 @@ public: name_container->hide(); install_path_container->hide(); rasterizer_container->hide(); + default_files_container->hide(); project_path->grab_focus(); } else if (mode == MODE_NEW) { @@ -752,6 +755,7 @@ public: name_container->show(); install_path_container->hide(); rasterizer_container->show(); + default_files_container->show(); project_name->call_deferred(SNAME("grab_focus")); project_name->call_deferred(SNAME("select_all")); @@ -762,6 +766,7 @@ public: name_container->show(); install_path_container->hide(); rasterizer_container->hide(); + default_files_container->hide(); project_path->grab_focus(); } @@ -845,7 +850,7 @@ public: iphb->add_child(install_browse); msg = memnew(Label); - msg->set_align(Label::ALIGN_CENTER); + msg->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); vb->add_child(msg); // rasterizer selection @@ -899,11 +904,26 @@ public: l->set_text(TTR("The renderer can be changed later, but scenes may need to be adjusted.")); // Add some extra spacing to separate it from the list above and the buttons below. l->set_custom_minimum_size(Size2(0, 40) * EDSCALE); - l->set_align(Label::ALIGN_CENTER); - l->set_valign(Label::VALIGN_CENTER); + l->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + l->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); l->set_modulate(Color(1, 1, 1, 0.7)); rasterizer_container->add_child(l); + default_files_container = memnew(HBoxContainer); + vb->add_child(default_files_container); + l = memnew(Label); + l->set_text(TTR("Version Control Metadata:")); + default_files_container->add_child(l); + vcs_metadata_selection = memnew(OptionButton); + vcs_metadata_selection->set_custom_minimum_size(Size2(100, 20)); + vcs_metadata_selection->add_item("None", (int)EditorVCSInterface::VCSMetadata::NONE); + vcs_metadata_selection->add_item("Git", (int)EditorVCSInterface::VCSMetadata::GIT); + vcs_metadata_selection->select((int)EditorVCSInterface::VCSMetadata::GIT); + default_files_container->add_child(vcs_metadata_selection); + Control *spacer = memnew(Control); + spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL); + default_files_container->add_child(spacer); + fdialog = memnew(FileDialog); fdialog->set_access(FileDialog::ACCESS_FILESYSTEM); fdialog_install = memnew(FileDialog); @@ -985,6 +1005,7 @@ public: String path; String icon; String main_scene; + PackedStringArray unsupported_features; uint64_t last_edited = 0; bool favorite = false; bool grayed = false; @@ -1001,6 +1022,7 @@ public: const String &p_path, const String &p_icon, const String &p_main_scene, + const PackedStringArray &p_unsupported_features, uint64_t p_last_edited, bool p_favorite, bool p_grayed, @@ -1012,6 +1034,7 @@ public: path = p_path; icon = p_icon; main_scene = p_main_scene; + unsupported_features = p_unsupported_features; last_edited = p_last_edited; favorite = p_favorite; grayed = p_grayed; @@ -1063,8 +1086,7 @@ private: void remove_project(int p_index, bool p_update_settings); void update_icons_async(); void load_project_icon(int p_index); - - static void load_project_data(const String &p_property_key, Item &p_item, bool p_favorite); + static Item load_project_data(const String &p_property_key, bool p_favorite); String _search_term; FilterOption _order_option; @@ -1136,7 +1158,7 @@ void ProjectList::load_project_icon(int p_index) { Ref<Texture2D> default_icon = get_theme_icon(SNAME("DefaultProjectIcon"), SNAME("EditorIcons")); Ref<Texture2D> icon; - if (item.icon != "") { + if (!item.icon.is_empty()) { Ref<Image> img; img.instantiate(); Error err = img->load(item.icon.replace_first("res://", item.path + "/")); @@ -1155,7 +1177,8 @@ void ProjectList::load_project_icon(int p_index) { item.control->icon_needs_reload = false; } -void ProjectList::load_project_data(const String &p_property_key, Item &p_item, bool p_favorite) { +// Load project data from p_property_key and return it in a ProjectList::Item. p_favorite is passed directly into the Item. +ProjectList::Item ProjectList::load_project_data(const String &p_property_key, bool p_favorite) { String path = EditorSettings::get_singleton()->get(p_property_key); String conf = path.plus_file("project.godot"); bool grayed = false; @@ -1168,20 +1191,23 @@ void ProjectList::load_project_data(const String &p_property_key, Item &p_item, String project_name = TTR("Unnamed Project"); if (cf_err == OK) { String cf_project_name = static_cast<String>(cf->get_value("application", "config/name", "")); - if (cf_project_name != "") { + if (!cf_project_name.is_empty()) { project_name = cf_project_name.xml_unescape(); } config_version = (int)cf->get_value("", "config_version", 0); } if (config_version > ProjectSettings::CONFIG_VERSION) { - // Comes from an incompatible (more recent) Godot version, grey it out + // Comes from an incompatible (more recent) Godot version, gray it out. grayed = true; } - String description = cf->get_value("application", "config/description", ""); - String icon = cf->get_value("application", "config/icon", ""); - String main_scene = cf->get_value("application", "run/main_scene", ""); + const String description = cf->get_value("application", "config/description", ""); + const String icon = cf->get_value("application", "config/icon", ""); + const String main_scene = cf->get_value("application", "run/main_scene", ""); + + PackedStringArray project_features = cf->get_value("application", "config/features", PackedStringArray()); + PackedStringArray unsupported_features = ProjectSettings::get_unsupported_features(project_features); uint64_t last_edited = 0; if (FileAccess::exists(conf)) { @@ -1203,9 +1229,9 @@ void ProjectList::load_project_data(const String &p_property_key, Item &p_item, print_line("Project is missing: " + conf); } - String project_key = p_property_key.get_slice("/", 1); + const String project_key = p_property_key.get_slice("/", 1); - p_item = Item(project_key, project_name, description, path, icon, main_scene, last_edited, p_favorite, grayed, missing, config_version); + return Item(project_key, project_name, description, path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version); } void ProjectList::load_projects() { @@ -1248,8 +1274,7 @@ void ProjectList::load_projects() { String project_key = property_key.get_slice("/", 1); bool favorite = favorites.has("favorite_projects/" + project_key); - Item item; - load_project_data(property_key, item, favorite); + Item item = load_project_data(property_key, favorite); _projects.push_back(item); } @@ -1297,8 +1322,7 @@ void ProjectList::update_dock_menu() { void ProjectList::_global_menu_new_window(const Variant &p_tag) { List<String> args; args.push_back("-p"); - String exec = OS::get_singleton()->get_executable_path(); - OS::get_singleton()->create_process(exec, args); + OS::get_singleton()->create_instance(args); } void ProjectList::_global_menu_open_project(const Variant &p_tag) { @@ -1308,8 +1332,7 @@ void ProjectList::_global_menu_open_project(const Variant &p_tag) { String conf = _projects[idx].path.plus_file("project.godot"); List<String> args; args.push_back(conf); - String exec = OS::get_singleton()->get_executable_path(); - OS::get_singleton()->create_process(exec, args); + OS::get_singleton()->create_instance(args); } } @@ -1334,11 +1357,11 @@ void ProjectList::create_project_item_control(int p_index) { TextureButton *favorite = memnew(TextureButton); favorite->set_name("FavoriteButton"); favorite->set_normal_texture(favorite_icon); - // This makes the project's "hover" style display correctly when hovering the favorite icon + // This makes the project's "hover" style display correctly when hovering the favorite icon. favorite->set_mouse_filter(MOUSE_FILTER_PASS); favorite->connect("pressed", callable_mp(this, &ProjectList::_favorite_pressed), varray(hb)); favorite_box->add_child(favorite); - favorite_box->set_alignment(BoxContainer::ALIGN_CENTER); + favorite_box->set_alignment(BoxContainer::ALIGNMENT_CENTER); hb->add_child(favorite_box); hb->favorite_button = favorite; hb->set_is_favorite(item.favorite); @@ -1364,40 +1387,65 @@ void ProjectList::create_project_item_control(int p_index) { ec->set_custom_minimum_size(Size2(0, 1)); ec->set_mouse_filter(MOUSE_FILTER_PASS); vb->add_child(ec); - Label *title = memnew(Label(!item.missing ? item.project_name : TTR("Missing Project"))); - title->add_theme_font_override("font", get_theme_font(SNAME("title"), SNAME("EditorFonts"))); - title->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("title_size"), SNAME("EditorFonts"))); - title->add_theme_color_override("font_color", font_color); - title->set_clip_text(true); - vb->add_child(title); - - HBoxContainer *path_hb = memnew(HBoxContainer); - path_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); - vb->add_child(path_hb); - - Button *show = memnew(Button); - // Display a folder icon if the project directory can be opened, or a "broken file" icon if it can't. - show->set_icon(get_theme_icon(!item.missing ? "Load" : "FileBroken", "EditorIcons")); - if (!item.grayed) { - // Don't make the icon less prominent if the parent is already grayed out. - show->set_modulate(Color(1, 1, 1, 0.5)); - } - path_hb->add_child(show); - - if (!item.missing) { - show->connect("pressed", callable_mp(this, &ProjectList::_show_project), varray(item.path)); - show->set_tooltip(TTR("Show in File Manager")); - } else { - show->set_tooltip(TTR("Error: Project is missing on the filesystem.")); - } - Label *fpath = memnew(Label(item.path)); - fpath->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); - path_hb->add_child(fpath); - fpath->set_h_size_flags(Control::SIZE_EXPAND_FILL); - fpath->set_modulate(Color(1, 1, 1, 0.5)); - fpath->add_theme_color_override("font_color", font_color); - fpath->set_clip_text(true); + { // Top half, title and unsupported features labels. + HBoxContainer *title_hb = memnew(HBoxContainer); + vb->add_child(title_hb); + + Label *title = memnew(Label(!item.missing ? item.project_name : TTR("Missing Project"))); + title->set_h_size_flags(Control::SIZE_EXPAND_FILL); + title->add_theme_font_override("font", get_theme_font(SNAME("title"), SNAME("EditorFonts"))); + title->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("title_size"), SNAME("EditorFonts"))); + title->add_theme_color_override("font_color", font_color); + title->set_clip_text(true); + title_hb->add_child(title); + + String unsupported_features_str = Variant(item.unsupported_features).operator String().trim_prefix("[").trim_suffix("]"); + int length = unsupported_features_str.length(); + if (length > 0) { + Label *unsupported_label = memnew(Label(unsupported_features_str)); + unsupported_label->set_custom_minimum_size(Size2(length * 15, 10) * EDSCALE); + unsupported_label->add_theme_font_override("font", get_theme_font(SNAME("title"), SNAME("EditorFonts"))); + unsupported_label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + unsupported_label->set_clip_text(true); + unsupported_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + title_hb->add_child(unsupported_label); + Control *spacer = memnew(Control()); + spacer->set_custom_minimum_size(Size2(10, 10)); + title_hb->add_child(spacer); + } + } + + { // Bottom half, containing the path and view folder button. + HBoxContainer *path_hb = memnew(HBoxContainer); + path_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + vb->add_child(path_hb); + + Button *show = memnew(Button); + // Display a folder icon if the project directory can be opened, or a "broken file" icon if it can't. + show->set_icon(get_theme_icon(!item.missing ? "Load" : "FileBroken", "EditorIcons")); + show->set_flat(true); + if (!item.grayed) { + // Don't make the icon less prominent if the parent is already grayed out. + show->set_modulate(Color(1, 1, 1, 0.5)); + } + path_hb->add_child(show); + + if (!item.missing) { + show->connect("pressed", callable_mp(this, &ProjectList::_show_project), varray(item.path)); + show->set_tooltip(TTR("Show in File Manager")); + } else { + show->set_tooltip(TTR("Error: Project is missing on the filesystem.")); + } + + Label *fpath = memnew(Label(item.path)); + fpath->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); + path_hb->add_child(fpath); + fpath->set_h_size_flags(Control::SIZE_EXPAND_FILL); + fpath->set_modulate(Color(1, 1, 1, 0.5)); + fpath->add_theme_color_override("font_color", font_color); + fpath->set_clip_text(true); + } _scroll_children->add_child(hb); item.control = hb; @@ -1425,7 +1473,7 @@ void ProjectList::sort_projects() { Item &item = _projects.write[i]; bool visible = true; - if (_search_term != "") { + if (!_search_term.is_empty()) { String search_path; if (_search_term.find("/") != -1) { // Search path will match the whole path @@ -1511,7 +1559,7 @@ void ProjectList::remove_project(int p_index, bool p_update_settings) { } memdelete(item.control); - _projects.remove(p_index); + _projects.remove_at(p_index); if (p_update_settings) { EditorSettings::get_singleton()->erase("projects/" + item.project_key); @@ -1602,8 +1650,7 @@ int ProjectList::refresh_project(const String &dir_path) { if (should_be_in_list) { // Recreate it with updated info - Item item; - load_project_data(property_key, item, is_favourite); + Item item = load_project_data(property_key, is_favourite); _projects.push_back(item); create_project_item_control(_projects.size() - 1); @@ -1701,7 +1748,7 @@ void ProjectList::erase_selected_projects(bool p_delete_project_contents) { } memdelete(item.control); - _projects.remove(i); + _projects.remove_at(i); --i; } } @@ -1737,8 +1784,8 @@ void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) { int clicked_index = p_hb->get_index(); const Item &clicked_project = _projects[clicked_index]; - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (mb->is_shift_pressed() && _selected_project_keys.size() > 0 && _last_clicked != "" && clicked_project.project_key != _last_clicked) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_shift_pressed() && _selected_project_keys.size() > 0 && !_last_clicked.is_empty() && clicked_project.project_key != _last_clicked) { int anchor_index = -1; for (int i = 0; i < _projects.size(); ++i) { const Item &p = _projects[i]; @@ -1901,7 +1948,7 @@ void ProjectManager::unhandled_key_input(const Ref<InputEvent> &p_ev) { // This is handled by the platform implementation on macOS, // so only define the shortcut on other platforms #ifndef OSX_ENABLED - if (k->get_keycode_with_modifiers() == (KEY_MASK_CMD | KEY_Q)) { + if (k->get_keycode_with_modifiers() == (KeyModifierMask::CMD | Key::Q)) { _dim_window(); get_tree()->quit(); } @@ -1914,24 +1961,24 @@ void ProjectManager::unhandled_key_input(const Ref<InputEvent> &p_ev) { bool keycode_handled = true; switch (k->get_keycode()) { - case KEY_ENTER: { + case Key::ENTER: { _open_selected_projects_ask(); } break; - case KEY_HOME: { + case Key::HOME: { if (_project_list->get_project_count() > 0) { _project_list->select_project(0); _update_project_buttons(); } } break; - case KEY_END: { + case Key::END: { if (_project_list->get_project_count() > 0) { _project_list->select_project(_project_list->get_project_count() - 1); _update_project_buttons(); } } break; - case KEY_UP: { + case Key::UP: { if (k->is_shift_pressed()) { break; } @@ -1945,7 +1992,7 @@ void ProjectManager::unhandled_key_input(const Ref<InputEvent> &p_ev) { break; } - case KEY_DOWN: { + case Key::DOWN: { if (k->is_shift_pressed()) { break; } @@ -1958,7 +2005,7 @@ void ProjectManager::unhandled_key_input(const Ref<InputEvent> &p_ev) { } } break; - case KEY_F: { + case Key::F: { if (k->is_command_pressed()) { this->search_box->grab_focus(); } else { @@ -2055,8 +2102,7 @@ void ProjectManager::_open_selected_projects() { args.push_back("--single-window"); } - String exec = OS::get_singleton()->get_executable_path(); - Error err = OS::get_singleton()->create_process(exec, args); + Error err = OS::get_singleton()->create_instance(args); ERR_FAIL_COND(err); } @@ -2083,8 +2129,12 @@ void ProjectManager::_open_selected_projects_ask() { } // Update the project settings or don't open - String conf = project.path.plus_file("project.godot"); - int config_version = project.version; + const String conf = project.path.plus_file("project.godot"); + const int config_version = project.version; + PackedStringArray unsupported_features = project.unsupported_features; + + Label *ask_update_label = ask_update_settings->get_label(); + ask_update_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); // Reset in case of previous center align. // Check if the config_version property was empty or 0 if (config_version == 0) { @@ -2104,6 +2154,35 @@ void ProjectManager::_open_selected_projects_ask() { dialog_error->popup_centered(); return; } + // Check if the project is using features not supported by this build of Godot. + if (!unsupported_features.is_empty()) { + String warning_message = ""; + for (int i = 0; i < unsupported_features.size(); i++) { + String feature = unsupported_features[i]; + if (feature == "Double Precision") { + warning_message += TTR("Warning: This project uses double precision floats, but this version of\nGodot uses single precision floats. Opening this project may cause data loss.\n\n"); + unsupported_features.remove_at(i); + i--; + } else if (feature == "C#") { + warning_message += TTR("Warning: This project uses C#, but this build of Godot does not have\nthe Mono module. If you proceed you will not be able to use any C# scripts.\n\n"); + unsupported_features.remove_at(i); + i--; + } else if (feature.substr(0, 3).is_numeric()) { + warning_message += vformat(TTR("Warning: This project was built in Godot %s.\nOpening will upgrade or downgrade the project to Godot %s.\n\n"), Variant(feature), Variant(VERSION_BRANCH)); + unsupported_features.remove_at(i); + i--; + } + } + if (!unsupported_features.is_empty()) { + String unsupported_features_str = Variant(unsupported_features).operator String().trim_prefix("[").trim_suffix("]"); + warning_message += vformat(TTR("Warning: This project uses the following features not supported by this build of Godot:\n\n%s\n\n"), unsupported_features_str); + } + warning_message += TTR("Open anyway? Project will be modified."); + ask_update_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + ask_update_settings->set_text(warning_message); + ask_update_settings->popup_centered(); + return; + } // Open if the project is up-to-date _open_selected_projects(); @@ -2114,7 +2193,7 @@ void ProjectManager::_run_project_confirm() { for (int i = 0; i < selected_list.size(); ++i) { const String &selected_main = selected_list[i].main_scene; - if (selected_main == "") { + if (selected_main.is_empty()) { run_error_diag->set_text(TTR("Can't run project: no main scene defined.\nPlease edit the project and set the main scene in the Project Settings under the \"Application\" category.")); run_error_diag->popup_centered(); continue; @@ -2123,8 +2202,8 @@ void ProjectManager::_run_project_confirm() { const String &selected = selected_list[i].project_key; String path = EditorSettings::get_singleton()->get("projects/" + selected); - // `.substr(6)` on `IMPORTED_FILES_PATH` strips away the leading "res://". - if (!DirAccess::exists(path.plus_file(ProjectSettings::IMPORTED_FILES_PATH.substr(6)))) { + // `.substr(6)` on `ProjectSettings::get_singleton()->get_imported_files_path()` strips away the leading "res://". + if (!DirAccess::exists(path.plus_file(ProjectSettings::get_singleton()->get_imported_files_path().substr(6)))) { run_error_diag->set_text(TTR("Can't run project: Assets need to be imported.\nPlease edit the project to trigger the initial import.")); run_error_diag->popup_centered(); continue; @@ -2141,8 +2220,7 @@ void ProjectManager::_run_project_confirm() { args.push_back("--disable-crash-handler"); } - String exec = OS::get_singleton()->get_executable_path(); - Error err = OS::get_singleton()->create_process(exec, args); + Error err = OS::get_singleton()->create_instance(args); ERR_FAIL_COND(err); } } @@ -2168,7 +2246,7 @@ void ProjectManager::_scan_dir(const String &path, List<String> *r_projects) { ERR_FAIL_COND_MSG(error != OK, "Could not scan directory at: " + path); da->list_dir_begin(); String n = da->get_next(); - while (n != String()) { + while (!n.is_empty()) { if (da->current_is_dir() && !n.begins_with(".")) { _scan_dir(da->get_current_dir().plus_file(n), r_projects); } else if (n == "project.godot") { @@ -2271,8 +2349,7 @@ void ProjectManager::_language_selected(int p_id) { void ProjectManager::_restart_confirm() { List<String> args = OS::get_singleton()->get_cmdline_args(); - String exec = OS::get_singleton()->get_executable_path(); - Error err = OS::get_singleton()->create_process(exec, args); + Error err = OS::get_singleton()->create_instance(args); ERR_FAIL_COND(err); _dim_window(); @@ -2311,7 +2388,7 @@ void ProjectManager::_files_dropped(PackedStringArray p_files, int p_screen) { if (dir->change_dir(folders[0]) == OK) { dir->list_dir_begin(); String file = dir->get_next(); - while (confirm && file != String()) { + while (confirm && !file.is_empty()) { if (!dir->current_is_dir() && file.ends_with("project.godot")) { confirm = false; } @@ -2387,6 +2464,11 @@ ProjectManager::ProjectManager() { EditorSettings::create(); } + // Turn off some servers we aren't going to be using in the Project Manager. + NavigationServer3D::get_singleton()->set_active(false); + PhysicsServer3D::get_singleton()->set_active(false); + PhysicsServer2D::get_singleton()->set_active(false); + EditorSettings::get_singleton()->set_optimize_save(false); //just write settings as they came { @@ -2455,7 +2537,7 @@ ProjectManager::ProjectManager() { tabs = memnew(TabContainer); center_box->add_child(tabs); tabs->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - tabs->set_tab_align(TabContainer::ALIGN_LEFT); + tabs->set_tab_alignment(TabContainer::ALIGNMENT_LEFT); tabs->connect("tab_changed", callable_mp(this, &ProjectManager::_on_tab_changed)); HBoxContainer *projects_hb = memnew(HBoxContainer); @@ -2513,7 +2595,7 @@ ProjectManager::ProjectManager() { _project_list = memnew(ProjectList); _project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons)); _project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask)); - _project_list->set_enable_h_scroll(false); + _project_list->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); pc->add_child(_project_list); } @@ -2525,19 +2607,19 @@ ProjectManager::ProjectManager() { Button *create = memnew(Button); create->set_text(TTR("New Project")); - create->set_shortcut(ED_SHORTCUT("project_manager/new_project", TTR("New Project"), KEY_MASK_CMD | KEY_N)); + create->set_shortcut(ED_SHORTCUT("project_manager/new_project", TTR("New Project"), KeyModifierMask::CMD | Key::N)); create->connect("pressed", callable_mp(this, &ProjectManager::_new_project)); tree_vb->add_child(create); Button *import = memnew(Button); import->set_text(TTR("Import")); - import->set_shortcut(ED_SHORTCUT("project_manager/import_project", TTR("Import Project"), KEY_MASK_CMD | KEY_I)); + import->set_shortcut(ED_SHORTCUT("project_manager/import_project", TTR("Import Project"), KeyModifierMask::CMD | Key::I)); import->connect("pressed", callable_mp(this, &ProjectManager::_import_project)); tree_vb->add_child(import); Button *scan = memnew(Button); scan->set_text(TTR("Scan")); - scan->set_shortcut(ED_SHORTCUT("project_manager/scan_projects", TTR("Scan Projects"), KEY_MASK_CMD | KEY_S)); + scan->set_shortcut(ED_SHORTCUT("project_manager/scan_projects", TTR("Scan Projects"), KeyModifierMask::CMD | Key::S)); scan->connect("pressed", callable_mp(this, &ProjectManager::_scan_projects)); tree_vb->add_child(scan); @@ -2545,25 +2627,26 @@ ProjectManager::ProjectManager() { open_btn = memnew(Button); open_btn->set_text(TTR("Edit")); - open_btn->set_shortcut(ED_SHORTCUT("project_manager/edit_project", TTR("Edit Project"), KEY_MASK_CMD | KEY_E)); + open_btn->set_shortcut(ED_SHORTCUT("project_manager/edit_project", TTR("Edit Project"), KeyModifierMask::CMD | Key::E)); open_btn->connect("pressed", callable_mp(this, &ProjectManager::_open_selected_projects_ask)); tree_vb->add_child(open_btn); run_btn = memnew(Button); run_btn->set_text(TTR("Run")); - run_btn->set_shortcut(ED_SHORTCUT("project_manager/run_project", TTR("Run Project"), KEY_MASK_CMD | KEY_R)); + run_btn->set_shortcut(ED_SHORTCUT("project_manager/run_project", TTR("Run Project"), KeyModifierMask::CMD | Key::R)); run_btn->connect("pressed", callable_mp(this, &ProjectManager::_run_project)); tree_vb->add_child(run_btn); rename_btn = memnew(Button); rename_btn->set_text(TTR("Rename")); - rename_btn->set_shortcut(ED_SHORTCUT("project_manager/rename_project", TTR("Rename Project"), KEY_F2)); + // The F2 shortcut isn't overridden with Enter on macOS as Enter is already used to edit a project. + rename_btn->set_shortcut(ED_SHORTCUT("project_manager/rename_project", TTR("Rename Project"), Key::F2)); rename_btn->connect("pressed", callable_mp(this, &ProjectManager::_rename_project)); tree_vb->add_child(rename_btn); erase_btn = memnew(Button); erase_btn->set_text(TTR("Remove")); - erase_btn->set_shortcut(ED_SHORTCUT("project_manager/remove_project", TTR("Remove Project"), KEY_DELETE)); + erase_btn->set_shortcut(ED_SHORTCUT("project_manager/remove_project", TTR("Remove Project"), Key::KEY_DELETE)); erase_btn->connect("pressed", callable_mp(this, &ProjectManager::_erase_project)); tree_vb->add_child(erase_btn); @@ -2583,7 +2666,7 @@ ProjectManager::ProjectManager() { { // Version info and language options settings_hb = memnew(HBoxContainer); - settings_hb->set_alignment(BoxContainer::ALIGN_END); + settings_hb->set_alignment(BoxContainer::ALIGNMENT_END); settings_hb->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); settings_hb->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT); @@ -2634,7 +2717,7 @@ ProjectManager::ProjectManager() { for (int i = 0; i < editor_languages.size(); i++) { String lang = editor_languages[i]; String lang_name = TranslationServer::get_singleton()->get_locale_name(lang); - language_btn->add_item(lang_name + " [" + lang + "]", i); + language_btn->add_item(vformat("[%s] %s", lang, lang_name), i); language_btn->set_item_metadata(i, lang); if (current_lang == lang) { language_btn->select(i); @@ -2743,7 +2826,7 @@ ProjectManager::ProjectManager() { } String autoscan_path = EditorSettings::get_singleton()->get("filesystem/directories/autoscan_project_path"); - if (autoscan_path != "") { + if (!autoscan_path.is_empty()) { if (dir_access->dir_exists(autoscan_path)) { _scan_begin(autoscan_path); } else { |