Fix #2091 copy/paste masks between layers

Edit > Copy with masks selected copied the layer, so Paste duplicated it
instead of adding the mask. Masks now go to their own clipboard and Paste
appends them, with unique names, to the selected unlocked layers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jerome CAMUS
2026-10-30 07:55:23 +01:00
co-authored by Claude Sonnet 5.5
parent fbb5488214
commit edb984a75a
6 changed files with 123 additions and 0 deletions
+50
View File
@@ -1943,3 +1943,53 @@ fn old_guides_load_as_pixel_guides_and_percent_round_trips() {
// A zero-sized comp never divides by zero.
assert!(g.px(0, 0).is_finite());
}
#[test]
fn copy_paste_masks_between_layers() {
let mut s = comp();
let a = solid(&mut s, "#ff0000");
let b = solid(&mut s, "#00ff00");
let n = s.active_comp().unwrap().layers.len();
s.execute("layer.select", json!({"layers": [a]})).unwrap();
s.execute("layer.addMask", json!({})).unwrap();
s.execute("layer.addMask", json!({"shape": "ellipse"})).unwrap();
let names = |s: &Session, id: u64| layer(s, id).masks().unwrap().groups().map(|m| m.name.clone()).collect::<Vec<_>>();
let uids = |s: &Session, id: u64| layer(s, id).masks().unwrap().groups().map(|m| m.uid).collect::<Vec<_>>();
let first = uids(&s, a)[0];
s.execute("prop.select", json!({"layer": a, "prop": first})).unwrap();
// Copy the mask: it is the mask that is copied, not its layer.
assert_eq!(s.execute("edit.copy", json!({})).unwrap()["masks"], json!(1));
assert!(s.state.clipboard.is_empty(), "not the layer");
assert!(s.is_enabled("edit.paste"));
s.execute("layer.select", json!({"layers": [b]})).unwrap();
let r = s.execute("edit.paste", json!({})).unwrap();
assert_eq!(s.active_comp().unwrap().layers.len(), n, "no new layer");
assert_eq!(names(&s, b), ["Mask 1"]);
let pasted = r["masks"][0].as_u64().unwrap();
assert_eq!(uids(&s, b), [pasted]);
assert_ne!(pasted, first);
assert_eq!(s.state.selected_props, vec![(effectcraft_project::LayerId(b), pasted)]);
assert_eq!(names(&s, a), ["Mask 1", "Mask 2"], "the source keeps its masks");
// Pasting again onto the same layer keeps names unique.
s.execute("edit.paste", json!({})).unwrap();
assert_eq!(names(&s, b), ["Mask 1", "Mask 2"]);
// One undo step per paste.
s.execute("edit.undo", json!({})).unwrap();
assert_eq!(names(&s, b), ["Mask 1"]);
}
#[test]
fn paste_masks_skips_locked_layers() {
let mut s = comp();
let a = solid(&mut s, "#ff0000");
let b = solid(&mut s, "#00ff00");
s.execute("layer.select", json!({"layers": [a]})).unwrap();
s.execute("layer.addMask", json!({})).unwrap();
let mask = layer(&s, a).masks().unwrap().groups().next().unwrap().uid;
s.execute("prop.select", json!({"layer": a, "prop": mask})).unwrap();
s.execute("edit.copy", json!({})).unwrap();
s.execute("layer.select", json!({"layers": [b]})).unwrap();
s.execute("layer.setSwitch", json!({"layers": [b], "switch": "lock", "value": true})).unwrap();
assert!(s.execute("edit.paste", json!({})).is_err());
assert_eq!(layer(&s, b).masks().unwrap().groups().count(), 0);
}