Transparency: a layer's art on a locked artboard or in a hidden or locked parent is not taken for an opacity mask

Make Opacity Mask expands a layer among its targets to the art that can
be edited: an object in a sublayer of a hidden or locked layer, or on a
locked artboard, is neither the mask nor masked, so it stays where it is.
This commit is contained in:
dotCooCoo
2026-11-01 08:32:54 +07:00
committed by bflatastic
parent d0834e6834
commit 482c011233
+36 -3
View File
@@ -194,11 +194,13 @@ fn make(s: &mut Session, p: &Value) -> Result<Value> {
}
None => super::appearance::subject_roots(s)?,
};
// A layer among several targets stands for its visible, unlocked art. A hidden, locked or
// template layer gives none.
// A layer among several targets stands for its art that can be edited: visible and unlocked,
// with every layer it is in, and not on a locked artboard. A hidden, locked or template layer
// gives none.
let editable = d.editable();
let art_of = |id: NodeId| match d.node(id) {
Some(n) if n.is_layer() && (!n.visible || n.locked || n.is_template()) => vec![],
Some(n) if n.is_layer() => n.layer_art(true),
Some(n) if n.is_layer() => n.layer_art(true).into_iter().filter(|c| editable(*c)).collect(),
_ => vec![id],
};
// A layer on top: the topmost object of its art is the mask, and the rest of it is masked.
@@ -236,6 +238,7 @@ fn make(s: &mut Session, p: &Value) -> Result<Value> {
} else {
art
};
drop(editable);
let (id, layer) = s.edit("Make Opacity Mask", |d, sel| {
let mask_art = match mask_id {
Some(m) => (*d.remove(m)?).clone(),
@@ -601,6 +604,36 @@ mod tests {
assert_eq!(s.doc().unwrap().doc.layers, hidden);
}
#[test]
fn make_leaves_out_layer_art_that_cannot_be_edited() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
let rect = |s: &mut Session, x: u64, y: u64| {
s.execute("shape.rectangle", &json!({"x": x, "y": y, "width": 20, "height": 20})).unwrap()["id"].as_u64().unwrap()
};
let a = rect(&mut s, 300, 10);
// A visible sublayer of a hidden layer holds no art that can be edited.
let l2 = s.execute("layer.new", &json!({})).unwrap()["id"].as_u64().unwrap();
let sub = s.execute("layer.newSublayer", &json!({"parent": l2})).unwrap()["id"].as_u64().unwrap();
let inner = rect(&mut s, 300, 40);
assert_eq!(s.doc().unwrap().doc.parent_of(NodeId(inner)), Some(NodeId(sub)));
s.execute("layer.setProps", &json!({"id": l2, "visible": false})).unwrap();
let before = s.doc().unwrap().doc.layers.clone();
assert!(s.execute("transparency.makeOpacityMask", &json!({ "ids": [a, sub] })).is_err());
assert_eq!(s.doc().unwrap().doc.layers, before, "the hidden layer's art stays");
// Art on a locked artboard acts as locked: the topmost object off it is the mask.
let l3 = s.execute("layer.new", &json!({"top": true})).unwrap()["id"].as_u64().unwrap();
let off = rect(&mut s, 300, 70);
let on = rect(&mut s, 10, 10);
s.execute("artboard.setProps", &json!({"index": 0, "locked": true})).unwrap();
let r = s.execute("transparency.makeOpacityMask", &json!({ "ids": [a, l3] })).unwrap();
let d = &s.doc().unwrap().doc;
let n = d.node(NodeId(r["id"].as_u64().unwrap())).unwrap();
assert_eq!(n.id, NodeId(a));
assert_eq!(n.extras().mask.as_ref().unwrap().art.id, NodeId(off));
assert_eq!(d.parent_of(NodeId(on)), Some(NodeId(l3)), "the object on the locked artboard stays");
}
#[test]
fn release_restores_mask_art_and_undo_restores_mask() {
let (mut s, a, b) = session_with_two();