fix(input): pan and box zoom through layout().axes, not the viewport
Every pixel-to-data conversion in the controller now maps through the same rectangle Plot::x_transform/y_transform are built from: - pan() derives the scale from layout().axes.w/.h, so a drag moves the grabbed data point by exactly the drag distance (the viewport model stranded it behind the pointer by the label-strip fraction). - apply_box_zoom() clamps the selection to layout().axes and sets each range from the transforms (x_transform().to_data_x(l/r), y_transform() .to_data_y(bottom/top)); the selection's bottom edge is the y minimum. - end_drag() takes the release event and refreshes the selection from its coordinates before applying, so a host that reports only press and release still frames the box instead of degrading it to a click. - axis_zoom() uses a per-pixel sensitivity (200 px == one decade) rather than reusing the wheel's per-notch constant, so a pixel drag is no longer treated as a wheel notch. Tests now assert the grab-and-drag invariant through the transform (the old pixel-fraction assertions certified the viewport defect), add a press+release-only box zoom, and pin the axis-zoom magnitude.
This commit is contained in:
1 parent
5ab854104a
commit
e0cdd95a3b
4 files changed
+106
-55
No files matched your search
@@ -61,16 +61,21 @@ PLOT_TEST(left_drag_pans_without_changing_span) {
|
||||
plot::input::Controller c(p);
|
||||
c.set_viewport({0, 0, 800, 400});
|
||||
const double span_before = p.x_range(ax).span();
|
||||
const f64 grabbed = p.x_transform(ax).to_data_x(100.0f); // data under the press point
|
||||
c.on_pointer({100, 100, 0, 0, plot::input::Button::Left, 0, true, true, false});
|
||||
PLOT_CHECK(c.drag_mode() == plot::input::DragMode::Pan);
|
||||
c.on_pointer({180, 130, 80, 30, plot::input::Button::Left, 0, true, false, false});
|
||||
c.on_pointer({180, 130, 0, 0, plot::input::Button::Left, 0, false, false, true});
|
||||
PLOT_CHECK(c.drag_mode() == plot::input::DragMode::None);
|
||||
PLOT_CHECK_NEAR(p.x_range(ax).span(), span_before, 1e-9); // span unchanged
|
||||
// 80 px right-drag on an 800 px plot showing [0, 100] reveals 10 units of
|
||||
// earlier data: the data follows the cursor. (The brief printed +10.0 here.)
|
||||
PLOT_CHECK_NEAR(p.x_range(ax).min, -10.0, 1e-6);
|
||||
// Assert the INVARIANT, not a pixel fraction. Grab-and-drag means the data
|
||||
// grabbed at the press point is under the pointer afterwards. Pixels map to
|
||||
// data through layout().axes, NOT the whole viewport: the viewport model makes
|
||||
// dragged data lag the cursor by the label-strip fraction, a defect no
|
||||
// pixel-fraction assertion can see because it certifies its own arithmetic.
|
||||
PLOT_CHECK_NEAR(p.x_transform(ax).to_data_x(180.0f), grabbed, 1e-9);
|
||||
PLOT_CHECK(p.y_range(ay).max > 1.0); // moving down raises y range
|
||||
PLOT_CHECK(!p.x_axis(ax).auto_scale); // a pan is a user choice, not a fit
|
||||
}
|
||||
|
||||
PLOT_TEST(shift_drag_box_zooms_to_selection) {
|
||||
@@ -80,14 +85,39 @@ PLOT_TEST(shift_drag_box_zooms_to_selection) {
|
||||
plot::input::Controller c(p);
|
||||
c.set_viewport({0, 0, 800, 400});
|
||||
const u32 e_shift = 1u << 0; // Mod::Shift
|
||||
const f64 want_lo = p.x_transform(ax).to_data_x(100.0f);
|
||||
const f64 want_hi = p.x_transform(ax).to_data_x(300.0f);
|
||||
c.on_pointer({100, 50, 0, 0, plot::input::Button::Left, e_shift, true, true, false});
|
||||
PLOT_CHECK(c.drag_mode() == plot::input::DragMode::BoxZoom);
|
||||
c.on_pointer({300, 150, 200, 100, plot::input::Button::Left, e_shift, true, false, false});
|
||||
PLOT_CHECK_NEAR(c.selection_rect().x, 100.0, 1e-6);
|
||||
PLOT_CHECK_NEAR(c.selection_rect().w, 200.0, 1e-6);
|
||||
c.on_pointer({300, 150, 0, 0, plot::input::Button::Left, e_shift, false, false, true});
|
||||
PLOT_CHECK_NEAR(p.x_range(ax).min, 100.0, 1e-6);
|
||||
PLOT_CHECK_NEAR(p.x_range(ax).max, 300.0, 1e-6);
|
||||
// The box frames exactly the data it encloses: the ranges come from the axes
|
||||
// transform, and the selection is clamped to layout().axes, not the viewport.
|
||||
PLOT_CHECK_NEAR(p.x_range(ax).min, want_lo, 1e-6);
|
||||
PLOT_CHECK_NEAR(p.x_range(ax).max, want_hi, 1e-6);
|
||||
}
|
||||
|
||||
PLOT_TEST(box_zoom_uses_release_position_without_a_move) {
|
||||
// A host that reports only a press and a release -- no motion event between
|
||||
// them -- MUST still frame the box the release describes: the release position
|
||||
// is authoritative for the end of the gesture.
|
||||
plot::Plot p;
|
||||
p.set_viewport({0, 0, 800, 400});
|
||||
const u32 ax = p.add_x_axis({"t", {0.0, 800.0}, true, plot::AxisPlace::Bottom, false});
|
||||
plot::input::Controller c(p);
|
||||
c.set_viewport({0, 0, 800, 400});
|
||||
const f64 want_lo = p.x_transform(ax).to_data_x(100.0f);
|
||||
const f64 want_hi = p.x_transform(ax).to_data_x(300.0f);
|
||||
c.on_pointer({100, 50, 0, 0, plot::input::Button::Left, kShift, true, true, false});
|
||||
PLOT_CHECK(c.drag_mode() == plot::input::DragMode::BoxZoom);
|
||||
// No move: selection_ is still 0x0 until the release supplies the far corner.
|
||||
c.on_pointer({300, 150, 0, 0, plot::input::Button::Left, kShift, false, false, true});
|
||||
PLOT_CHECK(c.drag_mode() == plot::input::DragMode::None);
|
||||
PLOT_CHECK_NEAR(p.x_range(ax).min, want_lo, 1e-6);
|
||||
PLOT_CHECK_NEAR(p.x_range(ax).max, want_hi, 1e-6);
|
||||
PLOT_CHECK(!p.x_axis(ax).auto_scale); // a box zoom is a user choice, not a fit
|
||||
}
|
||||
|
||||
PLOT_TEST(auto_scale_disabled_by_user_interaction) {
|
||||
@@ -179,7 +209,9 @@ PLOT_TEST(middle_drag_on_x_strip_zooms_x_only) {
|
||||
// Same sign convention as the wheel: a positive dy zooms in.
|
||||
f.controller.on_pointer({strip_x, strip_y + 20, 0, 20, plot::input::Button::Middle, 0, true, false, false});
|
||||
f.controller.on_pointer({strip_x, strip_y + 20, 0, 0, plot::input::Button::Middle, 0, false, false, true});
|
||||
PLOT_CHECK(f.plot.x_range(f.ax).span() < 100.0);
|
||||
// 20 px of drag is a tenth of a decade: the span is 10^(-20/200) of its
|
||||
// former self. A pixel is not a wheel notch, so this pins the magnitude.
|
||||
PLOT_CHECK_NEAR(f.plot.x_range(f.ax).span(), 100.0 * std::pow(10.0, -20.0 / 200.0), 1e-6);
|
||||
PLOT_CHECK_NEAR(f.plot.y_range(f.ay).span(), 2.0, 1e-9);
|
||||
PLOT_CHECK(!f.plot.x_axis(f.ax).auto_scale);
|
||||
PLOT_CHECK(f.plot.y_axis(f.ay).auto_scale); // the untouched y axis keeps auto scale
|
||||
@@ -196,7 +228,7 @@ PLOT_TEST(middle_drag_on_y_strip_zooms_y_only) {
|
||||
f.controller.on_pointer({strip_x, strip_y + 20, 0, 20, plot::input::Button::Middle, 0, true, false, false});
|
||||
f.controller.on_pointer({strip_x, strip_y + 20, 0, 0, plot::input::Button::Middle, 0, false, false, true});
|
||||
PLOT_CHECK_NEAR(f.plot.x_range(f.ax).span(), 100.0, 1e-9);
|
||||
PLOT_CHECK(f.plot.y_range(f.ay).span() < 2.0);
|
||||
PLOT_CHECK_NEAR(f.plot.y_range(f.ay).span(), 2.0 * std::pow(10.0, -20.0 / 200.0), 1e-6);
|
||||
}
|
||||
|
||||
PLOT_TEST(key_plus_zooms_in_about_the_centre) {
|
||||
|
||||
Reference in new issue
Block a user