From 79b5d73ca7bb7e8bbb0dc187e07aff1a855f60ca Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Tue, 13 Oct 2026 02:05:12 +0200 Subject: [PATCH] Guides: Lock Guides locks the paths made into guides too (part of #1534) --- crates/engine/src/cmd/docmenu.rs | 6 ++++-- crates/engine/src/lib.rs | 13 +++++++++++++ crates/engine/src/tests_menucmds.rs | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/docmenu.rs b/crates/engine/src/cmd/docmenu.rs index dd7c317b..174fc5b0 100644 --- a/crates/engine/src/cmd/docmenu.rs +++ b/crates/engine/src/cmd/docmenu.rs @@ -489,8 +489,10 @@ fn release_guides(s: &mut Session, _: &Value) -> Result { fn lock_guides(s: &mut Session, p: &Value) -> Result { let v = p.get("locked").and_then(Value::as_bool).unwrap_or(!s.menu.guides_locked); s.menu.guides_locked = v; - // Locked guides can't stay selected. - if v && !s.doc()?.selection.guides.is_empty() { + // Locked guides can't stay selected, ruler guides or guide paths ([`Session::select`] lets + // those go). + let st = s.doc()?; + if v && (!st.selection.guides.is_empty() || st.selection.objects.iter().any(|id| crate::is_guide_path(&st.doc, *id))) { s.select(|_, sel| sel.guides.clear())?; } Ok(json!({ "locked": v })) diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 9b2ea581..1658e831 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -578,6 +578,11 @@ fn strip_replay_only(id: &str, p: &mut Value) { } } +/// Is `id` a path made into a guide (View → Guides → Make Guides)? +pub(crate) fn is_guide_path(d: &Document, id: NodeId) -> bool { + d.node(id).is_some_and(|n| matches!(n.kind, NodeKind::Path { guide: true, .. })) +} + /// Cheap sanity check after an edit: artboards and the objects just touched (the selection) must /// have finite, in-range geometry, so saved files always reload and renderers never see NaN/∞. fn doc_sane(d: &Document, sel: &Selection) -> bool { @@ -1482,10 +1487,18 @@ impl Session { /// Change only the selection (not an undo step). pub fn select(&mut self, f: impl FnOnce(&Document, &mut Selection)) -> Result<()> { self.active_appearance_item = None; + let guides_locked = self.menu.guides_locked; let st = self.doc_mut()?; let before = st.selection.objects.clone(); f(&st.doc, &mut st.selection); st.selection.prune(&st.doc); + // Lock Guides locks the paths made into guides too (#1534), whatever selects them. + if guides_locked { + let locked: Vec = st.selection.objects.iter().copied().filter(|id| is_guide_path(&st.doc, *id)).collect(); + for id in locked { + st.selection.remove(id); + } + } // Selecting art makes its layer (or sublayer) the current one, as in the Layers panel of // the reference app: new art then goes beside it. if st.selection.objects != before diff --git a/crates/engine/src/tests_menucmds.rs b/crates/engine/src/tests_menucmds.rs index 76f0e7a3..ba547acc 100644 --- a/crates/engine/src/tests_menucmds.rs +++ b/crates/engine/src/tests_menucmds.rs @@ -1249,6 +1249,26 @@ fn ruler_guides_and_lock() { assert!(s.execute("guide.remove", &json!({"index": 0})).is_err()); } +/// #1534: Lock Guides locks the paths made into guides too: they can't be selected (so neither +/// moved, reshaped nor deleted), by a click, a marquee or Select All, and locking lets them go. +#[test] +fn locked_guides_lock_guide_paths() { + let mut s = session(); + let (a, b) = (rect(&mut s, 0.0, 0.0, 10.0, 10.0), rect(&mut s, 20.0, 20.0, 10.0, 10.0)); + sel(&mut s, &[a]); + s.execute("view.guides.make", &json!({})).unwrap(); + s.execute("view.guides.lock", &json!({"locked": true})).unwrap(); + sel(&mut s, &[a, b]); + assert_eq!(selected(&s), vec![b]); + s.execute("select.all", &json!({})).unwrap(); + assert_eq!(selected(&s), vec![b]); + s.execute("view.guides.lock", &json!({"locked": false})).unwrap(); + sel(&mut s, &[a, b]); + assert_eq!(selected(&s), vec![a, b]); + s.execute("view.guides.lock", &json!({"locked": true})).unwrap(); + assert_eq!(selected(&s), vec![b]); +} + fn guides(s: &Session) -> Vec<(bool, f64)> { s.doc().unwrap().doc.guides.iter().map(|g| (g.vertical, g.pos)).collect() }