refactor(market): extract pair selector into mktDrawPairSelector

Move the exchange/pair chip grid (wrapping flat chips + click-to-switch +
attribution line) out of RenderMarketTab into mktDrawPairSelector(app, registry,
availWidth), recomputing its own schema/fonts/market. Drop the now-unused ovFont
local. Verbatim body. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-04 00:43:43 -05:00
parent 7e8628f94c
commit 50873d1112

View File

@@ -1297,6 +1297,102 @@ static void mktDrawCard(const MktCardCtx& cx, ImVec2 gcMin, ImVec2 gcMax,
}
}
// Pair selector: every trading pair across all exchanges as flat wrapping chips; clicking one
// switches the active exchange/pair (persisted) and refreshes market data. Followed by attribution.
static void mktDrawPairSelector(App* app, const std::vector<data::ExchangeInfo>& registry, float availWidth)
{
auto& S = schema::UI();
const auto& market = app->getWalletState().market;
ImDrawList* dl = ImGui::GetWindowDrawList();
ImFont* capFont = Type().caption();
ImFont* ovFont = Type().overline();
float gap = Layout::cardGap();
char buf[128];
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.market", "exchange-top-gap").size));
{
float chipH = S.drawElement("tabs.market", "pair-chip-height").height;
float chipR = S.drawElement("tabs.market", "pair-chip-radius").radius;
float chipSpacing = S.drawElement("tabs.market", "pair-chip-spacing").size;
float innerGap = Layout::spacingSm();
float sidePad = Layout::spacingMd();
ImVec2 origin = ImGui::GetCursorScreenPos();
float left = origin.x;
float right = origin.x + availWidth;
float cx = left;
float cy = origin.y;
int btnCounter = 0;
for (int ex = 0; ex < (int)registry.size(); ex++) {
for (int pr = 0; pr < (int)registry[ex].pairs.size(); pr++) {
const std::string& pairName = registry[ex].pairs[pr].displayName;
const std::string& exName = registry[ex].name;
float pairW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, pairName.c_str()).x;
float exW = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, exName.c_str()).x;
float cw = sidePad * 2.0f + pairW + innerGap + exW;
// Wrap to the next row when the pill would overflow (never on an empty row).
if (cx + cw > right && cx > left) {
cx = left;
cy += chipH + chipSpacing;
}
ImVec2 cMin(cx, cy);
ImVec2 cMax(cx + cw, cy + chipH);
bool selected = (ex == s_mkt.exchangeIdx && pr == s_mkt.pairIdx);
bool hov = material::IsRectHovered(cMin, cMax);
ImU32 chipBg = selected ? WithAlpha(Primary(), 200)
: (hov ? WithAlpha(OnSurface(), 35) : WithAlpha(OnSurface(), 20));
ImU32 chipBorder = selected ? Primary() : WithAlpha(OnSurface(), 40);
ImU32 pairCol = selected ? IM_COL32(255, 255, 255, 255) : OnSurface();
ImU32 exCol = selected ? WithAlpha(IM_COL32(255, 255, 255, 255), 190) : OnSurfaceDisabled();
dl->AddRectFilled(cMin, cMax, chipBg, chipR);
dl->AddRect(cMin, cMax, chipBorder, chipR, 0, 1.0f);
float tx = cx + sidePad;
dl->AddText(capFont, capFont->LegacySize,
ImVec2(tx, cy + (chipH - capFont->LegacySize) * 0.5f), pairCol, pairName.c_str());
dl->AddText(ovFont, ovFont->LegacySize,
ImVec2(tx + pairW + innerGap, cy + (chipH - ovFont->LegacySize) * 0.5f),
exCol, exName.c_str());
ImGui::SetCursorScreenPos(cMin);
snprintf(buf, sizeof(buf), "##PairBtn%d", btnCounter++);
if (ImGui::InvisibleButton(buf, ImVec2(cw, chipH))) {
if (ex != s_mkt.exchangeIdx || pr != s_mkt.pairIdx) {
s_mkt.exchangeIdx = ex;
s_mkt.pairIdx = pr;
app->settings()->setSelectedExchange(exName);
app->settings()->setSelectedPair(pairName);
app->settings()->save();
app->refreshMarketData();
}
}
if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
cx += cw + chipSpacing;
}
}
// Advance the cursor past the wrapped rows.
ImGui::SetCursorScreenPos(ImVec2(left, cy + chipH));
ImGui::Dummy(ImVec2(availWidth, 0));
// Attribution (moved here from the removed exchange-selector row).
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("market_attribution"));
if (!market.last_updated.empty()) {
ImGui::SameLine(0, 12);
snprintf(buf, sizeof(buf), " \xc2\xb7 Updated %s", market.last_updated.c_str());
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
}
ImGui::Dummy(ImVec2(0, gap));
}
}
void RenderMarketTab(App* app)
{
auto& S = schema::UI();
@@ -1346,7 +1442,6 @@ void RenderMarketTab(App* app)
ImDrawList* dl = ImGui::GetWindowDrawList();
GlassPanelSpec glassSpec;
glassSpec.rounding = Layout::glassRounding();
ImFont* ovFont = Type().overline();
ImFont* capFont = Type().caption();
ImFont* sub1 = Type().subtitle1();
ImFont* body2 = Type().body2();
@@ -1794,88 +1889,7 @@ void RenderMarketTab(App* app)
// PAIR SELECTOR — every trading pair across all exchanges shown as
// round buttons (flat; no exchange dropdown). Wraps to multiple rows.
// ================================================================
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.market", "exchange-top-gap").size));
{
float chipH = S.drawElement("tabs.market", "pair-chip-height").height;
float chipR = S.drawElement("tabs.market", "pair-chip-radius").radius;
float chipSpacing = S.drawElement("tabs.market", "pair-chip-spacing").size;
float innerGap = Layout::spacingSm();
float sidePad = Layout::spacingMd();
ImVec2 origin = ImGui::GetCursorScreenPos();
float left = origin.x;
float right = origin.x + availWidth;
float cx = left;
float cy = origin.y;
int btnCounter = 0;
for (int ex = 0; ex < (int)registry.size(); ex++) {
for (int pr = 0; pr < (int)registry[ex].pairs.size(); pr++) {
const std::string& pairName = registry[ex].pairs[pr].displayName;
const std::string& exName = registry[ex].name;
float pairW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, pairName.c_str()).x;
float exW = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, exName.c_str()).x;
float cw = sidePad * 2.0f + pairW + innerGap + exW;
// Wrap to the next row when the pill would overflow (never on an empty row).
if (cx + cw > right && cx > left) {
cx = left;
cy += chipH + chipSpacing;
}
ImVec2 cMin(cx, cy);
ImVec2 cMax(cx + cw, cy + chipH);
bool selected = (ex == s_mkt.exchangeIdx && pr == s_mkt.pairIdx);
bool hov = material::IsRectHovered(cMin, cMax);
ImU32 chipBg = selected ? WithAlpha(Primary(), 200)
: (hov ? WithAlpha(OnSurface(), 35) : WithAlpha(OnSurface(), 20));
ImU32 chipBorder = selected ? Primary() : WithAlpha(OnSurface(), 40);
ImU32 pairCol = selected ? IM_COL32(255, 255, 255, 255) : OnSurface();
ImU32 exCol = selected ? WithAlpha(IM_COL32(255, 255, 255, 255), 190) : OnSurfaceDisabled();
dl->AddRectFilled(cMin, cMax, chipBg, chipR);
dl->AddRect(cMin, cMax, chipBorder, chipR, 0, 1.0f);
float tx = cx + sidePad;
dl->AddText(capFont, capFont->LegacySize,
ImVec2(tx, cy + (chipH - capFont->LegacySize) * 0.5f), pairCol, pairName.c_str());
dl->AddText(ovFont, ovFont->LegacySize,
ImVec2(tx + pairW + innerGap, cy + (chipH - ovFont->LegacySize) * 0.5f),
exCol, exName.c_str());
ImGui::SetCursorScreenPos(cMin);
snprintf(buf, sizeof(buf), "##PairBtn%d", btnCounter++);
if (ImGui::InvisibleButton(buf, ImVec2(cw, chipH))) {
if (ex != s_mkt.exchangeIdx || pr != s_mkt.pairIdx) {
s_mkt.exchangeIdx = ex;
s_mkt.pairIdx = pr;
app->settings()->setSelectedExchange(exName);
app->settings()->setSelectedPair(pairName);
app->settings()->save();
app->refreshMarketData();
}
}
if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
cx += cw + chipSpacing;
}
}
// Advance the cursor past the wrapped rows.
ImGui::SetCursorScreenPos(ImVec2(left, cy + chipH));
ImGui::Dummy(ImVec2(availWidth, 0));
// Attribution (moved here from the removed exchange-selector row).
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("market_attribution"));
if (!market.last_updated.empty()) {
ImGui::SameLine(0, 12);
snprintf(buf, sizeof(buf), " \xc2\xb7 Updated %s", market.last_updated.c_str());
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
}
ImGui::Dummy(ImVec2(0, gap));
}
mktDrawPairSelector(app, registry, availWidth);
// ================================================================
// PORTFOLIO — floating summary (no card background) + custom-group cards