test(input): pin the region snap's once-on-release rule and the live-follow data anchoring

This commit is contained in:
Martino Ferrari committed 2026-09-28 13:50:52 +02:00
1 parent 354e79eea2
commit 5e610bc138
1 file changed
+49
+49
View File
@@ -1155,3 +1155,52 @@ PLOT_TEST(region_holds_in_data_space_under_live_follow) {
PLOT_CHECK_NEAR(f.graph.region(rid).x0, x0 + delta, 1e-12); PLOT_CHECK_NEAR(f.graph.region(rid).x0, x0 + delta, 1e-12);
PLOT_CHECK_NEAR(f.graph.region(rid).x1, x1 + delta, 1e-12); PLOT_CHECK_NEAR(f.graph.region(rid).x1, x1 + delta, 1e-12);
} }
/// `RegionSnap::NearestSample` is applied **once**, on release -- not on every motion. A
/// body dragged a fraction of a sample would otherwise snap both ends back onto the
/// samples they started on, and the region would never move at all while the pointer did;
/// a region that never snapped would instead end its drag between two samples, which is
/// what the snap exists to prevent. Both halves are observable, and one drag shows both.
PLOT_TEST(region_snap_is_applied_once_on_release) {
constexpr u32 kSamples = 120u;
std::vector<f32> samples(kSamples);
for (u32 i = 0; i < kSamples; ++i) samples[i] = static_cast<f32>(i);
plot::data::ArraySeries s(std::move(samples), 0.0, 1.0); // the sample grid is the integers
Fixture f;
f.ay = f.plot.add_y_axis({"v", {0.0, 120.0}, false, plot::AxisPlace::Left, false});
f.plot.add_trace({&s, plot::Color::hex(0x40C0FF), 1.5f, f.ax, f.ay});
plot::Region snapping; // the default: NearestSample
snapping.x0 = 10.4;
snapping.x1 = 20.6;
const u32 rid = f.plot.add_region(snapping);
// On the way in, both bounds land on the grid the traces define.
PLOT_CHECK_NEAR(f.plot.region(rid).x0, 10.0, 1e-9);
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 21.0, 1e-9);
// The contrast: a Free region keeps what it was given. It is placed clear of the snapped
// one, because two bodies under the pointer resolve to the region on top -- the later
// one -- and this case is about the drag of *this* region.
plot::Region kept = snapping;
kept.x0 = 40.4;
kept.x1 = 50.6;
kept.snap = plot::RegionSnap::Free;
const u32 free_id = f.plot.add_region(kept);
PLOT_CHECK_NEAR(f.plot.region(free_id).x0, 40.4, 1e-12);
PLOT_CHECK_NEAR(f.plot.region(free_id).x1, 50.6, 1e-12);
// Drag the snapped region's body by six tenths of a sample.
const f32 mid = 0.5f * (bound_x(f.plot, rid, true) + bound_x(f.plot, rid, false));
const f32 to = column_of(f.plot, f.ax, data_at(f.plot, f.ax, mid) + 0.6);
press(f.controller, mid);
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveRegion);
move_to(f.controller, to);
// Mid-drag the bounds are exactly where the pointer put them: no snapping yet.
PLOT_CHECK_NEAR(f.plot.region(rid).x0, 10.6, 1e-3);
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 21.6, 1e-3);
release(f.controller, to);
// On release they are pulled onto the grid, once, and both ends moved.
PLOT_CHECK_NEAR(f.plot.region(rid).x0, 11.0, 1e-9);
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 22.0, 1e-9);
PLOT_CHECK(f.plot.region(rid).x1 > f.plot.region(rid).x0); // the interval survived both ends moving
}