From 8c88afe16e5ad28ec41137d58aba3a0243b13047 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 3 Jul 2026 17:09:40 -0500 Subject: [PATCH] =?UTF-8?q?feat(market):=20card/chart=20refinements=20?= =?UTF-8?q?=E2=80=94=208-cell=20min=20width,=20larger=20labels,=20flat=20f?= =?UTF-8?q?ill,=20range-matched=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Portfolio group cards: - Always show the secondary DRGX amount, including on 2-cell cards. - Larger group icon + label (subtitle1); value stays subtitle1 for consistency. - Minimum group width is now 8 cells (default width 8). Price chart: - Area fill is now a flat translucent fill matching the group-card sparklines (dropped the vertical gradient + its primitive-API helper). - Interval buttons sit near the top of the chart area (removed the empty gap above them) with a comfortable gap below before the plot. - The change badge's suffix now matches the SELECTED range (1H->1h, 1D->24h, 1W->7d, 1M->30d) instead of the raw data span (which read 23h / 6d). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/config/settings.h | 2 +- src/ui/windows/market_tab.cpp | 88 +++++++++++++---------------------- 2 files changed, 33 insertions(+), 57 deletions(-) diff --git a/src/config/settings.h b/src/config/settings.h index 214c2eb..7f986ec 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -96,7 +96,7 @@ public: // (-1 = auto-place), w/h in cells (span). Set when the user drags/resizes a card. int gridCol = -1; int gridRow = -1; - int gridW = 6; + int gridW = 8; // default group width (cells); minimum is 8 int gridH = 3; // default group height (cells); minimum is 2, card design adapts }; diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index b1aa947..4e675fb 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -197,27 +197,6 @@ static std::vector> pfChartSeries(const MarketInf return out; } -// Vertical-gradient area fill under a polyline (solid `topCol` at the curve, fading to `botCol` at -// `bottomY`). Drawn as one gradient quad per segment via the draw list's primitive API. -static void pfFillGradientArea(ImDrawList* dl, const std::vector& pts, float bottomY, - ImU32 topCol, ImU32 botCol) -{ - int n = (int)pts.size(); - if (n < 2) return; - const ImVec2 uv = dl->_Data->TexUvWhitePixel; - for (int i = 0; i + 1 < n; i++) { - const ImVec2& p0 = pts[i]; - const ImVec2& p1 = pts[i + 1]; - dl->PrimReserve(6, 4); - unsigned int base = (unsigned int)dl->_VtxCurrentIdx; - dl->PrimWriteVtx(p0, uv, topCol); - dl->PrimWriteVtx(p1, uv, topCol); - dl->PrimWriteVtx(ImVec2(p1.x, bottomY), uv, botCol); - dl->PrimWriteVtx(ImVec2(p0.x, bottomY), uv, botCol); - dl->PrimWriteIdx((ImDrawIdx)base); dl->PrimWriteIdx((ImDrawIdx)(base + 1)); dl->PrimWriteIdx((ImDrawIdx)(base + 2)); - dl->PrimWriteIdx((ImDrawIdx)base); dl->PrimWriteIdx((ImDrawIdx)(base + 2)); dl->PrimWriteIdx((ImDrawIdx)(base + 3)); - } -} // A group's value trend tracks the DRGX price, so a group sparkline plots the price history // (normalized) across a rect. Colored by overall direction. Only meaningful for market bases. @@ -929,7 +908,7 @@ void RenderMarketTab(App* app) // this grid and span many cells; they're drawn with an inner inset so adjacent cards don't touch. float pfCardGap = 0.0f; // gapless cell grid float pfInset = 3.0f * mktDp; // per-card inset for visual separation - const int pfMinW = 4, pfMinH = 2; // minimum card size in cells (~128x64) + const int pfMinW = 8, pfMinH = 2; // minimum card size in cells (~256x64) int pfCols = std::max(pfMinW, (int)(availWidth / (32.0f * mktDp))); float pfCellW = availWidth / (float)pfCols; // square cell edge (>= ~32px) float pfCellH = pfCellW; @@ -983,26 +962,18 @@ void RenderMarketTab(App* app) } // Change over the displayed period (Live falls back to the market's 24h figure). double periodChangePct = market.change_24h; - bool periodIsInterval = false; - if (s_chart_interval != 0 && s_price_history.size() >= 2 && s_price_history.front() > 0.0) { + if (s_chart_interval != 0 && s_price_history.size() >= 2 && s_price_history.front() > 0.0) periodChangePct = (s_price_history.back() - s_price_history.front()) / s_price_history.front() * 100.0; - periodIsInterval = true; - } bool chartUp = periodChangePct >= 0.0; - // Compact label for the displayed span (from the real timestamps), e.g. "24h", "7d", "1.0y". - auto pfSpanLabel = [](long sec) -> std::string { - char b[32]; - if (sec < 5400) snprintf(b, sizeof(b), "%ldm", sec / 60); - else if (sec < 172800) snprintf(b, sizeof(b), "%ldh", sec / 3600); - else if (sec < 1209600) snprintf(b, sizeof(b), "%ldd", sec / 86400); - else if (sec < 7776000) snprintf(b, sizeof(b), "%ldw", sec / 604800); - else if (sec < 63072000) snprintf(b, sizeof(b), "%ldmo", sec / 2592000); - else snprintf(b, sizeof(b), "%.1fy", sec / 31536000.0); - return b; - }; + // Suffix = the SELECTED range (not the raw data span), so 1D reads "24h", 1W reads "7d", etc. std::string periodSuffix = "24h"; - if (periodIsInterval && chartTimes.size() >= 2) - periodSuffix = pfSpanLabel((long)(chartTimes.back() - chartTimes.front())); + switch (s_chart_interval) { + case 1: periodSuffix = "1h"; break; // 1H + case 2: periodSuffix = "24h"; break; // 1D + case 3: periodSuffix = "7d"; break; // 1W + case 4: periodSuffix = "30d"; break; // 1M + default: periodSuffix = "24h"; break; // Live (uses the market's 24h change) + } // ================================================================ // PRICE SUMMARY — Combined hero card with price, stats, and exchange @@ -1140,11 +1111,14 @@ void RenderMarketTab(App* app) float chartPad = pad; float pillH = capFont->LegacySize + Layout::spacingXs() * 2.0f; - float stripH = pillH + Layout::spacingSm(); // top control/info strip height + // Buttons sit near the top of the chart area (minimal gap above), with a comfortable gap + // below before the plot so they aren't crowded against the chart. + float stripTop = chartMin.y + Layout::spacingXs(); + float stripH = pillH + Layout::spacingMd(); // ---- Top strip: interval buttons (left) + 24H volume / market cap + refresh (right) ---- { - float rowTop = chartMin.y + chartPad; + float rowTop = stripTop; float textY = rowTop + (pillH - capFont->LegacySize) * 0.5f; // Interval buttons (left). Live = in-session buffer; the rest are historical. @@ -1216,7 +1190,7 @@ void RenderMarketTab(App* app) float labelPadBottom = Layout::spacingXl() + Layout::spacingSm(); float plotLeft = chartMin.x + labelPadLeft; float plotRight = chartMax.x - chartPad; - float plotTop = chartMin.y + chartPad + stripH; + float plotTop = stripTop + stripH; float plotBottom = chartMax.y - labelPadBottom; float plotW = plotRight - plotLeft; float plotH = plotBottom - plotTop; @@ -1264,8 +1238,11 @@ void RenderMarketTab(App* app) points[i] = ImVec2(x, y); } - // Gradient area fill under the curve (solid near the line, fading to transparent). - pfFillGradientArea(dl, points, plotBottom, WithAlpha(dirCol, 65), WithAlpha(dirCol, 0)); + // Flat translucent area fill under the curve (matches the portfolio group sparklines). + for (size_t i = 0; i < n; i++) dl->PathLineTo(points[i]); + dl->PathLineTo(ImVec2(points[n - 1].x, plotBottom)); + dl->PathLineTo(ImVec2(points[0].x, plotBottom)); + dl->PathFillConcave(WithAlpha(dirCol, 28)); // Line dl->AddPolyline(points.data(), (int)points.size(), lineCol, ImDrawFlags_None, S.drawElement("tabs.market", "chart-line-thickness").size); @@ -1623,7 +1600,6 @@ void RenderMarketTab(App* app) // 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 cardTall = cardHeight > 112.0f * mktDp; bool cardShort = cardHeight < 90.0f * mktDp; float hpad = cardShort ? Layout::spacingSm() : pad; float hleft = gcMin.x + hpad, hright = gcMax.x - hpad; @@ -1635,7 +1611,7 @@ void RenderMarketTab(App* app) float valLeft = hright; bool showChange = e.show24h && (e.priceBasis == 0 || e.priceBasis == 1) && market.change_24h != 0.0; if (!heroStr.empty()) { - ImFont* vf = cardTall ? sub1 : body2; + 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; @@ -1651,19 +1627,19 @@ void RenderMarketTab(App* app) } } - // Left column: icon + name (top), secondary DRGX below on taller cards. + // 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 + body2->LegacySize * 0.5f, topY + body2->LegacySize * 0.5f), - accent ? accent : OnSurfaceMedium(), Type().iconSmall(), body2->LegacySize); - nameX += body2->LegacySize + Layout::spacingSm(); + 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(body2, body2->LegacySize, ImVec2(nameX, topY), OnSurfaceMedium(), - fit(e.label, body2, nameMaxW).c_str()); - float leftBottom = topY + body2->LegacySize; - if (!footStr.empty() && cardHeight > 88.0f * mktDp) { + 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; @@ -1753,8 +1729,8 @@ void RenderMarketTab(App* app) ImVec2 sz = cellSize(nw, nh), mx(mn.x + sz.x, mn.y + sz.y); drawCard(mn, mx, pfEntriesGeo[i], true); dl->AddRect(mn, mx, WithAlpha(Primary(), 200), 10.0f, 0, 2.0f * mktDp); - // Live size readout (e.g. "6\xC3\x972") so the reached grid span is visible — the - // minimum is pfMinW x pfMinH (4x2). + // Live size readout (e.g. "8\xC3\x972") so the reached grid span is visible — the + // minimum is pfMinW x pfMinH (8x2). { char szb[16]; snprintf(szb, sizeof(szb), "%d\xC3\x97%d", nw, nh); ImVec2 tsz = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, szb);