feat: an open box zoom draws the frame the release will apply

The controller has always tracked its selection; nothing drew it, so a Ctrl or
Shift drag framed a range with no feedback until the release.

The preview is the area the release will change, not what the pointer swept:
Ctrl frames the x range only, so a band across the plot is what it will zoom,
where the sliver a straight horizontal drag sweeps is only the gesture. The
plot carries the rect a host hands it -- it learns nothing about pointers --
and the renderer draws a low-alpha fill under a light border over everything
else inside the axes.
This commit is contained in:
Martino Ferrari committed 2026-10-11 11:07:55 +02:00
1 parent 1fc97d818f
commit be1f31a04f
14 files changed
+276 -1

No files matched your search

+51
View File
@@ -2403,3 +2403,54 @@ PLOT_TEST(an_unselectable_cursor_is_neither_hovered_nor_grabbed) {
PLOT_CHECK_NEAR(p.cursor(readout).x, before, 1e-12);
PLOT_CHECK(c.grabbed_cursor() == normal);
}
PLOT_TEST(a_ctrl_box_zoom_previews_the_band_it_will_zoom) {
// What a host draws for an open box zoom is not always the area the pointer swept. Ctrl frames the
// x range only, so what its release will change is a *band* across the plot -- and a pointer dragged
// straight across sweeps a sliver a couple of pixels tall, which would be the frame showing the
// gesture instead of its effect. Shift frames both axes and gets the swept box itself; Alt is the
// other single-axis mask, and gets the other frame.
plot::Plot p;
p.set_viewport({0, 0, 800, 400});
p.add_x_axis({"t", {0.0, 800.0}, true, plot::AxisPlace::Bottom, false});
const plot::Rect axes = p.layout().axes;
constexpr plot::u32 kAlt = static_cast<plot::u32>(plot::input::Mod::Alt);
plot::input::Controller c(p);
c.set_viewport({0, 0, 800, 400});
c.on_pointer({100, 200, 0, 0, plot::input::Button::Left, kCtrl, true, true, false});
// Nothing framed until something was swept: the press alone is an empty rect at the pointer, and a
// full-width band derived from it would claim a range that does not exist yet.
PLOT_CHECK_NEAR(c.selection_preview().w, 0.0, 1e-6);
c.on_pointer({300, 202, 200, 2, plot::input::Button::Left, kCtrl, true, false, false});
const plot::Rect ctrl = c.selection_preview();
PLOT_CHECK_NEAR(ctrl.x, 100.0, 1e-6);
PLOT_CHECK_NEAR(ctrl.w, 200.0, 1e-6);
PLOT_CHECK_NEAR(ctrl.y, axes.y, 1e-6);
PLOT_CHECK_NEAR(ctrl.h, axes.h, 1e-6);
// The gesture's own geometry is untouched: the widening happens in the preview, so the range the
// release computes is still the one the pointer described.
PLOT_CHECK_NEAR(c.selection_rect().h, 2.0, 1e-6);
c.on_pointer({300, 202, 0, 0, plot::input::Button::Left, kCtrl, false, false, true});
c.on_pointer({100, 50, 0, 0, plot::input::Button::Left, kShift, true, true, false});
c.on_pointer({400, 150, 300, 100, plot::input::Button::Left, kShift, true, false, false});
const plot::Rect both = c.selection_preview();
PLOT_CHECK_NEAR(both.x, 100.0, 1e-6);
PLOT_CHECK_NEAR(both.y, 50.0, 1e-6);
PLOT_CHECK_NEAR(both.w, 300.0, 1e-6);
PLOT_CHECK_NEAR(both.h, 100.0, 1e-6);
c.on_pointer({400, 150, 0, 0, plot::input::Button::Left, kShift, false, false, true});
c.on_pointer({100, 50, 0, 0, plot::input::Button::Left, kAlt, true, true, false});
c.on_pointer({110, 150, 10, 100, plot::input::Button::Left, kAlt, true, false, false});
const plot::Rect alt = c.selection_preview();
PLOT_CHECK_NEAR(alt.x, axes.x, 1e-6);
PLOT_CHECK_NEAR(alt.w, axes.w, 1e-6);
PLOT_CHECK_NEAR(alt.y, 50.0, 1e-6);
PLOT_CHECK_NEAR(alt.h, 100.0, 1e-6);
c.on_pointer({110, 150, 0, 0, plot::input::Button::Left, kAlt, false, false, true});
// Closed: a frame on a finished gesture would claim the old selection is still open.
PLOT_CHECK(c.drag_mode() == plot::input::DragMode::None);
PLOT_CHECK_NEAR(c.selection_preview().w, 0.0, 1e-6);
}