fix(input): refuse non-finite region bounds, keep narrow regions movable, republish hot handles

This commit is contained in:
Martino Ferrari committed 2026-09-28 14:08:11 +02:00
1 parent 4681d7015b
commit 7cc06f07f5
7 files changed
+263 -19

No files matched your search

+21 -7
View File
@@ -83,16 +83,22 @@ using plot::RegionGrab;
/// dragging either of the cursors that bound it, because its edges *are* those
/// cursors -- no second path, no second tolerance.
/// 2. **Regions next**, resolved nearest-first against the geometry `Plot::layout()`
/// measured: a bound inside the grab radius beats the body (so the bound of a
/// measured: a bound within the grab radius beats the body (so the bound of a
/// wide region is grabbable even though the pointer is inside the fill), the
/// nearer of the two bounds wins, and with no bound in range the topmost region
/// whose interior holds the pointer is the body.
/// whose interior holds the pointer is the body. The radius is the one above, cut
/// back when a region is too narrow to hold a band at each end *and* an interior:
/// a region narrower than twice it would otherwise have every interior point
/// within a bound's reach, and could be resized but never moved.
/// 3. **Nothing hot**, and the press pans.
///
/// A region narrower than a few logical pixels is a click rather than an edge grab:
/// its two bounds are under the pointer together, so an edge would always win and
/// the region could never be moved again once collapsed. Such a region is
/// grabbed by its body instead, widened to that same minimum, and it stays movable.
/// grabbed by its body instead, widened to that same minimum, and it stays movable --
/// and between that minimum and twice the grab radius, where the two bounds would
/// still cover every interior point, the edge bands are cut back until the middle is
/// left, so a region in that band of widths can be moved as well as resized.
/// The *body* drag translates both bounds by one delta -- the width is preserved
/// exactly -- while a bound drag moves one end and leaves the other where it was,
/// so a drag that crosses one bound over the other swaps them rather than inverting
@@ -103,7 +109,8 @@ using plot::RegionGrab;
/// both ends back to where they started and never move at all.
///
/// The hovered and grabbed handles are published to the plot
/// (`Plot::set_hot_handles()`) after every pointer event, so the renderer draws them
/// (`Plot::set_hot_handles()`) after every event that can move the geometry under the
/// pointer -- a pointer event, a wheel notch, a key stroke -- so the renderer draws them
/// highlighted without knowing what a pointer is; `hovered_cursor()`,
/// `grabbed_cursor()`, `hovered_region()`, `grabbed_region()` and `region_grab()` are
/// the same answer for a host that wants it directly -- a resize cursor, a tooltip,
@@ -190,7 +197,8 @@ class Controller {
/// The region the pointer is on, or `kInvalid`. `region_grab()` says which zone.
u64 hovered_region() const;
/// Which zone of `region` the pointer is on: the bound nearest within the grab
/// radius (`Left`/`Right`), else `Body` when the pointer is inside the interval --
/// radius (`Left`/`Right`) -- cut back for a narrow region, see `zone_hit()` -- else
/// `Body` when the pointer is inside the interval --
/// widened to the minimum a collapsed region keeps -- and `None` when it is on
/// neither, when the pointer is outside the plot area, or when no pointer has been
/// seen yet. What a host sets its resize cursor from; an id that names no region
@@ -229,6 +237,10 @@ class Controller {
RegionGrab zone = RegionGrab::None;
f32 distance = 0.0f;
};
/// The band a bound answers to is the controller's grab radius, cut back so that a
/// narrow region keeps an interior a body drag can reach (see the class comment):
/// `min(radius, (width - minimum interior) / 2)`, and no band at all when the two
/// bounds are closer together than the minimum width that separates them.
ZoneHit zone_hit(const RegionLayout& region, f32 px, f32 py) const;
/// Resolves that hit against the geometry `Plot::layout()` measured -- the same
/// rects the renderer paints, so what is drawn and what can be grabbed cannot
@@ -242,8 +254,10 @@ class Controller {
/// the width -- so a changed pointer motion never accumulates error.
bool begin_handle_drag(const PointerEvent& e);
/// Publishes what the pointer is on to the plot, for the renderer to highlight
/// (see HotHandles). Called after every pointer event, so hover and grab are the
/// same answer the hit test gave.
/// (see HotHandles). Called after every event that can move the geometry under the
/// pointer -- a pointer event, a wheel notch, a key stroke -- so hover and grab are
/// always the answer the hit test gives for the geometry of this frame rather than
/// the geometry of the last time the pointer moved.
void publish_hot();
/// The data x a zoom of `axis` holds fixed, for a gesture at (px, py). The