fix(input): refuse non-finite region bounds, keep narrow regions movable, republish hot handles
This commit is contained in:
1 parent
4681d7015b
commit
7cc06f07f5
7 files changed
+263
-19
No files matched your search
@@ -1204,3 +1204,105 @@ PLOT_TEST(region_snap_is_applied_once_on_release) {
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 22.0, 1e-9);
|
||||
PLOT_CHECK(f.plot.region(rid).x1 > f.plot.region(rid).x0); // the interval survived both ends moving
|
||||
}
|
||||
|
||||
/// A region the brief promises a pointer can still *move*: one whose bounds sit closer
|
||||
/// together than the fixed grab radius allows on both sides. With that radius kept on each
|
||||
/// bound, every point of a region narrower than twice it (10 logical px) lies within a
|
||||
/// bound's reach, so the edge branch always wins and the interior is unreachable -- such a
|
||||
/// region could be resized but never moved, and the collapsed-region rescue only engages
|
||||
/// below 3 px. The bands are therefore cut back until the middle is left, while a region
|
||||
/// wide enough for two full bands and an interior keeps the radius it always had.
|
||||
PLOT_TEST(narrow_region_stays_movable_by_its_body) {
|
||||
Fixture f;
|
||||
const f32 radius = f.plot.style().device(5.0f); // the controller's own grab radius, logical
|
||||
f32 column = 300.0f; // one strip of columns per region below
|
||||
|
||||
for (const f32 width_px : {4.0f, 6.0f, 9.0f}) {
|
||||
const f64 a = data_at(f.plot, f.ax, column);
|
||||
const f64 b = data_at(f.plot, f.ax, column + width_px);
|
||||
const u32 rid = f.plot.add_region(free_region(a, b));
|
||||
const f32 middle = column + 0.5f * width_px;
|
||||
|
||||
// Both bounds are grabbable: the edge still wins where the pointer is on it ...
|
||||
hover(f.controller, column);
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::Left);
|
||||
hover(f.controller, column + width_px);
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::Right);
|
||||
// ... and the middle is the body, so the region can be moved as well as resized.
|
||||
hover(f.controller, middle);
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::Body);
|
||||
press(f.controller, middle);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveRegion);
|
||||
PLOT_CHECK(f.controller.grabbed_region() == rid);
|
||||
move_to(f.controller, middle + 30.0f);
|
||||
const f64 delta = data_at(f.plot, f.ax, middle + 30.0f) - data_at(f.plot, f.ax, middle);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x0, a + delta, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1, b + delta, 1e-9); // moved, width intact
|
||||
release(f.controller, middle + 30.0f);
|
||||
column += 80.0f;
|
||||
}
|
||||
|
||||
// Where there is room the radius is unchanged: a press a few pixels inside a wide
|
||||
// region's bound is that bound and not the body.
|
||||
const u32 wide = f.plot.add_region(
|
||||
free_region(data_at(f.plot, f.ax, 600.0f), data_at(f.plot, f.ax, 700.0f)));
|
||||
hover(f.controller, 600.0f + radius - 1.0f);
|
||||
PLOT_CHECK(f.controller.region_grab(wide) == plot::RegionGrab::Left);
|
||||
press(f.controller, 600.0f + radius - 1.0f);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::ResizeRegionLeft);
|
||||
release(f.controller, 600.0f + radius - 1.0f);
|
||||
}
|
||||
|
||||
/// `publish_hot()` is the single writer of `HotHandles`, and the wheel and the keyboard
|
||||
/// change the geometry under a *stationary* pointer. Without a publish of their own, a
|
||||
/// frame drawn right after a scroll would highlight a handle the pointer has left -- or
|
||||
/// miss one it has just landed on -- until the next mouse move, which is exactly the
|
||||
/// stale-highlight defect the publish exists to prevent. The layout has to be refreshed
|
||||
/// with it: the region rects the hit test reads were measured for the range the zoom has
|
||||
/// just replaced.
|
||||
PLOT_TEST(hot_handles_follow_a_gesture_that_moves_the_geometry) {
|
||||
Fixture f;
|
||||
const u32 cid = f.plot.add_cursor(50.0, plot::Color::hex(0x00FF00));
|
||||
const u32 off_centre = f.plot.add_cursor(75.0, plot::Color::hex(0xFF00FF));
|
||||
const u32 rid = f.plot.add_region(free_region(70.0, 90.0));
|
||||
const f32 radius = f.plot.style().device(5.0f);
|
||||
|
||||
// The pointer sits four pixels to the right of the marker: on it, and published as such.
|
||||
const f32 px = screen_x_of(f.plot, f.ax, 50.0) + 4.0f;
|
||||
hover(f.controller, px);
|
||||
PLOT_CHECK(f.plot.hot_handles().cursor == cid);
|
||||
|
||||
// A zoom *in* about the pointer grows the marker's offset from it by the zoom factor --
|
||||
// 4 px becomes 6.3 -- so the marker leaves without the pointer moving at all.
|
||||
f.controller.on_wheel({px, 200.0f, 0.0f, 3.0f, 0u});
|
||||
PLOT_CHECK(std::fabs(screen_x_of(f.plot, f.ax, 50.0) - px) > radius);
|
||||
PLOT_CHECK(f.controller.hovered_cursor() == plot::kInvalid);
|
||||
PLOT_CHECK(f.plot.hot_handles().cursor == plot::kInvalid);
|
||||
|
||||
// And the other way round: a zoom out about the same column brings it back under the
|
||||
// stationary pointer, where the published state has to be the marker again.
|
||||
f.controller.on_wheel({px, 200.0f, 0.0f, -3.0f, 0u});
|
||||
PLOT_CHECK(std::fabs(screen_x_of(f.plot, f.ax, 50.0) - px) <= radius);
|
||||
PLOT_CHECK(f.controller.hovered_cursor() == cid);
|
||||
PLOT_CHECK(f.plot.hot_handles().cursor == cid);
|
||||
|
||||
// The keyboard path: a zoom about the plot centre moves the *other* marker, which the
|
||||
// pointer is on, out from under it.
|
||||
const f32 on_marker = screen_x_of(f.plot, f.ax, 75.0);
|
||||
hover(f.controller, on_marker);
|
||||
PLOT_CHECK(f.plot.hot_handles().cursor == off_centre);
|
||||
f.controller.on_key({plot::input::kKeyZoomIn, 0u, true});
|
||||
PLOT_CHECK(std::fabs(screen_x_of(f.plot, f.ax, 75.0) - on_marker) > radius);
|
||||
PLOT_CHECK(f.plot.hot_handles().cursor == plot::kInvalid);
|
||||
|
||||
// And a region bound: the zone that is published after the gesture is the zone the
|
||||
// geometry now gives, not the one the bound had before the zoom.
|
||||
const f32 bound = bound_x(f.plot, rid, true);
|
||||
hover(f.controller, bound);
|
||||
PLOT_CHECK(f.plot.hot_handles().region == rid);
|
||||
PLOT_CHECK(f.plot.hot_handles().zone == plot::RegionGrab::Left);
|
||||
f.controller.on_key({plot::input::kKeyZoomIn, 0u, true});
|
||||
PLOT_CHECK(std::fabs(bound_x(f.plot, rid, true) - bound) > radius); // the bound moved away
|
||||
PLOT_CHECK(f.plot.hot_handles().zone == f.controller.region_grab(rid));
|
||||
PLOT_CHECK(f.plot.hot_handles().zone != plot::RegionGrab::Left);
|
||||
}
|
||||
Reference in new issue
Block a user