fix(input): cursors follow the pointer while dragging and snap on drop

This commit is contained in:
Martino Ferrari committed 2026-09-28 20:46:14 +02:00
1 parent ea70cf4fca
commit d2ba67921e
7 files changed
+292 -27

No files matched your search

+20 -5
View File
@@ -28,15 +28,18 @@ constexpr f64 kZoomPerPixel = std::numbers::ln10 / 200.0;
/// radius the comparison needs through `Style::device()` at the plot's own scale
/// (see `cursor_at()`). An unscaled constant would make grabbing physically harder
/// -- half as forgiving -- on a 2x display, which is the defect the scale exists to
/// prevent.
constexpr f32 kCursorHitRadiusLogical = 5.0f;
/// prevent. Comfortably wider than a marker's 1 px stroke, so a press lands on the
/// handle the hover highlight promised: `cursor_at()` is the *one* value both the
/// hover and the grab read, and a region's edge bands are cut back from this same
/// number, so the affordance the host sees and the gesture it gets agree.
constexpr f32 kCursorHitRadiusLogical = 7.0f;
/// A box-zoom selection narrower than this on either side is a click. Logical for
/// the same reason: the gesture is the same hand movement on any display.
constexpr f32 kMinBoxLogical = 4.0f;
/// The narrowest *interior* a region's body keeps, in **logical** pixels: the edge bands are
/// cut back until this much of the middle is left, so a region too narrow for the full grab
/// radius on each side is still something a pointer can take hold of and *move*. Without it
/// every interior point of a region narrower than twice the radius (10 logical px) lies
/// every interior point of a region narrower than twice the radius (14 logical px) lies
/// within a bound's reach, and the bound branch would always win -- such a region could be
/// resized but never moved. Half of it either side of the midpoint, which is what makes the
/// middle of even the narrowest region with grabbable bounds (3 logical px) reachable.
@@ -457,6 +460,10 @@ void Controller::end_drag(const PointerEvent& e) {
// the drag itself stores them verbatim, so they follow the pointer instead of
// sticking to the sample grid as it moves (see Plot::snap_region()).
if (drag_region_ != kInvalid) plot_->snap_region(static_cast<u32>(drag_region_));
// And the same for a grabbed cursor: `move_cursor()` wrote the free pointer position
// for the whole gesture, so this is where the marker lands on its own snap rule --
// the drop commits the snap, both orientations alike (see Plot::snap_cursor()).
if (drag_cursor_ != kInvalid) plot_->snap_cursor(static_cast<u32>(drag_cursor_));
mode_ = DragMode::None;
selection_ = Rect{};
drag_cursor_ = kInvalid;
@@ -539,6 +546,12 @@ void Controller::move_cursor(const PointerEvent& e) {
// A grabbed cursor mutates the coordinate its own orientation owns and nothing else, and
// each half names the one setter for it -- so a ruler dragged along y can never disturb a
// marker's x, and the dead coordinate of either is untouched by construction.
//
// Both writes are `CursorPlacement::Free`: the marker follows the pointer for the whole
// gesture and lands on its grid only when the drag ends (`end_drag()` calls
// `Plot::snap_cursor()`), so a snapping cursor tracks the hand instead of hopping between
// samples -- the defect reported from use. A caller reading `cursor(id).x` / `.y` while
// the drag runs reads that free position.
if (cursor.is_horizontal()) {
// Clamped to the ruler's own y axis: a pointer that has left the axes has no data under
// it, and the coordinate it extrapolates to is past the end of the axis the ruler reads
@@ -553,10 +566,12 @@ void Controller::move_cursor(const PointerEvent& e) {
// the transform widens it. Order the bounds rather than assuming `min <= max`, or an
// inverted axis would collapse the dragged ruler onto one end (and the call is UB).
plot_->set_cursor_y(id, std::clamp(y, std::min(axis.range.min, axis.range.max),
std::max(axis.range.min, axis.range.max)));
std::max(axis.range.min, axis.range.max)),
CursorPlacement::Free);
return;
}
plot_->set_cursor_x(id, plot_->x_transform(cursor.axis_x).to_data_x(static_cast<f64>(e.x)));
plot_->set_cursor_x(id, plot_->x_transform(cursor.axis_x).to_data_x(static_cast<f64>(e.x)),
CursorPlacement::Free);
}
void Controller::move_region(const PointerEvent& e) {