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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user