fix(input): refuse a non-finite wheel amount instead of zeroing it

Zeroing an unusable wheel amount turned it into a zero-notch zoom: the factor was
exactly one, the range did not change, but the zoom body still ran through
set_x_range/set_y_range, which clear auto_scale unconditionally. One garbage wheel
therefore disarmed auto-fit on every axis. on_wheel now rejects an unusable amount
before the event can mutate anything, keeping the position fallback.

The tests that should have caught it are strengthened: the wheel case now asserts
auto scale is still armed (it was passing either way), and the invalid-position
case checks a pan measured from the last real position with the drag alive, rather
than a range nothing could have moved.
This commit is contained in:
Martino Ferrari committed 2026-09-28 01:03:07 +02:00
1 parent b5b9b42be1
commit dc0a977c86
4 files changed
+41 -6

No files matched your search

+11 -2
View File
@@ -66,14 +66,16 @@ PointerEvent sanitized(const PointerEvent& e, bool have_pointer, f32 last_x,
return in;
}
/// `e`, with an unusable position replaced by the last known one. The wheel's
/// amount is deliberately left alone: `on_wheel` rejects an unusable one *before*
/// the event is acted on, because a zeroed amount is a zoom of exactly one, and
/// running that over an unchanged range would still clear every axis' auto scale.
WheelEvent sanitized(const WheelEvent& e, bool have_pointer, f32 last_x, f32 last_y) noexcept {
WheelEvent in = e;
if (!finite(in.x) || !finite(in.y)) {
in.x = have_pointer ? last_x : 0.0f;
in.y = have_pointer ? last_y : 0.0f;
}
if (!finite(in.dx)) in.dx = 0.0f;
if (!finite(in.dy)) in.dy = 0.0f;
return in;
}
@@ -106,6 +108,13 @@ void Controller::on_pointer(const PointerEvent& raw) {
}
void Controller::on_wheel(const WheelEvent& raw) {
// An amount that is not a number is not a wheel: sanitising it to zero would look
// like a zero-notch zoom -- a factor of exactly one -- and the zoom body would
// still run, calling the range setters with the unchanged range. Those clear
// `auto_scale` unconditionally, so a single garbage wheel would silently disarm
// auto-fit on every axis. Return before the event can mutate anything.
if (!std::isfinite(raw.dy)) return;
const WheelEvent e = sanitized(raw, has_pointer_, pointer_x_, pointer_y_);
plot_->layout();
const f64 factor = zoom_factor(static_cast<f64>(e.dy));