feat(market): replace portfolio detail accordion with a segmented control

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) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 21:24:17 -05:00
parent fe18628216
commit e74989ec2c

View File

@@ -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