diff --git a/crates/engine/src/cmd/menucmds.rs b/crates/engine/src/cmd/menucmds.rs index bd8da087c..58125fa96 100644 --- a/crates/engine/src/cmd/menucmds.rs +++ b/crates/engine/src/cmd/menucmds.rs @@ -79,7 +79,7 @@ pub fn specs() -> Vec { "Unselected Artwork", ["Object", "Hide"], Some("Cmd+Alt+Shift+3"), - "{} hide, in one undo step, every object select.all takes that is not selected and holds no selected object; in a group that holds one it hides the other members one by one, and a clip group, compound path or other object that holds one stays as it is; guide paths stay as they are while Lock Guides is on; object.showAll shows them again; disabled in isolation mode → {count}", + "{} hide, in one undo step, every object select.all takes that is not selected and holds no selected object; in a group that holds one it hides the other members one by one, and a clip group, compound path or other object that holds one stays as it is; the objects of a selected layer count as selected; while Lock Guides is on, guide paths count as selected and stay as they are; object.showAll shows them again; disabled in isolation mode → {count}", has_selection_outside_isolation, hide_unselected ), @@ -322,17 +322,26 @@ fn other_layers(s: &mut Session, lock: bool) -> Result { } /// The objects [`super::select::walk_selectable`] visits that are not among the selected objects `roots` and hold none of -/// them. The walk goes into a plain group that holds one of `roots`; a clip group, a Shaper group, a compound path and -/// every other object that holds one is left out with its members. Guide paths are left out while `guides_locked`. -fn unselected(d: &vectorcraft_doc::Document, iso: Option, roots: &[NodeId], guides_locked: bool) -> Vec { - let holds: HashSet = d.index_paths(roots.iter().copied()).values().filter_map(|p| d.ancestry_at(p)).flatten().collect(); - let roots: HashSet = roots.iter().copied().collect(); +/// them. The objects of a selected layer count as selected. Guide paths are left out while `guides_locked`, and so are +/// the objects that hold one. The walk goes into a plain group that holds a selected object or such a guide path; a +/// clip group, a Shaper group, a compound path and every other object that holds one is left out with its members. +fn unselected(d: &vectorcraft_doc::Document, iso: Option, selection: &[NodeId], roots: &[NodeId], guides_locked: bool) -> Vec { + let mut roots: HashSet = roots.iter().copied().collect(); + roots.extend(selection.iter().filter_map(|id| d.node(*id)).filter(|n| n.is_layer()).flat_map(|l| l.layer_art(true))); + let mut kept: Vec = roots.iter().copied().collect(); + if guides_locked { + super::select::walk_selectable(d, iso, |n| { + if matches!(n.kind, NodeKind::Path { guide: true, .. }) { + kept.push(n.id); + } + true + }); + } + let holds: HashSet = d.index_paths(kept).values().filter_map(|p| d.ancestry_at(p)).flatten().collect(); let mut out = vec![]; super::select::walk_selectable(d, iso, |n| { if !holds.contains(&n.id) { - if !(guides_locked && matches!(n.kind, NodeKind::Path { guide: true, .. })) { - out.push(n.id); - } + out.push(n.id); return false; } !roots.contains(&n.id) && matches!(n.kind, NodeKind::Group { clip: false, .. }) && n.extras().shaper.is_none() @@ -344,7 +353,7 @@ fn hide_unselected(s: &mut Session, _: &Value) -> Result { let roots = selected_roots_to_transform(s)?; let guides_locked = s.menu.guides_locked; let st = s.doc()?; - let ids = unselected(&st.doc, st.isolation, &roots, guides_locked); + let ids = unselected(&st.doc, st.isolation, &st.selection.objects, &roots, guides_locked); // With nothing to hide, the document and its undo history stay as they are. if !ids.is_empty() { s.edit("Hide", |d, _| { diff --git a/crates/engine/src/tests/menucmds.rs b/crates/engine/src/tests/menucmds.rs index 2dd6658e2..47d12d343 100644 --- a/crates/engine/src/tests/menucmds.rs +++ b/crates/engine/src/tests/menucmds.rs @@ -598,6 +598,34 @@ fn hide_unselected_artwork_keeps_locked_guides_and_is_disabled_in_isolation_mode assert_eq!(shown(&s, &[g, c, outer, hole]), [false, true, true, true]); } +/// While Lock Guides is on, Hide › Unselected Artwork keeps a group that holds a guide path visible and hides its other +/// members one by one. With a layer selected, the objects on that layer stay visible and the rest are hidden. +#[test] +fn hide_unselected_artwork_keeps_groups_with_locked_guides_and_the_selected_layers_art() { + let mut s = session(); + let a = rect(&mut s, 0.0, 0.0, 10.0, 10.0); + let b = rect(&mut s, 20.0, 0.0, 10.0, 10.0); + let guide = rect(&mut s, 40.0, 0.0, 10.0, 10.0); + let x = rect(&mut s, 60.0, 0.0, 10.0, 10.0); + sel(&mut s, &[guide, x]); + let g = id_of(&s.execute("object.group", &json!({})).unwrap()); + sel(&mut s, &[guide]); + s.execute("view.guides.make", &json!({})).unwrap(); + s.execute("view.guides.lock", &json!({"locked": true})).unwrap(); + let other = id_of(&s.execute("layer.new", &json!({})).unwrap()); + let p = rect(&mut s, 0.0, 100.0, 10.0, 10.0); + let hide = |s: &mut Session| s.execute("object.hide.unselected", &json!({})).unwrap(); + let shown = |s: &Session, ids: &[NodeId]| ids.iter().map(|id| node(s, *id).visible).collect::>(); + sel(&mut s, &[a]); + assert_eq!(hide(&mut s), json!({"count": 3})); + assert_eq!(shown(&s, &[a, b, g, guide, x, p]), [true, false, true, true, false, false]); + s.execute("object.showAll", &json!({})).unwrap(); + sel(&mut s, &[other]); + assert_eq!(hide(&mut s), json!({"count": 3})); + assert_eq!(shown(&s, &[a, b, g, guide, x, p]), [false, false, true, true, false, true]); + assert_eq!(selected(&s), [other]); +} + // ---------- Transform Each ---------- #[test] diff --git a/docs/mcp.md b/docs/mcp.md index c16b9893b..5ea0f0772 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1372,8 +1372,8 @@ is one of them, and `edit.undo` selects them again with the key object. `object.hide.unselected` (Object › Hide › Unselected Artwork, Cmd+Alt+Shift+3) hides, in one undo step, every object that `select.all` takes and that is neither selected nor holds a selected object, and returns `{count}`. In a group that holds a selected object it hides the group's other members one by one; a clip group, a compound path or -another object that holds a selected object stays as it is with its members. While Lock Guides is on it leaves the -guide paths as they are. The selection stays as it is, and `object.showAll` shows the objects again. With nothing to +another object that holds a selected object stays as it is with its members. The objects of a selected layer count as +selected. While Lock Guides is on, the guide paths count as selected and stay as they are. The selection stays as it is, and `object.showAll` shows the objects again. With nothing to hide it records no undo step. In isolation mode the menu item is disabled, and the command returns the error "exit isolation mode first" and changes nothing.