diff --git a/src/config/settings.cpp b/src/config/settings.cpp index e037cda..6753ec1 100644 --- a/src/config/settings.cpp +++ b/src/config/settings.cpp @@ -330,6 +330,8 @@ bool Settings::load(const std::string& path) entry.showValue = e["show_value"].get(); if (e.contains("show_24h") && e["show_24h"].is_boolean()) entry.show24h = e["show_24h"].get(); + if (e.contains("show_sparkline") && e["show_sparkline"].is_boolean()) + entry.showSparkline = e["show_sparkline"].get(); if (!entry.label.empty()) portfolio_entries_.push_back(std::move(entry)); } } @@ -487,6 +489,7 @@ bool Settings::save(const std::string& path) entry["show_drgx"] = e.showDrgx; entry["show_value"] = e.showValue; entry["show_24h"] = e.show24h; + entry["show_sparkline"] = e.showSparkline; j["portfolio_entries"].push_back(std::move(entry)); } j["font_scale"] = font_scale_; diff --git a/src/config/settings.h b/src/config/settings.h index 0f3dba5..bd30bd8 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -89,6 +89,7 @@ public: bool showDrgx = true; // show the DRGX amount on the card bool showValue = true; // show the converted/fiat value on the card bool show24h = false; // show the 24h % change (live-market bases only) + bool showSparkline = false; // show a price-trend sparkline (live-market bases only) }; // Theme diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index e72c80c..b6a3c88 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -110,6 +110,26 @@ static char s_pf_manual_ccy[12] = "USD"; static bool s_pf_show_drgx = true; static bool s_pf_show_value = true; static bool s_pf_show_24h = false; +static bool s_pf_show_sparkline = false; + +// 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. +static void pfDrawSparkline(ImDrawList* dl, ImVec2 mn, ImVec2 mx, + const std::vector& hist, ImU32 col) +{ + int n = (int)hist.size(); + if (n < 2) return; + double lo = *std::min_element(hist.begin(), hist.end()); + double hi = *std::max_element(hist.begin(), hist.end()); + if (hi <= lo) hi = lo + 1e-9; + float w = mx.x - mn.x, h = mx.y - mn.y; + std::vector pts(n); + for (int i = 0; i < n; i++) { + float t = (n > 1) ? (float)i / (float)(n - 1) : 0.0f; + pts[i] = ImVec2(mn.x + t * w, mx.y - (float)((hist[i] - lo) / (hi - lo)) * h); + } + dl->AddPolyline(pts.data(), n, col, ImDrawFlags_None, 1.2f); +} // Build the right-hand amount strings for a group card / preview from its price-data config. struct PfDisplay { @@ -177,6 +197,7 @@ static void PortfolioBeginEdit(App* app, int index) s_pf_show_drgx = src.showDrgx; s_pf_show_value = src.showValue; s_pf_show_24h = src.show24h; + s_pf_show_sparkline = src.showSparkline; } else { s_pf_label[0] = '\0'; s_pf_addrs.clear(); @@ -188,6 +209,7 @@ static void PortfolioBeginEdit(App* app, int index) s_pf_show_drgx = true; s_pf_show_value = true; s_pf_show_24h = false; + s_pf_show_sparkline = false; } s_pf_search[0] = '\0'; s_pf_type_filter = 0; @@ -280,6 +302,14 @@ static void RenderPortfolioEditor(App* app) pdl->AddRectFilled(ImVec2(bx, pMin.y + ppad * 0.6f), ImVec2(bx + 3.0f, pMax.y - ppad * 0.6f), accent, 2.0f); } + if (s_pf_show_sparkline && (s_pf_price_basis == 0 || s_pf_price_basis == 1) && + state.market.price_history.size() >= 2) { + const auto& h = state.market.price_history; + ImU32 spCol = WithAlpha(h.back() >= h.front() ? Success() : Error(), 80); + ImVec2 spMin(pMin.x + ppad + (accent ? 6.0f : 0.0f), pMin.y + ph * 0.46f); + ImVec2 spMax(pMax.x - ppad, pMax.y - ppad * 0.6f); + pfDrawSparkline(pdl, spMin, spMax, h, spCol); + } float rowY = pMin.y + ppad, nameX = pMin.x + ppad + (accent ? 6.0f : 0.0f); if (!s_pf_icon.empty()) { material::project_icons::drawByName(pdl, s_pf_icon, @@ -418,6 +448,10 @@ static void RenderPortfolioEditor(App* app) ImGui::BeginDisabled(!(s_pf_price_basis == 0 || s_pf_price_basis == 1)); // 24h: live-market only ImGui::Checkbox(TR("portfolio_show_24h"), &s_pf_show_24h); ImGui::EndDisabled(); + // Sparkline on its own line (also live-market only). + ImGui::BeginDisabled(!(s_pf_price_basis == 0 || s_pf_price_basis == 1)); + ImGui::Checkbox(TR("portfolio_show_sparkline"), &s_pf_show_sparkline); + ImGui::EndDisabled(); } ImGui::Dummy(ImVec2(0, Layout::spacingSm())); @@ -629,6 +663,7 @@ static void RenderPortfolioEditor(App* app) e.showDrgx = s_pf_show_drgx; e.showValue = s_pf_show_value; e.show24h = s_pf_show_24h; + e.showSparkline = s_pf_show_sparkline; if (s_pf_editing >= 0 && s_pf_editing < (int)entries.size()) entries[s_pf_editing] = e; else entries.push_back(e); persist(entries); @@ -1298,6 +1333,17 @@ void RenderMarketTab(App* app) ImVec2(bx + 3.0f, gcMax.y - pfCardPad * 0.6f), accent, 2.0f); } + // Price-trend sparkline (faint, in the lower band, behind the text) for + // live-market groups. The group's value tracks the DRGX price, so plot its history. + if (e.showSparkline && (e.priceBasis == 0 || e.priceBasis == 1) && + market.price_history.size() >= 2) { + const auto& h = market.price_history; + ImU32 spCol = WithAlpha(h.back() >= h.front() ? Success() : Error(), 80); + ImVec2 spMin(gcMin.x + pfCardPad + (accent ? 6.0f : 0.0f), gcMin.y + pfCardH * 0.46f); + ImVec2 spMax(gcMax.x - pfCardPad, gcMax.y - pfCardPad * 0.6f); + pfDrawSparkline(dl, spMin, spMax, h, spCol); + } + double bal = data::SumPortfolioBalance(e.addresses, state.addresses); float rowY = gcMin.y + pfCardPad; float gcRight = gcMax.x - pfCardPad; diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 2224163..36b9ff7 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1134,6 +1134,7 @@ void I18n::loadBuiltinEnglish() strings_["portfolio_show"] = "Show:"; strings_["portfolio_show_value"] = "Value"; strings_["portfolio_show_24h"] = "24h"; + strings_["portfolio_show_sparkline"] = "Sparkline"; strings_["portfolio_appearance"] = "APPEARANCE"; strings_["portfolio_addresses_hdr"] = "ADDRESSES"; strings_["portfolio_search"] = "Search addresses\xE2\x80\xA6";