Path: blob/master/editor/project_manager/project_manager.cpp
20879 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/input/input.h"34#include "core/io/config_file.h"35#include "core/io/dir_access.h"36#include "core/io/file_access.h"37#include "core/os/keyboard.h"38#include "core/os/os.h"39#include "core/version.h"40#include "editor/asset_library/asset_library_editor_plugin.h"41#include "editor/editor_string_names.h"42#include "editor/gui/editor_about.h"43#include "editor/gui/editor_file_dialog.h"44#include "editor/gui/editor_title_bar.h"45#include "editor/gui/editor_version_button.h"46#include "editor/inspector/editor_inspector.h"47#include "editor/project_manager/engine_update_label.h"48#include "editor/project_manager/project_dialog.h"49#include "editor/project_manager/project_list.h"50#include "editor/project_manager/project_tag.h"51#include "editor/project_manager/quick_settings_dialog.h"52#include "editor/settings/editor_settings.h"53#include "editor/themes/editor_scale.h"54#include "editor/themes/editor_theme_manager.h"55#include "main/main.h"56#include "scene/gui/check_box.h"57#include "scene/gui/flow_container.h"58#include "scene/gui/line_edit.h"59#include "scene/gui/margin_container.h"60#include "scene/gui/menu_bar.h"61#include "scene/gui/option_button.h"62#include "scene/gui/panel_container.h"63#include "scene/gui/rich_text_label.h"64#include "scene/gui/separator.h"65#include "scene/main/window.h"66#include "scene/theme/theme_db.h"67#include "servers/display/display_server.h"68#include "servers/navigation_3d/navigation_server_3d.h"6970#ifndef PHYSICS_2D_DISABLED71#include "servers/physics_2d/physics_server_2d.h"72#endif // PHYSICS_2D_DISABLED7374#ifndef PHYSICS_3D_DISABLED75#include "servers/physics_3d/physics_server_3d.h"76#endif // PHYSICS_3D_DISABLED7778constexpr int GODOT4_CONFIG_VERSION = 5;7980ProjectManager *ProjectManager::singleton = nullptr;8182// Notifications.8384void ProjectManager::_notification(int p_what) {85switch (p_what) {86case NOTIFICATION_ENTER_TREE: {87Engine::get_singleton()->set_editor_hint(false);8889Window *main_window = get_window();90if (main_window) {91// Handle macOS fullscreen and extend-to-title changes.92main_window->connect("titlebar_changed", callable_mp(this, &ProjectManager::_titlebar_resized));93}9495// Theme has already been created in the constructor, so we can skip that step.96_update_theme(true);97} break;9899case NOTIFICATION_READY: {100DisplayServer::get_singleton()->screen_set_keep_on(EDITOR_GET("interface/editor/keep_screen_on"));101const int default_sorting = (int)EDITOR_GET("project_manager/sorting_order");102filter_option->select(default_sorting);103project_list->set_order_option(default_sorting, false);104105_select_main_view(MAIN_VIEW_PROJECTS);106_update_list_placeholder();107_titlebar_resized();108} break;109110case NOTIFICATION_TRANSLATION_CHANGED: {111// TRANSLATORS: This refers to the application where users manage their Godot projects.112SceneTree::get_singleton()->get_root()->set_title(GODOT_VERSION_NAME + String(" - ") + TTR("Project Manager", "Application"));113114const String line1 = TTR("You don't have any projects yet.");115const String line2 = TTR("Get started by creating a new one,\nimporting one that exists, or by downloading a project template from the Asset Library!");116empty_list_message->set_text(vformat("[center][b]%s[/b] %s[/center]", line1, line2));117118_titlebar_resized();119} break;120121case NOTIFICATION_VISIBILITY_CHANGED: {122set_process_shortcut_input(is_visible_in_tree());123} break;124125case NOTIFICATION_WM_CLOSE_REQUEST: {126_dim_window();127} break;128129case NOTIFICATION_WM_ABOUT: {130_show_about();131} break;132133case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {134if (EditorThemeManager::is_generated_theme_outdated()) {135_update_theme();136}137_update_list_placeholder();138} break;139}140}141142// Utility data.143144Ref<Texture2D> ProjectManager::_file_dialog_get_icon(const String &p_path) {145if (p_path.has_extension("godot")) {146return singleton->icon_type_cache["GodotMonochrome"];147}148149return singleton->icon_type_cache["Object"];150}151152Ref<Texture2D> ProjectManager::_file_dialog_get_thumbnail(const String &p_path) {153if (p_path.has_extension("godot")) {154return singleton->icon_type_cache["GodotFile"];155}156157return Ref<Texture2D>();158}159160void ProjectManager::_build_icon_type_cache(Ref<Theme> p_theme) {161if (p_theme.is_null()) {162return;163}164List<StringName> tl;165p_theme->get_icon_list(EditorStringName(EditorIcons), &tl);166for (const StringName &name : tl) {167icon_type_cache[name] = p_theme->get_icon(name, EditorStringName(EditorIcons));168}169}170171// Main layout.172173void ProjectManager::_update_size_limits() {174const Size2 minimum_size = Size2(720, 450) * EDSCALE;175176// Define a minimum window size to prevent UI elements from overlapping or being cut off.177Window *w = Object::cast_to<Window>(SceneTree::get_singleton()->get_root());178if (w) {179// Calling Window methods this early doesn't sync properties with DS.180w->set_min_size(minimum_size);181DisplayServer::get_singleton()->window_set_min_size(minimum_size);182}183Size2 real_size = DisplayServer::get_singleton()->window_get_size();184185Rect2i screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(DisplayServer::get_singleton()->window_get_current_screen());186if (screen_rect.size != Vector2i()) {187// Center the window on the screen.188Vector2i window_position;189window_position.x = screen_rect.position.x + (screen_rect.size.x - real_size.x) / 2;190window_position.y = screen_rect.position.y + (screen_rect.size.y - real_size.y) / 2;191192// Limit popup menus to prevent unusably long lists.193// We try to set it to half the screen resolution, but no smaller than the minimum window size.194Size2 half_screen_rect = (screen_rect.size * EDSCALE) / 2;195Size2 maximum_popup_size = MAX(half_screen_rect, minimum_size);196quick_settings_dialog->update_size_limits(maximum_popup_size);197}198}199200void ProjectManager::_update_theme(bool p_skip_creation) {201if (!p_skip_creation) {202theme = EditorThemeManager::generate_theme(theme);203DisplayServer::set_early_window_clear_color_override(true, theme->get_color("background", EditorStringName(Editor)));204}205206Vector<Ref<Theme>> editor_themes;207editor_themes.push_back(theme);208editor_themes.push_back(ThemeDB::get_singleton()->get_default_theme());209210ThemeContext *node_tc = ThemeDB::get_singleton()->get_theme_context(this);211if (node_tc) {212node_tc->set_themes(editor_themes);213} else {214ThemeDB::get_singleton()->create_theme_context(this, editor_themes);215}216217Window *owner_window = get_window();218if (owner_window) {219ThemeContext *window_tc = ThemeDB::get_singleton()->get_theme_context(owner_window);220if (window_tc) {221window_tc->set_themes(editor_themes);222} else {223ThemeDB::get_singleton()->create_theme_context(owner_window, editor_themes);224}225}226227// Update styles.228{229const int top_bar_separation = get_theme_constant("top_bar_separation", EditorStringName(Editor));230root_container->add_theme_constant_override("margin_left", top_bar_separation);231root_container->add_theme_constant_override("margin_top", top_bar_separation);232root_container->add_theme_constant_override("margin_bottom", top_bar_separation);233root_container->add_theme_constant_override("margin_right", top_bar_separation);234main_vbox->add_theme_constant_override("separation", top_bar_separation);235236background_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox("Background", EditorStringName(EditorStyles)));237main_view_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox("panel_container", "ProjectManager"));238239title_bar_logo->set_button_icon(get_editor_theme_icon("TitleBarLogo"));240241_set_main_view_icon(MAIN_VIEW_PROJECTS, get_editor_theme_icon("ProjectList"));242_set_main_view_icon(MAIN_VIEW_ASSETLIB, get_editor_theme_icon("AssetLib"));243244// Project list.245{246loading_label->add_theme_font_override(SceneStringName(font), get_theme_font("bold", EditorStringName(EditorFonts)));247project_list_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox("project_list", "ProjectManager"));248249empty_list_create_project->set_button_icon(get_editor_theme_icon("Add"));250empty_list_import_project->set_button_icon(get_editor_theme_icon("Load"));251empty_list_open_assetlib->set_button_icon(get_editor_theme_icon("AssetLib"));252253empty_list_online_warning->add_theme_font_override(SceneStringName(font), get_theme_font("italic", EditorStringName(EditorFonts)));254empty_list_online_warning->add_theme_color_override(SceneStringName(font_color), get_theme_color("font_placeholder_color", EditorStringName(Editor)));255256// Top bar.257search_box->set_right_icon(get_editor_theme_icon("Search"));258quick_settings_button->set_button_icon(get_editor_theme_icon("Tools"));259260// Sidebar.261create_btn->set_button_icon(get_editor_theme_icon("Add"));262import_btn->set_button_icon(get_editor_theme_icon("Load"));263scan_btn->set_button_icon(get_editor_theme_icon("Search"));264open_btn->set_button_icon(get_editor_theme_icon("Edit"));265open_options_btn->set_button_icon(get_editor_theme_icon("Collapse"));266run_btn->set_button_icon(get_editor_theme_icon("Play"));267rename_btn->set_button_icon(get_editor_theme_icon("Rename"));268duplicate_btn->set_button_icon(get_editor_theme_icon("Duplicate"));269manage_tags_btn->set_button_icon(get_editor_theme_icon("Script"));270erase_btn->set_button_icon(get_editor_theme_icon("Remove"));271erase_missing_btn->set_button_icon(get_editor_theme_icon("Clear"));272create_tag_btn->set_button_icon(get_editor_theme_icon("Add"));273donate_btn->set_button_icon(get_editor_theme_icon("Heart"));274275tag_error->add_theme_color_override(SceneStringName(font_color), get_theme_color("error_color", EditorStringName(Editor)));276tag_edit_error->add_theme_color_override(SceneStringName(font_color), get_theme_color("error_color", EditorStringName(Editor)));277278const int h_separation = get_theme_constant("sidebar_button_icon_separation", "ProjectManager");279create_btn->add_theme_constant_override("h_separation", h_separation);280import_btn->add_theme_constant_override("h_separation", h_separation);281scan_btn->add_theme_constant_override("h_separation", h_separation);282open_btn->add_theme_constant_override("h_separation", h_separation);283run_btn->add_theme_constant_override("h_separation", h_separation);284rename_btn->add_theme_constant_override("h_separation", h_separation);285duplicate_btn->add_theme_constant_override("h_separation", h_separation);286manage_tags_btn->add_theme_constant_override("h_separation", h_separation);287erase_btn->add_theme_constant_override("h_separation", h_separation);288erase_missing_btn->add_theme_constant_override("h_separation", h_separation);289290open_btn_container->add_theme_constant_override("separation", 0);291open_options_popup->set_item_icon(0, get_editor_theme_icon("Notification"));292open_options_popup->set_item_icon(1, get_editor_theme_icon("NodeWarning"));293}294295// Dialogs296migration_guide_button->set_button_icon(get_editor_theme_icon("ExternalLink"));297298// Asset library popup.299if (asset_library && EDITOR_GET("interface/theme/style") == "Classic") {300// Removes extra border margins.301asset_library->add_theme_style_override(SceneStringName(panel), memnew(StyleBoxEmpty));302}303}304#ifdef ANDROID_ENABLED305DisplayServer::get_singleton()->window_set_color(theme->get_color("background", EditorStringName(Editor)));306#endif307}308309Button *ProjectManager::_add_main_view(MainViewTab p_id, const String &p_name, const Ref<Texture2D> &p_icon, Control *p_view_control) {310ERR_FAIL_INDEX_V(p_id, MAIN_VIEW_MAX, nullptr);311ERR_FAIL_COND_V(main_view_map.has(p_id), nullptr);312ERR_FAIL_COND_V(main_view_toggle_map.has(p_id), nullptr);313314Button *toggle_button = memnew(Button);315toggle_button->set_flat(true);316toggle_button->set_theme_type_variation("MainScreenButton");317toggle_button->set_toggle_mode(true);318toggle_button->set_button_group(main_view_toggles_group);319toggle_button->set_text(p_name);320toggle_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_select_main_view).bind((int)p_id));321322main_view_toggles->add_child(toggle_button);323main_view_toggle_map[p_id] = toggle_button;324325_set_main_view_icon(p_id, p_icon);326327p_view_control->set_visible(false);328main_view_container->add_child(p_view_control);329main_view_map[p_id] = p_view_control;330331return toggle_button;332}333334void ProjectManager::_set_main_view_icon(MainViewTab p_id, const Ref<Texture2D> &p_icon) {335ERR_FAIL_INDEX(p_id, MAIN_VIEW_MAX);336ERR_FAIL_COND(!main_view_toggle_map.has(p_id));337338Button *toggle_button = main_view_toggle_map[p_id];339340Ref<Texture2D> old_icon = toggle_button->get_button_icon();341if (old_icon.is_valid()) {342old_icon->disconnect_changed(callable_mp((Control *)toggle_button, &Control::update_minimum_size));343}344345if (p_icon.is_valid()) {346toggle_button->set_button_icon(p_icon);347// Make sure the control is updated if the icon is reimported.348p_icon->connect_changed(callable_mp((Control *)toggle_button, &Control::update_minimum_size));349} else {350toggle_button->set_button_icon(Ref<Texture2D>());351}352}353354void ProjectManager::_select_main_view(int p_id) {355MainViewTab view_id = (MainViewTab)p_id;356357ERR_FAIL_INDEX(view_id, MAIN_VIEW_MAX);358ERR_FAIL_COND(!main_view_map.has(view_id));359ERR_FAIL_COND(!main_view_toggle_map.has(view_id));360361if (current_main_view != view_id) {362main_view_toggle_map[current_main_view]->set_pressed_no_signal(false);363main_view_map[current_main_view]->set_visible(false);364current_main_view = view_id;365}366main_view_toggle_map[current_main_view]->set_pressed_no_signal(true);367main_view_map[current_main_view]->set_visible(true);368369#ifndef ANDROID_ENABLED370if (current_main_view == MAIN_VIEW_PROJECTS && search_box->is_inside_tree()) {371// Automatically grab focus when the user moves from the Templates tab372// back to the Projects tab.373// Needs to be deferred, otherwise the focus outline is always drawn.374callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(true);375}376377// The Templates tab's search field is focused on display in the asset378// library editor plugin code.379#endif380}381382void ProjectManager::_show_about() {383about_dialog->popup_centered(Size2(780, 500) * EDSCALE);384}385386void ProjectManager::_open_asset_library_confirmed() {387const int network_mode = EDITOR_GET("network/connection/network_mode");388if (network_mode == EditorSettings::NETWORK_OFFLINE) {389EditorSettings::get_singleton()->set_setting("network/connection/network_mode", EditorSettings::NETWORK_ONLINE);390EditorSettings::get_singleton()->notify_changes();391EditorSettings::get_singleton()->save();392}393394asset_library->disable_community_support();395_select_main_view(MAIN_VIEW_ASSETLIB);396}397398void ProjectManager::_project_list_menu_option(int p_option) {399switch (p_option) {400case ProjectList::MENU_EDIT:401_open_selected_projects();402break;403404case ProjectList::MENU_EDIT_VERBOSE:405open_in_verbose_mode = true;406_open_selected_projects_check_warnings();407break;408409case ProjectList::MENU_EDIT_RECOVERY:410_open_recovery_mode_ask(true);411break;412413case ProjectList::MENU_RUN:414_run_project_confirm();415break;416417case ProjectList::MENU_SHOW_IN_FILE_MANAGER:418_show_project_in_file_manager();419break;420421case ProjectList::MENU_COPY_PATH: {422const Vector<ProjectList::Item> &selected_list = project_list->get_selected_projects();423if (selected_list.is_empty()) {424return;425}426DisplayServer::get_singleton()->clipboard_set(selected_list[0].path);427} break;428429case ProjectList::MENU_RENAME:430_rename_project();431break;432433case ProjectList::MENU_MANAGE_TAGS:434_manage_project_tags();435break;436437case ProjectList::MENU_DUPLICATE:438_duplicate_project();439break;440441case ProjectList::MENU_REMOVE:442_erase_project();443break;444}445}446447void ProjectManager::_show_error(const String &p_message, const Size2 &p_min_size) {448error_dialog->set_text(p_message);449error_dialog->popup_centered(p_min_size);450}451452void ProjectManager::_dim_window() {453// This method must be called before calling `get_tree()->quit()`.454// Otherwise, its effect won't be visible455456// Dim the project manager window while it's quitting to make it clearer that it's busy.457// No transition is applied, as the effect needs to be visible immediately458float c = 0.5f;459Color dim_color = Color(c, c, c);460set_modulate(dim_color);461}462463// Quick settings.464465void ProjectManager::_show_quick_settings() {466quick_settings_dialog->popup_centered(Size2(640, 200) * EDSCALE);467}468469void ProjectManager::_restart_confirmed() {470List<String> args = OS::get_singleton()->get_cmdline_args();471Error err = OS::get_singleton()->create_instance(args);472ERR_FAIL_COND(err);473474_dim_window();475get_tree()->quit();476}477478// Project list.479480void ProjectManager::_update_list_placeholder() {481if (project_list->get_project_count() > 0) {482empty_list_placeholder->hide();483return;484}485486empty_list_open_assetlib->set_visible(asset_library);487488const int network_mode = EDITOR_GET("network/connection/network_mode");489if (network_mode == EditorSettings::NETWORK_OFFLINE) {490empty_list_open_assetlib->set_text(TTRC("Go Online and Open Asset Library"));491empty_list_online_warning->set_visible(true);492} else {493empty_list_open_assetlib->set_text(TTRC("Open Asset Library"));494empty_list_online_warning->set_visible(false);495}496497empty_list_placeholder->show();498}499500void ProjectManager::_scan_projects() {501scan_dir->popup_file_dialog();502}503504void ProjectManager::_run_project() {505const HashSet<String> &selected_list = project_list->get_selected_project_keys();506507if (selected_list.size() < 1) {508return;509}510511if (selected_list.size() > 1) {512multi_run_ask->set_text(vformat(TTR("Are you sure to run %d projects at once?"), selected_list.size()));513multi_run_ask->popup_centered();514} else {515_run_project_confirm();516}517}518519void ProjectManager::_run_project_confirm() {520Vector<ProjectList::Item> selected_list = project_list->get_selected_projects();521522for (int i = 0; i < selected_list.size(); ++i) {523const String &selected_main = selected_list[i].main_scene;524if (selected_main.is_empty()) {525_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."));526continue;527}528529const String &path = selected_list[i].path;530531// `.substr(6)` on `ProjectSettings::get_singleton()->get_imported_files_path()` strips away the leading "res://".532if (!DirAccess::exists(path.path_join(ProjectSettings::get_singleton()->get_imported_files_path().substr(6)))) {533_show_error(TTRC("Can't run project: Assets need to be imported first.\nPlease edit the project to trigger the initial import."));534continue;535}536537print_line("Running project: " + path);538539List<String> args;540541for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_PROJECT)) {542args.push_back(a);543}544545args.push_back("--path");546args.push_back(path);547548Error err = OS::get_singleton()->create_instance(args);549ERR_FAIL_COND(err);550}551}552553void ProjectManager::_open_selected_projects() {554// Show loading text to tell the user that the project manager is busy loading.555// This is especially important for the Web project manager.556loading_label->show();557558const HashSet<String> &selected_list = project_list->get_selected_project_keys();559for (const String &path : selected_list) {560String conf = path.path_join("project.godot");561562if (!FileAccess::exists(conf)) {563loading_label->hide();564_show_error(vformat(TTR("Can't open project at '%s'.\nProject file doesn't exist or is inaccessible."), path));565return;566}567568print_line("Editing project: " + path);569570List<String> args;571572for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {573args.push_back(a);574}575576args.push_back("--path");577args.push_back(path);578579args.push_back("--editor");580581if (open_in_recovery_mode) {582args.push_back("--recovery-mode");583}584585if (open_in_verbose_mode) {586args.push_back("--verbose");587}588589Error err = OS::get_singleton()->create_instance(args);590if (err != OK) {591loading_label->hide();592_show_error(vformat(TTR("Can't open project at '%s'.\nFailed to start the editor."), path));593ERR_PRINT(vformat("Failed to start an editor instance for the project at '%s', error code %d.", path, err));594return;595}596}597598project_list->project_opening_initiated = true;599600_dim_window();601get_tree()->quit();602}603604void ProjectManager::_open_selected_projects_check_warnings() {605const HashSet<String> &selected_list = project_list->get_selected_project_keys();606if (selected_list.size() < 1) {607return;608}609610const Size2i popup_min_size = Size2i(400.0 * EDSCALE, 0);611612if (selected_list.size() > 1) {613multi_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()));614multi_open_ask->popup_centered(popup_min_size);615return;616}617618ProjectList::Item project = project_list->get_selected_projects()[0];619if (project.missing) {620return;621}622623// Update the project settings or don't open.624const int config_version = project.version;625PackedStringArray unsupported_features = project.unsupported_features;626627ask_update_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); // Reset in case of previous center align.628ask_update_backup->set_pressed(false);629full_convert_button->hide();630migration_guide_button->hide();631ask_update_backup->hide();632633ask_update_settings->get_ok_button()->set_text("OK");634635// Check if the config_version property was empty or 0.636if (config_version == 0) {637ask_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));638ask_update_settings->popup_centered(popup_min_size);639return;640}641// Check if we need to convert project settings from an earlier engine version.642if (config_version < ProjectSettings::CONFIG_VERSION) {643if (config_version == GODOT4_CONFIG_VERSION - 1 && ProjectSettings::CONFIG_VERSION == GODOT4_CONFIG_VERSION) { // Conversion from Godot 3 to 4.644full_convert_button->show();645ask_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));646ask_update_settings->get_ok_button()->set_text(TTRC("Convert project.godot Only"));647} else {648ask_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));649ask_update_settings->get_ok_button()->set_text(TTRC("Convert project.godot"));650}651ask_update_backup->show();652migration_guide_button->show();653ask_update_settings->popup_centered(popup_min_size);654ask_update_settings->get_cancel_button()->grab_focus(); // To prevent accidents.655return;656}657// Check if the file was generated by a newer, incompatible engine version.658if (config_version > ProjectSettings::CONFIG_VERSION) {659_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);660return;661}662// Check if the project is using features not supported by this build of Godot.663if (!unsupported_features.is_empty()) {664String warning_message = "";665for (int i = 0; i < unsupported_features.size(); i++) {666const String &feature = unsupported_features[i];667if (feature == "Double Precision") {668ask_update_backup->show();669warning_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");670unsupported_features.remove_at(i);671i--;672} else if (feature == "C#") {673warning_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");674unsupported_features.remove_at(i);675i--;676} else if (ProjectList::project_feature_looks_like_version(feature)) {677ask_update_backup->show();678migration_guide_button->show();679version_convert_feature = feature;680warning_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));681unsupported_features.remove_at(i);682i--;683}684}685if (!unsupported_features.is_empty()) {686String unsupported_features_str = String(", ").join(unsupported_features);687warning_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);688}689warning_message += TTR("Open anyway? Project will be modified.");690ask_update_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);691ask_update_label->set_text(warning_message);692ask_update_settings->popup_centered(popup_min_size);693return;694}695696// Open if the project is up-to-date.697_open_selected_projects();698}699700void ProjectManager::_open_selected_projects_check_recovery_mode() {701Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();702703if (selected_projects.is_empty()) {704return;705}706707const ProjectList::Item &project = selected_projects[0];708if (project.missing) {709return;710}711712open_in_verbose_mode = false;713open_in_recovery_mode = false;714// Check if the project failed to load during last startup.715if (project.recovery_mode) {716_open_recovery_mode_ask(false);717return;718}719720_open_selected_projects_check_warnings();721}722723void ProjectManager::_open_selected_projects_with_migration() {724if (ask_update_backup->is_pressed() && project_list->get_selected_projects().size() == 1) {725ask_update_settings->hide();726ask_update_backup->set_pressed(false);727728_duplicate_project_with_action(POST_DUPLICATE_ACTION_OPEN);729return;730}731732#ifndef DISABLE_DEPRECATED733if (project_list->get_selected_projects().size() == 1) {734// Only migrate if a single project is opened.735_minor_project_migrate();736}737#endif738_open_selected_projects();739}740741void ProjectManager::_install_project(const String &p_zip_path, const String &p_title) {742project_dialog->set_mode(ProjectDialog::MODE_INSTALL);743project_dialog->set_zip_path(p_zip_path);744project_dialog->set_zip_title(p_title);745project_dialog->show_dialog();746}747748void ProjectManager::_import_project() {749project_dialog->set_mode(ProjectDialog::MODE_IMPORT);750project_dialog->ask_for_path_and_show();751}752753void ProjectManager::_new_project() {754project_dialog->set_mode(ProjectDialog::MODE_NEW);755project_dialog->show_dialog();756}757758void ProjectManager::_rename_project() {759const Vector<ProjectList::Item> &selected_list = project_list->get_selected_projects();760761if (selected_list.is_empty()) {762return;763}764765for (const ProjectList::Item &E : selected_list) {766project_dialog->set_project_name(E.project_name);767project_dialog->set_project_path(E.path);768project_dialog->set_mode(ProjectDialog::MODE_RENAME);769project_dialog->show_dialog();770}771}772773void ProjectManager::_duplicate_project() {774_duplicate_project_with_action(POST_DUPLICATE_ACTION_NONE);775}776777void ProjectManager::_duplicate_project_with_action(PostDuplicateAction p_post_action) {778Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();779if (selected_projects.is_empty()) {780return;781}782783post_duplicate_action = p_post_action;784785const ProjectList::Item &project = selected_projects[0];786787project_dialog->set_mode(ProjectDialog::MODE_DUPLICATE);788project_dialog->set_project_name(vformat("%s (%s)", project.project_name, p_post_action == POST_DUPLICATE_ACTION_NONE ? "Copy" : project.project_version));789project_dialog->set_original_project_path(project.path);790project_dialog->set_duplicate_can_edit(p_post_action == POST_DUPLICATE_ACTION_NONE);791project_dialog->show_dialog(false);792}793794void ProjectManager::_show_project_in_file_manager() {795const Vector<ProjectList::Item> &selected_list = project_list->get_selected_projects();796if (selected_list.is_empty()) {797return;798}799800for (const ProjectList::Item &E : selected_list) {801OS::get_singleton()->shell_show_in_file_manager(E.path, true);802}803}804805void ProjectManager::_erase_project() {806const HashSet<String> &selected_list = project_list->get_selected_project_keys();807808if (selected_list.is_empty()) {809return;810}811812String confirm_message;813if (selected_list.size() >= 2) {814confirm_message = vformat(TTR("Remove %d projects from the list?"), selected_list.size());815} else {816confirm_message = TTRC("Remove this project from the list?");817}818819erase_ask_label->set_text(confirm_message);820//delete_project_contents->set_pressed(false);821erase_ask->popup_centered();822}823824void ProjectManager::_erase_missing_projects() {825erase_missing_ask->set_text(TTRC("Remove all missing projects from the list?\nThe project folders' contents won't be modified."));826erase_missing_ask->popup_centered();827}828829void ProjectManager::_erase_project_confirm() {830project_list->erase_selected_projects(false);831_update_project_buttons();832_update_list_placeholder();833}834835void ProjectManager::_erase_missing_projects_confirm() {836project_list->erase_missing_projects();837_update_project_buttons();838_update_list_placeholder();839}840841void ProjectManager::_update_project_buttons() {842Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();843bool empty_selection = selected_projects.is_empty();844845bool is_missing_project_selected = false;846for (int i = 0; i < selected_projects.size(); ++i) {847if (selected_projects[i].missing) {848is_missing_project_selected = true;849break;850}851}852853erase_btn->set_disabled(empty_selection);854open_btn->set_disabled(empty_selection || is_missing_project_selected);855open_options_btn->set_disabled(empty_selection || is_missing_project_selected);856rename_btn->set_disabled(empty_selection || is_missing_project_selected);857duplicate_btn->set_disabled(empty_selection || is_missing_project_selected);858manage_tags_btn->set_disabled(empty_selection || is_missing_project_selected || selected_projects.size() > 1);859run_btn->set_disabled(empty_selection || is_missing_project_selected);860861erase_missing_btn->set_disabled(!project_list->is_any_project_missing());862}863864void ProjectManager::_open_options_popup() {865Rect2 rect = open_btn_container->get_screen_rect();866rect.position.y += rect.size.height;867open_options_popup->set_size(Size2(rect.size.width, 0));868open_options_popup->set_position(rect.position);869870open_options_popup->popup();871}872873void ProjectManager::_open_recovery_mode_ask(bool manual) {874String recovery_mode_details;875876// Only show the initial crash preamble if this popup wasn't manually triggered.877if (!manual) {878recovery_mode_details +=879TTR("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.") +880String::utf8("\n\n");881}882883recovery_mode_details +=884TTR("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:") +885String::utf8("\n\n• ") + TTR("Tool scripts") +886String::utf8("\n• ") + TTR("Editor plugins") +887String::utf8("\n• ") + TTR("GDExtension addons") +888String::utf8("\n• ") + TTR("Automatic scene restoring") +889String::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.") +890String::utf8("\n\n") + TTR("Edit the project in Recovery Mode?");891892open_recovery_mode_ask->set_text(recovery_mode_details);893open_recovery_mode_ask->popup_centered(Size2(550, 70) * EDSCALE);894}895896void ProjectManager::_on_projects_updated() {897Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();898int index = 0;899for (int i = 0; i < selected_projects.size(); ++i) {900index = project_list->refresh_project(selected_projects[i].path);901}902if (index != -1) {903project_list->ensure_project_visible(index);904}905906project_list->update_dock_menu();907}908909void ProjectManager::_on_open_options_selected(int p_option) {910switch (p_option) {911case 0: // Edit in verbose mode.912open_in_verbose_mode = true;913_open_selected_projects_check_warnings();914break;915case 1: // Edit in recovery mode.916_open_recovery_mode_ask(true);917break;918}919}920921void ProjectManager::_on_recovery_mode_popup_open_normal() {922open_recovery_mode_ask->hide();923open_in_recovery_mode = false;924_open_selected_projects_check_warnings();925}926927void ProjectManager::_on_recovery_mode_popup_open_recovery() {928open_in_recovery_mode = true;929_open_selected_projects_check_warnings();930}931932void ProjectManager::_on_project_created(const String &dir, bool edit) {933project_list->add_project(dir, false);934project_list->save_config();935search_box->clear();936937int i = project_list->refresh_project(dir);938project_list->ensure_project_visible(i);939_update_list_placeholder();940941if (edit) {942_open_selected_projects_check_warnings();943}944945project_list->update_dock_menu();946}947948void ProjectManager::_on_project_duplicated(const String &p_original_path, const String &p_duplicate_path, bool p_edit) {949if (post_duplicate_action == POST_DUPLICATE_ACTION_NONE) {950_on_project_created(p_duplicate_path, p_edit);951} else {952project_list->add_project(p_duplicate_path, false);953project_list->save_config();954955if (post_duplicate_action == POST_DUPLICATE_ACTION_OPEN) {956_open_selected_projects_with_migration();957} else if (post_duplicate_action == POST_DUPLICATE_ACTION_FULL_CONVERSION) {958_full_convert_button_pressed();959}960961project_list->update_dock_menu();962}963964post_duplicate_action = POST_DUPLICATE_ACTION_NONE;965}966967void ProjectManager::_on_order_option_changed(int p_idx) {968if (is_inside_tree()) {969project_list->set_order_option(p_idx, true);970}971}972973void ProjectManager::_on_search_term_changed(const String &p_term) {974project_list->set_search_term(p_term);975project_list->sort_projects();976977// Select the first visible project in the list.978// This makes it possible to open a project without ever touching the mouse,979// as the search field is automatically focused on startup.980project_list->select_first_visible_project();981_update_project_buttons();982}983984void ProjectManager::_on_search_term_submitted(const String &p_text) {985if (current_main_view != MAIN_VIEW_PROJECTS) {986return;987}988989_open_selected_projects_check_recovery_mode();990}991992LineEdit *ProjectManager::get_search_box() {993return search_box;994}995996// Project tag management.997998void ProjectManager::_manage_project_tags() {999for (int i = 0; i < project_tags->get_child_count(); i++) {1000project_tags->get_child(i)->queue_free();1001}10021003const ProjectList::Item item = project_list->get_selected_projects()[0];1004current_project_tags = item.tags;1005for (const String &tag : current_project_tags) {1006ProjectTag *tag_control = memnew(ProjectTag(tag, true));1007project_tags->add_child(tag_control);1008tag_control->connect_button_to(callable_mp(this, &ProjectManager::_delete_project_tag).bind(tag));1009}10101011tag_edit_error->hide();1012tag_manage_dialog->popup_centered(Vector2i(500, 0) * EDSCALE);1013}10141015void ProjectManager::_add_project_tag(const String &p_tag) {1016if (current_project_tags.has(p_tag)) {1017return;1018}1019current_project_tags.append(p_tag);10201021ProjectTag *tag_control = memnew(ProjectTag(p_tag, true));1022project_tags->add_child(tag_control);1023tag_control->connect_button_to(callable_mp(this, &ProjectManager::_delete_project_tag).bind(p_tag));1024}10251026void ProjectManager::_delete_project_tag(const String &p_tag) {1027current_project_tags.erase(p_tag);1028for (int i = 0; i < project_tags->get_child_count(); i++) {1029ProjectTag *tag_control = Object::cast_to<ProjectTag>(project_tags->get_child(i));1030if (tag_control && tag_control->get_tag() == p_tag) {1031memdelete(tag_control);1032break;1033}1034}1035}10361037void ProjectManager::_apply_project_tags() {1038PackedStringArray tags;1039for (int i = 0; i < project_tags->get_child_count(); i++) {1040ProjectTag *tag_control = Object::cast_to<ProjectTag>(project_tags->get_child(i));1041if (tag_control) {1042tags.append(tag_control->get_tag());1043}1044}10451046const String project_godot = project_list->get_selected_projects()[0].path.path_join("project.godot");1047ProjectSettings *cfg = memnew(ProjectSettings(project_godot));1048if (!cfg->is_project_loaded()) {1049memdelete(cfg);1050tag_edit_error->set_text(vformat(TTR("Couldn't load project at '%s'. It may be missing or corrupted."), project_godot));1051tag_edit_error->show();1052callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred(); // Make sure the dialog does not disappear.1053return;1054} else {1055tags.sort();1056cfg->set("application/config/tags", tags);1057Error err = cfg->save_custom(project_godot);1058memdelete(cfg);10591060if (err != OK) {1061tag_edit_error->set_text(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err));1062tag_edit_error->show();1063callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred();1064return;1065}1066}10671068_on_projects_updated();1069}10701071void ProjectManager::_set_new_tag_name(const String p_name) {1072create_tag_dialog->get_ok_button()->set_disabled(true);1073if (p_name.strip_edges().is_empty()) {1074tag_error->set_text(TTRC("Tag name can't be empty."));1075return;1076}10771078if (p_name[0] == '_' || p_name[p_name.length() - 1] == '_') {1079tag_error->set_text(TTRC("Tag name can't begin or end with underscore."));1080return;1081}10821083bool was_underscore = false;1084for (const char32_t &c : p_name.span()) {1085// Treat spaces as underscores, as we convert spaces to underscores automatically in the tag input field.1086if (c == '_' || c == ' ') {1087if (was_underscore) {1088tag_error->set_text(TTRC("Tag name can't contain consecutive underscores or spaces."));1089return;1090}1091was_underscore = true;1092} else {1093was_underscore = false;1094}1095}10961097for (const String &c : forbidden_tag_characters) {1098if (p_name.contains(c)) {1099tag_error->set_text(vformat(TTR("These characters are not allowed in tags: %s."), String(" ").join(forbidden_tag_characters)));1100return;1101}1102}11031104tag_error->set_text("");1105create_tag_dialog->get_ok_button()->set_disabled(false);1106}11071108void ProjectManager::_create_new_tag() {1109if (!tag_error->get_text().is_empty()) {1110return;1111}1112create_tag_dialog->hide(); // When using text_submitted, need to hide manually.11131114// Enforce a valid tag name (no spaces, lowercase only) automatically.1115// The project manager displays underscores as spaces, and capitalization is performed automatically.1116const String new_tag = new_tag_name->get_text().strip_edges().to_lower().replace_char(' ', '_');1117add_new_tag(new_tag);1118_add_project_tag(new_tag);1119}11201121void ProjectManager::add_new_tag(const String &p_tag) {1122if (!tag_set.has(p_tag)) {1123tag_set.insert(p_tag);1124ProjectTag *tag_control = memnew(ProjectTag(p_tag));1125all_tags->add_child(tag_control);1126all_tags->move_child(tag_control, -2);1127tag_control->connect_button_to(callable_mp(this, &ProjectManager::_add_project_tag).bind(p_tag));1128}1129}11301131// Project converter/migration tool.11321133#ifndef DISABLE_DEPRECATED1134void ProjectManager::_minor_project_migrate() {1135const ProjectList::Item migrated_project = project_list->get_selected_projects()[0];11361137if (version_convert_feature.begins_with("4.3")) {1138// Migrate layout after scale changes.1139const float edscale = EDSCALE;1140if (edscale != 1.0) {1141Ref<ConfigFile> layout_file;1142layout_file.instantiate();11431144const String layout_path = migrated_project.path.path_join(".godot/editor/editor_layout.cfg");1145Error err = layout_file->load(layout_path);1146if (err == OK) {1147for (int i = 0; i < 4; i++) {1148const String key = "dock_hsplit_" + itos(i + 1);1149int old_value = layout_file->get_value("docks", key, 0);1150if (old_value != 0) {1151layout_file->set_value("docks", key, old_value / edscale);1152}1153}1154layout_file->save(layout_path);1155}1156}1157}1158}1159#endif11601161void ProjectManager::_full_convert_button_pressed() {1162ask_update_settings->hide();11631164if (ask_update_backup->is_pressed()) {1165ask_update_backup->set_pressed(false);11661167_duplicate_project_with_action(POST_DUPLICATE_ACTION_FULL_CONVERSION);1168return;1169}11701171ask_full_convert_dialog->popup_centered(Size2i(600.0 * EDSCALE, 0));1172ask_full_convert_dialog->get_cancel_button()->grab_focus();1173}11741175void ProjectManager::_migration_guide_button_pressed() {1176const String url = vformat("%s/tutorials/migrating/index.html", GODOT_VERSION_DOCS_URL);1177OS::get_singleton()->shell_open(url);1178}11791180void ProjectManager::_perform_full_project_conversion() {1181Vector<ProjectList::Item> selected_list = project_list->get_selected_projects();1182if (selected_list.is_empty()) {1183return;1184}11851186const String &path = selected_list[0].path;11871188print_line("Converting project: " + path);1189List<String> args;1190args.push_back("--path");1191args.push_back(path);1192args.push_back("--convert-3to4");1193args.push_back("--rendering-driver");1194args.push_back(OS::get_singleton()->get_current_rendering_driver_name());11951196Error err = OS::get_singleton()->create_instance(args);1197ERR_FAIL_COND(err);11981199project_list->set_project_version(path, GODOT4_CONFIG_VERSION);1200}12011202// Input and I/O.12031204void ProjectManager::shortcut_input(const Ref<InputEvent> &p_ev) {1205ERR_FAIL_COND(p_ev.is_null());12061207Ref<InputEventKey> k = p_ev;12081209if (k.is_valid()) {1210if (!k->is_pressed()) {1211return;1212}12131214// Pressing Command + Q quits the Project Manager1215// This is handled by the platform implementation on macOS,1216// so only define the shortcut on other platforms1217#ifndef MACOS_ENABLED1218if (k->get_keycode_with_modifiers() == (KeyModifierMask::META | Key::Q)) {1219_dim_window();1220get_tree()->quit();1221}1222#endif12231224if (current_main_view != MAIN_VIEW_PROJECTS) {1225return;1226}12271228bool keycode_handled = true;12291230switch (k->get_keycode()) {1231case Key::ENTER: {1232_open_selected_projects_check_recovery_mode();1233} break;1234case Key::HOME: {1235if (project_list->get_project_count() > 0) {1236project_list->ensure_project_visible(0);1237}12381239} break;1240case Key::END: {1241if (project_list->get_project_count() > 0) {1242project_list->ensure_project_visible(project_list->get_project_count() - 1);1243}12441245} break;1246case Key::F: {1247if (k->is_command_or_control_pressed()) {1248search_box->grab_focus();1249} else {1250keycode_handled = false;1251}1252} break;1253case Key::A: {1254if (k->is_command_or_control_pressed()) {1255if (k->is_shift_pressed()) {1256project_list->deselect_all_visible_projects();1257} else {1258project_list->select_all_visible_projects();1259}1260_update_project_buttons();1261}1262} break;1263default: {1264keycode_handled = false;1265} break;1266}12671268if (keycode_handled) {1269accept_event();1270}1271}1272}12731274void ProjectManager::_files_dropped(PackedStringArray p_files) {1275// TODO: Support installing multiple ZIPs at the same time?1276if (p_files.size() == 1 && p_files[0].ends_with(".zip")) {1277const String &file = p_files[0];1278_install_project(file, file.get_file().get_basename().capitalize());1279return;1280}12811282HashSet<String> folders_set;1283Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);1284for (int i = 0; i < p_files.size(); i++) {1285const String &file = p_files[i];1286folders_set.insert(da->dir_exists(file) ? file : file.get_base_dir());1287}1288ERR_FAIL_COND(folders_set.is_empty()); // This can't really happen, we consume every dropped file path above.12891290PackedStringArray folders;1291for (const String &E : folders_set) {1292folders.push_back(E);1293}1294project_list->find_projects_multiple(folders);1295}12961297void ProjectManager::_titlebar_resized() {1298DisplayServer::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);1299const Vector3i &margin = DisplayServer::get_singleton()->window_get_safe_title_margins(DisplayServer::MAIN_WINDOW_ID);1300if (left_menu_spacer) {1301int w = (root_container->is_layout_rtl()) ? margin.y : margin.x;1302left_menu_spacer->set_custom_minimum_size(Size2(w, 0));1303}1304if (right_menu_spacer) {1305int w = (root_container->is_layout_rtl()) ? margin.x : margin.y;1306right_menu_spacer->set_custom_minimum_size(Size2(w, 0));1307}1308if (title_bar) {1309title_bar->set_custom_minimum_size(Size2(0, margin.z - title_bar->get_global_position().y));1310}1311}13121313void ProjectManager::_open_donate_page() {1314OS::get_singleton()->shell_open("https://fund.godotengine.org/?ref=project_manager");1315}13161317// Object methods.13181319ProjectManager::ProjectManager() {1320singleton = this;13211322// Turn off some servers we aren't going to be using in the Project Manager.1323NavigationServer3D::get_singleton()->set_active(false);1324PhysicsServer3D::get_singleton()->set_active(false);1325PhysicsServer2D::get_singleton()->set_active(false);13261327// Initialize settings.1328{1329if (!EditorSettings::get_singleton()) {1330EditorSettings::create();1331}1332EditorSettings::get_singleton()->set_optimize_save(false); // Just write settings as they come.13331334{1335bool agile_input_event_flushing = EDITOR_GET("input/buffering/agile_event_flushing");1336bool use_accumulated_input = EDITOR_GET("input/buffering/use_accumulated_input");13371338Input::get_singleton()->set_agile_input_event_flushing(agile_input_event_flushing);1339Input::get_singleton()->set_use_accumulated_input(use_accumulated_input);1340}13411342int display_scale = EDITOR_GET("interface/editor/display_scale");13431344switch (display_scale) {1345case 0:1346// Try applying a suitable display scale automatically.1347EditorScale::set_scale(EditorSettings::get_auto_display_scale());1348break;1349case 1:1350EditorScale::set_scale(0.75);1351break;1352case 2:1353EditorScale::set_scale(1.0);1354break;1355case 3:1356EditorScale::set_scale(1.25);1357break;1358case 4:1359EditorScale::set_scale(1.5);1360break;1361case 5:1362EditorScale::set_scale(1.75);1363break;1364case 6:1365EditorScale::set_scale(2.0);1366break;1367default:1368EditorScale::set_scale(EDITOR_GET("interface/editor/custom_display_scale"));1369break;1370}1371FileDialog::set_get_icon_callback(callable_mp_static(ProjectManager::_file_dialog_get_icon));1372FileDialog::set_get_thumbnail_callback(callable_mp_static(ProjectManager::_file_dialog_get_thumbnail));13731374FileDialog::set_default_show_hidden_files(EDITOR_GET("filesystem/file_dialog/show_hidden_files"));1375FileDialog::set_default_display_mode((FileDialog::DisplayMode)EDITOR_GET("filesystem/file_dialog/display_mode").operator int());13761377int swap_cancel_ok = EDITOR_GET("interface/editor/accept_dialog_cancel_ok_buttons");1378if (swap_cancel_ok != 0) { // 0 is auto, set in register_scene based on DisplayServer.1379// Swap on means OK first.1380AcceptDialog::set_swap_cancel_ok(swap_cancel_ok == 2);1381}13821383OS::get_singleton()->set_low_processor_usage_mode(true);1384}13851386SceneTree::get_singleton()->get_root()->connect("files_dropped", callable_mp(this, &ProjectManager::_files_dropped));13871388// Initialize UI.1389{1390int pm_root_dir = EDITOR_GET("interface/editor/ui_layout_direction");1391Control::set_root_layout_direction(pm_root_dir);1392Window::set_root_layout_direction(pm_root_dir);13931394EditorThemeManager::initialize();1395theme = EditorThemeManager::generate_theme();1396DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor)));13971398set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);13991400_build_icon_type_cache(theme);1401}14021403// Project manager layout.14041405background_panel = memnew(Panel);1406add_child(background_panel);1407background_panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);14081409root_container = memnew(MarginContainer);1410add_child(root_container);1411root_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);14121413main_vbox = memnew(VBoxContainer);1414root_container->add_child(main_vbox);14151416// Title bar.1417bool can_expand = bool(EDITOR_GET("interface/editor/expand_to_title")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EXTEND_TO_TITLE);14181419{1420title_bar = memnew(EditorTitleBar);1421main_vbox->add_child(title_bar);14221423if (can_expand) {1424// Add spacer to avoid other controls under window minimize/maximize/close buttons (left side).1425left_menu_spacer = memnew(Control);1426left_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);1427title_bar->add_child(left_menu_spacer);1428}14291430HBoxContainer *left_hbox = memnew(HBoxContainer);1431left_hbox->set_alignment(BoxContainer::ALIGNMENT_BEGIN);1432left_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);1433left_hbox->set_stretch_ratio(1.0);1434title_bar->add_child(left_hbox);14351436title_bar_logo = memnew(Button);1437title_bar_logo->set_flat(true);1438title_bar_logo->set_tooltip_text(TTR("About Godot"));1439left_hbox->add_child(title_bar_logo);1440title_bar_logo->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_show_about));14411442bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU);1443if (global_menu) {1444MenuBar *main_menu_bar = memnew(MenuBar);1445main_menu_bar->set_start_index(0); // Main menu, add to the start of global menu.1446main_menu_bar->set_prefer_global_menu(true);1447left_hbox->add_child(main_menu_bar);14481449if (NativeMenu::get_singleton()->has_system_menu(NativeMenu::WINDOW_MENU_ID)) {1450PopupMenu *window_menu = memnew(PopupMenu);1451window_menu->set_system_menu(NativeMenu::WINDOW_MENU_ID);1452window_menu->set_name(TTRC("Window"));1453main_menu_bar->add_child(window_menu);1454}1455if (NativeMenu::get_singleton()->has_system_menu(NativeMenu::HELP_MENU_ID)) {1456PopupMenu *help_menu = memnew(PopupMenu);1457help_menu->set_system_menu(NativeMenu::HELP_MENU_ID);1458help_menu->set_name(TTRC("Help"));1459main_menu_bar->add_child(help_menu);1460}1461}1462if (can_expand) {1463// Spacer to center main toggles.1464left_spacer = memnew(Control);1465left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);1466title_bar->add_child(left_spacer);1467}14681469main_view_toggles = memnew(HBoxContainer);1470main_view_toggles->set_alignment(BoxContainer::ALIGNMENT_CENTER);1471main_view_toggles->set_h_size_flags(Control::SIZE_EXPAND_FILL);1472main_view_toggles->set_stretch_ratio(2.0);1473title_bar->add_child(main_view_toggles);1474title_bar->set_center_control(main_view_toggles);14751476if (can_expand) {1477// Spacer to center main toggles.1478right_spacer = memnew(Control);1479right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);1480title_bar->add_child(right_spacer);1481}14821483main_view_toggles_group.instantiate();14841485HBoxContainer *right_hbox = memnew(HBoxContainer);1486right_hbox->set_alignment(BoxContainer::ALIGNMENT_END);1487right_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);1488right_hbox->set_stretch_ratio(1.0);1489title_bar->add_child(right_hbox);14901491quick_settings_button = memnew(Button);1492quick_settings_button->set_flat(true);1493quick_settings_button->set_text(TTRC("Settings"));1494right_hbox->add_child(quick_settings_button);1495quick_settings_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_show_quick_settings));14961497if (can_expand) {1498// Add spacer to avoid other controls under the window minimize/maximize/close buttons (right side).1499right_menu_spacer = memnew(Control);1500right_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);1501title_bar->add_child(right_menu_spacer);1502}1503}15041505main_view_container = memnew(PanelContainer);1506main_view_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);1507main_vbox->add_child(main_view_container);15081509// Project list view.1510{1511local_projects_vb = memnew(VBoxContainer);1512local_projects_vb->set_name("LocalProjectsTab");1513_add_main_view(MAIN_VIEW_PROJECTS, TTRC("Projects"), Ref<Texture2D>(), local_projects_vb);15141515// Project list's top bar.1516{1517HBoxContainer *hb = memnew(HBoxContainer);1518hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);1519local_projects_vb->add_child(hb);15201521create_btn = memnew(Button);1522create_btn->set_text(TTRC("Create"));1523create_btn->set_shortcut(ED_SHORTCUT("project_manager/new_project", TTRC("New Project"), KeyModifierMask::CMD_OR_CTRL | Key::N));1524create_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_new_project));1525hb->add_child(create_btn);15261527import_btn = memnew(Button);1528import_btn->set_text(TTRC("Import"));1529import_btn->set_shortcut(ED_SHORTCUT("project_manager/import_project", TTRC("Import Project"), KeyModifierMask::CMD_OR_CTRL | Key::I));1530import_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_import_project));1531hb->add_child(import_btn);15321533scan_btn = memnew(Button);1534scan_btn->set_text(TTRC("Scan"));1535scan_btn->set_shortcut(ED_SHORTCUT("project_manager/scan_projects", TTRC("Scan Projects"), KeyModifierMask::CMD_OR_CTRL | Key::S));1536scan_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_scan_projects));1537hb->add_child(scan_btn);15381539loading_label = memnew(Label(TTRC("Loading, please wait...")));1540loading_label->set_accessibility_live(DisplayServer::AccessibilityLiveMode::LIVE_ASSERTIVE);1541loading_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);1542loading_label->hide();1543hb->add_child(loading_label);15441545search_box = memnew(LineEdit);1546search_box->set_placeholder(TTRC("Filter Projects"));1547search_box->set_accessibility_name(TTRC("Filter Projects"));1548search_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."));1549search_box->set_clear_button_enabled(true);1550search_box->connect(SceneStringName(text_changed), callable_mp(this, &ProjectManager::_on_search_term_changed));1551search_box->connect(SceneStringName(text_submitted), callable_mp(this, &ProjectManager::_on_search_term_submitted));1552search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);1553hb->add_child(search_box);15541555sort_label = memnew(Label);1556sort_label->set_text(TTRC("Sort:"));1557hb->add_child(sort_label);15581559filter_option = memnew(OptionButton);1560filter_option->set_clip_text(true);1561filter_option->set_h_size_flags(Control::SIZE_EXPAND_FILL);1562filter_option->set_stretch_ratio(0.3);1563filter_option->set_accessibility_name(TTRC("Sort:"));1564filter_option->connect(SceneStringName(item_selected), callable_mp(this, &ProjectManager::_on_order_option_changed));1565hb->add_child(filter_option);15661567filter_option->add_item(TTRC("Last Edited"));1568filter_option->add_item(TTRC("Name"));1569filter_option->add_item(TTRC("Path"));1570filter_option->add_item(TTRC("Tags"));1571}15721573// Project list and its sidebar.1574{1575HBoxContainer *project_list_hbox = memnew(HBoxContainer);1576local_projects_vb->add_child(project_list_hbox);1577project_list_hbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);15781579project_list_panel = memnew(PanelContainer);1580project_list_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);1581project_list_hbox->add_child(project_list_panel);15821583project_list = memnew(ProjectList);1584project_list->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);1585project_list_panel->add_child(project_list);1586project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));1587project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_list_placeholder));1588project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));1589project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_check_recovery_mode));1590project_list->connect(ProjectList::SIGNAL_MENU_OPTION_SELECTED, callable_mp(this, &ProjectManager::_project_list_menu_option));15911592// Empty project list placeholder.1593{1594empty_list_placeholder = memnew(VBoxContainer);1595empty_list_placeholder->set_v_size_flags(Control::SIZE_SHRINK_CENTER);1596empty_list_placeholder->add_theme_constant_override("separation", 16 * EDSCALE);1597empty_list_placeholder->hide();1598project_list_panel->add_child(empty_list_placeholder);15991600empty_list_message = memnew(RichTextLabel);1601empty_list_message->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);1602empty_list_message->set_use_bbcode(true);1603empty_list_message->set_fit_content(true);1604empty_list_message->set_h_size_flags(SIZE_EXPAND_FILL);1605empty_list_message->add_theme_style_override(CoreStringName(normal), memnew(StyleBoxEmpty));16061607empty_list_placeholder->add_child(empty_list_message);16081609FlowContainer *empty_list_actions = memnew(FlowContainer);1610empty_list_actions->set_alignment(FlowContainer::ALIGNMENT_CENTER);1611empty_list_placeholder->add_child(empty_list_actions);16121613empty_list_create_project = memnew(Button);1614empty_list_create_project->set_text(TTRC("Create New Project"));1615empty_list_create_project->set_theme_type_variation("PanelBackgroundButton");1616empty_list_actions->add_child(empty_list_create_project);1617empty_list_create_project->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_new_project));16181619empty_list_import_project = memnew(Button);1620empty_list_import_project->set_text(TTRC("Import Existing Project"));1621empty_list_import_project->set_theme_type_variation("PanelBackgroundButton");1622empty_list_actions->add_child(empty_list_import_project);1623empty_list_import_project->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_import_project));16241625empty_list_open_assetlib = memnew(Button);1626empty_list_open_assetlib->set_text(TTRC("Open Asset Library"));1627empty_list_open_assetlib->set_theme_type_variation("PanelBackgroundButton");1628empty_list_actions->add_child(empty_list_open_assetlib);1629empty_list_open_assetlib->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_asset_library_confirmed));16301631empty_list_online_warning = memnew(Label);1632empty_list_online_warning->set_focus_mode(FOCUS_ACCESSIBILITY);1633empty_list_online_warning->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);1634empty_list_online_warning->set_custom_minimum_size(Size2(220, 0) * EDSCALE);1635empty_list_online_warning->set_autowrap_mode(TextServer::AUTOWRAP_WORD);1636empty_list_online_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL);1637empty_list_online_warning->set_text(TTRC("Note: The Asset Library requires an online connection and involves sending data over the internet."));1638empty_list_placeholder->add_child(empty_list_online_warning);1639}16401641// The side bar with the edit, run, rename, etc. buttons.1642VBoxContainer *project_list_sidebar = memnew(VBoxContainer);1643project_list_sidebar->set_custom_minimum_size(Size2(120, 120));1644project_list_hbox->add_child(project_list_sidebar);16451646project_list_sidebar->add_child(memnew(HSeparator));16471648ScrollContainer *sidebar_scroll_containter = memnew(ScrollContainer);1649sidebar_scroll_containter->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);1650sidebar_scroll_containter->set_v_size_flags(Control::SIZE_EXPAND_FILL);1651project_list_sidebar->add_child(sidebar_scroll_containter);1652VBoxContainer *sidebar_buttons_containter = memnew(VBoxContainer);1653sidebar_scroll_containter->add_child(sidebar_buttons_containter);16541655open_btn_container = memnew(HBoxContainer);1656open_btn_container->set_anchors_preset(Control::PRESET_FULL_RECT);1657sidebar_buttons_containter->add_child(open_btn_container);16581659open_btn = memnew(Button);1660open_btn->set_text(TTRC("Edit"));1661open_btn->set_shortcut(ED_SHORTCUT("project_manager/edit_project", TTRC("Edit Project"), KeyModifierMask::CMD_OR_CTRL | Key::E));1662open_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_check_recovery_mode));1663open_btn->set_h_size_flags(Control::SIZE_EXPAND_FILL);1664open_btn_container->add_child(open_btn);16651666open_btn_container->add_child(memnew(VSeparator));16671668open_options_btn = memnew(Button);1669open_options_btn->set_accessibility_name(TTRC("Options"));1670open_options_btn->set_icon_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);1671open_options_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_options_popup));1672open_btn_container->add_child(open_options_btn);16731674open_options_popup = memnew(PopupMenu);1675open_options_popup->add_item(TTRC("Edit in verbose mode"));1676open_options_popup->add_item(TTRC("Edit in recovery mode"));1677open_options_popup->connect(SceneStringName(id_pressed), callable_mp(this, &ProjectManager::_on_open_options_selected));1678open_options_btn->add_child(open_options_popup);16791680open_btn_container->set_custom_minimum_size(Size2(120, open_btn->get_combined_minimum_size().y));16811682run_btn = memnew(Button);1683run_btn->set_text(TTRC("Run"));1684run_btn->set_shortcut(ED_SHORTCUT("project_manager/run_project", TTRC("Run Project"), KeyModifierMask::CMD_OR_CTRL | Key::R));1685run_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_run_project));1686sidebar_buttons_containter->add_child(run_btn);16871688rename_btn = memnew(Button);1689rename_btn->set_text(TTRC("Rename"));1690// The F2 shortcut isn't overridden with Enter on macOS as Enter is already used to edit a project.1691rename_btn->set_shortcut(ED_SHORTCUT("project_manager/rename_project", TTRC("Rename Project"), Key::F2));1692rename_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_rename_project));1693sidebar_buttons_containter->add_child(rename_btn);16941695duplicate_btn = memnew(Button);1696duplicate_btn->set_text(TTRC("Duplicate"));1697duplicate_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_duplicate_project));1698sidebar_buttons_containter->add_child(duplicate_btn);16991700manage_tags_btn = memnew(Button);1701manage_tags_btn->set_text(TTRC("Manage Tags"));1702manage_tags_btn->set_shortcut(ED_SHORTCUT("project_manager/project_tags", TTRC("Manage Tags"), KeyModifierMask::CMD_OR_CTRL | Key::T));1703sidebar_buttons_containter->add_child(manage_tags_btn);17041705erase_btn = memnew(Button);1706erase_btn->set_text(TTRC("Remove"));1707erase_btn->set_shortcut(ED_SHORTCUT("project_manager/remove_project", TTRC("Remove Project"), Key::KEY_DELETE));1708erase_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_project));1709sidebar_buttons_containter->add_child(erase_btn);17101711erase_missing_btn = memnew(Button);1712erase_missing_btn->set_text(TTRC("Remove Missing"));1713erase_missing_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_missing_projects));1714sidebar_buttons_containter->add_child(erase_missing_btn);17151716donate_btn = memnew(Button);1717donate_btn->set_text(TTRC("Donate"));1718donate_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_donate_page));1719project_list_sidebar->add_child(donate_btn);1720}1721}17221723// Asset library view.1724if (AssetLibraryEditorPlugin::is_available()) {1725asset_library = memnew(EditorAssetLibrary(true));1726asset_library->set_name("AssetLibraryTab");1727_add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Library"), Ref<Texture2D>(), asset_library);1728asset_library->connect("install_asset", callable_mp(this, &ProjectManager::_install_project));1729} else {1730VBoxContainer *asset_library_filler = memnew(VBoxContainer);1731asset_library_filler->set_name("AssetLibraryTab");1732Button *asset_library_toggle = _add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Library"), Ref<Texture2D>(), asset_library_filler);1733asset_library_toggle->set_disabled(true);1734asset_library_toggle->set_tooltip_text(TTRC("Asset Library not available (due to using Web editor, or because SSL support disabled)."));1735}17361737// Footer bar.1738{1739HBoxContainer *footer_bar = memnew(HBoxContainer);1740footer_bar->set_alignment(BoxContainer::ALIGNMENT_END);1741footer_bar->add_theme_constant_override("separation", 20 * EDSCALE);1742main_vbox->add_child(footer_bar);17431744#ifdef ENGINE_UPDATE_CHECK_ENABLED1745EngineUpdateLabel *update_label = memnew(EngineUpdateLabel);1746footer_bar->add_child(update_label);1747update_label->connect("offline_clicked", callable_mp(this, &ProjectManager::_show_quick_settings));1748#endif17491750EditorVersionButton *version_btn = memnew(EditorVersionButton(EditorVersionButton::FORMAT_WITH_BUILD));1751// Fade the version label to be less prominent, but still readable.1752version_btn->set_self_modulate(Color(1, 1, 1, 0.6));1753footer_bar->add_child(version_btn);1754}17551756// Dialogs.1757{1758quick_settings_dialog = memnew(QuickSettingsDialog);1759add_child(quick_settings_dialog);1760quick_settings_dialog->connect("restart_required", callable_mp(this, &ProjectManager::_restart_confirmed));17611762scan_dir = memnew(EditorFileDialog);1763scan_dir->set_access(EditorFileDialog::ACCESS_FILESYSTEM);1764scan_dir->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);1765scan_dir->set_title(TTRC("Select a Folder to Scan")); // Must be after mode or it's overridden.1766scan_dir->set_current_dir(EDITOR_GET("filesystem/directories/default_project_path"));1767add_child(scan_dir);1768scan_dir->connect("dir_selected", callable_mp(project_list, &ProjectList::find_projects));17691770erase_missing_ask = memnew(ConfirmationDialog);1771erase_missing_ask->set_ok_button_text(TTRC("Remove All"));1772erase_missing_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_missing_projects_confirm));1773add_child(erase_missing_ask);17741775erase_ask = memnew(ConfirmationDialog);1776erase_ask->set_ok_button_text(TTRC("Remove"));1777erase_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_project_confirm));1778add_child(erase_ask);17791780VBoxContainer *erase_ask_vb = memnew(VBoxContainer);1781erase_ask->add_child(erase_ask_vb);17821783erase_ask_label = memnew(Label);1784erase_ask_label->set_focus_mode(FOCUS_ACCESSIBILITY);1785erase_ask_vb->add_child(erase_ask_label);17861787// Comment out for now until we have a better warning system to1788// ensure users delete their project only.1789//delete_project_contents = memnew(CheckBox);1790//delete_project_contents->set_text(TTRC("Also delete project contents (no undo!)"));1791//erase_ask_vb->add_child(delete_project_contents);17921793multi_open_ask = memnew(ConfirmationDialog);1794multi_open_ask->set_ok_button_text(TTRC("Edit"));1795multi_open_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects));1796add_child(multi_open_ask);17971798multi_run_ask = memnew(ConfirmationDialog);1799multi_run_ask->set_ok_button_text(TTRC("Run"));1800multi_run_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_run_project_confirm));1801add_child(multi_run_ask);18021803open_recovery_mode_ask = memnew(ConfirmationDialog);1804open_recovery_mode_ask->set_min_size(Size2(550, 70) * EDSCALE);1805open_recovery_mode_ask->set_autowrap(true);1806open_recovery_mode_ask->add_button(TTRC("Edit normally"))->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_on_recovery_mode_popup_open_normal));1807open_recovery_mode_ask->set_ok_button_text(TTRC("Edit in Recovery Mode"));1808open_recovery_mode_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_on_recovery_mode_popup_open_recovery));1809add_child(open_recovery_mode_ask);18101811ask_update_settings = memnew(ConfirmationDialog);1812add_child(ask_update_settings);1813ask_update_vb = memnew(VBoxContainer);1814ask_update_settings->add_child(ask_update_vb);1815ask_update_label = memnew(Label);1816ask_update_label->set_focus_mode(FOCUS_ACCESSIBILITY);1817ask_update_label->set_custom_minimum_size(Size2(300 * EDSCALE, 1));1818ask_update_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);1819ask_update_label->set_v_size_flags(SIZE_EXPAND_FILL);1820ask_update_vb->add_child(ask_update_label);1821ask_update_backup = memnew(CheckBox);1822ask_update_backup->set_text(TTRC("Backup project first"));1823ask_update_backup->set_h_size_flags(SIZE_SHRINK_CENTER);1824ask_update_vb->add_child(ask_update_backup);1825ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_with_migration));1826int ed_swap_cancel_ok = EDITOR_GET("interface/editor/accept_dialog_cancel_ok_buttons");1827if (ed_swap_cancel_ok == 0) {1828ed_swap_cancel_ok = DisplayServer::get_singleton()->get_swap_cancel_ok() ? 2 : 1;1829}1830full_convert_button = ask_update_settings->add_button(TTRC("Convert Full Project"), ed_swap_cancel_ok != 2);1831full_convert_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_full_convert_button_pressed));1832migration_guide_button = ask_update_settings->add_button(TTRC("See Migration Guide"), ed_swap_cancel_ok != 2);1833migration_guide_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_migration_guide_button_pressed));18341835ask_full_convert_dialog = memnew(ConfirmationDialog);1836ask_full_convert_dialog->set_autowrap(true);1837ask_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."));1838ask_full_convert_dialog->connect(SceneStringName(confirmed), callable_mp(this, &ProjectManager::_perform_full_project_conversion));1839add_child(ask_full_convert_dialog);18401841project_dialog = memnew(ProjectDialog);1842project_dialog->connect("projects_updated", callable_mp(this, &ProjectManager::_on_projects_updated));1843project_dialog->connect("project_created", callable_mp(this, &ProjectManager::_on_project_created));1844project_dialog->connect("project_duplicated", callable_mp(this, &ProjectManager::_on_project_duplicated));1845add_child(project_dialog);18461847error_dialog = memnew(AcceptDialog);1848error_dialog->set_title(TTRC("Error"));1849add_child(error_dialog);18501851about_dialog = memnew(EditorAbout);1852add_child(about_dialog);1853}18541855// Tag management.1856{1857tag_manage_dialog = memnew(ConfirmationDialog);1858add_child(tag_manage_dialog);1859tag_manage_dialog->set_title(TTRC("Manage Project Tags"));1860tag_manage_dialog->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_apply_project_tags));1861manage_tags_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_manage_project_tags));18621863VBoxContainer *tag_vb = memnew(VBoxContainer);1864tag_manage_dialog->add_child(tag_vb);18651866Label *label = memnew(Label(TTRC("Project Tags")));1867tag_vb->add_child(label);1868label->set_theme_type_variation("HeaderMedium");1869label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);18701871label = memnew(Label(TTRC("Click tag to remove it from the project.")));1872tag_vb->add_child(label);1873label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);18741875project_tags = memnew(HFlowContainer);1876tag_vb->add_child(project_tags);1877project_tags->set_custom_minimum_size(Vector2(0, 100) * EDSCALE);18781879tag_vb->add_child(memnew(HSeparator));18801881label = memnew(Label(TTRC("All Tags")));1882tag_vb->add_child(label);1883label->set_theme_type_variation("HeaderMedium");1884label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);18851886label = memnew(Label(TTRC("Click tag to add it to the project.")));1887tag_vb->add_child(label);1888label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);18891890all_tags = memnew(HFlowContainer);1891tag_vb->add_child(all_tags);1892all_tags->set_custom_minimum_size(Vector2(0, 100) * EDSCALE);18931894tag_edit_error = memnew(Label);1895tag_vb->add_child(tag_edit_error);1896tag_edit_error->set_autowrap_mode(TextServer::AUTOWRAP_WORD);18971898create_tag_dialog = memnew(ConfirmationDialog);1899tag_manage_dialog->add_child(create_tag_dialog);1900create_tag_dialog->set_title(TTRC("Create New Tag"));1901create_tag_dialog->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_create_new_tag));19021903tag_vb = memnew(VBoxContainer);1904create_tag_dialog->add_child(tag_vb);19051906Label *info = memnew(Label(TTRC("Tags are capitalized automatically when displayed.")));1907tag_vb->add_child(info);19081909new_tag_name = memnew(LineEdit);1910tag_vb->add_child(new_tag_name);1911new_tag_name->set_accessibility_name(TTRC("New Tag Name"));1912new_tag_name->set_placeholder(TTRC("example_tag (will display as Example Tag)"));1913new_tag_name->connect(SceneStringName(text_changed), callable_mp(this, &ProjectManager::_set_new_tag_name));1914new_tag_name->connect(SceneStringName(text_submitted), callable_mp(this, &ProjectManager::_create_new_tag).unbind(1));1915create_tag_dialog->connect("about_to_popup", callable_mp(new_tag_name, &LineEdit::clear));1916create_tag_dialog->connect("about_to_popup", callable_mp((Control *)new_tag_name, &Control::grab_focus).bind(false), CONNECT_DEFERRED);19171918tag_error = memnew(Label);1919tag_error->set_focus_mode(FOCUS_ACCESSIBILITY);1920tag_vb->add_child(tag_error);19211922create_tag_btn = memnew(Button);1923create_tag_btn->set_accessibility_name(TTRC("Create Tag"));1924all_tags->add_child(create_tag_btn);1925create_tag_btn->connect(SceneStringName(pressed), callable_mp((Window *)create_tag_dialog, &Window::popup_centered).bind(Vector2i(500, 0) * EDSCALE));19261927_set_new_tag_name("");1928}19291930// Initialize project list.1931{1932project_list->load_project_list();19331934Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM);19351936String default_project_path = EDITOR_GET("filesystem/directories/default_project_path");1937if (!default_project_path.is_empty() && !dir_access->dir_exists(default_project_path)) {1938Error error = dir_access->make_dir_recursive(default_project_path);1939if (error != OK) {1940ERR_PRINT("Could not create default project directory at: " + default_project_path);1941}1942}19431944String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path");1945if (!autoscan_path.is_empty()) {1946if (dir_access->dir_exists(autoscan_path)) {1947project_list->find_projects(autoscan_path);1948} else {1949Error error = dir_access->make_dir_recursive(autoscan_path);1950if (error != OK) {1951ERR_PRINT("Could not create project autoscan directory at: " + autoscan_path);1952}1953}1954}1955project_list->update_project_list();1956initialized = true;1957}19581959// Extend menu bar to window title.1960if (can_expand) {1961DisplayServer::get_singleton()->process_events();1962DisplayServer::get_singleton()->window_set_flag(DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE, true, DisplayServer::MAIN_WINDOW_ID);1963title_bar->set_can_move_window(true);1964title_bar->connect(SceneStringName(item_rect_changed), callable_mp(this, &ProjectManager::_titlebar_resized));1965}19661967_update_size_limits();1968}19691970ProjectManager::~ProjectManager() {1971singleton = nullptr;1972EditorInspector::cleanup_plugins();1973if (EditorSettings::get_singleton()) {1974EditorSettings::destroy();1975}19761977EditorThemeManager::finalize();1978}197919801981