fix(paint): respect Lock All on layer masks (#3094) (#3111)

This commit is contained in:
Sir Starch authored and GitHub committed 2026-10-11 16:15:08 -04:00
1 parent b3993e89b3
commit f435eb0b55
2 files changed
+41 -1

No files matched your search

+12 -1
View File
@@ -67,7 +67,9 @@ pub(crate) fn has_paintable(s: &Session) -> std::result::Result<(), String> {
/// Surface a paint command writes to: the layer's pixels, or its mask with `"target":"mask"`.
pub(crate) fn paint_surface<'a>(doc: &'a mut Document, id: LayerId, p: &Value) -> Result<&'a mut photocraft_raster::Surface> {
if !is_mask_target(p) {
if is_mask_target(p) {
check_all_unlocked(doc, id)?;
} else {
check_pixels_unlocked(doc, id)?;
}
let l = doc.layer_mut(id).ok_or(EngineError::NoLayer(id))?;
@@ -78,6 +80,15 @@ pub(crate) fn paint_surface<'a>(doc: &'a mut Document, id: LayerId, p: &Value) -
}
}
fn check_all_unlocked(doc: &Document, id: LayerId) -> Result<()> {
let locks = doc.effective_locks(id);
let l = doc.layer(id).ok_or(EngineError::NoLayer(id))?;
if locks.all {
return Err(EngineError::Other(format!("Could not complete your request because the layer \"{}\" is locked", l.name)));
}
Ok(())
}
/// Refuse a layer whose pixels are locked (directly or through a group), with Photoshop's message.
pub(crate) fn check_pixels_unlocked(doc: &Document, id: LayerId) -> Result<()> {
let locks = doc.effective_locks(id);
+29
View File
@@ -1162,6 +1162,35 @@ fn layer_locks_are_set_and_enforced() {
assert!(l.locks.position && !l.locks.pixels);
}
#[test]
fn all_lock_blocks_painting_a_mask_but_pixels_lock_does_not() {
let mut s = Session::new();
s.execute("file.new", json!({"width": 20, "height": 20})).unwrap();
s.execute("layer.new.layer", json!({})).unwrap();
let id = s.active().unwrap().active_layer.unwrap();
s.edit("mask", |doc, _| {
doc.layer_mut(id).unwrap().mask = Some(photocraft_doc::LayerMask::reveal_all());
Ok(())
})
.unwrap();
s.execute("layer.setProps", json!({"locks": {"all": true}})).unwrap();
let history_steps = s.active().unwrap().history.entries().len();
let err = s.execute("paint.stroke", json!({"points": [[5, 5]], "size": 4, "hardness": 1.0, "color": "#000000", "target": "mask"})).unwrap_err();
assert_eq!(err.to_string(), "Could not complete your request because the layer \"Layer 1\" is locked");
let state = s.active().unwrap();
let mask = &state.doc.layer(id).unwrap().mask.as_ref().unwrap().surface;
assert!(mask.pixel(5, 5)[0] > 0.99, "rejected stroke leaves the mask reveal-all");
assert_eq!(state.history.entries().len(), history_steps, "rejected stroke adds no Brush Tool history step");
s.execute("layer.setProps", json!({"locks": {"all": false, "pixels": true}})).unwrap();
s.execute("paint.stroke", json!({"points": [[5, 5]], "size": 4, "hardness": 1.0, "color": "#000000", "target": "mask"})).unwrap();
let mask_value = |s: &Session| s.active().unwrap().doc.layer(id).unwrap().mask.as_ref().unwrap().surface.pixel(5, 5)[0];
assert!(mask_value(&s) < 0.01, "pixels-only lock permits mask painting");
s.execute("edit.undo", json!({})).unwrap();
assert!(mask_value(&s) > 0.99, "undo restores the mask");
}
#[test]
fn moving_a_layer_moves_its_effects_reference_point() {
let mut s = session_with_doc();