Paste path operators below the shapes they act on (#1681)

Pasting Trim Paths, Wiggle Paths or a paint into a shape layer with nothing
selected put it on top of Contents, where it acts on no shapes. It now goes
to the bottom, as Add places it. Also fixes #1629.
This commit is contained in:
MarekSpodBiedry
2026-10-17 14:37:42 +02:00
parent 8f02f52a44
commit b8d52cd8c4
2 changed files with 23 additions and 2 deletions
+8 -2
View File
@@ -127,8 +127,9 @@ pub(crate) fn cut_contents(s: &mut Session, items: &[(LayerId, Uid)]) -> Result<
}
/// Edit ▸ Paste with shape items on the clipboard: copies go into each selected shape layer's
/// Contents, above its topmost selected item (in that item's group), else on top. Names stay
/// unique in their group ("Rectangle 2"…), and the copies are selected.
/// Contents, above its topmost selected item (in that item's group), else on top (paints and path
/// operations alone: at the bottom). Names stay unique in their group ("Rectangle 2"…), and the
/// copies are selected.
pub(crate) fn paste_contents(s: &mut Session, p: &Value) -> Result<Value> {
let c = "edit.paste";
let clip = s.state.contents_clipboard.clone();
@@ -140,6 +141,7 @@ pub(crate) fn paste_contents(s: &mut Session, p: &Value) -> Result<Value> {
return Err(bad(c, "select a shape layer to paste shape items into"));
}
let sel = selected_contents(s);
let acts_on_above = clip.iter().all(|g| !matches!(g.match_id.as_str(), "group" | "rect" | "ellipse" | "star" | "path"));
let label = match clip.as_slice() {
[g] => format!("Paste {}", g.name),
_ => "Paste".to_string(),
@@ -154,11 +156,15 @@ pub(crate) fn paste_contents(s: &mut Session, p: &Value) -> Result<Value> {
let parent = l.props.parent_of(*u)?;
Some((parent.uid, parent.children.iter().position(|n| n.uid() == *u)?))
});
let unselected = spot.is_none();
let (parent, at) = match spot {
Some((pu, i)) => (l.props.find_group_mut(pu), i),
None => (l.props.sub_mut("contents"), 0),
};
let parent = parent.ok_or_else(|| bad(c, "the layer has no contents"))?;
// Trim Paths, Wiggle Paths, fills… act on the items above them (as Add places them),
// so with nothing selected they go to the bottom: on top they'd affect nothing (#1681).
let at = if unselected && acts_on_above { parent.children.len() } else { at };
for (k, g) in clip.iter().enumerate() {
let mut g = g.clone();
g.reassign_uids(&mut next);
+15
View File
@@ -1541,6 +1541,21 @@ fn shape_contents_copy_cut_and_paste_between_shape_layers() {
assert!(s.execute("edit.paste", json!({})).is_err());
}
/// Pasting Trim Paths into another shape layer with nothing selected puts it below the shapes,
/// where it acts on them (it acts on the items above it); on top it would trim nothing (#1681, #1629).
#[test]
fn pasted_path_operator_goes_below_the_shapes_it_acts_on() {
let mut s = comp();
let a = s.execute("layer.newShape", json!({"kind": "rect"})).unwrap()["layer"].as_u64().unwrap();
let trim = s.execute("layer.addShapeItem", json!({"layer": a, "kind": "trim"})).unwrap()["uid"].as_u64().unwrap();
let b = s.execute("layer.newShape", json!({"kind": "star", "name": "B"})).unwrap()["layer"].as_u64().unwrap();
s.execute("prop.select", json!({"layer": a, "prop": trim})).unwrap();
s.execute("edit.copy", json!({})).unwrap();
s.execute("layer.select", json!({"layers": [b]})).unwrap();
s.execute("edit.paste", json!({})).unwrap();
assert_eq!(contents_names(&s, b), ["Polystar 1", "Trim Paths 1"]);
}
/// Edit ▸ Duplicate with shape items selected duplicates them in their layer, above the
/// original and named "Rectangle 2", not the layer (#227).
#[test]