diff --git a/src/ui/material/draw_helpers.h b/src/ui/material/draw_helpers.h index ca4bfb2..60fca9a 100644 --- a/src/ui/material/draw_helpers.h +++ b/src/ui/material/draw_helpers.h @@ -9,6 +9,7 @@ #include "tooltip_style.h" #include "scroll_edge_fade.h" #include "rect_cull.h" +#include "fill_strip.h" #include "../layout.h" #include "../schema/element_styles.h" #include "../schema/color_var_resolver.h" diff --git a/src/ui/material/fill_strip.h b/src/ui/material/fill_strip.h new file mode 100644 index 0000000..f623a8f --- /dev/null +++ b/src/ui/material/fill_strip.h @@ -0,0 +1,38 @@ +#pragma once +#include "imgui.h" + +namespace dragonx { namespace ui { namespace material { + +inline int FillStripVertexCount(int n) { return n < 2 ? 0 : 2 * n; } +inline int FillStripIndexCount(int n) { return n < 2 ? 0 : 6 * (n - 1); } + +/// Index list for the strip: vertices come in (top_i, bottom_i) pairs, 2 triangles per column. +/// `base` is the first vertex index; `out` must hold FillStripIndexCount(n) entries. +inline void BuildFillStripIndices(int n, unsigned base, unsigned* out) { + for (int i = 0; i + 1 < n; ++i) { + const unsigned a = base + 2u * (unsigned)i, b = a + 1, c = a + 2, d = a + 3; + *out++ = a; *out++ = b; *out++ = d; + *out++ = a; *out++ = d; *out++ = c; + } +} + +/// Fills the area between an x-monotone polyline and the horizontal line y = baseY with a flat colour, +/// in O(n). Non-anti-aliased: draw the AA polyline on top (it hides the stepped top edge; the side/bottom +/// edges are imperceptible at the ~10% alpha these fills use). Not for concave/self-overlapping shapes. +inline void FillUnderPolyline(ImDrawList* dl, const ImVec2* pts, int n, float baseY, ImU32 col) { + if (n < 2 || (col & IM_COL32_A_MASK) == 0) return; + const ImVec2 uv = ImGui::GetFontTexUvWhitePixel(); + dl->PrimReserve(FillStripIndexCount(n), FillStripVertexCount(n)); + const unsigned base = dl->_VtxCurrentIdx; // read AFTER PrimReserve (it may roll the vertex offset) + for (int i = 0; i < n; ++i) { + dl->PrimWriteVtx(pts[i], uv, col); + dl->PrimWriteVtx(ImVec2(pts[i].x, baseY), uv, col); + } + for (int i = 0; i + 1 < n; ++i) { + const unsigned a = base + 2u * (unsigned)i; + dl->PrimWriteIdx((ImDrawIdx)a); dl->PrimWriteIdx((ImDrawIdx)(a + 1)); dl->PrimWriteIdx((ImDrawIdx)(a + 3)); + dl->PrimWriteIdx((ImDrawIdx)a); dl->PrimWriteIdx((ImDrawIdx)(a + 3)); dl->PrimWriteIdx((ImDrawIdx)(a + 2)); + } +} + +}}} // namespace diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index 3d0cb95..9c90fb5 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -183,10 +183,7 @@ static void pfDrawSparklineFilled(ImDrawList* dl, ImVec2 mn, ImVec2 mx, std::vector pts; if (!pfProjectSeries(hist, mn, mx, pts)) return; int n = (int)pts.size(); - for (int i = 0; i < n; i++) dl->PathLineTo(pts[i]); - dl->PathLineTo(ImVec2(pts[n - 1].x, mx.y)); - dl->PathLineTo(ImVec2(pts[0].x, mx.y)); - dl->PathFillConcave(WithAlpha(lineCol, 28)); + FillUnderPolyline(dl, pts.data(), n, mx.y, WithAlpha(lineCol, 28)); dl->AddPolyline(pts.data(), n, WithAlpha(lineCol, 220), ImDrawFlags_None, 1.4f * dp); } @@ -1826,10 +1823,7 @@ static void mktDrawPriceChart(const MktCtx& cx) points[i] = ImVec2(plotLeft + t * plotW, yOf(s_mkt.history[i])); } // 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)); + FillUnderPolyline(dl, points.data(), (int)n, plotBottom, WithAlpha(dirCol, 28)); // Line (no per-point dots — a clean curve). dl->AddPolyline(points.data(), (int)points.size(), lineCol, ImDrawFlags_None, S.drawElement("tabs.market", "chart-line-thickness").size); } diff --git a/src/ui/windows/mining_stats.cpp b/src/ui/windows/mining_stats.cpp index d700837..38f75a7 100644 --- a/src/ui/windows/mining_stats.cpp +++ b/src/ui/windows/mining_stats.cpp @@ -102,11 +102,7 @@ static void DrawHashrateSparkline(ImDrawList* dl, ImVec2 regionMin, ImVec2 regio } if (points.size() >= 2) { - for (size_t i = 0; i < points.size(); i++) - dl->PathLineTo(points[i]); - dl->PathLineTo(ImVec2(points.back().x, plotBottom)); - dl->PathLineTo(ImVec2(points.front().x, plotBottom)); - dl->PathFillConcave(WithAlpha(Success(), 25)); + FillUnderPolyline(dl, points.data(), (int)points.size(), plotBottom, WithAlpha(Success(), 25)); } dl->AddPolyline(points.data(), (int)points.size(), WithAlpha(Success(), 200), diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index 5b053c4..a5435e4 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -73,6 +73,7 @@ #include "ui/effects/border_sampling.h" #include "ui/effects/capture_extent.h" #include "ui/material/rect_cull.h" +#include "ui/material/fill_strip.h" #include "ui/notifications.h" #include "data/seed_migration_resume.h" #include "data/pin_vault_policy.h" @@ -18020,6 +18021,23 @@ void testBootstrapCompletion() } } +void testFillStrip() { + using namespace dragonx::ui::material; + EXPECT_EQ(FillStripVertexCount(1), 0); EXPECT_EQ(FillStripIndexCount(1), 0); + EXPECT_EQ(FillStripVertexCount(5), 10); EXPECT_EQ(FillStripIndexCount(5), 24); + unsigned idx[24]; BuildFillStripIndices(5, 100, idx); + for (int i = 0; i < 24; ++i) EXPECT_TRUE(idx[i] >= 100 && idx[i] < 110); + // area under the polyline == sum of triangle areas (trapezoid rule) + ImVec2 p[5] = {{0,5},{10,2},{20,8},{30,3},{40,7}}; const float baseY = 10.0f; float tri = 0, trap = 0; + ImVec2 v[10]; for (int i = 0; i < 5; ++i) { v[2*i] = p[i]; v[2*i+1] = ImVec2(p[i].x, baseY); } + for (int t = 0; t < 8; ++t) { + ImVec2 a = v[idx[3*t]-100], b = v[idx[3*t+1]-100], c = v[idx[3*t+2]-100]; + tri += 0.5f * std::fabs((b.x-a.x)*(c.y-a.y) - (c.x-a.x)*(b.y-a.y)); + } + for (int i = 0; i + 1 < 5; ++i) trap += (p[i+1].x - p[i].x) * ((baseY-p[i].y) + (baseY-p[i+1].y)) * 0.5f; + EXPECT_NEAR(tri, trap, 1e-3f); +} + } // namespace int main() @@ -18289,6 +18307,7 @@ int main() testEffectClock(); testBorderSampling(); testRectCull(); + testFillStrip(); testFormatFeePercent(); testPoolWeightedSelection(); testCoalescingWriter();