feat(market): merge price + chart into one card; simplify stats

Combine the top two Market cards: the price-summary hero and the chart now share
a single glass panel (one panel spans the price/stats header + the chart drawn
below it; the chart no longer draws its own panel or an inter-card gap).

Simplify the stat strip from three columns to two — drop the niche BTC-price
column, keeping 24h Volume + Market Cap — and remove the "updated Ns ago"
staleness line (data freshness is still shown by the attribution "Updated" line
under the pair buttons).

Full-node + lite build clean; ctest green; source hygiene clean. Completes the
Market "round buttons + combine/simplify top cards" item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 21:31:29 -05:00
parent 61f8c54742
commit 1faaa54a07

View File

@@ -162,7 +162,11 @@ void RenderMarketTab(App* app)
float statsRowH = ovFont->LegacySize + Layout::spacingXs() + sub1->LegacySize + pad;
float cardH = std::max(heroMinH + statsRowH + pad, (S.drawElement("tabs.market", "hero-card-height").size + statsRowH + pad) * vs);
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
// Combined hero + chart: draw ONE glass panel spanning the price/stats header AND the
// chart below it. The chart block no longer draws its own panel or an inter-card gap.
float mergedChartH = std::max(60.0f, chartElem.height * vs);
ImVec2 mergedMax(cardMin.x + availWidth, cardMax.y + mergedChartH);
DrawGlassPanel(dl, cardMin, mergedMax, glassSpec);
float cx = cardMin.x + Layout::spacingLg();
float cy = cardMin.y + Layout::spacingLg();
@@ -201,20 +205,19 @@ void RenderMarketTab(App* app)
ImVec2(cardMax.x - rnd * 0.5f, sepY),
WithAlpha(OnSurface(), 15), 1.0f * dp);
// ---- STATS ROW (BTC Price | Volume | Market Cap) ----
// ---- STATS ROW (24h Volume | Market Cap) ----
// (BTC-price column + the "updated Ns ago" staleness line were dropped to
// simplify; freshness is still shown by the attribution "Updated" line.)
float statsY = sepY + Layout::spacingSm();
float colW = (availWidth - Layout::spacingLg() * 2) / 3.0f;
float colW = (availWidth - Layout::spacingLg() * 2) / 2.0f;
struct StatItem { const char* label; std::string value; ImU32 valueCol; };
StatItem stats[3] = {
{TR("market_btc_price"), "", OnSurface()},
StatItem stats[2] = {
{TR("market_24h_volume"), FormatCompactUSD(market.volume_24h), OnSurface()},
{TR("market_cap"), FormatCompactUSD(market.market_cap), OnSurface()},
};
snprintf(buf, sizeof(buf), "%.10f", market.price_btc);
stats[0].value = buf;
for (int i = 0; i < 3; i++) {
for (int i = 0; i < 2; i++) {
float sx = cardMin.x + Layout::spacingLg() + i * colW;
float centerX = sx + colW * 0.5f;
@@ -230,26 +233,6 @@ void RenderMarketTab(App* app)
ImVec2(centerX - valSz.x * 0.5f, valY), stats[i].valueCol, stats[i].value.c_str());
}
// ---- STALENESS INDICATOR ----
{
auto fetchTime = market.last_fetch_time;
if (fetchTime.time_since_epoch().count() > 0) {
auto elapsed = std::chrono::steady_clock::now() - fetchTime;
int ageSecs = (int)std::chrono::duration_cast<std::chrono::seconds>(elapsed).count();
bool stale = ageSecs > 300; // 5 minutes
if (ageSecs < 60)
snprintf(buf, sizeof(buf), "%s %ds %s", stale ? ICON_MD_WARNING : "", ageSecs, TR("ago"));
else
snprintf(buf, sizeof(buf), "%s %dm %s", stale ? ICON_MD_WARNING : "", ageSecs / 60, TR("ago"));
ImFont* staleFont = capFont;
ImU32 staleCol = stale ? Warning() : WithAlpha(OnSurface(), 100);
float staleY = statsY + ovFont->LegacySize + Layout::spacingXs() + sub1->LegacySize + Layout::spacingSm();
ImVec2 staleSz = staleFont->CalcTextSizeA(staleFont->LegacySize, FLT_MAX, 0, buf);
dl->AddText(staleFont, staleFont->LegacySize,
ImVec2(cardMin.x + Layout::spacingLg(), staleY), staleCol, buf);
}
}
// ---- TRADE BUTTON (top-right of card) ----
if (!currentExchange.pairs.empty()) {
const char* pairName = currentExchange.pairs[s_pair_idx].displayName.c_str();
@@ -315,13 +298,13 @@ void RenderMarketTab(App* app)
}
}
// No inter-card gap — the chart is drawn immediately below, inside the same panel.
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(availWidth, 0));
ImGui::Dummy(ImVec2(0, gap));
}
// ================================================================
// PRICE CHART — Custom drawn inside glass panel (matches app design)
// PRICE CHART — drawn inside the combined hero card's glass panel (above)
// ================================================================
{
// Plot the REAL accumulated price history (one point per refresh, oldest→newest).
@@ -330,11 +313,11 @@ void RenderMarketTab(App* app)
s_price_history = market.price_history;
s_history_initialized = true;
// Chart height from schema
// Chart height from schema. No glass panel here — it shares the combined hero+chart
// card's panel drawn in PRICE SUMMARY above (mergedChartH uses this same height).
float chartH = std::max(60.0f, chartElem.height * vs);
ImVec2 chartMin = ImGui::GetCursorScreenPos();
ImVec2 chartMax(chartMin.x + availWidth, chartMin.y + chartH);
DrawGlassPanel(dl, chartMin, chartMax, glassSpec);
if (!s_price_history.empty() && s_price_history.size() >= 2) {
float chartPad = pad;