overlay: left nav for cards tab (#753)

Enable left nav for Card tab, splitting the single page into multiple
sub tabs.

Card Manager is now embedded in this UI (but can still be launched as a
separate window)
This commit is contained in:
bicarus
2026-06-12 00:57:52 -07:00
committed by GitHub
parent 1f073b2d75
commit 0cf07c38da
3 changed files with 81 additions and 26 deletions
+8 -2
View File
@@ -385,10 +385,16 @@ namespace overlay::windows {
ImGui::Spacing();
// cards list
// use all available vertical space, minus height footer (a row of buttons and separator)
// use all available vertical space, minus the footer that follows in
// build_content(): a separator + spacing, then a row of buttons
const ImGuiStyle &style = ImGui::GetStyle();
const float footer_height =
style.ItemSpacing.y * 3.f // spacing around the separator and before the footer
+ 1.f // separator line
+ ImGui::GetFrameHeightWithSpacing(); // footer button row
if (ImGui::BeginChild(
"cards",
ImVec2(0, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - 8.f))) {
ImVec2(0, ImGui::GetContentRegionAvail().y - footer_height))) {
// -card0 / -card1 override
for (size_t i = 0; i < 2; i++) {
+66 -23
View File
@@ -706,10 +706,7 @@ namespace overlay::windows {
}
if (ImGui::BeginTabItem("Cards")) {
tab_selected_new = ConfigTab::CONFIG_TAB_CARDS;
ImGui::BeginChild("Cards", ImVec2(
0, ImGui::GetWindowContentRegionMax().y - page_offset2), false);
this->build_cards();
ImGui::EndChild();
this->build_cards_tab(page_offset2);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Patches")) {
@@ -4124,9 +4121,9 @@ namespace overlay::windows {
return detected_controller;
}
void Config::build_cards() {
constexpr float TEXT_INPUT_WIDTH = 240.f;
void Config::build_cards_tab(float page_offset) {
const float content_height = ImGui::GetWindowContentRegionMax().y - page_offset;
const float nav_width = overlay::apply_scaling(100);
// early quit
if (this->games_selected < 0 || this->games_selected_name.empty()) {
@@ -4134,6 +4131,61 @@ namespace overlay::windows {
return;
}
// sub-pages shown in the left navigation, in display order
static const std::vector<std::pair<const char *, void (Config::*)()>> pages = {
{ "Virtual Cards", &Config::build_cards_virtual },
{ "Card Reader", &Config::build_cards_reader },
{ "Card Manager", &Config::build_cards_manager },
};
// default to the first page on first display
if (this->cards_page_label.empty()) {
this->cards_page_label = pages.front().first;
}
// left navigation: one header per sub-page; clicking a header selects that page
ImGui::BeginChild("CardsNav", ImVec2(nav_width, content_height), false);
// extra vertical padding between nav rows
const ImVec2 nav_item_spacing = ImGui::GetStyle().ItemSpacing;
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
ImVec2(nav_item_spacing.x, nav_item_spacing.y + overlay::apply_scaling(2)));
// arrow-less, non-collapsible nav header; selecting it switches the content pane
auto nav_header = [this](const char *label) {
if (this->build_nav_header(label, this->cards_page_label == label)) {
this->cards_page_label = label;
}
};
for (const auto &page : pages) {
nav_header(page.first);
}
ImGui::PopStyleVar();
ImGui::EndChild(); // CardsNav
ImGui::SameLine();
// content: only the selected sub-page
ImGui::BeginChild("CardsContent", ImVec2(0, content_height), false);
// breathing room at the top of the content area
ImGui::Dummy(ImVec2(0.0f, overlay::apply_scaling(4)));
for (const auto &page : pages) {
if (this->cards_page_label == page.first) {
(this->*page.second)();
break;
}
}
ImGui::EndChild(); // CardsContent
}
void Config::build_cards_virtual() {
constexpr float TEXT_INPUT_WIDTH = 240.f;
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Card overrides");
ImGui::Spacing();
ImGui::TextUnformatted(
@@ -4509,10 +4561,9 @@ namespace overlay::windows {
eamuse_update_keypad_bindings();
read_card();
}
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
void Config::build_cards_reader() {
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "NFC / API card reader status");
ImGui::Spacing();
if (cfg::CONFIGURATOR_STANDALONE) {
@@ -4521,7 +4572,7 @@ namespace overlay::windows {
ImGui::TextWrapped("Test NFC card readers and card insertions over API.");
ImGui::SameLine();
ImGui::HelpMarker(
"Enable card readers in Advanced tab, under Card Readers section, and restart. "
"Enable card readers in Options tab, under NFC Card Readers section, and restart. "
"Alternatively, launch spicecfg in command line and pass in the correct parameters. "
"BT5API readers currently do not show the card number in this UI.");
ImGui::Spacing();
@@ -4572,20 +4623,12 @@ namespace overlay::windows {
ImGui::TextDisabled("%s", "Only available in spicecfg.");
ImGui::Spacing();
}
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Card Manager");
ImGui::Spacing();
if (ImGui::Button("Open Card Manager")) {
if (this->overlay->window_cards != nullptr) {
this->overlay->window_cards->set_active(true);
this->overlay->window_cards->bring_to_front();
}
void Config::build_cards_manager() {
if (this->overlay->window_cards != nullptr) {
this->overlay->window_cards->build_content();
}
ImGui::TextUnformatted("");
ImGui::TextUnformatted("");
}
bool Config::validate_ea_card(char card_number[16]) {
+7 -1
View File
@@ -148,6 +148,9 @@ namespace overlay::windows {
// Controller tab left-nav: selected sub-page
std::string controller_page_label = "";
// Cards tab left-nav: selected sub-page
std::string cards_page_label = "";
std::filesystem::path file_picker_path;
std::thread *file_picker_thread = nullptr;
bool file_picker_done = false;
@@ -200,7 +203,10 @@ namespace overlay::windows {
const std::string &target_device);
ControllerTemplate capture_current_bindings(const std::string &name);
void build_cards();
void build_cards_tab(float page_offset);
void build_cards_virtual();
void build_cards_reader();
void build_cards_manager();
std::string build_option_value_picker_title(const OptionDefinition& option);
void build_option_value_picker(Option& option);
void build_options(