feat(input): draggable cursors, regions and measurement-band edges

This commit is contained in:
Martino Ferrari committed 2026-09-28 13:47:35 +02:00
1 parent f423cf6f99
commit 354e79eea2
2 files changed
+289

No files matched your search

+62
View File
@@ -1093,3 +1093,65 @@ PLOT_TEST(band_edge_drag_moves_the_owning_cursor_and_updates_the_measurement) {
PLOT_CHECK_NEAR(narrowed.ymax, 50.0, 1e-9);
release(f.controller, to_b);
}
/// A region is geometry in *data* coordinates, and `XFollow::Live` recomputes the x window
/// on every layout -- so the two claims that matter under a live axis are that the region
/// stays exactly where it was put in data space while the view scrolls under it, and that
/// dragging one moves the region rather than being read as a change to the range. The
/// live path owns the range, so both halves are things it could plausibly break: a region
/// measured in pixels would drift with the window, and a drag that reached the range
/// setters would move the view *and* clear nothing good.
PLOT_TEST(region_holds_in_data_space_under_live_follow) {
LiveFixture f(40, 200); // a saturated ring: the window slides, its width does not
f.live();
f.graph.layout();
const f64 window_span = f.xr().span();
const f64 x0 = f.xr().min + 0.25 * window_span;
const f64 x1 = f.xr().min + 0.65 * window_span;
const u32 rid = f.graph.add_region(free_region(x0, x1));
PLOT_CHECK_NEAR(f.graph.region(rid).x0, x0, 1e-12);
PLOT_CHECK_NEAR(f.graph.region(rid).x1, x1, 1e-12);
const f32 left_before = bound_x(f.graph, rid, true);
const f32 right_before = bound_x(f.graph, rid, false);
PLOT_CHECK(left_before >= 0.0f && right_before > left_before);
// The window advances on its own, which is what the follow path is for.
const f64 newest_before = f.newest();
f.feed(3);
f.graph.layout();
PLOT_CHECK(f.xr().max > newest_before);
PLOT_CHECK(f.graph.x_follow(f.ax) == plot::XFollow::Live);
// The region's own interval is untouched, to the last bit ...
PLOT_CHECK_NEAR(f.graph.region(rid).x0, x0, 1e-12);
PLOT_CHECK_NEAR(f.graph.region(rid).x1, x1, 1e-12);
// ... while the pixels it is drawn in moved: the view scrolled under a region that
// stayed in data space.
PLOT_CHECK(bound_x(f.graph, rid, true) < left_before);
PLOT_CHECK(bound_x(f.graph, rid, false) < right_before);
// A body drag moves the region and nothing else: not the window, not the follow state.
const plot::Range held = f.xr();
const f32 mid = 0.5f * (bound_x(f.graph, rid, true) + bound_x(f.graph, rid, false));
const f32 to = mid + 40.0f;
const f64 delta = data_at(f.graph, f.ax, to) - data_at(f.graph, f.ax, mid);
press(f.controller, mid);
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveRegion);
PLOT_CHECK(f.controller.grabbed_region() == rid);
move_to(f.controller, to);
PLOT_CHECK_NEAR(f.graph.region(rid).x0, x0 + delta, 1e-9);
PLOT_CHECK_NEAR(f.graph.region(rid).x1, x1 + delta, 1e-9);
PLOT_CHECK_NEAR(f.xr().min, held.min, 1e-9); // the range the follow path owns is untouched
PLOT_CHECK_NEAR(f.xr().max, held.max, 1e-9);
PLOT_CHECK(f.graph.x_follow(f.ax) == plot::XFollow::Live);
release(f.controller, to);
PLOT_CHECK(f.graph.x_follow(f.ax) == plot::XFollow::Live);
PLOT_CHECK_NEAR(f.graph.region(rid).x0, x0 + delta, 1e-9);
PLOT_CHECK_NEAR(f.graph.region(rid).x1, x1 + delta, 1e-9);
// And the region is still where the pointer left it once more samples arrive: the
// window moves, the region does not.
f.feed(2);
f.graph.layout();
PLOT_CHECK(f.xr().max > held.max);
PLOT_CHECK_NEAR(f.graph.region(rid).x0, x0 + delta, 1e-12);
PLOT_CHECK_NEAR(f.graph.region(rid).x1, x1 + delta, 1e-12);
}