Pen: ignore pointer movement inside a 3 px drag threshold
A press released within a few screen pixels is a click: the anchor stays a corner with no handles, as in Illustrator, instead of getting near-zero handles from a trembling hand. The threshold is in screen pixels, so it does not change with zoom. It applies to new anchors, closing a path and pulling a handle out of the last anchor. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
96d5a14738
commit
eace7c749b
|
|
@ -44,6 +44,9 @@ pub struct PenTool {
|
|||
in_h: Point,
|
||||
/// The pointer at the last button-down or drag: Space held moves the anchor as far as it moves.
|
||||
last: Point,
|
||||
/// The pointer has moved past the drag threshold since the press: a press released inside it
|
||||
/// is a click, so the anchor stays a corner without handles.
|
||||
dragged: bool,
|
||||
/// The last anchor of the path being drawn, while a click retracts its outgoing handle or a
|
||||
/// drag pulls a new one out: (path, subpath, anchor, its position).
|
||||
handle: Option<(NodeId, usize, usize, Point)>,
|
||||
|
|
@ -53,6 +56,9 @@ pub struct PenTool {
|
|||
snap: DrawSnap,
|
||||
}
|
||||
|
||||
/// Screen pixels the pointer must move after a press before it shapes handles instead of clicking.
|
||||
const DRAG_THRESHOLD: f64 = 3.0;
|
||||
|
||||
/// An anchor being placed, as the press decided: where it is, the path it goes on (none: the
|
||||
/// first anchor of a new path) and whether it closes that path (the press was on its first
|
||||
/// anchor). The drag only shapes its handles, whatever the path looks like meanwhile: a closing
|
||||
|
|
@ -181,6 +187,7 @@ impl Tool for PenTool {
|
|||
}
|
||||
PointerKind::Down => {
|
||||
self.last = ev.pos;
|
||||
self.dragged = false;
|
||||
if let Some((id, first, last, _)) = active {
|
||||
let end = last_anchor(cx, id);
|
||||
let closing_end = end.filter(|(_, ai)| *ai > 0).map(|_| first);
|
||||
|
|
@ -265,6 +272,14 @@ impl Tool for PenTool {
|
|||
if let Some(t) = &mut self.convert {
|
||||
return t.pointer(cx, ev);
|
||||
}
|
||||
// Illustrator treats a press as a drag only past a few screen pixels (`last` is
|
||||
// still the press position until then).
|
||||
if !self.dragged {
|
||||
if ev.pos.distance(self.last) < cx.tol(DRAG_THRESHOLD) {
|
||||
return vec![];
|
||||
}
|
||||
self.dragged = true;
|
||||
}
|
||||
if let Some((id, si, ai, a)) = self.handle {
|
||||
let mut h = ev.pos;
|
||||
if ev.mods.shift {
|
||||
|
|
@ -466,6 +481,20 @@ mod tests {
|
|||
use crate::testutil::*;
|
||||
use vectorcraft_doc::Selection;
|
||||
|
||||
#[test]
|
||||
fn press_inside_drag_threshold_places_a_corner() {
|
||||
let (d, s) = (vectorcraft_doc::Document::new(500.0, 500.0), Selection::default());
|
||||
let paint = paint();
|
||||
let cx =
|
||||
ToolContext { zoom: 1.0, smart_guides: false, snap_to_point: false, snap_to_pixel: false, snap_to_grid: false, ..cx(&d, &s, &paint) };
|
||||
let mut pen = PenTool::default();
|
||||
pen.pointer(&cx, &PointerEvent::new(PointerKind::Down, 100.0, 100.0));
|
||||
assert!(pen.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 101.0, 101.0)).is_empty());
|
||||
assert_eq!(pen.pointer(&cx, &PointerEvent::new(PointerKind::Up, 101.0, 101.0)), vec![Action::Commit]);
|
||||
pen.pointer(&cx, &PointerEvent::new(PointerKind::Down, 200.0, 200.0));
|
||||
assert_eq!(pen.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 204.0, 200.0)).len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapped_last_endpoint_converts_active_path_after_raw_miss() {
|
||||
let first = Point::new(100.0, 300.0);
|
||||
|
|
|
|||
Loading…
Reference in New Issue