mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3885 from mercuryVM/fix/3716-click-near-anchor-moves
Fix #3716: a click near an anchor selects without moving the object
This commit is contained in:
@@ -53,6 +53,9 @@ enum State {
|
||||
/// Pressed on an object; becomes Moving after the drag threshold.
|
||||
Moving {
|
||||
start: Point,
|
||||
/// Where the pointer went down: the drag threshold counts from there, not from `start`,
|
||||
/// which may be an anchor or centre the press grabbed a few pixels away (#3716).
|
||||
press: Point,
|
||||
/// The point that snaps, moving with the pointer: `start`, or where a press on a path's
|
||||
/// stroke or beside its edge took hold of the path ([`hold_point`]).
|
||||
hold: Point,
|
||||
@@ -734,7 +737,7 @@ impl Tool for SelectionTool {
|
||||
&& cx.select_behind
|
||||
&& let Some(behind) = object_behind(cx, p)
|
||||
{
|
||||
self.state = State::Moving { start: p, hold: p, began: false, deselect: None, key: None };
|
||||
self.state = State::Moving { start: p, press: p, hold: p, began: false, deselect: None, key: None };
|
||||
return vec![Action::Exec("select.set".into(), json!({ "ids": [behind.0] }))];
|
||||
}
|
||||
match hit_test(cx.doc, p, cx.selection_hit_options()) {
|
||||
@@ -758,7 +761,7 @@ impl Tool for SelectionTool {
|
||||
// Grabbed by no anchor or centre, a press on a path's stroke or beside its
|
||||
// edge holds the path there, and that point of it snaps (#3701).
|
||||
let hold = if grabbed.is_some() { grab } else { hold_point(cx, &h, p) };
|
||||
self.state = State::Moving { start: grab, hold, began: false, deselect, key };
|
||||
self.state = State::Moving { start: grab, press: p, hold, began: false, deselect, key };
|
||||
out
|
||||
}
|
||||
None => {
|
||||
@@ -767,10 +770,10 @@ impl Tool for SelectionTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
(PointerKind::Drag, State::Moving { start, hold, began, .. }) => {
|
||||
(PointerKind::Drag, State::Moving { start, press, hold, began, .. }) => {
|
||||
let mut out = vec![];
|
||||
if !began {
|
||||
if p.distance(start) < Self::drag_threshold(cx) {
|
||||
if p.distance(press) < Self::drag_threshold(cx) {
|
||||
return out;
|
||||
}
|
||||
out.push(Action::Begin(if m.alt { "Copy".into() } else { "Move".into() }));
|
||||
@@ -783,7 +786,7 @@ impl Tool for SelectionTool {
|
||||
}
|
||||
// Shift has already constrained `d`: any change is a snap.
|
||||
self.snapped = (d - unsnapped).hypot() > 1e-9;
|
||||
self.state = State::Moving { start, hold, began: true, deselect: None, key: None };
|
||||
self.state = State::Moving { start, press, hold, began: true, deselect: None, key: None };
|
||||
self.measure = cx.measurement_labels.then(|| (p, cx.offset_label(d.x, d.y)));
|
||||
out.push(Action::Preview("object.transform".into(), json!({ "matrix": matrix_json(Affine::translate(d)), "copy": m.alt })));
|
||||
out
|
||||
@@ -1104,6 +1107,31 @@ mod tests {
|
||||
assert!((200.0 + x - 350.3).abs() < 1e-9 && (200.0 + y - 250.0).abs() < 1e-9, "the corner lands at ({}, {})", 200.0 + x, 200.0 + y);
|
||||
}
|
||||
|
||||
/// #3716: a press a few pixels from an anchor grabs the object by that anchor, but the drag
|
||||
/// threshold counts from where the pointer went down. A click that wobbles by a pixel selects
|
||||
/// the object without moving it, instead of jumping the anchor onto the pointer.
|
||||
#[test]
|
||||
fn a_click_near_an_anchor_selects_without_moving() {
|
||||
let (d, id) = doc_with_rect();
|
||||
let (none, mut picked) = (Selection::default(), Selection::default());
|
||||
picked.add(id);
|
||||
let p = paint();
|
||||
let mut t = SelectionTool::default();
|
||||
// 5.7 px from the corner (200, 200): within reach of it, beyond the drag threshold.
|
||||
let a = t.pointer(&cx(&d, &none, &p), &ev(PointerKind::Down, 196.0, 196.0));
|
||||
assert!(a.iter().any(|a| matches!(a, Action::Exec(c, v) if c == "select.set" && v["ids"] == json!([id.0]))), "{a:?}");
|
||||
let a = t.pointer(&cx(&d, &picked, &p), &ev(PointerKind::Drag, 197.0, 196.0));
|
||||
assert!(a.is_empty(), "a 1 px wobble moves the object: {a:?}");
|
||||
let a = t.pointer(&cx(&d, &picked, &p), &ev(PointerKind::Up, 197.0, 196.0));
|
||||
assert!(!a.iter().any(|a| matches!(a, Action::Commit)), "{a:?}");
|
||||
// A drag past the threshold from the press still moves it by the corner (#812).
|
||||
let mut t = SelectionTool::default();
|
||||
t.pointer(&cx(&d, &none, &p), &ev(PointerKind::Down, 196.0, 196.0));
|
||||
let a = t.pointer(&cx(&d, &picked, &p), &ev(PointerKind::Drag, 230.0, 196.0));
|
||||
let Some(Action::Preview(_, v)) = a.get(1) else { panic!("{a:?}") };
|
||||
assert_eq!((v["matrix"][4].as_f64(), v["matrix"][5].as_f64()), (Some(30.0), Some(-4.0)), "the corner follows the pointer: {a:?}");
|
||||
}
|
||||
|
||||
/// A corner-radius drag is a transform, so the sharp selection box hides while the radius changes.
|
||||
#[test]
|
||||
fn a_corner_drag_counts_as_transforming_once_it_moves() {
|
||||
|
||||
Reference in New Issue
Block a user