refactor(market): extract drawCard lambda to mktDrawCard function
Lift the ~90-line [&]-capturing drawCard closure out of RenderMarketTab's grid code into a file-static mktDrawCard(MktCardCtx, min, max, entry, hov). The captured locals (dl/state/market/mktDp/pfInset/pfCardPad/sub1/capFont) are bundled into a MktCardCtx bound once per frame. Verbatim body. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1181,6 +1181,122 @@ static std::vector<PfCell> pfComputeGridLayout(
|
||||
return layout;
|
||||
}
|
||||
|
||||
// Shared drawing context for a portfolio group card (bound once per frame in RenderMarketTab).
|
||||
struct MktCardCtx {
|
||||
ImDrawList* dl;
|
||||
const WalletState* state;
|
||||
const MarketInfo* market;
|
||||
float mktDp;
|
||||
float pfInset;
|
||||
float pfCardPad;
|
||||
ImFont* sub1;
|
||||
ImFont* capFont;
|
||||
};
|
||||
|
||||
// Draw one portfolio group card (name/icon, value + %chg, secondary DRGX, sparkline body) within
|
||||
// [gcMin,gcMax] (inset for gaps). hov = hover/active highlight. Pure drawing into cx.dl.
|
||||
static void mktDrawCard(const MktCardCtx& cx, ImVec2 gcMin, ImVec2 gcMax,
|
||||
const config::Settings::PortfolioEntry& e, bool hov)
|
||||
{
|
||||
ImDrawList* dl = cx.dl;
|
||||
const auto& state = *cx.state;
|
||||
const auto& market = *cx.market;
|
||||
float mktDp = cx.mktDp, pfInset = cx.pfInset;
|
||||
ImFont* sub1 = cx.sub1, *capFont = cx.capFont;
|
||||
// The grid is gapless, so inset the card within its cell block for separation.
|
||||
gcMin.x += pfInset; gcMin.y += pfInset; gcMax.x -= pfInset; gcMax.y -= pfInset;
|
||||
double bal = data::SumPortfolioBalance(e.addresses, state.addresses);
|
||||
ImU32 accent = e.color ? (ImU32)e.color : 0;
|
||||
float pad = cx.pfCardPad;
|
||||
GlassPanelSpec gcGlass; gcGlass.rounding = 10.0f;
|
||||
gcGlass.fillAlpha = hov ? 34 : 22; gcGlass.borderAlpha = 40;
|
||||
DrawGlassPanel(dl, gcMin, gcMax, gcGlass);
|
||||
// Accented groups get a colored outline around the whole card (configurable opacity);
|
||||
// others get a subtle hover ring.
|
||||
if (accent) {
|
||||
int oa = (int)(std::max(0, std::min(100, e.outlineOpacity)) * 2.55f + 0.5f);
|
||||
dl->AddRect(gcMin, gcMax, WithAlpha(accent, oa), 10.0f, 0, hov ? 2.5f : 2.0f);
|
||||
} else if (hov) dl->AddRect(gcMin, gcMax, WithAlpha(OnSurface(), 70), 10.0f, 0, 1.0f);
|
||||
|
||||
auto fit = [&](std::string s, ImFont* f, float maxW) {
|
||||
bool t = false;
|
||||
while (s.size() > 1 && f->CalcTextSizeA(f->LegacySize, FLT_MAX, 0, s.c_str()).x > maxW) { s.pop_back(); t = true; }
|
||||
if (t) s += "\xE2\x80\xA6";
|
||||
return s;
|
||||
};
|
||||
|
||||
// Value + DRGX strings per basis.
|
||||
char vb[80]; std::string valueStr; bool hasValue = false;
|
||||
if (e.priceBasis == 0 && market.price_usd > 0) { snprintf(vb, sizeof(vb), "$%.2f", bal * market.price_usd); valueStr = vb; hasValue = true; }
|
||||
else if (e.priceBasis == 1 && market.price_btc > 0) { snprintf(vb, sizeof(vb), "%.8f BTC", bal * market.price_btc); valueStr = vb; hasValue = true; }
|
||||
else if (e.priceBasis == 3 && e.manualPrice > 0.0) { snprintf(vb, sizeof(vb), "%.2f %s", bal * e.manualPrice, e.manualCurrency.c_str()); valueStr = vb; hasValue = true; }
|
||||
char db[48]; snprintf(db, sizeof(db), "%.4f %s", bal, DRAGONX_TICKER);
|
||||
bool heroIsValue = e.showValue && hasValue;
|
||||
std::string heroStr = heroIsValue ? valueStr : (e.showDrgx ? std::string(db) : (hasValue ? valueStr : std::string(db)));
|
||||
std::string footStr = (heroIsValue && e.showDrgx) ? std::string(db) : std::string();
|
||||
|
||||
// Compact stat card: name (+ secondary DRGX) on the left; value with % change stacked
|
||||
// below it in the top-right corner; the sparkline fills the body — so it renders even
|
||||
// on a 2-cell card. Tighter padding on short cards.
|
||||
float cardHeight = gcMax.y - gcMin.y;
|
||||
bool cardShort = cardHeight < 90.0f * mktDp;
|
||||
float hpad = cardShort ? Layout::spacingSm() : pad;
|
||||
float hleft = gcMin.x + hpad, hright = gcMax.x - hpad;
|
||||
float hAvailW = hright - hleft;
|
||||
float topY = gcMin.y + hpad;
|
||||
|
||||
// Right column: value (top-right), % change just below it.
|
||||
float rightBottom = topY;
|
||||
float valLeft = hright;
|
||||
bool showChange = e.show24h && (e.priceBasis == 0 || e.priceBasis == 1) && market.change_24h != 0.0;
|
||||
if (!heroStr.empty()) {
|
||||
ImFont* vf = sub1;
|
||||
std::string vs = fit(heroStr, vf, hAvailW * 0.66f);
|
||||
float vw = vf->CalcTextSizeA(vf->LegacySize, FLT_MAX, 0, vs.c_str()).x;
|
||||
valLeft = hright - vw;
|
||||
dl->AddText(vf, vf->LegacySize, ImVec2(valLeft, topY), OnSurface(), vs.c_str());
|
||||
rightBottom = topY + vf->LegacySize;
|
||||
if (showChange) {
|
||||
char cb[32]; snprintf(cb, sizeof(cb), "%+.2f%%", market.change_24h);
|
||||
float cw = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, cb).x;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(hright - cw, rightBottom + 1.0f * mktDp),
|
||||
market.change_24h >= 0 ? Success() : Error(), cb);
|
||||
valLeft = std::min(valLeft, hright - cw);
|
||||
rightBottom += capFont->LegacySize + 1.0f * mktDp;
|
||||
}
|
||||
}
|
||||
|
||||
// Left column: icon + name (larger, top), secondary DRGX amount below (always shown).
|
||||
float nameX = hleft;
|
||||
if (!e.icon.empty()) {
|
||||
material::project_icons::drawByName(dl, e.icon.c_str(),
|
||||
ImVec2(nameX + sub1->LegacySize * 0.5f, topY + sub1->LegacySize * 0.5f),
|
||||
accent ? accent : OnSurfaceMedium(), Type().iconSmall(), sub1->LegacySize);
|
||||
nameX += sub1->LegacySize + Layout::spacingSm();
|
||||
}
|
||||
float nameMaxW = std::max(24.0f * mktDp, valLeft - Layout::spacingSm() - nameX);
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(nameX, topY), OnSurfaceMedium(),
|
||||
fit(e.label, sub1, nameMaxW).c_str());
|
||||
float leftBottom = topY + sub1->LegacySize;
|
||||
if (!footStr.empty()) {
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(nameX, leftBottom + 1.0f * mktDp),
|
||||
OnSurfaceDisabled(), fit(footStr, capFont, nameMaxW).c_str());
|
||||
leftBottom += capFont->LegacySize + 1.0f * mktDp;
|
||||
}
|
||||
|
||||
// Body: sparkline fills from below the header to the card bottom.
|
||||
if (e.showSparkline && (e.priceBasis == 0 || e.priceBasis == 1)) {
|
||||
std::vector<double> hh = data::sparklineSeries(market, e.sparklineInterval);
|
||||
if (hh.size() >= 2) {
|
||||
float sparkTop = std::max(leftBottom, rightBottom) + Layout::spacingXs();
|
||||
float sparkBot = gcMax.y - hpad;
|
||||
if (sparkBot - sparkTop >= 8.0f * mktDp)
|
||||
pfDrawSparklineFilled(dl, ImVec2(hleft, sparkTop), ImVec2(hright, sparkBot),
|
||||
hh, hh.back() >= hh.front() ? Success() : Error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderMarketTab(App* app)
|
||||
{
|
||||
auto& S = schema::UI();
|
||||
@@ -1874,100 +1990,7 @@ void RenderMarketTab(App* app)
|
||||
// Draw one card's content into [gcMin, gcMax].
|
||||
// Stat tile: header (icon + name, 24h badge) / hero value (large, centered band) /
|
||||
// footer (DRGX amount) + a dedicated sparkline strip along the bottom.
|
||||
auto drawCard = [&](ImVec2 gcMin, ImVec2 gcMax, const config::Settings::PortfolioEntry& e, bool hov) {
|
||||
// The grid is gapless, so inset the card within its cell block for separation.
|
||||
gcMin.x += pfInset; gcMin.y += pfInset; gcMax.x -= pfInset; gcMax.y -= pfInset;
|
||||
double bal = data::SumPortfolioBalance(e.addresses, state.addresses);
|
||||
ImU32 accent = e.color ? (ImU32)e.color : 0;
|
||||
float pad = pfCardPad;
|
||||
GlassPanelSpec gcGlass; gcGlass.rounding = 10.0f;
|
||||
gcGlass.fillAlpha = hov ? 34 : 22; gcGlass.borderAlpha = 40;
|
||||
DrawGlassPanel(dl, gcMin, gcMax, gcGlass);
|
||||
// Accented groups get a colored outline around the whole card (configurable opacity);
|
||||
// others get a subtle hover ring.
|
||||
if (accent) {
|
||||
int oa = (int)(std::max(0, std::min(100, e.outlineOpacity)) * 2.55f + 0.5f);
|
||||
dl->AddRect(gcMin, gcMax, WithAlpha(accent, oa), 10.0f, 0, hov ? 2.5f : 2.0f);
|
||||
} else if (hov) dl->AddRect(gcMin, gcMax, WithAlpha(OnSurface(), 70), 10.0f, 0, 1.0f);
|
||||
|
||||
auto fit = [&](std::string s, ImFont* f, float maxW) {
|
||||
bool t = false;
|
||||
while (s.size() > 1 && f->CalcTextSizeA(f->LegacySize, FLT_MAX, 0, s.c_str()).x > maxW) { s.pop_back(); t = true; }
|
||||
if (t) s += "\xE2\x80\xA6";
|
||||
return s;
|
||||
};
|
||||
|
||||
// Value + DRGX strings per basis.
|
||||
char vb[80]; std::string valueStr; bool hasValue = false;
|
||||
if (e.priceBasis == 0 && market.price_usd > 0) { snprintf(vb, sizeof(vb), "$%.2f", bal * market.price_usd); valueStr = vb; hasValue = true; }
|
||||
else if (e.priceBasis == 1 && market.price_btc > 0) { snprintf(vb, sizeof(vb), "%.8f BTC", bal * market.price_btc); valueStr = vb; hasValue = true; }
|
||||
else if (e.priceBasis == 3 && e.manualPrice > 0.0) { snprintf(vb, sizeof(vb), "%.2f %s", bal * e.manualPrice, e.manualCurrency.c_str()); valueStr = vb; hasValue = true; }
|
||||
char db[48]; snprintf(db, sizeof(db), "%.4f %s", bal, DRAGONX_TICKER);
|
||||
bool heroIsValue = e.showValue && hasValue;
|
||||
std::string heroStr = heroIsValue ? valueStr : (e.showDrgx ? std::string(db) : (hasValue ? valueStr : std::string(db)));
|
||||
std::string footStr = (heroIsValue && e.showDrgx) ? std::string(db) : std::string();
|
||||
|
||||
// Compact stat card: name (+ secondary DRGX) on the left; value with % change stacked
|
||||
// below it in the top-right corner; the sparkline fills the body — so it renders even
|
||||
// on a 2-cell card. Tighter padding on short cards.
|
||||
float cardHeight = gcMax.y - gcMin.y;
|
||||
bool cardShort = cardHeight < 90.0f * mktDp;
|
||||
float hpad = cardShort ? Layout::spacingSm() : pad;
|
||||
float hleft = gcMin.x + hpad, hright = gcMax.x - hpad;
|
||||
float hAvailW = hright - hleft;
|
||||
float topY = gcMin.y + hpad;
|
||||
|
||||
// Right column: value (top-right), % change just below it.
|
||||
float rightBottom = topY;
|
||||
float valLeft = hright;
|
||||
bool showChange = e.show24h && (e.priceBasis == 0 || e.priceBasis == 1) && market.change_24h != 0.0;
|
||||
if (!heroStr.empty()) {
|
||||
ImFont* vf = sub1;
|
||||
std::string vs = fit(heroStr, vf, hAvailW * 0.66f);
|
||||
float vw = vf->CalcTextSizeA(vf->LegacySize, FLT_MAX, 0, vs.c_str()).x;
|
||||
valLeft = hright - vw;
|
||||
dl->AddText(vf, vf->LegacySize, ImVec2(valLeft, topY), OnSurface(), vs.c_str());
|
||||
rightBottom = topY + vf->LegacySize;
|
||||
if (showChange) {
|
||||
char cb[32]; snprintf(cb, sizeof(cb), "%+.2f%%", market.change_24h);
|
||||
float cw = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, cb).x;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(hright - cw, rightBottom + 1.0f * mktDp),
|
||||
market.change_24h >= 0 ? Success() : Error(), cb);
|
||||
valLeft = std::min(valLeft, hright - cw);
|
||||
rightBottom += capFont->LegacySize + 1.0f * mktDp;
|
||||
}
|
||||
}
|
||||
|
||||
// Left column: icon + name (larger, top), secondary DRGX amount below (always shown).
|
||||
float nameX = hleft;
|
||||
if (!e.icon.empty()) {
|
||||
material::project_icons::drawByName(dl, e.icon.c_str(),
|
||||
ImVec2(nameX + sub1->LegacySize * 0.5f, topY + sub1->LegacySize * 0.5f),
|
||||
accent ? accent : OnSurfaceMedium(), Type().iconSmall(), sub1->LegacySize);
|
||||
nameX += sub1->LegacySize + Layout::spacingSm();
|
||||
}
|
||||
float nameMaxW = std::max(24.0f * mktDp, valLeft - Layout::spacingSm() - nameX);
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(nameX, topY), OnSurfaceMedium(),
|
||||
fit(e.label, sub1, nameMaxW).c_str());
|
||||
float leftBottom = topY + sub1->LegacySize;
|
||||
if (!footStr.empty()) {
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(nameX, leftBottom + 1.0f * mktDp),
|
||||
OnSurfaceDisabled(), fit(footStr, capFont, nameMaxW).c_str());
|
||||
leftBottom += capFont->LegacySize + 1.0f * mktDp;
|
||||
}
|
||||
|
||||
// Body: sparkline fills from below the header to the card bottom.
|
||||
if (e.showSparkline && (e.priceBasis == 0 || e.priceBasis == 1)) {
|
||||
std::vector<double> hh = data::sparklineSeries(market, e.sparklineInterval);
|
||||
if (hh.size() >= 2) {
|
||||
float sparkTop = std::max(leftBottom, rightBottom) + Layout::spacingXs();
|
||||
float sparkBot = gcMax.y - hpad;
|
||||
if (sparkBot - sparkTop >= 8.0f * mktDp)
|
||||
pfDrawSparklineFilled(dl, ImVec2(hleft, sparkTop), ImVec2(hright, sparkBot),
|
||||
hh, hh.back() >= hh.front() ? Success() : Error());
|
||||
}
|
||||
}
|
||||
};
|
||||
MktCardCtx cardCx{ dl, &state, &market, mktDp, pfInset, pfCardPad, sub1, capFont };
|
||||
|
||||
// Subtle dot grid while rearranging (fine 32px cells). Fill the whole visible grid area
|
||||
// — down to the bottom of the scroll viewport, not just the occupied rows — so a card can
|
||||
@@ -1995,7 +2018,7 @@ void RenderMarketTab(App* app)
|
||||
if (i == s_grid.resize) continue;
|
||||
|
||||
bool hov = !interacting && material::IsRectHovered(gcMin, gcMax);
|
||||
drawCard(gcMin, gcMax, pfEntriesGeo[i], hov);
|
||||
mktDrawCard(cardCx, gcMin, gcMax, pfEntriesGeo[i], hov);
|
||||
|
||||
// Resize grip (bottom-right).
|
||||
bool gripHov = material::IsRectHovered(ImVec2(gcMax.x - rh, gcMax.y - rh), gcMax);
|
||||
@@ -2038,7 +2061,7 @@ void RenderMarketTab(App* app)
|
||||
(int)((io.MousePos.x - mn.x + pfCardGap) / (pfCellW + pfCardGap) + 0.5f)));
|
||||
int nh = std::max(pfMinH, (int)((io.MousePos.y - mn.y + pfCardGap) / (pfCellH + pfCardGap) + 0.5f));
|
||||
ImVec2 sz = cellSize(nw, nh), mx(mn.x + sz.x, mn.y + sz.y);
|
||||
drawCard(mn, mx, pfEntriesGeo[i], true);
|
||||
mktDrawCard(cardCx, mn, mx, pfEntriesGeo[i], true);
|
||||
dl->AddRect(mn, mx, WithAlpha(Primary(), 200), 10.0f, 0, 2.0f * mktDp);
|
||||
// Live size readout (e.g. "8\xC3\x972") so the reached grid span is visible — the
|
||||
// minimum is pfMinW x pfMinH (8x2).
|
||||
@@ -2079,7 +2102,7 @@ void RenderMarketTab(App* app)
|
||||
dl->AddRectFilled(tmn, tmx, WithAlpha(Primary(), 25), 10.0f);
|
||||
dl->AddRect(tmn, tmx, WithAlpha(Primary(), 200), 10.0f, 0, 2.0f * mktDp);
|
||||
ImVec2 fmn(tx, ty), fmx(fmn.x + tsz.x, fmn.y + tsz.y);
|
||||
drawCard(fmn, fmx, pfEntriesGeo[i], true); // dragged card follows the mouse
|
||||
mktDrawCard(cardCx, fmn, fmx, pfEntriesGeo[i], true); // dragged card follows the mouse
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll);
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user