Godot 4 源码分析 - Project Manager

news2024/11/25 16:50:18

简单来说,Godot 4一共有三种运行模式:工程管理、编辑、运行

有点意思的是,每次调试,只能在其中一种模式下运行

如果同时配置了编辑器与工程管理器,则会报错:

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

这三种运行模式中,最简单的就是工程管理 - Project Manager

要以工程管理模式运行,在命令行中加入 -p 或--project-manager即可

在源码中,Main::setup中判断:

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

然后创建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();
}

从运行结果来看,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());
	}

代码与界面效果可一一对应。

除去界面各控件外,就是加载工程项_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);

 这个命令行,就是以编辑器模式运行。

当然,还有ENTER键会直接调用_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();
		}
	}
}

工程管理应该是最简单的,可以熟悉一下Godot的界面控件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/667921.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uni-app uni-forms组件的表单验证

前言 最近使用uni-app开发时&#xff0c;在使用加强表单时&#xff0c;使用表单验证的过程和PC端的区别uni-app文档说如果要使用自定义表单验证是需要去掉form中:rules"rules",使用ref绑定但最终我使用validateFunction 自定义校验规则&#xff0c;使用上面2种方式都…

音质更进一步,更耐用的骨传导耳机,南卡Runner Pro 4S上手

骨传导耳机是一种非常适合户外使用的耳机&#xff0c;很多喜欢运动的朋友都会配备一副&#xff0c;户外健身的时候会一直戴着。这种耳机使用时不入耳&#xff0c;通过震动颧骨来传递声音&#xff0c;不影响我们和别人的正常交流&#xff0c;户外也可以清楚感知车流、鸣笛的声音…

专业小程序开发平台 教你如何开发点餐小程序

今天小编借助在线工具乔拓云&#xff0c;只需借助在线模板和无编程开发工具&#xff0c;轻松实现点餐小程序开发和管理&#xff0c;下面跟着小编的教程一起学习&#xff0c;如何使用乔拓云工具开发专属的外卖点餐订餐小程序平台。 像这样一个点餐外卖小程序只需一个模板无编程就…

Java多线程与并发-原理

1、synchronized 线程安全问题的主要诱因 存在共享数据&#xff08;也称临界资源&#xff09;。存在多条线程共同操作这些共享数据。 解决问题的根本方法&#xff1a; 同一时刻有且只有一个线程在操作共享数据&#xff0c;其他线程必须等到该线程处理完数据后再对共享数据进…

【编译、链接、装载八】链接

【编译和链接八】链接 一、链接的起源——链接器年龄比编译器长1、 机器指令时代2、汇编指令时代3、链接4、静态链接5、结合CPU指令分析链接 二、链接的接口——符号1、ELF符号表结构1.1、符号类型和绑定信息&#xff08;st_info&#xff09;1.2、符号所在段&#xff08;st_shn…

图神经网络的基本结构

文章目录 图神经网络的基本结构图谱和图傅里叶变换基于频谱域的GNN和基于空间域的GNN的比较图神经网络的任务需求和模型要求任务需求模型要求 图神经网络的实用框架GCN图神经网络的几道面试题 图神经网络的基本结构 图神经网络 (Graph Neural Network, GNN) 是一类用于处理图数…

UI自动化测试的痛点有哪些?怎么解决

目录 前言 1、需求不稳定&#xff0c;频繁变更的项目 2、开发维护周期短的项目 3、被测系统开发不规范&#xff0c;可测试性需求不明确 总结&#xff1a; 前言 当我们找工作的时候查看招聘信息发现都需要有自动化测试经验&#xff0c;由此看来测试人员不会一点自动化测试技…

【已解决】无法启动此程序,因为计算机中丢失vcruntime140.dll(解决方案)

vcruntime140.dll是什么什么文件呢&#xff1f;为什么电脑在运行一些游戏的时候会出现丢失vcruntime140.dll&#xff0c;然后游戏运行失败?这个dll文件是电脑重要的运行库文件。丢失了会导致很多程序无法运行。下面将介绍【已解决】无法启动此程序,因为计算机中丢失vcruntime1…

AOP介绍

AOP的介绍AOP相关概念相关概念&#xff1a;细谈通知 Spring AOP使用准备工作前置通知具体实现环绕通知配置规则表达式解析 Spring AOP原理 AOP的介绍 AOP:AOP是一种思想&#xff1b;面向切面编程。它对某一类的事情做集中处理&#xff1b;更准确的说是面向集中功能的编程 Spri…

【Linux常用命令上】——Linux系统02

目录索引 快速复习导航&#xff1a;查看Linux的ip&#xff1a;查看当前用户&#xff1a;切换路径&#xff1a;退出当前文件夹&#xff1a;用户工作目录&#xff1a; 文件树&#xff1a;测试网路是否正常连接&#xff1a;清除指令&#xff1a;查看当前操作系统信息&#xff1a;s…

SpringBoot整合模板引擎Thymeleaf(3)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 概述 在本章节详细介绍Thymeleaf的内置对象及其工具类。 Thymeleaf内置对象 对象描述#ctx上下文对象#vars同 #ctx&#xff0c;表示上下文变量#locale上下文本地化&#…

Java集合中ArrayList、LinkedList异同(面试题)

为什么一般都使用 List list new ArrayList() ,而不用 ArrayList alist new ArrayList()呢&#xff1f; 1. 问题就在于List有多个实现类&#xff0c;如 LinkedList或者Vector等等&#xff0c; 现在你用的是ArrayList&#xff0c;也许哪一天你需要换成其它的实现类呢&#xf…

自定义异常

打开搜索界面&#xff0c;快捷键:双击shift键盘 如何创立一个自己定义简单的异常&#xff0c;如下: 1&#xff0c;先用extends继承Exception&#xff08;总异常类&#xff09;然后定义私有类变量 2&#xff0c;用快捷键&#xff1b;latinsert 选择构造器&#xff08;Constru…

Python+Requests+Unittest接口自动化测试

(1)接口自动化测试的意义、前后端分离思想 接口自动化测试的优缺点&#xff1a; 优点&#xff1a; 测试复用性。 维护成本相对UI自动化低一些。 为什么UI自动化维护成本更高&#xff1f; 因为前端页面变化太快&#xff0c;而且UI自动化比较耗时&#xff08;比如等待页面元素的…

C高级 day37

1、编写一个名为myfirstshell.sh的脚本&#xff0c;它包括以下内容。 1、包含一段注释&#xff0c;列出您的姓名、脚本的名称和编写这个脚本的目的 2、和当前用户说“hello 用户名” 3、显示您的机器名 hostname 4、显示上一级目录中的所有文件的列表 5、显示变量PATH和HOME的值…

ESP32 S3-OLED显示小数函数

ESP32 S3 ardino平台&#xff0c;配中景园7针0.96OLED屏显示小数 OLED网上的驱动代码一般厂商发货会提供驱动程序&#xff0c;但是显示小数很多都没有编写。这里编写了一段可显示任意位小数的代码&#xff08;以正点原子代码为基础&#xff09;&#xff0c;需要显示有符号的小数…

HDFS读写流程

读数据流程 客户端向NameNode请求文件的位置&#xff1a;客户端想要访问一个文件时&#xff0c;会向NameNode发送一个请求&#xff0c;要求获取该文件在HDFS上的位置信息。 NameNode将位置信息返回给客户端&#xff1a;NameNode接收到客户端的请求后&#xff0c;会返回该文件所…

【人脸检测0】视频分解图片与图片合成视频

一,引言 目标:这小节主要通过两个demo熟悉视频分解图片与图片合成视频的OpenCV的应用 环境:python3.6+OpenCV3.3.1 二,示例 Demo1:视频分解图片 目标: 1.指定文件夹中读取视频文件 2.将视频文件分解为图片 3.将图片保存在指定文件夹中 # -*-coding:utf-8-*- #auth…

Eureka配置文件详解

Eureka配置文件详解 文章目录 Eureka配置文件详解一、Eureka instance 配置项&#xff1a;二、Eureka Client 配置项三、Eureka Dashboard仪表板配置项四、Eureka Server配置项4.1 server与client关联配置4.2 server 自定义实现的配置4.3 server 与 remote 关联的配置4.4 serve…