overlay: migrate configurator UI from ImGui Columns to Tables (#282)

## Link to GitHub Issue, if one exists
n/a

## Description of change
`ImGui::Columns` is a deprecated component with many issues. Migrate
Buttons/Analog/Overlay/Lights/Options... tabs to the new table API.
Clean up some UI elements around it to reduce clutter.

Note: there are still other usages of Columns API (MIDI debug output,
ACIO viewer, window manager) but no one hardly ever sees those.
Eventually they will need to be migrated if ImGui ever drops support for
them.

## Compiling
👍 

## Testing
Testing all games in spicecfg and overlay.
This commit is contained in:
bicarus-dev
2025-04-02 18:33:39 -07:00
committed by GitHub
parent edaf5a386f
commit 6969dfcdd1
+150 -84
View File
@@ -36,6 +36,8 @@
namespace overlay::windows { namespace overlay::windows {
// same width as dummy marker
const float INDENT = 22.f;
const auto PROJECT_URL = "https://spice2x.github.io"; const auto PROJECT_URL = "https://spice2x.github.io";
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) { Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
@@ -248,10 +250,8 @@ namespace overlay::windows {
// help text for binding buttons, if the game has one // help text for binding buttons, if the game has one
const auto help_text = games::get_buttons_help(this->games_selected_name); const auto help_text = games::get_buttons_help(this->games_selected_name);
if (!help_text.empty()) { if (!help_text.empty()) {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Button Layout"); ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Button Layout");
ImGui::Spacing(); ImGui::Spacing();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
ImGui::TextWrapped("%s", help_text.c_str()); ImGui::TextWrapped("%s", help_text.c_str());
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
} }
@@ -262,15 +262,15 @@ namespace overlay::windows {
// keypad buttons // keypad buttons
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
if (this->games_selected_name == "Beatmania IIDX") { if (this->games_selected_name == "Beatmania IIDX") {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8); ImGui::Indent(INDENT);
ImGui::TextColored( ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f), ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: Lightning Model (TDJ) I/O will ignore the keypad!"); "WARNING: Lightning Model (TDJ) I/O will ignore the keypad!");
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
ImGui::TextWrapped( ImGui::TextWrapped(
"Use Toggle Sub Screen button to show the overlay and use your mouse, " "Use Toggle Sub Screen button to show the overlay and use your mouse, "
"connect using SpiceCompanion app, or connect a touch screen to enter " "connect using SpiceCompanion app, or connect a touch screen to enter "
"the PIN."); "the PIN.");
ImGui::Unindent(INDENT);
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
} }
auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name); auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
@@ -563,26 +563,25 @@ namespace overlay::windows {
} }
void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) { void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) {
ImGui::Columns(3, "ButtonsColumns", true);
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Button", name.c_str()); ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Buttons", name.c_str());
ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Binding");
ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Actions");
ImGui::SameLine();
ImGui::HelpMarker("Use 'Bind' to bind a button to a device using RawInput.\n"
"Use 'Naive' for device independent binding using GetAsyncKeyState.");
ImGui::NextColumn();
ImGui::Separator(); ImGui::Separator();
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);
// check if empty // check if empty
if (!buttons || buttons->empty()) { if (!buttons || buttons->empty()) {
ImGui::TextUnformatted("-"); ImGui::TableNextRow();
ImGui::NextColumn(); ImGui::TableNextColumn();
ImGui::TextUnformatted("-"); ImGui::Indent(INDENT);
ImGui::NextColumn(); ImGui::TextDisabled("-");
ImGui::TextUnformatted("-"); ImGui::Unindent(INDENT);
ImGui::NextColumn(); ImGui::TableNextColumn();
ImGui::TextDisabled("-");
ImGui::TableNextColumn();
ImGui::TextDisabled("-");
} }
// check buttons // check buttons
@@ -601,6 +600,9 @@ namespace overlay::windows {
button = &button_alternatives.at(this->buttons_page - 1); button = &button_alternatives.at(this->buttons_page - 1);
} }
ImGui::TableNextRow();
ImGui::TableNextColumn();
// get button info // get button info
ImGui::PushID(button); ImGui::PushID(button);
auto button_name = button->getName(); auto button_name = button->getName();
@@ -618,11 +620,15 @@ namespace overlay::windows {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.f));
} }
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::Indent(INDENT);
ImGui::Text("%s", button_name.c_str()); ImGui::Text("%s", button_name.c_str());
ImGui::NextColumn(); ImGui::Unindent(INDENT);
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::Text("%s", button_display.empty() ? "None" : button_display.c_str()); ImGui::Text("%s", button_display.empty() ? "-" : button_display.c_str());
ImGui::NextColumn();
ImGui::TableNextColumn();
if (style_color) { if (style_color) {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
@@ -679,6 +685,12 @@ namespace overlay::windows {
} }
} }
} }
if (ImGui::IsItemHovered()) {
ImGui::HelpTooltip(
"Use 'Bind' to bind a button to a device using RawInput.\n"
"Use 'Naive' for device independent binding using GetAsyncKeyState.");
}
if (ImGui::BeginPopupModal(bind_name.c_str(), NULL, if (ImGui::BeginPopupModal(bind_name.c_str(), NULL,
ImGuiWindowFlags_AlwaysAutoResize)) { ImGuiWindowFlags_AlwaysAutoResize)) {
@@ -1086,6 +1098,12 @@ namespace overlay::windows {
buttons_keyboard_state[i] = GetAsyncKeyState(i) != 0; buttons_keyboard_state[i] = GetAsyncKeyState(i) != 0;
} }
} }
if (ImGui::IsItemHovered()) {
ImGui::HelpTooltip(
"Use 'Bind' to bind a button to a device using RawInput.\n"
"Use 'Naive' for device independent binding using GetAsyncKeyState.");
}
if (ImGui::BeginPopupModal(naive_string.c_str(), NULL, if (ImGui::BeginPopupModal(naive_string.c_str(), NULL,
ImGuiWindowFlags_AlwaysAutoResize)) { ImGuiWindowFlags_AlwaysAutoResize)) {
buttons_bind_active = true; buttons_bind_active = true;
@@ -1598,28 +1616,33 @@ namespace overlay::windows {
} }
// clean up // clean up
ImGui::NextColumn();
ImGui::PopID(); ImGui::PopID();
} }
} }
ImGui::Columns();
ImGui::EndTable();
}
} }
void Config::build_analogs(const std::string &name, std::vector<Analog> *analogs) { void Config::build_analogs(const std::string &name, std::vector<Analog> *analogs) {
ImGui::Columns(3, "AnalogsColumns", true); ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Analogs");
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Analog", name.c_str()); ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Binding"); ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Actions"); ImGui::NextColumn();
ImGui::Separator(); ImGui::Separator();
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);
// check if empty // check if empty
if (!analogs || analogs->empty()) { if (!analogs || analogs->empty()) {
ImGui::TextUnformatted("-"); ImGui::TableNextRow();
ImGui::NextColumn(); ImGui::TableNextColumn();
ImGui::TextUnformatted("-"); ImGui::Indent(INDENT);
ImGui::NextColumn(); ImGui::TextDisabled("-");
ImGui::TextUnformatted("-"); ImGui::Unindent(INDENT);
ImGui::NextColumn(); ImGui::TableNextColumn();
ImGui::TextDisabled("-");
ImGui::TableNextColumn();
ImGui::TextDisabled("-");
} }
// check analogs // check analogs
@@ -1627,7 +1650,10 @@ namespace overlay::windows {
for (auto &analog : *analogs) { for (auto &analog : *analogs) {
// get analog info // get analog info
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(&analog); ImGui::PushID(&analog);
auto analog_name = analog.getName(); auto analog_name = analog.getName();
auto analog_display = analog.getDisplayString(RI_MGR.get()); auto analog_display = analog.getDisplayString(RI_MGR.get());
auto analog_state = GameAPI::Analogs::getState(RI_MGR, analog); auto analog_state = GameAPI::Analogs::getState(RI_MGR, analog);
@@ -1639,10 +1665,12 @@ namespace overlay::windows {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.f));
} }
ImGui::Text("%s", analog_name.c_str()); ImGui::Text("%s", analog_name.c_str());
ImGui::NextColumn();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::Text("%s", analog_display.empty() ? "None" : analog_display.c_str()); ImGui::Text("%s", analog_display.empty() ? "-" : analog_display.c_str());
ImGui::NextColumn();
ImGui::TableNextColumn();
if (analog_display.empty()) { if (analog_display.empty()) {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
@@ -2018,30 +2046,33 @@ namespace overlay::windows {
} }
// clean up // clean up
ImGui::NextColumn();
ImGui::PopID(); ImGui::PopID();
} }
} }
ImGui::Columns();
ImGui::EndTable();
}
} }
void Config::build_lights(const std::string &name, std::vector<Light> *lights) { void Config::build_lights(const std::string &name, std::vector<Light> *lights) {
ImGui::Columns(3, "LightsColumns", true); ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Lights");
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Name"); ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Binding"); ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Actions"); ImGui::SameLine();
ImGui::HelpMarker("Use 'Bind' to redirect cabinet light outputs to HID-compatible value output devices.");
ImGui::NextColumn();
ImGui::Separator(); ImGui::Separator();
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);
// check if empty // check if empty
if (!lights || lights->empty()) { if (!lights || lights->empty()) {
ImGui::TextUnformatted("-"); ImGui::TableNextRow();
ImGui::NextColumn(); ImGui::TableNextColumn();
ImGui::TextUnformatted("-"); ImGui::Indent(INDENT);
ImGui::NextColumn(); ImGui::TextDisabled("-");
ImGui::TextUnformatted("-"); ImGui::Unindent(INDENT);
ImGui::NextColumn(); ImGui::TableNextColumn();
ImGui::TextDisabled("-");
ImGui::TableNextColumn();
ImGui::TextDisabled("-");
} }
// check lights // check lights
@@ -2068,13 +2099,21 @@ namespace overlay::windows {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.f));
} }
ImGui::PushID(light); ImGui::PushID(light);
ImGui::TableNextRow();
// progress bar & light name
ImGui::TableNextColumn();
ImGui::ProgressBar(light_state, ImVec2(32.f, 0)); ImGui::ProgressBar(light_state, ImVec2(32.f, 0));
ImGui::SameLine(); ImGui::SameLine();
ImGui::Text("%s", light_name.c_str()); ImGui::Text("%s", light_name.c_str());
ImGui::NextColumn();
// binding name
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::Text("%s", light_display.empty() ? "None" : light_display.c_str()); ImGui::Text("%s", light_display.empty() ? "-" : light_display.c_str());
ImGui::NextColumn();
// bind / clear buttons
ImGui::TableNextColumn();
if (light_display.empty()) { if (light_display.empty()) {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
@@ -2296,11 +2335,12 @@ namespace overlay::windows {
} }
// clean up // clean up
ImGui::NextColumn();
ImGui::PopID(); ImGui::PopID();
} }
} }
ImGui::Columns();
ImGui::EndTable();
}
} }
void Config::build_cards() { void Config::build_cards() {
@@ -2563,29 +2603,27 @@ namespace overlay::windows {
std::vector<Option> *options, const std::string &category, const std::string *filter) { std::vector<Option> *options, const std::string &category, const std::string *filter) {
int options_count; int options_count;
ImGui::Columns(3, "OptionsColumns", true); // category name
std::string cat = "Options";
if (!category.empty()) { if (!category.empty()) {
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), category.c_str()); cat = category;
} else if (filter != nullptr) { } else if (filter != nullptr) {
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Search results"); cat = "Search results";
} else {
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Options");
} }
ImGui::NextColumn(); ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), cat.c_str());
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Parameter");
ImGui::SameLine();
ImGui::HelpMarker(
"These are the command-line parameters you can use in your .bat file to set the options.\n"
"Example: spice.exe -w -ea\n"
" spice64.exe -api 1337 -apipass changeme");
ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Setting");
ImGui::NextColumn();
ImGui::Separator(); ImGui::Separator();
// render table
// 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);
// iterate options // iterate options
options_count = 0; options_count = 0;
for (auto &option : *options) { for (auto &option : *options) {
// get option definition // get option definition
auto &definition = option.get_definition(); auto &definition = option.get_definition();
@@ -2628,6 +2666,11 @@ namespace overlay::windows {
// list entry // list entry
ImGui::PushID(&option); ImGui::PushID(&option);
ImGui::Indent(INDENT);
ImGui::TableNextRow();
// option name
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
if (option.is_active()) { if (option.is_active()) {
// active option // active option
@@ -2647,14 +2690,33 @@ namespace overlay::windows {
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::HelpTooltip(definition.desc.c_str()); ImGui::HelpTooltip(definition.desc.c_str());
} }
ImGui::NextColumn();
// command line parameter
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
std::string param = "-";
if (definition.display_name.empty()) { if (definition.display_name.empty()) {
ImGui::TextDisabled("-%s", definition.name.c_str()); param += definition.name;
} else { } else {
ImGui::TextDisabled("-%s", definition.display_name.c_str()); param += definition.display_name;
} }
ImGui::NextColumn(); ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.f), "%s", param.c_str());
if (ImGui::IsItemHovered()) {
const auto help =
param +
"\n\nClick to copy the parameter to the clipboard.\n\n"
"These are the command-line parameters you can use in your .bat file to set the options.\n"
"Example: spice.exe -w -ea\n"
" spice64.exe -api 1337 -apipass changeme";
ImGui::HelpTooltip(help.c_str());
}
if (ImGui::IsItemClicked()) {
clipboard::copy_text(param.c_str());
}
// option widgets
ImGui::TableNextColumn();
if (option.disabled || definition.disabled) { if (option.disabled || definition.disabled) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
@@ -2822,20 +2884,24 @@ namespace overlay::windows {
// next item // next item
ImGui::PopID(); ImGui::PopID();
ImGui::NextColumn(); ImGui::Unindent(INDENT);
} }
// check if empty // check if empty
if (options_count == 0) { if (options_count == 0) {
ImGui::TextUnformatted("-"); ImGui::TableNextRow();
ImGui::NextColumn(); ImGui::TableNextColumn();
ImGui::TextUnformatted("-"); ImGui::Indent(INDENT);
ImGui::NextColumn(); ImGui::TextDisabled("-");
ImGui::TextUnformatted("-"); ImGui::Unindent(INDENT);
ImGui::NextColumn(); ImGui::TableNextColumn();
ImGui::TextDisabled("-");
ImGui::TableNextColumn();
ImGui::TextDisabled("-");
}
ImGui::EndTable();
} }
ImGui::Columns(1);
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
} }