diff --git a/ui/xemu-hud.cc b/ui/xemu-hud.cc index 1b033420a8..c9ed338737 100644 --- a/ui/xemu-hud.cc +++ b/ui/xemu-hud.cc @@ -97,33 +97,23 @@ static void ShowAboutWindow(bool* p_open); bool show_demo_window = false; -void xemu_hud_init(SDL_Window* window, void* sdl_gl_context) +float ui_scale = 1.0; +bool trigger_style_update = true; + +static void init_style(void) { - xemu_monitor_init(); - - initialize_custom_ui_rendering(); - - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; - io.IniFilename = NULL; - // Load fonts - io.Fonts->AddFontFromFileTTF(xemu_get_resource_path("Roboto-Medium.ttf"), 16); - fixed_width_font = io.Fonts->AddFontDefault(); + io.Fonts->Clear(); + io.Fonts->AddFontFromFileTTF(xemu_get_resource_path("Roboto-Medium.ttf"), 16*ui_scale); + ImFontConfig font_cfg = ImFontConfig(); + font_cfg.OversampleH = font_cfg.OversampleV = 1; + font_cfg.PixelSnapH = true; + font_cfg.SizePixels = 13.0f*ui_scale; + fixed_width_font = io.Fonts->AddFontDefault(&font_cfg); + ImGui_ImplOpenGL3_CreateFontsTexture(); - // Setup Platform/Renderer bindings - ImGui_ImplSDL2_InitForOpenGL(window, sdl_gl_context); - const char *glsl_version = "#version 150"; - ImGui_ImplOpenGL3_Init(glsl_version); - - // Set default theme, override - ImGui::StyleColorsDark(); - - ImGuiStyle& style = ImGui::GetStyle(); + ImGuiStyle style; style.FrameRounding = 8.0; style.GrabRounding = 12.0; style.PopupRounding = 5.0; @@ -134,6 +124,11 @@ void xemu_hud_init(SDL_Window* window, void* sdl_gl_context) style.PopupBorderSize = 0; style.FrameBorderSize = 0; style.TabBorderSize = 0; + ImGui::GetStyle() = style; + ImGui::GetStyle().ScaleAllSizes(ui_scale); + + // Set default theme, override + ImGui::StyleColorsDark(); ImVec4* colors = ImGui::GetStyle().Colors; colors[ImGuiCol_Text] = ImVec4(0.86f, 0.93f, 0.89f, 0.78f); @@ -184,6 +179,26 @@ void xemu_hud_init(SDL_Window* window, void* sdl_gl_context) colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.16f, 0.16f, 0.16f, 0.73f); +} + +void xemu_hud_init(SDL_Window* window, void* sdl_gl_context) +{ + xemu_monitor_init(); + + initialize_custom_ui_rendering(); + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + io.IniFilename = NULL; + + // Setup Platform/Renderer bindings + ImGui_ImplSDL2_InitForOpenGL(window, sdl_gl_context); + const char *glsl_version = "#version 150"; + ImGui_ImplOpenGL3_Init(glsl_version); show_first_boot_window = xemu_settings_did_fail_to_load(); if (show_first_boot_window) { @@ -249,7 +264,15 @@ static void ShowMainMenu() if (ImGui::BeginMenu("View")) { - if (ImGui::Combo("Scaling Mode", &scaling_mode, "Center\0Scale\0Stretch\0\0")) { + int ui_scale_combo = ui_scale - 1.0; + if (ui_scale_combo < 0) ui_scale_combo = 0; + if (ui_scale_combo > 1) ui_scale_combo = 1; + if (ImGui::Combo("UI Scale", &ui_scale_combo, "1x\0" "2x\0")) { + ui_scale = ui_scale_combo + 1; + trigger_style_update = true; + } + + if (ImGui::Combo("Scaling Mode", &scaling_mode, "Center\0Scale\0Stretch\0")) { xemu_settings_set_enum(XEMU_SETTINGS_DISPLAY_SCALE, scaling_mode); xemu_settings_save(); } @@ -257,6 +280,7 @@ static void ShowMainMenu() if (ImGui::MenuItem("Fullscreen", NULL, xemu_is_fullscreen(), true)) { xemu_toggle_fullscreen(); } + ImGui::EndMenu(); } if (ImGui::BeginMenu("Debug")) @@ -272,11 +296,11 @@ static void ShowMainMenu() ImGui::EndMenu(); } - ImGui::SetCursorPosX(ImGui::GetWindowWidth()-100.0); + ImGui::SetCursorPosX(ImGui::GetWindowWidth()-100.0*ui_scale); extern float fps; ImGui::Text("%.3f", fps); ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetWindowWidth()-300.0); + ImGui::SetCursorPosX(ImGui::GetWindowWidth()-200.0*ui_scale); ImGui::Text("%.3f", 1000.0/fps); main_menu_height = ImGui::GetWindowHeight(); @@ -418,6 +442,11 @@ void xemu_hud_render(SDL_Window *window) // to navigate the HUD xemu_input_set_test_mode(controller_focus_capture); + if (trigger_style_update) { + init_style(); + trigger_style_update = false; + } + // Start the Dear ImGui frame ImGui_ImplOpenGL3_NewFrame(); @@ -531,7 +560,6 @@ void xemu_hud_render(SDL_Window *window) ImGui::EndPopup(); } - // Rendering ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } @@ -573,7 +601,7 @@ struct MonitorConsole void Draw(const char* title, bool* p_open) { - ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(520*ui_scale, 600*ui_scale), ImGuiCond_FirstUseEver); if (!ImGui::Begin(title, p_open)) { ImGui::End(); @@ -696,7 +724,7 @@ struct InputWindow void Draw(const char* title, bool* p_open) { - ImGui::SetNextWindowSize(ImVec2(500,620), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(500*ui_scale,620*ui_scale), ImGuiCond_Appearing); // Remove window X padding for this window to easily center stuff ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0,ImGui::GetStyle().WindowPadding.y)); @@ -738,7 +766,7 @@ struct InputWindow 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-2*port_padding)/2)); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+(int)((ImGui::GetColumnWidth()-b_w*ui_scale-2*port_padding*ui_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 @@ -747,7 +775,7 @@ struct InputWindow float x = b_x+i*b_x_stride; ImGui::PushStyleColor(ImGuiCol_Button, is_currently_selected ? color_active : color_inactive); bool activated = ImGui::ImageButton(id, - ImVec2(b_w,b_h), + ImVec2(b_w*ui_scale,b_h*ui_scale), ImVec2(x/t_w, (b_y+b_h)/t_h), ImVec2((x+b_w)/t_w, b_y/t_h), port_padding); @@ -778,12 +806,12 @@ struct InputWindow // // Center the combo above the controller with the same width - ImGui::SetCursorPosX(ImGui::GetCursorPosX()+(int)((ImGui::GetColumnWidth()-controller_width)/2.0)); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+(int)((ImGui::GetColumnWidth()-controller_width*ui_scale)/2.0)); // Note: SetNextItemWidth applies only to the combo element, but not the // associated label which follows, so scale back a bit to make space for // the label. - ImGui::SetNextItemWidth(controller_width*0.75); + ImGui::SetNextItemWidth(controller_width*0.75*ui_scale); // List available input devices const char *not_connected = "Not Connected"; @@ -857,9 +885,9 @@ struct InputWindow // update_sdl_controller_state(&state); // update_sdl_kbd_controller_state(&state); ImVec2 cur = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX()+(int)((ImGui::GetColumnWidth()-controller_width)/2.0)); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+(int)((ImGui::GetColumnWidth()-controller_width*ui_scale)/2.0)); ImGui::Image(id, - ImVec2(controller_width, controller_height), + ImVec2(controller_width*ui_scale, controller_height*ui_scale), ImVec2(0, controller_height/t_h), ImVec2(controller_width/t_w, 0)); @@ -867,8 +895,8 @@ struct InputWindow // ImGui::SameLine(); const char *msg = "Please select an available input device"; ImVec2 dim = ImGui::CalcTextSize(msg); - ImGui::SetCursorPosX(cur.x + (controller_width-dim.x)/2); - ImGui::SetCursorPosY(cur.y +(controller_height-dim.y)/2); + ImGui::SetCursorPosX(cur.x + (controller_width*ui_scale-dim.x)/2); + ImGui::SetCursorPosY(cur.y + (controller_height*ui_scale-dim.y)/2); ImGui::Text("%s", msg); ImGui::SameLine(); } @@ -978,7 +1006,7 @@ struct SettingsWindow dirty = true; } ImGui::SameLine(); - if (ImGui::Button("Browse...", ImVec2(100, 0))) { + if (ImGui::Button("Browse...", ImVec2(100*ui_scale, 0))) { const char *selected = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, buf, NULL); if ((selected != NULL) && (strcmp(buf, selected) != 0)) { strncpy(buf, selected, len-1); @@ -990,7 +1018,7 @@ struct SettingsWindow void Draw(const char* title, bool* p_open) { - ImGui::SetNextWindowSize(ImVec2(550, 300), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(550*ui_scale, 300*ui_scale), ImGuiCond_Appearing); if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) { ImGui::End(); @@ -1010,7 +1038,7 @@ struct SettingsWindow ImGui::Text("Flash (BIOS) File"); ImGui::NextColumn(); - float picker_width = ImGui::GetColumnWidth()-120; + float picker_width = ImGui::GetColumnWidth()-120*ui_scale; ImGui::SetNextItemWidth(picker_width); FilePicker("###Flash", flash_path, MAX_STRING_LEN, rom_file_filters); ImGui::NextColumn(); @@ -1042,7 +1070,7 @@ struct SettingsWindow ImGui::Text("System Memory"); ImGui::NextColumn(); ImGui::SetNextItemWidth(ImGui::GetColumnWidth()*0.5); - if (ImGui::Combo("###mem", &memory_idx, "64 MiB\0" "128 MiB\0" "\0")) { + if (ImGui::Combo("###mem", &memory_idx, "64 MiB\0" "128 MiB\0")) { dirty = true; } ImGui::NextColumn(); @@ -1056,7 +1084,7 @@ struct SettingsWindow ImGui::Columns(1); - ImGui::SetCursorPosY(ImGui::GetWindowHeight()-(10+20)); + ImGui::SetCursorPosY(ImGui::GetWindowHeight()-(10+20)*ui_scale); if (dirty) { ImGui::Text("Warning: Unsaved changes!"); ImGui::SameLine(); @@ -1065,11 +1093,11 @@ struct SettingsWindow ImGui::SameLine(); } - ImGui::SetCursorPosY(ImGui::GetWindowHeight()-(10+25)); - ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+10)); + ImGui::SetCursorPosY(ImGui::GetWindowHeight()-(10+25)*ui_scale); + ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+10)*ui_scale); ImGui::SetItemDefaultFocus(); - if (ImGui::Button("Save", ImVec2(120, 0))) { + if (ImGui::Button("Save", ImVec2(120*ui_scale, 0))) { Save(); dirty = false; pending_restart = true; @@ -1105,7 +1133,7 @@ struct AboutWindow void Draw(const char* title, bool* p_open) { - ImGui::SetNextWindowSize(ImVec2(400, 350), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(400*ui_scale, 350*ui_scale), ImGuiCond_Appearing); if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) { ImGui::End(); @@ -1118,15 +1146,15 @@ struct AboutWindow } uint32_t now = SDL_GetTicks() - time_start; - ImGui::SetCursorPosY(ImGui::GetCursorPosY()-50); - ImGui::SetCursorPosX((ImGui::GetWindowWidth()-256)/2); + ImGui::SetCursorPosY(ImGui::GetCursorPosY()-50*ui_scale); + ImGui::SetCursorPosX((ImGui::GetWindowWidth()-256*ui_scale)/2); ImTextureID id = (ImTextureID)render_to_fbo(logo_fbo); float t_w = 256.0; float t_h = 256.0; float x_off = 0; ImGui::Image(id, - ImVec2(t_w-x_off, t_h), + ImVec2((t_w-x_off)*ui_scale, t_h*ui_scale), ImVec2(x_off/t_w, t_h/t_h), ImVec2(t_w/t_w, 0)); if (ImGui::IsItemClicked()) { @@ -1134,21 +1162,21 @@ struct AboutWindow } render_logo(now, 0x42e335ff, 0x42e335ff, 0x00000000); render_to_default_fb(); - ImGui::SetCursorPosX(10); + ImGui::SetCursorPosX(10*ui_scale); - ImGui::SetCursorPosY(ImGui::GetCursorPosY()-100); + ImGui::SetCursorPosY(ImGui::GetCursorPosY()-100*ui_scale); ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(xemu_version).x)/2); ImGui::Text("%s", xemu_version); - ImGui::SetCursorPosX(10); - ImGui::Dummy(ImVec2(0,35)); + ImGui::SetCursorPosX(10*ui_scale); + ImGui::Dummy(ImVec2(0,35*ui_scale)); const char *msg = "Visit https://xemu.app for more information"; ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2); ImGui::Text("%s", msg); - ImGui::Dummy(ImVec2(0,35)); + ImGui::Dummy(ImVec2(0,35*ui_scale)); ImGui::PushFont(fixed_width_font); ImGui::InputTextMultiline("##build_info", build_info_text, IM_ARRAYSIZE(build_info_text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 6), ImGuiInputTextFlags_ReadOnly); @@ -1183,7 +1211,7 @@ struct FirstBootWindow void Draw(const char* title, bool* p_open) { - ImVec2 size(400, 300); + ImVec2 size(400*ui_scale, 300*ui_scale); ImGuiIO& io = ImGui::GetIO(); ImVec2 window_pos = ImVec2((io.DisplaySize.x - size.x)/2, (io.DisplaySize.y - size.y)/2); @@ -1201,38 +1229,38 @@ struct FirstBootWindow } uint32_t now = SDL_GetTicks() - time_start; - ImGui::SetCursorPosY(ImGui::GetCursorPosY()-50); - ImGui::SetCursorPosX((ImGui::GetWindowWidth()-256)/2); + ImGui::SetCursorPosY(ImGui::GetCursorPosY()-50*ui_scale); + ImGui::SetCursorPosX((ImGui::GetWindowWidth()-256*ui_scale)/2); ImTextureID id = (ImTextureID)render_to_fbo(logo_fbo); float t_w = 256.0; float t_h = 256.0; float x_off = 0; ImGui::Image(id, - ImVec2(t_w-x_off, t_h), + ImVec2((t_w-x_off)*ui_scale, t_h*ui_scale), ImVec2(x_off/t_w, t_h/t_h), ImVec2(t_w/t_w, 0)); render_logo(now, 0x42e335ff, 0x42e335ff, 0x00000000); render_to_default_fb(); - ImGui::SetCursorPosX(10); + ImGui::SetCursorPosX(10*ui_scale); - ImGui::SetCursorPosY(ImGui::GetCursorPosY()-75); + ImGui::SetCursorPosY(ImGui::GetCursorPosY()-75*ui_scale); const char *msg = "To get started, please configure machine settings."; ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2); ImGui::Text("%s", msg); - ImGui::Dummy(ImVec2(0,20)); - ImGui::SetCursorPosX((ImGui::GetWindowWidth()-120)/2); - if (ImGui::Button("Settings", ImVec2(120, 0))) { + ImGui::Dummy(ImVec2(0,20*ui_scale)); + ImGui::SetCursorPosX((ImGui::GetWindowWidth()-120*ui_scale)/2); + if (ImGui::Button("Settings", ImVec2(120*ui_scale, 0))) { show_settings_window = true; } - ImGui::Dummy(ImVec2(0,20)); + ImGui::Dummy(ImVec2(0,20*ui_scale)); - ImGui::SetCursorPosX(10); + ImGui::SetCursorPosX(10*ui_scale); msg = "Visit https://xemu.app for more information"; - ImGui::SetCursorPosY(ImGui::GetWindowHeight()-ImGui::CalcTextSize(msg).y-10); + ImGui::SetCursorPosY(ImGui::GetWindowHeight()-ImGui::CalcTextSize(msg).y-10*ui_scale); ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2); ImGui::Text("%s", msg);