// // xemu User Interface // // Copyright (C) 2020-2022 Matt Borgerson // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // #include "common.hh" #include "scene-manager.hh" #include "widgets.hh" #include "main-menu.hh" #include "font-manager.hh" #include "input-manager.hh" #include "viewport-manager.hh" #include "xemu-hud.h" #include "misc.hh" #include "gl-helpers.hh" #include "reporting.hh" #include "../xemu-input.h" #include "../xemu-notifications.h" #include "../xemu-settings.h" #include "../xemu-monitor.h" #include "../xemu-version.h" #include "../xemu-net.h" #include "../xemu-os-utils.h" #include "../xemu-xbe.h" MainMenuScene g_main_menu; MainMenuTabView::~MainMenuTabView() {} void MainMenuTabView::Draw() {} void MainMenuGeneralView::Draw() { #if defined(_WIN32) SectionTitle("Updates"); Toggle("Check for updates", &g_config.general.updates.check, "Check for updates whenever xemu is opened"); #endif #if defined(__x86_64__) SectionTitle("Performance"); Toggle("Hard FPU emulation", &g_config.perf.hard_fpu, "Use hardware-accelerated floating point emulation (requires restart)"); #endif // toggle("Cache shaders to disk", &g_config.perf.cache_shaders, // "Reduce stutter in games by caching previously generated shaders"); SectionTitle("Miscellaneous"); Toggle("Skip startup animation", &g_config.general.skip_boot_anim, "Skip the full Xbox boot animation sequence"); FilePicker("Screenshot output directory", &g_config.general.screenshot_dir, NULL, true); // toggle("Throttle DVD/HDD speeds", &g_config.general.throttle_io, // "Limit DVD/HDD throughput to approximate Xbox load times"); } void MainMenuInputView::Draw() { SectionTitle("Controllers"); ImGui::PushFont(g_font_mgr.m_menu_font_small); static int active = 0; // Output dimensions of texture float t_w = 512, t_h = 512; // Dimensions of (port+label)s float b_x = 0, b_x_stride = 100, b_y = 400; float b_w = 68, b_h = 81; // Dimensions of controller (rendered at origin) float controller_width = 477.0f; float controller_height = 395.0f; // Setup rendering to fbo for controller and port images controller_fbo->Target(); ImTextureID id = (ImTextureID)(intptr_t)controller_fbo->Texture(); // // Render buttons with icons of the Xbox style port sockets with // circular numbers above them. These buttons can be activated to // configure the associated port, like a tabbed interface. // ImVec4 color_active(0.50, 0.86, 0.54, 0.12); ImVec4 color_inactive(0, 0, 0, 0); // Begin a 4-column layout to render the ports ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, g_viewport_mgr.Scale(ImVec2(0, 12))); ImGui::Columns(4, "mixed", false); const int port_padding = 8; for (int i = 0; i < 4; i++) { bool is_selected = (i == active); bool port_is_bound = (xemu_input_get_bound(i) != NULL); // Set an X offset to center the image button within the column ImGui::SetCursorPosX( ImGui::GetCursorPosX() + (int)((ImGui::GetColumnWidth() - b_w * g_viewport_mgr.m_scale - 2 * port_padding * g_viewport_mgr.m_scale) / 2)); // We are using the same texture for all buttons, but ImageButton // uses the texture as a unique ID. Push a new ID now to resolve // the conflict. ImGui::PushID(i); float x = b_x+i*b_x_stride; ImGui::PushStyleColor(ImGuiCol_Button, is_selected ? color_active : color_inactive); bool activated = ImGui::ImageButton(id, ImVec2(b_w*g_viewport_mgr.m_scale,b_h*g_viewport_mgr.m_scale), ImVec2(x/t_w, (b_y+b_h)/t_h), ImVec2((x+b_w)/t_w, b_y/t_h), port_padding * g_viewport_mgr.m_scale); ImGui::PopStyleColor(); if (activated) { active = i; } uint32_t port_color = 0xafafafff; bool is_hovered = ImGui::IsItemHovered(); if (is_hovered) { port_color = 0xffffffff; } else if (is_selected || port_is_bound) { port_color = 0x81dc8a00; } RenderControllerPort(x, b_y, i, port_color); ImGui::PopID(); ImGui::NextColumn(); } ImGui::PopStyleVar(); // ItemSpacing ImGui::Columns(1); // // Render input device combo // // List available input devices const char *not_connected = "Not Connected"; ControllerState *bound_state = xemu_input_get_bound(active); // Get current controller name const char *name; if (bound_state == NULL) { name = not_connected; } else { name = bound_state->name; } ImGui::SetNextItemWidth(-FLT_MIN); if (ImGui::BeginCombo("###InputDevices", name, ImGuiComboFlags_NoArrowButton)) { // Handle "Not connected" bool is_selected = bound_state == NULL; if (ImGui::Selectable(not_connected, is_selected)) { xemu_input_bind(active, NULL, 1); bound_state = NULL; } if (is_selected) { ImGui::SetItemDefaultFocus(); } // Handle all available input devices ControllerState *iter; QTAILQ_FOREACH(iter, &available_controllers, entry) { is_selected = bound_state == iter; ImGui::PushID(iter); const char *selectable_label = iter->name; char buf[128]; if (iter->bound >= 0) { snprintf(buf, sizeof(buf), "%s (Port %d)", iter->name, iter->bound+1); selectable_label = buf; } if (ImGui::Selectable(selectable_label, is_selected)) { xemu_input_bind(active, iter, 1); bound_state = iter; } if (is_selected) { ImGui::SetItemDefaultFocus(); } ImGui::PopID(); } ImGui::EndCombo(); } DrawComboChevron(); ImGui::Columns(1); // // Add a separator between input selection and controller graphic // ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y / 2)); // // Render controller image // bool device_selected = false; if (bound_state) { device_selected = true; RenderController(0, 0, 0x81dc8a00, 0x0f0f0f00, bound_state); } else { static ControllerState state = { 0 }; RenderController(0, 0, 0x1f1f1f00, 0x0f0f0f00, &state); } ImVec2 cur = ImGui::GetCursorPos(); ImVec2 controller_display_size; if (ImGui::GetContentRegionMax().x < controller_width*g_viewport_mgr.m_scale) { controller_display_size.x = ImGui::GetContentRegionMax().x; controller_display_size.y = controller_display_size.x * controller_height / controller_width; } else { controller_display_size = ImVec2(controller_width * g_viewport_mgr.m_scale, controller_height * g_viewport_mgr.m_scale); } ImGui::SetCursorPosX( ImGui::GetCursorPosX() + (int)((ImGui::GetColumnWidth() - controller_display_size.x) / 2.0)); ImGui::Image(id, controller_display_size, ImVec2(0, controller_height/t_h), ImVec2(controller_width/t_w, 0)); ImVec2 pos = ImGui::GetCursorPos(); if (!device_selected) { const char *msg = "Please select an available input device"; ImVec2 dim = ImGui::CalcTextSize(msg); ImGui::SetCursorPosX(cur.x + (controller_display_size.x-dim.x)/2); ImGui::SetCursorPosY(cur.y + (controller_display_size.y-dim.y)/2); ImGui::Text("%s", msg); } controller_fbo->Restore(); ImGui::PopFont(); ImGui::SetCursorPos(pos); SectionTitle("Options"); Toggle("Auto-bind controllers", &g_config.input.auto_bind, "Bind newly connected controllers to any open port"); Toggle("Background controller input capture", &g_config.input.background_input_capture, "Capture even if window is unfocused (requires restart)"); } void MainMenuDisplayView::Draw() { SectionTitle("Quality"); int rendering_scale = nv2a_get_surface_scale_factor() - 1; if (ChevronCombo("Internal resolution scale", &rendering_scale, "1x\0" "2x\0" "3x\0" "4x\0" "5x\0" "6x\0" "7x\0" "8x\0" "9x\0" "10x\0", "Increase surface scaling factor for higher quality")) { nv2a_set_surface_scale_factor(rendering_scale+1); } SectionTitle("Window"); bool fs = xemu_is_fullscreen(); if (Toggle("Fullscreen", &fs, "Enable fullscreen now")) { xemu_toggle_fullscreen(); } Toggle("Fullscreen on startup", &g_config.display.window.fullscreen_on_startup, "Start xemu in fullscreen when opened"); if (ChevronCombo("Window size", &g_config.display.window.startup_size, "Last Used\0" "640x480\0" "1280x720\0" "1280x800\0" "1280x960\0" "1920x1080\0" "2560x1440\0" "2560x1600\0" "2560x1920\0" "3840x2160\0", "Select preferred startup window size")) { } Toggle("Vertical refresh sync", &g_config.display.window.vsync, "Sync to screen vertical refresh to reduce tearing artifacts"); SectionTitle("Interface"); Toggle("Show main menu bar", &g_config.display.ui.show_menubar, "Show main menu bar when mouse is activated"); int ui_scale_idx; if (g_config.display.ui.auto_scale) { ui_scale_idx = 0; } else { ui_scale_idx = g_config.display.ui.scale; if (ui_scale_idx < 0) ui_scale_idx = 0; else if (ui_scale_idx > 2) ui_scale_idx = 2; } if (ChevronCombo("UI scale", &ui_scale_idx, "Auto\0" "1x\0" "2x\0", "Interface element scale")) { if (ui_scale_idx == 0) { g_config.display.ui.auto_scale = true; } else { g_config.display.ui.auto_scale = false; g_config.display.ui.scale = ui_scale_idx; } } Toggle("Animations", &g_config.display.ui.use_animations, "Enable xemu user interface animations"); ChevronCombo("Display mode", &g_config.display.ui.fit, "Center\0" "Scale\0" "Scale (Widescreen 16:9)\0" "Scale (4:3)\0" "Stretch\0", "Select how the framebuffer should fit or scale into the window"); } void MainMenuAudioView::Draw() { SectionTitle("Volume"); char buf[32]; snprintf(buf, sizeof(buf), "Limit output volume (%d%%)", (int)(g_config.audio.volume_limit * 100)); Slider("Output volume limit", &g_config.audio.volume_limit, buf); SectionTitle("Quality"); Toggle("Real-time DSP processing", &g_config.audio.use_dsp, "Enable improved audio accuracy (experimental)"); } NetworkInterface::NetworkInterface(pcap_if_t *pcap_desc, char *_friendlyname) { m_pcap_name = pcap_desc->name; m_description = pcap_desc->description ?: pcap_desc->name; if (_friendlyname) { char *tmp = g_strdup_printf("%s (%s)", _friendlyname, m_description.c_str()); m_friendly_name = tmp; g_free((gpointer)tmp); } else { m_friendly_name = m_description; } } NetworkInterfaceManager::NetworkInterfaceManager() { m_current_iface = NULL; m_failed_to_load_lib = false; } void NetworkInterfaceManager::Refresh(void) { pcap_if_t *alldevs, *iter; char err[PCAP_ERRBUF_SIZE]; if (xemu_net_is_enabled()) { return; } #if defined(_WIN32) if (pcap_load_library()) { m_failed_to_load_lib = true; return; } #endif m_ifaces.clear(); m_current_iface = NULL; if (pcap_findalldevs(&alldevs, err)) { return; } for (iter=alldevs; iter != NULL; iter=iter->next) { #if defined(_WIN32) char *friendly_name = get_windows_interface_friendly_name(iter->name); m_ifaces.emplace_back(new NetworkInterface(iter, friendly_name)); if (friendly_name) { g_free((gpointer)friendly_name); } #else m_ifaces.emplace_back(new NetworkInterface(iter)); #endif if (!strcmp(g_config.net.pcap.netif, iter->name)) { m_current_iface = m_ifaces.back().get(); } } pcap_freealldevs(alldevs); } void NetworkInterfaceManager::Select(NetworkInterface &iface) { m_current_iface = &iface; xemu_settings_set_string(&g_config.net.pcap.netif, iface.m_pcap_name.c_str()); } bool NetworkInterfaceManager::IsCurrent(NetworkInterface &iface) { return &iface == m_current_iface; } MainMenuNetworkView::MainMenuNetworkView() { should_refresh = true; } void MainMenuNetworkView::Draw() { SectionTitle("Adapter"); bool enabled = xemu_net_is_enabled(); g_config.net.enable = enabled; if (Toggle("Enable", &g_config.net.enable, enabled ? "Virtual network connected (disable to change network " "settings)" : "Connect virtual network cable to machine")) { if (enabled) { xemu_net_disable(); } else { xemu_net_enable(); } } bool appearing = ImGui::IsWindowAppearing(); if (enabled) ImGui::BeginDisabled(); if (ChevronCombo( "Attached to", &g_config.net.backend, "NAT\0" "UDP Tunnel\0" "Bridged Adapter\0", "Controls what the virtual network controller interfaces with")) { appearing = true; } SectionTitle("Options"); switch (g_config.net.backend) { case CONFIG_NET_BACKEND_PCAP: DrawPcapOptions(appearing); break; case CONFIG_NET_BACKEND_NAT: DrawNatOptions(appearing); break; case CONFIG_NET_BACKEND_UDP: DrawUdpOptions(appearing); break; default: break; } if (enabled) ImGui::EndDisabled(); } void MainMenuNetworkView::DrawPcapOptions(bool appearing) { if (iface_mgr.get() == nullptr) { iface_mgr.reset(new NetworkInterfaceManager()); iface_mgr->Refresh(); } if (iface_mgr->m_failed_to_load_lib) { #if defined(_WIN32) const char *msg = "npcap library could not be loaded.\n" "To use this backend, please install npcap."; ImGui::Text("%s", msg); ImGui::Dummy(ImVec2(0,10*g_viewport_mgr.m_scale)); ImGui::SetCursorPosX((ImGui::GetWindowWidth()-120*g_viewport_mgr.m_scale)/2); if (ImGui::Button("Install npcap", ImVec2(120*g_viewport_mgr.m_scale, 0))) { xemu_open_web_browser("https://nmap.org/npcap/"); } #endif } else { const char *selected_display_name = (iface_mgr->m_current_iface ? iface_mgr->m_current_iface->m_friendly_name.c_str() : g_config.net.pcap.netif); float combo_width = ImGui::GetColumnWidth(); float combo_size_ratio = 0.5; combo_width *= combo_size_ratio; PrepareComboTitleDescription("Network interface", "Host network interface to bridge with", combo_size_ratio); ImGui::SetNextItemWidth(combo_width); ImGui::PushFont(g_font_mgr.m_menu_font_small); if (ImGui::BeginCombo("###network_iface", selected_display_name, ImGuiComboFlags_NoArrowButton)) { if (should_refresh) { iface_mgr->Refresh(); should_refresh = false; } int i = 0; for (auto &iface : iface_mgr->m_ifaces) { bool is_selected = iface_mgr->IsCurrent((*iface)); ImGui::PushID(i++); if (ImGui::Selectable(iface->m_friendly_name.c_str(), is_selected)) { iface_mgr->Select((*iface)); } if (is_selected) ImGui::SetItemDefaultFocus(); ImGui::PopID(); } ImGui::EndCombo(); } else { should_refresh = true; } ImGui::PopFont(); DrawComboChevron(); } } void MainMenuNetworkView::DrawNatOptions(bool appearing) { static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; WidgetTitleDescriptionItem( "Port Forwarding", "Configure xemu to forward connections to guest on these ports"); float p = ImGui::GetFrameHeight() * 0.3; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(p, p)); if (ImGui::BeginTable("port_forward_tbl", 4, flags)) { ImGui::TableSetupColumn("Host Port"); ImGui::TableSetupColumn("Guest Port"); ImGui::TableSetupColumn("Protocol"); ImGui::TableSetupColumn("Action"); ImGui::TableHeadersRow(); for (unsigned int row = 0; row < g_config.net.nat.forward_ports_count; row++) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text("%d", g_config.net.nat.forward_ports[row].host); ImGui::TableSetColumnIndex(1); ImGui::Text("%d", g_config.net.nat.forward_ports[row].guest); ImGui::TableSetColumnIndex(2); switch (g_config.net.nat.forward_ports[row].protocol) { case CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_TCP: ImGui::TextUnformatted("TCP"); break; case CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_UDP: ImGui::TextUnformatted("UDP"); break; default: assert(0); } ImGui::TableSetColumnIndex(3); ImGui::PushID(row); if (ImGui::Button("Remove")) { remove_net_nat_forward_ports(row); } ImGui::PopID(); } ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); static char buf[8] = {"1234"}; ImGui::SetNextItemWidth(ImGui::GetColumnWidth()); ImGui::InputText("###hostport", buf, sizeof(buf)); ImGui::TableSetColumnIndex(1); static char buf2[8] = {"1234"}; ImGui::SetNextItemWidth(ImGui::GetColumnWidth()); ImGui::InputText("###guestport", buf2, sizeof(buf2)); ImGui::TableSetColumnIndex(2); static CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol = CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_TCP; assert(sizeof(protocol) >= sizeof(int)); ImGui::SetNextItemWidth(ImGui::GetColumnWidth()); ImGui::Combo("###protocol", &protocol, "TCP\0UDP\0"); ImGui::TableSetColumnIndex(3); if (ImGui::Button("Add")) { int host, guest; if (sscanf(buf, "%d", &host) == 1 && sscanf(buf2, "%d", &guest) == 1) { add_net_nat_forward_ports(host, guest, protocol); } } ImGui::EndTable(); } ImGui::PopStyleVar(); } void MainMenuNetworkView::DrawUdpOptions(bool appearing) { if (appearing) { strncpy(remote_addr, g_config.net.udp.remote_addr, sizeof(remote_addr)-1); strncpy(local_addr, g_config.net.udp.bind_addr, sizeof(local_addr)-1); } float size_ratio = 0.5; float width = ImGui::GetColumnWidth() * size_ratio; ImGui::PushFont(g_font_mgr.m_menu_font_small); PrepareComboTitleDescription( "Remote Address", "Destination addr:port to forward packets to (1.2.3.4:9968)", size_ratio); ImGui::SetNextItemWidth(width); if (ImGui::InputText("###remote_host", remote_addr, sizeof(remote_addr))) { xemu_settings_set_string(&g_config.net.udp.remote_addr, remote_addr); } PrepareComboTitleDescription( "Bind Address", "Local addr:port to receive packets on (0.0.0.0:9968)", size_ratio); ImGui::SetNextItemWidth(width); if (ImGui::InputText("###local_host", local_addr, sizeof(local_addr))) { xemu_settings_set_string(&g_config.net.udp.bind_addr, local_addr); } ImGui::PopFont(); } #if 0 class MainMenuSnapshotsView : public virtual MainMenuTabView { protected: GLuint screenshot; public: void initScreenshot() { if (screenshot == 0) { glGenTextures(1, &screenshot); int w, h, n; stbi_set_flip_vertically_on_load(0); unsigned char *data = stbi_load("./data/screenshot.png", &w, &h, &n, 4); assert(n == 4); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, screenshot); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); stbi_image_free(data); } } void snapshotBigButton(const char *name, const char *title_name, GLuint screenshot) { ImGuiStyle &style = ImGui::GetStyle(); ImVec2 pos = ImGui::GetCursorPos(); ImDrawList *draw_list = ImGui::GetWindowDrawList(); ImGui::PushFont(g_font_mgr.m_menuFont); const char *icon = ICON_FA_CIRCLE_XMARK; ImVec2 ts_icon = ImGui::CalcTextSize(icon); ts_icon.x += 2*style.FramePadding.x; ImGui::PopFont(); ImGui::PushFont(g_font_mgr.m_menuFontSmall); ImVec2 ts_sub = ImGui::CalcTextSize(name); ImGui::PopFont(); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.scale(ImVec2(5, 5))); ImGui::PushFont(g_font_mgr.m_menuFontMedium); ImVec2 ts_title = ImGui::CalcTextSize(name); ImVec2 thumbnail_size = g_viewport_mgr.scale(ImVec2(160, 120)); ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y); ImVec2 text_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y); ImVec2 subtext_pos(text_pos.x, text_pos.y + ts_title.y + style.FramePadding.x); ImGui::Button("###button", ImVec2(ImGui::GetContentRegionAvail().x, fmax(thumbnail_size.y + style.FramePadding.y * 2, ts_title.y + ts_sub.y + style.FramePadding.y * 3))); ImGui::PopFont(); const ImVec2 sz = ImGui::GetItemRectSize(); const ImVec2 p0 = ImGui::GetItemRectMin(); const ImVec2 p1 = ImGui::GetItemRectMax(); ts_icon.y = sz.y; // Snapshot thumbnail ImGui::SetItemAllowOverlap(); ImGui::SameLine(); ImGui::SetCursorPosX(pos.x + thumbnail_pos.x); ImGui::SetCursorPosY(pos.y + thumbnail_pos.y); ImGui::Image((ImTextureID)screenshot, thumbnail_size, ImVec2(0,0), ImVec2(1,1)); draw_list->PushClipRect(p0, p1, true); // Snapshot title ImGui::PushFont(g_font_mgr.m_menuFontMedium); draw_list->AddText(ImVec2(p0.x + text_pos.x, p0.y + text_pos.y), IM_COL32(255, 255, 255, 255), name); ImGui::PopFont(); // Snapshot subtitle ImGui::PushFont(g_font_mgr.m_menuFontSmall); draw_list->AddText(ImVec2(p0.x + subtext_pos.x, p0.y + subtext_pos.y), IM_COL32(255, 255, 255, 200), title_name); ImGui::PopFont(); draw_list->PopClipRect(); // Delete button ImGui::SameLine(); ImGui::SetCursorPosY(pos.y); ImGui::SetCursorPosX(pos.x + sz.x - ts_icon.x); ImGui::PushFont(g_font_mgr.m_menuFont); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); ImGui::Button(icon, ts_icon); ImGui::PopStyleColor(); ImGui::PopStyleVar(1); ImGui::PopFont(); ImGui::PopStyleVar(2); } void Draw() { initScreenshot(); for (int i = 0; i < 15; i++) { char buf[64]; snprintf(buf, sizeof(buf), "%s", "Apr 9 2022 19:44"); ImGui::PushID(i); snapshotBigButton(buf, "Halo: Combat Evolved", screenshot); ImGui::PopID(); } } }; #endif MainMenuSystemView::MainMenuSystemView() : m_dirty(false) { } void MainMenuSystemView::Draw() { const char *rom_file_filters = ".bin Files\0*.bin\0.rom Files\0*.rom\0All Files\0*.*\0"; const char *qcow_file_filters = ".qcow2 Files\0*.qcow2\0All Files\0*.*\0"; if (m_dirty) { ImGui::TextColored(ImVec4(1,0,0,1), "Application restart required to apply settings"); } SectionTitle("System Configuration"); if (ChevronCombo( "System Memory", &g_config.sys.mem_limit, "64 MiB (Default)\0""128 MiB\0", "Increase to 128 MiB for debug or homebrew applications")) { m_dirty = true; } if (ChevronCombo( "AV Pack", &g_config.sys.avpack, "SCART\0HDTV (Default)\0VGA\0RFU\0S-Video\0Composite\0None\0", "Select the attached AV pack")) { m_dirty = true; } SectionTitle("Files"); if (FilePicker("MCPX Boot ROM", &g_config.sys.files.bootrom_path, rom_file_filters)) { m_dirty = true; } if (FilePicker("Flash ROM (BIOS)", &g_config.sys.files.flashrom_path, rom_file_filters)) { m_dirty = true; } if (FilePicker("Hard Disk", &g_config.sys.files.hdd_path, qcow_file_filters)) { m_dirty = true; } if (FilePicker("EEPROM", &g_config.sys.files.eeprom_path, rom_file_filters)) { m_dirty = true; } } void MainMenuAboutView::Draw() { static const char *build_info_text = NULL; if (build_info_text == NULL) { build_info_text = g_strdup_printf( "Version: %s\nBranch: %s\nCommit: %s\nDate: %s", xemu_version, xemu_branch, xemu_commit, xemu_date); } static const char *sys_info_text = NULL; if (sys_info_text == NULL) { const char *gl_shader_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); const char *gl_version = (const char*)glGetString(GL_VERSION); const char *gl_renderer = (const char*)glGetString(GL_RENDERER); const char *gl_vendor = (const char*)glGetString(GL_VENDOR); sys_info_text = g_strdup_printf( "CPU: %s\nOS Platform: %s\nOS Version: %s\nManufacturer: %s\n" "GPU Model: %s\nDriver: %s\nShader: %s", xemu_get_cpu_info(), xemu_get_os_platform(), xemu_get_os_info(), gl_vendor, gl_renderer, gl_version, gl_shader_version); } Logo(); SectionTitle("Build Information"); ImGui::PushFont(g_font_mgr.m_fixed_width_font); ImGui::InputTextMultiline("##build_info", (char *)build_info_text, strlen(build_info_text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 5), ImGuiInputTextFlags_ReadOnly); ImGui::PopFont(); SectionTitle("System Information"); ImGui::PushFont(g_font_mgr.m_fixed_width_font); ImGui::InputTextMultiline("###systeminformation", (char *)sys_info_text, strlen(sys_info_text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 8), ImGuiInputTextFlags_ReadOnly); ImGui::PopFont(); SectionTitle("Community"); ImGui::Text("Visit"); ImGui::SameLine(); if (ImGui::SmallButton("https://xemu.app")) { xemu_open_web_browser("https://xemu.app"); } ImGui::SameLine(); ImGui::Text("for more information"); } MainMenuTabButton::MainMenuTabButton(std::string text, std::string icon) : m_icon(icon), m_text(text) { } bool MainMenuTabButton::Draw(bool selected) { ImGuiStyle &style = ImGui::GetStyle(); ImU32 col = selected ? ImGui::GetColorU32(style.Colors[ImGuiCol_ButtonHovered]) : IM_COL32(0, 0, 0, 0); ImGui::PushStyleColor(ImGuiCol_Button, col); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, selected ? col : IM_COL32(32, 32, 32, 255)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, selected ? col : IM_COL32(32, 32, 32, 255)); int p = ImGui::GetTextLineHeight() * 0.5; ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(p, p)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0.5)); ImGui::PushFont(g_font_mgr.m_menu_font); ImVec2 button_size = ImVec2(-FLT_MIN, 0); auto text = string_format("%s %s", m_icon.c_str(), m_text.c_str()); ImGui::PushID(this); bool status = ImGui::Button(text.c_str(), button_size); ImGui::PopID(); ImGui::PopFont(); ImGui::PopStyleVar(3); ImGui::PopStyleColor(3); return status; } MainMenuScene::MainMenuScene() : m_animation(0.12, 0.12), m_general_button("General", ICON_FA_GEARS), m_input_button("Input", ICON_FA_GAMEPAD), m_display_button("Display", ICON_FA_TV), m_audio_button("Audio", ICON_FA_VOLUME_HIGH), m_network_button("Network", ICON_FA_NETWORK_WIRED), // m_snapshots_button("Snapshots", ICON_FA_CLOCK_ROTATE_LEFT), m_system_button("System", ICON_FA_MICROCHIP), m_about_button("About", ICON_FA_CIRCLE_INFO) { m_had_focus_last_frame = false; m_focus_view = false; m_tabs.push_back(&m_general_button); m_tabs.push_back(&m_input_button); m_tabs.push_back(&m_display_button); m_tabs.push_back(&m_audio_button); m_tabs.push_back(&m_network_button); // m_tabs.push_back(&m_snapshots_button); m_tabs.push_back(&m_system_button); m_tabs.push_back(&m_about_button); m_views.push_back(&m_general_view); m_views.push_back(&m_input_view); m_views.push_back(&m_display_view); m_views.push_back(&m_audio_view); m_views.push_back(&m_network_view); // m_views.push_back(&m_snapshots_view); m_views.push_back(&m_system_view); m_views.push_back(&m_about_view); m_current_view_index = 0; m_next_view_index = m_current_view_index; } void MainMenuScene::ShowGeneral() { SetNextViewIndexWithFocus(0); } void MainMenuScene::ShowInput() { SetNextViewIndexWithFocus(1); } void MainMenuScene::ShowDisplay() { SetNextViewIndexWithFocus(2); } void MainMenuScene::ShowAudio() { SetNextViewIndexWithFocus(3); } void MainMenuScene::ShowNetwork() { SetNextViewIndexWithFocus(4); } // void MainMenuScene::showSnapshots() { SetNextViewIndexWithFocus(5); } void MainMenuScene::ShowSystem() { SetNextViewIndexWithFocus(5); } void MainMenuScene::ShowAbout() { SetNextViewIndexWithFocus(6); } void MainMenuScene::SetNextViewIndexWithFocus(int i) { m_focus_view = true; SetNextViewIndex(i); if (!g_scene_mgr.IsDisplayingScene()) { g_scene_mgr.PushScene(*this); } } void MainMenuScene::Show() { m_background.Show(); m_nav_control_view.Show(); m_animation.EaseIn(); } void MainMenuScene::Hide() { m_background.Hide(); m_nav_control_view.Hide(); m_animation.EaseOut(); } bool MainMenuScene::IsAnimating() { return m_animation.IsAnimating(); } void MainMenuScene::SetNextViewIndex(int i) { m_next_view_index = i % m_tabs.size(); g_config.general.last_viewed_menu_index = i; } void MainMenuScene::HandleInput() { bool nofocus = !ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow); bool focus = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows | ImGuiFocusedFlags_NoPopupHierarchy); // XXX: Ensure we have focus for two frames. If a user cancels a popup window, we do not want to cancel main // window as well. if (nofocus || (focus && m_had_focus_last_frame && ImGui::IsNavInputTest(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed))) { Hide(); return; } if (focus && m_had_focus_last_frame) { if (ImGui::IsKeyPressed(ImGuiKey_GamepadL1)) { SetNextViewIndex((m_current_view_index + m_tabs.size() - 1) % m_tabs.size()); } if (ImGui::IsKeyPressed(ImGuiKey_GamepadR1)) { SetNextViewIndex((m_current_view_index + 1) % m_tabs.size()); } } m_had_focus_last_frame = focus; } bool MainMenuScene::Draw() { m_animation.Step(); m_background.Draw(); m_nav_control_view.Draw(); ImGuiIO &io = ImGui::GetIO(); float t = m_animation.GetSinInterpolatedValue(); float window_alpha = t; ImGui::PushStyleVar(ImGuiStyleVar_Alpha, window_alpha); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); ImVec4 extents = g_viewport_mgr.GetExtents(); ImVec2 window_pos = ImVec2(io.DisplaySize.x / 2, extents.y); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, ImVec2(0.5, 0)); ImVec2 max_size = g_viewport_mgr.Scale(ImVec2(800, 0)); float x = fmin(io.DisplaySize.x - extents.x - extents.z, max_size.x); float y = io.DisplaySize.y - extents.y - extents.w; ImGui::SetNextWindowSize(ImVec2(x, y)); if (ImGui::Begin("###MainWindow", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { // // Nav menu // float width = ImGui::GetWindowWidth(); float nav_width = width * 0.3; float content_width = width - nav_width; ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(26,26,26,255)); ImGui::BeginChild("###MainWindowNav", ImVec2(nav_width, -1), true, ImGuiWindowFlags_NavFlattened); bool move_focus_to_tab = false; if (m_current_view_index != m_next_view_index) { m_current_view_index = m_next_view_index; if (!m_focus_view) { move_focus_to_tab = true; } } int i = 0; for (auto &button : m_tabs) { if (move_focus_to_tab && i == m_current_view_index) { ImGui::SetKeyboardFocusHere(); move_focus_to_tab = false; } if (button->Draw(i == m_current_view_index)) { SetNextViewIndex(i); } if (i == m_current_view_index) { ImGui::SetItemDefaultFocus(); } i++; } ImGui::EndChild(); ImGui::PopStyleColor(); // // Content // ImGui::SameLine(); int s = ImGui::GetTextLineHeight() * 0.75; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(s, s)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(s, s)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6*g_viewport_mgr.m_scale); ImGui::PushID(m_current_view_index); ImGui::BeginChild("###MainWindowContent", ImVec2(content_width, -1), true, ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NavFlattened); if (!g_input_mgr.IsNavigatingWithController()) { // Close button ImGui::PushFont(g_font_mgr.m_menu_font); ImGuiStyle &style = ImGui::GetStyle(); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); ImVec2 pos = ImGui::GetCursorPos(); ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - style.FramePadding.x * 2.0f - ImGui::GetTextLineHeight()); if (ImGui::Button(ICON_FA_XMARK)) { Hide(); } ImGui::SetCursorPos(pos); ImGui::PopStyleColor(2); ImGui::PopFont(); } ImGui::PushFont(g_font_mgr.m_default_font); if (m_focus_view) { ImGui::SetKeyboardFocusHere(); m_focus_view = false; } m_views[m_current_view_index]->Draw(); ImGui::PopFont(); ImGui::EndChild(); ImGui::PopID(); ImGui::PopStyleVar(3); HandleInput(); } ImGui::End(); ImGui::PopStyleVar(5); return !m_animation.IsComplete(); }