mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 13:17:19 -06:00
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:
@@ -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, _| {
|
||||
|
||||
@@ -518,8 +518,9 @@ fn lock_and_hide_with_nothing_to_lock_or_hide_record_no_undo_step() {
|
||||
|
||||
/// Hide › Unselected Artwork hides, in one undo step, each object that is not selected and holds no selected object:
|
||||
/// here `b`, the member `n` of the group that holds the selected `m`, and `p` on another layer. The selected objects,
|
||||
/// that group, the clip group that holds the selected `c2` with all its members, a locked object, the art on a locked
|
||||
/// layer and an object hidden before stay as they are, and so does the selection. Show All shows them again.
|
||||
/// that group, the clip group that holds the selected `c2` with all its members, the compound path that holds the
|
||||
/// selected `hole` with both paths, a guide under Lock Guides, a locked object, the art on a locked layer and an
|
||||
/// object hidden before stay as they are, and so does the selection. Show All shows them again.
|
||||
#[test]
|
||||
fn hide_unselected_artwork_hides_every_object_that_is_not_selected() {
|
||||
let mut s = session();
|
||||
@@ -534,6 +535,14 @@ fn hide_unselected_artwork_hides_every_object_that_is_not_selected() {
|
||||
let c3 = rect(&mut s, 80.0, 0.0, 30.0, 30.0);
|
||||
sel(&mut s, &[c1, c2, c3]);
|
||||
let clip = id_of(&s.execute("object.clippingMask.make", &json!({})).unwrap());
|
||||
let outer = rect(&mut s, 0.0, 200.0, 100.0, 100.0);
|
||||
let hole = rect(&mut s, 40.0, 240.0, 20.0, 20.0);
|
||||
sel(&mut s, &[outer, hole]);
|
||||
let compound = id_of(&s.execute("object.compoundPath.make", &json!({})).unwrap());
|
||||
let guide = rect(&mut s, 200.0, 0.0, 10.0, 10.0);
|
||||
sel(&mut s, &[guide]);
|
||||
s.execute("view.guides.make", &json!({})).unwrap();
|
||||
s.execute("view.guides.lock", &json!({"locked": true})).unwrap();
|
||||
let k = rect(&mut s, 120.0, 0.0, 10.0, 10.0);
|
||||
s.execute("layer.setProps", &json!({"id": k.0, "locked": true})).unwrap();
|
||||
let h = rect(&mut s, 140.0, 0.0, 10.0, 10.0);
|
||||
@@ -544,13 +553,13 @@ fn hide_unselected_artwork_hides_every_object_that_is_not_selected() {
|
||||
let q = rect(&mut s, 20.0, 100.0, 10.0, 10.0);
|
||||
s.execute("layer.setProps", &json!({"id": locked_layer.0, "locked": true})).unwrap();
|
||||
let shown = |s: &Session, ids: &[NodeId]| ids.iter().map(|id| node(s, *id).visible).collect::<Vec<_>>();
|
||||
sel(&mut s, &[a, m, c2]);
|
||||
sel(&mut s, &[a, m, c2, hole]);
|
||||
let steps = undo_len(&s);
|
||||
assert_eq!(s.execute("object.hide.unselected", &json!({})).unwrap(), json!({"count": 3}));
|
||||
assert_eq!(shown(&s, &[b, n, p, h]), [false; 4]);
|
||||
assert_eq!(shown(&s, &[a, m, g, c1, c2, c3, clip, k, q]), [true; 9]);
|
||||
assert_eq!(shown(&s, &[a, m, g, c1, c2, c3, clip, compound, outer, hole, guide, k, q]), [true; 13]);
|
||||
assert!(node(&s, k).locked);
|
||||
assert_eq!((undo_len(&s), selected(&s)), (steps + 1, vec![a, m, c2]));
|
||||
assert_eq!((undo_len(&s), selected(&s)), (steps + 1, vec![a, m, c2, hole]));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(shown(&s, &[b, n, p]), [true; 3]);
|
||||
s.execute("edit.redo", &json!({})).unwrap();
|
||||
@@ -650,11 +659,10 @@ fn hide_unselected_artwork_keeps_groups_with_locked_guides_and_the_selected_laye
|
||||
assert_eq!(selected(&s), [other]);
|
||||
}
|
||||
|
||||
/// Lock › Unselected Artwork locks, in one undo step labeled "Lock", each object that is not selected and holds no
|
||||
/// selected object: here `b`, the member `n` of the group that holds the selected `m`, and `p` on another layer. The
|
||||
/// selected objects, that group, the clip group that holds the selected `c2` with all its members, an object hidden
|
||||
/// before and the art on a locked layer stay unlocked, and the selection stays as it is. Unlock All unlocks them again
|
||||
/// with the object locked before, and selects them.
|
||||
/// Lock Unselected Artwork locks `b`, `n` in the group that holds selected `m`, `p` on another layer, and the clip
|
||||
/// group's unselected members `c1` and clipping path `c3` in one undo step labeled "Lock". The clip group container,
|
||||
/// `c2`, the selected objects, group `g`, hidden object `h` and art `q` on a locked layer stay unlocked. `k` was locked
|
||||
/// before. The selection stays as it is. Unlock All unlocks all six locked objects and selects them.
|
||||
#[test]
|
||||
fn lock_unselected_artwork_locks_every_object_that_is_not_selected() {
|
||||
let mut s = session();
|
||||
@@ -681,29 +689,28 @@ fn lock_unselected_artwork_locks_every_object_that_is_not_selected() {
|
||||
let locked = |s: &Session, ids: &[NodeId]| ids.iter().map(|id| node(s, *id).locked).collect::<Vec<_>>();
|
||||
sel(&mut s, &[a, m, c2]);
|
||||
let steps = undo_len(&s);
|
||||
assert_eq!(s.execute("object.lock.unselected", &json!({})).unwrap(), json!({"count": 3}));
|
||||
assert_eq!(locked(&s, &[b, n, p, k]), [true; 4]);
|
||||
assert_eq!(locked(&s, &[a, m, g, c1, c2, c3, clip, h, q]), [false; 9]);
|
||||
assert_eq!(s.execute("object.lock.unselected", &json!({})).unwrap(), json!({"count": 5}));
|
||||
assert_eq!(locked(&s, &[b, n, p, c1, c3, k]), [true; 6]);
|
||||
assert_eq!(locked(&s, &[a, m, g, c2, clip, h, q]), [false; 7]);
|
||||
assert_eq!((undo_len(&s), selected(&s)), (steps + 1, vec![a, m, c2]));
|
||||
assert_eq!(s.doc().unwrap().history.undo.last().unwrap().label, "Lock");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(locked(&s, &[b, n, p]), [false; 3]);
|
||||
s.execute("edit.redo", &json!({})).unwrap();
|
||||
assert_eq!(locked(&s, &[b, n, p]), [true; 3]);
|
||||
assert_eq!(s.execute("object.unlockAll", &json!({})).unwrap(), json!({"count": 4}));
|
||||
assert_eq!(locked(&s, &[b, n, p, k]), [false; 4]);
|
||||
assert_eq!(s.execute("object.unlockAll", &json!({})).unwrap(), json!({"count": 6}));
|
||||
assert_eq!(locked(&s, &[b, n, p, c1, c3, k]), [false; 6]);
|
||||
let mut unlocked = selected(&s);
|
||||
unlocked.sort();
|
||||
let mut want = vec![b, n, p, k];
|
||||
let mut want = vec![b, n, p, c1, c3, k];
|
||||
want.sort();
|
||||
assert_eq!(unlocked, want);
|
||||
}
|
||||
|
||||
/// In isolation mode Lock › Unselected Artwork is disabled: the menu item is off, and the command returns an error that
|
||||
/// names isolation mode and changes nothing. While Lock Guides is on it leaves the guides unlocked. With nothing left to
|
||||
/// lock it records no undo step and returns a count of 0. With Lock Guides off it locks a guide path like other art.
|
||||
/// Lock Unselected Artwork affects only the isolated group's other members. Lock Guides does not protect an unselected
|
||||
/// guide from this command. With nothing left to lock it records no undo step.
|
||||
#[test]
|
||||
fn lock_unselected_artwork_keeps_locked_guides_and_is_disabled_in_isolation_mode() {
|
||||
fn lock_unselected_artwork_scopes_isolation_and_locks_guides() {
|
||||
let mut s = session();
|
||||
let guide = rect(&mut s, 0.0, 0.0, 10.0, 10.0);
|
||||
sel(&mut s, &[guide]);
|
||||
@@ -718,26 +725,83 @@ fn lock_unselected_artwork_keeps_locked_guides_and_is_disabled_in_isolation_mode
|
||||
let locked = |s: &Session, ids: &[NodeId]| ids.iter().map(|id| node(s, *id).locked).collect::<Vec<_>>();
|
||||
s.execute("object.isolate", &json!({"id": g.0})).unwrap();
|
||||
select_with_a_step_to_redo(&mut s, &[m]);
|
||||
let before = history(&s);
|
||||
let info = find_command("object.lock.unselected").unwrap().info(&s);
|
||||
assert!(!info.enabled && info.disabled_reason.as_deref() == Some("exit isolation mode first"), "{info:?}");
|
||||
let e = s.execute("object.lock.unselected", &json!({})).unwrap_err();
|
||||
assert!(matches!(e, EngineError::Disabled(..)) && e.to_string().contains("isolation mode"), "{e}");
|
||||
assert_eq!(locked(&s, &[m, n, out, guide]), [false; 4]);
|
||||
assert_eq!((history(&s), selected(&s)), (before, vec![m]));
|
||||
assert!(info.enabled, "{info:?}");
|
||||
let before = undo_len(&s);
|
||||
assert_eq!(lock(&mut s), json!({"count": 1}));
|
||||
assert_eq!(locked(&s, &[m, n, out, guide]), [false, true, false, false]);
|
||||
assert_eq!((undo_len(&s), selected(&s)), (before + 1, vec![m]));
|
||||
s.execute("object.exitIsolation", &json!({})).unwrap();
|
||||
sel(&mut s, &[m]);
|
||||
assert!(find_command("object.lock.unselected").unwrap().info(&s).enabled);
|
||||
assert_eq!(lock(&mut s), json!({"count": 2}));
|
||||
assert_eq!(locked(&s, &[n, out, guide, g, m]), [true, true, false, false, false]);
|
||||
assert_eq!(locked(&s, &[n, out, guide, g, m]), [true, true, true, false, false]);
|
||||
select_with_a_step_to_redo(&mut s, &[m]);
|
||||
let before = history(&s);
|
||||
assert_eq!(lock(&mut s), json!({"count": 0}));
|
||||
assert_eq!(history(&s), before);
|
||||
s.execute("view.guides.lock", &json!({"locked": false})).unwrap();
|
||||
sel(&mut s, &[m]);
|
||||
assert_eq!(lock(&mut s), json!({"count": 1}));
|
||||
assert_eq!(locked(&s, &[guide]), [true]);
|
||||
}
|
||||
|
||||
/// Lock Unselected Artwork locks the unselected path of a partly selected compound path, but needs a selection.
|
||||
#[test]
|
||||
fn lock_unselected_artwork_descends_into_compound_and_needs_selection() {
|
||||
let mut s = session();
|
||||
let outer = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
let hole = rect(&mut s, 40.0, 40.0, 20.0, 20.0);
|
||||
sel(&mut s, &[outer, hole]);
|
||||
let compound = id_of(&s.execute("object.compoundPath.make", &json!({})).unwrap());
|
||||
s.execute("select.none", &json!({})).unwrap();
|
||||
let before = history(&s);
|
||||
assert!(!find_command("object.lock.unselected").unwrap().info(&s).enabled);
|
||||
assert!(matches!(s.execute("object.lock.unselected", &json!({})), Err(EngineError::Disabled(..))));
|
||||
assert_eq!(history(&s), before);
|
||||
sel(&mut s, &[hole]);
|
||||
assert_eq!(s.execute("object.lock.unselected", &json!({})).unwrap(), json!({"count": 1}));
|
||||
assert!(node(&s, outer).locked);
|
||||
assert!(!node(&s, hole).locked);
|
||||
assert!(!node(&s, compound).locked);
|
||||
assert_eq!(selected(&s), [hole]);
|
||||
}
|
||||
|
||||
/// Lock Unselected Artwork leaves all art on a selected layer unlocked and locks unselected art on another layer.
|
||||
#[test]
|
||||
fn lock_unselected_artwork_keeps_a_selected_layers_art() {
|
||||
let mut s = session();
|
||||
let selected_layer = id_of(&s.execute("layer.new", &json!({})).unwrap());
|
||||
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);
|
||||
s.execute("layer.new", &json!({})).unwrap();
|
||||
let other = rect(&mut s, 40.0, 0.0, 10.0, 10.0);
|
||||
sel(&mut s, &[selected_layer]);
|
||||
assert_eq!(s.execute("object.lock.unselected", &json!({})).unwrap(), json!({"count": 1}));
|
||||
assert_eq!([a, b, other].map(|id| node(&s, id).locked), [false, false, true]);
|
||||
assert_eq!(selected(&s), [selected_layer]);
|
||||
}
|
||||
|
||||
/// In a plain group, Lock and Hide Unselected Artwork affect the same members of identical documents.
|
||||
#[test]
|
||||
fn lock_and_hide_unselected_artwork_choose_the_same_objects() {
|
||||
let prepare = || {
|
||||
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 m = rect(&mut s, 40.0, 0.0, 10.0, 10.0);
|
||||
let n = rect(&mut s, 60.0, 0.0, 10.0, 10.0);
|
||||
sel(&mut s, &[m, n]);
|
||||
let g = id_of(&s.execute("object.group", &json!({})).unwrap());
|
||||
sel(&mut s, &[a, m]);
|
||||
(s, [a, b, m, n, g])
|
||||
};
|
||||
let (mut locked, lock_ids) = prepare();
|
||||
let (mut hidden, hide_ids) = prepare();
|
||||
assert_eq!(lock_ids, hide_ids);
|
||||
assert_eq!(locked.execute("object.lock.unselected", &json!({})).unwrap(), json!({"count": 2}));
|
||||
assert_eq!(hidden.execute("object.hide.unselected", &json!({})).unwrap(), json!({"count": 2}));
|
||||
let changed_by_lock: Vec<_> = lock_ids.into_iter().filter(|id| node(&locked, *id).locked).collect();
|
||||
let changed_by_hide: Vec<_> = hide_ids.into_iter().filter(|id| !node(&hidden, *id).visible).collect();
|
||||
assert_eq!(changed_by_lock, changed_by_hide);
|
||||
assert_eq!(changed_by_lock, [lock_ids[1], lock_ids[3]]);
|
||||
}
|
||||
|
||||
// ---------- Transform Each ----------
|
||||
|
||||
@@ -677,7 +677,11 @@ fn lock_and_hide_menus_are_greyed_out_in_isolation_mode() {
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
let a = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap()["id"].clone();
|
||||
let b = app.run("shape.rectangle", json!({"x": 80, "y": 10, "width": 50, "height": 50})).unwrap()["id"].clone();
|
||||
app.run("select.none", json!({})).unwrap();
|
||||
let lock_unselected = |app: &VectorcraftApp| crate::menus::enabled(app, "object.lock.unselected");
|
||||
assert!(!lock_unselected(&app), "nothing selected");
|
||||
app.run("select.set", json!({"ids": [a, b.clone()]})).unwrap();
|
||||
assert!(lock_unselected(&app), "selection present");
|
||||
let g = app.run("object.group", json!({})).unwrap()["id"].clone();
|
||||
let lock_and_hide: Vec<String> = menu_entries(&app)
|
||||
.into_iter()
|
||||
@@ -712,6 +716,7 @@ fn lock_and_hide_menus_are_greyed_out_in_isolation_mode() {
|
||||
app.run("object.isolate", json!({"id": g.clone()})).unwrap();
|
||||
app.run("select.set", json!({"ids": [b]})).unwrap();
|
||||
assert_eq!(shown(&app), [false, false, false, false, false, false, false, false, false, true], "in isolation mode");
|
||||
assert!(lock_unselected(&app), "shortcut command remains enabled in isolation");
|
||||
app.run("object.exitIsolation", json!({})).unwrap();
|
||||
app.run("select.set", json!({"ids": [g]})).unwrap();
|
||||
assert_eq!(shown(&app), [true; 10], "after Exit Isolation Mode");
|
||||
|
||||
@@ -165,12 +165,7 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec<Item>)> {
|
||||
c("Ungroup", "object.ungroup"),
|
||||
sub(
|
||||
"Lock",
|
||||
vec![
|
||||
c("Selection", "object.lock"),
|
||||
c("All Artwork Above", "object.lock.above"),
|
||||
c("Other Layers", "object.lock.otherLayers"),
|
||||
c("Unselected Artwork", "object.lock.unselected"),
|
||||
],
|
||||
vec![c("Selection", "object.lock"), c("All Artwork Above", "object.lock.above"), c("Other Layers", "object.lock.otherLayers")],
|
||||
),
|
||||
c("Unlock All", "object.unlockAll"),
|
||||
sub(
|
||||
|
||||
@@ -336,22 +336,25 @@ fn cmd_alt_shift_j_opens_average_and_join() {
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.str("to")).as_deref(), Some("smooth"), "the choice confirmed last");
|
||||
}
|
||||
|
||||
/// Cmd+Alt+Shift+2 is Object › Lock › Unselected Artwork: it locks the object that is not
|
||||
/// selected. Cmd+Alt+2 still unlocks all, and Cmd+2 still locks the selection. In isolation mode
|
||||
/// the item is disabled.
|
||||
/// Cmd+Alt+Shift+2 locks unselected artwork, including inside an isolated group.
|
||||
#[test]
|
||||
fn cmd_alt_shift_2_locks_unselected_artwork() {
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
|
||||
assert_eq!(crate::menus::shortcut_of("object.lock.unselected"), Some("Cmd+Alt+Shift+2"));
|
||||
assert_eq!(crate::menus::shortcut_of("object.hide.unselected"), Some("Cmd+Alt+Shift+3"));
|
||||
assert!(!crate::menus::menu_tree_named(true).iter().any(|(_, items)| format!("{items:?}").contains("object.lock.unselected")));
|
||||
let rect = |app: &mut VectorcraftApp, x: f64| {
|
||||
let r = app.session.execute("shape.rectangle", &json!({"x": x, "y": 10, "width": 20, "height": 20})).unwrap();
|
||||
vectorcraft_doc::NodeId(r["id"].as_u64().unwrap())
|
||||
};
|
||||
let (a, b) = (rect(&mut app, 10.0), rect(&mut app, 50.0));
|
||||
app.session.execute("select.none", &json!({})).unwrap();
|
||||
assert!(!crate::menus::enabled(&app, "object.lock.unselected"), "nothing selected");
|
||||
let locked = |app: &VectorcraftApp| [a, b].map(|id| app.session.doc().unwrap().doc.node(id).unwrap().locked);
|
||||
let two = |modifiers| egui::Event::Key { key: Key::Num2, physical_key: None, pressed: true, repeat: false, modifiers };
|
||||
app.session.execute("select.set", &json!({"ids": [a.0]})).unwrap();
|
||||
assert!(crate::menus::enabled(&app, "object.lock.unselected"), "selection present");
|
||||
frame(&mut app, vec![two(Modifiers::COMMAND | Modifiers::ALT | Modifiers::SHIFT)]);
|
||||
assert_eq!(locked(&app), [false, true]);
|
||||
frame(&mut app, vec![two(Modifiers::COMMAND | Modifiers::ALT)]);
|
||||
@@ -359,15 +362,15 @@ fn cmd_alt_shift_2_locks_unselected_artwork() {
|
||||
app.session.execute("select.set", &json!({"ids": [a.0]})).unwrap();
|
||||
frame(&mut app, vec![two(Modifiers::COMMAND)]);
|
||||
assert_eq!(locked(&app), [true, false], "Cmd+2 locks the selection");
|
||||
// In isolation mode the menu item is off, and the shortcut locks nothing.
|
||||
// The shortcut uses the isolated group as its scope.
|
||||
app.session.execute("object.unlockAll", &json!({})).unwrap();
|
||||
app.session.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
|
||||
let g = app.session.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap();
|
||||
app.session.execute("object.isolate", &json!({"id": g})).unwrap();
|
||||
app.session.execute("select.set", &json!({"ids": [a.0]})).unwrap();
|
||||
assert!(!crate::menus::enabled(&app, "object.lock.unselected"));
|
||||
assert!(crate::menus::enabled(&app, "object.lock.unselected"));
|
||||
frame(&mut app, vec![two(Modifiers::COMMAND | Modifiers::ALT | Modifiers::SHIFT)]);
|
||||
assert_eq!(locked(&app), [false, false], "nothing is locked in isolation mode");
|
||||
assert_eq!(locked(&app), [false, true], "unselected member locks in isolation mode");
|
||||
}
|
||||
|
||||
/// Cmd+Shift+B while the Type tool edits text is Type › Bold, not Hide Bounding Box (#724).
|
||||
|
||||
+7
-5
@@ -1398,11 +1398,13 @@ selected. While Lock Guides is on, the guide paths count as selected and stay as
|
||||
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.
|
||||
|
||||
`object.lock.unselected` (Object › Lock › Unselected Artwork, Cmd+Alt+Shift+2) locks, in one undo step, the objects
|
||||
that `object.hide.unselected` hides, and returns `{count}`. Hidden objects stay unlocked. The selection stays as it
|
||||
is, and `object.unlockAll` unlocks the objects again and selects them. With nothing to lock 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.
|
||||
`object.lock.unselected` (Cmd+Alt+Shift+2; shortcut and command, with no Object › Lock menu item) locks unselected
|
||||
artwork in one undo step and returns `{count}`. In a partly selected group, clipping group, or compound path, it locks
|
||||
the unselected members and leaves the container and selected members unlocked. It includes unselected guides even
|
||||
while Lock Guides is on. Hidden art and art on locked layers stay individually unlocked; a selected layer's artwork
|
||||
counts as selected. In ordinary isolation mode it locks only unselected art inside the isolated object. The selection
|
||||
stays as it is. `object.unlockAll` unlocks and selects all locked artwork in the document, including art locked before
|
||||
this command. With nothing to lock it records no undo step.
|
||||
|
||||
These Select menu commands select only the objects that `select.all` selects and the objects inside them: every
|
||||
`select.same.*` and `select.object.*` command except `select.object.slices`, `select.nextAbove`, `select.nextBelow`
|
||||
|
||||
@@ -962,8 +962,8 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
The Libraries panel holds gradients and patterns: its + button adds the selection's fill or stroke gradient or pattern, a gradient or pattern swatch dragged from the Swatches panel is added, and a click paints the selection through the active Fill/Stroke proxy, a pattern bringing its definition and swatch into the document.
|
||||
Object › Lock › All Artwork Above and Object › Hide › All Artwork Above deselect the selected objects they lock or hide, the key object included.
|
||||
Object › Hide › Unselected Artwork (Cmd/Ctrl+Alt+Shift+3, `object.hide.unselected`) hides, in one undo step, every object Select All takes that is not selected and holds no selected object; the members of a group that holds a selected object are hidden one by one, and Object › Show All shows them again. It is disabled in isolation mode. Public references: [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html), "Hide unselected artwork"; [How to lock, hide, and delete objects in Illustrator](https://web.archive.org/web/20220303070637/https://helpx.adobe.com/illustrator/using/locking-hiding-deleting-objects.html), which says the Show All and Hide menus are unavailable in isolation mode.
|
||||
In isolation mode, the items of Object › Lock and Object › Hide, Unlock All and Show All are greyed out ([lock, hide, and delete objects](https://web.archive.org/web/20220303070637/https://helpx.adobe.com/illustrator/using/locking-hiding-deleting-objects.html)); the Contextual Task Bar's Lock button is greyed out too, the Properties panel's Quick Actions have no Lock, and the commands return an error. Pattern editing mode and opacity mask editing do not count as isolation mode for them.
|
||||
Object › Lock › Unselected Artwork (Cmd/Ctrl+Alt+Shift+2, `object.lock.unselected`) locks, in one undo step, the objects that Hide › Unselected Artwork hides, and Object › Unlock All unlocks them again. It is disabled in isolation mode. Public references: [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html), "Lock all deselected artwork"; [How to lock, hide, and delete objects in Illustrator](https://web.archive.org/web/20220303070637/https://helpx.adobe.com/illustrator/using/locking-hiding-deleting-objects.html), "The Lock menus are disabled when you’re in isolation mode."
|
||||
Cmd/Ctrl+Alt+Shift+2 (`object.lock.unselected`) locks unselected artwork in one undo step and has no Object › Lock menu item. It locks unselected members of partly selected groups, clipping groups and compound paths, and unselected guides while Lock Guides is on. In ordinary isolation mode it locks only unselected art inside the isolated object. Object › Unlock All unlocks all locked art in the document. The [public shortcut page](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) calls the action "Lock all deselected artwork".
|
||||
In isolation mode, the visible items of Object › Lock and Object › Hide, Unlock All and Show All are greyed out ([lock, hide, and delete objects](https://web.archive.org/web/20220303070637/https://helpx.adobe.com/illustrator/using/locking-hiding-deleting-objects.html)); the shortcut-only `object.lock.unselected` command remains enabled with a selection. The Contextual Task Bar's Lock button is greyed out too, the Properties panel's Quick Actions have no Lock, and the other Lock and Hide commands return an error. Pattern editing mode and opacity mask editing do not count as isolation mode for them.
|
||||
Liquify (its brush Width, Height and Angle, Intensity, Detail, Simplify, Twirl Rate, Complexity, Horizontal and Vertical), Puppet Warp (its Expand and each pin's angle), Transform in Perspective (its depth), Nudge in Perspective (its direction) and Move Plane (its location or distance) refuse a value other than a number or null, such as "40", Draw in Perspective refuses a click point that is not two numbers, and Width Profile refuses a point that is not three numbers; each leaves the document and its undo history as they were.
|
||||
Object › Lock › All Artwork Above and Object › Hide › All Artwork Above go through each top-level layer once, checking each object that is not selected against the selected objects below it: with 100 tiles selected under 25,000 rectangles, Lock took 6.4 s and now takes under 0.1 s, and with all of 5,001 objects selected it took 8.8 s and now takes under 0.1 s.
|
||||
Object › Unlock All and Object › Show All clicked in the in-window menu bar with Shift+Alt (Shift+Option) held unlock or show only the objects inside the groups that hold the selected objects ([lock, hide, and delete objects](https://web.archive.org/web/20230623025912/https://helpx.adobe.com/illustrator/using/locking-hiding-deleting-objects.html)); `object.unlockAll` and `object.showAll` take `scope: "group"` for it.
|
||||
|
||||
Reference in New Issue
Block a user