Godot 4 源码分析 - Project Manager

news/2024/11/30 13:45:05/

简单来说,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 managerproject_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 procreinterpret_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_ENABLEDif (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_ENABLEDif (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);}
#endifRegisterTouchWindow(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 barVBoxContainer *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 barVBoxContainer *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 optionssettings_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();
#endifVector<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).");}{// Dialogslanguage_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 overriddenscan_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 listfor (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 controlsfor (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 matchERR_FAIL_COND(p_index != _scroll_children->get_child_count());Item &item = _projects.write[p_index];ERR_FAIL_COND(item.control != nullptr); // Already createdRef<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_ENABLEDif (k->get_keycode_with_modifiers() == (KeyModifierMask::META | Key::Q)) {_dim_window();get_tree()->quit();}
#endifif (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.ppmy.cn/news/487505.html

相关文章

毕业设计心得总结10篇

毕业设计心得总结1 201_年5月30日上午&#xff0c;我们的毕业论文答辩圆满结束了。当刘老师给我们送上人生的祝语时&#xff0c;一种即将离别的难舍之情油然而生&#xff0c;我开始眷恋培养我大学四年的母校&#xff0c;看着一张张熟悉的面孔&#xff0c;过去的欢乐和不快都烟消…

C语言学习笔记:文件

✨博文作者&#xff1a;烟雨孤舟 &#x1f496; 喜欢的可以 点赞 收藏 关注哦~~ ✍️ 作者简介: 一个热爱大数据的学习者 ✍️ 笔记简介&#xff1a;作为大数据爱好者&#xff0c;以下是个人总结的学习笔记&#xff0c;如有错误&#xff0c;请多多指教&#xff01; 目录 打开文…

崩盘预警币圈项目:Fintoch(分投趣)为何模式能做千亿资金?

崩盘预警币圈项目&#xff1a;Fintoch&#xff08;分投趣&#xff09;为何模式能做千亿资金&#xff1f; 大家好&#xff0c;小编是微三云的胡佳东&#xff0c;一家软件开发公司的负责人 Fintoch&#xff08;分投趣&#xff09;为何模式崩盘前能够吸引如此巨额资金&#xff1f…

C语言深度刨析(二)——符号

文章目录 前言注释符号几个似非而是的注释问题y x/*p如何编写出出色的注释 接续符和转义符单引号、双引号逻辑运算符位运算符左移和右移0x01<<23的值为多少&#xff1f; 花括号、--操作符2/(-2)的值是多少&#xff1f;运算符的优先级运算符的优先级表一些容易出错的优先…

vm无法打开电源

背景 某次操作过程中&#xff0c;vm升级后启动报无法打开电源 分析 查看vm所在主机的上联端口状态异常&#xff0c;显示断开&#xff0c;备用上联端口未启用生效。官方资料表明&#xff0c;这是 vsphere6.7的已知问题。 当 Network I/O Control 已启用并且遇到以下情况时&a…

计算机主机电源是否都一样,电脑主机电源全模组和非模组有什么不同,到底该怎么选?...

原标题&#xff1a;电脑主机电源全模组和非模组有什么不同&#xff0c;到底该怎么选&#xff1f; 一台电脑能不能发挥它的极限性能&#xff0c;关键是要看电源。俗话说得好酒足饭饱好做事。一台电脑无论配置高低&#xff0c;都需要有充足的电力才能有高的性能动力。如果你配了一…

设置按下电源按钮时锁定计算机,WIN7定义电源按钮并启动密码保护里选项全灰...

2016-07-16 10:03赵飞虹 客户经理 试试下面的方法吧: 1. 按开始按钮,依次点击“控制面板”-〉“硬件和声音”-〉“电源选项”-〉“唤醒计算 机时需要密码”。 2. 在唤醒时的密码保护那一块,单击“更改当前不可用的设置”。 (进行下面的操作,需要拥有管理员权限;如果您的网…

电源管理总线 (PMBus)

TOC 1 介绍 1.1 简介 电源管理总线 &#xff08;PMBus&#xff09; 是系统管理总线 &#xff08;SMBus&#xff09; 的变体&#xff0c;旨在实现电源的数字管理。与SMBus一样&#xff0c;它是一种基于IC的相对慢速的双线通信协议。与这两个标准中的任何一个不同&#xff0c;…