test(input): pin the dragged nearest-tick ruler's snap on drop

This commit is contained in:
Martino Ferrari committed 2026-09-28 20:54:58 +02:00
1 parent d2ba67921e
commit 2307aa1158
1 file changed
+47 -1
+47 -1
View File
@@ -366,7 +366,7 @@ PLOT_TEST(key_f_auto_fits_traces) {
/// The grab radius is a distance the user aims with, so it must be the same
/// *physical* distance at any DPI: the controller compares device-pixel coordinates
/// against `logical * Plot::ui_scale()`. The press positions below are the same
/// logical distance (0.9 and 1.1 of the 5 px radius) from the cursor at both scales,
/// logical distance (0.9 and 1.1 of the 7 px radius) from the cursor at both scales,
/// so the same press grabs -- and the same press just outside does not.
PLOT_TEST(grab_radius_scales_with_dpi) {
for (const f32 scale : {1.0f, 2.0f}) {
@@ -1053,6 +1053,52 @@ PLOT_TEST(horizontal_ruler_follows_the_pointer_on_an_inverted_y_range) {
PLOT_CHECK(f.plot.cursor(ruler).y > 50.0);
}
/// The horizontal half of `cursor_snaps_when_the_drag_ends`: an `HRulerSnap::NearestTick` ruler
/// dragged through the controller writes the free pointer row for the whole gesture and lands on
/// a y tick only on release -- the snap-on-drop a marker now uses, which a ruler did *not* before
/// (it snapped continuously inside `set_cursor_y`). This is the case that catches a regression
/// to that continuous behaviour: mid-drag the stored y is on no tick and equals the pointer's row,
/// and only the drop moves it to the grid.
PLOT_TEST(a_dragged_nearest_tick_ruler_snaps_only_when_the_drag_ends) {
Fixture f;
f.ay = f.plot.add_y_axis({"v", {0.0, 100.0}, false, plot::AxisPlace::Left, false});
const u32 ruler = f.plot.add_h_ruler(40.0, plot::Color::hex(0x35D0A8));
f.plot.set_cursor_h_snap(ruler, plot::HRulerSnap::NearestTick);
f.plot.layout();
const std::vector<plot::Tick>& ticks = f.plot.y_axis(f.ay).ticks;
PLOT_CHECK(ticks.size() >= 3u);
// A row between two adjacent ticks, nearer the lower-valued one (the larger screen row).
const f64 lo = ticks[1].value;
const f64 hi = ticks[2].value;
PLOT_CHECK(lo < hi);
const f64 between = lo + 0.4 * (hi - lo); // equal to no tick, nearer `lo`
const auto is_tick = [&ticks](f64 y) {
for (const plot::Tick& t : ticks) {
if (std::fabs(t.value - y) < 1e-9) return true;
}
return false;
};
PLOT_CHECK(!is_tick(between));
const f32 column = screen_x_of(f.plot, f.ax, 70.0);
const f32 to = static_cast<f32>(row_of(f.plot, f.ay, between));
const f32 from = static_cast<f32>(row_of(f.plot, f.ay, f.plot.cursor(ruler).y));
press(f.controller, column, from);
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveCursor);
PLOT_CHECK(f.controller.grabbed_cursor() == ruler);
move_to(f.controller, column, to);
// Mid-drag the stored y is the free pointer row and on no tick: the snap has not run.
PLOT_CHECK_NEAR(f.plot.cursor(ruler).y, data_at_row(f.plot, f.ay, to), 1e-9);
PLOT_CHECK(!is_tick(f.plot.cursor(ruler).y));
// The drop commits the snap: the stored y is the nearest tick and it was not before.
release(f.controller, column, to);
PLOT_CHECK_NEAR(f.plot.cursor(ruler).y, lo, 1e-9);
PLOT_CHECK(is_tick(f.plot.cursor(ruler).y));
}
/// Nearest-first resolution *across* orientations: a pointer within reach of both a marker and a
/// ruler takes the nearer of the two, so neither widens the other's target -- which is what lets
/// one hit test serve two instruments. Each press below is 1 px from its winner and 4 px from the