Godot 4 ソース コード分析 - プロジェクト マネージャー

簡単に言えば、Godot 4 にはプロジェクト管理、編集、実行という 3 つの動作モードがあります。

興味深いのは、デバッグするたびに、いずれかのモードでしか実行できないことです。

エディターとプロジェクト マネージャーが同時に設定されている場合、エラーが報告されます。

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;
}

3 つの操作モードの中で、最も単純なプロジェクト管理はプロジェクト マネージャーです。

プロジェクト管理モードで実行するには、コマンド ラインに -p または --project-manager を追加します。

ソースコードでは、Main::setup は以下を判断します。

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

次に、プロジェクトマネージャーを作成します

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();
}

実行結果から判断すると、ProjectManager は単純な Windows ウィンドウ アプリケーションです。

ウィンドウ プログラムの理解に基づいて、どこかにウィンドウが作成されている必要があります。その結果は、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;
}

ウィンドウのタイトルは、project_manager ソース ファイルのコンストラクター ProjectManager::ProjectManager() で設定されます。

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

引き続き下を見て、実際に Godot の Control クラスを使用してインターフェイスを構築します。

	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());
	}

コードとインターフェース効果は 1 対 1 に対応させることができます。

インターフェース上のコントロールに加えて、直接処理されるプロジェクト項目 _load_recent_projects() をロードします。

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();
}

私はこれまで RAD を使用して Windows プログラムを開発していましたが、さまざまなプロジェクト項目を処理する create_project_item_control など、この種のビルドを最初から見たことがありませんでした。

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;
}

実際、それはすべてについてです

Godot をインターフェースとして使用したい場合は、これらのコードをよく見てください。

たとえば、もう存在しないプロジェクトの場合は、コードを変更します。

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

ために

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

表示効果は次のようになります。 

残りのコントロールは研究されておらず、ほぼ同じです。

以上が初期化処理です。その中のループについては、ProjectManager コンストラクターで設定されているため、

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

したがって、ループは、この条件が満たされるまでウィンドウが自動的に再生されることを意味します。

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

つまり、シグナル SIGNAL_PROJECT_ASK_OPEN がトリガーされたときに呼び出されます。

実際、_open_selected_projects_ask は最終的に _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();
}

 その本質は、コマンド ライン パラメータ --editor --path ... を設定し、新しいインスタンスを呼び出した後にプログラムを終了することです。

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

		args.push_back("--editor");

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

 このコマンド ラインはエディタ モードで実行されます。

もちろん、_open_selected_projects_ask を直接呼び出す ENTER キーもあります。

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();
		}
	}
}

プロジェクト管理は最も簡単である必要があり、Godot のインターフェイス コントロールに慣れることができます。

おすすめ

転載: blog.csdn.net/drgraph/article/details/131310745