Paste: in front or back of a compound path's member, other art goes beside the compound path

With a member of a compound path selected, Paste in Front and Paste in Back
put the clipboard's objects next to that member, inside the compound path,
whatever they were. A pasted group, compound path or type object then sat
inside a compound path, which holds only paths. The model-based test
`random_command_sequences` stops on this with "compound #N has a non-path
child".

When the clipboard holds anything other than paths, the objects now go
right above or below the compound path. Paths still go next to the member,
inside the compound path.
This commit is contained in:
dotCooCoo
2026-11-01 06:42:33 -08:00
parent 05d5f99fd7
commit 827e137b5c
2 changed files with 49 additions and 2 deletions
+13 -2
View File
@@ -813,11 +813,13 @@ fn paste_clip(s: &mut Session, p: &Value, mode: PasteMode, clip: &Clipboard, cho
&& (st.doc.paste_remembers_layers || boards.is_some())
&& parent.is_some_and(|p| st.doc.node(p).is_some_and(Node::is_layer));
// Front/back: relative to the selection (top-most / bottom-most selected object); with
// nothing selected, the top / bottom of the current layer.
// nothing selected, the top / bottom of the current layer. Art other than paths goes beside
// the compound path that holds the selected object, not into it.
let anchor = if front || back {
let order = st.selection.in_paint_order(&st.doc);
let a = if front { order.last() } else { order.first() };
a.and_then(|id| st.doc.position(*id))
let paths_only = clip.nodes.iter().all(|n| matches!(n.kind, vectorcraft_doc::NodeKind::Path { .. }));
a.map(|id| paste_anchor(&st.doc, *id, paths_only)).and_then(|id| st.doc.position(id))
} else {
None
};
@@ -900,6 +902,15 @@ fn paste_clip(s: &mut Session, p: &Value, mode: PasteMode, clip: &Clipboard, cho
}))
}
/// What pasted art goes beside: `id`, or the compound path holding it when the art isn't only
/// paths (a compound path holds paths only).
fn paste_anchor(doc: &Document, id: NodeId, paths_only: bool) -> NodeId {
match doc.parent_of(id) {
Some(p) if !paths_only && doc.node(p).is_some_and(|n| matches!(n.kind, vectorcraft_doc::NodeKind::Compound { .. })) => p,
_ => id,
}
}
/// The first unlocked layer or sublayer called `name` (topmost first), or a new top layer.
fn layer_named(d: &mut Document, name: &str) -> NodeId {
let mut found = None;
+36
View File
@@ -563,6 +563,42 @@ fn front_and_back_without_a_selection_use_the_current_layers_ends() {
assert_eq!(&l[i - 2..i], &below[..]);
}
#[test]
fn front_and_back_beside_a_compound_paths_member_go_beside_the_compound_path() {
let mut s = session();
let a = rect(&mut s, 0.0, 0.0, 10.0, 10.0);
let b = rect(&mut s, 20.0, 0.0, 10.0, 10.0);
sel(&mut s, &[a, b]);
let c = NodeId(s.execute("object.compoundPath.make", &json!({})).unwrap()["id"].as_u64().unwrap());
let members = |s: &Session| doc(s).node(c).unwrap().children().unwrap().iter().map(|n| n.id).collect::<Vec<_>>();
let before = members(&s);
let layer = |s: &Session| doc(s).layers[0].children().unwrap().iter().map(|n| n.id).collect::<Vec<_>>();
// A compound path and a group, each pasted with one of the compound path's members selected.
let other = rect(&mut s, 40.0, 0.0, 10.0, 10.0);
sel(&mut s, &[other]);
let group = NodeId(s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap());
for copied in [c, group] {
copy(&mut s, &[copied]);
sel(&mut s, &[before[0]]);
let above = ids_of(&s.execute("edit.pasteInFront", &json!({})).unwrap());
let l = layer(&s);
let i = l.iter().position(|id| *id == c).unwrap();
assert_eq!(&l[i + 1..i + 2], &above[..], "right above the compound path");
sel(&mut s, &[before[1]]);
let below = ids_of(&s.execute("edit.pasteInBack", &json!({})).unwrap());
let l = layer(&s);
let i = l.iter().position(|id| *id == c).unwrap();
assert_eq!(&l[i - 1..i], &below[..], "right below the compound path");
assert_eq!(members(&s), before, "the compound path keeps its own members");
}
// A path still goes in front of the member, inside the compound path.
let loose = rect(&mut s, 60.0, 0.0, 10.0, 10.0);
copy(&mut s, &[loose]);
sel(&mut s, &[before[0]]);
let path = ids_of(&s.execute("edit.pasteInFront", &json!({})).unwrap());
assert_eq!(members(&s), [before[0], path[0], before[1]]);
}
#[test]
fn paste_on_all_artboards_keeps_the_offset_to_the_source_artboard() {
let mut s = Session::new();