fix(render): keep a cursor parked on an axis bound inside and visible

This commit is contained in:
Martino Ferrari committed 2026-09-28 17:49:18 +02:00
1 parent eb7371d7a5
commit 93ad774281
3 files changed
+84 -18

No files matched your search

+50 -5
View File
@@ -4332,6 +4332,15 @@ bool ruler_pixel(const plot::u8 px[4]) {
return px[1] > px[0] + 50 && px[1] > px[2] + 20 && px[1] > 90;
}
/// The same hue at *any* coverage. A stroke centred exactly on `axes.bottom()` -- where the
/// clip's inward rounding leaves only the inside half -- can survive as a faint sub-pixel
/// sliver when that edge is fractional, which `ruler_pixel`'s brightness floor does not see.
/// The hue gate still excludes the grey chrome, the pale text and the (absent here) blue
/// trace, so a count of it is still a count of the ruler.
bool ruler_ink_any(const plot::u8 px[4]) {
return px[1] > px[0] + 15 && px[2] > px[0] + 10 && px[1] > 35 && px[1] >= px[2] - 8;
}
/// The extent of the ink a predicate accepts anywhere in a read-back frame: its bounding
/// box in top-down framebuffer rows/columns (like every `Rect` here), and its pixel count.
struct InkExtent {
@@ -4484,16 +4493,24 @@ PLOT_TEST(horizontal_ruler_stays_inside_the_axes_at_the_edges) {
};
// Dragged past the *bottom*: the pointer extrapolates below the axis and the drag clamps
// to the range's minimum rather than storing a value no tick could reach.
// to the range's minimum rather than storing a value no tick could reach. That value maps
// to the axes' *bottom* row, and the ruler is pinned visible there -- the same outcome as
// a drag past the top, which is the point of the far-edge rule being strict: an at-bound
// coordinate is inside the rect. (The clip's inward rounding keeps the ink on the last row
// inside the edge, so "at the boundary" reads as within a stroke width of it.)
grab();
drag_to(axes.bottom() + 400.0f);
PLOT_CHECK_NEAR(p.cursor(ruler).y, 0.0, 1e-9);
{
const std::vector<plot::u8> frame = frame_now();
PLOT_CHECK(predicate_pixels(frame, kW, kFrameRect, ruler_pixel) ==
predicate_pixels(frame, kW, axes, ruler_pixel));
PLOT_CHECK(predicate_pixels(frame, kW, p.layout().x_label_area, ruler_pixel) == 0u);
PLOT_CHECK(predicate_pixels(frame, kW, p.layout().y_label_area, ruler_pixel) == 0u);
const InkExtent ink = ink_extent(frame, kW, kH, ruler_ink_any);
PLOT_CHECK(ink.pixels > 0u); // drawn, not hidden
PLOT_CHECK(static_cast<plot::f32>(ink.bottom) >= axes.bottom() - 2.0f);
PLOT_CHECK(static_cast<plot::f32>(ink.top) <= axes.bottom());
PLOT_CHECK(predicate_pixels(frame, kW, kFrameRect, ruler_ink_any) ==
predicate_pixels(frame, kW, axes, ruler_ink_any));
PLOT_CHECK(predicate_pixels(frame, kW, p.layout().x_label_area, ruler_ink_any) == 0u);
PLOT_CHECK(predicate_pixels(frame, kW, p.layout().y_label_area, ruler_ink_any) == 0u);
}
// Dragged past the *top*: clamped to the range's maximum, and the clamped ruler is still
@@ -4512,6 +4529,34 @@ PLOT_TEST(horizontal_ruler_stays_inside_the_axes_at_the_edges) {
PLOT_CHECK(predicate_pixels(frame, kW, p.layout().y_label_area, ruler_pixel) == 0u);
}
// A degenerate y range (min == max) is widened by +/-0.5 in the transform, so the one
// value it holds maps to the middle of the axes: a ruler there is genuinely in range and
// must be drawn -- the strict far-edge rule cannot hide it. An inverted range is widened
// the same way, so a value strictly between its bounds also maps inside and draws; only
// the two bounds land ~0.5 data units outside the widened span, which is a row outside the
// rect and correctly skipped (the old rule skipped them too, so this is not a regression).
p.set_y_range(ay, {50.0, 50.0});
p.set_cursor_y(ruler, 50.0);
PLOT_CHECK_NEAR(p.cursor(ruler).y, 50.0, 1e-9);
{
const std::vector<plot::u8> frame = frame_now();
const InkExtent ink = ink_extent(frame, kW, kH, ruler_pixel);
PLOT_CHECK(ink.pixels > 0u);
PLOT_CHECK(predicate_pixels(frame, kW, kFrameRect, ruler_pixel) ==
predicate_pixels(frame, kW, axes, ruler_pixel));
}
p.set_y_range(ay, {100.0, 0.0});
p.set_cursor_y(ruler, 50.0);
PLOT_CHECK_NEAR(p.cursor(ruler).y, 50.0, 1e-9);
{
const std::vector<plot::u8> frame = frame_now();
const InkExtent ink = ink_extent(frame, kW, kH, ruler_pixel);
PLOT_CHECK(ink.pixels > 0u);
PLOT_CHECK(predicate_pixels(frame, kW, kFrameRect, ruler_pixel) ==
predicate_pixels(frame, kW, axes, ruler_pixel));
}
p.set_y_range(ay, {0.0, 100.0});
// The direct setter is verbatim -- a caller may park a ruler off range -- and the frame
// still holds the ink in: an off-range ruler is skipped rather than drawn over a strip.
p.set_cursor_y(ruler, 500.0);