Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/project_manager/project_manager.cpp
9902 views
1
/**************************************************************************/
2
/* project_manager.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "project_manager.h"
32
33
#include "core/config/project_settings.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/project_manager/engine_update_label.h"
47
#include "editor/project_manager/project_dialog.h"
48
#include "editor/project_manager/project_list.h"
49
#include "editor/project_manager/project_tag.h"
50
#include "editor/project_manager/quick_settings_dialog.h"
51
#include "editor/settings/editor_settings.h"
52
#include "editor/themes/editor_scale.h"
53
#include "editor/themes/editor_theme_manager.h"
54
#include "main/main.h"
55
#include "scene/gui/check_box.h"
56
#include "scene/gui/flow_container.h"
57
#include "scene/gui/line_edit.h"
58
#include "scene/gui/margin_container.h"
59
#include "scene/gui/option_button.h"
60
#include "scene/gui/panel_container.h"
61
#include "scene/gui/rich_text_label.h"
62
#include "scene/gui/separator.h"
63
#include "scene/main/window.h"
64
#include "scene/theme/theme_db.h"
65
#include "servers/display_server.h"
66
#include "servers/navigation_server_3d.h"
67
68
#ifndef PHYSICS_3D_DISABLED
69
#include "servers/physics_server_3d.h"
70
#endif // PHYSICS_3D_DISABLED
71
72
#ifndef PHYSICS_2D_DISABLED
73
#include "servers/physics_server_2d.h"
74
#endif // PHYSICS_2D_DISABLED
75
76
constexpr int GODOT4_CONFIG_VERSION = 5;
77
78
ProjectManager *ProjectManager::singleton = nullptr;
79
80
// Notifications.
81
82
void ProjectManager::_notification(int p_what) {
83
switch (p_what) {
84
case NOTIFICATION_ENTER_TREE: {
85
Engine::get_singleton()->set_editor_hint(false);
86
87
Window *main_window = get_window();
88
if (main_window) {
89
// Handle macOS fullscreen and extend-to-title changes.
90
main_window->connect("titlebar_changed", callable_mp(this, &ProjectManager::_titlebar_resized));
91
}
92
93
// Theme has already been created in the constructor, so we can skip that step.
94
_update_theme(true);
95
} break;
96
97
case NOTIFICATION_READY: {
98
DisplayServer::get_singleton()->screen_set_keep_on(EDITOR_GET("interface/editor/keep_screen_on"));
99
const int default_sorting = (int)EDITOR_GET("project_manager/sorting_order");
100
filter_option->select(default_sorting);
101
project_list->set_order_option(default_sorting);
102
103
_select_main_view(MAIN_VIEW_PROJECTS);
104
_update_list_placeholder();
105
_titlebar_resized();
106
} break;
107
108
case NOTIFICATION_TRANSLATION_CHANGED: {
109
// TRANSLATORS: This refers to the application where users manage their Godot projects.
110
SceneTree::get_singleton()->get_root()->set_title(GODOT_VERSION_NAME + String(" - ") + TTR("Project Manager", "Application"));
111
112
const String line1 = TTR("You don't have any projects yet.");
113
const String line2 = TTR("Get started by creating a new one,\nimporting one that exists, or by downloading a project template from the Asset Library!");
114
empty_list_message->set_text(vformat("[center][b]%s[/b] %s[/center]", line1, line2));
115
116
_titlebar_resized();
117
} break;
118
119
case NOTIFICATION_VISIBILITY_CHANGED: {
120
set_process_shortcut_input(is_visible_in_tree());
121
} break;
122
123
case NOTIFICATION_WM_CLOSE_REQUEST: {
124
_dim_window();
125
} break;
126
127
case NOTIFICATION_WM_ABOUT: {
128
_show_about();
129
} break;
130
131
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
132
if (EditorThemeManager::is_generated_theme_outdated()) {
133
_update_theme();
134
}
135
_update_list_placeholder();
136
} break;
137
}
138
}
139
140
// Utility data.
141
142
Ref<Texture2D> ProjectManager::_file_dialog_get_icon(const String &p_path) {
143
if (p_path.get_extension().to_lower() == "godot") {
144
return singleton->icon_type_cache["GodotMonochrome"];
145
}
146
147
return singleton->icon_type_cache["Object"];
148
}
149
150
Ref<Texture2D> ProjectManager::_file_dialog_get_thumbnail(const String &p_path) {
151
if (p_path.get_extension().to_lower() == "godot") {
152
return singleton->icon_type_cache["GodotFile"];
153
}
154
155
return Ref<Texture2D>();
156
}
157
158
void ProjectManager::_build_icon_type_cache(Ref<Theme> p_theme) {
159
if (p_theme.is_null()) {
160
return;
161
}
162
List<StringName> tl;
163
p_theme->get_icon_list(EditorStringName(EditorIcons), &tl);
164
for (const StringName &name : tl) {
165
icon_type_cache[name] = p_theme->get_icon(name, EditorStringName(EditorIcons));
166
}
167
}
168
169
// Main layout.
170
171
void ProjectManager::_update_size_limits() {
172
const Size2 minimum_size = Size2(720, 450) * EDSCALE;
173
174
// Define a minimum window size to prevent UI elements from overlapping or being cut off.
175
Window *w = Object::cast_to<Window>(SceneTree::get_singleton()->get_root());
176
if (w) {
177
// Calling Window methods this early doesn't sync properties with DS.
178
w->set_min_size(minimum_size);
179
DisplayServer::get_singleton()->window_set_min_size(minimum_size);
180
}
181
Size2 real_size = DisplayServer::get_singleton()->window_get_size();
182
183
Rect2i screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(DisplayServer::get_singleton()->window_get_current_screen());
184
if (screen_rect.size != Vector2i()) {
185
// Center the window on the screen.
186
Vector2i window_position;
187
window_position.x = screen_rect.position.x + (screen_rect.size.x - real_size.x) / 2;
188
window_position.y = screen_rect.position.y + (screen_rect.size.y - real_size.y) / 2;
189
190
// Limit popup menus to prevent unusably long lists.
191
// We try to set it to half the screen resolution, but no smaller than the minimum window size.
192
Size2 half_screen_rect = (screen_rect.size * EDSCALE) / 2;
193
Size2 maximum_popup_size = MAX(half_screen_rect, minimum_size);
194
quick_settings_dialog->update_size_limits(maximum_popup_size);
195
}
196
}
197
198
void ProjectManager::_update_theme(bool p_skip_creation) {
199
if (!p_skip_creation) {
200
theme = EditorThemeManager::generate_theme(theme);
201
DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor)));
202
}
203
204
Vector<Ref<Theme>> editor_themes;
205
editor_themes.push_back(theme);
206
editor_themes.push_back(ThemeDB::get_singleton()->get_default_theme());
207
208
ThemeContext *node_tc = ThemeDB::get_singleton()->get_theme_context(this);
209
if (node_tc) {
210
node_tc->set_themes(editor_themes);
211
} else {
212
ThemeDB::get_singleton()->create_theme_context(this, editor_themes);
213
}
214
215
Window *owner_window = get_window();
216
if (owner_window) {
217
ThemeContext *window_tc = ThemeDB::get_singleton()->get_theme_context(owner_window);
218
if (window_tc) {
219
window_tc->set_themes(editor_themes);
220
} else {
221
ThemeDB::get_singleton()->create_theme_context(owner_window, editor_themes);
222
}
223
}
224
225
// Update styles.
226
{
227
const int top_bar_separation = get_theme_constant(SNAME("top_bar_separation"), EditorStringName(Editor));
228
root_container->add_theme_constant_override("margin_left", top_bar_separation);
229
root_container->add_theme_constant_override("margin_top", top_bar_separation);
230
root_container->add_theme_constant_override("margin_bottom", top_bar_separation);
231
root_container->add_theme_constant_override("margin_right", top_bar_separation);
232
main_vbox->add_theme_constant_override("separation", top_bar_separation);
233
234
background_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("Background"), EditorStringName(EditorStyles)));
235
main_view_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("TabContainer")));
236
237
title_bar_logo->set_button_icon(get_editor_theme_icon(SNAME("TitleBarLogo")));
238
239
_set_main_view_icon(MAIN_VIEW_PROJECTS, get_editor_theme_icon(SNAME("ProjectList")));
240
_set_main_view_icon(MAIN_VIEW_ASSETLIB, get_editor_theme_icon(SNAME("AssetLib")));
241
242
// Project list.
243
{
244
loading_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));
245
project_list_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("project_list"), SNAME("ProjectManager")));
246
247
empty_list_create_project->set_button_icon(get_editor_theme_icon(SNAME("Add")));
248
empty_list_import_project->set_button_icon(get_editor_theme_icon(SNAME("Load")));
249
empty_list_open_assetlib->set_button_icon(get_editor_theme_icon(SNAME("AssetLib")));
250
251
empty_list_online_warning->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("italic"), EditorStringName(EditorFonts)));
252
empty_list_online_warning->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("font_placeholder_color"), EditorStringName(Editor)));
253
254
// Top bar.
255
search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
256
quick_settings_button->set_button_icon(get_editor_theme_icon(SNAME("Tools")));
257
258
// Sidebar.
259
create_btn->set_button_icon(get_editor_theme_icon(SNAME("Add")));
260
import_btn->set_button_icon(get_editor_theme_icon(SNAME("Load")));
261
scan_btn->set_button_icon(get_editor_theme_icon(SNAME("Search")));
262
open_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
263
open_options_btn->set_button_icon(get_editor_theme_icon(SNAME("Collapse")));
264
run_btn->set_button_icon(get_editor_theme_icon(SNAME("Play")));
265
rename_btn->set_button_icon(get_editor_theme_icon(SNAME("Rename")));
266
duplicate_btn->set_button_icon(get_editor_theme_icon(SNAME("Duplicate")));
267
manage_tags_btn->set_button_icon(get_editor_theme_icon("Script"));
268
erase_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
269
erase_missing_btn->set_button_icon(get_editor_theme_icon(SNAME("Clear")));
270
create_tag_btn->set_button_icon(get_editor_theme_icon("Add"));
271
272
tag_error->add_theme_color_override(SceneStringName(font_color), get_theme_color("error_color", EditorStringName(Editor)));
273
tag_edit_error->add_theme_color_override(SceneStringName(font_color), get_theme_color("error_color", EditorStringName(Editor)));
274
275
create_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
276
import_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
277
scan_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
278
open_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
279
run_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
280
rename_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
281
duplicate_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
282
manage_tags_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
283
erase_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
284
erase_missing_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
285
286
open_btn_container->add_theme_constant_override("separation", 0);
287
open_options_popup->set_item_icon(0, get_editor_theme_icon(SNAME("Notification")));
288
open_options_popup->set_item_icon(1, get_editor_theme_icon(SNAME("NodeWarning")));
289
}
290
291
// Dialogs
292
migration_guide_button->set_button_icon(get_editor_theme_icon(SNAME("ExternalLink")));
293
294
// Asset library popup.
295
if (asset_library) {
296
// Removes extra border margins.
297
asset_library->add_theme_style_override(SceneStringName(panel), memnew(StyleBoxEmpty));
298
}
299
}
300
}
301
302
Button *ProjectManager::_add_main_view(MainViewTab p_id, const String &p_name, const Ref<Texture2D> &p_icon, Control *p_view_control) {
303
ERR_FAIL_INDEX_V(p_id, MAIN_VIEW_MAX, nullptr);
304
ERR_FAIL_COND_V(main_view_map.has(p_id), nullptr);
305
ERR_FAIL_COND_V(main_view_toggle_map.has(p_id), nullptr);
306
307
Button *toggle_button = memnew(Button);
308
toggle_button->set_flat(true);
309
toggle_button->set_theme_type_variation("MainScreenButton");
310
toggle_button->set_toggle_mode(true);
311
toggle_button->set_button_group(main_view_toggles_group);
312
toggle_button->set_text(p_name);
313
toggle_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_select_main_view).bind((int)p_id));
314
315
main_view_toggles->add_child(toggle_button);
316
main_view_toggle_map[p_id] = toggle_button;
317
318
_set_main_view_icon(p_id, p_icon);
319
320
p_view_control->set_visible(false);
321
main_view_container->add_child(p_view_control);
322
main_view_map[p_id] = p_view_control;
323
324
return toggle_button;
325
}
326
327
void ProjectManager::_set_main_view_icon(MainViewTab p_id, const Ref<Texture2D> &p_icon) {
328
ERR_FAIL_INDEX(p_id, MAIN_VIEW_MAX);
329
ERR_FAIL_COND(!main_view_toggle_map.has(p_id));
330
331
Button *toggle_button = main_view_toggle_map[p_id];
332
333
Ref<Texture2D> old_icon = toggle_button->get_button_icon();
334
if (old_icon.is_valid()) {
335
old_icon->disconnect_changed(callable_mp((Control *)toggle_button, &Control::update_minimum_size));
336
}
337
338
if (p_icon.is_valid()) {
339
toggle_button->set_button_icon(p_icon);
340
// Make sure the control is updated if the icon is reimported.
341
p_icon->connect_changed(callable_mp((Control *)toggle_button, &Control::update_minimum_size));
342
} else {
343
toggle_button->set_button_icon(Ref<Texture2D>());
344
}
345
}
346
347
void ProjectManager::_select_main_view(int p_id) {
348
MainViewTab view_id = (MainViewTab)p_id;
349
350
ERR_FAIL_INDEX(view_id, MAIN_VIEW_MAX);
351
ERR_FAIL_COND(!main_view_map.has(view_id));
352
ERR_FAIL_COND(!main_view_toggle_map.has(view_id));
353
354
if (current_main_view != view_id) {
355
main_view_toggle_map[current_main_view]->set_pressed_no_signal(false);
356
main_view_map[current_main_view]->set_visible(false);
357
current_main_view = view_id;
358
}
359
main_view_toggle_map[current_main_view]->set_pressed_no_signal(true);
360
main_view_map[current_main_view]->set_visible(true);
361
362
#ifndef ANDROID_ENABLED
363
if (current_main_view == MAIN_VIEW_PROJECTS && search_box->is_inside_tree()) {
364
// Automatically grab focus when the user moves from the Templates tab
365
// back to the Projects tab.
366
search_box->grab_focus();
367
}
368
369
// The Templates tab's search field is focused on display in the asset
370
// library editor plugin code.
371
#endif
372
}
373
374
void ProjectManager::_show_about() {
375
about_dialog->popup_centered(Size2(780, 500) * EDSCALE);
376
}
377
378
void ProjectManager::_open_asset_library_confirmed() {
379
const int network_mode = EDITOR_GET("network/connection/network_mode");
380
if (network_mode == EditorSettings::NETWORK_OFFLINE) {
381
EditorSettings::get_singleton()->set_setting("network/connection/network_mode", EditorSettings::NETWORK_ONLINE);
382
EditorSettings::get_singleton()->notify_changes();
383
EditorSettings::get_singleton()->save();
384
}
385
386
asset_library->disable_community_support();
387
_select_main_view(MAIN_VIEW_ASSETLIB);
388
}
389
390
void ProjectManager::_show_error(const String &p_message, const Size2 &p_min_size) {
391
error_dialog->set_text(p_message);
392
error_dialog->popup_centered(p_min_size);
393
}
394
395
void ProjectManager::_dim_window() {
396
// This method must be called before calling `get_tree()->quit()`.
397
// Otherwise, its effect won't be visible
398
399
// Dim the project manager window while it's quitting to make it clearer that it's busy.
400
// No transition is applied, as the effect needs to be visible immediately
401
float c = 0.5f;
402
Color dim_color = Color(c, c, c);
403
set_modulate(dim_color);
404
}
405
406
// Quick settings.
407
408
void ProjectManager::_show_quick_settings() {
409
quick_settings_dialog->popup_centered(Size2(640, 200) * EDSCALE);
410
}
411
412
void ProjectManager::_restart_confirmed() {
413
List<String> args = OS::get_singleton()->get_cmdline_args();
414
Error err = OS::get_singleton()->create_instance(args);
415
ERR_FAIL_COND(err);
416
417
_dim_window();
418
get_tree()->quit();
419
}
420
421
// Project list.
422
423
void ProjectManager::_update_list_placeholder() {
424
if (project_list->get_project_count() > 0) {
425
empty_list_placeholder->hide();
426
return;
427
}
428
429
empty_list_open_assetlib->set_visible(asset_library);
430
431
const int network_mode = EDITOR_GET("network/connection/network_mode");
432
if (network_mode == EditorSettings::NETWORK_OFFLINE) {
433
empty_list_open_assetlib->set_text(TTRC("Go Online and Open Asset Library"));
434
empty_list_online_warning->set_visible(true);
435
} else {
436
empty_list_open_assetlib->set_text(TTRC("Open Asset Library"));
437
empty_list_online_warning->set_visible(false);
438
}
439
440
empty_list_placeholder->show();
441
}
442
443
void ProjectManager::_scan_projects() {
444
scan_dir->popup_file_dialog();
445
}
446
447
void ProjectManager::_run_project() {
448
const HashSet<String> &selected_list = project_list->get_selected_project_keys();
449
450
if (selected_list.size() < 1) {
451
return;
452
}
453
454
if (selected_list.size() > 1) {
455
multi_run_ask->set_text(vformat(TTR("Are you sure to run %d projects at once?"), selected_list.size()));
456
multi_run_ask->popup_centered();
457
} else {
458
_run_project_confirm();
459
}
460
}
461
462
void ProjectManager::_run_project_confirm() {
463
Vector<ProjectList::Item> selected_list = project_list->get_selected_projects();
464
465
for (int i = 0; i < selected_list.size(); ++i) {
466
const String &selected_main = selected_list[i].main_scene;
467
if (selected_main.is_empty()) {
468
_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."));
469
continue;
470
}
471
472
const String &path = selected_list[i].path;
473
474
// `.substr(6)` on `ProjectSettings::get_singleton()->get_imported_files_path()` strips away the leading "res://".
475
if (!DirAccess::exists(path.path_join(ProjectSettings::get_singleton()->get_imported_files_path().substr(6)))) {
476
_show_error(TTRC("Can't run project: Assets need to be imported first.\nPlease edit the project to trigger the initial import."));
477
continue;
478
}
479
480
print_line("Running project: " + path);
481
482
List<String> args;
483
484
for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_PROJECT)) {
485
args.push_back(a);
486
}
487
488
args.push_back("--path");
489
args.push_back(path);
490
491
Error err = OS::get_singleton()->create_instance(args);
492
ERR_FAIL_COND(err);
493
}
494
}
495
496
void ProjectManager::_open_selected_projects() {
497
// Show loading text to tell the user that the project manager is busy loading.
498
// This is especially important for the Web project manager.
499
loading_label->show();
500
501
const HashSet<String> &selected_list = project_list->get_selected_project_keys();
502
for (const String &path : selected_list) {
503
String conf = path.path_join("project.godot");
504
505
if (!FileAccess::exists(conf)) {
506
loading_label->hide();
507
_show_error(vformat(TTR("Can't open project at '%s'.\nProject file doesn't exist or is inaccessible."), path));
508
return;
509
}
510
511
print_line("Editing project: " + path);
512
513
List<String> args;
514
515
for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {
516
args.push_back(a);
517
}
518
519
args.push_back("--path");
520
args.push_back(path);
521
522
args.push_back("--editor");
523
524
if (open_in_recovery_mode) {
525
args.push_back("--recovery-mode");
526
}
527
528
if (open_in_verbose_mode) {
529
args.push_back("--verbose");
530
}
531
532
Error err = OS::get_singleton()->create_instance(args);
533
if (err != OK) {
534
loading_label->hide();
535
_show_error(vformat(TTR("Can't open project at '%s'.\nFailed to start the editor."), path));
536
ERR_PRINT(vformat("Failed to start an editor instance for the project at '%s', error code %d.", path, err));
537
return;
538
}
539
}
540
541
project_list->project_opening_initiated = true;
542
543
_dim_window();
544
get_tree()->quit();
545
}
546
547
void ProjectManager::_open_selected_projects_check_warnings() {
548
const HashSet<String> &selected_list = project_list->get_selected_project_keys();
549
if (selected_list.size() < 1) {
550
return;
551
}
552
553
const Size2i popup_min_size = Size2i(400.0 * EDSCALE, 0);
554
555
if (selected_list.size() > 1) {
556
multi_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()));
557
multi_open_ask->popup_centered(popup_min_size);
558
return;
559
}
560
561
ProjectList::Item project = project_list->get_selected_projects()[0];
562
if (project.missing) {
563
return;
564
}
565
566
// Update the project settings or don't open.
567
const int config_version = project.version;
568
PackedStringArray unsupported_features = project.unsupported_features;
569
570
ask_update_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); // Reset in case of previous center align.
571
ask_update_backup->set_pressed(false);
572
full_convert_button->hide();
573
migration_guide_button->hide();
574
ask_update_backup->hide();
575
576
ask_update_settings->get_ok_button()->set_text("OK");
577
578
// Check if the config_version property was empty or 0.
579
if (config_version == 0) {
580
ask_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));
581
ask_update_settings->popup_centered(popup_min_size);
582
return;
583
}
584
// Check if we need to convert project settings from an earlier engine version.
585
if (config_version < ProjectSettings::CONFIG_VERSION) {
586
if (config_version == GODOT4_CONFIG_VERSION - 1 && ProjectSettings::CONFIG_VERSION == GODOT4_CONFIG_VERSION) { // Conversion from Godot 3 to 4.
587
full_convert_button->show();
588
ask_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));
589
ask_update_settings->get_ok_button()->set_text(TTRC("Convert project.godot Only"));
590
} else {
591
ask_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));
592
ask_update_settings->get_ok_button()->set_text(TTRC("Convert project.godot"));
593
}
594
ask_update_backup->show();
595
migration_guide_button->show();
596
ask_update_settings->popup_centered(popup_min_size);
597
ask_update_settings->get_cancel_button()->grab_focus(); // To prevent accidents.
598
return;
599
}
600
// Check if the file was generated by a newer, incompatible engine version.
601
if (config_version > ProjectSettings::CONFIG_VERSION) {
602
_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);
603
return;
604
}
605
// Check if the project is using features not supported by this build of Godot.
606
if (!unsupported_features.is_empty()) {
607
String warning_message = "";
608
for (int i = 0; i < unsupported_features.size(); i++) {
609
const String &feature = unsupported_features[i];
610
if (feature == "Double Precision") {
611
ask_update_backup->show();
612
warning_message += TTR("Warning: This project uses double precision floats, but this version of\nGodot uses single precision floats. Opening this project may cause data loss.\n\n");
613
unsupported_features.remove_at(i);
614
i--;
615
} else if (feature == "C#") {
616
warning_message += TTR("Warning: This project uses C#, but this build of Godot does not have\nthe Mono module. If you proceed you will not be able to use any C# scripts.\n\n");
617
unsupported_features.remove_at(i);
618
i--;
619
} else if (ProjectList::project_feature_looks_like_version(feature)) {
620
ask_update_backup->show();
621
migration_guide_button->show();
622
version_convert_feature = feature;
623
warning_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));
624
unsupported_features.remove_at(i);
625
i--;
626
}
627
}
628
if (!unsupported_features.is_empty()) {
629
String unsupported_features_str = String(", ").join(unsupported_features);
630
warning_message += vformat(TTR("Warning: This project uses the following features not supported by this build of Godot:\n\n%s\n\n"), unsupported_features_str);
631
}
632
warning_message += TTR("Open anyway? Project will be modified.");
633
ask_update_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
634
ask_update_label->set_text(warning_message);
635
ask_update_settings->popup_centered(popup_min_size);
636
return;
637
}
638
639
// Open if the project is up-to-date.
640
_open_selected_projects();
641
}
642
643
void ProjectManager::_open_selected_projects_check_recovery_mode() {
644
Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();
645
646
if (selected_projects.is_empty()) {
647
return;
648
}
649
650
const ProjectList::Item &project = selected_projects[0];
651
if (project.missing) {
652
return;
653
}
654
655
open_in_verbose_mode = false;
656
open_in_recovery_mode = false;
657
// Check if the project failed to load during last startup.
658
if (project.recovery_mode) {
659
_open_recovery_mode_ask(false);
660
return;
661
}
662
663
_open_selected_projects_check_warnings();
664
}
665
666
void ProjectManager::_open_selected_projects_with_migration() {
667
if (ask_update_backup->is_pressed() && project_list->get_selected_projects().size() == 1) {
668
ask_update_settings->hide();
669
ask_update_backup->set_pressed(false);
670
671
_duplicate_project_with_action(POST_DUPLICATE_ACTION_OPEN);
672
return;
673
}
674
675
#ifndef DISABLE_DEPRECATED
676
if (project_list->get_selected_projects().size() == 1) {
677
// Only migrate if a single project is opened.
678
_minor_project_migrate();
679
}
680
#endif
681
_open_selected_projects();
682
}
683
684
void ProjectManager::_install_project(const String &p_zip_path, const String &p_title) {
685
project_dialog->set_mode(ProjectDialog::MODE_INSTALL);
686
project_dialog->set_zip_path(p_zip_path);
687
project_dialog->set_zip_title(p_title);
688
project_dialog->show_dialog();
689
}
690
691
void ProjectManager::_import_project() {
692
project_dialog->set_mode(ProjectDialog::MODE_IMPORT);
693
project_dialog->ask_for_path_and_show();
694
}
695
696
void ProjectManager::_new_project() {
697
project_dialog->set_mode(ProjectDialog::MODE_NEW);
698
project_dialog->show_dialog();
699
}
700
701
void ProjectManager::_rename_project() {
702
const Vector<ProjectList::Item> &selected_list = project_list->get_selected_projects();
703
704
if (selected_list.is_empty()) {
705
return;
706
}
707
708
for (const ProjectList::Item &E : selected_list) {
709
project_dialog->set_project_name(E.project_name);
710
project_dialog->set_project_path(E.path);
711
project_dialog->set_mode(ProjectDialog::MODE_RENAME);
712
project_dialog->show_dialog();
713
}
714
}
715
716
void ProjectManager::_duplicate_project() {
717
_duplicate_project_with_action(POST_DUPLICATE_ACTION_NONE);
718
}
719
720
void ProjectManager::_duplicate_project_with_action(PostDuplicateAction p_post_action) {
721
Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();
722
if (selected_projects.is_empty()) {
723
return;
724
}
725
726
post_duplicate_action = p_post_action;
727
728
const ProjectList::Item &project = selected_projects[0];
729
730
project_dialog->set_mode(ProjectDialog::MODE_DUPLICATE);
731
project_dialog->set_project_name(vformat("%s (%s)", project.project_name, p_post_action == POST_DUPLICATE_ACTION_NONE ? "Copy" : project.project_version));
732
project_dialog->set_original_project_path(project.path);
733
project_dialog->set_duplicate_can_edit(p_post_action == POST_DUPLICATE_ACTION_NONE);
734
project_dialog->show_dialog(false);
735
}
736
737
void ProjectManager::_erase_project() {
738
const HashSet<String> &selected_list = project_list->get_selected_project_keys();
739
740
if (selected_list.is_empty()) {
741
return;
742
}
743
744
String confirm_message;
745
if (selected_list.size() >= 2) {
746
confirm_message = vformat(TTR("Remove %d projects from the list?"), selected_list.size());
747
} else {
748
confirm_message = TTRC("Remove this project from the list?");
749
}
750
751
erase_ask_label->set_text(confirm_message);
752
//delete_project_contents->set_pressed(false);
753
erase_ask->popup_centered();
754
}
755
756
void ProjectManager::_erase_missing_projects() {
757
erase_missing_ask->set_text(TTRC("Remove all missing projects from the list?\nThe project folders' contents won't be modified."));
758
erase_missing_ask->popup_centered();
759
}
760
761
void ProjectManager::_erase_project_confirm() {
762
project_list->erase_selected_projects(false);
763
_update_project_buttons();
764
_update_list_placeholder();
765
}
766
767
void ProjectManager::_erase_missing_projects_confirm() {
768
project_list->erase_missing_projects();
769
_update_project_buttons();
770
_update_list_placeholder();
771
}
772
773
void ProjectManager::_update_project_buttons() {
774
Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();
775
bool empty_selection = selected_projects.is_empty();
776
777
bool is_missing_project_selected = false;
778
for (int i = 0; i < selected_projects.size(); ++i) {
779
if (selected_projects[i].missing) {
780
is_missing_project_selected = true;
781
break;
782
}
783
}
784
785
erase_btn->set_disabled(empty_selection);
786
open_btn->set_disabled(empty_selection || is_missing_project_selected);
787
open_options_btn->set_disabled(empty_selection || is_missing_project_selected);
788
rename_btn->set_disabled(empty_selection || is_missing_project_selected);
789
duplicate_btn->set_disabled(empty_selection || is_missing_project_selected);
790
manage_tags_btn->set_disabled(empty_selection || is_missing_project_selected || selected_projects.size() > 1);
791
run_btn->set_disabled(empty_selection || is_missing_project_selected);
792
793
erase_missing_btn->set_disabled(!project_list->is_any_project_missing());
794
}
795
796
void ProjectManager::_open_options_popup() {
797
Rect2 rect = open_btn_container->get_screen_rect();
798
rect.position.y += rect.size.height;
799
open_options_popup->set_size(Size2(rect.size.width, 0));
800
open_options_popup->set_position(rect.position);
801
802
open_options_popup->popup();
803
}
804
805
void ProjectManager::_open_recovery_mode_ask(bool manual) {
806
String recovery_mode_details;
807
808
// Only show the initial crash preamble if this popup wasn't manually triggered.
809
if (!manual) {
810
recovery_mode_details +=
811
TTR("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.") +
812
String::utf8("\n\n");
813
}
814
815
recovery_mode_details +=
816
TTR("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:") +
817
String::utf8("\n\n• ") + TTR("Tool scripts") +
818
String::utf8("\n• ") + TTR("Editor plugins") +
819
String::utf8("\n• ") + TTR("GDExtension addons") +
820
String::utf8("\n• ") + TTR("Automatic scene restoring") +
821
String::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.") +
822
String::utf8("\n\n") + TTR("Edit the project in Recovery Mode?");
823
824
open_recovery_mode_ask->set_text(recovery_mode_details);
825
open_recovery_mode_ask->popup_centered(Size2(550, 70) * EDSCALE);
826
}
827
828
void ProjectManager::_on_projects_updated() {
829
Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();
830
int index = 0;
831
for (int i = 0; i < selected_projects.size(); ++i) {
832
index = project_list->refresh_project(selected_projects[i].path);
833
}
834
if (index != -1) {
835
project_list->ensure_project_visible(index);
836
}
837
838
project_list->update_dock_menu();
839
}
840
841
void ProjectManager::_on_open_options_selected(int p_option) {
842
switch (p_option) {
843
case 0: // Edit in verbose mode.
844
open_in_verbose_mode = true;
845
_open_selected_projects_check_warnings();
846
break;
847
case 1: // Edit in recovery mode.
848
_open_recovery_mode_ask(true);
849
break;
850
}
851
}
852
853
void ProjectManager::_on_recovery_mode_popup_open_normal() {
854
open_recovery_mode_ask->hide();
855
open_in_recovery_mode = false;
856
_open_selected_projects_check_warnings();
857
}
858
859
void ProjectManager::_on_recovery_mode_popup_open_recovery() {
860
open_in_recovery_mode = true;
861
_open_selected_projects_check_warnings();
862
}
863
864
void ProjectManager::_on_project_created(const String &dir, bool edit) {
865
project_list->add_project(dir, false);
866
project_list->save_config();
867
search_box->clear();
868
869
int i = project_list->refresh_project(dir);
870
project_list->select_project(i);
871
project_list->ensure_project_visible(i);
872
_update_project_buttons();
873
_update_list_placeholder();
874
875
if (edit) {
876
_open_selected_projects_check_warnings();
877
}
878
879
project_list->update_dock_menu();
880
}
881
882
void ProjectManager::_on_project_duplicated(const String &p_original_path, const String &p_duplicate_path, bool p_edit) {
883
if (post_duplicate_action == POST_DUPLICATE_ACTION_NONE) {
884
_on_project_created(p_duplicate_path, p_edit);
885
} else {
886
project_list->add_project(p_duplicate_path, false);
887
project_list->save_config();
888
889
if (post_duplicate_action == POST_DUPLICATE_ACTION_OPEN) {
890
_open_selected_projects_with_migration();
891
} else if (post_duplicate_action == POST_DUPLICATE_ACTION_FULL_CONVERSION) {
892
_full_convert_button_pressed();
893
}
894
895
project_list->update_dock_menu();
896
}
897
898
post_duplicate_action = POST_DUPLICATE_ACTION_NONE;
899
}
900
901
void ProjectManager::_on_order_option_changed(int p_idx) {
902
if (is_inside_tree()) {
903
project_list->set_order_option(p_idx);
904
}
905
}
906
907
void ProjectManager::_on_search_term_changed(const String &p_term) {
908
project_list->set_search_term(p_term);
909
project_list->sort_projects();
910
911
// Select the first visible project in the list.
912
// This makes it possible to open a project without ever touching the mouse,
913
// as the search field is automatically focused on startup.
914
project_list->select_first_visible_project();
915
_update_project_buttons();
916
}
917
918
void ProjectManager::_on_search_term_submitted(const String &p_text) {
919
if (current_main_view != MAIN_VIEW_PROJECTS) {
920
return;
921
}
922
923
_open_selected_projects_check_recovery_mode();
924
}
925
926
LineEdit *ProjectManager::get_search_box() {
927
return search_box;
928
}
929
930
// Project tag management.
931
932
void ProjectManager::_manage_project_tags() {
933
for (int i = 0; i < project_tags->get_child_count(); i++) {
934
project_tags->get_child(i)->queue_free();
935
}
936
937
const ProjectList::Item item = project_list->get_selected_projects()[0];
938
current_project_tags = item.tags;
939
for (const String &tag : current_project_tags) {
940
ProjectTag *tag_control = memnew(ProjectTag(tag, true));
941
project_tags->add_child(tag_control);
942
tag_control->connect_button_to(callable_mp(this, &ProjectManager::_delete_project_tag).bind(tag));
943
}
944
945
tag_edit_error->hide();
946
tag_manage_dialog->popup_centered(Vector2i(500, 0) * EDSCALE);
947
}
948
949
void ProjectManager::_add_project_tag(const String &p_tag) {
950
if (current_project_tags.has(p_tag)) {
951
return;
952
}
953
current_project_tags.append(p_tag);
954
955
ProjectTag *tag_control = memnew(ProjectTag(p_tag, true));
956
project_tags->add_child(tag_control);
957
tag_control->connect_button_to(callable_mp(this, &ProjectManager::_delete_project_tag).bind(p_tag));
958
}
959
960
void ProjectManager::_delete_project_tag(const String &p_tag) {
961
current_project_tags.erase(p_tag);
962
for (int i = 0; i < project_tags->get_child_count(); i++) {
963
ProjectTag *tag_control = Object::cast_to<ProjectTag>(project_tags->get_child(i));
964
if (tag_control && tag_control->get_tag() == p_tag) {
965
memdelete(tag_control);
966
break;
967
}
968
}
969
}
970
971
void ProjectManager::_apply_project_tags() {
972
PackedStringArray tags;
973
for (int i = 0; i < project_tags->get_child_count(); i++) {
974
ProjectTag *tag_control = Object::cast_to<ProjectTag>(project_tags->get_child(i));
975
if (tag_control) {
976
tags.append(tag_control->get_tag());
977
}
978
}
979
980
const String project_godot = project_list->get_selected_projects()[0].path.path_join("project.godot");
981
ProjectSettings *cfg = memnew(ProjectSettings(project_godot));
982
if (!cfg->is_project_loaded()) {
983
memdelete(cfg);
984
tag_edit_error->set_text(vformat(TTR("Couldn't load project at '%s'. It may be missing or corrupted."), project_godot));
985
tag_edit_error->show();
986
callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred(); // Make sure the dialog does not disappear.
987
return;
988
} else {
989
tags.sort();
990
cfg->set("application/config/tags", tags);
991
Error err = cfg->save_custom(project_godot);
992
memdelete(cfg);
993
994
if (err != OK) {
995
tag_edit_error->set_text(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err));
996
tag_edit_error->show();
997
callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred();
998
return;
999
}
1000
}
1001
1002
_on_projects_updated();
1003
}
1004
1005
void ProjectManager::_set_new_tag_name(const String p_name) {
1006
create_tag_dialog->get_ok_button()->set_disabled(true);
1007
if (p_name.is_empty()) {
1008
tag_error->set_text(TTRC("Tag name can't be empty."));
1009
return;
1010
}
1011
1012
if (p_name.contains_char(' ')) {
1013
tag_error->set_text(TTRC("Tag name can't contain spaces."));
1014
return;
1015
}
1016
1017
if (p_name[0] == '_' || p_name[p_name.length() - 1] == '_') {
1018
tag_error->set_text(TTRC("Tag name can't begin or end with underscore."));
1019
return;
1020
}
1021
1022
bool was_underscore = false;
1023
for (const char32_t &c : p_name.span()) {
1024
if (c == '_') {
1025
if (was_underscore) {
1026
tag_error->set_text(TTRC("Tag name can't contain consecutive underscores."));
1027
return;
1028
}
1029
was_underscore = true;
1030
} else {
1031
was_underscore = false;
1032
}
1033
}
1034
1035
for (const String &c : forbidden_tag_characters) {
1036
if (p_name.contains(c)) {
1037
tag_error->set_text(vformat(TTRC("These characters are not allowed in tags: %s."), String(" ").join(forbidden_tag_characters)));
1038
return;
1039
}
1040
}
1041
1042
if (p_name.to_lower() != p_name) {
1043
tag_error->set_text(TTRC("Tag name must be lowercase."));
1044
return;
1045
}
1046
1047
tag_error->set_text("");
1048
create_tag_dialog->get_ok_button()->set_disabled(false);
1049
}
1050
1051
void ProjectManager::_create_new_tag() {
1052
if (!tag_error->get_text().is_empty()) {
1053
return;
1054
}
1055
create_tag_dialog->hide(); // When using text_submitted, need to hide manually.
1056
add_new_tag(new_tag_name->get_text());
1057
_add_project_tag(new_tag_name->get_text());
1058
}
1059
1060
void ProjectManager::add_new_tag(const String &p_tag) {
1061
if (!tag_set.has(p_tag)) {
1062
tag_set.insert(p_tag);
1063
ProjectTag *tag_control = memnew(ProjectTag(p_tag));
1064
all_tags->add_child(tag_control);
1065
all_tags->move_child(tag_control, -2);
1066
tag_control->connect_button_to(callable_mp(this, &ProjectManager::_add_project_tag).bind(p_tag));
1067
}
1068
}
1069
1070
// Project converter/migration tool.
1071
1072
#ifndef DISABLE_DEPRECATED
1073
void ProjectManager::_minor_project_migrate() {
1074
const ProjectList::Item migrated_project = project_list->get_selected_projects()[0];
1075
1076
if (version_convert_feature.begins_with("4.3")) {
1077
// Migrate layout after scale changes.
1078
const float edscale = EDSCALE;
1079
if (edscale != 1.0) {
1080
Ref<ConfigFile> layout_file;
1081
layout_file.instantiate();
1082
1083
const String layout_path = migrated_project.path.path_join(".godot/editor/editor_layout.cfg");
1084
Error err = layout_file->load(layout_path);
1085
if (err == OK) {
1086
for (int i = 0; i < 4; i++) {
1087
const String key = "dock_hsplit_" + itos(i + 1);
1088
int old_value = layout_file->get_value("docks", key, 0);
1089
if (old_value != 0) {
1090
layout_file->set_value("docks", key, old_value / edscale);
1091
}
1092
}
1093
layout_file->save(layout_path);
1094
}
1095
}
1096
}
1097
}
1098
#endif
1099
1100
void ProjectManager::_full_convert_button_pressed() {
1101
ask_update_settings->hide();
1102
1103
if (ask_update_backup->is_pressed()) {
1104
ask_update_backup->set_pressed(false);
1105
1106
_duplicate_project_with_action(POST_DUPLICATE_ACTION_FULL_CONVERSION);
1107
return;
1108
}
1109
1110
ask_full_convert_dialog->popup_centered(Size2i(600.0 * EDSCALE, 0));
1111
ask_full_convert_dialog->get_cancel_button()->grab_focus();
1112
}
1113
1114
void ProjectManager::_migration_guide_button_pressed() {
1115
const String url = vformat("%s/tutorials/migrating/index.html", GODOT_VERSION_DOCS_URL);
1116
OS::get_singleton()->shell_open(url);
1117
}
1118
1119
void ProjectManager::_perform_full_project_conversion() {
1120
Vector<ProjectList::Item> selected_list = project_list->get_selected_projects();
1121
if (selected_list.is_empty()) {
1122
return;
1123
}
1124
1125
const String &path = selected_list[0].path;
1126
1127
print_line("Converting project: " + path);
1128
List<String> args;
1129
args.push_back("--path");
1130
args.push_back(path);
1131
args.push_back("--convert-3to4");
1132
args.push_back("--rendering-driver");
1133
args.push_back(Main::get_rendering_driver_name());
1134
1135
Error err = OS::get_singleton()->create_instance(args);
1136
ERR_FAIL_COND(err);
1137
1138
project_list->set_project_version(path, GODOT4_CONFIG_VERSION);
1139
}
1140
1141
// Input and I/O.
1142
1143
void ProjectManager::shortcut_input(const Ref<InputEvent> &p_ev) {
1144
ERR_FAIL_COND(p_ev.is_null());
1145
1146
Ref<InputEventKey> k = p_ev;
1147
1148
if (k.is_valid()) {
1149
if (!k->is_pressed()) {
1150
return;
1151
}
1152
1153
// Pressing Command + Q quits the Project Manager
1154
// This is handled by the platform implementation on macOS,
1155
// so only define the shortcut on other platforms
1156
#ifndef MACOS_ENABLED
1157
if (k->get_keycode_with_modifiers() == (KeyModifierMask::META | Key::Q)) {
1158
_dim_window();
1159
get_tree()->quit();
1160
}
1161
#endif
1162
1163
if (current_main_view != MAIN_VIEW_PROJECTS) {
1164
return;
1165
}
1166
1167
bool keycode_handled = true;
1168
1169
switch (k->get_keycode()) {
1170
case Key::ENTER: {
1171
_open_selected_projects_check_recovery_mode();
1172
} break;
1173
case Key::HOME: {
1174
if (project_list->get_project_count() > 0) {
1175
project_list->select_project(0);
1176
_update_project_buttons();
1177
}
1178
1179
} break;
1180
case Key::END: {
1181
if (project_list->get_project_count() > 0) {
1182
project_list->select_project(project_list->get_project_count() - 1);
1183
_update_project_buttons();
1184
}
1185
1186
} break;
1187
case Key::UP: {
1188
if (k->is_shift_pressed()) {
1189
break;
1190
}
1191
1192
int index = project_list->get_single_selected_index();
1193
if (index > 0) {
1194
project_list->select_project(index - 1);
1195
project_list->ensure_project_visible(index - 1);
1196
_update_project_buttons();
1197
}
1198
1199
break;
1200
}
1201
case Key::DOWN: {
1202
if (k->is_shift_pressed()) {
1203
break;
1204
}
1205
1206
int index = project_list->get_single_selected_index();
1207
if (index + 1 < project_list->get_project_count()) {
1208
project_list->select_project(index + 1);
1209
project_list->ensure_project_visible(index + 1);
1210
_update_project_buttons();
1211
}
1212
1213
} break;
1214
case Key::F: {
1215
if (k->is_command_or_control_pressed()) {
1216
search_box->grab_focus();
1217
} else {
1218
keycode_handled = false;
1219
}
1220
} break;
1221
default: {
1222
keycode_handled = false;
1223
} break;
1224
}
1225
1226
if (keycode_handled) {
1227
accept_event();
1228
}
1229
}
1230
}
1231
1232
void ProjectManager::_files_dropped(PackedStringArray p_files) {
1233
// TODO: Support installing multiple ZIPs at the same time?
1234
if (p_files.size() == 1 && p_files[0].ends_with(".zip")) {
1235
const String &file = p_files[0];
1236
_install_project(file, file.get_file().get_basename().capitalize());
1237
return;
1238
}
1239
1240
HashSet<String> folders_set;
1241
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1242
for (int i = 0; i < p_files.size(); i++) {
1243
const String &file = p_files[i];
1244
folders_set.insert(da->dir_exists(file) ? file : file.get_base_dir());
1245
}
1246
ERR_FAIL_COND(folders_set.is_empty()); // This can't really happen, we consume every dropped file path above.
1247
1248
PackedStringArray folders;
1249
for (const String &E : folders_set) {
1250
folders.push_back(E);
1251
}
1252
project_list->find_projects_multiple(folders);
1253
}
1254
1255
void ProjectManager::_titlebar_resized() {
1256
DisplayServer::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);
1257
const Vector3i &margin = DisplayServer::get_singleton()->window_get_safe_title_margins(DisplayServer::MAIN_WINDOW_ID);
1258
if (left_menu_spacer) {
1259
int w = (root_container->is_layout_rtl()) ? margin.y : margin.x;
1260
left_menu_spacer->set_custom_minimum_size(Size2(w, 0));
1261
}
1262
if (right_menu_spacer) {
1263
int w = (root_container->is_layout_rtl()) ? margin.x : margin.y;
1264
right_menu_spacer->set_custom_minimum_size(Size2(w, 0));
1265
}
1266
if (title_bar) {
1267
title_bar->set_custom_minimum_size(Size2(0, margin.z - title_bar->get_global_position().y));
1268
}
1269
}
1270
1271
// Object methods.
1272
1273
ProjectManager::ProjectManager() {
1274
singleton = this;
1275
1276
// Turn off some servers we aren't going to be using in the Project Manager.
1277
NavigationServer3D::get_singleton()->set_active(false);
1278
PhysicsServer3D::get_singleton()->set_active(false);
1279
PhysicsServer2D::get_singleton()->set_active(false);
1280
1281
// Initialize settings.
1282
{
1283
if (!EditorSettings::get_singleton()) {
1284
EditorSettings::create();
1285
}
1286
EditorSettings::get_singleton()->set_optimize_save(false); // Just write settings as they come.
1287
1288
{
1289
bool agile_input_event_flushing = EDITOR_GET("input/buffering/agile_event_flushing");
1290
bool use_accumulated_input = EDITOR_GET("input/buffering/use_accumulated_input");
1291
1292
Input::get_singleton()->set_agile_input_event_flushing(agile_input_event_flushing);
1293
Input::get_singleton()->set_use_accumulated_input(use_accumulated_input);
1294
}
1295
1296
int display_scale = EDITOR_GET("interface/editor/display_scale");
1297
1298
switch (display_scale) {
1299
case 0:
1300
// Try applying a suitable display scale automatically.
1301
EditorScale::set_scale(EditorSettings::get_auto_display_scale());
1302
break;
1303
case 1:
1304
EditorScale::set_scale(0.75);
1305
break;
1306
case 2:
1307
EditorScale::set_scale(1.0);
1308
break;
1309
case 3:
1310
EditorScale::set_scale(1.25);
1311
break;
1312
case 4:
1313
EditorScale::set_scale(1.5);
1314
break;
1315
case 5:
1316
EditorScale::set_scale(1.75);
1317
break;
1318
case 6:
1319
EditorScale::set_scale(2.0);
1320
break;
1321
default:
1322
EditorScale::set_scale(EDITOR_GET("interface/editor/custom_display_scale"));
1323
break;
1324
}
1325
EditorFileDialog::get_icon_func = &ProjectManager::_file_dialog_get_icon;
1326
EditorFileDialog::get_thumbnail_func = &ProjectManager::_file_dialog_get_thumbnail;
1327
1328
EditorFileDialog::set_default_show_hidden_files(EDITOR_GET("filesystem/file_dialog/show_hidden_files"));
1329
EditorFileDialog::set_default_display_mode((EditorFileDialog::DisplayMode)EDITOR_GET("filesystem/file_dialog/display_mode").operator int());
1330
1331
int swap_cancel_ok = EDITOR_GET("interface/editor/accept_dialog_cancel_ok_buttons");
1332
if (swap_cancel_ok != 0) { // 0 is auto, set in register_scene based on DisplayServer.
1333
// Swap on means OK first.
1334
AcceptDialog::set_swap_cancel_ok(swap_cancel_ok == 2);
1335
}
1336
1337
OS::get_singleton()->set_low_processor_usage_mode(true);
1338
}
1339
1340
SceneTree::get_singleton()->get_root()->connect("files_dropped", callable_mp(this, &ProjectManager::_files_dropped));
1341
1342
// Initialize UI.
1343
{
1344
int pm_root_dir = EDITOR_GET("interface/editor/ui_layout_direction");
1345
Control::set_root_layout_direction(pm_root_dir);
1346
Window::set_root_layout_direction(pm_root_dir);
1347
1348
EditorThemeManager::initialize();
1349
theme = EditorThemeManager::generate_theme();
1350
DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor)));
1351
1352
set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
1353
1354
_build_icon_type_cache(theme);
1355
}
1356
1357
// Project manager layout.
1358
1359
background_panel = memnew(Panel);
1360
add_child(background_panel);
1361
background_panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
1362
1363
root_container = memnew(MarginContainer);
1364
add_child(root_container);
1365
root_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
1366
1367
main_vbox = memnew(VBoxContainer);
1368
root_container->add_child(main_vbox);
1369
1370
// Title bar.
1371
bool can_expand = bool(EDITOR_GET("interface/editor/expand_to_title")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EXTEND_TO_TITLE);
1372
1373
{
1374
title_bar = memnew(EditorTitleBar);
1375
main_vbox->add_child(title_bar);
1376
1377
if (can_expand) {
1378
// Add spacer to avoid other controls under window minimize/maximize/close buttons (left side).
1379
left_menu_spacer = memnew(Control);
1380
left_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
1381
title_bar->add_child(left_menu_spacer);
1382
}
1383
1384
HBoxContainer *left_hbox = memnew(HBoxContainer);
1385
left_hbox->set_alignment(BoxContainer::ALIGNMENT_BEGIN);
1386
left_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1387
left_hbox->set_stretch_ratio(1.0);
1388
title_bar->add_child(left_hbox);
1389
1390
title_bar_logo = memnew(Button);
1391
title_bar_logo->set_flat(true);
1392
title_bar_logo->set_tooltip_text(TTR("About Godot"));
1393
left_hbox->add_child(title_bar_logo);
1394
title_bar_logo->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_show_about));
1395
1396
if (can_expand) {
1397
// Spacer to center main toggles.
1398
left_spacer = memnew(Control);
1399
left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
1400
title_bar->add_child(left_spacer);
1401
}
1402
1403
main_view_toggles = memnew(HBoxContainer);
1404
main_view_toggles->set_alignment(BoxContainer::ALIGNMENT_CENTER);
1405
main_view_toggles->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1406
main_view_toggles->set_stretch_ratio(2.0);
1407
title_bar->add_child(main_view_toggles);
1408
title_bar->set_center_control(main_view_toggles);
1409
1410
if (can_expand) {
1411
// Spacer to center main toggles.
1412
right_spacer = memnew(Control);
1413
right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
1414
title_bar->add_child(right_spacer);
1415
}
1416
1417
main_view_toggles_group.instantiate();
1418
1419
HBoxContainer *right_hbox = memnew(HBoxContainer);
1420
right_hbox->set_alignment(BoxContainer::ALIGNMENT_END);
1421
right_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1422
right_hbox->set_stretch_ratio(1.0);
1423
title_bar->add_child(right_hbox);
1424
1425
quick_settings_button = memnew(Button);
1426
quick_settings_button->set_flat(true);
1427
quick_settings_button->set_text(TTRC("Settings"));
1428
right_hbox->add_child(quick_settings_button);
1429
quick_settings_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_show_quick_settings));
1430
1431
if (can_expand) {
1432
// Add spacer to avoid other controls under the window minimize/maximize/close buttons (right side).
1433
right_menu_spacer = memnew(Control);
1434
right_menu_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
1435
title_bar->add_child(right_menu_spacer);
1436
}
1437
}
1438
1439
main_view_container = memnew(PanelContainer);
1440
main_view_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1441
main_vbox->add_child(main_view_container);
1442
1443
// Project list view.
1444
{
1445
local_projects_vb = memnew(VBoxContainer);
1446
local_projects_vb->set_name("LocalProjectsTab");
1447
_add_main_view(MAIN_VIEW_PROJECTS, TTRC("Projects"), Ref<Texture2D>(), local_projects_vb);
1448
1449
// Project list's top bar.
1450
{
1451
HBoxContainer *hb = memnew(HBoxContainer);
1452
hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1453
local_projects_vb->add_child(hb);
1454
1455
create_btn = memnew(Button);
1456
create_btn->set_text(TTRC("Create"));
1457
create_btn->set_shortcut(ED_SHORTCUT("project_manager/new_project", TTRC("New Project"), KeyModifierMask::CMD_OR_CTRL | Key::N));
1458
create_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_new_project));
1459
hb->add_child(create_btn);
1460
1461
import_btn = memnew(Button);
1462
import_btn->set_text(TTRC("Import"));
1463
import_btn->set_shortcut(ED_SHORTCUT("project_manager/import_project", TTRC("Import Project"), KeyModifierMask::CMD_OR_CTRL | Key::I));
1464
import_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_import_project));
1465
hb->add_child(import_btn);
1466
1467
scan_btn = memnew(Button);
1468
scan_btn->set_text(TTRC("Scan"));
1469
scan_btn->set_shortcut(ED_SHORTCUT("project_manager/scan_projects", TTRC("Scan Projects"), KeyModifierMask::CMD_OR_CTRL | Key::S));
1470
scan_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_scan_projects));
1471
hb->add_child(scan_btn);
1472
1473
loading_label = memnew(Label(TTRC("Loading, please wait...")));
1474
loading_label->set_accessibility_live(DisplayServer::AccessibilityLiveMode::LIVE_ASSERTIVE);
1475
loading_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1476
loading_label->hide();
1477
hb->add_child(loading_label);
1478
1479
search_box = memnew(LineEdit);
1480
search_box->set_placeholder(TTRC("Filter Projects"));
1481
search_box->set_accessibility_name(TTRC("Filter Projects"));
1482
search_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."));
1483
search_box->set_clear_button_enabled(true);
1484
search_box->connect(SceneStringName(text_changed), callable_mp(this, &ProjectManager::_on_search_term_changed));
1485
search_box->connect(SceneStringName(text_submitted), callable_mp(this, &ProjectManager::_on_search_term_submitted));
1486
search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1487
hb->add_child(search_box);
1488
1489
sort_label = memnew(Label);
1490
sort_label->set_text(TTRC("Sort:"));
1491
hb->add_child(sort_label);
1492
1493
filter_option = memnew(OptionButton);
1494
filter_option->set_clip_text(true);
1495
filter_option->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1496
filter_option->set_stretch_ratio(0.3);
1497
filter_option->set_accessibility_name(TTRC("Sort:"));
1498
filter_option->connect(SceneStringName(item_selected), callable_mp(this, &ProjectManager::_on_order_option_changed));
1499
hb->add_child(filter_option);
1500
1501
filter_option->add_item(TTRC("Last Edited"));
1502
filter_option->add_item(TTRC("Name"));
1503
filter_option->add_item(TTRC("Path"));
1504
filter_option->add_item(TTRC("Tags"));
1505
}
1506
1507
// Project list and its sidebar.
1508
{
1509
HBoxContainer *project_list_hbox = memnew(HBoxContainer);
1510
local_projects_vb->add_child(project_list_hbox);
1511
project_list_hbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1512
1513
project_list_panel = memnew(PanelContainer);
1514
project_list_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1515
project_list_hbox->add_child(project_list_panel);
1516
1517
project_list = memnew(ProjectList);
1518
project_list->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
1519
project_list_panel->add_child(project_list);
1520
project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
1521
project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_list_placeholder));
1522
project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
1523
project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_check_recovery_mode));
1524
1525
// Empty project list placeholder.
1526
{
1527
empty_list_placeholder = memnew(VBoxContainer);
1528
empty_list_placeholder->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
1529
empty_list_placeholder->add_theme_constant_override("separation", 16 * EDSCALE);
1530
empty_list_placeholder->hide();
1531
project_list_panel->add_child(empty_list_placeholder);
1532
1533
empty_list_message = memnew(RichTextLabel);
1534
empty_list_message->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
1535
empty_list_message->set_use_bbcode(true);
1536
empty_list_message->set_fit_content(true);
1537
empty_list_message->set_h_size_flags(SIZE_EXPAND_FILL);
1538
empty_list_message->add_theme_style_override(CoreStringName(normal), memnew(StyleBoxEmpty));
1539
1540
empty_list_placeholder->add_child(empty_list_message);
1541
1542
FlowContainer *empty_list_actions = memnew(FlowContainer);
1543
empty_list_actions->set_alignment(FlowContainer::ALIGNMENT_CENTER);
1544
empty_list_placeholder->add_child(empty_list_actions);
1545
1546
empty_list_create_project = memnew(Button);
1547
empty_list_create_project->set_text(TTRC("Create New Project"));
1548
empty_list_create_project->set_theme_type_variation("PanelBackgroundButton");
1549
empty_list_actions->add_child(empty_list_create_project);
1550
empty_list_create_project->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_new_project));
1551
1552
empty_list_import_project = memnew(Button);
1553
empty_list_import_project->set_text(TTRC("Import Existing Project"));
1554
empty_list_import_project->set_theme_type_variation("PanelBackgroundButton");
1555
empty_list_actions->add_child(empty_list_import_project);
1556
empty_list_import_project->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_import_project));
1557
1558
empty_list_open_assetlib = memnew(Button);
1559
empty_list_open_assetlib->set_text(TTRC("Open Asset Library"));
1560
empty_list_open_assetlib->set_theme_type_variation("PanelBackgroundButton");
1561
empty_list_actions->add_child(empty_list_open_assetlib);
1562
empty_list_open_assetlib->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_asset_library_confirmed));
1563
1564
empty_list_online_warning = memnew(Label);
1565
empty_list_online_warning->set_focus_mode(FOCUS_ACCESSIBILITY);
1566
empty_list_online_warning->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
1567
empty_list_online_warning->set_custom_minimum_size(Size2(220, 0) * EDSCALE);
1568
empty_list_online_warning->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
1569
empty_list_online_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1570
empty_list_online_warning->set_text(TTRC("Note: The Asset Library requires an online connection and involves sending data over the internet."));
1571
empty_list_placeholder->add_child(empty_list_online_warning);
1572
}
1573
1574
// The side bar with the edit, run, rename, etc. buttons.
1575
VBoxContainer *project_list_sidebar = memnew(VBoxContainer);
1576
project_list_sidebar->set_custom_minimum_size(Size2(120, 120));
1577
project_list_hbox->add_child(project_list_sidebar);
1578
1579
project_list_sidebar->add_child(memnew(HSeparator));
1580
1581
open_btn_container = memnew(HBoxContainer);
1582
open_btn_container->set_anchors_preset(Control::PRESET_FULL_RECT);
1583
project_list_sidebar->add_child(open_btn_container);
1584
1585
open_btn = memnew(Button);
1586
open_btn->set_text(TTRC("Edit"));
1587
open_btn->set_shortcut(ED_SHORTCUT("project_manager/edit_project", TTRC("Edit Project"), KeyModifierMask::CMD_OR_CTRL | Key::E));
1588
open_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_check_recovery_mode));
1589
open_btn->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1590
open_btn_container->add_child(open_btn);
1591
1592
open_btn_container->add_child(memnew(VSeparator));
1593
1594
open_options_btn = memnew(Button);
1595
open_options_btn->set_accessibility_name(TTRC("Options"));
1596
open_options_btn->set_icon_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
1597
open_options_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_options_popup));
1598
open_btn_container->add_child(open_options_btn);
1599
1600
open_options_popup = memnew(PopupMenu);
1601
open_options_popup->add_item(TTRC("Edit in verbose mode"));
1602
open_options_popup->add_item(TTRC("Edit in recovery mode"));
1603
open_options_popup->connect(SceneStringName(id_pressed), callable_mp(this, &ProjectManager::_on_open_options_selected));
1604
open_options_btn->add_child(open_options_popup);
1605
1606
open_btn_container->set_custom_minimum_size(Size2(120, open_btn->get_combined_minimum_size().y));
1607
1608
run_btn = memnew(Button);
1609
run_btn->set_text(TTRC("Run"));
1610
run_btn->set_shortcut(ED_SHORTCUT("project_manager/run_project", TTRC("Run Project"), KeyModifierMask::CMD_OR_CTRL | Key::R));
1611
run_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_run_project));
1612
project_list_sidebar->add_child(run_btn);
1613
1614
rename_btn = memnew(Button);
1615
rename_btn->set_text(TTRC("Rename"));
1616
// The F2 shortcut isn't overridden with Enter on macOS as Enter is already used to edit a project.
1617
rename_btn->set_shortcut(ED_SHORTCUT("project_manager/rename_project", TTRC("Rename Project"), Key::F2));
1618
rename_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_rename_project));
1619
project_list_sidebar->add_child(rename_btn);
1620
1621
duplicate_btn = memnew(Button);
1622
duplicate_btn->set_text(TTRC("Duplicate"));
1623
duplicate_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_duplicate_project));
1624
project_list_sidebar->add_child(duplicate_btn);
1625
1626
manage_tags_btn = memnew(Button);
1627
manage_tags_btn->set_text(TTRC("Manage Tags"));
1628
manage_tags_btn->set_shortcut(ED_SHORTCUT("project_manager/project_tags", TTRC("Manage Tags"), KeyModifierMask::CMD_OR_CTRL | Key::T));
1629
project_list_sidebar->add_child(manage_tags_btn);
1630
1631
erase_btn = memnew(Button);
1632
erase_btn->set_text(TTRC("Remove"));
1633
erase_btn->set_shortcut(ED_SHORTCUT("project_manager/remove_project", TTRC("Remove Project"), Key::KEY_DELETE));
1634
erase_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_project));
1635
project_list_sidebar->add_child(erase_btn);
1636
1637
Control *filler = memnew(Control);
1638
filler->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1639
project_list_sidebar->add_child(filler);
1640
1641
erase_missing_btn = memnew(Button);
1642
erase_missing_btn->set_text(TTRC("Remove Missing"));
1643
erase_missing_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_missing_projects));
1644
project_list_sidebar->add_child(erase_missing_btn);
1645
}
1646
}
1647
1648
// Asset library view.
1649
if (AssetLibraryEditorPlugin::is_available()) {
1650
asset_library = memnew(EditorAssetLibrary(true));
1651
asset_library->set_name("AssetLibraryTab");
1652
_add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Library"), Ref<Texture2D>(), asset_library);
1653
asset_library->connect("install_asset", callable_mp(this, &ProjectManager::_install_project));
1654
} else {
1655
VBoxContainer *asset_library_filler = memnew(VBoxContainer);
1656
asset_library_filler->set_name("AssetLibraryTab");
1657
Button *asset_library_toggle = _add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Library"), Ref<Texture2D>(), asset_library_filler);
1658
asset_library_toggle->set_disabled(true);
1659
asset_library_toggle->set_tooltip_text(TTRC("Asset Library not available (due to using Web editor, or because SSL support disabled)."));
1660
}
1661
1662
// Footer bar.
1663
{
1664
HBoxContainer *footer_bar = memnew(HBoxContainer);
1665
footer_bar->set_alignment(BoxContainer::ALIGNMENT_END);
1666
footer_bar->add_theme_constant_override("separation", 20 * EDSCALE);
1667
main_vbox->add_child(footer_bar);
1668
1669
#ifdef ENGINE_UPDATE_CHECK_ENABLED
1670
EngineUpdateLabel *update_label = memnew(EngineUpdateLabel);
1671
footer_bar->add_child(update_label);
1672
update_label->connect("offline_clicked", callable_mp(this, &ProjectManager::_show_quick_settings));
1673
#endif
1674
1675
EditorVersionButton *version_btn = memnew(EditorVersionButton(EditorVersionButton::FORMAT_WITH_BUILD));
1676
// Fade the version label to be less prominent, but still readable.
1677
version_btn->set_self_modulate(Color(1, 1, 1, 0.6));
1678
footer_bar->add_child(version_btn);
1679
}
1680
1681
// Dialogs.
1682
{
1683
quick_settings_dialog = memnew(QuickSettingsDialog);
1684
add_child(quick_settings_dialog);
1685
quick_settings_dialog->connect("restart_required", callable_mp(this, &ProjectManager::_restart_confirmed));
1686
1687
scan_dir = memnew(EditorFileDialog);
1688
scan_dir->set_previews_enabled(false);
1689
scan_dir->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
1690
scan_dir->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
1691
scan_dir->set_title(TTRC("Select a Folder to Scan")); // Must be after mode or it's overridden.
1692
scan_dir->set_current_dir(EDITOR_GET("filesystem/directories/default_project_path"));
1693
add_child(scan_dir);
1694
scan_dir->connect("dir_selected", callable_mp(project_list, &ProjectList::find_projects));
1695
1696
erase_missing_ask = memnew(ConfirmationDialog);
1697
erase_missing_ask->set_ok_button_text(TTRC("Remove All"));
1698
erase_missing_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_missing_projects_confirm));
1699
add_child(erase_missing_ask);
1700
1701
erase_ask = memnew(ConfirmationDialog);
1702
erase_ask->set_ok_button_text(TTRC("Remove"));
1703
erase_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_erase_project_confirm));
1704
add_child(erase_ask);
1705
1706
VBoxContainer *erase_ask_vb = memnew(VBoxContainer);
1707
erase_ask->add_child(erase_ask_vb);
1708
1709
erase_ask_label = memnew(Label);
1710
erase_ask_label->set_focus_mode(FOCUS_ACCESSIBILITY);
1711
erase_ask_vb->add_child(erase_ask_label);
1712
1713
// Comment out for now until we have a better warning system to
1714
// ensure users delete their project only.
1715
//delete_project_contents = memnew(CheckBox);
1716
//delete_project_contents->set_text(TTRC("Also delete project contents (no undo!)"));
1717
//erase_ask_vb->add_child(delete_project_contents);
1718
1719
multi_open_ask = memnew(ConfirmationDialog);
1720
multi_open_ask->set_ok_button_text(TTRC("Edit"));
1721
multi_open_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects));
1722
add_child(multi_open_ask);
1723
1724
multi_run_ask = memnew(ConfirmationDialog);
1725
multi_run_ask->set_ok_button_text(TTRC("Run"));
1726
multi_run_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_run_project_confirm));
1727
add_child(multi_run_ask);
1728
1729
open_recovery_mode_ask = memnew(ConfirmationDialog);
1730
open_recovery_mode_ask->set_min_size(Size2(550, 70) * EDSCALE);
1731
open_recovery_mode_ask->set_autowrap(true);
1732
open_recovery_mode_ask->add_button(TTRC("Edit normally"))->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_on_recovery_mode_popup_open_normal));
1733
open_recovery_mode_ask->set_ok_button_text(TTRC("Edit in Recovery Mode"));
1734
open_recovery_mode_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_on_recovery_mode_popup_open_recovery));
1735
add_child(open_recovery_mode_ask);
1736
1737
ask_update_settings = memnew(ConfirmationDialog);
1738
add_child(ask_update_settings);
1739
ask_update_vb = memnew(VBoxContainer);
1740
ask_update_settings->add_child(ask_update_vb);
1741
ask_update_label = memnew(Label);
1742
ask_update_label->set_focus_mode(FOCUS_ACCESSIBILITY);
1743
ask_update_label->set_custom_minimum_size(Size2(300 * EDSCALE, 1));
1744
ask_update_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
1745
ask_update_label->set_v_size_flags(SIZE_EXPAND_FILL);
1746
ask_update_vb->add_child(ask_update_label);
1747
ask_update_backup = memnew(CheckBox);
1748
ask_update_backup->set_text(TTRC("Backup project first"));
1749
ask_update_backup->set_h_size_flags(SIZE_SHRINK_CENTER);
1750
ask_update_vb->add_child(ask_update_backup);
1751
ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_with_migration));
1752
int ed_swap_cancel_ok = EDITOR_GET("interface/editor/accept_dialog_cancel_ok_buttons");
1753
if (ed_swap_cancel_ok == 0) {
1754
ed_swap_cancel_ok = DisplayServer::get_singleton()->get_swap_cancel_ok() ? 2 : 1;
1755
}
1756
full_convert_button = ask_update_settings->add_button(TTRC("Convert Full Project"), ed_swap_cancel_ok != 2);
1757
full_convert_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_full_convert_button_pressed));
1758
migration_guide_button = ask_update_settings->add_button(TTRC("See Migration Guide"), ed_swap_cancel_ok != 2);
1759
migration_guide_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_migration_guide_button_pressed));
1760
1761
ask_full_convert_dialog = memnew(ConfirmationDialog);
1762
ask_full_convert_dialog->set_autowrap(true);
1763
ask_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."));
1764
ask_full_convert_dialog->connect(SceneStringName(confirmed), callable_mp(this, &ProjectManager::_perform_full_project_conversion));
1765
add_child(ask_full_convert_dialog);
1766
1767
project_dialog = memnew(ProjectDialog);
1768
project_dialog->connect("projects_updated", callable_mp(this, &ProjectManager::_on_projects_updated));
1769
project_dialog->connect("project_created", callable_mp(this, &ProjectManager::_on_project_created));
1770
project_dialog->connect("project_duplicated", callable_mp(this, &ProjectManager::_on_project_duplicated));
1771
add_child(project_dialog);
1772
1773
error_dialog = memnew(AcceptDialog);
1774
error_dialog->set_title(TTRC("Error"));
1775
add_child(error_dialog);
1776
1777
about_dialog = memnew(EditorAbout);
1778
add_child(about_dialog);
1779
}
1780
1781
// Tag management.
1782
{
1783
tag_manage_dialog = memnew(ConfirmationDialog);
1784
add_child(tag_manage_dialog);
1785
tag_manage_dialog->set_title(TTRC("Manage Project Tags"));
1786
tag_manage_dialog->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_apply_project_tags));
1787
manage_tags_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_manage_project_tags));
1788
1789
VBoxContainer *tag_vb = memnew(VBoxContainer);
1790
tag_manage_dialog->add_child(tag_vb);
1791
1792
Label *label = memnew(Label(TTRC("Project Tags")));
1793
tag_vb->add_child(label);
1794
label->set_theme_type_variation("HeaderMedium");
1795
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1796
1797
label = memnew(Label(TTRC("Click tag to remove it from the project.")));
1798
tag_vb->add_child(label);
1799
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1800
1801
project_tags = memnew(HFlowContainer);
1802
tag_vb->add_child(project_tags);
1803
project_tags->set_custom_minimum_size(Vector2(0, 100) * EDSCALE);
1804
1805
tag_vb->add_child(memnew(HSeparator));
1806
1807
label = memnew(Label(TTRC("All Tags")));
1808
tag_vb->add_child(label);
1809
label->set_theme_type_variation("HeaderMedium");
1810
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1811
1812
label = memnew(Label(TTRC("Click tag to add it to the project.")));
1813
tag_vb->add_child(label);
1814
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1815
1816
all_tags = memnew(HFlowContainer);
1817
tag_vb->add_child(all_tags);
1818
all_tags->set_custom_minimum_size(Vector2(0, 100) * EDSCALE);
1819
1820
tag_edit_error = memnew(Label);
1821
tag_vb->add_child(tag_edit_error);
1822
tag_edit_error->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
1823
1824
create_tag_dialog = memnew(ConfirmationDialog);
1825
tag_manage_dialog->add_child(create_tag_dialog);
1826
create_tag_dialog->set_title(TTRC("Create New Tag"));
1827
create_tag_dialog->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_create_new_tag));
1828
1829
tag_vb = memnew(VBoxContainer);
1830
create_tag_dialog->add_child(tag_vb);
1831
1832
Label *info = memnew(Label(TTRC("Tags are capitalized automatically when displayed.")));
1833
tag_vb->add_child(info);
1834
1835
new_tag_name = memnew(LineEdit);
1836
tag_vb->add_child(new_tag_name);
1837
new_tag_name->set_accessibility_name(TTRC("New Tag Name"));
1838
new_tag_name->connect(SceneStringName(text_changed), callable_mp(this, &ProjectManager::_set_new_tag_name));
1839
new_tag_name->connect(SceneStringName(text_submitted), callable_mp(this, &ProjectManager::_create_new_tag).unbind(1));
1840
create_tag_dialog->connect("about_to_popup", callable_mp(new_tag_name, &LineEdit::clear));
1841
create_tag_dialog->connect("about_to_popup", callable_mp((Control *)new_tag_name, &Control::grab_focus), CONNECT_DEFERRED);
1842
1843
tag_error = memnew(Label);
1844
tag_error->set_focus_mode(FOCUS_ACCESSIBILITY);
1845
tag_vb->add_child(tag_error);
1846
1847
create_tag_btn = memnew(Button);
1848
create_tag_btn->set_accessibility_name(TTRC("Create Tag"));
1849
all_tags->add_child(create_tag_btn);
1850
create_tag_btn->connect(SceneStringName(pressed), callable_mp((Window *)create_tag_dialog, &Window::popup_centered).bind(Vector2i(500, 0) * EDSCALE));
1851
1852
_set_new_tag_name("");
1853
}
1854
1855
// Initialize project list.
1856
{
1857
project_list->load_project_list();
1858
1859
Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM);
1860
1861
String default_project_path = EDITOR_GET("filesystem/directories/default_project_path");
1862
if (!default_project_path.is_empty() && !dir_access->dir_exists(default_project_path)) {
1863
Error error = dir_access->make_dir_recursive(default_project_path);
1864
if (error != OK) {
1865
ERR_PRINT("Could not create default project directory at: " + default_project_path);
1866
}
1867
}
1868
1869
String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path");
1870
if (!autoscan_path.is_empty()) {
1871
if (dir_access->dir_exists(autoscan_path)) {
1872
project_list->find_projects(autoscan_path);
1873
} else {
1874
Error error = dir_access->make_dir_recursive(autoscan_path);
1875
if (error != OK) {
1876
ERR_PRINT("Could not create project autoscan directory at: " + autoscan_path);
1877
}
1878
}
1879
}
1880
project_list->update_project_list();
1881
initialized = true;
1882
}
1883
1884
// Extend menu bar to window title.
1885
if (can_expand) {
1886
DisplayServer::get_singleton()->process_events();
1887
DisplayServer::get_singleton()->window_set_flag(DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE, true, DisplayServer::MAIN_WINDOW_ID);
1888
title_bar->set_can_move_window(true);
1889
title_bar->connect(SceneStringName(item_rect_changed), callable_mp(this, &ProjectManager::_titlebar_resized));
1890
}
1891
1892
_update_size_limits();
1893
}
1894
1895
ProjectManager::~ProjectManager() {
1896
singleton = nullptr;
1897
if (EditorSettings::get_singleton()) {
1898
EditorSettings::destroy();
1899
}
1900
1901
EditorThemeManager::finalize();
1902
}
1903
1904