fix(imgui): keep the plot inside the child's clip rect and reject unusable input
The widget drew a region derived from the child's content rect alone, so a child the hosting window cut off cleared and painted over the host's pixels; it also scaled ImGui's (-FLT_MAX, -FLT_MAX) no-pointer sentinel and differenced it against the last real position, turning a drag out of the window into a ~1e35 pan, and it forgot the host's button levels across a re-bind, handing the next plot a press that never happened. The region is now the content rect intersected with the child's clip rect (a no-op when nothing clips it), an invalid position is no position update, and the button levels survive a re-bind. The controller sanitises non-finite coordinates and deltas for any host, and the widget's input translation now has a committed test on the headless EGL context.
This commit is contained in:
1 parent
15931a2405
commit
b5b9b42be1
8 files changed
+697
-58
No files matched your search
@@ -39,6 +39,44 @@ f64 from_space(f64 v, bool log) noexcept {
|
||||
/// key) zoom in, so the span shrinks by `exp(-notches * kZoomPerNotch)`.
|
||||
f64 zoom_factor(f64 notches) noexcept { return std::exp(-notches * kZoomPerNotch); }
|
||||
|
||||
/// True when a coordinate is one arithmetic can work with. Every event here comes
|
||||
/// from a host, and a host can hand over anything: a toolkit's "no pointer"
|
||||
/// sentinel, a NaN from a degenerate mapping of its own, an infinity from a lost
|
||||
/// device. A non-finite value rides the pan and zoom arithmetic into a range that
|
||||
/// is no longer a range, and since a gesture that sets a range also clears auto
|
||||
/// scale, `f` would be the only way back -- so it must not get in.
|
||||
bool finite(f32 v) noexcept { return std::isfinite(v); }
|
||||
|
||||
/// `e`, with an unusable position replaced by the last known one and an unusable
|
||||
/// delta by no movement. Sanitising rather than rejecting keeps the buttons in the
|
||||
/// same event alive: a drag whose release arrived with a garbage position must
|
||||
/// still end.
|
||||
PointerEvent sanitized(const PointerEvent& e, bool have_pointer, f32 last_x,
|
||||
f32 last_y) noexcept {
|
||||
PointerEvent 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;
|
||||
in.dx = 0.0f;
|
||||
in.dy = 0.0f;
|
||||
return in;
|
||||
}
|
||||
if (!finite(in.dx)) in.dx = 0.0f;
|
||||
if (!finite(in.dy)) in.dy = 0.0f;
|
||||
return in;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Controller::set_viewport(Rect px) {
|
||||
@@ -49,7 +87,8 @@ void Controller::set_viewport(Rect px) {
|
||||
plot_->layout();
|
||||
}
|
||||
|
||||
void Controller::on_pointer(const PointerEvent& e) {
|
||||
void Controller::on_pointer(const PointerEvent& raw) {
|
||||
const PointerEvent e = sanitized(raw, has_pointer_, pointer_x_, pointer_y_);
|
||||
has_pointer_ = true;
|
||||
pointer_x_ = e.x;
|
||||
pointer_y_ = e.y;
|
||||
@@ -66,7 +105,8 @@ void Controller::on_pointer(const PointerEvent& e) {
|
||||
if (e.down && mode_ != DragMode::None) update_drag(e);
|
||||
}
|
||||
|
||||
void Controller::on_wheel(const WheelEvent& e) {
|
||||
void Controller::on_wheel(const WheelEvent& raw) {
|
||||
const WheelEvent e = sanitized(raw, has_pointer_, pointer_x_, pointer_y_);
|
||||
plot_->layout();
|
||||
const f64 factor = zoom_factor(static_cast<f64>(e.dy));
|
||||
if (!(factor > 0.0)) return;
|
||||
|
||||
Reference in new issue
Block a user