mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 07:16:52 +00:00
Blends: Convert on a replaced spine changes only its handles
Convert to Smooth or Corner on a blend whose spine came from Replace Spine stored key anchors with the converted spine, so the spine's points then moved onto the key centers. It now changes only the spine's handles: the points stay where they are and the keys stay spread along the spine. Direct Selection's spine conversion and the other spine edits are unchanged.
This commit is contained in:
@@ -112,7 +112,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Convert Anchor Points",
|
||||
[],
|
||||
None,
|
||||
"{to: \"corner\"|\"smooth\"} convert the direct-selected anchors (all anchors of fully selected paths, every point of a selected blend's spine) as one undo step: corner retracts both handles, smooth pulls handles in line with the neighbours (smooth anchors keep theirs)",
|
||||
"{to: \"corner\"|\"smooth\"} convert the direct-selected anchors (all anchors of fully selected paths, every point of a selected blend's spine; a spine from Replace Spine changes only its handles, its points and the keys' spread along it staying as they are) as one undo step: corner retracts both handles, smooth pulls handles in line with the neighbours (smooth anchors keep theirs)",
|
||||
has_selection,
|
||||
convert_anchors
|
||||
),
|
||||
@@ -1155,11 +1155,7 @@ fn convert_anchors(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in &blends {
|
||||
let Some(NodeKind::Blend { children, spec }) = d.node_mut(*id).map(|n| &mut n.kind) else { continue };
|
||||
let Some((mut path, anchors)) = vectorcraft_doc::live::pin_spine(children, spec) else { continue };
|
||||
let pinned = path.clone();
|
||||
let Some(sp) = path.subpaths.first_mut() else { continue };
|
||||
let convert = |sp: &mut SubPath| {
|
||||
for ai in 0..sp.anchors.len() {
|
||||
if smooth {
|
||||
sp.smooth_anchor(ai);
|
||||
@@ -1167,6 +1163,21 @@ fn convert_anchors(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
a.retract();
|
||||
}
|
||||
}
|
||||
};
|
||||
for id in &blends {
|
||||
let Some(NodeKind::Blend { children, spec }) = d.node_mut(*id).map(|n| &mut n.kind) else { continue };
|
||||
// A spine from Replace Spine keeps its points and its keys spread along it: only the
|
||||
// handles change, and the keys are not pinned to points.
|
||||
if vectorcraft_doc::live::blend_spine(children, spec).is_some_and(|(_, anchors)| anchors.is_none()) {
|
||||
if let Some(sp) = spec.spine.as_mut().and_then(|p| p.subpaths.first_mut()) {
|
||||
convert(sp);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let Some((mut path, anchors)) = vectorcraft_doc::live::pin_spine(children, spec) else { continue };
|
||||
let pinned = path.clone();
|
||||
let Some(sp) = path.subpaths.first_mut() else { continue };
|
||||
convert(sp);
|
||||
// A spine the conversion leaves as it was stays as the blend has it.
|
||||
if path != pinned {
|
||||
spec.key_anchors = anchors.iter().map(|a| u32::try_from(*a).unwrap_or(u32::MAX)).collect();
|
||||
|
||||
@@ -144,6 +144,42 @@ fn converting_anchors_of_a_selected_blend_converts_its_spine() {
|
||||
assert_eq!(spec(&s, g).spine.unwrap().subpaths[0].anchors[1], middle, "one undo step");
|
||||
}
|
||||
|
||||
/// Converting the points of a blend whose spine came from Replace Spine changes only the spine's
|
||||
/// handles: its points stay where they are, the keys are not pinned to points, and the steps stay
|
||||
/// spread along the spine instead of the spine's ends moving onto the keys.
|
||||
#[test]
|
||||
fn converting_anchors_of_a_replaced_spine_changes_only_its_handles() {
|
||||
let mut s = session();
|
||||
let (_, _, g) = blend(&mut s);
|
||||
let spine = id_of(&s.execute("path.create", &json!({"d": "M100 300 L200 200 L300 300"})).unwrap());
|
||||
s.execute("select.set", &json!({"ids": [g.0, spine.0]})).unwrap();
|
||||
s.execute("object.blend.replaceSpine", &json!({})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [g.0]})).unwrap();
|
||||
let points = |s: &Session| spec(s, g).spine.unwrap().subpaths[0].anchors.iter().map(|a| a.p).collect::<Vec<_>>();
|
||||
let before = points(&s);
|
||||
let ends = |s: &Session| {
|
||||
let st = steps(s, g);
|
||||
(center(&st[0]), center(&st[st.len() - 1]))
|
||||
};
|
||||
let ends_before = ends(&s);
|
||||
s.execute("path.convertAnchors", &json!({"to": "smooth"})).unwrap();
|
||||
let sp = spec(&s, g);
|
||||
assert!(sp.key_anchors.is_empty(), "{:?}", sp.key_anchors);
|
||||
assert_eq!(points(&s), before, "the spine's points stay put");
|
||||
let middle = sp.spine.as_ref().unwrap().subpaths[0].anchors[1];
|
||||
assert!(middle.has_in() && middle.has_out(), "{middle:?}");
|
||||
assert_eq!(ends(&s), ends_before, "the end steps stay at the spine's ends");
|
||||
assert!(center(&steps(&s, g)[1]).distance(Point::new(200.0, 200.0)) < 1e-6, "the step stays on the spine");
|
||||
|
||||
s.execute("path.convertAnchors", &json!({"to": "corner"})).unwrap();
|
||||
let sp = spec(&s, g);
|
||||
assert!(sp.key_anchors.is_empty(), "{:?}", sp.key_anchors);
|
||||
assert!(sp.spine.unwrap().subpaths[0].anchors.iter().all(|a| !a.has_in() && !a.has_out()));
|
||||
assert_eq!(points(&s), before);
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(spec(&s, g).spine.unwrap().subpaths[0].anchors[1], middle, "one undo step");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_a_key_point_moves_the_key_and_the_other_way_round() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in New Issue
Block a user