Object: Cmd/Ctrl+Alt+Shift+2 locks inside clip groups and isolation, with no menu item

object.lock.unselected is now reached by its key and as a command only:
the Object > Lock > Unselected Artwork menu item is removed, and the
command stays in the registry, the shortcut editor and automation.

In a partly selected clip group or compound path it now locks the
unselected members, the clipping path included, and leaves the container
and the selected members unlocked. It locks unselected guides while Lock
Guides is on. Hidden art and art on a locked layer stay unlocked, and a
selected layer's art counts as selected.

With a selection in isolation mode the command is enabled and locks only
the unselected art inside the isolated group. The other Lock and Hide
items keep their isolation behavior.

Lock and Hide still share one selector. A lock flag on it selects the
clip group, compound path and guide cases above; Hide > Unselected
Artwork keeps its behavior, and a test covers both directions.

Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-31 03:34:47 +07:00
committed by bflatastic
parent a372d39a31
commit fc7ef3102f
7 changed files with 139 additions and 59 deletions
+21 -10
View File
@@ -61,8 +61,8 @@ pub fn specs() -> Vec<CommandSpec> {
"Unselected Artwork",
["Object", "Lock"],
Some("Cmd+Alt+Shift+2"),
"{} lock, 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 locks 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.unlockAll unlocks them again; disabled in isolation mode → {count}",
has_selection_outside_isolation,
"{} lock unselected artwork in one undo step, including unselected members of partly selected groups, clipping groups and compound paths, and guides while Lock Guides is on; hidden art and art on locked layers stay unlocked; a selected layer's art counts as selected; in isolation, only art inside the isolated object is affected; object.unlockAll unlocks locked art document-wide → {count}",
has_selection,
|s, _| unselected_art(s, true)
),
cmd!(
@@ -330,15 +330,24 @@ fn other_layers(s: &mut Session, lock: bool) -> Result<Value> {
Ok(json!({ "count": n }))
}
/// The objects [`super::select::walk_selectable`] visits that are not among the selected objects `roots` and hold none of
/// 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<NodeId>, selection: &[NodeId], roots: &[NodeId], guides_locked: bool) -> Vec<NodeId> {
/// Returns the objects [`super::select::walk_selectable`] visits that are not among the selected objects `roots` and
/// hold none of them. A selected layer's objects count as selected. The walk goes into a plain group, but not a Shaper
/// group, that holds a selected object. With `lock`, it also goes into a clip group or compound path that holds one,
/// and includes guide paths while `guides_locked`. Without `lock`, guide paths and the objects that hold one are left
/// out while `guides_locked`; a clip group, Shaper group, compound path and every other object that holds a selected
/// object or such a guide are left out with their members.
fn unselected(
d: &vectorcraft_doc::Document,
iso: Option<NodeId>,
selection: &[NodeId],
roots: &[NodeId],
guides_locked: bool,
lock: bool,
) -> Vec<NodeId> {
let mut roots: HashSet<NodeId> = 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<NodeId> = roots.iter().copied().collect();
if guides_locked {
if guides_locked && !lock {
super::select::walk_selectable(d, iso, |n| {
if matches!(n.kind, NodeKind::Path { guide: true, .. }) {
kept.push(n.id);
@@ -353,7 +362,9 @@ fn unselected(d: &vectorcraft_doc::Document, iso: Option<NodeId>, selection: &[N
out.push(n.id);
return false;
}
!roots.contains(&n.id) && matches!(n.kind, NodeKind::Group { clip: false, .. }) && n.extras().shaper.is_none()
!roots.contains(&n.id)
&& (matches!(n.kind, NodeKind::Group { clip: false, .. }) && n.extras().shaper.is_none()
|| lock && (matches!(n.kind, NodeKind::Group { clip: true, .. } | NodeKind::Compound { .. })))
});
out
}
@@ -362,7 +373,7 @@ fn unselected_art(s: &mut Session, lock: bool) -> Result<Value> {
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, &st.selection.objects, &roots, guides_locked);
let ids = unselected(&st.doc, st.isolation, &st.selection.objects, &roots, guides_locked, lock);
// With nothing to lock (Lock) or hide (Hide), the document and its undo history stay as they are.
if !ids.is_empty() {
s.edit(if lock { "Lock" } else { "Hide" }, |d, _| {