From 949473f1e392cc047f76329d22823887d7c04460 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Sat, 17 Oct 2026 14:33:14 +0200 Subject: [PATCH] Magic Wand: only select objects inside the isolated group (#2655) --- crates/engine/src/cmd/wand.rs | 24 +++++++++++++++++++++++- docs/target-app-parity.md | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/wand.rs b/crates/engine/src/cmd/wand.rs index a2d7613a3..d73ef9222 100644 --- a/crates/engine/src/cmd/wand.rs +++ b/crates/engine/src/cmd/wand.rs @@ -7,6 +7,7 @@ use serde_json::{Value, json}; use vectorcraft_color::Paint; use vectorcraft_doc::{Document, Node, NodeId, NodeKind}; +use super::select::selectable_ids; use super::*; /// Magic Wand panel settings. Tolerances: colours as 0–255 RGB distance, stroke weight in @@ -153,7 +154,13 @@ fn magic_wand(s: &mut Session, p: &Value) -> Result { let st = s.doc()?; let reference = st.doc.node(id).ok_or(EngineError::NoNode(id))?.clone(); let w = s.menu.wand.clone(); - let hits: Vec = paintable(&st.doc).into_iter().filter(|i| st.doc.node(*i).is_some_and(|n| w.matches(&reference, n))).collect(); + // In isolation mode only the isolated group's contents can be picked, as for Select Same. + let inside = st.isolation.map(|iso| selectable_ids(&st.doc, Some(iso))); + let hits: Vec = paintable(&st.doc) + .into_iter() + .filter(|i| inside.as_ref().is_none_or(|set| set.contains(i))) + .filter(|i| st.doc.node(*i).is_some_and(|n| w.matches(&reference, n))) + .collect(); let ids: Vec = match str_param(p, "mode").unwrap_or("set") { "add" => st.selection.objects.iter().copied().chain(hits.iter().copied().filter(|h| !st.selection.objects.contains(h))).collect(), "subtract" => st.selection.objects.iter().copied().filter(|i| !hits.contains(i)).collect(), @@ -222,4 +229,19 @@ mod tests { s.execute("select.magicWand", &json!({"id": b})).unwrap(); assert_eq!(selected(&s), [b]); } + + #[test] + fn isolation_mode_limits_the_wand_to_the_isolated_group() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 200})).unwrap(); + let a = rect(&mut s, 0.0, "#ff0000", 1.0); + let b = rect(&mut s, 20.0, "#ff0000", 1.0); + let outside = rect(&mut s, 40.0, "#ff0000", 1.0); + s.execute("select.set", &json!({"ids": [a, b]})).unwrap(); + let g = s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap(); + s.execute("object.isolate", &json!({"id": g})).unwrap(); + s.execute("select.magicWand", &json!({"id": a})).unwrap(); + assert_eq!(selected(&s), [a, b]); + assert!(!selected(&s).contains(&outside)); + } } diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 3a5c64db7..1ce8f6f66 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -497,7 +497,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur The command palette lists `select.key` as Cancel Key Object, and a pick cancels the key object (#541). Agent selection commands validate every object id before changing the selection and report the resulting count and ids (#784). Agents read the key object: `select.key` returns it, and `document.inspect` and the `ui.pointer` and `pointer_gesture` replies report it as `key`. `select.key` with an id that isn't a selected top-level object returns an error and leaves the key unchanged. - The Select menu's Same commands, its Object commands that select art, Next Object Above and Below and saved selections select only objects that Select All selects and objects inside them: nothing hidden or locked, nothing in a hidden or locked group, layer or sublayer, nothing on a template layer, and in isolation mode nothing outside the isolated group or layer. In isolation mode Select All, All on Active Artboard and Inverse select nothing while the isolated group or layer, or a group or layer it is in, is hidden, locked or a template layer. + The Select menu's Same commands, its Object commands that select art, Next Object Above and Below, saved selections and the Magic Wand (#2655) select only objects that Select All selects and objects inside them: nothing hidden or locked, nothing in a hidden or locked group, layer or sublayer, nothing on a template layer, and in isolation mode nothing outside the isolated group or layer. In isolation mode Select All, All on Active Artboard and Inverse select nothing while the isolated group or layer, or a group or layer it is in, is hidden, locked or a template layer. Align and Distribute Spacing keep a compound path or selected group that contains the key object in place, as they keep the key object itself. Anchor Align moves the selected points to their bounds or the artboard (#1180). Repeating it, or moving by at most 1e-9 pt on both axes as whole-object Align does, leaves the document, Undo, Redo and saved state unchanged; actual moves still take one undo step. `crates/engine/src/tests.rs` covers repeated alignment, rounding-only alignment, unchanged handles, saved state, command recording and Undo/Redo. A symbol instance is measured by its symbol's art, as its transform places it, for clicks, the marquee, the bounding box, the Transform panel, Align and Distribute (the art's strokes too with Use Preview Bounds), Fit to Artwork Bounds, exports of the art, thumbnails and canvas culling; one whose symbol is missing is measured as the 20 × 20 square around the origin.