From ea70cf4fca6175273e6ed0d2727f342ecd0f971f Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Mon, 28 Sep 2026 20:33:35 +0200 Subject: [PATCH] feat(plot): the figure crosshair marks the hovered row, and the stroke is thin, dashed and configurable --- examples/glfw_demo.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/examples/glfw_demo.cpp b/examples/glfw_demo.cpp index 7dac57b..52bd38d 100644 --- a/examples/glfw_demo.cpp +++ b/examples/glfw_demo.cpp @@ -528,6 +528,14 @@ int main() { /// The scripted region sequence, off unless `PLOT_DEMO_REGIONS` asks for it. Same shape /// as the follow script: a driven run whose verdict is carried out in its `failed`. RegionScript region_script = RegionScript::from_env(); + /// `PLOT_DEMO_CROSSHAIR=1`: park a pointer at the centre of the data area every frame, so the + /// controller places the two-line crosshair (spec §17's thin, dashed hover instrument) and a + /// headless screenshot has its ink to measure. Off by default, like the two scripts above. + bool crosshair_demo = false; + if (const char* v = std::getenv("PLOT_DEMO_CROSSHAIR"); + v != nullptr && v[0] != '\0' && v[0] != '0') { + crosshair_demo = true; + } // The region the frame carries: a direct-manipulation primitive with the statistics // readout that is the point of one, so the demo reaches it without a script as well as @@ -589,6 +597,19 @@ int main() { } } + if (crosshair_demo) { + // A pointer parked at the centre of the data area, injected through the controller so the + // *real* placement path runs: `update_crosshair()` reads both data coordinates off the + // pointer's pixel position and the plot draws the full cross there. + const plot::Rect a = std::as_const(graph).layout().axes; + plot::input::PointerEvent e; + e.x = a.x + 0.5f * a.w; + e.y = a.y + 0.45f * a.h; + e.mods = 0; + e.down = false; + host.controller().on_pointer(e); + } + meter.elapsed += dt; meter.frames += 1; meter.frame_ms += 0.1 * (dt * 1.0e3 - meter.frame_ms); // EMA @@ -674,6 +695,17 @@ int main() { const plot::Renderer* r = host.renderer(); const plot::FrameStats stats = r != nullptr ? r->stats() : plot::FrameStats{}; const plot::Rect axes = std::as_const(p).layout().axes; + // The crosshair's geometry as the last frame laid it out (spec §17): the stroke and dash the + // layout resolved and the two stroke boxes -- what the screenshot's ink is read against. + { + const plot::CrosshairLayout& ch = std::as_const(p).layout().crosshair; + std::printf("[glfw_demo] crosshair active %d, stroke %.2f device px, dash %.2f/%.2f," + " vertical box w %.2f at x %.1f, horizontal box h %.2f at y %.1f\n", + ch.active ? 1 : 0, static_cast(ch.stroke), + static_cast(ch.dash.x), static_cast(ch.dash.y), + static_cast(ch.vertical.w), static_cast(ch.vertical.x), + static_cast(ch.horizontal.h), static_cast(ch.horizontal.y)); + } const f64 spp = axes.w > 0.0f ? static_cast(stats.visible_samples) / static_cast(axes.w) : 0.0; const f64 dense = r != nullptr ? static_cast(r->style().dense_threshold_spp) : 0.0;