Isolation mode: object.isolate checks a Shaper Group's source group

object.isolate on a Shaper Group isolates the group's source group, but
the hidden and locked check ran on the Shaper Group itself, so a source
group hidden or locked with object.setProps was still isolated. The
check now runs on the object that isolation mode enters.
This commit is contained in:
dotCooCoo
2026-10-21 16:55:16 +07:00
committed by bflatastic
parent bdf54007a0
commit b1abc73e16
3 changed files with 25 additions and 3 deletions
+4 -3
View File
@@ -1068,15 +1068,16 @@ fn isolate(s: &mut Session, p: &Value) -> Result<Value> {
if matches!(st.doc.node(id).ok_or(EngineError::NoNode(id))?.kind, NodeKind::Text(_)) {
return Err(bad("object.isolate", "type objects can't be isolated"));
}
let isolation = st.doc.node(id).filter(|n| n.extras().shaper.is_some()).and_then(|n| n.children()).and_then(|c| c.first()).map_or(id, |n| n.id);
// A hidden or locked object, one in a hidden or locked group or layer, and a guide path while
// Lock Guides is on aren't isolated either: a click with the Selection tool doesn't select them.
if !st.doc.is_editable(id) {
// A Shaper Group isolates its source group, so that group is the one checked.
if !st.doc.is_editable(isolation) {
return Err(bad("object.isolate", "the object is hidden or locked"));
}
if guides_locked && crate::is_guide_path(&st.doc, id) {
if guides_locked && crate::is_guide_path(&st.doc, isolation) {
return Err(EngineError::Other("guides are locked".into()));
}
let isolation = st.doc.node(id).filter(|n| n.extras().shaper.is_some()).and_then(|n| n.children()).and_then(|c| c.first()).map_or(id, |n| n.id);
st.isolation = Some(isolation);
st.selection.clear();
st.revision += 1;
+4
View File
@@ -2077,8 +2077,12 @@ fn guide_path(s: &mut Session, x: f64) -> NodeId {
fn exit_isolation_leaves_a_locked_guide_path_deselected() {
let mut s = session();
let g = guide_path(&mut s, 0.0);
// A locked guide path can't be isolated; one isolated before Lock Guides went on stays isolated.
s.execute("view.guides.lock", &json!({"locked": true})).unwrap();
assert!(s.execute("object.isolate", &json!({"id": g.0})).is_err());
s.execute("view.guides.lock", &json!({"locked": false})).unwrap();
s.execute("object.isolate", &json!({"id": g.0})).unwrap();
s.execute("view.guides.lock", &json!({"locked": true})).unwrap();
s.execute("object.exitIsolation", &json!({})).unwrap();
assert!(selected(&s).is_empty());
let _ = s.execute("object.move", &json!({"dx": 10, "dy": 0}));
+17
View File
@@ -311,6 +311,23 @@ fn shaper_construction_taps_stay_in_the_isolated_group() {
assert_eq!(s.doc().unwrap().isolation, Some(sources));
}
/// `object.isolate` on a Shaper Group whose source group is locked or hidden is refused, and
/// isolation mode stays off.
#[test]
fn shaper_isolate_refuses_a_locked_or_hidden_source_group() {
for (key, on) in [("locked", true), ("visible", false)] {
let mut s = session();
rectangle(&mut s, 0.0, "#ff0000");
rectangle(&mut s, 50.0, "#0000ff");
let g = scribble(&mut s, &[[25.0, 20.0], [75.0, 35.0], [30.0, 50.0], [125.0, 65.0]]);
let sources = node(&s, g).children().unwrap()[0].id;
s.execute("object.setProps", &json!({"ids": [sources.0], key: on})).unwrap();
let r = s.execute("object.isolate", &json!({"id": g.0})).map_err(|e| e.to_string());
assert_eq!(r, Err("invalid parameters for `object.isolate`: the object is hidden or locked".into()), "{key}");
assert_eq!(s.doc().unwrap().isolation, None, "{key}");
}
}
#[test]
fn shaper_lines_keep_free_angles_with_or_without_shift() {
for shift in [false, true] {