Path: blob/master/editor/project_manager/project_manager.cpp
9902 views
/**************************************************************************/1/* project_manager.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "project_manager.h"3132#include "core/config/project_settings.h"33#include "core/io/config_file.h"34#include "core/io/dir_access.h"35#include "core/io/file_access.h"36#include "core/os/keyboard.h"37#include "core/os/os.h"38#include "core/version.h"39#include "editor/asset_library/asset_library_editor_plugin.h"40#include "editor/editor_string_names.h"41#include "editor/gui/editor_about.h"42#include "editor/gui/editor_file_dialog.h"43#include "editor/gui/editor_title_bar.h"44#include "editor/gui/editor_version_button.h"45#include "editor/project_manager/engine_update_label.h"46#include "editor/project_manager/project_dialog.h"47#include "editor/project_manager/project_list.h"48#include "editor/project_manager/project_tag.h"49#include "editor/project_manager/quick_settings_dialog.h"50#include "editor/settings/editor_settings.h"51#include "editor/themes/editor_scale.h"52#include "editor/themes/editor_theme_manager.h"53#include "main/main.h"54#include "scene/gui/check_box.h"55#include "scene/gui/flow_container.h"56#include "scene/gui/line_edit.h"57#include "scene/gui/margin_container.h"58#include "scene/gui/option_button.h"59#include "scene/gui/panel_container.h"60#include "scene/gui/rich_text_label.h"61#include "scene/gui/separator.h"62#include "scene/main/window.h"63#include "scene/theme/theme_db.h"64#include "servers/display_server.h"65#include "servers/navigation_server_3d.h"6667#ifndef PHYSICS_3D_DISABLED68#include "servers/physics_server_3d.h"69#endif // PHYSICS_3D_DISABLED7071#ifndef PHYSICS_2D_DISABLED72#include "servers/physics_server_2d.h"73#endif // PHYSICS_2D_DISABLED7475constexpr int GODOT4_CONFIG_VERSION = 5;7677ProjectManager *ProjectManager::singleton = nullptr;7879// Notifications.8081void ProjectManager::_notification(int p_what) {82switch (p_what) {83case NOTIFICATION_ENTER_TREE: {84Engine::get_singleton()->set_editor_hint(false);8586Window *main_window = get_window();87if (main_window) {88// Handle macOS fullscreen and extend-to-title changes.89main_window->connect("titlebar_changed", callable_mp(this, &ProjectManager::_titlebar_resized));90}9192// Theme has already been created in the constructor, so we can skip that step.93_update_theme(true);94} break;9596case NOTIFICATION_READY: {97DisplayServer::get_singleton()->screen_set_keep_on(EDITOR_GET("interface/editor/keep_screen_on"));98const int default_sorting = (int)EDITOR_GET("project_manager/sorting_order");99filter_option->select(default_sorting);100project_list->set_order_option(default_sorting);101102_select_main_view(MAIN_VIEW_PROJECTS);103_update_list_placeholder();104_titlebar_resized();105} break;106107case NOTIFICATION_TRANSLATION_CHANGED: {108// TRANSLATORS: This refers to the application where users manage their Godot projects.109SceneTree::get_singleton()->get_root()->set_title(GODOT_VERSION_NAME + String(" - ") + TTR("Project Manager", "Application"));110111const String line1 = TTR("You don't have any projects yet.");112const String line2 = TTR("Get started by creating a new one,\nimporting one that exists, or by downloading a project template from the Asset Library!");113empty_list_message->set_text(vformat("[center][b]%s[/b] %s[/center]", line1, line2));114115_titlebar_resized();116} break;117118case NOTIFICATION_VISIBILITY_CHANGED: {119set_process_shortcut_input(is_visible_in_tree());120} break;121122case NOTIFICATION_WM_CLOSE_REQUEST: {123_dim_window();124} break;125126case NOTIFICATION_WM_ABOUT: {127_show_about();128} break;129130case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {131if (EditorThemeManager::is_generated_theme_outdated()) {132_update_theme();133}134_update_list_placeholder();135} break;136}137}138139// Utility data.140141Ref<Texture2D> ProjectManager::_file_dialog_get_icon(const String &p_path) {142if (p_path.get_extension().to_lower() == "godot") {143return singleton->icon_type_cache["GodotMonochrome"];144}145146return singleton->icon_type_cache["Object"];147}148149Ref<Texture2D> ProjectManager::_file_dialog_get_thumbnail(const String &p_path) {150if (p_path.get_extension().to_lower() == "godot") {151return singleton->icon_type_cache["GodotFile"];152}153154return Ref<Texture2D>();155}156157void ProjectManager::_build_icon_type_cache(Ref<Theme> p_theme) {158if (p_theme.is_null()) {159return;160}161List<StringName> tl;162p_theme->get_icon_list(EditorStringName(EditorIcons), &tl);163for (const StringName &name : tl) {164icon_type_cache[name] = p_theme->get_icon(name, EditorStringName(EditorIcons));165}166}167168// Main layout.169170void ProjectManager::_update_size_limits() {171const Size2 minimum_size = Size2(720, 450) * EDSCALE;172173// Define a minimum window size to prevent UI elements from overlapping or being cut off.174Window *w = Object::cast_to<Window>(SceneTree::get_singleton()->get_root());175if (w) {176// Calling Window methods this early doesn't sync properties with DS.177w->set_min_size(minimum_size);178DisplayServer::get_singleton()->window_set_min_size(minimum_size);179}180Size2 real_size = DisplayServer::get_singleton()->window_get_size();181182Rect2i screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(DisplayServer::get_singleton()->window_get_current_screen());183if (screen_rect.size != Vector2i()) {184// Center the window on the screen.185Vector2i window_position;186window_position.x = screen_rect.position.x + (screen_rect.size.x - real_size.x) / 2;187window_position.y = screen_rect.position.y + (screen_rect.size.y - real_size.y) / 2;188189// Limit popup menus to prevent unusably long lists.190// We try to set it to half the screen resolution, but no smaller than the minimum window size.191Size2 half_screen_rect = (screen_rect.size * EDSCALE) / 2;192Size2 maximum_popup_size = MAX(half_screen_rect, minimum_size);193quick_settings_dialog->update_size_limits(maximum_popup_size);194}195}196197void ProjectManager::_update_theme(bool p_skip_creation) {198if (!p_skip_creation) {199theme = EditorThemeManager::generate_theme(theme);200DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor)));201}202203Vector<Ref<Theme>> editor_themes;204editor_themes.push_back(theme);205editor_themes.push_back(ThemeDB::get_singleton()->get_default_theme());206207ThemeContext *node_tc = ThemeDB::get_singleton()->get_theme_context(this);208if (node_tc) {209node_tc->set_themes(editor_themes);210} else {211ThemeDB::get_singleton()->create_theme_context(this, editor_themes);212}213214Window *owner_window = get_window();215if (owner_window) {216ThemeContext *window_tc = ThemeDB::get_singleton()->get_theme_context(owner_window);217if (window_tc) {218window_tc->set_themes(editor_themes);219} else {220ThemeDB::get_singleton()->create_theme_context(owner_window, editor_themes);221}222}223224// Update styles.225{226const int top_bar_separation = get_theme_constant(SNAME("top_bar_separation"), EditorStringName(Editor));227root_container->add_theme_constant_override("margin_left", top_bar_separation);228root_container->add_theme_constant_override("margin_top", top_bar_separation);229root_container->add_theme_constant_override("margin_bottom", top_bar_separation);230root_container->add_theme_constant_override("margin_right", top_bar_separation);231main_vbox->add_theme_constant_override("separation", top_bar_separation);232233background_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("Background"), EditorStringName(EditorStyles)));234main_view_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("TabContainer")));235236title_bar_logo->set_button_icon(get_editor_theme_icon(SNAME("TitleBarLogo")));237238_set_main_view_icon(MAIN_VIEW_PROJECTS, get_editor_theme_icon(SNAME("ProjectList")));239_set_main_view_icon(MAIN_VIEW_ASSETLIB, get_editor_theme_icon(SNAME("AssetLib")));240241// Project list.242{243loading_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));244project_list_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("project_list"), SNAME("ProjectManager")));245246empty_list_create_project->set_button_icon(get_editor_theme_icon(SNAME("Add")));247empty_list_import_project->set_button_icon(get_editor_theme_icon(SNAME("Load")));248empty_list_open_assetlib->set_button_icon(get_editor_theme_icon(SNAME("AssetLib")));249250empty_list_online_warning->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("italic"), EditorStringName(EditorFonts)));251empty_list_online_warning->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("font_placeholder_color"), EditorStringName(Editor)));252253// Top bar.254search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));255quick_settings_button->set_button_icon(get_editor_theme_icon(SNAME("Tools")));256257// Sidebar.258create_btn->set_button_icon(get_editor_theme_icon(SNAME("Add")));259import_btn->set_button_icon(get_editor_theme_icon(SNAME("Load")));260scan_btn->set_button_icon(get_editor_theme_icon(SNAME("Search")));261open_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));262open_options_btn->set_button_icon(get_editor_theme_icon(SNAME("Collapse")));263run_btn->set_button_icon(get_editor_theme_icon(SNAME("Play")));264rename_btn->set_button_icon(get_editor_theme_icon(SNAME("Rename")));265duplicate_btn->set_button_icon(get_editor_theme_icon(SNAME("Duplicate")));266manage_tags_btn->set_button_icon(get_editor_theme_icon("Script"));267erase_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));268erase_missing_btn->set_button_icon(get_editor_theme_icon(SNAME("Clear")));269create_tag_btn->set_button_icon(get_editor_theme_icon("Add"));270271tag_error->add_theme_color_override(SceneStringName(font_color), get_theme_color("error_color", EditorStringName(Editor)));272tag_edit_error->add_theme_color_override(SceneStringName(font_color), get_theme_color("error_color", EditorStringName(Editor)));273274create_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));275import_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));276scan_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));277open_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));278run_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));279rename_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));280duplicate_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));281manage_tags_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));282erase_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));283erase_missing_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));284285open_btn_container->add_theme_constant_override("separation", 0);286open_options_popup->set_item_icon(0, get_editor_theme_icon(SNAME("Notification")));287open_options_popup->set_item_icon(1, get_editor_theme_icon(SNAME("NodeWarning")));288}289290// Dialogs291migration_guide_button->set_button_icon(get_editor_theme_icon(SNAME("ExternalLink")));292293// Asset library popup.294if (asset_library) {295// Removes extra border margins.296asset_library->add_theme_style_override(SceneStringName(panel), memnew(StyleBoxEmpty));297}298}299}300301Button *ProjectManager::_add_main_view(MainViewTab p_id, const String &p_name, const Ref<Texture2D> &p_icon, Control *p_view_control) {302ERR_FAIL_INDEX_V(p_id, MAIN_VIEW_MAX, nullptr);303ERR_FAIL_COND_V(main_view_map.has(p_id), nullptr);304ERR_FAIL_COND_V(main_view_toggle_map.has(p_id), nullptr);305306Button *toggle_button = memnew(Button);307toggle_button->set_flat(true);308toggle_button->set_theme_type_variation("MainScreenButton");309toggle_button->set_toggle_mode(true);310toggle_button->set_button_group(main_view_toggles_group);311toggle_button->set_text(p_name);312toggle_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_select_main_view).bind((int)p_id));313314main_view_toggles->add_child(toggle_button);315main_view_toggle_map[p_id] = toggle_button;316317_set_main_view_icon(p_id, p_icon);318319p_view_control->set_visible(false);320main_view_container->add_child(p_view_control);321main_view_map[p_id] = p_view_control;322323return toggle_button;324}325326void ProjectManager::_set_main_view_icon(MainViewTab p_id, const Ref<Texture2D> &p_icon) {327ERR_FAIL_INDEX(p_id, MAIN_VIEW_MAX);328ERR_FAIL_COND(!main_view_toggle_map.has(p_id));329330Button *toggle_button = main_view_toggle_map[p_id];331332Ref<Texture2D> old_icon = toggle_button->get_button_icon();333if (old_icon.is_valid()) {334old_icon->disconnect_changed(callable_mp((Control *)toggle_button, &Control::update_minimum_size));335}336337if (p_icon.is_valid()) {338toggle_button->set_button_icon(p_icon);339// Make sure the control is updated if the icon is reimported.340p_icon->connect_changed(callable_mp((Control *)toggle_button, &Control::update_minimum_size));341} else {342toggle_button->set_button_icon(Ref<Texture2D>());343}344}345346void ProjectManager::_select_main_view(int p_id) {347MainViewTab view_id = (MainViewTab)p_id;348349ERR_FAIL_INDEX(view_id, MAIN_VIEW_MAX);350ERR_FAIL_COND(!main_view_map.has(view_id));351ERR_FAIL_COND(!main_view_toggle_map.has(view_id));352353if (current_main_view != view_id) {354main_view_toggle_map[current_main_view]->set_pressed_no_signal(false);355main_view_map[current_main_view]->set_visible(false);356current_main_view = view_id;357}358main_view_toggle_map[current_main_view]->set_pressed_no_signal(true);359main_view_map[current_main_view]->set_visible(true);360361#ifndef ANDROID_ENABLED362if (current_main_view == MAIN_VIEW_PROJECTS && search_box->is_inside_tree()) {363// Automatically grab focus when the user moves from the Templates tab364// back to the Projects tab.365search_box->grab_focus();366}367368// The Templates tab's search field is focused on display in the asset369// library editor plugin code.370#endif371}372373void ProjectManager::_show_about() {374about_dialog->popup_centered(Size2(780, 500) * EDSCALE);375}376377void ProjectManager::_open_asset_library_confirmed() {378const int network_mode = EDITOR_GET("network/connection/network_mode");379if (network_mode == EditorSettings::NETWORK_OFFLINE) {380EditorSettings::get_singleton()->set_setting("network/connection/network_mode", EditorSettings::NETWORK_ONLINE);381EditorSettings::get_singleton()->notify_changes();382EditorSettings::get_singleton()->save();383}384385asset_library->disable_community_support();386_select_main_view(MAIN_VIEW_ASSETLIB);387}388389void ProjectManager::_show_error(const String &p_message, const Size2 &p_min_size) {390error_dialog->set_text(p_message);391error_dialog->popup_centered(p_min_size);392}393394void ProjectManager::_dim_window() {395// This method must be called before calling `get_tree()->quit()`.396// Otherwise, its effect won't be visible397398// Dim the project manager window while it's quitting to make it clearer that it's busy.399// No transition is applied, as the effect needs to be visible immediately400float c = 0.5f;401Color dim_color = Color(c, c, c);402set_modulate(dim_color);403}404405// Quick settings.406407void ProjectManager::_show_quick_settings() {408quick_settings_dialog->popup_centered(Size2(640, 200) * EDSCALE);409}410411void ProjectManager::_restart_confirmed() {412List<String> args = OS::get_singleton()->get_cmdline_args();413Error err = OS::get_singleton()->create_instance(args);414ERR_FAIL_COND(err);415416_dim_window();417get_tree()->quit();418}419420// Project list.421422void ProjectManager::_update_list_placeholder() {423if (project_list->get_project_count() > 0) {424empty_list_placeholder->hide();425return;426}427428empty_list_open_assetlib->set_visible(asset_library);429430const int network_mode = EDITOR_GET("network/connection/network_mode");431if (network_mode == EditorSettings::NETWORK_OFFLINE) {432empty_list_open_assetlib->set_text(TTRC("Go Online and Open Asset Library"));433empty_list_online_warning->set_visible(true);434} else {435empty_list_open_assetlib->set_text(TTRC("Open Asset Library"));436empty_list_online_warning->set_visible(false);437}438439empty_list_placeholder->show();440}441442void ProjectManager::_scan_projects() {443scan_dir->popup_file_dialog();444}445446void ProjectManager::_run_project() {447const HashSet<String> &selected_list = project_list->get_selected_project_keys();448449if (selected_list.size() < 1) {450return;451}452453if (selected_list.size() > 1) {454multi_run_ask->set_text(vformat(TTR("Are you sure to run %d projects at once?"), selected_list.size()));455multi_run_ask->popup_centered();456} else {457_run_project_confirm();458}459}460461void ProjectManager::_run_project_confirm() {462Vector<ProjectList::Item> selected_list = project_list->get_selected_projects();463464for (int i = 0; i < selected_list.size(); ++i) {465const String &selected_main = selected_list[i].main_scene;466if (selected_main.is_empty()) {467_show_error(TTRC("Can't run project: Project has no main scene defined.\nPlease edit the project and set the main scene in the Project Settings under the \"Application\" category."));468continue;469}470471const String &path = selected_list[i].path;472473// `.substr(6)` on `ProjectSettings::get_singleton()->get_imported_files_path()` strips away the leading "res://".474if (!DirAccess::exists(path.path_join(ProjectSettings::get_singleton()->get_imported_files_path().substr(6)))) {475_show_error(TTRC("Can't run project: Assets need to be imported first.\nPlease edit the project to trigger the initial import."));476continue;477}478479print_line("Running project: " + path);480481List<String> args;482483for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_PROJECT)) {484args.push_back(a);485}486487args.push_back("--path");488args.push_back(path);489490Error err = OS::get_singleton()->create_instance(args);491ERR_FAIL_COND(err);492}493}494495void ProjectManager::_open_selected_projects() {496// Show loading text to tell the user that the project manager is busy loading.497// This is especially important for the Web project manager.498loading_label->show();499500const HashSet<String> &selected_list = project_list->get_selected_project_keys();501for (const String &path : selected_list) {502String conf = path.path_join("project.godot");503504if (!FileAccess::exists(conf)) {505loading_label->hide();506_show_error(vformat(TTR("Can't open project at '%s'.\nProject file doesn't exist or is inaccessible."), path));507return;508}509510print_line("Editing project: " + path);511512List<String> args;513514for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {515args.push_back(a);516}517518args.push_back("--path");519args.push_back(path);520521args.push_back("--editor");522523if (open_in_recovery_mode) {524args.push_back("--recovery-mode");525}526527if (open_in_verbose_mode) {528args.push_back("--verbose");529}530531Error err = OS::get_singleton()->create_instance(args);532if (err != OK) {533loading_label->hide();534_show_error(vformat(TTR("Can't open project at '%s'.\nFailed to start the editor."), path));535ERR_PRINT(vformat("Failed to start an editor instance for the project at '%s', error code %d.", path, err));536return;537}538}539540project_list->project_opening_initiated = true;541542_dim_window();543get_tree()->quit();544}545546void ProjectManager::_open_selected_projects_check_warnings() {547const HashSet<String> &selected_list = project_list->get_selected_project_keys();548if (selected_list.size() < 1) {549return;550}551552const Size2i popup_min_size = Size2i(400.0 * EDSCALE, 0);553554if (selected_list.size() > 1) {555multi_open_ask->set_text(vformat(TTR("You requested to open %d projects in parallel. Do you confirm?\nNote that usual checks for engine version compatibility will be bypassed."), selected_list.size()));556multi_open_ask->popup_centered(popup_min_size);557return;558}559560ProjectList::Item project = project_list->get_selected_projects()[0];561if (project.missing) {562return;563}564565// Update the project settings or don't open.566const int config_version = project.version;567PackedStringArray unsupported_features = project.unsupported_features;568569ask_update_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); // Reset in case of previous center align.570ask_update_backup->set_pressed(false);571full_convert_button->hide();572migration_guide_button->hide();573ask_update_backup->hide();574575ask_update_settings->get_ok_button()->set_text("OK");576577// Check if the config_version property was empty or 0.578if (config_version == 0) {579ask_update_label->set_text(vformat(TTR("The selected project \"%s\" does not specify its supported Godot version in its configuration file (\"project.godot\").\n\nProject path: %s\n\nIf you proceed with opening it, it will be converted to Godot's current configuration file format.\n\nWarning: You won't be able to open the project with previous versions of the engine anymore."), project.project_name, project.path));580ask_update_settings->popup_centered(popup_min_size);581return;582}583// Check if we need to convert project settings from an earlier engine version.584if (config_version < ProjectSettings::CONFIG_VERSION) {585if (config_version == GODOT4_CONFIG_VERSION - 1 && ProjectSettings::CONFIG_VERSION == GODOT4_CONFIG_VERSION) { // Conversion from Godot 3 to 4.586full_convert_button->show();587ask_update_label->set_text(vformat(TTR("The selected project \"%s\" was generated by Godot 3.x, and needs to be converted for Godot 4.x.\n\nProject path: %s\n\nYou have three options:\n- Convert only the configuration file (\"project.godot\"). Use this to open the project without attempting to convert its scenes, resources and scripts.\n- Convert the entire project including its scenes, resources and scripts (recommended if you are upgrading).\n- Do nothing and go back.\n\nWarning: If you select a conversion option, you won't be able to open the project with previous versions of the engine anymore."), project.project_name, project.path));588ask_update_settings->get_ok_button()->set_text(TTRC("Convert project.godot Only"));589} else {590ask_update_label->set_text(vformat(TTR("The selected project \"%s\" was generated by an older engine version, and needs to be converted for this version.\n\nProject path: %s\n\nDo you want to convert it?\n\nWarning: You won't be able to open the project with previous versions of the engine anymore."), project.project_name, project.path));591ask_update_settings->get_ok_button()->set_text(TTRC("Convert project.godot"));592}593ask_update_backup->show();594migration_guide_button->show();595ask_update_settings->popup_centered(popup_min_size);596ask_update_settings->get_cancel_button()->grab_focus(); // To prevent accidents.597return;598}599// Check if the file was generated by a newer, incompatible engine version.600if (config_version > ProjectSettings::CONFIG_VERSION) {601_show_error(vformat(TTR("Can't open project \"%s\" at the following path:\n\n%s\n\nThe project settings were created by a newer engine version, whose settings are not compatible with this version."), project.project_name, project.path), popup_min_size);602return;603}604// Check if the project is using features not supported by this build of Godot.605if (!unsupported_features.is_empty()) {606String warning_message = "";607for (int i = 0; i < unsupported_features.size(); i++) {608const String &feature = unsupported_features[i];609if (feature == "Double Precision") {610ask_update_backup->show();611warning_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");612unsupported_features.remove_at(i);613i--;614} else if (feature == "C#") {615warning_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");616unsupported_features.remove_at(i);617i--;618} else if (ProjectList::project_feature_looks_like_version(feature)) {619ask_update_backup->show();620migration_guide_button->show();621version_convert_feature = feature;622warning_message += vformat(TTR("Warning: This project was last edited in Godot %s. Opening will change it to Godot %s.\n\n"), Variant(feature), Variant(GODOT_VERSION_BRANCH));623unsupported_features.remove_at(i);624i--;625}626}627if (!unsupported_features.is_empty()) {628String unsupported_features_str = String(", ").join(unsupported_features);629warning_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);630}631warning_message += TTR("Open anyway? Project will be modified.");632ask_update_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);633ask_update_label->set_text(warning_message);634ask_update_settings->popup_centered(popup_min_size);635return;636}637638// Open if the project is up-to-date.639_open_selected_projects();640}641642void ProjectManager::_open_selected_projects_check_recovery_mode() {643Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();644645if (selected_projects.is_empty()) {646return;647}648649const ProjectList::Item &project = selected_projects[0];650if (project.missing) {651return;652}653654open_in_verbose_mode = false;655open_in_recovery_mode = false;656// Check if the project failed to load during last startup.657if (project.recovery_mode) {658_open_recovery_mode_ask(false);659return;660}661662_open_selected_projects_check_warnings();663}664665void ProjectManager::_open_selected_projects_with_migration() {666if (ask_update_backup->is_pressed() && project_list->get_selected_projects().size() == 1) {667ask_update_settings->hide();668ask_update_backup->set_pressed(false);669670_duplicate_project_with_action(POST_DUPLICATE_ACTION_OPEN);671return;672}673674#ifndef DISABLE_DEPRECATED675if (project_list->get_selected_projects().size() == 1) {676// Only migrate if a single project is opened.677_minor_project_migrate();678}679#endif680_open_selected_projects();681}682683void ProjectManager::_install_project(const String &p_zip_path, const String &p_title) {684project_dialog->set_mode(ProjectDialog::MODE_INSTALL);685project_dialog->set_zip_path(p_zip_path);686project_dialog->set_zip_title(p_title);687project_dialog->show_dialog();688}689690void ProjectManager::_import_project() {691project_dialog->set_mode(ProjectDialog::MODE_IMPORT);692project_dialog->ask_for_path_and_show();693}694695void ProjectManager::_new_project() {696project_dialog->set_mode(ProjectDialog::MODE_NEW);697project_dialog->show_dialog();698}699700void ProjectManager::_rename_project() {701const Vector<ProjectList::Item> &selected_list = project_list->get_selected_projects();702703if (selected_list.is_empty()) {704return;705}706707for (const ProjectList::Item &E : selected_list) {708project_dialog->set_project_name(E.project_name);709project_dialog->set_project_path(E.path);710project_dialog->set_mode(ProjectDialog::MODE_RENAME);711project_dialog->show_dialog();712}713}714715void ProjectManager::_duplicate_project() {716_duplicate_project_with_action(POST_DUPLICATE_ACTION_NONE);717}718719void ProjectManager::_duplicate_project_with_action(PostDuplicateAction p_post_action) {720Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();721if (selected_projects.is_empty()) {722return;723}724725post_duplicate_action = p_post_action;726727const ProjectList::Item &project = selected_projects[0];728729project_dialog->set_mode(ProjectDialog::MODE_DUPLICATE);730project_dialog->set_project_name(vformat("%s (%s)", project.project_name, p_post_action == POST_DUPLICATE_ACTION_NONE ? "Copy" : project.project_version));731project_dialog->set_original_project_path(project.path);732project_dialog->set_duplicate_can_edit(p_post_action == POST_DUPLICATE_ACTION_NONE);733project_dialog->show_dialog(false);734}735736void ProjectManager::_erase_project() {737const HashSet<String> &selected_list = project_list->get_selected_project_keys();738739if (selected_list.is_empty()) {740return;741}742743String confirm_message;744if (selected_list.size() >= 2) {745confirm_message = vformat(TTR("Remove %d projects from the list?"), selected_list.size());746} else {747confirm_message = TTRC("Remove this project from the list?");748}749750erase_ask_label->set_text(confirm_message);751//delete_project_contents->set_pressed(false);752erase_ask->popup_centered();753}754755void ProjectManager::_erase_missing_projects() {756erase_missing_ask->set_text(TTRC("Remove all missing projects from the list?\nThe project folders' contents won't be modified."));757erase_missing_ask->popup_centered();758}759760void ProjectManager::_erase_project_confirm() {761project_list->erase_selected_projects(false);762_update_project_buttons();763_update_list_placeholder();764}765766void ProjectManager::_erase_missing_projects_confirm() {767project_list->erase_missing_projects();768_update_project_buttons();769_update_list_placeholder();770}771772void ProjectManager::_update_project_buttons() {773Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();774bool empty_selection = selected_projects.is_empty();775776bool is_missing_project_selected = false;777for (int i = 0; i < selected_projects.size(); ++i) {778if (selected_projects[i].missing) {779is_missing_project_selected = true;780break;781}782}783784erase_btn->set_disabled(empty_selection);785open_btn->set_disabled(empty_selection || is_missing_project_selected);786open_options_btn->set_disabled(empty_selection || is_missing_project_selected);787rename_btn->set_disabled(empty_selection || is_missing_project_selected);788duplicate_btn->set_disabled(empty_selection || is_missing_project_selected);789manage_tags_btn->set_disabled(empty_selection || is_missing_project_selected || selected_projects.size() > 1);790run_btn->set_disabled(empty_selection || is_missing_project_selected);791792erase_missing_btn->set_disabled(!project_list->is_any_project_missing());793}794795void ProjectManager::_open_options_popup() {796Rect2 rect = open_btn_container->get_screen_rect();797rect.position.y += rect.size.height;798open_options_popup->set_size(Size2(rect.size.width, 0));799open_options_popup->set_position(rect.position);800801open_options_popup->popup();802}803804void ProjectManager::_open_recovery_mode_ask(bool manual) {805String recovery_mode_details;806807// Only show the initial crash preamble if this popup wasn't manually triggered.808if (!manual) {809recovery_mode_details +=810TTR("It looks like Godot crashed when opening this project the last time. If you're having problems editing this project, you can try to open it in Recovery Mode.") +811String::utf8("\n\n");812}813814recovery_mode_details +=815TTR("Recovery Mode is a special mode that may help to recover projects that crash the engine during initialization. This mode temporarily disables the following features:") +816String::utf8("\n\n• ") + TTR("Tool scripts") +817String::utf8("\n• ") + TTR("Editor plugins") +818String::utf8("\n• ") + TTR("GDExtension addons") +819String::utf8("\n• ") + TTR("Automatic scene restoring") +820String::utf8("\n\n") + TTR("This mode is intended only for basic editing to troubleshoot such issues, and therefore it will not be possible to run the project during this mode. It is also a good idea to make a backup of your project before proceeding.") +821String::utf8("\n\n") + TTR("Edit the project in Recovery Mode?");822823open_recovery_mode_ask->set_text(recovery_mode_details);824open_recovery_mode_ask->popup_centered(Size2(550, 70) * EDSCALE);825}826827void ProjectManager::_on_projects_updated() {828Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();829int index = 0;830for (int i = 0; i < selected_projects.size(); ++i) {831index = project_list->refresh_project(selected_projects[i].path);832}833if (index != -1) {834project_list->ensure_project_visible(index);835}836837project_list->update_dock_menu();838}839840void ProjectManager::_on_open_options_selected(int p_option) {841switch (p_option) {842case 0: // Edit in verbose mode.843open_in_verbose_mode = true;844_open_selected_projects_check_warnings();845break;846case 1: // Edit in recovery mode.847_open_recovery_mode_ask(true);848break;849}850}851852void ProjectManager::_on_recovery_mode_popup_open_normal() {853open_recovery_mode_ask->hide();854open_in_recovery_mode = false;855_open_selected_projects_check_warnings();856}857858void ProjectManager::_on_recovery_mode_popup_open_recovery() {859open_in_recovery_mode = true;860_open_selected_projects_check_warnings();861}862863void ProjectManager::_on_project_created(const String &dir, bool edit) {864project_list->add_project(dir, false);865project_list->save_config();866search_box->clear();867868int i = project_list->refresh_project(dir);869project_list->select_project(i);870project_list->ensure_project_visible(i);871_update_project_buttons();872_update_list_placeholder();873874if (edit) {875_open_selected_projects_check_warnings();876}877878project_list->update_dock_menu();879}880881void ProjectManager::_on_project_duplicated(const String &p_original_path, const String &p_duplicate_path, bool p_edit) {882if (post_duplicate_action == POST_DUPLICATE_ACTION_NONE) {883_on_project_created(p_duplicate_path, p_edit);884} else {885project_list->add_project(p_duplicate_path, false);886project_list->save_config();887888if (post_duplicate_action == POST_DUPLICATE_ACTION_OPEN) {889_open_selected_projects_with_migration();890} else if (post_duplicate_action == POST_DUPLICATE_ACTION_FULL_CONVERSION) {891_full_convert_button_pressed();892}893894project_list->update_dock_menu();895}896897post_duplicate_action = POST_DUPLICATE_ACTION_NONE;898}899900void ProjectManager::_on_order_option_changed(int p_idx) {901if (is_inside_tree()) {902project_list->set_order_option(p_idx);903}904}905906void ProjectManager::_on_search_term_changed(const String &p_term) {907project_list->set_search_term(p_term);908project_list->sort_projects();909910// Select the first visible project in the list.911// This makes it possible to open a project without ever touching the mouse,912// as the search field is automatically focused on startup.913project_list->select_first_visible_project();914_update_project_buttons();915}916917void ProjectManager::_on_search_term_submitted(const String &p_text) {918if (current_main_view != MAIN_VIEW_PROJECTS) {919return;920}921922_open_selected_projects_check_recovery_mode();923}924925LineEdit *ProjectManager::get_search_box() {926return search_box;927}928929// Project tag management.930931void ProjectManager::_manage_project_tags() {932for (int i = 0; i < project_tags->get_child_count(); i++) {933project_tags->get_child(i)->queue_free();934}935936const ProjectList::Item item = project_list->get_selected_projects()[0];937current_project_tags = item.tags;938for (const String &tag : current_project_tags) {939ProjectTag *tag_control = memnew(ProjectTag(tag, true));940project_tags->add_child(tag_control);941tag_control->connect_button_to(callable_mp(this, &ProjectManager::_delete_project_tag).bind(tag));942}943944tag_edit_error->hide();945tag_manage_dialog->popup_centered(Vector2i(500, 0) * EDSCALE);946}947948void ProjectManager::_add_project_tag(const String &p_tag) {949if (current_project_tags.has(p_tag)) {950return;951}952current_project_tags.append(p_tag);953954ProjectTag *tag_control = memnew(ProjectTag(p_tag, true));955project_tags->add_child(tag_control);956tag_control->connect_button_to(callable_mp(this, &ProjectManager::_delete_project_tag).bind(p_tag));957}958959void ProjectManager::_delete_project_tag(const String &p_tag) {960current_project_tags.erase(p_tag);961for (int i = 0; i < project_tags->get_child_count(); i++) {962ProjectTag *tag_control = Object::cast_to<ProjectTag>(project_tags->get_child(i));963if (tag_control && tag_control->get_tag() == p_tag) {964memdelete(tag_control);965break;966}967}968}969970void ProjectManager::_apply_project_tags() {971PackedStringArray tags;972for (int i = 0; i < project_tags->get_child_count(); i++) {973ProjectTag *tag_control = Object::cast_to<ProjectTag>(project_tags->get_child(i));974if (tag_control) {975tags.append(tag_control->get_tag());976}977}978979const String project_godot = project_list->get_selected_projects()[0].path.path_join("project.godot");980ProjectSettings *cfg = memnew(ProjectSettings(project_godot));981if (!cfg->is_project_loaded()) {982memdelete(cfg);983tag_edit_error->set_text(vformat(TTR("Couldn't load project at '%s'. It may be missing or corrupted."), project_godot));984tag_edit_error->show();985callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred(); // Make sure the dialog does not disappear.986return;987} else {988tags.sort();989cfg->set("application/config/tags", tags);990Error err = cfg->save_custom(project_godot);991memdelete(cfg);992993if (err != OK) {994tag_edit_error->set_text(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err));995tag_edit_error->show();996callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred();997return;998}999}10001001_on_projects_updated();1002}10031004void ProjectManager::_set_new_tag_name(const String p_name) {1005create_tag_dialog->get_ok_button()->set_disabled(true);1006if (p_name.is_empty()) {1007tag_error->set_text(TTRC("Tag name can't be empty."));1008return;1009}10101011if (p_name.contains_char(' ')) {1012tag_error->set_text(TTRC("Tag name can't contain spaces."));1013return;1014}10151016if (p_name[0] == '_' || p_name[p_name.length() - 1] == '_') {1017tag_error->set_text(TTRC("Tag name can't begin or end with underscore."));1018return;1019}10201021bool was_underscore = false;1022for (const char32_t &c : p_name.span()) {1023if (c == '_') {1024if (was_underscore) {1025tag_error->set_text(TTRC("Tag name can't contain consecutive underscores."));1026return;1027}1028was_underscore = true;1029} else {1030was_underscore = false;1031}1032}10331034for (const String &c : forbidden_tag_characters) {1035if (p_name.contains(c)) {1036tag_error->set_text(vformat(TTRC("These characters are not allowed in tags: %s."), String(" ").join(forbidden_tag_characters)));1037return;1038}1039}10401041if (p_name.to_lower() != p_name) {1042tag_error->set_text(TTRC("Tag name must be lowercase."));1043return;1044}10451046tag_error->set_text("");1047create_tag_dialog->get_ok_button()->set_disabled(false);1048}10491050void ProjectManager::_create_new_tag() {1051if (!tag_error->get_text().is_empty()) {1052return;1053}1054create_tag_dialog->hide(); // When using text_submitted, need to hide manually.1055add_new_tag(new_tag_name->get_text());1056_add_project_tag(new_tag_name->get_text());1057}10581059void ProjectManager::add_new_tag(const String &p_tag) {1060if (!tag_set.has(p_tag)) {1061tag_set.insert(p_tag);1062ProjectTag *tag_control = memnew(ProjectTag(p_tag));1063all_tags->add_child(tag_control);1064all_tags->move_child(tag_control, -2);1065tag_control->connect_button_to(callable_mp(this, &ProjectManager::_add_project_tag).bind(p_tag));1066}1067}10681069// Project converter/migration tool.10701071#ifndef DISABLE_DEPRECATED1072void ProjectManager::_minor_project_migrate() {1073const ProjectList::Item migrated_project = project_list->get_selected_projects()[0];10741075if (version_convert_feature.begins_with("4.3")) {1076// Migrate layout after scale changes.1077const float edscale = EDSCALE;1078if (edscale != 1.0) {1079Ref<ConfigFile> layout_file;1080layout_file.instantiate();10811082const String layout_path = migrated_project.path.path_join(".godot/editor/editor_layout.cfg");1083Error err = layout_file->load(layout_path);1084if (err == OK) {1085for (int i = 0; i < 4; i++) {1086const String key = "dock_hsplit_" + itos(i + 1);1087int old_value = layout_file->get_value("docks", key, 0);1088if (old_value != 0) {1089layout_file->set_value("docks", key, old_value / edscale);1090}1091}1092layout_file->save(layout_path);1093}1094}1095}1096}1097#endif10981099void ProjectManager::_full_convert_button_pressed() {1100ask_update_settings->hide();11011102if (ask_update_backup->is_pressed()) {1103ask_update_backup->set_pressed(false);11041105_duplicate_project_with_action(POST_DUPLICATE_ACTION_FULL_CONVERSION);1106return;1107}11081109ask_full_convert_dialog->popup_centered(Size2i(600.0 * EDSCALE, 0));1110ask_full_convert_dialog->get_cancel_button()->grab_focus();1111}11121113void ProjectManager::_migration_guide_button_pressed() {1114const String url = vformat("%s/tutorials/migrating/index.html", GODOT_VERSION_DOCS_URL);1115OS::get_singleton()->shell_open(url);1116}11171118void ProjectManager::_perform_full_project_conversion() {1119Vector<ProjectList::Item> selected_list = project_list->get_selected_projects();1120if (selected_list.is_empty()) {1121return;1122}11231124const String &path = selected_list[0].path;11251126print_line("Converting project: " + path);1127List<String> args;1128args.push_back("--path");1129args.push_back(path);1130args.push_back("--convert-3to4");1131args.push_back("--rendering-driver");1132args.push_back(Main::get_rendering_driver_name());11331134Error err = OS::get_singleton()->create_instance(args);1135ERR_FAIL_COND(err);11361137project_list->set_project_version(path, GODOT4_CONFIG_VERSION);1138}11391140// Input and I/O.11411142void ProjectManager::shortcut_input(const Ref<InputEvent> &p_ev) {1143ERR_FAIL_COND(p_ev.is_null());11441145Ref<InputEventKey> k = p_ev;11461147if (k.is_valid()) {1148if (!k->is_pressed()) {1149return;1150}11511152// Pressing Command + Q quits the Project Manager1153// This is handled by the platform implementation on macOS,1154// so only define the shortcut on other platforms1155#ifndef MACOS_ENABLED1156if (k->get_keycode_with_modifiers() == (KeyModifierMask::META | Key::Q)) {1157_dim_window();1158get_tree()->quit();1159}1160#endif11611162if (current_main_view != MAIN_VIEW_PROJECTS) {1163return;1164}11651166bool keycode_handled = true;11671168switch (k->get_keycode()) {1169case Key::ENTER: {1170_open_selected_projects_check_recovery_mode();1171} break;1172case Key::HOME: {1173if (project_list->get_project_count() > 0) {1174project_list->select_project(0);1175_update_project_buttons();1176}11771178} break;1179case Key::END: {1180if (project_list->get_project_count() > 0) {1181project_list->select_project(project_list->get_project_count() - 1);1182_update_project_buttons();1183}11841185} break;1186case Key::UP: {1187if (k->is_shift_pressed()) {1188break;1189}11901191int index = project_list->get_single_selected_index();1192if (index > 0) {1193project_list->select_project(index - 1);1194project_list->ensure_project_visible(index - 1);1195_update_project_buttons();1196}11971198break;1199}1200case Key::DOWN: {1201if (k->is_shift_pressed()) {1202break;1203}12041205int index = project_list->get_single_selected_index();1206if (index + 1 < project_list->get_project_count()) {1207project_list->select_project(index + 1);1208project_list->ensure_project_visible(index + 1);1209_update_project_buttons();1210}12111212} break;1213case Key::F: {1214if (k->is_command_or_control_pressed()) {1215search_box->grab_focus();1216} else {1217keycode_handled = false;1218}1219} break;1220default: {1221keycode_handled = false;1222} break;1223}12241225if (keycode_handled) {1226accept_event();1227}1228}1229}12301231void ProjectManager::_files_dropped(PackedStringArray p_files) {1232// TODO: Support installing multiple ZIPs at the same time?1233if (p_files.size() == 1 && p_files[0].ends_with(".zip")) {1234const String &file = p_files[0];1235_install_project(file, file.get_file().get_basename().capitalize());1236return;1237}12381239HashSet<String> folders_set;1240Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);1241for (int i = 0; i < p_files.size(); i++) {1242const String &file = p_files[i];1243folders_set.insert(da->dir_exists(file) ? file : file.get_base_dir());1244}1245ERR_FAIL_COND(folders_set.is_empty()); // This can't really happen, we consume every dropped file path above.12461247PackedStringArray folders;1248for (const String &E : folders_set) {1249folders.push_back(E);1250}1251project_list->find_projects_multiple(folders);1252}12531254void ProjectManager::_titlebar_resized() {1255DisplayServer::get_singleton()->window_set_window_buttons_offset(Vector2i(title_bar->get_global_position().y + title_bar->get_size().y / 2, title_bar->get_global_position().y + title_bar->get_size().y / 2), DisplayServer::MAIN_WINDOW_ID);1256const Vector3i &margin = DisplayServer::get_singleton()->window_get_safe_title_margins(DisplayServer::MAIN_WINDOW_ID);1257if (left_menu_spacer) {1258int w = (root_container->is_layout_rtl()) ? margin.y : margin.x;1259left_menu_spacer->set_custom_minimum_size(Size2(w, 0));1260}1261if (right_menu_spacer) {1262int w = (root_container->is_layout_rtl()) ? margin.x : margin.y;1263right_menu_spacer->set_custom_minimum_size(Size2(w, 0));1264}1265if (title_bar) {1266title_bar->set_custom_minimum_size(Size2(0, margin.z - title_bar->get_global_position().y));1267}1268}12691270// Object methods.12711272ProjectManager::ProjectManager() {1273singleton = this;12741275// Turn off some servers we aren't going to be using in the Project Manager.1276NavigationServer3D::get_singleton()->set_active(false);1277PhysicsServer3D::get_singleton()->set_active(false);1278PhysicsServer2D::get_singleton()->set_active(false);12791280// Initialize settings.1281{1282if (!EditorSettings::get_singleton()) {1283EditorSettings::create();1284}1285EditorSettings::get_singleton()->set_optimize_save(false); // Just write settings as they come.12861287{1288bool agile_input_event_flushing = EDITOR_GET("input/buffering/agile_event_flushing");1289bool use_accumulated_input = EDITOR_GET("input/buffering/use_accumulated_input");12901291Input::get_singleton()->set_agile_input_event_flushing(agile_input_event_flushing);1292Input::get_singleton()->set_use_accumulated_input(use_accumulated_input);1293}12941295int display_scale = EDITOR_GET("interface/editor/display_scale");12961297switch (display_scale) {1298case 0:1299// Try applying a suitable display scale automatically.1300EditorScale::set_scale(EditorSettings::get_auto_display_scale());1301break;1302case 1:1303EditorScale::set_scale(0.75);1304break;1305case 2:1306EditorScale::set_scale(1.0);1307break;1308case 3:1309EditorScale::set_scale(1.25);1310break;1311case 4:1312EditorScale::set_scale(1.5);1313break;1314case 5:1315EditorScale::set_scale(1.75);1316break;1317case 6:1318EditorScale::set_scale(2.0);1319break;1320default:1321EditorScale::set_scale(EDITOR_GET("interface/editor/custom_display_scale"));1322break;1323}1324EditorFileDialog::get_icon_func = &ProjectManager::_file_dialog_get_icon;1325EditorFileDialog::get_thumbnail_func = &ProjectManager::_file_dialog_get_thumbnail;13261327EditorFileDialog::set_default_show_hidden_files(EDITOR_GET("filesystem/file_dialog/show_hidden_files"));1328EditorFileDialog::set_default_display_mode((EditorFileDialog::DisplayMode)EDITOR_GET("filesystem/file_dialog/display_mode").operator int());13291330int swap_cancel_ok = EDITOR_GET("interface/editor/accept_dialog_cancel_ok_buttons");1331if (swap_cancel_ok != 0) { // 0 is auto, set in register_scene based on DisplayServer.1332// Swap on means OK first.1333AcceptDialog::set_swap_cancel_ok(swap_cancel_ok == 2);1334}13351336OS::get_singleton()->set_low_processor_usage_mode(true);1337}13381339SceneTree::get_singleton()->get_root()->connect("files_dropped", callable_mp(this, &ProjectManager::_files_dropped));13401341// Initialize UI.1342{1343int pm_root_dir = EDITOR_GET("interface/editor/ui_layout_direction");1344Control::set_root_layout_direction(pm_root_dir);1345Window::set_root_layout_direction(pm_root_dir);13461347EditorThemeManager::initialize();1348theme = EditorThemeManager::generate_theme();1349DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor)));13501351set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);13521353_build_icon_type_cache(theme);1354}13551356// Project manager layout.13571358background_panel = memnew(Panel);1359add_child(background_panel);1360background_panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);13611362root_container = memnew(MarginContainer);1363add_child(root_container);1364root_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);13651366main_vbox = memnew(VBoxContainer);1367root_container->add_child(main_vbox);13681369// Title bar.1370bool can_expand = bool(EDITOR_GET("interface/editor/expand_to_title")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EXTEND_TO_TITLE);13711372{1373title_bar = memnew(EditorTitleBar);1374main_vbox->add_child(title_bar);13751376if (can_expand) {1377// Add spacer to avoid other controls under window minimize/maximize/close buttons (left side).1378left_menu_spacer = memnew(Control);1379left_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);1380title_bar->add_child(left_menu_spacer);1381}13821383HBoxContainer *left_hbox = memnew(HBoxContainer);1384left_hbox->set_alignment(BoxContainer::ALIGNMENT_BEGIN);1385left_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);1386left_hbox->set_stretch_ratio(1.0);1387title_bar->add_child(left_hbox);13881389title_bar_logo = memnew(Button);1390title_bar_logo->set_flat(true);1391title_bar_logo->set_tooltip_text(TTR("About Godot"));1392left_hbox->add_child(title_bar_logo);1393title_bar_logo->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_show_about));13941395if (can_expand) {1396// Spacer to center main toggles.1397left_spacer = memnew(Control);1398left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);1399title_bar->add_child(left_spacer);1400}14011402main_view_toggles = memnew(HBoxContainer);1403main_view_toggles->set_alignment(BoxContainer::ALIGNMENT_CENTER);1404main_view_toggles->set_h_size_flags(Control::SIZE_EXPAND_FILL);1405main_view_toggles->set_stretch_ratio(2.0);1406title_bar->add_child(main_view_toggles);1407title_bar->set_center_control(main_view_toggles);14081409if (can_expand) {1410// Spacer to center main toggles.1411right_spacer = memnew(Control);1412right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);1413title_bar->add_child(right_spacer);1414}14151416main_view_toggles_group.instantiate();14171418HBoxContainer *right_hbox = memnew(HBoxContainer);1419right_hbox->set_alignment(BoxContainer::ALIGNMENT_END);1420right_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);1421right_hbox->set_stretch_ratio(1.0);1422title_bar->add_child(right_hbox);14231424quick_settings_button = memnew(Button);1425quick_settings_button->set_flat(true);1426quick_settings_button->set_text(TTRC("Settings"));1427right_hbox->add_child(quick_settings_button);1428quick_settings_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_show_quick_settings));14291430if (can_expand) {1431// Add spacer to avoid other controls under the window minimize/maximize/close buttons (right side).1432right_menu_spacer = memnew(Control);1433right_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);1434title_bar->add_child(right_menu_spacer);1435}1436}14371438main_view_container = memnew(PanelContainer);1439main_view_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);1440main_vbox->add_child(main_view_container);14411442// Project list view.1443{1444local_projects_vb = memnew(VBoxContainer);1445local_projects_vb->set_name("LocalProjectsTab");1446_add_main_view(MAIN_VIEW_PROJECTS, TTRC("Projects"), Ref<Texture2D>(), local_projects_vb);14471448// Project list's top bar.1449{1450HBoxContainer *hb = memnew(HBoxContainer);1451hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);1452local_projects_vb->add_child(hb);14531454create_btn = memnew(Button);1455create_btn->set_text(TTRC("Create"));1456create_btn->set_shortcut(ED_SHORTCUT("project_manager/new_project", TTRC("New Project"), KeyModifierMask::CMD_OR_CTRL | Key::N));1457create_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_new_project));1458hb->add_child(create_btn);14591460import_btn = memnew(Button);1461import_btn->set_text(TTRC("Import"));1462import_btn->set_shortcut(ED_SHORTCUT("project_manager/import_project", TTRC("Import Project"), KeyModifierMask::CMD_OR_CTRL | Key::I));1463import_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_import_project));1464hb->add_child(import_btn);14651466scan_btn = memnew(Button);1467scan_btn->set_text(TTRC("Scan"));1468scan_btn->set_shortcut(ED_SHORTCUT("project_manager/scan_projects", TTRC("Scan Projects"), KeyModifierMask::CMD_OR_CTRL | Key::S));1469scan_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_scan_projects));1470hb->add_child(scan_btn);14711472loading_label = memnew(Label(TTRC("Loading, please wait...")));1473loading_label->set_accessibility_live(DisplayServer::AccessibilityLiveMode::LIVE_ASSERTIVE);1474loading_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);1475loading_label->hide();1476hb->add_child(loading_label);14771478search_box = memnew(LineEdit);1479search_box->set_placeholder(TTRC("Filter Projects"));1480search_box->set_accessibility_name(TTRC("Filter Projects"));1481search_box->set_tooltip_text(TTRC("This field filters projects by name and last path component.\nTo filter projects by name and full path, the query must contain at least one `/` character."));1482search_box->set_clear_button_enabled(true);1483search_box->connect(SceneStringName(text_changed), callable_mp(this, &ProjectManager::_on_search_term_changed));1484search_box->connect(SceneStringName(text_submitted), callable_mp(this, &ProjectManager::_on_search_term_submitted));1485search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);1486hb->add_child(search_box);14871488sort_label = memnew(Label);1489sort_label->set_text(TTRC("Sort:"));1490hb->add_child(sort_label);14911492filter_option = memnew(OptionButton);1493filter_option->set_clip_text(true);1494filter_option->set_h_size_flags(Control::SIZE_EXPAND_FILL);1495filter_option->set_stretch_ratio(0.3);1496filter_option->set_accessibility_name(TTRC("Sort:"));1497filter_option->connect(SceneStringName(item_selected), callable_mp(this, &ProjectManager::_on_order_option_changed));1498hb->add_child(filter_option);14991500filter_option->add_item(TTRC("Last Edited"));1501filter_option->add_item(TTRC("Name"));1502filter_option->add_item(TTRC("Path"));1503filter_option->add_item(TTRC("Tags"));1504}15051506// Project list and its sidebar.1507{1508HBoxContainer *project_list_hbox = memnew(HBoxContainer);1509local_projects_vb->add_child(project_list_hbox);1510project_list_hbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);15111512project_list_panel = memnew(PanelContainer);1513project_list_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);1514project_list_hbox->add_child(project_list_panel);15151516project_list = memnew(ProjectList);1517project_list->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);1518project_list_panel->add_child(project_list);1519project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));1520project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_list_placeholder));1521project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));1522project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_check_recovery_mode));15231524// Empty project list placeholder.1525{1526empty_list_placeholder = memnew(VBoxContainer);1527empty_list_placeholder->set_v_size_flags(Control::SIZE_SHRINK_CENTER);1528empty_list_placeholder->add_theme_constant_override("separation", 16 * EDSCALE);1529empty_list_placeholder->hide();1530project_list_panel->add_child(empty_list_placeholder);15311532empty_list_message = memnew(RichTextLabel);1533empty_list_message->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);1534empty_list_message->set_use_bbcode(true);1535empty_list_message->set_fit_content(true);1536empty_list_message->set_h_size_flags(SIZE_EXPAND_FILL);1537empty_list_message->add_theme_style_override(CoreStringName(normal), memnew(StyleBoxEmpty));15381539empty_list_placeholder->add_child(empty_list_message);15401541FlowContainer *empty_list_actions = memnew(FlowContainer);1542empty_list_actions->set_alignment(FlowContainer::ALIGNMENT_CENTER);1543empty_list_placeholder->add_child(empty_list_actions);15441545empty_list_create_project = memnew(Button);1546empty_list_create_project->set_text(TTRC("Create New Project"));1547empty_list_create_project->set_theme_type_variation("PanelBackgroundButton");1548empty_list_actions->add_child(empty_list_create_project);1549empty_list_create_project->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_new_project));15501551empty_list_import_project = memnew(Button);1552empty_list_import_project->set_text(TTRC("Import Existing Project"));1553empty_list_import_project->set_theme_type_variation("PanelBackgroundButton");1554empty_list_actions->add_child(empty_list_import_project);1555empty_list_import_project->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_import_project));15561557empty_list_open_assetlib = memnew(Button);1558empty_list_open_assetlib->set_text(TTRC("Open Asset Library"));1559empty_list_open_assetlib->set_theme_type_variation("PanelBackgroundButton");1560empty_list_actions->add_child(empty_list_open_assetlib);1561empty_list_open_assetlib->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_asset_library_confirmed));15621563empty_list_online_warning = memnew(Label);1564empty_list_online_warning->set_focus_mode(FOCUS_ACCESSIBILITY);1565empty_list_online_warning->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);1566empty_list_online_warning->set_custom_minimum_size(Size2(220, 0) * EDSCALE);1567empty_list_online_warning->set_autowrap_mode(TextServer::AUTOWRAP_WORD);1568empty_list_online_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL);1569empty_list_online_warning->set_text(TTRC("Note: The Asset Library requires an online connection and involves sending data over the internet."));1570empty_list_placeholder->add_child(empty_list_online_warning);1571}15721573// The side bar with the edit, run, rename, etc. buttons.1574VBoxContainer *project_list_sidebar = memnew(VBoxContainer);1575project_list_sidebar->set_custom_minimum_size(Size2(120, 120));1576project_list_hbox->add_child(project_list_sidebar);15771578project_list_sidebar->add_child(memnew(HSeparator));15791580open_btn_container = memnew(HBoxContainer);1581open_btn_container->set_anchors_preset(Control::PRESET_FULL_RECT);1582project_list_sidebar->add_child(open_btn_container);15831584open_btn = memnew(Button);1585open_btn->set_text(TTRC("Edit"));1586open_btn->set_shortcut(ED_SHORTCUT("project_manager/edit_project", TTRC("Edit Project"), KeyModifierMask::CMD_OR_CTRL | Key::E));1587open_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_check_recovery_mode));1588open_btn->set_h_size_flags(Control::SIZE_EXPAND_FILL);1589open_btn_container->add_child(open_btn);15901591open_btn_container->add_child(memnew(VSeparator));15921593open_options_btn = memnew(Button);1594open_options_btn->set_accessibility_name(TTRC("Options"));1595open_options_btn->set_icon_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);1596open_options_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_options_popup));1597open_btn_container->add_child(open_options_btn);15981599open_options_popup = memnew(PopupMenu);1600open_options_popup->add_item(TTRC("Edit in verbose mode"));1601open_options_popup->add_item(TTRC("Edit in recovery mode"));1602open_options_popup->connect(SceneStringName(id_pressed), callable_mp(this, &ProjectManager::_on_open_options_selected));1603open_options_btn->add_child(open_options_popup);16041605open_btn_container->set_custom_minimum_size(Size2(120, open_btn->get_combined_minimum_size().y));16061607run_btn = memnew(Button);1608run_btn->set_text(TTRC("Run"));1609run_btn->set_shortcut(ED_SHORTCUT("project_manager/run_project", TTRC("Run Project"), KeyModifierMask::CMD_OR_CTRL | Key::R));1610run_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_run_project));1611project_list_sidebar->add_child(run_btn);16121613rename_btn = memnew(Button);1614rename_btn->set_text(TTRC("Rename"));1615// The F2 shortcut isn't overridden with Enter on macOS as Enter is already used to edit a project.1616rename_btn->set_shortcut(ED_SHORTCUT("project_manager/rename_project", TTRC("Rename Project"), Key::F2));1617rename_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_rename_project));1618project_list_sidebar->add_child(rename_btn);16191620duplicate_btn = memnew(Button);1621duplicate_btn->set_text(TTRC("Duplicate"));1622duplicate_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_duplicate_project));1623project_list_sidebar->add_child(duplicate_btn);16241625manage_tags_btn = memnew(Button);1626manage_tags_btn->set_text(TTRC("Manage Tags"));1627manage_tags_btn->set_shortcut(ED_SHORTCUT("project_manager/project_tags", TTRC("Manage Tags"), KeyModifierMask::CMD_OR_CTRL | Key::T));1628project_list_sidebar->add_child(manage_tags_btn);16291630erase_btn = memnew(Button);1631erase_btn->set_text(TTRC("Remove"));1632erase_btn->set_shortcut(ED_SHORTCUT("project_manager/remove_project", TTRC("Remove Project"), Key::KEY_DELETE));1633erase_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_project));1634project_list_sidebar->add_child(erase_btn);16351636Control *filler = memnew(Control);1637filler->set_v_size_flags(Control::SIZE_EXPAND_FILL);1638project_list_sidebar->add_child(filler);16391640erase_missing_btn = memnew(Button);1641erase_missing_btn->set_text(TTRC("Remove Missing"));1642erase_missing_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_missing_projects));1643project_list_sidebar->add_child(erase_missing_btn);1644}1645}16461647// Asset library view.1648if (AssetLibraryEditorPlugin::is_available()) {1649asset_library = memnew(EditorAssetLibrary(true));1650asset_library->set_name("AssetLibraryTab");1651_add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Library"), Ref<Texture2D>(), asset_library);1652asset_library->connect("install_asset", callable_mp(this, &ProjectManager::_install_project));1653} else {1654VBoxContainer *asset_library_filler = memnew(VBoxContainer);1655asset_library_filler->set_name("AssetLibraryTab");1656Button *asset_library_toggle = _add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Library"), Ref<Texture2D>(), asset_library_filler);1657asset_library_toggle->set_disabled(true);1658asset_library_toggle->set_tooltip_text(TTRC("Asset Library not available (due to using Web editor, or because SSL support disabled)."));1659}16601661// Footer bar.1662{1663HBoxContainer *footer_bar = memnew(HBoxContainer);1664footer_bar->set_alignment(BoxContainer::ALIGNMENT_END);1665footer_bar->add_theme_constant_override("separation", 20 * EDSCALE);1666main_vbox->add_child(footer_bar);16671668#ifdef ENGINE_UPDATE_CHECK_ENABLED1669EngineUpdateLabel *update_label = memnew(EngineUpdateLabel);1670footer_bar->add_child(update_label);1671update_label->connect("offline_clicked", callable_mp(this, &ProjectManager::_show_quick_settings));1672#endif16731674EditorVersionButton *version_btn = memnew(EditorVersionButton(EditorVersionButton::FORMAT_WITH_BUILD));1675// Fade the version label to be less prominent, but still readable.1676version_btn->set_self_modulate(Color(1, 1, 1, 0.6));1677footer_bar->add_child(version_btn);1678}16791680// Dialogs.1681{1682quick_settings_dialog = memnew(QuickSettingsDialog);1683add_child(quick_settings_dialog);1684quick_settings_dialog->connect("restart_required", callable_mp(this, &ProjectManager::_restart_confirmed));16851686scan_dir = memnew(EditorFileDialog);1687scan_dir->set_previews_enabled(false);1688scan_dir->set_access(EditorFileDialog::ACCESS_FILESYSTEM);1689scan_dir->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);1690scan_dir->set_title(TTRC("Select a Folder to Scan")); // Must be after mode or it's overridden.1691scan_dir->set_current_dir(EDITOR_GET("filesystem/directories/default_project_path"));1692add_child(scan_dir);1693scan_dir->connect("dir_selected", callable_mp(project_list, &ProjectList::find_projects));16941695erase_missing_ask = memnew(ConfirmationDialog);1696erase_missing_ask->set_ok_button_text(TTRC("Remove All"));1697erase_missing_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_missing_projects_confirm));1698add_child(erase_missing_ask);16991700erase_ask = memnew(ConfirmationDialog);1701erase_ask->set_ok_button_text(TTRC("Remove"));1702erase_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_project_confirm));1703add_child(erase_ask);17041705VBoxContainer *erase_ask_vb = memnew(VBoxContainer);1706erase_ask->add_child(erase_ask_vb);17071708erase_ask_label = memnew(Label);1709erase_ask_label->set_focus_mode(FOCUS_ACCESSIBILITY);1710erase_ask_vb->add_child(erase_ask_label);17111712// Comment out for now until we have a better warning system to1713// ensure users delete their project only.1714//delete_project_contents = memnew(CheckBox);1715//delete_project_contents->set_text(TTRC("Also delete project contents (no undo!)"));1716//erase_ask_vb->add_child(delete_project_contents);17171718multi_open_ask = memnew(ConfirmationDialog);1719multi_open_ask->set_ok_button_text(TTRC("Edit"));1720multi_open_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects));1721add_child(multi_open_ask);17221723multi_run_ask = memnew(ConfirmationDialog);1724multi_run_ask->set_ok_button_text(TTRC("Run"));1725multi_run_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_run_project_confirm));1726add_child(multi_run_ask);17271728open_recovery_mode_ask = memnew(ConfirmationDialog);1729open_recovery_mode_ask->set_min_size(Size2(550, 70) * EDSCALE);1730open_recovery_mode_ask->set_autowrap(true);1731open_recovery_mode_ask->add_button(TTRC("Edit normally"))->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_on_recovery_mode_popup_open_normal));1732open_recovery_mode_ask->set_ok_button_text(TTRC("Edit in Recovery Mode"));1733open_recovery_mode_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_on_recovery_mode_popup_open_recovery));1734add_child(open_recovery_mode_ask);17351736ask_update_settings = memnew(ConfirmationDialog);1737add_child(ask_update_settings);1738ask_update_vb = memnew(VBoxContainer);1739ask_update_settings->add_child(ask_update_vb);1740ask_update_label = memnew(Label);1741ask_update_label->set_focus_mode(FOCUS_ACCESSIBILITY);1742ask_update_label->set_custom_minimum_size(Size2(300 * EDSCALE, 1));1743ask_update_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);1744ask_update_label->set_v_size_flags(SIZE_EXPAND_FILL);1745ask_update_vb->add_child(ask_update_label);1746ask_update_backup = memnew(CheckBox);1747ask_update_backup->set_text(TTRC("Backup project first"));1748ask_update_backup->set_h_size_flags(SIZE_SHRINK_CENTER);1749ask_update_vb->add_child(ask_update_backup);1750ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_with_migration));1751int ed_swap_cancel_ok = EDITOR_GET("interface/editor/accept_dialog_cancel_ok_buttons");1752if (ed_swap_cancel_ok == 0) {1753ed_swap_cancel_ok = DisplayServer::get_singleton()->get_swap_cancel_ok() ? 2 : 1;1754}1755full_convert_button = ask_update_settings->add_button(TTRC("Convert Full Project"), ed_swap_cancel_ok != 2);1756full_convert_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_full_convert_button_pressed));1757migration_guide_button = ask_update_settings->add_button(TTRC("See Migration Guide"), ed_swap_cancel_ok != 2);1758migration_guide_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_migration_guide_button_pressed));17591760ask_full_convert_dialog = memnew(ConfirmationDialog);1761ask_full_convert_dialog->set_autowrap(true);1762ask_full_convert_dialog->set_text(TTRC("This option will perform full project conversion, updating scenes, resources and scripts from Godot 3 to work in Godot 4.\n\nNote that this is a best-effort conversion, i.e. it makes upgrading the project easier, but it will not open out-of-the-box and will still require manual adjustments.\n\nIMPORTANT: Make sure to backup your project before converting, as this operation makes it impossible to open it in older versions of Godot."));1763ask_full_convert_dialog->connect(SceneStringName(confirmed), callable_mp(this, &ProjectManager::_perform_full_project_conversion));1764add_child(ask_full_convert_dialog);17651766project_dialog = memnew(ProjectDialog);1767project_dialog->connect("projects_updated", callable_mp(this, &ProjectManager::_on_projects_updated));1768project_dialog->connect("project_created", callable_mp(this, &ProjectManager::_on_project_created));1769project_dialog->connect("project_duplicated", callable_mp(this, &ProjectManager::_on_project_duplicated));1770add_child(project_dialog);17711772error_dialog = memnew(AcceptDialog);1773error_dialog->set_title(TTRC("Error"));1774add_child(error_dialog);17751776about_dialog = memnew(EditorAbout);1777add_child(about_dialog);1778}17791780// Tag management.1781{1782tag_manage_dialog = memnew(ConfirmationDialog);1783add_child(tag_manage_dialog);1784tag_manage_dialog->set_title(TTRC("Manage Project Tags"));1785tag_manage_dialog->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_apply_project_tags));1786manage_tags_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_manage_project_tags));17871788VBoxContainer *tag_vb = memnew(VBoxContainer);1789tag_manage_dialog->add_child(tag_vb);17901791Label *label = memnew(Label(TTRC("Project Tags")));1792tag_vb->add_child(label);1793label->set_theme_type_variation("HeaderMedium");1794label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);17951796label = memnew(Label(TTRC("Click tag to remove it from the project.")));1797tag_vb->add_child(label);1798label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);17991800project_tags = memnew(HFlowContainer);1801tag_vb->add_child(project_tags);1802project_tags->set_custom_minimum_size(Vector2(0, 100) * EDSCALE);18031804tag_vb->add_child(memnew(HSeparator));18051806label = memnew(Label(TTRC("All Tags")));1807tag_vb->add_child(label);1808label->set_theme_type_variation("HeaderMedium");1809label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);18101811label = memnew(Label(TTRC("Click tag to add it to the project.")));1812tag_vb->add_child(label);1813label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);18141815all_tags = memnew(HFlowContainer);1816tag_vb->add_child(all_tags);1817all_tags->set_custom_minimum_size(Vector2(0, 100) * EDSCALE);18181819tag_edit_error = memnew(Label);1820tag_vb->add_child(tag_edit_error);1821tag_edit_error->set_autowrap_mode(TextServer::AUTOWRAP_WORD);18221823create_tag_dialog = memnew(ConfirmationDialog);1824tag_manage_dialog->add_child(create_tag_dialog);1825create_tag_dialog->set_title(TTRC("Create New Tag"));1826create_tag_dialog->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_create_new_tag));18271828tag_vb = memnew(VBoxContainer);1829create_tag_dialog->add_child(tag_vb);18301831Label *info = memnew(Label(TTRC("Tags are capitalized automatically when displayed.")));1832tag_vb->add_child(info);18331834new_tag_name = memnew(LineEdit);1835tag_vb->add_child(new_tag_name);1836new_tag_name->set_accessibility_name(TTRC("New Tag Name"));1837new_tag_name->connect(SceneStringName(text_changed), callable_mp(this, &ProjectManager::_set_new_tag_name));1838new_tag_name->connect(SceneStringName(text_submitted), callable_mp(this, &ProjectManager::_create_new_tag).unbind(1));1839create_tag_dialog->connect("about_to_popup", callable_mp(new_tag_name, &LineEdit::clear));1840create_tag_dialog->connect("about_to_popup", callable_mp((Control *)new_tag_name, &Control::grab_focus), CONNECT_DEFERRED);18411842tag_error = memnew(Label);1843tag_error->set_focus_mode(FOCUS_ACCESSIBILITY);1844tag_vb->add_child(tag_error);18451846create_tag_btn = memnew(Button);1847create_tag_btn->set_accessibility_name(TTRC("Create Tag"));1848all_tags->add_child(create_tag_btn);1849create_tag_btn->connect(SceneStringName(pressed), callable_mp((Window *)create_tag_dialog, &Window::popup_centered).bind(Vector2i(500, 0) * EDSCALE));18501851_set_new_tag_name("");1852}18531854// Initialize project list.1855{1856project_list->load_project_list();18571858Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM);18591860String default_project_path = EDITOR_GET("filesystem/directories/default_project_path");1861if (!default_project_path.is_empty() && !dir_access->dir_exists(default_project_path)) {1862Error error = dir_access->make_dir_recursive(default_project_path);1863if (error != OK) {1864ERR_PRINT("Could not create default project directory at: " + default_project_path);1865}1866}18671868String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path");1869if (!autoscan_path.is_empty()) {1870if (dir_access->dir_exists(autoscan_path)) {1871project_list->find_projects(autoscan_path);1872} else {1873Error error = dir_access->make_dir_recursive(autoscan_path);1874if (error != OK) {1875ERR_PRINT("Could not create project autoscan directory at: " + autoscan_path);1876}1877}1878}1879project_list->update_project_list();1880initialized = true;1881}18821883// Extend menu bar to window title.1884if (can_expand) {1885DisplayServer::get_singleton()->process_events();1886DisplayServer::get_singleton()->window_set_flag(DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE, true, DisplayServer::MAIN_WINDOW_ID);1887title_bar->set_can_move_window(true);1888title_bar->connect(SceneStringName(item_rect_changed), callable_mp(this, &ProjectManager::_titlebar_resized));1889}18901891_update_size_limits();1892}18931894ProjectManager::~ProjectManager() {1895singleton = nullptr;1896if (EditorSettings::get_singleton()) {1897EditorSettings::destroy();1898}18991900EditorThemeManager::finalize();1901}190219031904