From 6ffaa77f58c826f1838b708568f6bc51770b0fdc Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:54:25 -0800 Subject: [PATCH] overlay: UI scaling option (#495) ## Link to GitHub Issue, if one exists #477 ## Description of change Add a new option for scaling the overlay. Gitadora Arena will receive 250% by default. Various overlay windows have been updated to scale properly, not perfect but they are at least usable. --- src/spice2x/games/gitadora/gitadora.cpp | 8 +++++ src/spice2x/launcher/launcher.cpp | 6 ++++ src/spice2x/launcher/options.cpp | 12 +++++++ src/spice2x/launcher/options.h | 1 + src/spice2x/overlay/overlay.cpp | 25 +++++++++++--- src/spice2x/overlay/overlay.h | 13 +++++++ .../overlay/windows/camera_control.cpp | 2 +- src/spice2x/overlay/windows/card_manager.cpp | 4 +-- src/spice2x/overlay/windows/config.cpp | 34 ++++++++++++------- src/spice2x/overlay/windows/exitprompt.cpp | 4 +-- src/spice2x/overlay/windows/fps.cpp | 7 ++-- src/spice2x/overlay/windows/iopanel.cpp | 4 +-- src/spice2x/overlay/windows/iopanel_gfdm.cpp | 6 ++-- src/spice2x/overlay/windows/keypad.cpp | 20 +++++++---- 14 files changed, 112 insertions(+), 34 deletions(-) diff --git a/src/spice2x/games/gitadora/gitadora.cpp b/src/spice2x/games/gitadora/gitadora.cpp index 24f180f..ec5993c 100644 --- a/src/spice2x/games/gitadora/gitadora.cpp +++ b/src/spice2x/games/gitadora/gitadora.cpp @@ -5,6 +5,7 @@ #include "cfg/configurator.h" #include "hooks/audio/mme.h" #include "hooks/graphics/graphics.h" +#include "overlay/overlay.h" #include "util/cpuutils.h" #include "util/detour.h" #include "util/libutils.h" @@ -212,6 +213,13 @@ namespace games::gitadora { log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series"); } #endif + + // arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay + // force scaling to make things usable + if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() && + GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) { + overlay::UI_SCALE_PERCENT = 250; + } } } diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index 2ab18fd..6e95de7 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -959,6 +959,12 @@ int main_implementation(int argc, char *argv[]) { if (options[launcher::Options::DisableOverlay].value_bool()) { overlay::ENABLED = false; } + if (options[launcher::Options::OverlayScaling].is_active() && !cfg::CONFIGURATOR_STANDALONE && !cfg_run) { + const auto val = options[launcher::Options::OverlayScaling].value_uint32(); + if (10 <= val && val <= 400 && val != 100) { + overlay::UI_SCALE_PERCENT = options[launcher::Options::OverlayScaling].value_uint32(); + } + } if (options[launcher::Options::DisableAudioHooks].value_bool()) { hooks::audio::ENABLED = false; deferredlogs::defer_error_messages({ diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index 8f56197..295f570 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -383,12 +383,24 @@ static const std::vector OPTION_DEFINITIONS = { .disabled = true, }, { + // DisableOverlay .title = "Disable Spice Overlay", .name = "overlaydisable", .desc = "Disables the in-game overlay", .type = OptionType::Bool, .category = "Overlay", }, + { + // OverlayScaling + .title = "Spice Overlay UI Scale %", + .name = "overlayscale", + .desc = "Forces UI scaling for the overlay, " + "things can look off since the UI was written for 100% scaling. " + "Enter value in percentage, between 10-400, inclusive", + .type = OptionType::Integer, + .setting_name = "200", + .category = "Overlay", + }, { // spice2x_FpsAutoShow .title = "Auto Show FPS/Clock", diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index 6ec5eaa..00a66f9 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -47,6 +47,7 @@ namespace launcher { SOFTID, VREnable, DisableOverlay, + OverlayScaling, spice2x_FpsAutoShow, spice2x_FpsOpposite, spice2x_SubScreenAutoShow, diff --git a/src/spice2x/overlay/overlay.cpp b/src/spice2x/overlay/overlay.cpp index 7d97f2a..4589225 100644 --- a/src/spice2x/overlay/overlay.cpp +++ b/src/spice2x/overlay/overlay.cpp @@ -3,6 +3,7 @@ #include "avs/game.h" #include "cfg/configurator.h" #include "games/io.h" +#include "games/gitadora/gitadora.h" #include "games/iidx/iidx.h" #include "hooks/graphics/graphics.h" #include "misc/eamuse.h" @@ -51,16 +52,26 @@ namespace overlay { bool AUTO_SHOW_IOPANEL = false; bool AUTO_SHOW_KEYPAD_P1 = false; bool AUTO_SHOW_KEYPAD_P2 = false; - bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = false; - bool FPS_SHOULD_FLIP = false; + std::optional UI_SCALE_PERCENT; // global std::mutex OVERLAY_MUTEX; std::unique_ptr OVERLAY = nullptr; ImFont* DSEG_FONT = nullptr; bool SHOW_DEBUG_LOG_WINDOW = false; + + ImVec2 apply_scaling_to_vector(float x, float y) { + if (!UI_SCALE_PERCENT.has_value()) { + return ImVec2(x, y); + } + return ImVec2(apply_scaling(x), apply_scaling(y)); + } + + ImVec2 apply_scaling_to_vector(const ImVec2& input) { + return apply_scaling_to_vector(input.x, input.y); + } } static void *ImGui_Alloc(size_t sz, void *user_data) { @@ -170,6 +181,12 @@ void overlay::SpiceOverlay::init() { style.WindowRounding = 0; } + if (UI_SCALE_PERCENT.has_value()) { + const auto scale = static_cast(UI_SCALE_PERCENT.value()) / 100.f; + ImGui::GetStyle().ScaleAllSizes(scale); + ImGui::GetIO().FontGlobalScale = scale; + } + // red theme based on: // https://github.com/ocornut/imgui/issues/707#issuecomment-760220280 // r, g, b, a @@ -245,7 +262,6 @@ void overlay::SpiceOverlay::init() { if (!cfg::CONFIGURATOR_STANDALONE) { io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; } if (is_touch_available("SpiceOverlay::init")) { io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen; @@ -366,7 +382,8 @@ void overlay::SpiceOverlay::init() { window_iopanel = new overlay::windows::IIDXIOPanel(this); } else if (avs::game::is_model("MDX")) { window_iopanel = new overlay::windows::DDRIOPanel(this); - } else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) { + } else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"}) && + !games::gitadora::is_arena_model()) { window_iopanel = new overlay::windows::GitadoraIOPanel(this); } else { window_iopanel = new overlay::windows::IOPanel(this); diff --git a/src/spice2x/overlay/overlay.h b/src/spice2x/overlay/overlay.h index 0bc4a42..c478081 100644 --- a/src/spice2x/overlay/overlay.h +++ b/src/spice2x/overlay/overlay.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,18 @@ namespace overlay { extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT; extern bool FPS_SHOULD_FLIP; extern bool SHOW_DEBUG_LOG_WINDOW; + extern std::optional UI_SCALE_PERCENT; + + template + float apply_scaling(T input) { + if (!UI_SCALE_PERCENT.has_value()) { + return input; + } + return static_cast(input) * UI_SCALE_PERCENT.value() / 100.f; + } + + ImVec2 apply_scaling_to_vector(const ImVec2& input); + ImVec2 apply_scaling_to_vector(float x, float y); class SpiceOverlay { public: diff --git a/src/spice2x/overlay/windows/camera_control.cpp b/src/spice2x/overlay/windows/camera_control.cpp index fef3b46..b4b7c8a 100644 --- a/src/spice2x/overlay/windows/camera_control.cpp +++ b/src/spice2x/overlay/windows/camera_control.cpp @@ -21,7 +21,7 @@ namespace overlay::windows { CameraControl::CameraControl(SpiceOverlay *overlay) : Window(overlay) { this->title = "IIDX Camera Control"; this->flags |= ImGuiWindowFlags_AlwaysAutoResize; - this->init_pos = ImVec2(40, 40); + this->init_pos = overlay::apply_scaling_to_vector(40, 40); this->toggle_button = games::OverlayButtons::ToggleCameraControl; } diff --git a/src/spice2x/overlay/windows/card_manager.cpp b/src/spice2x/overlay/windows/card_manager.cpp index 6517870..9693d32 100644 --- a/src/spice2x/overlay/windows/card_manager.cpp +++ b/src/spice2x/overlay/windows/card_manager.cpp @@ -17,7 +17,7 @@ namespace overlay::windows { CardManager::CardManager(SpiceOverlay *overlay) : Window(overlay) { this->title = "Card Manager"; - this->init_size = ImVec2(420, 420); + this->init_size = overlay::apply_scaling_to_vector(ImVec2(420, 420)); if (cfg::CONFIGURATOR_STANDALONE) { this->init_pos = ImVec2(40, 40); @@ -303,7 +303,7 @@ namespace overlay::windows { // // setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below // to return true, making it necessary to provide a callback... - ImGui::SetNextItemWidth(240); + ImGui::SetNextItemWidth(overlay::apply_scaling(240)); if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) { this->current_card = nullptr; this->search_filter_in_lower_case = strtolower(this->search_filter); diff --git a/src/spice2x/overlay/windows/config.cpp b/src/spice2x/overlay/windows/config.cpp index b522961..828236a 100644 --- a/src/spice2x/overlay/windows/config.cpp +++ b/src/spice2x/overlay/windows/config.cpp @@ -42,8 +42,8 @@ namespace overlay::windows { Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) { this->title = "Configuration"; this->toggle_button = games::OverlayButtons::ToggleConfig; - this->init_size = ImVec2(800, 600); - this->size_min = ImVec2(100, 200); + this->init_size = overlay::apply_scaling_to_vector(ImVec2(800, 600)); + this->size_min = overlay::apply_scaling_to_vector(ImVec2(100, 200)); this->init_pos = ImVec2(0, 0); if (cfg::CONFIGURATOR_STANDALONE && cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) { this->active = true; @@ -238,8 +238,8 @@ namespace overlay::windows { // tab selection auto tab_selected_new = ConfigTab::CONFIG_TAB_INVALID; if (ImGui::BeginTabBar("Config Tabs", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { - const int page_offset = cfg::CONFIGURATOR_STANDALONE ? 88 : 110; - const int page_offset2 = cfg::CONFIGURATOR_STANDALONE ? 65 : 87; + const int page_offset = overlay::apply_scaling(cfg::CONFIGURATOR_STANDALONE ? 88 : 110); + const int page_offset2 = overlay::apply_scaling(cfg::CONFIGURATOR_STANDALONE ? 65 : 87); if (ImGui::BeginTabItem("Buttons")) { tab_selected_new = ConfigTab::CONFIG_TAB_BUTTONS; @@ -575,7 +575,7 @@ namespace overlay::windows { if (ImGui::BeginTable("ButtonsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 240); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240)); // check if empty if (!buttons || buttons->empty()) { @@ -1644,7 +1644,7 @@ namespace overlay::windows { if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 240); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240)); // check if empty if (!analogs || analogs->empty()) { @@ -2086,7 +2086,7 @@ namespace overlay::windows { if (ImGui::BeginTable("LightsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 240); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240)); // check if empty if (!lights || lights->empty()) { @@ -2643,8 +2643,14 @@ namespace overlay::windows { // tables must share the same ID to have synced column settings if (ImGui::BeginTable("OptionsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("CMD Line Parameter", ImGuiTableColumnFlags_WidthFixed, 216); - ImGui::TableSetupColumn("Setting", ImGuiTableColumnFlags_WidthFixed, 240); + ImGui::TableSetupColumn( + "CMD Line Parameter", + ImGuiTableColumnFlags_WidthFixed, + overlay::apply_scaling(216)); + ImGui::TableSetupColumn( + "Setting", + ImGuiTableColumnFlags_WidthFixed, + overlay::apply_scaling(240)); // iterate options options_count = 0; @@ -3056,7 +3062,11 @@ namespace overlay::windows { ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.34f, 0.14f, 0.14f, 0.54f)); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.34f, 0.14f, 0.14f, 0.54f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.34f, 0.14f, 0.14f, 0.64f)); - ImGui::PushItemWidth(MIN(700, MAX(100, ImGui::GetWindowSize().x - 400))); + ImGui::PushItemWidth( + MIN(overlay::apply_scaling(700), + MAX(overlay::apply_scaling(100), + ImGui::GetWindowSize().x - overlay::apply_scaling(400)))); + ImGui::Combo("##game_selector", game_selected, games_names.data(), (int)games_list.size()); ImGui::PopItemWidth(); ImGui::PopStyleColor(3); @@ -3080,8 +3090,8 @@ namespace overlay::windows { // draw popups { const ImVec2 popup_size( - std::min(ImGui::GetIO().DisplaySize.x * 0.9f, 800.f), - std::min(ImGui::GetIO().DisplaySize.y * 0.9f, 800.f)); + std::min(ImGui::GetIO().DisplaySize.x * 0.9f, overlay::apply_scaling(800.f)), + std::min(ImGui::GetIO().DisplaySize.y * 0.9f, overlay::apply_scaling(800.f))); const ImVec2 popup_pos( ImGui::GetIO().DisplaySize.x / 2 - popup_size.x / 2, diff --git a/src/spice2x/overlay/windows/exitprompt.cpp b/src/spice2x/overlay/windows/exitprompt.cpp index 71977bf..5544f7a 100644 --- a/src/spice2x/overlay/windows/exitprompt.cpp +++ b/src/spice2x/overlay/windows/exitprompt.cpp @@ -10,10 +10,10 @@ namespace overlay::windows { this->title = "spice2x"; this->init_size = ImVec2( (ImGui::GetFontSize() * 14) + (ImGui::GetStyle().ItemSpacing.x * 2), - 120); + overlay::apply_scaling(120)); this->init_pos = ImVec2( ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2, - 10); + overlay::apply_scaling(10)); this->flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse diff --git a/src/spice2x/overlay/windows/fps.cpp b/src/spice2x/overlay/windows/fps.cpp index d1f07b0..761f3b7 100644 --- a/src/spice2x/overlay/windows/fps.cpp +++ b/src/spice2x/overlay/windows/fps.cpp @@ -20,8 +20,11 @@ namespace overlay::windows { void FPS::calculate_initial_window() { // width is 114x82 px with window decoration, 98x47 for the content - int pos_x = overlay::FPS_SHOULD_FLIP ? 8 : ImGui::GetIO().DisplaySize.x - 122; - this->init_pos = ImVec2(pos_x, 8); + int pos_x = + overlay::FPS_SHOULD_FLIP ? + overlay::apply_scaling(8) : + ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(122); + this->init_pos = ImVec2(pos_x, overlay::apply_scaling(8)); } void FPS::build_content() { diff --git a/src/spice2x/overlay/windows/iopanel.cpp b/src/spice2x/overlay/windows/iopanel.cpp index d181a79..eb1fd5d 100644 --- a/src/spice2x/overlay/windows/iopanel.cpp +++ b/src/spice2x/overlay/windows/iopanel.cpp @@ -11,7 +11,7 @@ namespace overlay::windows { this->flags = ImGuiWindowFlags_AlwaysAutoResize; this->init_size = ImVec2( - 80, + overlay::apply_scaling(80), ImGui::GetFrameHeight() + ImGui::GetStyle().WindowPadding.y * 2 + ImGui::GetFrameHeightWithSpacing() * 3 + @@ -58,7 +58,7 @@ namespace overlay::windows { void IOPanel::build_io_panel() {} void IOPanel::build_operator_menu() { - const ImVec2 wide(60, 0); + const ImVec2 wide = overlay::apply_scaling_to_vector(60, 0); const float spacing_x = ImGui::GetStyle().ItemSpacing.y; const ImVec2 tall( ImGui::GetFrameHeight(), diff --git a/src/spice2x/overlay/windows/iopanel_gfdm.cpp b/src/spice2x/overlay/windows/iopanel_gfdm.cpp index a6111a6..54bd554 100644 --- a/src/spice2x/overlay/windows/iopanel_gfdm.cpp +++ b/src/spice2x/overlay/windows/iopanel_gfdm.cpp @@ -68,7 +68,7 @@ namespace overlay::windows { } void GitadoraIOPanel::build_io_panel() { - ImGui::Dummy(ImVec2(12, 0)); + ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0)); ImGui::SameLine(); this->draw_buttons(0); @@ -80,7 +80,7 @@ namespace overlay::windows { // draw p2 only if guitar freaks if (this->two_players) { ImGui::SameLine(); - ImGui::Dummy(ImVec2(12, 0)); + ImGui::Dummy(overlay::apply_scaling_to_vector(12, 0)); ImGui::SameLine(); this->draw_buttons(1); if (this->has_guitar_knobs) { @@ -146,7 +146,7 @@ namespace overlay::windows { games::gitadora::Analogs::GuitarP1Knob : games::gitadora::Analogs::GuitarP2Knob; - const ImVec2 slider_size(32, get_suggested_height()); + const ImVec2 slider_size(overlay::apply_scaling(32), get_suggested_height()); // get analog const auto analogs = games::get_analogs(eamuse_get_game()); diff --git a/src/spice2x/overlay/windows/keypad.cpp b/src/spice2x/overlay/windows/keypad.cpp index e326442..aa7388d 100644 --- a/src/spice2x/overlay/windows/keypad.cpp +++ b/src/spice2x/overlay/windows/keypad.cpp @@ -19,15 +19,15 @@ namespace overlay::windows { case 0: { this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP1; this->init_pos = ImVec2( - 26, - ImGui::GetIO().DisplaySize.y - 264); + overlay::apply_scaling(26), + ImGui::GetIO().DisplaySize.y - overlay::apply_scaling(264)); break; } case 1: { this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP2; this->init_pos = ImVec2( - ImGui::GetIO().DisplaySize.x - 220, - ImGui::GetIO().DisplaySize.y - 264); + ImGui::GetIO().DisplaySize.x - overlay::apply_scaling(220), + ImGui::GetIO().DisplaySize.y - overlay::apply_scaling(264)); break; } } @@ -82,9 +82,17 @@ namespace overlay::windows { // add selectable (fill last line) if (i == std::size(BUTTONS) - 1) { - ImGui::Selectable(button.text, false, 0, ImVec2(112, 32)); + ImGui::Selectable( + button.text, + false, + 0, + overlay::apply_scaling_to_vector(ImVec2(112, 32))); } else { - ImGui::Selectable(button.text, false, 0, ImVec2(32, 32)); + ImGui::Selectable( + button.text, + false, + 0, + overlay::apply_scaling_to_vector(ImVec2(32, 32))); } // mouse down handler