Análisis del código fuente de Godot 4 - Project Manager

En pocas palabras, Godot 4 tiene tres modos de funcionamiento: gestión de proyectos, edición y ejecución.

Lo interesante es que cada vez que depura, solo puede ejecutar uno de los modos

Si el editor y el administrador de proyectos se configuran al mismo tiempo, se informará un error:

if (editor && project_manager) {
	OS::get_singleton()->print(
		"Error: Command line arguments implied opening both editor and project manager, which is not possible. Aborting.\n");
	goto error;
}

Entre los tres modos de funcionamiento, el más sencillo es la gestión de proyectos - Project Manager

Para ejecutar en modo de gestión de proyectos, agregue -p o --project-manager a la línea de comando

En el código fuente, Main::setup juzga:

else if (I->get() == "-p" || I->get() == "--project-manager") { // starts project manager
    project_manager = true;
}

Luego crea el ProjectManager

if (project_manager) {
	Engine::get_singleton()->startup_benchmark_begin_measure("project_manager");
	Engine::get_singleton()->set_editor_hint(true);
	ProjectManager *pmanager = memnew(ProjectManager);
	ProgressDialog *progress_dialog = memnew(ProgressDialog);
	pmanager->add_child(progress_dialog);
	sml->get_root()->add_child(pmanager);
	DisplayServer::get_singleton()->set_context(DisplayServer::CONTEXT_PROJECTMAN);
	Engine::get_singleton()->startup_benchmark_end_measure();
}

A juzgar por los resultados de ejecución, ProjectManager es una sencilla aplicación de ventana de Windows

Según la comprensión del programa de la ventana, debe haber una ventana creada en algún lugar, y el resultado se encuentra en el código fuente de display_server_windows:

DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) {
	DWORD dwExStyle;
	DWORD dwStyle;

	_get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle);

	RECT WindowRect;

	WindowRect.left = p_rect.position.x;
	WindowRect.right = p_rect.position.x + p_rect.size.x;
	WindowRect.top = p_rect.position.y;
	WindowRect.bottom = p_rect.position.y + p_rect.size.y;

	int rq_screen = get_screen_from_rect(p_rect);
	if (rq_screen < 0) {
		rq_screen = get_primary_screen(); // Requested window rect is outside any screen bounds.
	}

	if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
		Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen));

		WindowRect.left = screen_rect.position.x;
		WindowRect.right = screen_rect.position.x + screen_rect.size.x;
		WindowRect.top = screen_rect.position.y;
		WindowRect.bottom = screen_rect.position.y + screen_rect.size.y;
	} else {
		Rect2i srect = screen_get_usable_rect(rq_screen);
		Point2i wpos = p_rect.position;
		if (srect != Rect2i()) {
			wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3);
			wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3);
		}

		WindowRect.left = wpos.x;
		WindowRect.right = wpos.x + p_rect.size.x;
		WindowRect.top = wpos.y;
		WindowRect.bottom = wpos.y + p_rect.size.y;
	}

	Point2i offset = _get_screens_origin();
	WindowRect.left += offset.x;
	WindowRect.right += offset.x;
	WindowRect.top += offset.y;
	WindowRect.bottom += offset.y;

	AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);

	WindowID id = window_id_counter;
	{
		WindowData &wd = windows[id];

		wd.hWnd = CreateWindowExW(
				dwExStyle,
				L"Engine", L"",
				dwStyle,
				//				(GetSystemMetrics(SM_CXSCREEN) - WindowRect.right) / 2,
				//				(GetSystemMetrics(SM_CYSCREEN) - WindowRect.bottom) / 2,
				WindowRect.left,
				WindowRect.top,
				WindowRect.right - WindowRect.left,
				WindowRect.bottom - WindowRect.top,
				nullptr,
				nullptr,
				hInstance,
				// tunnel the WindowData we need to handle creation message
				// lifetime is ensured because we are still on the stack when this is
				// processed in the window proc
				reinterpret_cast<void *>(&wd));
		if (!wd.hWnd) {
			MessageBoxW(nullptr, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
			windows.erase(id);
			ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Windows OS window.");
		}
		if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
			wd.fullscreen = true;
			if (p_mode == WINDOW_MODE_FULLSCREEN) {
				wd.multiwindow_fs = true;
			}
		}
		if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
			wd.pre_fs_valid = true;
		}

		if (is_dark_mode_supported() && dark_title_available) {
			BOOL value = is_dark_mode();
			::DwmSetWindowAttribute(wd.hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
		}

#ifdef VULKAN_ENABLED
		if (context_vulkan) {
			if (context_vulkan->window_create(id, p_vsync_mode, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) != OK) {
				memdelete(context_vulkan);
				context_vulkan = nullptr;
				windows.erase(id);
				ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Vulkan Window.");
			}
			wd.context_created = true;
		}
#endif

#ifdef GLES3_ENABLED
		if (gl_manager) {
			if (gl_manager->window_create(id, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) != OK) {
				memdelete(gl_manager);
				gl_manager = nullptr;
				windows.erase(id);
				ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create an OpenGL window.");
			}
			window_set_vsync_mode(p_vsync_mode, id);
		}
#endif

		RegisterTouchWindow(wd.hWnd, 0);
		DragAcceptFiles(wd.hWnd, true);

		if ((tablet_get_current_driver() == "wintab") && wintab_available) {
			wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc);
			wd.wtlc.lcOptions |= CXO_MESSAGES;
			wd.wtlc.lcPktData = PK_STATUS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
			wd.wtlc.lcMoveMask = PK_STATUS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE;
			wd.wtlc.lcPktMode = 0;
			wd.wtlc.lcOutOrgX = 0;
			wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX;
			wd.wtlc.lcOutOrgY = 0;
			wd.wtlc.lcOutExtY = -wd.wtlc.lcInExtY;
			wd.wtctx = wintab_WTOpen(wd.hWnd, &wd.wtlc, false);
			if (wd.wtctx) {
				wintab_WTEnable(wd.wtctx, true);
				AXIS pressure;
				if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
					wd.min_pressure = int(pressure.axMin);
					wd.max_pressure = int(pressure.axMax);
				}
				AXIS orientation[3];
				if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
					wd.tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
				}
			} else {
				print_verbose("WinTab context creation failed.");
			}
		} else {
			wd.wtctx = 0;
		}

		if (p_mode == WINDOW_MODE_MAXIMIZED) {
			wd.maximized = true;
			wd.minimized = false;
		}

		if (p_mode == WINDOW_MODE_MINIMIZED) {
			wd.maximized = false;
			wd.minimized = true;
		}

		wd.last_pressure = 0;
		wd.last_pressure_update = 0;
		wd.last_tilt = Vector2();

		// IME.
		wd.im_himc = ImmGetContext(wd.hWnd);
		ImmAssociateContext(wd.hWnd, (HIMC)0);

		wd.im_position = Vector2();

		if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN || p_mode == WINDOW_MODE_MAXIMIZED) {
			RECT r;
			GetClientRect(wd.hWnd, &r);
			ClientToScreen(wd.hWnd, (POINT *)&r.left);
			ClientToScreen(wd.hWnd, (POINT *)&r.right);
			wd.last_pos = Point2i(r.left, r.top) - _get_screens_origin();
			wd.width = r.right - r.left;
			wd.height = r.bottom - r.top;
		} else {
			wd.last_pos = p_rect.position;
			wd.width = p_rect.size.width;
			wd.height = p_rect.size.height;
		}

		window_id_counter++;
	}

	return id;
}

El título de la ventana se establece en el constructor ProjectManager::ProjectManager() en el archivo fuente project_manager:

DisplayServer::get_singleton()->window_set_title(VERSION_NAME + String(" - ") + TTR("Project Manager", "Application"));

Continúe mirando hacia abajo y use la clase Control de Godot para construir la interfaz:

	Panel *panel = memnew(Panel);
	add_child(panel);
	panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
	panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("Background"), SNAME("EditorStyles")));

	VBoxContainer *vb = memnew(VBoxContainer);
	panel->add_child(vb);
	vb->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 8 * EDSCALE);

	Control *center_box = memnew(Control);
	center_box->set_v_size_flags(Control::SIZE_EXPAND_FILL);
	vb->add_child(center_box);

	tabs = memnew(TabContainer);
	center_box->add_child(tabs);
	tabs->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
	tabs->connect("tab_changed", callable_mp(this, &ProjectManager::_on_tab_changed));

	local_projects_hb = memnew(HBoxContainer);
	local_projects_hb->set_name(TTR("Local Projects"));
	tabs->add_child(local_projects_hb);

	{
		// Projects + search bar
		VBoxContainer *search_tree_vb = memnew(VBoxContainer);
		local_projects_hb->add_child(search_tree_vb);
		search_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);

		HBoxContainer *hb = memnew(HBoxContainer);
		hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
		search_tree_vb->add_child(hb);

		search_box = memnew(LineEdit);
		search_box->set_placeholder(TTR("Filter Projects"));
		search_box->set_tooltip_text(TTR("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."));
		search_box->connect("text_changed", callable_mp(this, &ProjectManager::_on_search_term_changed));
		search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
		hb->add_child(search_box);

		loading_label = memnew(Label(TTR("Loading, please wait...")));
		loading_label->add_theme_font_override("font", get_theme_font(SNAME("bold"), SNAME("EditorFonts")));
		loading_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
		hb->add_child(loading_label);
		// The loading label is shown later.
		loading_label->hide();

		Label *sort_label = memnew(Label);
		sort_label->set_text(TTR("Sort:"));
		hb->add_child(sort_label);

		filter_option = memnew(OptionButton);
		filter_option->set_clip_text(true);
		filter_option->set_h_size_flags(Control::SIZE_EXPAND_FILL);
		filter_option->connect("item_selected", callable_mp(this, &ProjectManager::_on_order_option_changed));
		hb->add_child(filter_option);

		Vector<String> sort_filter_titles;
		sort_filter_titles.push_back(TTR("Last Edited"));
		sort_filter_titles.push_back(TTR("Name"));
		sort_filter_titles.push_back(TTR("Path"));

		for (int i = 0; i < sort_filter_titles.size(); i++) {
			filter_option->add_item(sort_filter_titles[i]);
		}

		PanelContainer *pc = memnew(PanelContainer);
		pc->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
		pc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
		search_tree_vb->add_child(pc);

		_project_list = memnew(ProjectList);
		_project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
		_project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask));
		_project_list->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
		pc->add_child(_project_list);
	}

	{
		// Project tab side bar
		VBoxContainer *tree_vb = memnew(VBoxContainer);
		tree_vb->set_custom_minimum_size(Size2(120, 120));
		local_projects_hb->add_child(tree_vb);

		const int btn_h_separation = int(6 * EDSCALE);

		create_btn = memnew(Button);
		create_btn->set_text(TTR("New Project"));
		create_btn->add_theme_constant_override("h_separation", btn_h_separation);
		create_btn->set_shortcut(ED_SHORTCUT("project_manager/new_project", TTR("New Project"), KeyModifierMask::CMD_OR_CTRL | Key::N));
		create_btn->connect("pressed", callable_mp(this, &ProjectManager::_new_project));
		tree_vb->add_child(create_btn);

		import_btn = memnew(Button);
		import_btn->set_text(TTR("Import"));
		import_btn->add_theme_constant_override("h_separation", btn_h_separation);
		import_btn->set_shortcut(ED_SHORTCUT("project_manager/import_project", TTR("Import Project"), KeyModifierMask::CMD_OR_CTRL | Key::I));
		import_btn->connect("pressed", callable_mp(this, &ProjectManager::_import_project));
		tree_vb->add_child(import_btn);

		scan_btn = memnew(Button);
		scan_btn->set_text(TTR("Scan"));
		scan_btn->add_theme_constant_override("h_separation", btn_h_separation);
		scan_btn->set_shortcut(ED_SHORTCUT("project_manager/scan_projects", TTR("Scan Projects"), KeyModifierMask::CMD_OR_CTRL | Key::S));
		scan_btn->connect("pressed", callable_mp(this, &ProjectManager::_scan_projects));
		tree_vb->add_child(scan_btn);

		tree_vb->add_child(memnew(HSeparator));

		open_btn = memnew(Button);
		open_btn->set_text(TTR("Edit"));
		open_btn->add_theme_constant_override("h_separation", btn_h_separation);
		open_btn->set_shortcut(ED_SHORTCUT("project_manager/edit_project", TTR("Edit Project"), KeyModifierMask::CMD_OR_CTRL | Key::E));
		open_btn->connect("pressed", callable_mp(this, &ProjectManager::_open_selected_projects_ask));
		tree_vb->add_child(open_btn);

		run_btn = memnew(Button);
		run_btn->set_text(TTR("Run"));
		run_btn->add_theme_constant_override("h_separation", btn_h_separation);
		run_btn->set_shortcut(ED_SHORTCUT("project_manager/run_project", TTR("Run Project"), KeyModifierMask::CMD_OR_CTRL | Key::R));
		run_btn->connect("pressed", callable_mp(this, &ProjectManager::_run_project));
		tree_vb->add_child(run_btn);

		rename_btn = memnew(Button);
		rename_btn->set_text(TTR("Rename"));
		rename_btn->add_theme_constant_override("h_separation", btn_h_separation);
		// The F2 shortcut isn't overridden with Enter on macOS as Enter is already used to edit a project.
		rename_btn->set_shortcut(ED_SHORTCUT("project_manager/rename_project", TTR("Rename Project"), Key::F2));
		rename_btn->connect("pressed", callable_mp(this, &ProjectManager::_rename_project));
		tree_vb->add_child(rename_btn);

		erase_btn = memnew(Button);
		erase_btn->set_text(TTR("Remove"));
		erase_btn->add_theme_constant_override("h_separation", btn_h_separation);
		erase_btn->set_shortcut(ED_SHORTCUT("project_manager/remove_project", TTR("Remove Project"), Key::KEY_DELETE));
		erase_btn->connect("pressed", callable_mp(this, &ProjectManager::_erase_project));
		tree_vb->add_child(erase_btn);

		erase_missing_btn = memnew(Button);
		erase_missing_btn->set_text(TTR("Remove Missing"));
		erase_missing_btn->add_theme_constant_override("h_separation", btn_h_separation);
		erase_missing_btn->connect("pressed", callable_mp(this, &ProjectManager::_erase_missing_projects));
		tree_vb->add_child(erase_missing_btn);

		tree_vb->add_spacer();

		about_btn = memnew(Button);
		about_btn->set_text(TTR("About"));
		about_btn->connect("pressed", callable_mp(this, &ProjectManager::_show_about));
		tree_vb->add_child(about_btn);
	}

	{
		// Version info and language options
		settings_hb = memnew(HBoxContainer);
		settings_hb->set_alignment(BoxContainer::ALIGNMENT_END);
		settings_hb->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN);
		settings_hb->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT);

		// A VBoxContainer that contains a dummy Control node to adjust the LinkButton's vertical position.
		VBoxContainer *spacer_vb = memnew(VBoxContainer);
		settings_hb->add_child(spacer_vb);

		Control *v_spacer = memnew(Control);
		spacer_vb->add_child(v_spacer);

		version_btn = memnew(LinkButton);
		String hash = String(VERSION_HASH);
		if (hash.length() != 0) {
			hash = " " + vformat("[%s]", hash.left(9));
		}
		version_btn->set_text("v" VERSION_FULL_BUILD + hash);
		// Fade the version label to be less prominent, but still readable.
		version_btn->set_self_modulate(Color(1, 1, 1, 0.6));
		version_btn->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
		version_btn->set_tooltip_text(TTR("Click to copy."));
		version_btn->connect("pressed", callable_mp(this, &ProjectManager::_version_button_pressed));
		spacer_vb->add_child(version_btn);

		// Add a small horizontal spacer between the version and language buttons
		// to distinguish them.
		Control *h_spacer = memnew(Control);
		settings_hb->add_child(h_spacer);

		language_btn = memnew(OptionButton);
		language_btn->set_icon(get_theme_icon(SNAME("Environment"), SNAME("EditorIcons")));
		language_btn->set_focus_mode(Control::FOCUS_NONE);
		language_btn->set_fit_to_longest_item(false);
		language_btn->set_flat(true);
		language_btn->connect("item_selected", callable_mp(this, &ProjectManager::_language_selected));
#ifdef ANDROID_ENABLED
		// The language selection dropdown doesn't work on Android (as the setting isn't saved), see GH-60353.
		// Also, the dropdown it spawns is very tall and can't be scrolled without a hardware mouse.
		// Hiding the language selection dropdown also leaves more space for the version label to display.
		language_btn->hide();
#endif

		Vector<String> editor_languages;
		List<PropertyInfo> editor_settings_properties;
		EditorSettings::get_singleton()->get_property_list(&editor_settings_properties);
		for (const PropertyInfo &pi : editor_settings_properties) {
			if (pi.name == "interface/editor/editor_language") {
				editor_languages = pi.hint_string.split(",");
				break;
			}
		}

		String current_lang = EDITOR_GET("interface/editor/editor_language");
		language_btn->set_text(current_lang);

		for (int i = 0; i < editor_languages.size(); i++) {
			String lang = editor_languages[i];
			String lang_name = TranslationServer::get_singleton()->get_locale_name(lang);
			language_btn->add_item(vformat("[%s] %s", lang, lang_name), i);
			language_btn->set_item_metadata(i, lang);
			if (current_lang == lang) {
				language_btn->select(i);
			}
		}

		settings_hb->add_child(language_btn);
		center_box->add_child(settings_hb);
	}

	if (AssetLibraryEditorPlugin::is_available()) {
		asset_library = memnew(EditorAssetLibrary(true));
		asset_library->set_name(TTR("Asset Library Projects"));
		tabs->add_child(asset_library);
		asset_library->connect("install_asset", callable_mp(this, &ProjectManager::_install_project));
	} else {
		print_verbose("Asset Library not available (due to using Web editor, or SSL support disabled).");
	}

	{
		// Dialogs
		language_restart_ask = memnew(ConfirmationDialog);
		language_restart_ask->set_ok_button_text(TTR("Restart Now"));
		language_restart_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_restart_confirm));
		language_restart_ask->set_cancel_button_text(TTR("Continue"));
		add_child(language_restart_ask);

		scan_dir = memnew(EditorFileDialog);
		scan_dir->set_previews_enabled(false);
		scan_dir->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
		scan_dir->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
		scan_dir->set_title(TTR("Select a Folder to Scan")); // must be after mode or it's overridden
		scan_dir->set_current_dir(EDITOR_GET("filesystem/directories/default_project_path"));
		add_child(scan_dir);
		scan_dir->connect("dir_selected", callable_mp(this, &ProjectManager::_scan_begin));

		erase_missing_ask = memnew(ConfirmationDialog);
		erase_missing_ask->set_ok_button_text(TTR("Remove All"));
		erase_missing_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_erase_missing_projects_confirm));
		add_child(erase_missing_ask);

		erase_ask = memnew(ConfirmationDialog);
		erase_ask->set_ok_button_text(TTR("Remove"));
		erase_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_erase_project_confirm));
		add_child(erase_ask);

		VBoxContainer *erase_ask_vb = memnew(VBoxContainer);
		erase_ask->add_child(erase_ask_vb);

		erase_ask_label = memnew(Label);
		erase_ask_vb->add_child(erase_ask_label);

		delete_project_contents = memnew(CheckBox);
		delete_project_contents->set_text(TTR("Also delete project contents (no undo!)"));
		erase_ask_vb->add_child(delete_project_contents);

		multi_open_ask = memnew(ConfirmationDialog);
		multi_open_ask->set_ok_button_text(TTR("Edit"));
		multi_open_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_open_selected_projects));
		add_child(multi_open_ask);

		multi_run_ask = memnew(ConfirmationDialog);
		multi_run_ask->set_ok_button_text(TTR("Run"));
		multi_run_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_run_project_confirm));
		add_child(multi_run_ask);

		multi_scan_ask = memnew(ConfirmationDialog);
		multi_scan_ask->set_ok_button_text(TTR("Scan"));
		add_child(multi_scan_ask);

		ask_update_settings = memnew(ConfirmationDialog);
		ask_update_settings->set_autowrap(true);
		ask_update_settings->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_confirm_update_settings));
		full_convert_button = ask_update_settings->add_button(TTR("Convert Full Project"), !GLOBAL_GET("gui/common/swap_cancel_ok"));
		full_convert_button->connect("pressed", callable_mp(this, &ProjectManager::_full_convert_button_pressed));
		add_child(ask_update_settings);

		ask_full_convert_dialog = memnew(ConfirmationDialog);
		ask_full_convert_dialog->set_autowrap(true);
		ask_full_convert_dialog->set_text(TTR("This option will perform full project conversion, updating scenes, resources and scripts from Godot 3.x to work in Godot 4.0.\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."));
		ask_full_convert_dialog->connect("confirmed", callable_mp(this, &ProjectManager::_perform_full_project_conversion));
		add_child(ask_full_convert_dialog);

		npdialog = memnew(ProjectDialog);
		npdialog->connect("projects_updated", callable_mp(this, &ProjectManager::_on_projects_updated));
		npdialog->connect("project_created", callable_mp(this, &ProjectManager::_on_project_created));
		add_child(npdialog);

		run_error_diag = memnew(AcceptDialog);
		run_error_diag->set_title(TTR("Can't run project"));
		add_child(run_error_diag);

		dialog_error = memnew(AcceptDialog);
		add_child(dialog_error);

		if (asset_library) {
			open_templates = memnew(ConfirmationDialog);
			open_templates->set_text(TTR("You currently don't have any projects.\nWould you like to explore official example projects in the Asset Library?"));
			open_templates->set_ok_button_text(TTR("Open Asset Library"));
			open_templates->connect("confirmed", callable_mp(this, &ProjectManager::_open_asset_library));
			add_child(open_templates);
		}

		about = memnew(EditorAbout);
		add_child(about);

		_build_icon_type_cache(get_theme());
	}

Los códigos y los efectos de la interfaz pueden tener una correspondencia uno a uno.

Además de los controles en la interfaz, es para cargar el elemento del proyecto _load_recent_projects(), que se procesa directamente

void ProjectList::load_projects() {
	// This is a full, hard reload of the list. Don't call this unless really required, it's expensive.
	// If you have 150 projects, it may read through 150 files on your disk at once + load 150 icons.

	// Clear whole list
	for (int i = 0; i < _projects.size(); ++i) {
		Item &project = _projects.write[i];
		CRASH_COND(project.control == nullptr);
		memdelete(project.control); // Why not queue_free()?
	}
	_projects.clear();
	_last_clicked = "";
	_selected_project_paths.clear();

	List<String> sections;
	_config.load(_config_path);
	_config.get_sections(&sections);

	for (const String &path : sections) {
		bool favorite = _config.get_value(path, "favorite", false);
		_projects.push_back(load_project_data(path, favorite));
	}

	// Create controls
	for (int i = 0; i < _projects.size(); ++i) {
		create_project_item_control(i);
	}

	sort_projects();

	set_v_scroll(0);

	update_icons_async();

	update_dock_menu();
}

He estado usando RAD para desarrollar programas de Windows antes y nunca había visto este tipo de compilación desde cero, como create_project_item_control para manejar varios elementos del proyecto.

void ProjectList::create_project_item_control(int p_index) {
	// Will be added last in the list, so make sure indexes match
	ERR_FAIL_COND(p_index != _scroll_children->get_child_count());

	Item &item = _projects.write[p_index];
	ERR_FAIL_COND(item.control != nullptr); // Already created

	Ref<Texture2D> favorite_icon = get_theme_icon(SNAME("Favorites"), SNAME("EditorIcons"));
	Color font_color = get_theme_color(SNAME("font_color"), SNAME("Tree"));

	ProjectListItemControl *hb = memnew(ProjectListItemControl);
	hb->connect("draw", callable_mp(this, &ProjectList::_panel_draw).bind(hb));
	hb->connect("gui_input", callable_mp(this, &ProjectList::_panel_input).bind(hb));
	hb->add_theme_constant_override("separation", 10 * EDSCALE);
	hb->set_tooltip_text(item.description);

	VBoxContainer *favorite_box = memnew(VBoxContainer);
	favorite_box->set_name("FavoriteBox");
	TextureButton *favorite = memnew(TextureButton);
	favorite->set_name("FavoriteButton");
	favorite->set_texture_normal(favorite_icon);
	// This makes the project's "hover" style display correctly when hovering the favorite icon.
	favorite->set_mouse_filter(MOUSE_FILTER_PASS);
	favorite->connect("pressed", callable_mp(this, &ProjectList::_favorite_pressed).bind(hb));
	favorite_box->add_child(favorite);
	favorite_box->set_alignment(BoxContainer::ALIGNMENT_CENTER);
	hb->add_child(favorite_box);
	hb->favorite_button = favorite;
	hb->set_is_favorite(item.favorite);

	TextureRect *tf = memnew(TextureRect);
	// The project icon may not be loaded by the time the control is displayed,
	// so use a loading placeholder.
	tf->set_texture(get_theme_icon(SNAME("ProjectIconLoading"), SNAME("EditorIcons")));
	tf->set_v_size_flags(SIZE_SHRINK_CENTER);
	if (item.missing) {
		tf->set_modulate(Color(1, 1, 1, 0.5));
	}
	hb->add_child(tf);
	hb->icon = tf;

	VBoxContainer *vb = memnew(VBoxContainer);
	if (item.grayed) {
		vb->set_modulate(Color(1, 1, 1, 0.5));
	}
	vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
	hb->add_child(vb);
	Control *ec = memnew(Control);
	ec->set_custom_minimum_size(Size2(0, 1));
	ec->set_mouse_filter(MOUSE_FILTER_PASS);
	vb->add_child(ec);

	{ // Top half, title and unsupported features labels.
		HBoxContainer *title_hb = memnew(HBoxContainer);
		vb->add_child(title_hb);

		Label *title = memnew(Label(!item.missing ? item.project_name : TTR("Missing Project")));
		title->set_h_size_flags(Control::SIZE_EXPAND_FILL);
		title->add_theme_font_override("font", get_theme_font(SNAME("title"), SNAME("EditorFonts")));
		title->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("title_size"), SNAME("EditorFonts")));
		title->add_theme_color_override("font_color", font_color);
		title->set_clip_text(true);
		title_hb->add_child(title);

		String unsupported_features_str = String(", ").join(item.unsupported_features);
		int length = unsupported_features_str.length();
		if (length > 0) {
			Label *unsupported_label = memnew(Label(unsupported_features_str));
			unsupported_label->set_custom_minimum_size(Size2(length * 15, 10) * EDSCALE);
			unsupported_label->add_theme_font_override("font", get_theme_font(SNAME("title"), SNAME("EditorFonts")));
			unsupported_label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor")));
			unsupported_label->set_clip_text(true);
			unsupported_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
			title_hb->add_child(unsupported_label);
			Control *spacer = memnew(Control());
			spacer->set_custom_minimum_size(Size2(10, 10));
			title_hb->add_child(spacer);
		}
	}

	{ // Bottom half, containing the path and view folder button.
		HBoxContainer *path_hb = memnew(HBoxContainer);
		path_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
		vb->add_child(path_hb);

		Button *show = memnew(Button);
		// Display a folder icon if the project directory can be opened, or a "broken file" icon if it can't.
		show->set_icon(get_theme_icon(!item.missing ? SNAME("Load") : SNAME("FileBroken"), SNAME("EditorIcons")));
		show->set_flat(true);
		if (!item.grayed) {
			// Don't make the icon less prominent if the parent is already grayed out.
			show->set_modulate(Color(1, 1, 1, 0.5));
		}
		path_hb->add_child(show);

		if (!item.missing) {
#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
			show->connect("pressed", callable_mp(this, &ProjectList::_show_project).bind(item.path));
			show->set_tooltip_text(TTR("Show in File Manager"));
#else
			// Opening the system file manager is not supported on the Android and web editors.
			show->hide();
#endif
		} else {
			show->set_tooltip_text(TTR("Error: Project is missing on the filesystem."));
		}

		Label *fpath = memnew(Label(item.path));
		fpath->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
		path_hb->add_child(fpath);
		fpath->set_h_size_flags(Control::SIZE_EXPAND_FILL);
		fpath->set_modulate(Color(1, 1, 1, 0.5));
		fpath->add_theme_color_override("font_color", font_color);
		fpath->set_clip_text(true);
	}

	_scroll_children->add_child(hb);
	item.control = hb;
}

De hecho, se trata de todo.

Si quieres usar Godot como interfaz, puedes echar un buen vistazo a estos códigos.

Por ejemplo, para proyectos que ya no existen, modifique el código

Label *title = memnew(Label(!item.missing ? item.project_name : TTR("Missing Project")));

para

Label *title = memnew(Label(!item.missing ? item.project_name : TTR("Missing Project") + " - " + item.path));

Entonces el efecto de visualización es: 

El resto de controles ya no se estudian, más o menos lo mismo.

Lo anterior es el proceso de inicialización. En cuanto al bucle en él, porque en el constructor ProjectManager, se ha establecido

	NavigationServer3D::get_singleton()->set_active(false);
	PhysicsServer3D::get_singleton()->set_active(false);
	PhysicsServer2D::get_singleton()->set_active(false);

Por lo tanto, el bucle significa que la ventana se reproduce sola hasta que se cumple esta condición.

_project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask));

Es decir, se llamará cuando se active la señal SIGNAL_PROJECT_ASK_OPEN

_open_selected_projects_ask, de hecho, finalmente llama a _open_selected_projects

void ProjectManager::_open_selected_projects() {
	// Show loading text to tell the user that the project manager is busy loading.
	// This is especially important for the Web project manager.
	loading_label->show();

	const HashSet<String> &selected_list = _project_list->get_selected_project_keys();

	for (const String &path : selected_list) {
		String conf = path.path_join("project.godot");

		if (!FileAccess::exists(conf)) {
			dialog_error->set_text(vformat(TTR("Can't open project at '%s'."), path));
			dialog_error->popup_centered();
			return;
		}

		print_line("Editing project: " + path);

		List<String> args;

		for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {
			args.push_back(a);
		}

		args.push_back("--path");
		args.push_back(path);

		args.push_back("--editor");

		Error err = OS::get_singleton()->create_instance(args);
		ERR_FAIL_COND(err);
	}

	_project_list->project_opening_initiated = true;

	_dim_window();
	get_tree()->quit();
}

 Su esencia es establecer los parámetros de la línea de comando --editor --path ..., y salir del programa después de llamar a la nueva instancia

		args.push_back("--path");
		args.push_back(path);

		args.push_back("--editor");

		Error err = OS::get_singleton()->create_instance(args);

 Esta línea de comando se ejecuta en modo editor.

Por supuesto, también existe la tecla ENTER que llamará directamente a _open_selected_projects_ask

void ProjectManager::shortcut_input(const Ref<InputEvent> &p_ev) {
	ERR_FAIL_COND(p_ev.is_null());

	Ref<InputEventKey> k = p_ev;

	if (k.is_valid()) {
		if (!k->is_pressed()) {
			return;
		}

		// Pressing Command + Q quits the Project Manager
		// This is handled by the platform implementation on macOS,
		// so only define the shortcut on other platforms
#ifndef MACOS_ENABLED
		if (k->get_keycode_with_modifiers() == (KeyModifierMask::META | Key::Q)) {
			_dim_window();
			get_tree()->quit();
		}
#endif

		if (tabs->get_current_tab() != 0) {
			return;
		}

		bool keycode_handled = true;

		switch (k->get_keycode()) {
			case Key::ENTER: {
				_open_selected_projects_ask();
			} break;
			case Key::HOME: {
				if (_project_list->get_project_count() > 0) {
					_project_list->select_project(0);
					_update_project_buttons();
				}

			} break;
			case Key::END: {
				if (_project_list->get_project_count() > 0) {
					_project_list->select_project(_project_list->get_project_count() - 1);
					_update_project_buttons();
				}

			} break;
			case Key::UP: {
				if (k->is_shift_pressed()) {
					break;
				}

				int index = _project_list->get_single_selected_index();
				if (index > 0) {
					_project_list->select_project(index - 1);
					_project_list->ensure_project_visible(index - 1);
					_update_project_buttons();
				}

				break;
			}
			case Key::DOWN: {
				if (k->is_shift_pressed()) {
					break;
				}

				int index = _project_list->get_single_selected_index();
				if (index + 1 < _project_list->get_project_count()) {
					_project_list->select_project(index + 1);
					_project_list->ensure_project_visible(index + 1);
					_update_project_buttons();
				}

			} break;
			case Key::F: {
				if (k->is_command_or_control_pressed()) {
					this->search_box->grab_focus();
				} else {
					keycode_handled = false;
				}
			} break;
			default: {
				keycode_handled = false;
			} break;
		}

		if (keycode_handled) {
			accept_event();
		}
	}
}

La gestión de proyectos debería ser la más fácil, puedes familiarizarte con los controles de la interfaz de Godot.

Supongo que te gusta

Origin blog.csdn.net/drgraph/article/details/131310745
Recomendado
Clasificación