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;
|
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)
|
void RenderMarketTab(App* app)
|
||||||
{
|
{
|
||||||
auto& S = schema::UI();
|
auto& S = schema::UI();
|
||||||
@@ -1874,100 +1990,7 @@ void RenderMarketTab(App* app)
|
|||||||
// Draw one card's content into [gcMin, gcMax].
|
// Draw one card's content into [gcMin, gcMax].
|
||||||
// Stat tile: header (icon + name, 24h badge) / hero value (large, centered band) /
|
// Stat tile: header (icon + name, 24h badge) / hero value (large, centered band) /
|
||||||
// footer (DRGX amount) + a dedicated sparkline strip along the bottom.
|
// footer (DRGX amount) + a dedicated sparkline strip along the bottom.
|
||||||
auto drawCard = [&](ImVec2 gcMin, ImVec2 gcMax, const config::Settings::PortfolioEntry& e, bool hov) {
|
MktCardCtx cardCx{ dl, &state, &market, mktDp, pfInset, pfCardPad, sub1, 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 = 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Subtle dot grid while rearranging (fine 32px cells). Fill the whole visible grid area
|
// 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
|
// — 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;
|
if (i == s_grid.resize) continue;
|
||||||
|
|
||||||
bool hov = !interacting && material::IsRectHovered(gcMin, gcMax);
|
bool hov = !interacting && material::IsRectHovered(gcMin, gcMax);
|
||||||
drawCard(gcMin, gcMax, pfEntriesGeo[i], hov);
|
mktDrawCard(cardCx, gcMin, gcMax, pfEntriesGeo[i], hov);
|
||||||
|
|
||||||
// Resize grip (bottom-right).
|
// Resize grip (bottom-right).
|
||||||
bool gripHov = material::IsRectHovered(ImVec2(gcMax.x - rh, gcMax.y - rh), gcMax);
|
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)((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));
|
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);
|
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);
|
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
|
// Live size readout (e.g. "8\xC3\x972") so the reached grid span is visible — the
|
||||||
// minimum is pfMinW x pfMinH (8x2).
|
// minimum is pfMinW x pfMinH (8x2).
|
||||||
@@ -2079,7 +2102,7 @@ void RenderMarketTab(App* app)
|
|||||||
dl->AddRectFilled(tmn, tmx, WithAlpha(Primary(), 25), 10.0f);
|
dl->AddRectFilled(tmn, tmx, WithAlpha(Primary(), 25), 10.0f);
|
||||||
dl->AddRect(tmn, tmx, WithAlpha(Primary(), 200), 10.0f, 0, 2.0f * mktDp);
|
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);
|
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);
|
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user