mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Anchor Point tool: a click on a handle end retracts that handle only (fixes #2267)
This commit is contained in:
committed by
bflatastic
parent
f88242f576
commit
bb66070841
@@ -430,6 +430,25 @@ fn anchor_point_tool_converts() {
|
||||
assert!(d < 0.5, "{q:?}");
|
||||
}
|
||||
|
||||
/// #2267: clicking one handle end with the Anchor Point tool retracts that handle alone, as one
|
||||
/// undo step.
|
||||
#[test]
|
||||
fn anchor_point_tool_click_on_a_handle_retracts_it() {
|
||||
let mut s = session();
|
||||
let id = rect(&mut s, 100.0, 100.0, 100.0, 100.0);
|
||||
drag(&mut s, "anchorPoint", &[(100.0, 100.0), (130.0, 80.0)], Mods::default());
|
||||
s.execute("select.set", &json!({"ids": [id.0]})).unwrap();
|
||||
let smooth = path(&s, id).subpaths[0].anchors[0];
|
||||
let undo = undo_steps(&s);
|
||||
click(&mut s, "anchorPoint", 130.0, 80.0);
|
||||
let a = path(&s, id).subpaths[0].anchors[0];
|
||||
assert!(!a.has_out());
|
||||
assert_eq!((a.p, a.h_in, a.kind), (smooth.p, smooth.h_in, vectorcraft_geom::AnchorKind::Corner));
|
||||
assert_eq!(undo_steps(&s), undo + 1);
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(path(&s, id).subpaths[0].anchors[0], smooth);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scissors_opens_closed_path() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
//!
|
||||
//! Add: click a segment to insert an anchor without changing the shape (Alt = delete).
|
||||
//! Delete: click an anchor to remove it, re-fitting the neighbouring curve (Alt = add).
|
||||
//! Anchor Point: click a smooth anchor → corner; drag an anchor → pull out smooth handles; drag a
|
||||
//! handle → move it independently (Shift: at 45° steps round its anchor); drag a segment → reshape
|
||||
//! the curve, a selected blend's spine's too, straight or curved (#1111).
|
||||
//! Anchor Point: click a smooth anchor → corner; drag an anchor → pull out smooth handles; click a
|
||||
//! handle end → retract that handle only (#2267); drag a handle → move it independently (Shift: at
|
||||
//! 45° steps round its anchor); drag a segment → reshape the curve, a selected blend's spine's too,
|
||||
//! straight or curved (#1111).
|
||||
//! Scissors: click a path to split it there.
|
||||
|
||||
use serde_json::json;
|
||||
@@ -31,6 +32,7 @@ enum State {
|
||||
si: usize,
|
||||
ai: usize,
|
||||
out: bool,
|
||||
start: Point,
|
||||
began: bool,
|
||||
},
|
||||
/// Bending a segment: a path's, or (`spine`) a selected blend's spine's.
|
||||
@@ -110,7 +112,7 @@ impl Tool for AnchorTool {
|
||||
}
|
||||
("anchorPoint", PointerKind::Down) => {
|
||||
if let Some((id, si, ai, out)) = hit_handle(cx, p, point) {
|
||||
self.state = State::Handle { id, si, ai, out, began: false };
|
||||
self.state = State::Handle { id, si, ai, out, start: p, began: false };
|
||||
self.snap = HandleSnap::default();
|
||||
} else if let Some((id, si, ai)) = hit_anchor(cx, p, point) {
|
||||
self.state = State::Convert { id, si, ai, start: p, began: false };
|
||||
@@ -125,8 +127,9 @@ impl Tool for AnchorTool {
|
||||
let mut out = vec![];
|
||||
let (began, far) = match self.state {
|
||||
State::Idle => return out,
|
||||
State::Convert { began, start, .. } | State::Reshape { began, start, .. } => (began, p.distance(start) >= cx.tol(2.0)),
|
||||
State::Handle { began, .. } => (began, true),
|
||||
State::Convert { began, start, .. } | State::Handle { began, start, .. } | State::Reshape { began, start, .. } => {
|
||||
(began, p.distance(start) >= cx.tol(2.0))
|
||||
}
|
||||
};
|
||||
if !began {
|
||||
if !far {
|
||||
@@ -183,6 +186,15 @@ impl Tool for AnchorTool {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
State::Handle { id, si, ai, out, began: false, .. } => {
|
||||
let Some(a) = cx.doc.node(id).and_then(|n| n.path_data()).and_then(|pd| pd.subpaths.get(si)?.anchors.get(ai)) else {
|
||||
return vec![];
|
||||
};
|
||||
vec![Action::Exec(
|
||||
"path.setHandle".into(),
|
||||
json!({"id": id.0, "subpath": si, "anchor": ai, "which": if out { "out" } else { "in" }, "x": a.p.x, "y": a.p.y, "independent": true}),
|
||||
)]
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
@@ -305,4 +317,39 @@ mod tests {
|
||||
let single = ToolContext { handles_multiple: false, ..cx(&d, &s, &p) };
|
||||
assert_eq!(dragged(press(&single, 240.0, 301.0)), "path.reshapeSegment");
|
||||
}
|
||||
|
||||
/// #2267: clicking a handle end retracts that handle only; the anchor becomes a corner and
|
||||
/// keeps the other handle, as Illustrator's Anchor Point tool does.
|
||||
#[test]
|
||||
fn anchor_point_click_on_a_handle_retracts_only_that_handle() {
|
||||
let (mut d, _) = doc_with_rect();
|
||||
let l = d.layers[0].id;
|
||||
let id = d.alloc_id();
|
||||
let mut sp = vectorcraft_geom::SubPath::polyline(&[Point::new(100.0, 300.0), Point::new(200.0, 300.0), Point::new(300.0, 300.0)], false);
|
||||
sp.anchors[1] = vectorcraft_geom::Anchor::smooth(Point::new(200.0, 300.0), Point::new(240.0, 320.0));
|
||||
let path = vectorcraft_geom::PathData::single(sp);
|
||||
d.insert(Some(l), 1, vectorcraft_doc::Node::path(id, path, vectorcraft_doc::Appearance::default_art())).unwrap();
|
||||
let mut s = Selection::default();
|
||||
s.set([id]);
|
||||
let p = paint();
|
||||
let cx = cx(&d, &s, &p);
|
||||
let retract = |which: &str| {
|
||||
vec![Action::Exec(
|
||||
"path.setHandle".into(),
|
||||
json!({"id": id.0, "subpath": 0, "anchor": 1, "which": which, "x": 200.0, "y": 300.0, "independent": true}),
|
||||
)]
|
||||
};
|
||||
let mut t = AnchorTool::new("anchorPoint");
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 240.0, 320.0));
|
||||
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 240.0, 320.0)), retract("out"));
|
||||
// A hand that shakes by a pixel still clicks; the incoming handle sits opposite, at (160, 280).
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 160.0, 280.0));
|
||||
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 160.5, 280.5)).is_empty());
|
||||
assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 160.5, 280.5)), retract("in"));
|
||||
// The command leaves the other handle where it was.
|
||||
let mut a = vectorcraft_geom::Anchor::smooth(Point::new(200.0, 300.0), Point::new(240.0, 320.0));
|
||||
a.set_handle(true, a.p, true);
|
||||
assert!(!a.has_out());
|
||||
assert_eq!(a.h_in, Point::new(160.0, 280.0));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user