From e74989ec2c6d75c916491ca92d6d0116ced831d5 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 3 Jul 2026 21:24:17 -0500 Subject: [PATCH] feat(market): replace portfolio detail accordion with a segmented control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swap the collapsible Appearance/Price/Addresses dropdown sections for a single segmented pill control (one section shown at a time) — no dropdown headers. The active section now owns the full detail height, so the icon grid and address list fill the available space instead of fixed heights. State: s_pf_section (0/1/2) replaces the three accordion open-flags. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/windows/market_tab.cpp | 72 ++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index 9211e67..04396e9 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -96,9 +96,7 @@ static std::string FormatPrice(double price) // ---- Portfolio editor (modal) state ---- static bool s_portfolio_editor_open = false; static int s_pf_sel = -1; // selected group index in the master list; -1 = none, -2 = unsaved draft -static bool s_pf_sec_appearance = true; // right-pane accordion: which section is expanded -static bool s_pf_sec_price = false; -static bool s_pf_sec_addresses = false; +static int s_pf_section = 0; // right-pane segmented control: 0=Appearance 1=Price 2=Addresses // Backdrop live-capture: capture once on open + on viewport resize (not every frame). The blur is a // frozen snapshot while the modal is open — efficient and flicker-free. static bool s_pf_was_open = false; @@ -646,31 +644,42 @@ static void RenderPortfolioEditor(App* app) ImGui::InputTextWithHint("##pfLabel", TR("portfolio_group_name"), s_pf_label, sizeof(s_pf_label)); ImGui::Dummy(ImVec2(0, Layout::spacingSm())); - // Accordion: exactly one section expanded at a time. Collapsed sections show a compact - // summary on the right so a group's state is readable at a glance without expanding. - auto section = [&](const char* label, bool& openFlag, const std::string& summary) -> bool { - ImGui::SetNextItemOpen(openFlag, ImGuiCond_Always); - bool now = ImGui::CollapsingHeader(label); - if (!now && !summary.empty()) { - ImVec2 hmn = ImGui::GetItemRectMin(), hmx = ImGui::GetItemRectMax(); - float sw2 = capF->CalcTextSizeA(capF->LegacySize, FLT_MAX, 0, summary.c_str()).x; - ImGui::GetWindowDrawList()->AddText(capF, capF->LegacySize, - ImVec2(hmx.x - sw2 - Layout::spacingMd(), (hmn.y + hmx.y) * 0.5f - capF->LegacySize * 0.5f), - OnSurfaceMedium(), summary.c_str()); + // Segmented control switches the detail section (no collapsible dropdowns) — one at a time. + { + const char* secs[3] = { TR("portfolio_appearance"), TR("portfolio_price"), + TR("portfolio_addresses_hdr") }; + float segH = 30.0f * dp; + float segW = ImGui::GetContentRegionAvail().x; + float cellW = segW / 3.0f; + ImVec2 sMin = ImGui::GetCursorScreenPos(); + ImDrawList* sdl = ImGui::GetWindowDrawList(); + sdl->AddRectFilled(sMin, ImVec2(sMin.x + segW, sMin.y + segH), WithAlpha(OnSurface(), 20), segH * 0.5f); + for (int s = 0; s < 3; s++) { + ImVec2 cMin(sMin.x + s * cellW, sMin.y), cMax(cMin.x + cellW, sMin.y + segH); + bool active = (s_pf_section == s); + bool hov = ImGui::IsMouseHoveringRect(cMin, cMax); + if (active) + sdl->AddRectFilled(ImVec2(cMin.x + 2.0f * dp, cMin.y + 2.0f * dp), + ImVec2(cMax.x - 2.0f * dp, cMax.y - 2.0f * dp), + WithAlpha(Primary(), 210), (segH - 4.0f * dp) * 0.5f); + ImVec2 ts = body2f->CalcTextSizeA(body2f->LegacySize, FLT_MAX, 0, secs[s]); + sdl->AddText(body2f, body2f->LegacySize, + ImVec2(cMin.x + (cellW - ts.x) * 0.5f, cMin.y + (segH - ts.y) * 0.5f), + active ? IM_COL32(255, 255, 255, 255) : (hov ? OnSurface() : OnSurfaceMedium()), + secs[s]); + ImGui::PushID(s); + ImGui::SetCursorScreenPos(cMin); + if (ImGui::InvisibleButton("##pfseg", ImVec2(cellW, segH))) s_pf_section = s; + if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + ImGui::PopID(); } - if (now != openFlag) { s_pf_sec_appearance = s_pf_sec_price = s_pf_sec_addresses = false; openFlag = now; } - return openFlag; - }; - // Collapsed-section summaries. - const char* basisNames[4] = { TR("portfolio_price_usd"), TR("portfolio_price_btc"), - TR("portfolio_price_drgx"), TR("portfolio_price_manual") }; - std::string apSummary = s_pf_icon.empty() ? std::string(TR("portfolio_no_icon")) : s_pf_icon; - std::string prSummary = basisNames[std::max(0, std::min(3, s_pf_price_basis))]; - char adSummary[48]; snprintf(adSummary, sizeof(adSummary), TR("portfolio_addresses_sel"), (int)s_pf_addrs.size()); + ImGui::SetCursorScreenPos(ImVec2(sMin.x, sMin.y + segH)); + ImGui::Dummy(ImVec2(segW, 0)); + } + ImGui::Dummy(ImVec2(0, Layout::spacingMd())); // ---- APPEARANCE (color + outline opacity + icon) ---- - if (section(TR("portfolio_appearance"), s_pf_sec_appearance, apSummary)) { - ImGui::Indent(Layout::spacingSm()); + if (s_pf_section == 0) { // Color picker: preset swatches + a custom-color circle that opens a picker popup. { float sw = 22.0f * dp; @@ -741,7 +750,7 @@ static void RenderPortfolioEditor(App* app) { float cell = 28.0f * dp, cellGap = 4.0f * dp; ImGui::TextUnformatted(TR("portfolio_icon")); - ImGui::BeginChild("##pfIconGrid", ImVec2(0, 132.0f * dp), true); + ImGui::BeginChild("##pfIconGrid", ImVec2(0, std::max(120.0f * dp, ImGui::GetContentRegionAvail().y)), true); ImDrawList* gdl = ImGui::GetWindowDrawList(); float availW = ImGui::GetContentRegionAvail().x; int cols = std::max(1, (int)((availW + cellGap) / (cell + cellGap))); @@ -780,12 +789,10 @@ static void RenderPortfolioEditor(App* app) } ImGui::EndChild(); } - ImGui::Unindent(Layout::spacingSm()); } // ---- PRICE (basis, manual price, shown fields, sparkline) ---- - if (section(TR("portfolio_price"), s_pf_sec_price, prSummary)) { - ImGui::Indent(Layout::spacingSm()); + if (s_pf_section == 1) { const char* basisItems[4] = { TR("portfolio_price_usd"), TR("portfolio_price_btc"), TR("portfolio_price_drgx"), TR("portfolio_price_manual") }; ImGui::SetNextItemWidth(-1); @@ -819,12 +826,10 @@ static void RenderPortfolioEditor(App* app) ImGui::Combo("##pfSparkInterval", &s_pf_spark_interval, spItems, 5); ImGui::EndDisabled(); ImGui::EndDisabled(); - ImGui::Unindent(Layout::spacingSm()); } // ---- ADDRESSES (search, filter, select) ---- - if (section(TR("portfolio_addresses_hdr"), s_pf_sec_addresses, adSummary)) { - ImGui::Indent(Layout::spacingSm()); + if (s_pf_section == 2) { { char selc[48]; snprintf(selc, sizeof(selc), TR("portfolio_addresses_sel"), (int)s_pf_addrs.size()); @@ -870,7 +875,7 @@ static void RenderPortfolioEditor(App* app) ImGui::SameLine(); if (ImGui::SmallButton(TR("portfolio_clear_sel"))) s_pf_addrs.clear(); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); - ImGui::BeginChild("##pfAddrList", ImVec2(0, 300.0f * dp), true); + ImGui::BeginChild("##pfAddrList", ImVec2(0, std::max(120.0f * dp, ImGui::GetContentRegionAvail().y)), true); ImDrawList* ldl = ImGui::GetWindowDrawList(); float rowH = std::max(26.0f * dp, body2f->LegacySize + Layout::spacingSm()); float lw = ImGui::GetContentRegionAvail().x; @@ -934,7 +939,6 @@ static void RenderPortfolioEditor(App* app) ImGui::PopID(); } ImGui::EndChild(); - ImGui::Unindent(Layout::spacingSm()); } } ImGui::EndChild(); // ##pfDetail