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.
This commit is contained in:
bicarus
2026-01-02 21:54:25 -08:00
committed by GitHub
parent 7a1f46d1be
commit 6ffaa77f58
14 changed files with 112 additions and 34 deletions
+8
View File
@@ -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;
}
}
}
+6
View File
@@ -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({
+12
View File
@@ -383,12 +383,24 @@ static const std::vector<OptionDefinition> 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",
+1
View File
@@ -47,6 +47,7 @@ namespace launcher {
SOFTID,
VREnable,
DisableOverlay,
OverlayScaling,
spice2x_FpsAutoShow,
spice2x_FpsOpposite,
spice2x_SubScreenAutoShow,
+21 -4
View File
@@ -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<uint32_t> UI_SCALE_PERCENT;
// global
std::mutex OVERLAY_MUTEX;
std::unique_ptr<overlay::SpiceOverlay> 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<float>(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);
+13
View File
@@ -3,6 +3,7 @@
#include <memory>
#include <mutex>
#include <functional>
#include <optional>
#include <vector>
#include <windows.h>
#include <d3d9.h>
@@ -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<uint32_t> UI_SCALE_PERCENT;
template <typename T>
float apply_scaling(T input) {
if (!UI_SCALE_PERCENT.has_value()) {
return input;
}
return static_cast<float>(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:
@@ -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;
}
+2 -2
View File
@@ -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);
+22 -12
View File
@@ -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,
+2 -2
View File
@@ -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
+5 -2
View File
@@ -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() {
+2 -2
View File
@@ -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(),
+3 -3
View File
@@ -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());
+14 -6
View File
@@ -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